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