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