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