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