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