]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/g_triggers.qc
Merge remote branch 'origin/master' into samual/mutator_ctf
[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 if(other.damagedbytriggers)
470         {
471                 if(other.takedamage)
472                 {
473                         EXACTTRIGGER_TOUCH;
474                         Damage(other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
475                 }
476         }
477         else
478         {
479                 if (!other.owner)
480                 {
481                         if (other.classname == "rune")                  // reset runes
482                         {
483                                 EXACTTRIGGER_TOUCH;
484                                 other.nextthink = min(other.nextthink, time + 1);
485                         }
486                 }
487         }
488
489         return;
490 }
491
492 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
493 Any object touching this will be hurt
494 set dmg to damage amount
495 defalt dmg = 5
496 */
497 .entity trigger_hurt_next;
498 entity trigger_hurt_last;
499 entity trigger_hurt_first;
500 void spawnfunc_trigger_hurt()
501 {
502         EXACTTRIGGER_INIT;
503         self.active = ACTIVE_ACTIVE;
504         self.touch = trigger_hurt_touch;
505         self.use = trigger_hurt_use;
506         self.enemy = world; // I hate you all
507         if (!self.dmg)
508                 self.dmg = 1000;
509         if (!self.message)
510                 self.message = "was in the wrong place";
511         if (!self.message2)
512                 self.message2 = "was thrown into a world of hurt by";
513         // self.message = "someone like %s always gets wrongplaced";
514
515         if(!trigger_hurt_first)
516                 trigger_hurt_first = self;
517         if(trigger_hurt_last)
518                 trigger_hurt_last.trigger_hurt_next = self;
519         trigger_hurt_last = self;
520 }
521
522 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
523 {
524         entity th;
525
526         for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
527                 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
528                         return TRUE;
529
530         return FALSE;
531 }
532
533 //////////////////////////////////////////////////////////////
534 //
535 //
536 //
537 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
538 //
539 //////////////////////////////////////////////////////////////
540
541 .float triggerhealtime;
542 void trigger_heal_touch()
543 {
544         if (self.active != ACTIVE_ACTIVE) 
545                 return;
546         
547         // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
548         if (other.iscreature)
549         {
550                 if (other.takedamage)
551                 if (other.triggerhealtime < time)
552                 {
553                         EXACTTRIGGER_TOUCH;
554                         other.triggerhealtime = time + 1;
555                         
556                         if (other.health < self.max_health)
557                         {
558                                 other.health = min(other.health + self.health, self.max_health);
559                                 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
560                                 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
561                         }
562                 }
563         }
564 }
565
566 void spawnfunc_trigger_heal()
567 {
568         self.active = ACTIVE_ACTIVE;
569         
570         EXACTTRIGGER_INIT;
571         self.touch = trigger_heal_touch;
572         if (!self.health)
573                 self.health = 10;
574         if (!self.max_health)
575                 self.max_health = 200; //Max health topoff for field
576         if(self.noise == "")
577                 self.noise = "misc/mediumhealth.wav";
578         precache_sound(self.noise);
579 }
580
581
582 //////////////////////////////////////////////////////////////
583 //
584 //
585 //
586 //End trigger_heal
587 //
588 //////////////////////////////////////////////////////////////
589
590 .entity trigger_gravity_check;
591 void trigger_gravity_remove(entity own)
592 {
593         if(own.trigger_gravity_check.owner == own)
594         {
595                 UpdateCSQCProjectile(own);
596                 own.gravity = own.trigger_gravity_check.gravity;
597                 remove(own.trigger_gravity_check);
598         }
599         else
600                 backtrace("Removing a trigger_gravity_check with no valid owner");
601         own.trigger_gravity_check = world;
602 }
603 void trigger_gravity_check_think()
604 {
605         // This spawns when a player enters the gravity zone and checks if he left.
606         // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
607         // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
608         if(self.count <= 0)
609         {
610                 if(self.owner.trigger_gravity_check == self)
611                         trigger_gravity_remove(self.owner);
612                 else
613                         remove(self);
614                 return;
615         }
616         else
617         {
618                 self.count -= 1;
619                 self.nextthink = time;
620         }
621 }
622
623 void trigger_gravity_use()
624 {
625         self.state = !self.state;
626 }
627
628 void trigger_gravity_touch()
629 {
630         float g;
631
632         if(self.state != TRUE)
633                 return;
634
635         EXACTTRIGGER_TOUCH;
636
637         g = self.gravity;
638
639         if not(self.spawnflags & 1)
640         {
641                 if(other.trigger_gravity_check)
642                 {
643                         if(self == other.trigger_gravity_check.enemy)
644                         {
645                                 // same?
646                                 other.trigger_gravity_check.count = 2; // gravity one more frame...
647                                 return;
648                         }
649
650                         // compare prio
651                         if(self.cnt > other.trigger_gravity_check.enemy.cnt)
652                                 trigger_gravity_remove(other);
653                         else
654                                 return;
655                 }
656                 other.trigger_gravity_check = spawn();
657                 other.trigger_gravity_check.enemy = self;
658                 other.trigger_gravity_check.owner = other;
659                 other.trigger_gravity_check.gravity = other.gravity;
660                 other.trigger_gravity_check.think = trigger_gravity_check_think;
661                 other.trigger_gravity_check.nextthink = time;
662                 other.trigger_gravity_check.count = 2;
663                 if(other.gravity)
664                         g *= other.gravity;
665         }
666
667         if (other.gravity != g)
668         {
669                 other.gravity = g;
670                 if(self.noise != "")
671                         sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
672                 UpdateCSQCProjectile(self.owner);
673         }
674 }
675
676 void spawnfunc_trigger_gravity()
677 {
678         if(self.gravity == 1)
679                 return;
680
681         EXACTTRIGGER_INIT;
682         self.touch = trigger_gravity_touch;
683         if(self.noise != "")
684                 precache_sound(self.noise);
685
686         self.state = TRUE;
687         IFTARGETED
688         {
689                 self.use = trigger_gravity_use;
690                 if(self.spawnflags & 2)
691                         self.state = FALSE;
692         }
693 }
694
695 //=============================================================================
696
697 // TODO add a way to do looped sounds with sound(); then complete this entity
698 .float volume, atten;
699 void target_speaker_use_off();
700 void target_speaker_use_activator()
701 {
702         if(clienttype(activator) != CLIENTTYPE_REAL)
703                 return;
704         string snd;
705         if(substring(self.noise, 0, 1) == "*")
706         {
707                 var .string sample;
708                 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
709                 if(GetPlayerSoundSampleField_notFound)
710                         snd = "misc/null.wav";
711                 else if(activator.sample == "")
712                         snd = "misc/null.wav";
713                 else
714                 {
715                         tokenize_console(activator.sample);
716                         float n;
717                         n = stof(argv(1));
718                         if(n > 0)
719                                 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
720                         else
721                                 snd = strcat(argv(0), ".wav"); // randomization
722                 }
723         }
724         else
725                 snd = self.noise;
726         msg_entity = activator;
727         soundto(MSG_ONE, self, CH_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
728 }
729 void target_speaker_use_on()
730 {
731         string snd;
732         if(substring(self.noise, 0, 1) == "*")
733         {
734                 var .string sample;
735                 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
736                 if(GetPlayerSoundSampleField_notFound)
737                         snd = "misc/null.wav";
738                 else if(activator.sample == "")
739                         snd = "misc/null.wav";
740                 else
741                 {
742                         tokenize_console(activator.sample);
743                         float n;
744                         n = stof(argv(1));
745                         if(n > 0)
746                                 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
747                         else
748                                 snd = strcat(argv(0), ".wav"); // randomization
749                 }
750         }
751         else
752                 snd = self.noise;
753         sound(self, CH_TRIGGER_SINGLE, snd, VOL_BASE * self.volume, self.atten);
754         if(self.spawnflags & 3)
755                 self.use = target_speaker_use_off;
756 }
757 void target_speaker_use_off()
758 {
759         sound(self, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_BASE * self.volume, self.atten);
760         self.use = target_speaker_use_on;
761 }
762 void target_speaker_reset()
763 {
764         if(self.spawnflags & 1) // LOOPED_ON
765         {
766                 if(self.use == target_speaker_use_on)
767                         target_speaker_use_on();
768         }
769         else if(self.spawnflags & 2)
770         {
771                 if(self.use == target_speaker_use_off)
772                         target_speaker_use_off();
773         }
774 }
775
776 void spawnfunc_target_speaker()
777 {
778         // TODO: "*" prefix to sound file name
779         // TODO: wait and random (just, HOW? random is not a field)
780         if(self.noise)
781                 precache_sound (self.noise);
782
783         if(!self.atten && !(self.spawnflags & 4))
784         {
785                 IFTARGETED
786                         self.atten = ATTN_NORM;
787                 else
788                         self.atten = ATTN_STATIC;
789         }
790         else if(self.atten < 0)
791                 self.atten = 0;
792
793         if(!self.volume)
794                 self.volume = 1;
795
796         IFTARGETED
797         {
798                 if(self.spawnflags & 8) // ACTIVATOR
799                         self.use = target_speaker_use_activator;
800                 else if(self.spawnflags & 1) // LOOPED_ON
801                 {
802                         target_speaker_use_on();
803                         self.reset = target_speaker_reset;
804                 }
805                 else if(self.spawnflags & 2) // LOOPED_OFF
806                 {
807                         self.use = target_speaker_use_on;
808                         self.reset = target_speaker_reset;
809                 }
810                 else
811                         self.use = target_speaker_use_on;
812         }
813         else if(self.spawnflags & 1) // LOOPED_ON
814         {
815                 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
816                 remove(self);
817         }
818         else if(self.spawnflags & 2) // LOOPED_OFF
819         {
820                 objerror("This sound entity can never be activated");
821         }
822         else
823         {
824                 // Quake/Nexuiz fallback
825                 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
826                 remove(self);
827         }
828 }
829
830
831 void spawnfunc_func_stardust() {
832         self.effects = EF_STARDUST;
833 }
834
835 .string bgmscript;
836 .float bgmscriptattack;
837 .float bgmscriptdecay;
838 .float bgmscriptsustain;
839 .float bgmscriptrelease;
840 float pointparticles_SendEntity(entity to, float fl)
841 {
842         WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
843
844         // optional features to save space
845         fl = fl & 0x0F;
846         if(self.spawnflags & 2)
847                 fl |= 0x10; // absolute count on toggle-on
848         if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
849                 fl |= 0x20; // 4 bytes - saves CPU
850         if(self.waterlevel || self.count != 1)
851                 fl |= 0x40; // 4 bytes - obscure features almost never used
852         if(self.mins != '0 0 0' || self.maxs != '0 0 0')
853                 fl |= 0x80; // 14 bytes - saves lots of space
854
855         WriteByte(MSG_ENTITY, fl);
856         if(fl & 2)
857         {
858                 if(self.state)
859                         WriteCoord(MSG_ENTITY, self.impulse);
860                 else
861                         WriteCoord(MSG_ENTITY, 0); // off
862         }
863         if(fl & 4)
864         {
865                 WriteCoord(MSG_ENTITY, self.origin_x);
866                 WriteCoord(MSG_ENTITY, self.origin_y);
867                 WriteCoord(MSG_ENTITY, self.origin_z);
868         }
869         if(fl & 1)
870         {
871                 if(self.model != "null")
872                 {
873                         WriteShort(MSG_ENTITY, self.modelindex);
874                         if(fl & 0x80)
875                         {
876                                 WriteCoord(MSG_ENTITY, self.mins_x);
877                                 WriteCoord(MSG_ENTITY, self.mins_y);
878                                 WriteCoord(MSG_ENTITY, self.mins_z);
879                                 WriteCoord(MSG_ENTITY, self.maxs_x);
880                                 WriteCoord(MSG_ENTITY, self.maxs_y);
881                                 WriteCoord(MSG_ENTITY, self.maxs_z);
882                         }
883                 }
884                 else
885                 {
886                         WriteShort(MSG_ENTITY, 0);
887                         if(fl & 0x80)
888                         {
889                                 WriteCoord(MSG_ENTITY, self.maxs_x);
890                                 WriteCoord(MSG_ENTITY, self.maxs_y);
891                                 WriteCoord(MSG_ENTITY, self.maxs_z);
892                         }
893                 }
894                 WriteShort(MSG_ENTITY, self.cnt);
895                 if(fl & 0x20)
896                 {
897                         WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
898                         WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
899                 }
900                 if(fl & 0x40)
901                 {
902                         WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
903                         WriteByte(MSG_ENTITY, self.count * 16.0);
904                 }
905                 WriteString(MSG_ENTITY, self.noise);
906                 if(self.noise != "")
907                 {
908                         WriteByte(MSG_ENTITY, floor(self.atten * 64));
909                         WriteByte(MSG_ENTITY, floor(self.volume * 255));
910                 }
911                 WriteString(MSG_ENTITY, self.bgmscript);
912                 if(self.bgmscript != "")
913                 {
914                         WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
915                         WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
916                         WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
917                         WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
918                 }
919         }
920         return 1;
921 }
922
923 void pointparticles_use()
924 {
925         self.state = !self.state;
926         self.SendFlags |= 2;
927 }
928
929 void pointparticles_think()
930 {
931         if(self.origin != self.oldorigin)
932         {
933                 self.SendFlags |= 4;
934                 self.oldorigin = self.origin;
935         }
936         self.nextthink = time;
937 }
938
939 void pointparticles_reset()
940 {
941         if(self.spawnflags & 1)
942                 self.state = 1;
943         else
944                 self.state = 0;
945 }
946
947 void spawnfunc_func_pointparticles()
948 {
949         if(self.model != "")
950                 setmodel(self, self.model);
951         if(self.noise != "")
952                 precache_sound (self.noise);
953         
954         if(!self.bgmscriptsustain)
955                 self.bgmscriptsustain = 1;
956         else if(self.bgmscriptsustain < 0)
957                 self.bgmscriptsustain = 0;
958
959         if(!self.atten)
960                 self.atten = ATTN_NORM;
961         else if(self.atten < 0)
962                 self.atten = 0;
963         if(!self.volume)
964                 self.volume = 1;
965         if(!self.count)
966                 self.count = 1;
967         if(!self.impulse)
968                 self.impulse = 1;
969
970         if(!self.modelindex)
971         {
972                 setorigin(self, self.origin + self.mins);
973                 setsize(self, '0 0 0', self.maxs - self.mins);
974         }
975         if(!self.cnt)
976                 self.cnt = particleeffectnum(self.mdl);
977
978         Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
979
980         IFTARGETED
981         {
982                 self.use = pointparticles_use;
983                 self.reset = pointparticles_reset;
984                 self.reset();
985         }
986         else
987                 self.state = 1;
988         self.think = pointparticles_think;
989         self.nextthink = time;
990 }
991
992 void spawnfunc_func_sparks()
993 {
994         // self.cnt is the amount of sparks that one burst will spawn
995         if(self.cnt < 1) {
996                 self.cnt = 25.0; // nice default value
997         }
998
999         // self.wait is the probability that a sparkthink will spawn a spark shower
1000         // range: 0 - 1, but 0 makes little sense, so...
1001         if(self.wait < 0.05) {
1002                 self.wait = 0.25; // nice default value
1003         }
1004
1005         self.count = self.cnt;
1006         self.mins = '0 0 0';
1007         self.maxs = '0 0 0';
1008         self.velocity = '0 0 -1';
1009         self.mdl = "TE_SPARK";
1010         self.impulse = 10 * self.wait; // by default 2.5/sec
1011         self.wait = 0;
1012         self.cnt = 0; // use mdl
1013
1014         spawnfunc_func_pointparticles();
1015 }
1016
1017 float rainsnow_SendEntity(entity to, float sf)
1018 {
1019         WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
1020         WriteByte(MSG_ENTITY, self.state);
1021         WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
1022         WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
1023         WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
1024         WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
1025         WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
1026         WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
1027         WriteShort(MSG_ENTITY, compressShortVector(self.dest));
1028         WriteShort(MSG_ENTITY, self.count);
1029         WriteByte(MSG_ENTITY, self.cnt);
1030         return 1;
1031 }
1032
1033 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
1034 This is an invisible area like a trigger, which rain falls inside of.
1035
1036 Keys:
1037 "velocity"
1038  falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
1039 "cnt"
1040  sets color of rain (default 12 - white)
1041 "count"
1042  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1043 */
1044 void spawnfunc_func_rain()
1045 {
1046         self.dest = self.velocity;
1047         self.velocity = '0 0 0';
1048         if (!self.dest)
1049                 self.dest = '0 0 -700';
1050         self.angles = '0 0 0';
1051         self.movetype = MOVETYPE_NONE;
1052         self.solid = SOLID_NOT;
1053         SetBrushEntityModel();
1054         if (!self.cnt)
1055                 self.cnt = 12;
1056         if (!self.count)
1057                 self.count = 2000;
1058         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1059         if (self.count < 1)
1060                 self.count = 1;
1061         if(self.count > 65535)
1062                 self.count = 65535;
1063
1064         self.state = 1; // 1 is rain, 0 is snow
1065         self.Version = 1;
1066
1067         Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1068 }
1069
1070
1071 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1072 This is an invisible area like a trigger, which snow falls inside of.
1073
1074 Keys:
1075 "velocity"
1076  falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1077 "cnt"
1078  sets color of rain (default 12 - white)
1079 "count"
1080  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1081 */
1082 void spawnfunc_func_snow()
1083 {
1084         self.dest = self.velocity;
1085         self.velocity = '0 0 0';
1086         if (!self.dest)
1087                 self.dest = '0 0 -300';
1088         self.angles = '0 0 0';
1089         self.movetype = MOVETYPE_NONE;
1090         self.solid = SOLID_NOT;
1091         SetBrushEntityModel();
1092         if (!self.cnt)
1093                 self.cnt = 12;
1094         if (!self.count)
1095                 self.count = 2000;
1096         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1097         if (self.count < 1)
1098                 self.count = 1;
1099         if(self.count > 65535)
1100                 self.count = 65535;
1101
1102         self.state = 0; // 1 is rain, 0 is snow
1103         self.Version = 1;
1104
1105         Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1106 }
1107
1108
1109 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
1110
1111 .float modelscale;
1112 void misc_laser_aim()
1113 {
1114         vector a;
1115         if(self.enemy)
1116         {
1117                 if(self.spawnflags & 2)
1118                 {
1119                         if(self.enemy.origin != self.mangle)
1120                         {
1121                                 self.mangle = self.enemy.origin;
1122                                 self.SendFlags |= 2;
1123                         }
1124                 }
1125                 else
1126                 {
1127                         a = vectoangles(self.enemy.origin - self.origin);
1128                         a_x = -a_x;
1129                         if(a != self.mangle)
1130                         {
1131                                 self.mangle = a;
1132                                 self.SendFlags |= 2;
1133                         }
1134                 }
1135         }
1136         else
1137         {
1138                 if(self.angles != self.mangle)
1139                 {
1140                         self.mangle = self.angles;
1141                         self.SendFlags |= 2;
1142                 }
1143         }
1144         if(self.origin != self.oldorigin)
1145         {
1146                 self.SendFlags |= 1;
1147                 self.oldorigin = self.origin;
1148         }
1149 }
1150
1151 void misc_laser_init()
1152 {
1153         if(self.target != "")
1154                 self.enemy = find(world, targetname, self.target);
1155 }
1156
1157 .entity pusher;
1158 void misc_laser_think()
1159 {
1160         vector o;
1161         entity oldself;
1162         entity hitent;
1163         vector hitloc;
1164
1165         self.nextthink = time;
1166
1167         if(!self.state)
1168                 return;
1169
1170         misc_laser_aim();
1171
1172         if(self.enemy)
1173         {
1174                 o = self.enemy.origin;
1175                 if not(self.spawnflags & 2)
1176                         o = self.origin + normalize(o - self.origin) * 32768;
1177         }
1178         else
1179         {
1180                 makevectors(self.mangle);
1181                 o = self.origin + v_forward * 32768;
1182         }
1183
1184         if(self.dmg || self.enemy.target != "")
1185         {
1186                 traceline(self.origin, o, MOVE_NORMAL, self);
1187         }
1188         hitent = trace_ent;
1189         hitloc = trace_endpos;
1190
1191         if(self.enemy.target != "") // DETECTOR laser
1192         {
1193                 if(trace_ent.iscreature)
1194                 {
1195                         self.pusher = hitent;
1196                         if(!self.count)
1197                         {
1198                                 self.count = 1;
1199
1200                                 oldself = self;
1201                                 self = self.enemy;
1202                                 activator = self.pusher;
1203                                 SUB_UseTargets();
1204                                 self = oldself;
1205                         }
1206                 }
1207                 else
1208                 {
1209                         if(self.count)
1210                         {
1211                                 self.count = 0;
1212
1213                                 oldself = self;
1214                                 self = self.enemy;
1215                                 activator = self.pusher;
1216                                 SUB_UseTargets();
1217                                 self = oldself;
1218                         }
1219                 }
1220         }
1221
1222         if(self.dmg)
1223         {
1224                 if(self.team)
1225                         if((self.spawnflags & 8 == 0) == (self.team != hitent.team))
1226                                 return;
1227                 if(hitent.takedamage)
1228                         Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1229         }
1230 }
1231
1232 float laser_SendEntity(entity to, float fl)
1233 {
1234         WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1235         fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1236         if(self.spawnflags & 2)
1237                 fl |= 0x80;
1238         if(self.alpha)
1239                 fl |= 0x40;
1240         if(self.scale != 1 || self.modelscale != 1)
1241                 fl |= 0x20;
1242         if(self.spawnflags & 4)
1243                 fl |= 0x10;
1244         WriteByte(MSG_ENTITY, fl);
1245         if(fl & 1)
1246         {
1247                 WriteCoord(MSG_ENTITY, self.origin_x);
1248                 WriteCoord(MSG_ENTITY, self.origin_y);
1249                 WriteCoord(MSG_ENTITY, self.origin_z);
1250         }
1251         if(fl & 8)
1252         {
1253                 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1254                 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1255                 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1256                 if(fl & 0x40)
1257                         WriteByte(MSG_ENTITY, self.alpha * 255.0);
1258                 if(fl & 0x20)
1259                 {
1260                         WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1261                         WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1262                 }
1263                 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1264                         WriteShort(MSG_ENTITY, self.cnt + 1);
1265         }
1266         if(fl & 2)
1267         {
1268                 if(fl & 0x80)
1269                 {
1270                         WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1271                         WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1272                         WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1273                 }
1274                 else
1275                 {
1276                         WriteAngle(MSG_ENTITY, self.mangle_x);
1277                         WriteAngle(MSG_ENTITY, self.mangle_y);
1278                 }
1279         }
1280         if(fl & 4)
1281                 WriteByte(MSG_ENTITY, self.state);
1282         return 1;
1283 }
1284
1285 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1286 Any object touching the beam will be hurt
1287 Keys:
1288 "target"
1289  spawnfunc_target_position where the laser ends
1290 "mdl"
1291  name of beam end effect to use
1292 "colormod"
1293  color of the beam (default: red)
1294 "dmg"
1295  damage per second (-1 for a laser that kills immediately)
1296 */
1297 void laser_use()
1298 {
1299         self.state = !self.state;
1300         self.SendFlags |= 4;
1301         misc_laser_aim();
1302 }
1303
1304 void laser_reset()
1305 {
1306         if(self.spawnflags & 1)
1307                 self.state = 1;
1308         else
1309                 self.state = 0;
1310 }
1311
1312 void spawnfunc_misc_laser()
1313 {
1314         if(self.mdl)
1315         {
1316                 if(self.mdl == "none")
1317                         self.cnt = -1;
1318                 else
1319                 {
1320                         self.cnt = particleeffectnum(self.mdl);
1321                         if(self.cnt < 0)
1322                                 if(self.dmg)
1323                                         self.cnt = particleeffectnum("laser_deadly");
1324                 }
1325         }
1326         else if(!self.cnt)
1327         {
1328                 if(self.dmg)
1329                         self.cnt = particleeffectnum("laser_deadly");
1330                 else
1331                         self.cnt = -1;
1332         }
1333         if(self.cnt < 0)
1334                 self.cnt = -1;
1335
1336         if(self.colormod == '0 0 0')
1337                 if(!self.alpha)
1338                         self.colormod = '1 0 0';
1339         if(!self.message)
1340                 self.message = "saw the light";
1341         if (!self.message2)
1342                 self.message2 = "was pushed into a laser by";
1343         if(!self.scale)
1344                 self.scale = 1;
1345         if(!self.modelscale)
1346                 self.modelscale = 1;
1347         else if(self.modelscale < 0)
1348                 self.modelscale = 0;
1349         self.think = misc_laser_think;
1350         self.nextthink = time;
1351         InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1352
1353         self.mangle = self.angles;
1354
1355         Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1356
1357         IFTARGETED
1358         {
1359                 self.reset = laser_reset;
1360                 laser_reset();
1361                 self.use = laser_use;
1362         }
1363         else
1364                 self.state = 1;
1365 }
1366
1367 // tZorks trigger impulse / gravity
1368 .float radius;
1369 .float falloff;
1370 .float strength;
1371 .float lastpushtime;
1372
1373 // targeted (directional) mode
1374 void trigger_impulse_touch1()
1375 {
1376         entity targ;
1377     float pushdeltatime;
1378     float str;
1379
1380         if (self.active != ACTIVE_ACTIVE) 
1381                 return;
1382
1383         if (!isPushable(other))
1384                 return;
1385
1386         EXACTTRIGGER_TOUCH;
1387
1388     targ = find(world, targetname, self.target);
1389     if(!targ)
1390     {
1391         objerror("trigger_force without a (valid) .target!\n");
1392         remove(self);
1393         return;
1394     }
1395
1396     if(self.falloff == 1)
1397         str = (str / self.radius) * self.strength;
1398     else if(self.falloff == 2)
1399         str = (1 - (str / self.radius)) * self.strength;
1400     else
1401         str = self.strength;
1402
1403     pushdeltatime = time - other.lastpushtime;
1404     if (pushdeltatime > 0.15) pushdeltatime = 0;
1405     other.lastpushtime = time;
1406     if(!pushdeltatime) return;
1407
1408     other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1409     other.flags &~= FL_ONGROUND;
1410     UpdateCSQCProjectile(other);
1411 }
1412
1413 // Directionless (accelerator/decelerator) mode
1414 void trigger_impulse_touch2()
1415 {
1416     float pushdeltatime;
1417
1418         if (self.active != ACTIVE_ACTIVE) 
1419                 return;
1420
1421         if (!isPushable(other))
1422                 return;
1423
1424         EXACTTRIGGER_TOUCH;
1425
1426     pushdeltatime = time - other.lastpushtime;
1427     if (pushdeltatime > 0.15) pushdeltatime = 0;
1428     other.lastpushtime = time;
1429     if(!pushdeltatime) return;
1430
1431     // div0: ticrate independent, 1 = identity (not 20)
1432     other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1433     UpdateCSQCProjectile(other);
1434 }
1435
1436 // Spherical (gravity/repulsor) mode
1437 void trigger_impulse_touch3()
1438 {
1439     float pushdeltatime;
1440     float str;
1441
1442         if (self.active != ACTIVE_ACTIVE) 
1443                 return;
1444
1445         if (!isPushable(other))
1446                 return;
1447
1448         EXACTTRIGGER_TOUCH;
1449
1450     pushdeltatime = time - other.lastpushtime;
1451     if (pushdeltatime > 0.15) pushdeltatime = 0;
1452     other.lastpushtime = time;
1453     if(!pushdeltatime) return;
1454
1455     setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1456
1457         str = min(self.radius, vlen(self.origin - other.origin));
1458
1459     if(self.falloff == 1)
1460         str = (1 - str / self.radius) * self.strength; // 1 in the inside
1461     else if(self.falloff == 2)
1462         str = (str / self.radius) * self.strength; // 0 in the inside
1463     else
1464         str = self.strength;
1465
1466     other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1467     UpdateCSQCProjectile(other);
1468 }
1469
1470 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1471 -------- KEYS --------
1472 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1473          If not, this trigger acts like a damper/accelerator field.
1474
1475 strength : This is how mutch force to add in the direction of .target each second
1476            when .target is set. If not, this is hoe mutch to slow down/accelerate
1477            someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1478
1479 radius   : If set, act as a spherical device rather then a liniar one.
1480
1481 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1482
1483 -------- NOTES --------
1484 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1485 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1486 */
1487
1488 void spawnfunc_trigger_impulse()
1489 {
1490         self.active = ACTIVE_ACTIVE;
1491
1492         EXACTTRIGGER_INIT;
1493     if(self.radius)
1494     {
1495         if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1496         setorigin(self, self.origin);
1497         setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1498         self.touch = trigger_impulse_touch3;
1499     }
1500     else
1501     {
1502         if(self.target)
1503         {
1504             if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1505             self.touch = trigger_impulse_touch1;
1506         }
1507         else
1508         {
1509             if(!self.strength) self.strength = 0.9;
1510                         self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1511             self.touch = trigger_impulse_touch2;
1512         }
1513     }
1514 }
1515
1516 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1517 "Flip-flop" trigger gate... lets only every second trigger event through
1518 */
1519 void flipflop_use()
1520 {
1521         self.state = !self.state;
1522         if(self.state)
1523                 SUB_UseTargets();
1524 }
1525
1526 void spawnfunc_trigger_flipflop()
1527 {
1528         if(self.spawnflags & 1)
1529                 self.state = 1;
1530         self.use = flipflop_use;
1531         self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1532 }
1533
1534 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1535 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1536 */
1537 void monoflop_use()
1538 {
1539         self.nextthink = time + self.wait;
1540         self.enemy = activator;
1541         if(self.state)
1542                 return;
1543         self.state = 1;
1544         SUB_UseTargets();
1545 }
1546 void monoflop_fixed_use()
1547 {
1548         if(self.state)
1549                 return;
1550         self.nextthink = time + self.wait;
1551         self.state = 1;
1552         self.enemy = activator;
1553         SUB_UseTargets();
1554 }
1555
1556 void monoflop_think()
1557 {
1558         self.state = 0;
1559         activator = self.enemy;
1560         SUB_UseTargets();
1561 }
1562
1563 void monoflop_reset()
1564 {
1565         self.state = 0;
1566         self.nextthink = 0;
1567 }
1568
1569 void spawnfunc_trigger_monoflop()
1570 {
1571         if(!self.wait)
1572                 self.wait = 1;
1573         if(self.spawnflags & 1)
1574                 self.use = monoflop_fixed_use;
1575         else
1576                 self.use = monoflop_use;
1577         self.think = monoflop_think;
1578         self.state = 0;
1579         self.reset = monoflop_reset;
1580 }
1581
1582 void multivibrator_send()
1583 {
1584         float newstate;
1585         float cyclestart;
1586
1587         cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1588
1589         newstate = (time < cyclestart + self.wait);
1590
1591         activator = self;
1592         if(self.state != newstate)
1593                 SUB_UseTargets();
1594         self.state = newstate;
1595
1596         if(self.state)
1597                 self.nextthink = cyclestart + self.wait + 0.01;
1598         else
1599                 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1600 }
1601
1602 void multivibrator_toggle()
1603 {
1604         if(self.nextthink == 0)
1605         {
1606                 multivibrator_send();
1607         }
1608         else
1609         {
1610                 if(self.state)
1611                 {
1612                         SUB_UseTargets();
1613                         self.state = 0;
1614                 }
1615                 self.nextthink = 0;
1616         }
1617 }
1618
1619 void multivibrator_reset()
1620 {
1621         if(!(self.spawnflags & 1))
1622                 self.nextthink = 0; // wait for a trigger event
1623         else
1624                 self.nextthink = max(1, time);
1625 }
1626
1627 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1628 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1629 -------- KEYS --------
1630 target: trigger all entities with this targetname when it goes off
1631 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1632 phase: offset of the timing
1633 wait: "on" cycle time (default: 1)
1634 respawntime: "off" cycle time (default: same as wait)
1635 -------- SPAWNFLAGS --------
1636 START_ON: assume it is already turned on (when targeted)
1637 */
1638 void spawnfunc_trigger_multivibrator()
1639 {
1640         if(!self.wait)
1641                 self.wait = 1;
1642         if(!self.respawntime)
1643                 self.respawntime = self.wait;
1644
1645         self.state = 0;
1646         self.use = multivibrator_toggle;
1647         self.think = multivibrator_send;
1648         self.nextthink = max(1, time);
1649
1650         IFTARGETED
1651                 multivibrator_reset();
1652 }
1653
1654
1655 void follow_init()
1656 {
1657         entity src, dst;
1658         src = world;
1659         dst = world;
1660         if(self.killtarget != "")
1661                 src = find(world, targetname, self.killtarget);
1662         if(self.target != "")
1663                 dst = find(world, targetname, self.target);
1664
1665         if(!src && !dst)
1666         {
1667                 objerror("follow: could not find target/killtarget");
1668                 return;
1669         }
1670
1671         if(self.jointtype)
1672         {
1673                 // already done :P entity must stay
1674                 self.aiment = src;
1675                 self.enemy = dst;
1676         }
1677         else if(!src || !dst)
1678         {
1679                 objerror("follow: could not find target/killtarget");
1680                 return;
1681         }
1682         else if(self.spawnflags & 1)
1683         {
1684                 // attach
1685                 if(self.spawnflags & 2)
1686                 {
1687                         setattachment(dst, src, self.message);
1688                 }
1689                 else
1690                 {
1691                         attach_sameorigin(dst, src, self.message);
1692                 }
1693
1694                 dst.solid = SOLID_NOT; // solid doesn't work with attachment
1695                 remove(self);
1696         }
1697         else
1698         {
1699                 if(self.spawnflags & 2)
1700                 {
1701                         dst.movetype = MOVETYPE_FOLLOW;
1702                         dst.aiment = src;
1703                         // dst.punchangle = '0 0 0'; // keep unchanged
1704                         dst.view_ofs = dst.origin;
1705                         dst.v_angle = dst.angles;
1706                 }
1707                 else
1708                 {
1709                         follow_sameorigin(dst, src);
1710                 }
1711
1712                 remove(self);
1713         }
1714 }
1715
1716 void spawnfunc_misc_follow()
1717 {
1718         InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1719 }
1720
1721
1722
1723 void gamestart_use() {
1724         activator = self;
1725         SUB_UseTargets();
1726         remove(self);
1727 }
1728
1729 void spawnfunc_trigger_gamestart() {
1730         self.use = gamestart_use;
1731         self.reset2 = spawnfunc_trigger_gamestart;
1732
1733         if(self.wait)
1734         {
1735                 self.think = self.use;
1736                 self.nextthink = game_starttime + self.wait;
1737         }
1738         else
1739                 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1740 }
1741
1742
1743
1744
1745 .entity voicescript; // attached voice script
1746 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1747 .float voicescript_nextthink; // time to play next voice
1748 .float voicescript_voiceend; // time when this voice ends
1749
1750 void target_voicescript_clear(entity pl)
1751 {
1752         pl.voicescript = world;
1753 }
1754
1755 void target_voicescript_use()
1756 {
1757         if(activator.voicescript != self)
1758         {
1759                 activator.voicescript = self;
1760                 activator.voicescript_index = 0;
1761                 activator.voicescript_nextthink = time + self.delay;
1762         }
1763 }
1764
1765 void target_voicescript_next(entity pl)
1766 {
1767         entity vs;
1768         float i, n, dt;
1769
1770         vs = pl.voicescript;
1771         if(!vs)
1772                 return;
1773         if(vs.message == "")
1774                 return;
1775         if(pl.classname != "player")
1776                 return;
1777         if(gameover)
1778                 return;
1779
1780         if(time >= pl.voicescript_voiceend)
1781         {
1782                 if(time >= pl.voicescript_nextthink)
1783                 {
1784                         // get the next voice...
1785                         n = tokenize_console(vs.message);
1786
1787                         if(pl.voicescript_index < vs.cnt)
1788                                 i = pl.voicescript_index * 2;
1789                         else if(n > vs.cnt * 2)
1790                                 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1791                         else
1792                                 i = -1;
1793
1794                         if(i >= 0)
1795                         {
1796                                 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1797                                 dt = stof(argv(i + 1));
1798                                 if(dt >= 0)
1799                                 {
1800                                         pl.voicescript_voiceend = time + dt;
1801                                         pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1802                                 }
1803                                 else
1804                                 {
1805                                         pl.voicescript_voiceend = time - dt;
1806                                         pl.voicescript_nextthink = pl.voicescript_voiceend;
1807                                 }
1808
1809                                 pl.voicescript_index += 1;
1810                         }
1811                         else
1812                         {
1813                                 pl.voicescript = world; // stop trying then
1814                         }
1815                 }
1816         }
1817 }
1818
1819 void spawnfunc_target_voicescript()
1820 {
1821         // netname: directory of the sound files
1822         // message: list of "sound file" duration "sound file" duration, a *, and again a list
1823         //          foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1824         //          Here, a - in front of the duration means that no delay is to be
1825         //          added after this message
1826         // wait: average time between messages
1827         // delay: initial delay before the first message
1828         
1829         float i, n;
1830         self.use = target_voicescript_use;
1831
1832         n = tokenize_console(self.message);
1833         self.cnt = n / 2;
1834         for(i = 0; i+1 < n; i += 2)
1835         {
1836                 if(argv(i) == "*")
1837                 {
1838                         self.cnt = i / 2;
1839                         ++i;
1840                 }
1841                 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1842         }
1843 }
1844
1845
1846
1847 void trigger_relay_teamcheck_use()
1848 {
1849         if(activator.team)
1850         {
1851                 if(self.spawnflags & 2)
1852                 {
1853                         if(activator.team != self.team)
1854                                 SUB_UseTargets();
1855                 }
1856                 else
1857                 {
1858                         if(activator.team == self.team)
1859                                 SUB_UseTargets();
1860                 }
1861         }
1862         else
1863         {
1864                 if(self.spawnflags & 1)
1865                         SUB_UseTargets();
1866         }
1867 }
1868
1869 void trigger_relay_teamcheck_reset()
1870 {
1871         self.team = self.team_saved;
1872 }
1873
1874 void spawnfunc_trigger_relay_teamcheck()
1875 {
1876         self.team_saved = self.team;
1877         self.use = trigger_relay_teamcheck_use;
1878         self.reset = trigger_relay_teamcheck_reset;
1879 }
1880
1881
1882
1883 void trigger_disablerelay_use()
1884 {
1885         entity e;
1886
1887         float a, b;
1888         a = b = 0;
1889
1890         for(e = world; (e = find(e, targetname, self.target)); )
1891         {
1892                 if(e.use == SUB_UseTargets)
1893                 {
1894                         e.use = SUB_DontUseTargets;
1895                         ++a;
1896                 }
1897                 else if(e.use == SUB_DontUseTargets)
1898                 {
1899                         e.use = SUB_UseTargets;
1900                         ++b;
1901                 }
1902         }
1903
1904         if((!a) == (!b))
1905                 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1906 }
1907
1908 void spawnfunc_trigger_disablerelay()
1909 {
1910         self.use = trigger_disablerelay_use;
1911 }
1912
1913 float magicear_matched;
1914 float W_Tuba_HasPlayed(entity pl, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo);
1915 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1916 {
1917         float domatch, dotrigger, matchstart, l;
1918         string s, msg;
1919         entity oldself;
1920         string savemessage;
1921
1922         magicear_matched = FALSE;
1923
1924         dotrigger = ((source.classname == "player") && (source.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1925         domatch = ((ear.spawnflags & 32) || dotrigger);
1926
1927         if not(domatch)
1928                 return msgin;
1929
1930         if not(msgin)
1931         {
1932                 // we are in TUBA mode!
1933                 if not(ear.spawnflags & 256)
1934                         return msgin;
1935
1936                 if(!W_Tuba_HasPlayed(source, ear.message, ear.movedir_x, !(ear.spawnflags & 512), ear.movedir_y, ear.movedir_z))
1937                         return msgin;
1938
1939                 magicear_matched = TRUE;
1940
1941                 if(dotrigger)
1942                 {
1943                         oldself = self;
1944                         activator = source;
1945                         self = ear;
1946                         savemessage = self.message;
1947                         self.message = string_null;
1948                         SUB_UseTargets();
1949                         self.message = savemessage;
1950                         self = oldself;
1951                 }
1952
1953                 if(ear.netname != "")
1954                         return ear.netname;
1955
1956                 return msgin;
1957         }
1958
1959         if(ear.spawnflags & 256) // ENOTUBA
1960                 return msgin;
1961
1962         if(privatesay)
1963         {
1964                 if(ear.spawnflags & 4)
1965                         return msgin;
1966         }
1967         else
1968         {
1969                 if(!teamsay)
1970                         if(ear.spawnflags & 1)
1971                                 return msgin;
1972                 if(teamsay > 0)
1973                         if(ear.spawnflags & 2)
1974                                 return msgin;
1975                 if(teamsay < 0)
1976                         if(ear.spawnflags & 8)
1977                                 return msgin;
1978         }
1979         
1980         matchstart = -1;
1981         l = strlen(ear.message);
1982
1983         if(ear.spawnflags & 128)
1984                 msg = msgin;
1985         else
1986                 msg = strdecolorize(msgin);
1987
1988         if(substring(ear.message, 0, 1) == "*")
1989         {
1990                 if(substring(ear.message, -1, 1) == "*")
1991                 {
1992                         // two wildcards
1993                         // as we need multi-replacement here...
1994                         s = substring(ear.message, 1, -2);
1995                         l -= 2;
1996                         if(strstrofs(msg, s, 0) >= 0)
1997                                 matchstart = -2; // we use strreplace on s
1998                 }
1999                 else
2000                 {
2001                         // match at start
2002                         s = substring(ear.message, 1, -1);
2003                         l -= 1;
2004                         if(substring(msg, -l, l) == s)
2005                                 matchstart = strlen(msg) - l;
2006                 }
2007         }
2008         else
2009         {
2010                 if(substring(ear.message, -1, 1) == "*")
2011                 {
2012                         // match at end
2013                         s = substring(ear.message, 0, -2);
2014                         l -= 1;
2015                         if(substring(msg, 0, l) == s)
2016                                 matchstart = 0;
2017                 }
2018                 else
2019                 {
2020                         // full match
2021                         s = ear.message;
2022                         if(msg == ear.message)
2023                                 matchstart = 0;
2024                 }
2025         }
2026
2027         if(matchstart == -1) // no match
2028                 return msgin;
2029
2030         magicear_matched = TRUE;
2031
2032         if(dotrigger)
2033         {
2034                 oldself = self;
2035                 activator = source;
2036                 self = ear;
2037                 savemessage = self.message;
2038                 self.message = string_null;
2039                 SUB_UseTargets();
2040                 self.message = savemessage;
2041                 self = oldself;
2042         }
2043
2044         if(ear.spawnflags & 16)
2045         {
2046                 return ear.netname;
2047         }
2048         else if(ear.netname != "")
2049         {
2050                 if(matchstart < 0)
2051                         return strreplace(s, ear.netname, msg);
2052                 else
2053                         return strcat(
2054                                 substring(msg, 0, matchstart),
2055                                 ear.netname,
2056                                 substring(msg, matchstart + l, -1)
2057                         );
2058         }
2059         else
2060                 return msgin;
2061 }
2062
2063 entity magicears;
2064 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2065 {
2066         entity ear;
2067         string msgout;
2068         for(ear = magicears; ear; ear = ear.enemy)
2069         {
2070                 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2071                 if not(ear.spawnflags & 64)
2072                         if(magicear_matched)
2073                                 return msgout;
2074                 msgin = msgout;
2075         }
2076         return msgin;
2077 }
2078
2079 void spawnfunc_trigger_magicear()
2080 {
2081         self.enemy = magicears;
2082         magicears = self;
2083
2084         // actually handled in "say" processing
2085         // spawnflags:
2086         //    1 = ignore say
2087         //    2 = ignore teamsay
2088         //    4 = ignore tell
2089         //    8 = ignore tell to unknown player
2090         //   16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2091         //   32 = perform the replacement even if outside the radius or dead
2092         //   64 = continue replacing/triggering even if this one matched
2093         //  128 = don't decolorize message before matching
2094         //  256 = message is a tuba note sequence (pitch.duration pitch.duration ...)
2095         //  512 = tuba notes must be exact right pitch, no transposing
2096         // message: either
2097         //   *pattern*
2098         // or
2099         //   *pattern
2100         // or
2101         //   pattern*
2102         // or
2103         //   pattern
2104         // netname:
2105         //   if set, replacement for the matched text
2106         // radius:
2107         //   "hearing distance"
2108         // target:
2109         //   what to trigger
2110         // movedir:
2111         //   for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter)
2112
2113         self.movedir_x -= 1; // map to tuba instrument numbers
2114 }
2115
2116 void relay_activators_use()
2117 {
2118         entity trg, os;
2119         
2120         os = self;
2121         
2122         for(trg = world; (trg = find(trg, targetname, os.target)); )
2123         {
2124                 self = trg;
2125                 if (trg.setactive)
2126                         trg.setactive(os.cnt);
2127                 else
2128                 {
2129                         //bprint("Not using setactive\n");
2130                         if(os.cnt == ACTIVE_TOGGLE)
2131                                 if(trg.active == ACTIVE_ACTIVE)
2132                                         trg.active = ACTIVE_NOT;
2133                                 else    
2134                                         trg.active = ACTIVE_ACTIVE;
2135                         else
2136                                 trg.active = os.cnt;
2137                 }               
2138         }
2139         self = os;
2140 }
2141
2142 void spawnfunc_relay_activate()
2143 {
2144         self.cnt = ACTIVE_ACTIVE;
2145         self.use = relay_activators_use;
2146 }
2147
2148 void spawnfunc_relay_deactivate()
2149 {
2150         self.cnt = ACTIVE_NOT;
2151         self.use = relay_activators_use;        
2152 }
2153
2154 void spawnfunc_relay_activatetoggle()
2155 {
2156         self.cnt = ACTIVE_TOGGLE;
2157         self.use = relay_activators_use;        
2158 }
2159
2160 .string chmap, gametype;
2161 void spawnfunc_target_changelevel_use()
2162 {
2163         if(self.gametype != "")
2164                 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2165
2166         if (self.chmap == "")
2167                 localcmd("endmatch\n");
2168         else
2169                 localcmd(strcat("changelevel ", self.chmap, "\n"));
2170 }
2171
2172 void spawnfunc_target_changelevel()
2173 {
2174         self.use = spawnfunc_target_changelevel_use;
2175 }