]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/g_triggers.qc
Merge remote branch 'origin/samual/hitindication'
[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                 remove(self);
1726         }
1727         else
1728         {
1729                 if(self.spawnflags & 2)
1730                 {
1731                         dst.movetype = MOVETYPE_FOLLOW;
1732                         dst.aiment = src;
1733                         // dst.punchangle = '0 0 0'; // keep unchanged
1734                         dst.view_ofs = dst.origin;
1735                         dst.v_angle = dst.angles;
1736                 }
1737                 else
1738                 {
1739                         follow_sameorigin(dst, src);
1740                 }
1741
1742                 remove(self);
1743         }
1744 }
1745
1746 void spawnfunc_misc_follow()
1747 {
1748         InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1749 }
1750
1751
1752
1753 void gamestart_use() {
1754         activator = self;
1755         SUB_UseTargets();
1756         remove(self);
1757 }
1758
1759 void spawnfunc_trigger_gamestart() {
1760         self.use = gamestart_use;
1761         self.reset2 = spawnfunc_trigger_gamestart;
1762
1763         if(self.wait)
1764         {
1765                 self.think = self.use;
1766                 self.nextthink = game_starttime + self.wait;
1767         }
1768         else
1769                 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1770 }
1771
1772
1773
1774
1775 .entity voicescript; // attached voice script
1776 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1777 .float voicescript_nextthink; // time to play next voice
1778 .float voicescript_voiceend; // time when this voice ends
1779
1780 void target_voicescript_clear(entity pl)
1781 {
1782         pl.voicescript = world;
1783 }
1784
1785 void target_voicescript_use()
1786 {
1787         if(activator.voicescript != self)
1788         {
1789                 activator.voicescript = self;
1790                 activator.voicescript_index = 0;
1791                 activator.voicescript_nextthink = time + self.delay;
1792         }
1793 }
1794
1795 void target_voicescript_next(entity pl)
1796 {
1797         entity vs;
1798         float i, n, dt;
1799
1800         vs = pl.voicescript;
1801         if(!vs)
1802                 return;
1803         if(vs.message == "")
1804                 return;
1805         if(pl.classname != "player")
1806                 return;
1807         if(gameover)
1808                 return;
1809
1810         if(time >= pl.voicescript_voiceend)
1811         {
1812                 if(time >= pl.voicescript_nextthink)
1813                 {
1814                         // get the next voice...
1815                         n = tokenize_console(vs.message);
1816
1817                         if(pl.voicescript_index < vs.cnt)
1818                                 i = pl.voicescript_index * 2;
1819                         else if(n > vs.cnt * 2)
1820                                 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1821                         else
1822                                 i = -1;
1823
1824                         if(i >= 0)
1825                         {
1826                                 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1827                                 dt = stof(argv(i + 1));
1828                                 if(dt >= 0)
1829                                 {
1830                                         pl.voicescript_voiceend = time + dt;
1831                                         pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1832                                 }
1833                                 else
1834                                 {
1835                                         pl.voicescript_voiceend = time - dt;
1836                                         pl.voicescript_nextthink = pl.voicescript_voiceend;
1837                                 }
1838
1839                                 pl.voicescript_index += 1;
1840                         }
1841                         else
1842                         {
1843                                 pl.voicescript = world; // stop trying then
1844                         }
1845                 }
1846         }
1847 }
1848
1849 void spawnfunc_target_voicescript()
1850 {
1851         // netname: directory of the sound files
1852         // message: list of "sound file" duration "sound file" duration, a *, and again a list
1853         //          foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1854         //          Here, a - in front of the duration means that no delay is to be
1855         //          added after this message
1856         // wait: average time between messages
1857         // delay: initial delay before the first message
1858         
1859         float i, n;
1860         self.use = target_voicescript_use;
1861
1862         n = tokenize_console(self.message);
1863         self.cnt = n / 2;
1864         for(i = 0; i+1 < n; i += 2)
1865         {
1866                 if(argv(i) == "*")
1867                 {
1868                         self.cnt = i / 2;
1869                         ++i;
1870                 }
1871                 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1872         }
1873 }
1874
1875
1876
1877 void trigger_relay_teamcheck_use()
1878 {
1879         if(activator.team)
1880         {
1881                 if(self.spawnflags & 2)
1882                 {
1883                         if(activator.team != self.team)
1884                                 SUB_UseTargets();
1885                 }
1886                 else
1887                 {
1888                         if(activator.team == self.team)
1889                                 SUB_UseTargets();
1890                 }
1891         }
1892         else
1893         {
1894                 if(self.spawnflags & 1)
1895                         SUB_UseTargets();
1896         }
1897 }
1898
1899 void trigger_relay_teamcheck_reset()
1900 {
1901         self.team = self.team_saved;
1902 }
1903
1904 void spawnfunc_trigger_relay_teamcheck()
1905 {
1906         self.team_saved = self.team;
1907         self.use = trigger_relay_teamcheck_use;
1908         self.reset = trigger_relay_teamcheck_reset;
1909 }
1910
1911
1912
1913 void trigger_disablerelay_use()
1914 {
1915         entity e;
1916
1917         float a, b;
1918         a = b = 0;
1919
1920         for(e = world; (e = find(e, targetname, self.target)); )
1921         {
1922                 if(e.use == SUB_UseTargets)
1923                 {
1924                         e.use = SUB_DontUseTargets;
1925                         ++a;
1926                 }
1927                 else if(e.use == SUB_DontUseTargets)
1928                 {
1929                         e.use = SUB_UseTargets;
1930                         ++b;
1931                 }
1932         }
1933
1934         if((!a) == (!b))
1935                 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1936 }
1937
1938 void spawnfunc_trigger_disablerelay()
1939 {
1940         self.use = trigger_disablerelay_use;
1941 }
1942
1943 float magicear_matched;
1944 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1945 {
1946         float domatch, dotrigger, matchstart, l;
1947         string s, msg;
1948         entity oldself;
1949
1950         magicear_matched = FALSE;
1951
1952         dotrigger = ((self.classname == "player") && (self.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1953         domatch = ((ear.spawnflags & 32) || dotrigger);
1954         if not(domatch)
1955                 return msgin;
1956
1957         if(privatesay)
1958         {
1959                 if(ear.spawnflags & 4)
1960                         return msgin;
1961         }
1962         else
1963         {
1964                 if(!teamsay)
1965                         if(ear.spawnflags & 1)
1966                                 return msgin;
1967                 if(teamsay > 0)
1968                         if(ear.spawnflags & 2)
1969                                 return msgin;
1970                 if(teamsay < 0)
1971                         if(ear.spawnflags & 8)
1972                                 return msgin;
1973         }
1974         
1975         matchstart = -1;
1976         l = strlen(ear.message);
1977
1978         if(self.spawnflags & 128)
1979                 msg = msgin;
1980         else
1981                 msg = strdecolorize(msgin);
1982
1983         if(substring(ear.message, 0, 1) == "*")
1984         {
1985                 if(substring(ear.message, -1, 1) == "*")
1986                 {
1987                         // two wildcards
1988                         // as we need multi-replacement here...
1989                         s = substring(ear.message, 1, -2);
1990                         l -= 2;
1991                         if(strstrofs(msg, s, 0) >= 0)
1992                                 matchstart = -2; // we use strreplace on s
1993                 }
1994                 else
1995                 {
1996                         // match at start
1997                         s = substring(ear.message, 1, -1);
1998                         l -= 1;
1999                         if(substring(msg, -l, l) == s)
2000                                 matchstart = strlen(msg) - l;
2001                 }
2002         }
2003         else
2004         {
2005                 if(substring(ear.message, -1, 1) == "*")
2006                 {
2007                         // match at end
2008                         s = substring(ear.message, 0, -2);
2009                         l -= 1;
2010                         if(substring(msg, 0, l) == s)
2011                                 matchstart = 0;
2012                 }
2013                 else
2014                 {
2015                         // full match
2016                         s = ear.message;
2017                         if(msg == ear.message)
2018                                 matchstart = 0;
2019                 }
2020         }
2021
2022         if(matchstart == -1) // no match
2023                 return msgin;
2024
2025         magicear_matched = TRUE;
2026
2027         if(dotrigger)
2028         {
2029                 oldself = activator = self;
2030                 self = ear;
2031                 SUB_UseTargets();
2032                 self = oldself;
2033         }
2034
2035         if(ear.spawnflags & 16)
2036         {
2037                 return ear.netname;
2038         }
2039         else if(ear.netname != "")
2040         {
2041                 if(matchstart < 0)
2042                         return strreplace(s, ear.netname, msg);
2043                 else
2044                         return strcat(
2045                                 substring(msg, 0, matchstart),
2046                                 ear.netname,
2047                                 substring(msg, matchstart + l, -1)
2048                         );
2049         }
2050         else
2051                 return msgin;
2052 }
2053
2054 entity magicears;
2055 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2056 {
2057         entity ear;
2058         string msgout;
2059         for(ear = magicears; ear; ear = ear.enemy)
2060         {
2061                 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2062                 if not(ear.spawnflags & 64)
2063                         if(magicear_matched)
2064                                 return msgout;
2065                 msgin = msgout;
2066         }
2067         return msgin;
2068 }
2069
2070 void spawnfunc_trigger_magicear()
2071 {
2072         self.enemy = magicears;
2073         magicears = self;
2074
2075         // actually handled in "say" processing
2076         // spawnflags:
2077         //   1 = ignore say
2078         //   2 = ignore teamsay
2079         //   4 = ignore tell
2080         //   8 = ignore tell to unknown player
2081         //   16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2082         //   32 = perform the replacement even if outside the radius or dead
2083         //   64 = continue replacing/triggering even if this one matched
2084         // message: either
2085         //   *pattern*
2086         // or
2087         //   *pattern
2088         // or
2089         //   pattern*
2090         // or
2091         //   pattern
2092         // netname:
2093         //   if set, replacement for the matched text
2094         // radius:
2095         //   "hearing distance"
2096         // target:
2097         //   what to trigger
2098 }
2099
2100 void relay_activators_use()
2101 {
2102         entity trg, os;
2103         
2104         os = self;
2105         
2106         for(trg = world; (trg = find(trg, targetname, os.target)); )
2107         {
2108                 self = trg;
2109                 if (trg.setactive)
2110                         trg.setactive(os.cnt);
2111                 else
2112                 {
2113                         //bprint("Not using setactive\n");
2114                         if(os.cnt == ACTIVE_TOGGLE)
2115                                 if(trg.active == ACTIVE_ACTIVE)
2116                                         trg.active = ACTIVE_NOT;
2117                                 else    
2118                                         trg.active = ACTIVE_ACTIVE;
2119                         else
2120                                 trg.active = os.cnt;
2121                 }               
2122         }
2123         self = os;
2124 }
2125
2126 void spawnfunc_relay_activate()
2127 {
2128         self.cnt = ACTIVE_ACTIVE;
2129         self.use = relay_activators_use;
2130 }
2131
2132 void spawnfunc_relay_deactivate()
2133 {
2134         self.cnt = ACTIVE_NOT;
2135         self.use = relay_activators_use;        
2136 }
2137
2138 void spawnfunc_relay_activatetoggle()
2139 {
2140         self.cnt = ACTIVE_TOGGLE;
2141         self.use = relay_activators_use;        
2142 }
2143
2144 .string chmap, gametype;
2145 void spawnfunc_target_changelevel_use()
2146 {
2147         if(self.gametype != "")
2148                 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2149
2150         if (self.chmap == "")
2151                 localcmd("endmatch\n");
2152         else
2153                 localcmd(strcat("changelevel ", self.chmap, "\n"));
2154 };
2155
2156 void spawnfunc_target_changelevel()
2157 {
2158         self.use = spawnfunc_target_changelevel_use;
2159 };