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