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