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