]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/g_triggers.qc
Merge remote-tracking branch 'origin/mirceakitsune/bloom_scenebrightness'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / g_triggers.qc
1 void SUB_DontUseTargets()
2 {
3 }
4
5
6 void() SUB_UseTargets;
7
8 void DelayThink()
9 {
10         activator = self.enemy;
11         SUB_UseTargets ();
12         remove(self);
13 }
14
15 /*
16 ==============================
17 SUB_UseTargets
18
19 the global "activator" should be set to the entity that initiated the firing.
20
21 If self.delay is set, a DelayedUse entity will be created that will actually
22 do the SUB_UseTargets after that many seconds have passed.
23
24 Centerprints any self.message to the activator.
25
26 Removes all entities with a targetname that match self.killtarget,
27 and removes them, so some events can remove other triggers.
28
29 Search for (string)targetname in all entities that
30 match (string)self.target and call their .use function
31
32 ==============================
33 */
34 void SUB_UseTargets()
35 {
36         entity t, stemp, otemp, act;
37         string s;
38         float i;
39
40 //
41 // check for a delay
42 //
43         if (self.delay)
44         {
45         // create a temp object to fire at a later time
46                 t = spawn();
47                 t.classname = "DelayedUse";
48                 t.nextthink = time + self.delay;
49                 t.think = DelayThink;
50                 t.enemy = activator;
51                 t.message = self.message;
52                 t.killtarget = self.killtarget;
53                 t.target = self.target;
54                 t.target2 = self.target2;
55                 t.target3 = self.target3;
56                 t.target4 = self.target4;
57                 return;
58         }
59
60
61 //
62 // print the message
63 //
64         if (activator.classname == "player" && self.message != "")
65         {
66                 if(clienttype(activator) == CLIENTTYPE_REAL)
67                 {
68                         centerprint (activator, self.message);
69                         if (!self.noise)
70                                 play2(activator, "misc/talk.wav");
71                 }
72         }
73
74 //
75 // kill the killtagets
76 //
77         s = self.killtarget;
78         if (s != "")
79         {
80                 for(t = world; (t = find(t, targetname, s)); )
81                         remove(t);
82         }
83
84 //
85 // fire targets
86 //
87         act = activator;
88         stemp = self;
89         otemp = other;
90
91         if(stemp.target_random)
92                 RandomSelection_Init();
93
94         for(i = 0; i < 4; ++i)
95         {
96                 switch(i)
97                 {
98                         default:
99                         case 0: s = stemp.target; break;
100                         case 1: s = stemp.target2; break;
101                         case 2: s = stemp.target3; break;
102                         case 3: s = stemp.target4; break;
103                 }
104                 if (s != "")
105                 {
106                         for(t = world; (t = find(t, targetname, s)); )
107                         if(t.use)
108                         {
109                                 if(stemp.target_random)
110                                 {
111                                         RandomSelection_Add(t, 0, string_null, 1, 0);
112                                 }
113                                 else
114                                 {
115                                         self = t;
116                                         other = stemp;
117                                         activator = act;
118                                         self.use();
119                                 }
120                         }
121                 }
122         }
123
124         if(stemp.target_random && RandomSelection_chosen_ent)
125         {
126                 self = RandomSelection_chosen_ent;
127                 other = stemp;
128                 activator = act;
129                 self.use();
130         }
131
132         activator = act;
133         self = stemp;
134         other = otemp;
135 }
136
137
138 //=============================================================================
139
140 float   SPAWNFLAG_NOMESSAGE = 1;
141 float   SPAWNFLAG_NOTOUCH = 1;
142
143 // the wait time has passed, so set back up for another activation
144 void multi_wait()
145 {
146         if (self.max_health)
147         {
148                 self.health = self.max_health;
149                 self.takedamage = DAMAGE_YES;
150                 self.solid = SOLID_BBOX;
151         }
152 }
153
154
155 // the trigger was just touched/killed/used
156 // self.enemy should be set to the activator so it can be held through a delay
157 // so wait for the delay time before firing
158 void multi_trigger()
159 {
160         if (self.nextthink > time)
161         {
162                 return;         // allready been triggered
163         }
164
165         if (self.classname == "trigger_secret")
166         {
167                 if (self.enemy.classname != "player")
168                         return;
169                 found_secrets = found_secrets + 1;
170                 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
171         }
172
173         if (self.noise)
174                 sound (self.enemy, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
175
176 // don't trigger again until reset
177         self.takedamage = DAMAGE_NO;
178
179         activator = self.enemy;
180         other = self.goalentity;
181         SUB_UseTargets();
182
183         if (self.wait > 0)
184         {
185                 self.think = multi_wait;
186                 self.nextthink = time + self.wait;
187         }
188         else if (self.wait == 0)
189         {
190                 multi_wait(); // waiting finished
191         }
192         else
193         {       // we can't just remove (self) here, because this is a touch function
194                 // called wheil C code is looping through area links...
195                 self.touch = SUB_Null;
196         }
197 }
198
199 void multi_use()
200 {
201         self.goalentity = other;
202         self.enemy = activator;
203         multi_trigger();
204 }
205
206 void multi_touch()
207 {
208         if not(self.spawnflags & 2)
209                 if not(other.iscreature)
210                         return;
211
212         if(self.team)
213                 if((self.spawnflags & 4 == 0) == (self.team != other.team))
214                         return;
215
216 // if the trigger has an angles field, check player's facing direction
217         if (self.movedir != '0 0 0')
218         {
219                 makevectors (other.angles);
220                 if (v_forward * self.movedir < 0)
221                         return;         // not facing the right way
222         }
223
224         EXACTTRIGGER_TOUCH;
225
226         self.enemy = other;
227         self.goalentity = other;
228         multi_trigger ();
229 }
230
231 void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
232 {
233         if (!self.takedamage)
234                 return;
235         if(self.spawnflags & DOOR_NOSPLASH)
236                 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
237                         return;
238         self.health = self.health - damage;
239         if (self.health <= 0)
240         {
241                 self.enemy = attacker;
242                 self.goalentity = inflictor;
243                 multi_trigger();
244         }
245 }
246
247 void multi_reset()
248 {
249         if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
250                 self.touch = multi_touch;
251         if (self.max_health)
252         {
253                 self.health = self.max_health;
254                 self.takedamage = DAMAGE_YES;
255                 self.solid = SOLID_BBOX;
256         }
257         self.think = SUB_Null;
258         self.team = self.team_saved;
259 }
260
261 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
262 Variable sized repeatable trigger.  Must be targeted at one or more entities.  If "health" is set, the trigger must be killed to activate each time.
263 If "delay" is set, the trigger waits some time after activating before firing.
264 "wait" : Seconds between triggerings. (.2 default)
265 If notouch is set, the trigger is only fired by other entities, not by touching.
266 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
267 sounds
268 1)      secret
269 2)      beep beep
270 3)      large switch
271 4)
272 set "message" to text string
273 */
274 void spawnfunc_trigger_multiple()
275 {
276         self.reset = multi_reset;
277         if (self.sounds == 1)
278         {
279                 precache_sound ("misc/secret.wav");
280                 self.noise = "misc/secret.wav";
281         }
282         else if (self.sounds == 2)
283         {
284                 precache_sound ("misc/talk.wav");
285                 self.noise = "misc/talk.wav";
286         }
287         else if (self.sounds == 3)
288         {
289                 precache_sound ("misc/trigger1.wav");
290                 self.noise = "misc/trigger1.wav";
291         }
292
293         if (!self.wait)
294                 self.wait = 0.2;
295         else if(self.wait < -1)
296                 self.wait = 0;
297         self.use = multi_use;
298
299         EXACTTRIGGER_INIT;
300
301         self.team_saved = self.team;
302
303         if (self.health)
304         {
305                 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
306                         objerror ("health and notouch don't make sense\n");
307                 self.max_health = self.health;
308                 self.event_damage = multi_eventdamage;
309                 self.takedamage = DAMAGE_YES;
310                 self.solid = SOLID_BBOX;
311                 setorigin (self, self.origin);  // make sure it links into the world
312         }
313         else
314         {
315                 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
316                 {
317                         self.touch = multi_touch;
318                         setorigin (self, self.origin);  // make sure it links into the world
319                 }
320         }
321 }
322
323
324 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
325 Variable sized trigger. Triggers once, then removes itself.  You must set the key "target" to the name of another object in the level that has a matching
326 "targetname".  If "health" is set, the trigger must be killed to activate.
327 If notouch is set, the trigger is only fired by other entities, not by touching.
328 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
329 if "angle" is set, the trigger will only fire when someone is facing the direction of the angle.  Use "360" for an angle of 0.
330 sounds
331 1)      secret
332 2)      beep beep
333 3)      large switch
334 4)
335 set "message" to text string
336 */
337 void spawnfunc_trigger_once()
338 {
339         self.wait = -1;
340         spawnfunc_trigger_multiple();
341 }
342
343 //=============================================================================
344
345 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
346 This fixed size trigger cannot be touched, it can only be fired by other events.  It can contain killtargets, targets, delays, and messages.
347 */
348 void spawnfunc_trigger_relay()
349 {
350         self.use = SUB_UseTargets;
351         self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
352 }
353
354 void delay_use()
355 {
356     self.think = SUB_UseTargets;
357     self.nextthink = self.wait;
358 }
359
360 void delay_reset()
361 {
362         self.think = SUB_Null;
363 }
364
365 void spawnfunc_trigger_delay()
366 {
367     if(!self.wait)
368         self.wait = 1;
369
370     self.use = delay_use;
371     self.reset = delay_reset;
372 }
373
374 //=============================================================================
375
376
377 void counter_use()
378 {
379         self.count = self.count - 1;
380         if (self.count < 0)
381                 return;
382
383         if (self.count != 0)
384         {
385                 if (activator.classname == "player"
386                 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
387                 {
388                         if (self.count >= 4)
389                                 centerprint (activator, "There are more to go...");
390                         else if (self.count == 3)
391                                 centerprint (activator, "Only 3 more to go...");
392                         else if (self.count == 2)
393                                 centerprint (activator, "Only 2 more to go...");
394                         else
395                                 centerprint (activator, "Only 1 more to go...");
396                 }
397                 return;
398         }
399
400         if (activator.classname == "player"
401         && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
402                 centerprint(activator, "Sequence completed!");
403         self.enemy = activator;
404         multi_trigger ();
405 }
406
407 void counter_reset()
408 {
409         self.count = self.cnt;
410         multi_reset();
411 }
412
413 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
414 Acts as an intermediary for an action that takes multiple inputs.
415
416 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
417
418 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
419 */
420 void spawnfunc_trigger_counter()
421 {
422         self.wait = -1;
423         if (!self.count)
424                 self.count = 2;
425         self.cnt = self.count;
426
427         self.use = counter_use;
428         self.reset = counter_reset;
429 }
430
431 void trigger_hurt_use()
432 {
433         if(activator.classname == "player")
434                 self.enemy = activator;
435         else
436                 self.enemy = world; // let's just destroy it, if taking over is too much work
437 }
438
439 .float triggerhurttime;
440 void trigger_hurt_touch()
441 {
442         if (self.active != ACTIVE_ACTIVE) 
443                 return;
444
445         if(self.team)
446                 if((self.spawnflags & 4 == 0) == (self.team != other.team))
447                         return;
448
449         // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
450         if (other.iscreature)
451         {
452                 if (other.takedamage)
453                 if (other.triggerhurttime < time)
454                 {
455                         EXACTTRIGGER_TOUCH;
456                         other.triggerhurttime = time + 1;
457
458                         entity own;
459                         own = self.enemy;
460                         if(own.classname != "player")
461                         {
462                                 own = self;
463                                 self.enemy = world; // I still hate you all
464                         }
465
466                         Damage (other, self, own, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
467                 }
468         }
469         else
470         {
471                 if (!other.owner)
472                 {
473                         if (other.items & IT_KEY1 || other.items & IT_KEY2)     // reset flag
474                         {
475                                 EXACTTRIGGER_TOUCH;
476                                 other.pain_finished = min(other.pain_finished, time + 2);
477                         }
478                         else if (other.classname == "rune")                     // reset runes
479                         {
480                                 EXACTTRIGGER_TOUCH;
481                                 other.nextthink = min(other.nextthink, time + 1);
482                         }
483                 }
484         }
485
486         return;
487 }
488
489 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
490 Any object touching this will be hurt
491 set dmg to damage amount
492 defalt dmg = 5
493 */
494 .entity trigger_hurt_next;
495 entity trigger_hurt_last;
496 entity trigger_hurt_first;
497 void spawnfunc_trigger_hurt()
498 {
499         EXACTTRIGGER_INIT;
500         self.active = ACTIVE_ACTIVE;
501         self.touch = trigger_hurt_touch;
502         self.use = trigger_hurt_use;
503         self.enemy = world; // I hate you all
504         if (!self.dmg)
505                 self.dmg = 1000;
506         if (!self.message)
507                 self.message = "was in the wrong place";
508         if (!self.message2)
509                 self.message2 = "was thrown into a world of hurt by";
510         // self.message = "someone like %s always gets wrongplaced";
511
512         if(!trigger_hurt_first)
513                 trigger_hurt_first = self;
514         if(trigger_hurt_last)
515                 trigger_hurt_last.trigger_hurt_next = self;
516         trigger_hurt_last = self;
517 }
518
519 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
520 {
521         entity th;
522
523         for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
524                 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
525                         return TRUE;
526
527         return FALSE;
528 }
529
530 //////////////////////////////////////////////////////////////
531 //
532 //
533 //
534 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
535 //
536 //////////////////////////////////////////////////////////////
537
538 .float triggerhealtime;
539 void trigger_heal_touch()
540 {
541         if (self.active != ACTIVE_ACTIVE) 
542                 return;
543         
544         // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
545         if (other.iscreature)
546         {
547                 if (other.takedamage)
548                 if (!other.deadflag)
549                 if (other.triggerhealtime < time)
550                 {
551                         EXACTTRIGGER_TOUCH;
552                         other.triggerhealtime = time + 1;
553                         
554                         if (other.health < self.max_health)
555                         {
556                                 other.health = min(other.health + self.health, self.max_health);
557                                 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
558                                 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
559                         }
560                 }
561         }
562 }
563
564 void spawnfunc_trigger_heal()
565 {
566         self.active = ACTIVE_ACTIVE;
567         
568         EXACTTRIGGER_INIT;
569         self.touch = trigger_heal_touch;
570         if (!self.health)
571                 self.health = 10;
572         if (!self.max_health)
573                 self.max_health = 200; //Max health topoff for field
574         if(self.noise == "")
575                 self.noise = "misc/mediumhealth.wav";
576         precache_sound(self.noise);
577 }
578
579
580 //////////////////////////////////////////////////////////////
581 //
582 //
583 //
584 //End trigger_heal
585 //
586 //////////////////////////////////////////////////////////////
587
588 .entity trigger_gravity_check;
589 void trigger_gravity_remove(entity own)
590 {
591         if(own.trigger_gravity_check.owner == own)
592         {
593                 UpdateCSQCProjectile(own);
594                 own.gravity = own.trigger_gravity_check.gravity;
595                 remove(own.trigger_gravity_check);
596         }
597         else
598                 backtrace("Removing a trigger_gravity_check with no valid owner");
599         own.trigger_gravity_check = world;
600 }
601 void trigger_gravity_check_think()
602 {
603         // This spawns when a player enters the gravity zone and checks if he left.
604         // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
605         // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
606         if(self.count <= 0)
607         {
608                 if(self.owner.trigger_gravity_check == self)
609                         trigger_gravity_remove(self.owner);
610                 else
611                         remove(self);
612                 return;
613         }
614         else
615         {
616                 self.count -= 1;
617                 self.nextthink = time;
618         }
619 }
620
621 void trigger_gravity_use()
622 {
623         self.state = !self.state;
624 }
625
626 void trigger_gravity_touch()
627 {
628         float g;
629
630         if(self.state != TRUE)
631                 return;
632
633         EXACTTRIGGER_TOUCH;
634
635         g = self.gravity;
636
637         if not(self.spawnflags & 1)
638         {
639                 if(other.trigger_gravity_check)
640                 {
641                         if(self == other.trigger_gravity_check.enemy)
642                         {
643                                 // same?
644                                 other.trigger_gravity_check.count = 2; // gravity one more frame...
645                                 return;
646                         }
647
648                         // compare prio
649                         if(self.cnt > other.trigger_gravity_check.enemy.cnt)
650                                 trigger_gravity_remove(other);
651                         else
652                                 return;
653                 }
654                 other.trigger_gravity_check = spawn();
655                 other.trigger_gravity_check.enemy = self;
656                 other.trigger_gravity_check.owner = other;
657                 other.trigger_gravity_check.gravity = other.gravity;
658                 other.trigger_gravity_check.think = trigger_gravity_check_think;
659                 other.trigger_gravity_check.nextthink = time;
660                 other.trigger_gravity_check.count = 2;
661                 if(other.gravity)
662                         g *= other.gravity;
663         }
664
665         if (other.gravity != g)
666         {
667                 other.gravity = g;
668                 if(self.noise != "")
669                         sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
670                 UpdateCSQCProjectile(self.owner);
671         }
672 }
673
674 void spawnfunc_trigger_gravity()
675 {
676         if(self.gravity == 1)
677                 return;
678
679         EXACTTRIGGER_INIT;
680         self.touch = trigger_gravity_touch;
681         if(self.noise != "")
682                 precache_sound(self.noise);
683
684         self.state = TRUE;
685         IFTARGETED
686         {
687                 self.use = trigger_gravity_use;
688                 if(self.spawnflags & 2)
689                         self.state = FALSE;
690         }
691 }
692
693 //=============================================================================
694
695 // TODO add a way to do looped sounds with sound(); then complete this entity
696 .float volume, atten;
697 void target_speaker_use_off();
698 void target_speaker_use_activator()
699 {
700         if(clienttype(activator) != CLIENTTYPE_REAL)
701                 return;
702         string snd;
703         if(substring(self.noise, 0, 1) == "*")
704         {
705                 var .string sample;
706                 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
707                 if(GetPlayerSoundSampleField_notFound)
708                         snd = "misc/null.wav";
709                 else if(activator.sample == "")
710                         snd = "misc/null.wav";
711                 else
712                 {
713                         tokenize_console(activator.sample);
714                         float n;
715                         n = stof(argv(1));
716                         if(n > 0)
717                                 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
718                         else
719                                 snd = strcat(argv(0), ".wav"); // randomization
720                 }
721         }
722         else
723                 snd = self.noise;
724         msg_entity = activator;
725         soundto(MSG_ONE, self, CH_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
726 }
727 void target_speaker_use_on()
728 {
729         string snd;
730         if(substring(self.noise, 0, 1) == "*")
731         {
732                 var .string sample;
733                 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
734                 if(GetPlayerSoundSampleField_notFound)
735                         snd = "misc/null.wav";
736                 else if(activator.sample == "")
737                         snd = "misc/null.wav";
738                 else
739                 {
740                         tokenize_console(activator.sample);
741                         float n;
742                         n = stof(argv(1));
743                         if(n > 0)
744                                 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
745                         else
746                                 snd = strcat(argv(0), ".wav"); // randomization
747                 }
748         }
749         else
750                 snd = self.noise;
751         sound(self, CH_TRIGGER_SINGLE, snd, VOL_BASE * self.volume, self.atten);
752         if(self.spawnflags & 3)
753                 self.use = target_speaker_use_off;
754 }
755 void target_speaker_use_off()
756 {
757         sound(self, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_BASE * self.volume, self.atten);
758         self.use = target_speaker_use_on;
759 }
760 void target_speaker_reset()
761 {
762         if(self.spawnflags & 1) // LOOPED_ON
763         {
764                 if(self.use == target_speaker_use_on)
765                         target_speaker_use_on();
766         }
767         else if(self.spawnflags & 2)
768         {
769                 if(self.use == target_speaker_use_off)
770                         target_speaker_use_off();
771         }
772 }
773
774 void spawnfunc_target_speaker()
775 {
776         // TODO: "*" prefix to sound file name
777         // TODO: wait and random (just, HOW? random is not a field)
778         if(self.noise)
779                 precache_sound (self.noise);
780
781         if(!self.atten && !(self.spawnflags & 4))
782         {
783                 IFTARGETED
784                         self.atten = ATTN_NORM;
785                 else
786                         self.atten = ATTN_STATIC;
787         }
788         else if(self.atten < 0)
789                 self.atten = 0;
790
791         if(!self.volume)
792                 self.volume = 1;
793
794         IFTARGETED
795         {
796                 if(self.spawnflags & 8) // ACTIVATOR
797                         self.use = target_speaker_use_activator;
798                 else if(self.spawnflags & 1) // LOOPED_ON
799                 {
800                         target_speaker_use_on();
801                         self.reset = target_speaker_reset;
802                 }
803                 else if(self.spawnflags & 2) // LOOPED_OFF
804                 {
805                         self.use = target_speaker_use_on;
806                         self.reset = target_speaker_reset;
807                 }
808                 else
809                         self.use = target_speaker_use_on;
810         }
811         else if(self.spawnflags & 1) // LOOPED_ON
812         {
813                 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
814                 remove(self);
815         }
816         else if(self.spawnflags & 2) // LOOPED_OFF
817         {
818                 objerror("This sound entity can never be activated");
819         }
820         else
821         {
822                 // Quake/Nexuiz fallback
823                 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
824                 remove(self);
825         }
826 }
827
828
829 void spawnfunc_func_stardust() {
830         self.effects = EF_STARDUST;
831 }
832
833 .string bgmscript;
834 .float bgmscriptattack;
835 .float bgmscriptdecay;
836 .float bgmscriptsustain;
837 .float bgmscriptrelease;
838 float pointparticles_SendEntity(entity to, float fl)
839 {
840         WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
841
842         // optional features to save space
843         fl = fl & 0x0F;
844         if(self.spawnflags & 2)
845                 fl |= 0x10; // absolute count on toggle-on
846         if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
847                 fl |= 0x20; // 4 bytes - saves CPU
848         if(self.waterlevel || self.count != 1)
849                 fl |= 0x40; // 4 bytes - obscure features almost never used
850         if(self.mins != '0 0 0' || self.maxs != '0 0 0')
851                 fl |= 0x80; // 14 bytes - saves lots of space
852
853         WriteByte(MSG_ENTITY, fl);
854         if(fl & 2)
855         {
856                 if(self.state)
857                         WriteCoord(MSG_ENTITY, self.impulse);
858                 else
859                         WriteCoord(MSG_ENTITY, 0); // off
860         }
861         if(fl & 4)
862         {
863                 WriteCoord(MSG_ENTITY, self.origin_x);
864                 WriteCoord(MSG_ENTITY, self.origin_y);
865                 WriteCoord(MSG_ENTITY, self.origin_z);
866         }
867         if(fl & 1)
868         {
869                 if(self.model != "null")
870                 {
871                         WriteShort(MSG_ENTITY, self.modelindex);
872                         if(fl & 0x80)
873                         {
874                                 WriteCoord(MSG_ENTITY, self.mins_x);
875                                 WriteCoord(MSG_ENTITY, self.mins_y);
876                                 WriteCoord(MSG_ENTITY, self.mins_z);
877                                 WriteCoord(MSG_ENTITY, self.maxs_x);
878                                 WriteCoord(MSG_ENTITY, self.maxs_y);
879                                 WriteCoord(MSG_ENTITY, self.maxs_z);
880                         }
881                 }
882                 else
883                 {
884                         WriteShort(MSG_ENTITY, 0);
885                         if(fl & 0x80)
886                         {
887                                 WriteCoord(MSG_ENTITY, self.maxs_x);
888                                 WriteCoord(MSG_ENTITY, self.maxs_y);
889                                 WriteCoord(MSG_ENTITY, self.maxs_z);
890                         }
891                 }
892                 WriteShort(MSG_ENTITY, self.cnt);
893                 if(fl & 0x20)
894                 {
895                         WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
896                         WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
897                 }
898                 if(fl & 0x40)
899                 {
900                         WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
901                         WriteByte(MSG_ENTITY, self.count * 16.0);
902                 }
903                 WriteString(MSG_ENTITY, self.noise);
904                 if(self.noise != "")
905                 {
906                         WriteByte(MSG_ENTITY, floor(self.atten * 64));
907                         WriteByte(MSG_ENTITY, floor(self.volume * 255));
908                 }
909                 WriteString(MSG_ENTITY, self.bgmscript);
910                 if(self.bgmscript != "")
911                 {
912                         WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
913                         WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
914                         WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
915                         WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
916                 }
917         }
918         return 1;
919 }
920
921 void pointparticles_use()
922 {
923         self.state = !self.state;
924         self.SendFlags |= 2;
925 }
926
927 void pointparticles_think()
928 {
929         if(self.origin != self.oldorigin)
930         {
931                 self.SendFlags |= 4;
932                 self.oldorigin = self.origin;
933         }
934         self.nextthink = time;
935 }
936
937 void pointparticles_reset()
938 {
939         if(self.spawnflags & 1)
940                 self.state = 1;
941         else
942                 self.state = 0;
943 }
944
945 void spawnfunc_func_pointparticles()
946 {
947         if(self.model != "")
948                 setmodel(self, self.model);
949         if(self.noise != "")
950                 precache_sound (self.noise);
951         
952         if(!self.bgmscriptsustain)
953                 self.bgmscriptsustain = 1;
954         else if(self.bgmscriptsustain < 0)
955                 self.bgmscriptsustain = 0;
956
957         if(!self.atten)
958                 self.atten = ATTN_NORM;
959         else if(self.atten < 0)
960                 self.atten = 0;
961         if(!self.volume)
962                 self.volume = 1;
963         if(!self.count)
964                 self.count = 1;
965         if(!self.impulse)
966                 self.impulse = 1;
967
968         if(!self.modelindex)
969         {
970                 setorigin(self, self.origin + self.mins);
971                 setsize(self, '0 0 0', self.maxs - self.mins);
972         }
973         if(!self.cnt)
974                 self.cnt = particleeffectnum(self.mdl);
975
976         Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
977
978         IFTARGETED
979         {
980                 self.use = pointparticles_use;
981                 self.reset = pointparticles_reset;
982                 self.reset();
983         }
984         else
985                 self.state = 1;
986         self.think = pointparticles_think;
987         self.nextthink = time;
988 }
989
990 void spawnfunc_func_sparks()
991 {
992         // self.cnt is the amount of sparks that one burst will spawn
993         if(self.cnt < 1) {
994                 self.cnt = 25.0; // nice default value
995         }
996
997         // self.wait is the probability that a sparkthink will spawn a spark shower
998         // range: 0 - 1, but 0 makes little sense, so...
999         if(self.wait < 0.05) {
1000                 self.wait = 0.25; // nice default value
1001         }
1002
1003         self.count = self.cnt;
1004         self.mins = '0 0 0';
1005         self.maxs = '0 0 0';
1006         self.velocity = '0 0 -1';
1007         self.mdl = "TE_SPARK";
1008         self.impulse = 10 * self.wait; // by default 2.5/sec
1009         self.wait = 0;
1010         self.cnt = 0; // use mdl
1011
1012         spawnfunc_func_pointparticles();
1013 }
1014
1015 float rainsnow_SendEntity(entity to, float sf)
1016 {
1017         WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
1018         WriteByte(MSG_ENTITY, self.state);
1019         WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
1020         WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
1021         WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
1022         WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
1023         WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
1024         WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
1025         WriteShort(MSG_ENTITY, compressShortVector(self.dest));
1026         WriteShort(MSG_ENTITY, self.count);
1027         WriteByte(MSG_ENTITY, self.cnt);
1028         return 1;
1029 }
1030
1031 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
1032 This is an invisible area like a trigger, which rain falls inside of.
1033
1034 Keys:
1035 "velocity"
1036  falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
1037 "cnt"
1038  sets color of rain (default 12 - white)
1039 "count"
1040  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1041 */
1042 void spawnfunc_func_rain()
1043 {
1044         self.dest = self.velocity;
1045         self.velocity = '0 0 0';
1046         if (!self.dest)
1047                 self.dest = '0 0 -700';
1048         self.angles = '0 0 0';
1049         self.movetype = MOVETYPE_NONE;
1050         self.solid = SOLID_NOT;
1051         SetBrushEntityModel();
1052         if (!self.cnt)
1053                 self.cnt = 12;
1054         if (!self.count)
1055                 self.count = 2000;
1056         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1057         if (self.count < 1)
1058                 self.count = 1;
1059         if(self.count > 65535)
1060                 self.count = 65535;
1061
1062         self.state = 1; // 1 is rain, 0 is snow
1063         self.Version = 1;
1064
1065         Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1066 }
1067
1068
1069 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1070 This is an invisible area like a trigger, which snow falls inside of.
1071
1072 Keys:
1073 "velocity"
1074  falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1075 "cnt"
1076  sets color of rain (default 12 - white)
1077 "count"
1078  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1079 */
1080 void spawnfunc_func_snow()
1081 {
1082         self.dest = self.velocity;
1083         self.velocity = '0 0 0';
1084         if (!self.dest)
1085                 self.dest = '0 0 -300';
1086         self.angles = '0 0 0';
1087         self.movetype = MOVETYPE_NONE;
1088         self.solid = SOLID_NOT;
1089         SetBrushEntityModel();
1090         if (!self.cnt)
1091                 self.cnt = 12;
1092         if (!self.count)
1093                 self.count = 2000;
1094         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1095         if (self.count < 1)
1096                 self.count = 1;
1097         if(self.count > 65535)
1098                 self.count = 65535;
1099
1100         self.state = 0; // 1 is rain, 0 is snow
1101         self.Version = 1;
1102
1103         Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1104 }
1105
1106
1107 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
1108
1109 .float modelscale;
1110 void misc_laser_aim()
1111 {
1112         vector a;
1113         if(self.enemy)
1114         {
1115                 if(self.spawnflags & 2)
1116                 {
1117                         if(self.enemy.origin != self.mangle)
1118                         {
1119                                 self.mangle = self.enemy.origin;
1120                                 self.SendFlags |= 2;
1121                         }
1122                 }
1123                 else
1124                 {
1125                         a = vectoangles(self.enemy.origin - self.origin);
1126                         a_x = -a_x;
1127                         if(a != self.mangle)
1128                         {
1129                                 self.mangle = a;
1130                                 self.SendFlags |= 2;
1131                         }
1132                 }
1133         }
1134         else
1135         {
1136                 if(self.angles != self.mangle)
1137                 {
1138                         self.mangle = self.angles;
1139                         self.SendFlags |= 2;
1140                 }
1141         }
1142         if(self.origin != self.oldorigin)
1143         {
1144                 self.SendFlags |= 1;
1145                 self.oldorigin = self.origin;
1146         }
1147 }
1148
1149 void misc_laser_init()
1150 {
1151         if(self.target != "")
1152                 self.enemy = find(world, targetname, self.target);
1153 }
1154
1155 .entity pusher;
1156 void misc_laser_think()
1157 {
1158         vector o;
1159         entity oldself;
1160         entity hitent;
1161         vector hitloc;
1162
1163         self.nextthink = time;
1164
1165         if(!self.state)
1166                 return;
1167
1168         misc_laser_aim();
1169
1170         if(self.enemy)
1171         {
1172                 o = self.enemy.origin;
1173                 if not(self.spawnflags & 2)
1174                         o = self.origin + normalize(o - self.origin) * 32768;
1175         }
1176         else
1177         {
1178                 makevectors(self.mangle);
1179                 o = self.origin + v_forward * 32768;
1180         }
1181
1182         if(self.dmg || self.enemy.target != "")
1183         {
1184                 traceline(self.origin, o, MOVE_NORMAL, self);
1185         }
1186         hitent = trace_ent;
1187         hitloc = trace_endpos;
1188
1189         if(self.enemy.target != "") // DETECTOR laser
1190         {
1191                 if(trace_ent.iscreature)
1192                 {
1193                         self.pusher = hitent;
1194                         if(!self.count)
1195                         {
1196                                 self.count = 1;
1197
1198                                 oldself = self;
1199                                 self = self.enemy;
1200                                 activator = self.pusher;
1201                                 SUB_UseTargets();
1202                                 self = oldself;
1203                         }
1204                 }
1205                 else
1206                 {
1207                         if(self.count)
1208                         {
1209                                 self.count = 0;
1210
1211                                 oldself = self;
1212                                 self = self.enemy;
1213                                 activator = self.pusher;
1214                                 SUB_UseTargets();
1215                                 self = oldself;
1216                         }
1217                 }
1218         }
1219
1220         if(self.dmg)
1221         {
1222                 if(self.team)
1223                         if((self.spawnflags & 8 == 0) == (self.team != hitent.team))
1224                                 return;
1225                 if(hitent.takedamage)
1226                         Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1227         }
1228 }
1229
1230 float laser_SendEntity(entity to, float fl)
1231 {
1232         WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1233         fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1234         if(self.spawnflags & 2)
1235                 fl |= 0x80;
1236         if(self.alpha)
1237                 fl |= 0x40;
1238         if(self.scale != 1 || self.modelscale != 1)
1239                 fl |= 0x20;
1240         if(self.spawnflags & 4)
1241                 fl |= 0x10;
1242         WriteByte(MSG_ENTITY, fl);
1243         if(fl & 1)
1244         {
1245                 WriteCoord(MSG_ENTITY, self.origin_x);
1246                 WriteCoord(MSG_ENTITY, self.origin_y);
1247                 WriteCoord(MSG_ENTITY, self.origin_z);
1248         }
1249         if(fl & 8)
1250         {
1251                 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1252                 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1253                 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1254                 if(fl & 0x40)
1255                         WriteByte(MSG_ENTITY, self.alpha * 255.0);
1256                 if(fl & 0x20)
1257                 {
1258                         WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1259                         WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1260                 }
1261                 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1262                         WriteShort(MSG_ENTITY, self.cnt + 1);
1263         }
1264         if(fl & 2)
1265         {
1266                 if(fl & 0x80)
1267                 {
1268                         WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1269                         WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1270                         WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1271                 }
1272                 else
1273                 {
1274                         WriteAngle(MSG_ENTITY, self.mangle_x);
1275                         WriteAngle(MSG_ENTITY, self.mangle_y);
1276                 }
1277         }
1278         if(fl & 4)
1279                 WriteByte(MSG_ENTITY, self.state);
1280         return 1;
1281 }
1282
1283 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1284 Any object touching the beam will be hurt
1285 Keys:
1286 "target"
1287  spawnfunc_target_position where the laser ends
1288 "mdl"
1289  name of beam end effect to use
1290 "colormod"
1291  color of the beam (default: red)
1292 "dmg"
1293  damage per second (-1 for a laser that kills immediately)
1294 */
1295 void laser_use()
1296 {
1297         self.state = !self.state;
1298         self.SendFlags |= 4;
1299         misc_laser_aim();
1300 }
1301
1302 void laser_reset()
1303 {
1304         if(self.spawnflags & 1)
1305                 self.state = 1;
1306         else
1307                 self.state = 0;
1308 }
1309
1310 void spawnfunc_misc_laser()
1311 {
1312         if(self.mdl)
1313         {
1314                 if(self.mdl == "none")
1315                         self.cnt = -1;
1316                 else
1317                 {
1318                         self.cnt = particleeffectnum(self.mdl);
1319                         if(self.cnt < 0)
1320                                 if(self.dmg)
1321                                         self.cnt = particleeffectnum("laser_deadly");
1322                 }
1323         }
1324         else if(!self.cnt)
1325         {
1326                 if(self.dmg)
1327                         self.cnt = particleeffectnum("laser_deadly");
1328                 else
1329                         self.cnt = -1;
1330         }
1331         if(self.cnt < 0)
1332                 self.cnt = -1;
1333
1334         if(self.colormod == '0 0 0')
1335                 if(!self.alpha)
1336                         self.colormod = '1 0 0';
1337         if(!self.message)
1338                 self.message = "saw the light";
1339         if (!self.message2)
1340                 self.message2 = "was pushed into a laser by";
1341         if(!self.scale)
1342                 self.scale = 1;
1343         if(!self.modelscale)
1344                 self.modelscale = 1;
1345         else if(self.modelscale < 0)
1346                 self.modelscale = 0;
1347         self.think = misc_laser_think;
1348         self.nextthink = time;
1349         InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1350
1351         self.mangle = self.angles;
1352
1353         Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1354
1355         IFTARGETED
1356         {
1357                 self.reset = laser_reset;
1358                 laser_reset();
1359                 self.use = laser_use;
1360         }
1361         else
1362                 self.state = 1;
1363 }
1364
1365 // tZorks trigger impulse / gravity
1366 .float radius;
1367 .float falloff;
1368 .float strength;
1369 .float lastpushtime;
1370
1371 // targeted (directional) mode
1372 void trigger_impulse_touch1()
1373 {
1374         entity targ;
1375     float pushdeltatime;
1376     float str;
1377
1378         if (self.active != ACTIVE_ACTIVE) 
1379                 return;
1380
1381         if (!isPushable(other))
1382                 return;
1383
1384         EXACTTRIGGER_TOUCH;
1385
1386     targ = find(world, targetname, self.target);
1387     if(!targ)
1388     {
1389         objerror("trigger_force without a (valid) .target!\n");
1390         remove(self);
1391         return;
1392     }
1393
1394     str = min(self.radius, vlen(self.origin - other.origin));
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 }