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