]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/g_triggers.qc
some fixes to what can be pushed, teleported, trigger_impulsed
[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         if(stemp.target_random)
92                 RandomSelection_Init();
93
94         for(i = 0; i < 4; ++i)
95         {
96                 switch(i)
97                 {
98                         default:
99                         case 0: s = stemp.target; break;
100                         case 1: s = stemp.target2; break;
101                         case 2: s = stemp.target3; break;
102                         case 3: s = stemp.target4; break;
103                 }
104                 if (s != "")
105                 {
106                         for(t = world; (t = find(t, targetname, s)); )
107                         if(t.use)
108                         {
109                                 if(stemp.target_random)
110                                 {
111                                         RandomSelection_Add(t, 0, string_null, 1, 0);
112                                 }
113                                 else
114                                 {
115                                         self = t;
116                                         other = stemp;
117                                         activator = act;
118                                         self.use();
119                                 }
120                         }
121                 }
122         }
123
124         if(stemp.target_random && RandomSelection_chosen_ent)
125         {
126                 self = RandomSelection_chosen_ent;
127                 other = stemp;
128                 activator = act;
129                 self.use();
130         }
131
132         activator = act;
133         self = stemp;
134         other = otemp;
135 };
136
137
138 //=============================================================================
139
140 float   SPAWNFLAG_NOMESSAGE = 1;
141 float   SPAWNFLAG_NOTOUCH = 1;
142
143 // the wait time has passed, so set back up for another activation
144 void multi_wait()
145 {
146         if (self.max_health)
147         {
148                 self.health = self.max_health;
149                 self.takedamage = DAMAGE_YES;
150                 self.solid = SOLID_BBOX;
151         }
152 };
153
154
155 // the trigger was just touched/killed/used
156 // self.enemy should be set to the activator so it can be held through a delay
157 // so wait for the delay time before firing
158 void multi_trigger()
159 {
160         if (self.nextthink > time)
161         {
162                 return;         // allready been triggered
163         }
164
165         if (self.classname == "trigger_secret")
166         {
167                 if (self.enemy.classname != "player")
168                         return;
169                 found_secrets = found_secrets + 1;
170                 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
171         }
172
173         if (self.noise)
174                 sound (self.enemy, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
175
176 // don't trigger again until reset
177         self.takedamage = DAMAGE_NO;
178
179         activator = self.enemy;
180         other = self.goalentity;
181         SUB_UseTargets();
182
183         if (self.wait > 0)
184         {
185                 self.think = multi_wait;
186                 self.nextthink = time + self.wait;
187         }
188         else if (self.wait == 0)
189         {
190                 multi_wait(); // waiting finished
191         }
192         else
193         {       // we can't just remove (self) here, because this is a touch function
194                 // called wheil C code is looping through area links...
195                 self.touch = SUB_Null;
196         }
197 };
198
199 void multi_use()
200 {
201         self.goalentity = other;
202         self.enemy = activator;
203         multi_trigger();
204 };
205
206 void multi_touch()
207 {
208         if not(self.spawnflags & 2)
209                 if not(other.iscreature)
210                         return;
211
212         if(self.team)
213                 if((self.spawnflags & 4 == 0) == (self.team != other.team))
214                         return;
215
216 // if the trigger has an angles field, check player's facing direction
217         if (self.movedir != '0 0 0')
218         {
219                 makevectors (other.angles);
220                 if (v_forward * self.movedir < 0)
221                         return;         // not facing the right way
222         }
223
224         EXACTTRIGGER_TOUCH;
225
226         self.enemy = other;
227         self.goalentity = other;
228         multi_trigger ();
229 };
230
231 void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
232 {
233         if (!self.takedamage)
234                 return;
235         if(self.spawnflags & DOOR_NOSPLASH)
236                 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
237                         return;
238         self.health = self.health - damage;
239         if (self.health <= 0)
240         {
241                 self.enemy = attacker;
242                 self.goalentity = inflictor;
243                 multi_trigger();
244         }
245 }
246
247 void multi_reset()
248 {
249         if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
250                 self.touch = multi_touch;
251         if (self.max_health)
252         {
253                 self.health = self.max_health;
254                 self.takedamage = DAMAGE_YES;
255                 self.solid = SOLID_BBOX;
256         }
257         self.think = SUB_Null;
258         self.team = self.team_saved;
259 }
260
261 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
262 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.
263 If "delay" is set, the trigger waits some time after activating before firing.
264 "wait" : Seconds between triggerings. (.2 default)
265 If notouch is set, the trigger is only fired by other entities, not by touching.
266 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
267 sounds
268 1)      secret
269 2)      beep beep
270 3)      large switch
271 4)
272 set "message" to text string
273 */
274 void spawnfunc_trigger_multiple()
275 {
276         self.reset = multi_reset;
277         if (self.sounds == 1)
278         {
279                 precache_sound ("misc/secret.wav");
280                 self.noise = "misc/secret.wav";
281         }
282         else if (self.sounds == 2)
283         {
284                 precache_sound ("misc/talk.wav");
285                 self.noise = "misc/talk.wav";
286         }
287         else if (self.sounds == 3)
288         {
289                 precache_sound ("misc/trigger1.wav");
290                 self.noise = "misc/trigger1.wav";
291         }
292
293         if (!self.wait)
294                 self.wait = 0.2;
295         else if(self.wait < -1)
296                 self.wait = 0;
297         self.use = multi_use;
298
299         EXACTTRIGGER_INIT;
300
301         self.team_saved = self.team;
302
303         if (self.health)
304         {
305                 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
306                         objerror ("health and notouch don't make sense\n");
307                 self.max_health = self.health;
308                 self.event_damage = multi_eventdamage;
309                 self.takedamage = DAMAGE_YES;
310                 self.solid = SOLID_BBOX;
311                 setorigin (self, self.origin);  // make sure it links into the world
312         }
313         else
314         {
315                 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
316                 {
317                         self.touch = multi_touch;
318                         setorigin (self, self.origin);  // make sure it links into the world
319                 }
320         }
321 };
322
323
324 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
325 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
326 "targetname".  If "health" is set, the trigger must be killed to activate.
327 If notouch is set, the trigger is only fired by other entities, not by touching.
328 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
329 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.
330 sounds
331 1)      secret
332 2)      beep beep
333 3)      large switch
334 4)
335 set "message" to text string
336 */
337 void spawnfunc_trigger_once()
338 {
339         self.wait = -1;
340         spawnfunc_trigger_multiple();
341 };
342
343 //=============================================================================
344
345 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
346 This fixed size trigger cannot be touched, it can only be fired by other events.  It can contain killtargets, targets, delays, and messages.
347 */
348 void spawnfunc_trigger_relay()
349 {
350         self.use = SUB_UseTargets;
351         self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
352 };
353
354 void delay_use()
355 {
356     self.think = SUB_UseTargets;
357     self.nextthink = self.wait;
358 }
359
360 void delay_reset()
361 {
362         self.think = SUB_Null;
363 }
364
365 void spawnfunc_trigger_delay()
366 {
367     if(!self.wait)
368         self.wait = 1;
369
370     self.use = delay_use;
371     self.reset = delay_reset;
372 }
373
374 //=============================================================================
375
376
377 void counter_use()
378 {
379         self.count = self.count - 1;
380         if (self.count < 0)
381                 return;
382
383         if (self.count != 0)
384         {
385                 if (activator.classname == "player"
386                 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
387                 {
388                         if (self.count >= 4)
389                                 centerprint (activator, "There are more to go...");
390                         else if (self.count == 3)
391                                 centerprint (activator, "Only 3 more to go...");
392                         else if (self.count == 2)
393                                 centerprint (activator, "Only 2 more to go...");
394                         else
395                                 centerprint (activator, "Only 1 more to go...");
396                 }
397                 return;
398         }
399
400         if (activator.classname == "player"
401         && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
402                 centerprint(activator, "Sequence completed!");
403         self.enemy = activator;
404         multi_trigger ();
405 };
406
407 void counter_reset()
408 {
409         self.count = self.cnt;
410         multi_reset();
411 }
412
413 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
414 Acts as an intermediary for an action that takes multiple inputs.
415
416 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
417
418 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
419 */
420 void spawnfunc_trigger_counter()
421 {
422         self.wait = -1;
423         if (!self.count)
424                 self.count = 2;
425         self.cnt = self.count;
426
427         self.use = counter_use;
428         self.reset = counter_reset;
429 };
430
431 void trigger_hurt_use()
432 {
433         if(activator.classname == "player")
434                 self.enemy = activator;
435         else
436                 self.enemy = world; // let's just destroy it, if taking over is too much work
437 }
438
439 .float triggerhurttime;
440 void trigger_hurt_touch()
441 {
442         if (self.active != ACTIVE_ACTIVE) 
443                 return;
444
445         if(self.team)
446                 if((self.spawnflags & 4 == 0) == (self.team != other.team))
447                         return;
448
449         // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
450         if (other.iscreature)
451         {
452                 if (other.takedamage)
453                 if (other.triggerhurttime < time)
454                 {
455                         EXACTTRIGGER_TOUCH;
456                         other.triggerhurttime = time + 1;
457
458                         entity own;
459                         own = self.enemy;
460                         if(own.classname != "player")
461                         {
462                                 own = self;
463                                 self.enemy = world; // I still hate you all
464                         }
465
466                         Damage (other, self, own, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
467                 }
468         }
469         else
470         {
471                 if (!other.owner)
472                 {
473                         if (other.items & IT_KEY1 || other.items & IT_KEY2)     // reset flag
474                         {
475                                 EXACTTRIGGER_TOUCH;
476                                 other.pain_finished = min(other.pain_finished, time + 2);
477                         }
478                         else if (other.classname == "rune")                     // reset runes
479                         {
480                                 EXACTTRIGGER_TOUCH;
481                                 other.nextthink = min(other.nextthink, time + 1);
482                         }
483                 }
484         }
485
486         return;
487 };
488
489 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
490 Any object touching this will be hurt
491 set dmg to damage amount
492 defalt dmg = 5
493 */
494 .entity trigger_hurt_next;
495 entity trigger_hurt_last;
496 entity trigger_hurt_first;
497 void spawnfunc_trigger_hurt()
498 {
499         EXACTTRIGGER_INIT;
500         self.active = ACTIVE_ACTIVE;
501         self.touch = trigger_hurt_touch;
502         self.use = trigger_hurt_use;
503         self.enemy = world; // I hate you all
504         if (!self.dmg)
505                 self.dmg = 1000;
506         if (!self.message)
507                 self.message = "was in the wrong place";
508         if (!self.message2)
509                 self.message2 = "was thrown into a world of hurt by";
510         // self.message = "someone like %s always gets wrongplaced";
511
512         if(!trigger_hurt_first)
513                 trigger_hurt_first = self;
514         if(trigger_hurt_last)
515                 trigger_hurt_last.trigger_hurt_next = self;
516         trigger_hurt_last = self;
517 };
518
519 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
520 {
521         entity th;
522
523         for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
524                 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
525                         return TRUE;
526
527         return FALSE;
528 }
529
530 //////////////////////////////////////////////////////////////
531 //
532 //
533 //
534 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
535 //
536 //////////////////////////////////////////////////////////////
537
538 .float triggerhealtime;
539 void trigger_heal_touch()
540 {
541         if (self.active != ACTIVE_ACTIVE) 
542                 return;
543         
544         // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
545         if (other.iscreature)
546         {
547                 if (other.takedamage)
548                 if (other.triggerhealtime < time)
549                 {
550                         EXACTTRIGGER_TOUCH;
551                         other.triggerhealtime = time + 1;
552                         
553                         if (other.health < self.max_health)
554                         {
555                                 other.health = min(other.health + self.health, self.max_health);
556                                 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
557                                 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
558                         }
559                 }
560         }
561 };
562
563 void spawnfunc_trigger_heal()
564 {
565         self.active = ACTIVE_ACTIVE;
566         
567         EXACTTRIGGER_INIT;
568         self.touch = trigger_heal_touch;
569         if (!self.health)
570                 self.health = 10;
571         if (!self.max_health)
572                 self.max_health = 200; //Max health topoff for field
573         if(self.noise == "")
574                 self.noise = "misc/mediumhealth.wav";
575         precache_sound(self.noise);
576 };
577
578
579 //////////////////////////////////////////////////////////////
580 //
581 //
582 //
583 //End trigger_heal
584 //
585 //////////////////////////////////////////////////////////////
586
587 .entity trigger_gravity_check;
588 void trigger_gravity_remove(entity own)
589 {
590         if(own.trigger_gravity_check.owner == own)
591         {
592                 UpdateCSQCProjectile(own);
593                 own.gravity = own.trigger_gravity_check.gravity;
594                 remove(own.trigger_gravity_check);
595         }
596         else
597                 backtrace("Removing a trigger_gravity_check with no valid owner");
598         own.trigger_gravity_check = world;
599 }
600 void trigger_gravity_check_think()
601 {
602         // This spawns when a player enters the gravity zone and checks if he left.
603         // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
604         // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
605         if(self.count <= 0)
606         {
607                 if(self.owner.trigger_gravity_check == self)
608                         trigger_gravity_remove(self.owner);
609                 else
610                         remove(self);
611                 return;
612         }
613         else
614         {
615                 self.count -= 1;
616                 self.nextthink = time;
617         }
618 };
619
620 void trigger_gravity_use()
621 {
622         self.state = !self.state;
623 };
624
625 void trigger_gravity_touch()
626 {
627         float g;
628
629         if(self.state != TRUE)
630                 return;
631
632         EXACTTRIGGER_TOUCH;
633
634         g = self.gravity;
635
636         if not(self.spawnflags & 1)
637         {
638                 if(other.trigger_gravity_check)
639                 {
640                         if(self == other.trigger_gravity_check.enemy)
641                         {
642                                 // same?
643                                 other.trigger_gravity_check.count = 2; // gravity one more frame...
644                                 return;
645                         }
646
647                         // compare prio
648                         if(self.cnt > other.trigger_gravity_check.enemy.cnt)
649                                 trigger_gravity_remove(other);
650                         else
651                                 return;
652                 }
653                 other.trigger_gravity_check = spawn();
654                 other.trigger_gravity_check.enemy = self;
655                 other.trigger_gravity_check.owner = other;
656                 other.trigger_gravity_check.gravity = other.gravity;
657                 other.trigger_gravity_check.think = trigger_gravity_check_think;
658                 other.trigger_gravity_check.nextthink = time;
659                 other.trigger_gravity_check.count = 2;
660                 if(other.gravity)
661                         g *= other.gravity;
662         }
663
664         if (other.gravity != g)
665         {
666                 other.gravity = g;
667                 if(self.noise != "")
668                         sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
669                 UpdateCSQCProjectile(self.owner);
670         }
671 };
672
673 void spawnfunc_trigger_gravity()
674 {
675         if(self.gravity == 1)
676                 return;
677
678         EXACTTRIGGER_INIT;
679         self.touch = trigger_gravity_touch;
680         if(self.noise != "")
681                 precache_sound(self.noise);
682
683         self.state = TRUE;
684         IFTARGETED
685         {
686                 self.use = trigger_gravity_use;
687                 if(self.spawnflags & 2)
688                         self.state = FALSE;
689         }
690 };
691
692 //=============================================================================
693
694 // TODO add a way to do looped sounds with sound(); then complete this entity
695 .float volume, atten;
696 void target_speaker_use_off();
697 void target_speaker_use_activator()
698 {
699         if(clienttype(activator) != CLIENTTYPE_REAL)
700                 return;
701         string snd;
702         if(substring(self.noise, 0, 1) == "*")
703         {
704                 var .string sample;
705                 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
706                 if(GetPlayerSoundSampleField_notFound)
707                         snd = "misc/null.wav";
708                 else if(activator.sample == "")
709                         snd = "misc/null.wav";
710                 else
711                 {
712                         tokenize_console(activator.sample);
713                         float n;
714                         n = stof(argv(1));
715                         if(n > 0)
716                                 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
717                         else
718                                 snd = strcat(argv(0), ".wav"); // randomization
719                 }
720         }
721         else
722                 snd = self.noise;
723         msg_entity = activator;
724         soundto(MSG_ONE, self, CHAN_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
725 }
726 void target_speaker_use_on()
727 {
728         string snd;
729         if(substring(self.noise, 0, 1) == "*")
730         {
731                 var .string sample;
732                 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
733                 if(GetPlayerSoundSampleField_notFound)
734                         snd = "misc/null.wav";
735                 else if(activator.sample == "")
736                         snd = "misc/null.wav";
737                 else
738                 {
739                         tokenize_console(activator.sample);
740                         float n;
741                         n = stof(argv(1));
742                         if(n > 0)
743                                 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
744                         else
745                                 snd = strcat(argv(0), ".wav"); // randomization
746                 }
747         }
748         else
749                 snd = self.noise;
750         sound(self, CHAN_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
751         if(self.spawnflags & 3)
752                 self.use = target_speaker_use_off;
753 }
754 void target_speaker_use_off()
755 {
756         sound(self, CHAN_TRIGGER, "misc/null.wav", VOL_BASE * self.volume, self.atten);
757         self.use = target_speaker_use_on;
758 }
759 void target_speaker_reset()
760 {
761         if(self.spawnflags & 1) // LOOPED_ON
762         {
763                 if(self.use == target_speaker_use_on)
764                         target_speaker_use_on();
765         }
766         else if(self.spawnflags & 2)
767         {
768                 if(self.use == target_speaker_use_off)
769                         target_speaker_use_off();
770         }
771 }
772
773 void spawnfunc_target_speaker()
774 {
775         // TODO: "*" prefix to sound file name
776         // TODO: wait and random (just, HOW? random is not a field)
777         if(self.noise)
778                 precache_sound (self.noise);
779
780         if(!self.atten && !(self.spawnflags & 4))
781         {
782                 IFTARGETED
783                         self.atten = ATTN_NORM;
784                 else
785                         self.atten = ATTN_STATIC;
786         }
787         else if(self.atten < 0)
788                 self.atten = 0;
789
790         if(!self.volume)
791                 self.volume = 1;
792
793         IFTARGETED
794         {
795                 if(self.spawnflags & 8) // ACTIVATOR
796                         self.use = target_speaker_use_activator;
797                 else if(self.spawnflags & 1) // LOOPED_ON
798                 {
799                         target_speaker_use_on();
800                         self.reset = target_speaker_reset;
801                 }
802                 else if(self.spawnflags & 2) // LOOPED_OFF
803                 {
804                         self.use = target_speaker_use_on;
805                         self.reset = target_speaker_reset;
806                 }
807                 else
808                         self.use = target_speaker_use_on;
809         }
810         else if(self.spawnflags & 1) // LOOPED_ON
811         {
812                 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
813                 remove(self);
814         }
815         else if(self.spawnflags & 2) // LOOPED_OFF
816         {
817                 objerror("This sound entity can never be activated");
818         }
819         else
820         {
821                 // Quake/Nexuiz fallback
822                 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
823                 remove(self);
824         }
825 };
826
827
828 void spawnfunc_func_stardust() {
829         self.effects = EF_STARDUST;
830 }
831
832 .string bgmscript;
833 .float bgmscriptattack;
834 .float bgmscriptdecay;
835 .float bgmscriptsustain;
836 .float bgmscriptrelease;
837 float pointparticles_SendEntity(entity to, float fl)
838 {
839         WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
840
841         // optional features to save space
842         fl = fl & 0x0F;
843         if(self.spawnflags & 2)
844                 fl |= 0x10; // absolute count on toggle-on
845         if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
846                 fl |= 0x20; // 4 bytes - saves CPU
847         if(self.waterlevel || self.count != 1)
848                 fl |= 0x40; // 4 bytes - obscure features almost never used
849         if(self.mins != '0 0 0' || self.maxs != '0 0 0')
850                 fl |= 0x80; // 14 bytes - saves lots of space
851
852         WriteByte(MSG_ENTITY, fl);
853         if(fl & 2)
854         {
855                 if(self.state)
856                         WriteCoord(MSG_ENTITY, self.impulse);
857                 else
858                         WriteCoord(MSG_ENTITY, 0); // off
859         }
860         if(fl & 4)
861         {
862                 WriteCoord(MSG_ENTITY, self.origin_x);
863                 WriteCoord(MSG_ENTITY, self.origin_y);
864                 WriteCoord(MSG_ENTITY, self.origin_z);
865         }
866         if(fl & 1)
867         {
868                 if(self.model != "null")
869                 {
870                         WriteShort(MSG_ENTITY, self.modelindex);
871                         if(fl & 0x80)
872                         {
873                                 WriteCoord(MSG_ENTITY, self.mins_x);
874                                 WriteCoord(MSG_ENTITY, self.mins_y);
875                                 WriteCoord(MSG_ENTITY, self.mins_z);
876                                 WriteCoord(MSG_ENTITY, self.maxs_x);
877                                 WriteCoord(MSG_ENTITY, self.maxs_y);
878                                 WriteCoord(MSG_ENTITY, self.maxs_z);
879                         }
880                 }
881                 else
882                 {
883                         WriteShort(MSG_ENTITY, 0);
884                         if(fl & 0x80)
885                         {
886                                 WriteCoord(MSG_ENTITY, self.maxs_x);
887                                 WriteCoord(MSG_ENTITY, self.maxs_y);
888                                 WriteCoord(MSG_ENTITY, self.maxs_z);
889                         }
890                 }
891                 WriteShort(MSG_ENTITY, self.cnt);
892                 if(fl & 0x20)
893                 {
894                         WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
895                         WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
896                 }
897                 if(fl & 0x40)
898                 {
899                         WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
900                         WriteByte(MSG_ENTITY, self.count * 16.0);
901                 }
902                 WriteString(MSG_ENTITY, self.noise);
903                 if(self.noise != "")
904                 {
905                         WriteByte(MSG_ENTITY, floor(self.atten * 64));
906                         WriteByte(MSG_ENTITY, floor(self.volume * 255));
907                 }
908                 WriteString(MSG_ENTITY, self.bgmscript);
909                 if(self.bgmscript != "")
910                 {
911                         WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
912                         WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
913                         WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
914                         WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
915                 }
916         }
917         return 1;
918 }
919
920 void pointparticles_use()
921 {
922         self.state = !self.state;
923         self.SendFlags |= 2;
924 }
925
926 void pointparticles_think()
927 {
928         if(self.origin != self.oldorigin)
929         {
930                 self.SendFlags |= 4;
931                 self.oldorigin = self.origin;
932         }
933         self.nextthink = time;
934 }
935
936 void pointparticles_reset()
937 {
938         if(self.spawnflags & 1)
939                 self.state = 1;
940         else
941                 self.state = 0;
942 }
943
944 void spawnfunc_func_pointparticles()
945 {
946         if(self.model != "")
947                 setmodel(self, self.model);
948         if(self.noise != "")
949                 precache_sound (self.noise);
950         
951         if(!self.bgmscriptsustain)
952                 self.bgmscriptsustain = 1;
953         else if(self.bgmscriptsustain < 0)
954                 self.bgmscriptsustain = 0;
955
956         if(!self.atten)
957                 self.atten = ATTN_NORM;
958         else if(self.atten < 0)
959                 self.atten = 0;
960         if(!self.volume)
961                 self.volume = 1;
962         if(!self.count)
963                 self.count = 1;
964         if(!self.impulse)
965                 self.impulse = 1;
966
967         if(!self.modelindex)
968         {
969                 setorigin(self, self.origin + self.mins);
970                 setsize(self, '0 0 0', self.maxs - self.mins);
971         }
972         if(!self.cnt)
973                 self.cnt = particleeffectnum(self.mdl);
974
975         Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
976
977         IFTARGETED
978         {
979                 self.use = pointparticles_use;
980                 self.reset = pointparticles_reset;
981                 self.reset();
982         }
983         else
984                 self.state = 1;
985         self.think = pointparticles_think;
986         self.nextthink = time;
987 }
988
989 void spawnfunc_func_sparks()
990 {
991         // self.cnt is the amount of sparks that one burst will spawn
992         if(self.cnt < 1) {
993                 self.cnt = 25.0; // nice default value
994         }
995
996         // self.wait is the probability that a sparkthink will spawn a spark shower
997         // range: 0 - 1, but 0 makes little sense, so...
998         if(self.wait < 0.05) {
999                 self.wait = 0.25; // nice default value
1000         }
1001
1002         self.count = self.cnt;
1003         self.mins = '0 0 0';
1004         self.maxs = '0 0 0';
1005         self.velocity = '0 0 -1';
1006         self.mdl = "TE_SPARK";
1007         self.impulse = 10 * self.wait; // by default 2.5/sec
1008         self.wait = 0;
1009         self.cnt = 0; // use mdl
1010
1011         spawnfunc_func_pointparticles();
1012 }
1013
1014 float rainsnow_SendEntity(entity to, float sf)
1015 {
1016         WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
1017         WriteByte(MSG_ENTITY, self.state);
1018         WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
1019         WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
1020         WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
1021         WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
1022         WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
1023         WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
1024         WriteShort(MSG_ENTITY, compressShortVector(self.dest));
1025         WriteShort(MSG_ENTITY, self.count);
1026         WriteByte(MSG_ENTITY, self.cnt);
1027         return 1;
1028 };
1029
1030 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
1031 This is an invisible area like a trigger, which rain falls inside of.
1032
1033 Keys:
1034 "velocity"
1035  falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
1036 "cnt"
1037  sets color of rain (default 12 - white)
1038 "count"
1039  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1040 */
1041 void spawnfunc_func_rain()
1042 {
1043         self.dest = self.velocity;
1044         self.velocity = '0 0 0';
1045         if (!self.dest)
1046                 self.dest = '0 0 -700';
1047         self.angles = '0 0 0';
1048         self.movetype = MOVETYPE_NONE;
1049         self.solid = SOLID_NOT;
1050         SetBrushEntityModel();
1051         if (!self.cnt)
1052                 self.cnt = 12;
1053         if (!self.count)
1054                 self.count = 2000;
1055         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1056         if (self.count < 1)
1057                 self.count = 1;
1058         if(self.count > 65535)
1059                 self.count = 65535;
1060
1061         self.state = 1; // 1 is rain, 0 is snow
1062         self.Version = 1;
1063
1064         Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1065 };
1066
1067
1068 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1069 This is an invisible area like a trigger, which snow falls inside of.
1070
1071 Keys:
1072 "velocity"
1073  falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1074 "cnt"
1075  sets color of rain (default 12 - white)
1076 "count"
1077  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1078 */
1079 void spawnfunc_func_snow()
1080 {
1081         self.dest = self.velocity;
1082         self.velocity = '0 0 0';
1083         if (!self.dest)
1084                 self.dest = '0 0 -300';
1085         self.angles = '0 0 0';
1086         self.movetype = MOVETYPE_NONE;
1087         self.solid = SOLID_NOT;
1088         SetBrushEntityModel();
1089         if (!self.cnt)
1090                 self.cnt = 12;
1091         if (!self.count)
1092                 self.count = 2000;
1093         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1094         if (self.count < 1)
1095                 self.count = 1;
1096         if(self.count > 65535)
1097                 self.count = 65535;
1098
1099         self.state = 0; // 1 is rain, 0 is snow
1100         self.Version = 1;
1101
1102         Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1103 };
1104
1105
1106 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
1107
1108 .float modelscale;
1109 void misc_laser_aim()
1110 {
1111         vector a;
1112         if(self.enemy)
1113         {
1114                 if(self.spawnflags & 2)
1115                 {
1116                         if(self.enemy.origin != self.mangle)
1117                         {
1118                                 self.mangle = self.enemy.origin;
1119                                 self.SendFlags |= 2;
1120                         }
1121                 }
1122                 else
1123                 {
1124                         a = vectoangles(self.enemy.origin - self.origin);
1125                         a_x = -a_x;
1126                         if(a != self.mangle)
1127                         {
1128                                 self.mangle = a;
1129                                 self.SendFlags |= 2;
1130                         }
1131                 }
1132         }
1133         else
1134         {
1135                 if(self.angles != self.mangle)
1136                 {
1137                         self.mangle = self.angles;
1138                         self.SendFlags |= 2;
1139                 }
1140         }
1141         if(self.origin != self.oldorigin)
1142         {
1143                 self.SendFlags |= 1;
1144                 self.oldorigin = self.origin;
1145         }
1146 }
1147
1148 void misc_laser_init()
1149 {
1150         if(self.target != "")
1151                 self.enemy = find(world, targetname, self.target);
1152 }
1153
1154 .entity pusher;
1155 void misc_laser_think()
1156 {
1157         vector o;
1158         entity oldself;
1159         entity hitent;
1160         vector hitloc;
1161
1162         self.nextthink = time;
1163
1164         if(!self.state)
1165                 return;
1166
1167         misc_laser_aim();
1168
1169         if(self.enemy)
1170         {
1171                 o = self.enemy.origin;
1172                 if not(self.spawnflags & 2)
1173                         o = self.origin + normalize(o - self.origin) * 32768;
1174         }
1175         else
1176         {
1177                 makevectors(self.mangle);
1178                 o = self.origin + v_forward * 32768;
1179         }
1180
1181         if(self.dmg || self.enemy.target != "")
1182         {
1183                 traceline(self.origin, o, MOVE_NORMAL, self);
1184         }
1185         hitent = trace_ent;
1186         hitloc = trace_endpos;
1187
1188         if(self.enemy.target != "") // DETECTOR laser
1189         {
1190                 if(trace_ent.iscreature)
1191                 {
1192                         self.pusher = hitent;
1193                         if(!self.count)
1194                         {
1195                                 self.count = 1;
1196
1197                                 oldself = self;
1198                                 self = self.enemy;
1199                                 activator = self.pusher;
1200                                 SUB_UseTargets();
1201                                 self = oldself;
1202                         }
1203                 }
1204                 else
1205                 {
1206                         if(self.count)
1207                         {
1208                                 self.count = 0;
1209
1210                                 oldself = self;
1211                                 self = self.enemy;
1212                                 activator = self.pusher;
1213                                 SUB_UseTargets();
1214                                 self = oldself;
1215                         }
1216                 }
1217         }
1218
1219         if(self.dmg)
1220         {
1221                 if(self.team)
1222                         if((self.spawnflags & 8 == 0) == (self.team != hitent.team))
1223                                 return;
1224                 if(hitent.takedamage)
1225                         Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1226         }
1227 }
1228
1229 float laser_SendEntity(entity to, float fl)
1230 {
1231         WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1232         fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1233         if(self.spawnflags & 2)
1234                 fl |= 0x80;
1235         if(self.alpha)
1236                 fl |= 0x40;
1237         if(self.scale != 1 || self.modelscale != 1)
1238                 fl |= 0x20;
1239         if(self.spawnflags & 4)
1240                 fl |= 0x10;
1241         WriteByte(MSG_ENTITY, fl);
1242         if(fl & 1)
1243         {
1244                 WriteCoord(MSG_ENTITY, self.origin_x);
1245                 WriteCoord(MSG_ENTITY, self.origin_y);
1246                 WriteCoord(MSG_ENTITY, self.origin_z);
1247         }
1248         if(fl & 8)
1249         {
1250                 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1251                 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1252                 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1253                 if(fl & 0x40)
1254                         WriteByte(MSG_ENTITY, self.alpha * 255.0);
1255                 if(fl & 0x20)
1256                 {
1257                         WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1258                         WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1259                 }
1260                 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1261                         WriteShort(MSG_ENTITY, self.cnt + 1);
1262         }
1263         if(fl & 2)
1264         {
1265                 if(fl & 0x80)
1266                 {
1267                         WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1268                         WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1269                         WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1270                 }
1271                 else
1272                 {
1273                         WriteAngle(MSG_ENTITY, self.mangle_x);
1274                         WriteAngle(MSG_ENTITY, self.mangle_y);
1275                 }
1276         }
1277         if(fl & 4)
1278                 WriteByte(MSG_ENTITY, self.state);
1279         return 1;
1280 }
1281
1282 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1283 Any object touching the beam will be hurt
1284 Keys:
1285 "target"
1286  spawnfunc_target_position where the laser ends
1287 "mdl"
1288  name of beam end effect to use
1289 "colormod"
1290  color of the beam (default: red)
1291 "dmg"
1292  damage per second (-1 for a laser that kills immediately)
1293 */
1294 void laser_use()
1295 {
1296         self.state = !self.state;
1297         self.SendFlags |= 4;
1298         misc_laser_aim();
1299 }
1300
1301 void laser_reset()
1302 {
1303         if(self.spawnflags & 1)
1304                 self.state = 1;
1305         else
1306                 self.state = 0;
1307 }
1308
1309 void spawnfunc_misc_laser()
1310 {
1311         if(self.mdl)
1312         {
1313                 if(self.mdl == "none")
1314                         self.cnt = -1;
1315                 else
1316                 {
1317                         self.cnt = particleeffectnum(self.mdl);
1318                         if(self.cnt < 0)
1319                                 if(self.dmg)
1320                                         self.cnt = particleeffectnum("laser_deadly");
1321                 }
1322         }
1323         else if(!self.cnt)
1324         {
1325                 if(self.dmg)
1326                         self.cnt = particleeffectnum("laser_deadly");
1327                 else
1328                         self.cnt = -1;
1329         }
1330         if(self.cnt < 0)
1331                 self.cnt = -1;
1332
1333         if(self.colormod == '0 0 0')
1334                 if(!self.alpha)
1335                         self.colormod = '1 0 0';
1336         if(!self.message)
1337                 self.message = "saw the light";
1338         if (!self.message2)
1339                 self.message2 = "was pushed into a laser by";
1340         if(!self.scale)
1341                 self.scale = 1;
1342         if(!self.modelscale)
1343                 self.modelscale = 1;
1344         else if(self.modelscale < 0)
1345                 self.modelscale = 0;
1346         self.think = misc_laser_think;
1347         self.nextthink = time;
1348         InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1349
1350         self.mangle = self.angles;
1351
1352         Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1353
1354         IFTARGETED
1355         {
1356                 self.reset = laser_reset;
1357                 laser_reset();
1358                 self.use = laser_use;
1359         }
1360         else
1361                 self.state = 1;
1362 }
1363
1364 // tZorks trigger impulse / gravity
1365 .float radius;
1366 .float falloff;
1367 .float strength;
1368 .float lastpushtime;
1369
1370 // targeted (directional) mode
1371 void trigger_impulse_touch1()
1372 {
1373         entity targ;
1374     float pushdeltatime;
1375     float str;
1376
1377         if (self.active != ACTIVE_ACTIVE) 
1378                 return;
1379
1380         if(!isPushable(other))
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 };