]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/g_triggers.qc
Nexball goes official :D
[divverent/nexuiz.git] / data / 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
161         SUB_UseTargets();
162
163         if (self.wait > 0)
164         {
165                 self.think = multi_wait;
166                 self.nextthink = time + self.wait;
167         }
168         else
169         {       // we can't just remove (self) here, because this is a touch function
170                 // called wheil C code is looping through area links...
171                 self.touch = SUB_Null;
172         }
173 };
174
175 void multi_use()
176 {
177         self.enemy = activator;
178         multi_trigger();
179 };
180
181 void multi_touch()
182 {
183         if not(self.spawnflags & 2)
184         {
185                 if not(other.iscreature)
186                         return;
187
188                 if(self.team)
189                 if(self.team == other.team)
190                         return;
191         }
192
193 // if the trigger has an angles field, check player's facing direction
194         if (self.movedir != '0 0 0')
195         {
196                 makevectors (other.angles);
197                 if (v_forward * self.movedir < 0)
198                         return;         // not facing the right way
199         }
200
201         EXACTTRIGGER_TOUCH;
202
203         self.enemy = other;
204         multi_trigger ();
205 };
206
207 void multi_eventdamage (vector hitloc, float damage, entity inflictor, entity attacker, float deathtype)
208 {
209         if (!self.takedamage)
210                 return;
211         self.health = self.health - damage;
212         if (self.health <= 0)
213         {
214                 self.enemy = attacker;
215                 multi_trigger();
216         }
217 }
218
219 void multi_reset()
220 {
221         self.touch = multi_touch;
222         self.health = self.max_health;
223         self.takedamage = DAMAGE_YES;
224         self.solid = SOLID_BBOX;
225         self.think = SUB_Null;
226         self.team = self.team_saved;
227 }
228
229 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
230 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.
231 If "delay" is set, the trigger waits some time after activating before firing.
232 "wait" : Seconds between triggerings. (.2 default)
233 If notouch is set, the trigger is only fired by other entities, not by touching.
234 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
235 sounds
236 1)      secret
237 2)      beep beep
238 3)      large switch
239 4)
240 set "message" to text string
241 */
242 void spawnfunc_trigger_multiple()
243 {
244         self.reset = multi_reset;
245         if (self.sounds == 1)
246         {
247                 precache_sound ("misc/secret.wav");
248                 self.noise = "misc/secret.wav";
249         }
250         else if (self.sounds == 2)
251         {
252                 precache_sound ("misc/talk.wav");
253                 self.noise = "misc/talk.wav";
254         }
255         else if (self.sounds == 3)
256         {
257                 precache_sound ("misc/trigger1.wav");
258                 self.noise = "misc/trigger1.wav";
259         }
260
261         if (!self.wait)
262                 self.wait = 0.2;
263         self.use = multi_use;
264
265         EXACTTRIGGER_INIT;
266
267         self.team_saved = self.team;
268
269         if (self.health)
270         {
271                 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
272                         objerror ("health and notouch don't make sense\n");
273                 self.max_health = self.health;
274                 self.event_damage = multi_eventdamage;
275                 self.takedamage = DAMAGE_YES;
276                 self.solid = SOLID_BBOX;
277                 setorigin (self, self.origin);  // make sure it links into the world
278         }
279         else
280         {
281                 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
282                 {
283                         self.touch = multi_touch;
284                         setorigin (self, self.origin);  // make sure it links into the world
285                 }
286         }
287 };
288
289
290 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
291 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
292 "targetname".  If "health" is set, the trigger must be killed to activate.
293 If notouch is set, the trigger is only fired by other entities, not by touching.
294 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
295 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.
296 sounds
297 1)      secret
298 2)      beep beep
299 3)      large switch
300 4)
301 set "message" to text string
302 */
303 void spawnfunc_trigger_once()
304 {
305         self.wait = -1;
306         spawnfunc_trigger_multiple();
307 };
308
309 //=============================================================================
310
311 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
312 This fixed size trigger cannot be touched, it can only be fired by other events.  It can contain killtargets, targets, delays, and messages.
313 */
314 void spawnfunc_trigger_relay()
315 {
316         self.use = SUB_UseTargets;
317         self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
318 };
319
320 void delay_use()
321 {
322     self.think = SUB_UseTargets;
323     self.nextthink = self.wait;
324 }
325
326 void delay_reset()
327 {
328         self.think = SUB_Null;
329 }
330
331 void spawnfunc_trigger_delay()
332 {
333     if(!self.wait)
334         self.wait = 1;
335
336     self.use = delay_use;
337     self.reset = delay_reset;
338 }
339
340 //=============================================================================
341
342
343 void counter_use()
344 {
345         self.count = self.count - 1;
346         if (self.count < 0)
347                 return;
348
349         if (self.count != 0)
350         {
351                 if (activator.classname == "player"
352                 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
353                 {
354                         if (self.count >= 4)
355                                 centerprint (activator, "There are more to go...");
356                         else if (self.count == 3)
357                                 centerprint (activator, "Only 3 more to go...");
358                         else if (self.count == 2)
359                                 centerprint (activator, "Only 2 more to go...");
360                         else
361                                 centerprint (activator, "Only 1 more to go...");
362                 }
363                 return;
364         }
365
366         if (activator.classname == "player"
367         && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
368                 centerprint(activator, "Sequence completed!");
369         self.enemy = activator;
370         multi_trigger ();
371 };
372
373 void counter_reset()
374 {
375         self.count = self.cnt;
376         multi_reset();
377 }
378
379 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
380 Acts as an intermediary for an action that takes multiple inputs.
381
382 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
383
384 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
385 */
386 void spawnfunc_trigger_counter()
387 {
388         self.wait = -1;
389         if (!self.count)
390                 self.count = 2;
391         self.cnt = self.count;
392
393         self.use = counter_use;
394         self.reset = counter_reset;
395 };
396
397 .float triggerhurttime;
398 void trigger_hurt_touch()
399 {
400         // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
401         if (other.iscreature)
402         {
403                 if (other.takedamage)
404                 if (other.triggerhurttime < time)
405                 {
406                         EXACTTRIGGER_TOUCH;
407                         other.triggerhurttime = time + 1;
408                         Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
409                 }
410         }
411         else
412         {
413                 if (!other.owner)
414                 {
415                         if (other.items & IT_KEY1 || other.items & IT_KEY2)     // reset flag
416                         {
417                                 EXACTTRIGGER_TOUCH;
418                                 other.pain_finished = min(other.pain_finished, time + 2);
419                         }
420                         else if (other.classname == "rune")                     // reset runes
421                         {
422                                 EXACTTRIGGER_TOUCH;
423                                 other.nextthink = min(other.nextthink, time + 1);
424                         }
425                 }
426         }
427
428         return;
429 };
430
431 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
432 Any object touching this will be hurt
433 set dmg to damage amount
434 defalt dmg = 5
435 */
436 .entity trigger_hurt_next;
437 entity trigger_hurt_last;
438 entity trigger_hurt_first;
439 void spawnfunc_trigger_hurt()
440 {
441         EXACTTRIGGER_INIT;
442         self.touch = trigger_hurt_touch;
443         if (!self.dmg)
444                 self.dmg = 1000;
445         if (!self.message)
446                 self.message = "was in the wrong place";
447         if (!self.message2)
448                 self.message2 = "was thrown into a world of hurt by";
449
450         if(!trigger_hurt_first)
451                 trigger_hurt_first = self;
452         if(trigger_hurt_last)
453                 trigger_hurt_last.trigger_hurt_next = self;
454         trigger_hurt_last = self;
455 };
456
457 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
458 {
459         entity th;
460
461         for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
462                 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
463                         return TRUE;
464
465         return FALSE;
466 }
467
468 //////////////////////////////////////////////////////////////
469 //
470 //
471 //
472 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
473 //
474 //////////////////////////////////////////////////////////////
475
476 .float triggerhealtime;
477 void trigger_heal_touch()
478 {
479         // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
480         if (other.iscreature)
481         {
482                 if (other.takedamage)
483                 if (other.triggerhealtime < time)
484                 {
485                         EXACTTRIGGER_TOUCH;
486                         other.triggerhealtime = time + 1;
487                         
488                         if (other.health < self.max_health)
489                         {
490                                 other.health = min(other.health + self.health, self.max_health);
491                                 other.pauserothealth_finished = max(other.pauserothealth_finished, time + cvar("g_balance_pause_health_rot"));
492                                 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
493                         }
494                 }
495         }
496 };
497
498 void spawnfunc_trigger_heal()
499 {
500         EXACTTRIGGER_INIT;
501         self.touch = trigger_heal_touch;
502         if (!self.health)
503                 self.health = 10;
504         if (!self.max_health)
505                 self.max_health = 200; //Max health topoff for field
506         if(self.noise == "")
507                 self.noise = "misc/mediumhealth.wav";
508         precache_sound(self.noise);
509 };
510
511
512 //////////////////////////////////////////////////////////////
513 //
514 //
515 //
516 //End trigger_heal
517 //
518 //////////////////////////////////////////////////////////////
519
520
521
522 // TODO add a way to do looped sounds with sound(); then complete this entity
523 .float volume, atten;
524 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
525
526 void spawnfunc_target_speaker()
527 {
528         if(self.noise)
529                 precache_sound (self.noise);
530         IFTARGETED
531         {
532                 if(!self.atten)
533                         self.atten = ATTN_NORM;
534                 else if(self.atten < 0)
535                         self.atten = 0;
536                 if(!self.volume)
537                         self.volume = 1;
538                 self.use = target_speaker_use;
539         }
540         else
541         {
542                 if(!self.atten)
543                         self.atten = ATTN_STATIC;
544                 else if(self.atten < 0)
545                         self.atten = 0;
546                 if(!self.volume)
547                         self.volume = 1;
548                 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
549         }
550 };
551
552
553 void spawnfunc_func_stardust() {
554         self.effects = EF_STARDUST;
555 }
556
557 .string bgmscript;
558 .float bgmscriptattack;
559 .float bgmscriptdecay;
560 .float bgmscriptsustain;
561 .float bgmscriptrelease;
562 float pointparticles_SendEntity(entity to, float fl)
563 {
564         WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
565
566         // optional features to save space
567         fl = fl & 0x0F;
568         if(self.spawnflags & 2)
569                 fl |= 0x10; // absolute count on toggle-on
570         if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
571                 fl |= 0x20; // 4 bytes - saves CPU
572         if(self.waterlevel || self.count != 1)
573                 fl |= 0x40; // 4 bytes - obscure features almost never used
574         if(self.mins != '0 0 0' || self.maxs != '0 0 0')
575                 fl |= 0x80; // 14 bytes - saves lots of space
576
577         WriteByte(MSG_ENTITY, fl);
578         if(fl & 2)
579         {
580                 if(self.state)
581                         WriteCoord(MSG_ENTITY, self.impulse);
582                 else
583                         WriteCoord(MSG_ENTITY, 0); // off
584         }
585         if(fl & 4)
586         {
587                 WriteCoord(MSG_ENTITY, self.origin_x);
588                 WriteCoord(MSG_ENTITY, self.origin_y);
589                 WriteCoord(MSG_ENTITY, self.origin_z);
590         }
591         if(fl & 1)
592         {
593                 if(self.model != "null")
594                 {
595                         WriteShort(MSG_ENTITY, self.modelindex);
596                         if(fl & 0x80)
597                         {
598                                 WriteCoord(MSG_ENTITY, self.mins_x);
599                                 WriteCoord(MSG_ENTITY, self.mins_y);
600                                 WriteCoord(MSG_ENTITY, self.mins_z);
601                                 WriteCoord(MSG_ENTITY, self.maxs_x);
602                                 WriteCoord(MSG_ENTITY, self.maxs_y);
603                                 WriteCoord(MSG_ENTITY, self.maxs_z);
604                         }
605                 }
606                 else
607                 {
608                         WriteShort(MSG_ENTITY, 0);
609                         if(fl & 0x80)
610                         {
611                                 WriteCoord(MSG_ENTITY, self.maxs_x);
612                                 WriteCoord(MSG_ENTITY, self.maxs_y);
613                                 WriteCoord(MSG_ENTITY, self.maxs_z);
614                         }
615                 }
616                 WriteShort(MSG_ENTITY, self.cnt);
617                 if(fl & 0x20)
618                 {
619                         WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
620                         WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
621                 }
622                 if(fl & 0x40)
623                 {
624                         WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
625                         WriteByte(MSG_ENTITY, self.count * 16.0);
626                 }
627                 WriteString(MSG_ENTITY, self.noise);
628                 if(self.noise != "")
629                 {
630                         WriteByte(MSG_ENTITY, floor(self.atten * 64));
631                         WriteByte(MSG_ENTITY, floor(self.volume * 255));
632                 }
633                 WriteString(MSG_ENTITY, self.bgmscript);
634                 if(self.bgmscript != "")
635                 {
636                         WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
637                         WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
638                         WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
639                         WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
640                 }
641         }
642         return 1;
643 }
644
645 void pointparticles_use()
646 {
647         self.state = !self.state;
648         self.SendFlags |= 2;
649 }
650
651 void pointparticles_think()
652 {
653         if(self.origin != self.oldorigin)
654         {
655                 self.SendFlags |= 4;
656                 self.oldorigin = self.origin;
657         }
658         self.nextthink = time;
659 }
660
661 void pointparticles_reset()
662 {
663         if(self.spawnflags & 1)
664                 self.state = 1;
665         else
666                 self.state = 0;
667 }
668
669 void spawnfunc_func_pointparticles()
670 {
671         if(self.model != "")
672                 setmodel(self, self.model);
673         if(self.noise != "")
674                 precache_sound (self.noise);
675         
676         if(!self.bgmscriptsustain)
677                 self.bgmscriptsustain = 1;
678         else if(self.bgmscriptsustain < 0)
679                 self.bgmscriptsustain = 0;
680
681         if(!self.atten)
682                 self.atten = ATTN_NORM;
683         else if(self.atten < 0)
684                 self.atten = 0;
685         if(!self.volume)
686                 self.volume = 1;
687         if(!self.count)
688                 self.count = 1;
689         if(!self.impulse)
690                 self.impulse = 1;
691
692         if(!self.modelindex)
693         {
694                 setorigin(self, self.origin + self.mins);
695                 setsize(self, '0 0 0', self.maxs - self.mins);
696         }
697         if(!self.cnt)
698                 self.cnt = particleeffectnum(self.mdl);
699
700         Net_LinkEntity(self, FALSE, 0, pointparticles_SendEntity);
701
702         IFTARGETED
703         {
704                 self.use = pointparticles_use;
705                 self.reset = pointparticles_reset;
706                 self.reset();
707         }
708         else
709                 self.state = 1;
710         self.think = pointparticles_think;
711         self.nextthink = time;
712 }
713
714 void spawnfunc_func_sparks()
715 {
716         // self.cnt is the amount of sparks that one burst will spawn
717         if(self.cnt < 1) {
718                 self.cnt = 25.0; // nice default value
719         }
720
721         // self.wait is the probability that a sparkthink will spawn a spark shower
722         // range: 0 - 1, but 0 makes little sense, so...
723         if(self.wait < 0.05) {
724                 self.wait = 0.25; // nice default value
725         }
726
727         self.count = self.cnt;
728         self.mins = '0 0 0';
729         self.maxs = '0 0 0';
730         self.velocity = '0 0 -1';
731         self.mdl = "TE_SPARK";
732         self.impulse = 10 * self.wait; // by default 2.5/sec
733         self.wait = 0;
734         self.cnt = 0; // use mdl
735
736         spawnfunc_func_pointparticles();
737 }
738
739 float rainsnow_SendEntity(entity to, float sf)
740 {
741         WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
742         WriteByte(MSG_ENTITY, self.state);
743         WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
744         WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
745         WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
746         WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
747         WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
748         WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
749         WriteShort(MSG_ENTITY, compressShortVector(self.dest));
750         WriteShort(MSG_ENTITY, self.count);
751         WriteByte(MSG_ENTITY, self.cnt);
752         return 1;
753 };
754
755 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
756 This is an invisible area like a trigger, which rain falls inside of.
757
758 Keys:
759 "velocity"
760  falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
761 "cnt"
762  sets color of rain (default 12 - white)
763 "count"
764  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
765 */
766 void spawnfunc_func_rain()
767 {
768         self.dest = self.velocity;
769         self.velocity = '0 0 0';
770         if (!self.dest)
771                 self.dest = '0 0 -700';
772         self.angles = '0 0 0';
773         self.movetype = MOVETYPE_NONE;
774         self.solid = SOLID_NOT;
775         SetBrushEntityModel();
776         if (!self.cnt)
777                 self.cnt = 12;
778         if (!self.count)
779                 self.count = 2000;
780         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
781         if (self.count < 1)
782                 self.count = 1;
783         if(self.count > 65535)
784                 self.count = 65535;
785
786         self.state = 1; // 1 is rain, 0 is snow
787         self.Version = 1;
788
789         Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
790 };
791
792
793 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
794 This is an invisible area like a trigger, which snow falls inside of.
795
796 Keys:
797 "velocity"
798  falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
799 "cnt"
800  sets color of rain (default 12 - white)
801 "count"
802  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
803 */
804 void spawnfunc_func_snow()
805 {
806         self.dest = self.velocity;
807         self.velocity = '0 0 0';
808         if (!self.dest)
809                 self.dest = '0 0 -300';
810         self.angles = '0 0 0';
811         self.movetype = MOVETYPE_NONE;
812         self.solid = SOLID_NOT;
813         SetBrushEntityModel();
814         if (!self.cnt)
815                 self.cnt = 12;
816         if (!self.count)
817                 self.count = 2000;
818         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
819         if (self.count < 1)
820                 self.count = 1;
821         if(self.count > 65535)
822                 self.count = 65535;
823
824         self.state = 0; // 1 is rain, 0 is snow
825         self.Version = 1;
826
827         Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
828 };
829
830
831 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
832
833 .float modelscale;
834 void misc_laser_aim()
835 {
836         vector a;
837         if(self.enemy)
838         {
839                 if(self.spawnflags & 2)
840                 {
841                         if(self.enemy.origin != self.mangle)
842                         {
843                                 self.mangle = self.enemy.origin;
844                                 self.SendFlags |= 2;
845                         }
846                 }
847                 else
848                 {
849                         a = vectoangles(self.enemy.origin - self.origin);
850                         a_x = -a_x;
851                         if(a != self.mangle)
852                         {
853                                 self.mangle = a;
854                                 self.SendFlags |= 2;
855                         }
856                 }
857         }
858         else
859         {
860                 if(self.angles != self.mangle)
861                 {
862                         self.mangle = self.angles;
863                         self.SendFlags |= 2;
864                 }
865         }
866         if(self.origin != self.oldorigin)
867         {
868                 self.SendFlags |= 1;
869                 self.oldorigin = self.origin;
870         }
871 }
872
873 void misc_laser_init()
874 {
875         if(self.target != "")
876                 self.enemy = find(world, targetname, self.target);
877 }
878
879 .entity pusher;
880 void misc_laser_think()
881 {
882         vector o;
883         entity oldself;
884
885         self.nextthink = time;
886
887         if(!self.state)
888                 return;
889
890         misc_laser_aim();
891
892         if(self.enemy)
893         {
894                 o = self.enemy.origin;
895                 if not(self.spawnflags & 2)
896                         o = self.origin + normalize(o - self.origin) * 32768;
897         }
898         else
899         {
900                 makevectors(self.mangle);
901                 o = self.origin + v_forward * 32768;
902         }
903
904         if(self.dmg)
905         {
906                 if(self.dmg < 0)
907                         FireRailgunBullet(self.origin, o, 100000, 0, DEATH_HURTTRIGGER);
908                 else
909                         FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, DEATH_HURTTRIGGER);
910         }
911
912         if(self.enemy.target != "") // DETECTOR laser
913         {
914                 traceline(self.origin, o, MOVE_NORMAL, self);
915                 if(trace_ent.iscreature)
916                 {
917                         self.pusher = trace_ent;
918                         if(!self.count)
919                         {
920                                 self.count = 1;
921
922                                 oldself = self;
923                                 self = self.enemy;
924                                 activator = self.pusher;
925                                 SUB_UseTargets();
926                                 self = oldself;
927                         }
928                 }
929                 else
930                 {
931                         if(self.count)
932                         {
933                                 self.count = 0;
934
935                                 oldself = self;
936                                 self = self.enemy;
937                                 activator = self.pusher;
938                                 SUB_UseTargets();
939                                 self = oldself;
940                         }
941                 }
942         }
943 }
944
945 float laser_SendEntity(entity to, float fl)
946 {
947         WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
948         fl = fl - (fl & 0xE0); // use that bit to indicate finite length laser
949         if(self.spawnflags & 2)
950                 fl |= 0x80;
951         if(self.alpha)
952                 fl |= 0x40;
953         if(self.scale != 1 || self.modelscale != 1)
954                 fl |= 0x20;
955         WriteByte(MSG_ENTITY, fl);
956         if(fl & 1)
957         {
958                 WriteCoord(MSG_ENTITY, self.origin_x);
959                 WriteCoord(MSG_ENTITY, self.origin_y);
960                 WriteCoord(MSG_ENTITY, self.origin_z);
961         }
962         if(fl & 8)
963         {
964                 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
965                 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
966                 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
967                 if(fl & 0x40)
968                         WriteByte(MSG_ENTITY, self.alpha * 255.0);
969                 if(fl & 0x20)
970                 {
971                         WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
972                         WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
973                 }
974                 WriteShort(MSG_ENTITY, self.cnt + 1);
975         }
976         if(fl & 2)
977         {
978                 if(fl & 0x80)
979                 {
980                         WriteCoord(MSG_ENTITY, self.enemy.origin_x);
981                         WriteCoord(MSG_ENTITY, self.enemy.origin_y);
982                         WriteCoord(MSG_ENTITY, self.enemy.origin_z);
983                 }
984                 else
985                 {
986                         WriteAngle(MSG_ENTITY, self.mangle_x);
987                         WriteAngle(MSG_ENTITY, self.mangle_y);
988                 }
989         }
990         if(fl & 4)
991                 WriteByte(MSG_ENTITY, self.state);
992         return 1;
993 }
994
995 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
996 Any object touching the beam will be hurt
997 Keys:
998 "target"
999  spawnfunc_target_position where the laser ends
1000 "mdl"
1001  name of beam end effect to use
1002 "colormod"
1003  color of the beam (default: red)
1004 "dmg"
1005  damage per second (-1 for a laser that kills immediately)
1006 */
1007 void laser_use()
1008 {
1009         self.state = !self.state;
1010         self.SendFlags |= 4;
1011         misc_laser_aim();
1012 }
1013
1014 void laser_reset()
1015 {
1016         if(self.spawnflags & 1)
1017                 self.state = 1;
1018         else
1019                 self.state = 0;
1020 }
1021
1022 void spawnfunc_misc_laser()
1023 {
1024         if(self.mdl)
1025         {
1026                 if(self.mdl == "none")
1027                         self.cnt = -1;
1028                 else
1029                 {
1030                         self.cnt = particleeffectnum(self.mdl);
1031                         if(self.cnt < 0)
1032                                 if(self.dmg)
1033                                         self.cnt = particleeffectnum("laser_deadly");
1034                 }
1035         }
1036         else if(!self.cnt)
1037         {
1038                 if(self.dmg)
1039                         self.cnt = particleeffectnum("laser_deadly");
1040                 else
1041                         self.cnt = -1;
1042         }
1043         if(self.cnt < 0)
1044                 self.cnt = -1;
1045
1046         if(self.colormod == '0 0 0')
1047                 if(!self.alpha)
1048                         self.colormod = '1 0 0';
1049         if(!self.message)
1050                 self.message = "saw the light";
1051         if (!self.message2)
1052                 self.message2 = "was pushed into a laser by";
1053         if(!self.scale)
1054                 self.scale = 1;
1055         if(!self.modelscale)
1056                 self.modelscale = 1;
1057         self.think = misc_laser_think;
1058         self.nextthink = time;
1059         InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1060
1061         self.mangle = self.angles;
1062
1063         Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1064
1065         IFTARGETED
1066         {
1067                 self.reset = laser_reset;
1068                 laser_reset();
1069                 self.use = laser_use;
1070         }
1071         else
1072                 self.state = 1;
1073 }
1074
1075 // tZorks trigger impulse / gravity
1076 .float radius;
1077 .float falloff;
1078 .float strength;
1079 .float lastpushtime;
1080
1081 // targeted (directional) mode
1082 void trigger_impulse_touch1()
1083 {
1084         entity targ;
1085     float pushdeltatime;
1086     float str;
1087
1088         // FIXME: Better checking for what to push and not.
1089         if not(other.iscreature)
1090         if (other.classname != "corpse")
1091         if (other.classname != "body")
1092         if (other.classname != "gib")
1093         if (other.classname != "missile")
1094         if (other.classname != "rocket")
1095         if (other.classname != "casing")
1096         if (other.classname != "grenade")
1097         if (other.classname != "plasma")
1098         if (other.classname != "plasma_prim")
1099         if (other.classname != "plasma_chain")
1100         if (other.classname != "droppedweapon")
1101         if (other.classname != "ball_basketball")
1102         if (other.classname != "ball_football")
1103                 return;
1104
1105         if (other.deadflag && other.iscreature)
1106                 return;
1107
1108         EXACTTRIGGER_TOUCH;
1109
1110     targ = find(world, targetname, self.target);
1111     if(!targ)
1112     {
1113         objerror("trigger_force without a (valid) .target!\n");
1114         remove(self);
1115         return;
1116     }
1117
1118     if(self.falloff == 1)
1119         str = (str / self.radius) * self.strength;
1120     else if(self.falloff == 2)
1121         str = (1 - (str / self.radius)) * self.strength;
1122     else
1123         str = self.strength;
1124
1125     pushdeltatime = time - other.lastpushtime;
1126     if (pushdeltatime > 0.15) pushdeltatime = 0;
1127     other.lastpushtime = time;
1128     if(!pushdeltatime) return;
1129
1130     other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
1131         other.flags &~= FL_ONGROUND;
1132 }
1133
1134 // Directionless (accelerator/decelerator) mode
1135 void trigger_impulse_touch2()
1136 {
1137     float pushdeltatime;
1138
1139         // FIXME: Better checking for what to push and not.
1140         if not(other.iscreature)
1141         if (other.classname != "corpse")
1142         if (other.classname != "body")
1143         if (other.classname != "gib")
1144         if (other.classname != "missile")
1145         if (other.classname != "rocket")
1146         if (other.classname != "casing")
1147         if (other.classname != "grenade")
1148         if (other.classname != "plasma")
1149         if (other.classname != "plasma_prim")
1150         if (other.classname != "plasma_chain")
1151         if (other.classname != "droppedweapon")
1152         if (other.classname != "ball_basketball")
1153         if (other.classname != "ball_football")
1154                 return;
1155
1156         if (other.deadflag && other.iscreature)
1157                 return;
1158
1159         EXACTTRIGGER_TOUCH;
1160
1161     pushdeltatime = time - other.lastpushtime;
1162     if (pushdeltatime > 0.15) pushdeltatime = 0;
1163     other.lastpushtime = time;
1164     if(!pushdeltatime) return;
1165
1166     //if(self.strength > 1)
1167         other.velocity = other.velocity * (self.strength * pushdeltatime);
1168     //else
1169     //    other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
1170 }
1171
1172 // Spherical (gravity/repulsor) mode
1173 void trigger_impulse_touch3()
1174 {
1175     float pushdeltatime;
1176     float str;
1177
1178         // FIXME: Better checking for what to push and not.
1179         if not(other.iscreature)
1180         if (other.classname != "corpse")
1181         if (other.classname != "body")
1182         if (other.classname != "gib")
1183         if (other.classname != "missile")
1184         if (other.classname != "rocket")
1185         if (other.classname != "casing")
1186         if (other.classname != "grenade")
1187         if (other.classname != "plasma")
1188         if (other.classname != "plasma_prim")
1189         if (other.classname != "plasma_chain")
1190         if (other.classname != "droppedweapon")
1191         if (other.classname != "ball_basketball")
1192         if (other.classname != "ball_football")
1193                 return;
1194
1195         if (other.deadflag && other.iscreature)
1196                 return;
1197
1198         EXACTTRIGGER_TOUCH;
1199
1200     pushdeltatime = time - other.lastpushtime;
1201     if (pushdeltatime > 0.15) pushdeltatime = 0;
1202     other.lastpushtime = time;
1203     if(!pushdeltatime) return;
1204
1205     setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1206
1207         str = min(self.radius, vlen(self.origin - other.origin));
1208
1209     if(self.falloff == 1)
1210         str = (1 - str / self.radius) * self.strength; // 1 in the inside
1211     else if(self.falloff == 2)
1212         str = (str / self.radius) * self.strength; // 0 in the inside
1213     else
1214         str = self.strength;
1215
1216     other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1217 }
1218
1219 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1220 -------- KEYS --------
1221 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1222          If not, this trigger acts like a damper/accelerator field.
1223
1224 strength : This is how mutch force to add in the direction of .target each second
1225            when .target is set. If not, this is hoe mutch to slow down/accelerate
1226            someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1227
1228 radius   : If set, act as a spherical device rather then a liniar one.
1229
1230 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1231
1232 -------- NOTES --------
1233 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1234 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1235 */
1236
1237 void spawnfunc_trigger_impulse()
1238 {
1239         EXACTTRIGGER_INIT;
1240     if(self.radius)
1241     {
1242         if(!self.strength) self.strength = 2000;
1243         setorigin(self, self.origin);
1244         setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1245         self.touch = trigger_impulse_touch3;
1246     }
1247     else
1248     {
1249         if(self.target)
1250         {
1251             if(!self.strength) self.strength = 950;
1252             self.touch = trigger_impulse_touch1;
1253         }
1254         else
1255         {
1256             if(!self.strength) self.strength = 0.9;
1257             self.touch = trigger_impulse_touch2;
1258         }
1259     }
1260 }
1261
1262 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1263 "Flip-flop" trigger gate... lets only every second trigger event through
1264 */
1265 void flipflop_use()
1266 {
1267         self.state = !self.state;
1268         if(self.state)
1269                 SUB_UseTargets();
1270 }
1271
1272 void spawnfunc_trigger_flipflop()
1273 {
1274         if(self.spawnflags & 1)
1275                 self.state = 1;
1276         self.use = flipflop_use;
1277         self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1278 }
1279
1280 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1281 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1282 */
1283 void monoflop_use()
1284 {
1285         self.nextthink = time + self.wait;
1286         self.enemy = activator;
1287         if(self.state)
1288                 return;
1289         self.state = 1;
1290         SUB_UseTargets();
1291 }
1292 void monoflop_fixed_use()
1293 {
1294         if(self.state)
1295                 return;
1296         self.nextthink = time + self.wait;
1297         self.state = 1;
1298         self.enemy = activator;
1299         SUB_UseTargets();
1300 }
1301
1302 void monoflop_think()
1303 {
1304         self.state = 0;
1305         activator = self.enemy;
1306         SUB_UseTargets();
1307 }
1308
1309 void monoflop_reset()
1310 {
1311         self.state = 0;
1312         self.nextthink = 0;
1313 }
1314
1315 void spawnfunc_trigger_monoflop()
1316 {
1317         if(!self.wait)
1318                 self.wait = 1;
1319         if(self.spawnflags & 1)
1320                 self.use = monoflop_fixed_use;
1321         else
1322                 self.use = monoflop_use;
1323         self.think = monoflop_think;
1324         self.state = 0;
1325         self.reset = monoflop_reset;
1326 }
1327
1328 void multivibrator_send()
1329 {
1330         float newstate;
1331         float cyclestart;
1332
1333         cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1334
1335         newstate = (time < cyclestart + self.wait);
1336
1337         activator = self;
1338         if(self.state != newstate)
1339                 SUB_UseTargets();
1340         self.state = newstate;
1341
1342         if(self.state)
1343                 self.nextthink = cyclestart + self.wait + 0.01;
1344         else
1345                 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1346 }
1347
1348 void multivibrator_toggle()
1349 {
1350         if(self.nextthink == 0)
1351         {
1352                 multivibrator_send();
1353         }
1354         else
1355         {
1356                 if(self.state)
1357                 {
1358                         SUB_UseTargets();
1359                         self.state = 0;
1360                 }
1361                 self.nextthink = 0;
1362         }
1363 }
1364
1365 void multivibrator_reset()
1366 {
1367         if(!(self.spawnflags & 1))
1368                 self.nextthink = 0; // wait for a trigger event
1369         else
1370                 self.nextthink = max(1, time);
1371 }
1372
1373 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1374 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1375 -------- KEYS --------
1376 target: trigger all entities with this targetname when it goes off
1377 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1378 phase: offset of the timing
1379 wait: "on" cycle time (default: 1)
1380 respawntime: "off" cycle time (default: same as wait)
1381 -------- SPAWNFLAGS --------
1382 START_ON: assume it is already turned on (when targeted)
1383 */
1384 void spawnfunc_trigger_multivibrator()
1385 {
1386         if(!self.wait)
1387                 self.wait = 1;
1388         if(!self.respawntime)
1389                 self.respawntime = self.wait;
1390
1391         self.state = 0;
1392         self.use = multivibrator_toggle;
1393         self.think = multivibrator_send;
1394         self.nextthink = time;
1395
1396         IFTARGETED
1397                 multivibrator_reset();
1398 }
1399
1400
1401 void follow_init()
1402 {
1403         entity src, dst;
1404         src = find(world, targetname, self.killtarget);
1405         dst = find(world, targetname, self.target);
1406
1407         if(!src || !dst)
1408         {
1409                 objerror("follow: could not find target/killtarget");
1410                 return;
1411         }
1412
1413         if(self.spawnflags & 1)
1414         {
1415                 // attach
1416                 if(self.spawnflags & 2)
1417                 {
1418                         setattachment(dst, src, self.message);
1419                 }
1420                 else
1421                 {
1422                         attach_sameorigin(dst, src, self.message);
1423                 }
1424         }
1425         else
1426         {
1427                 if(self.spawnflags & 2)
1428                 {
1429                         dst.movetype = MOVETYPE_FOLLOW;
1430                         dst.aiment = src;
1431                         // dst.punchangle = '0 0 0'; // keep unchanged
1432                         dst.view_ofs = dst.origin;
1433                         dst.v_angle = dst.angles;
1434                 }
1435                 else
1436                 {
1437                         follow_sameorigin(dst, src);
1438                 }
1439         }
1440
1441         remove(self);
1442 }
1443
1444 void spawnfunc_misc_follow()
1445 {
1446         InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1447 }
1448
1449
1450
1451 void gamestart_use() {
1452         activator = self;
1453         SUB_UseTargets();
1454         remove(self);
1455 }
1456
1457 void spawnfunc_trigger_gamestart() {
1458         self.use = gamestart_use;
1459         self.reset2 = spawnfunc_trigger_gamestart;
1460
1461         if(self.wait)
1462         {
1463                 self.think = self.use;
1464                 self.nextthink = game_starttime + self.wait;
1465         }
1466         else
1467                 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1468 }
1469
1470
1471
1472
1473 .entity voicescript; // attached voice script
1474 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1475 .float voicescript_nextthink; // time to play next voice
1476 .float voicescript_voiceend; // time when this voice ends
1477
1478 void target_voicescript_clear(entity pl)
1479 {
1480         pl.voicescript = world;
1481 }
1482
1483 void target_voicescript_use()
1484 {
1485         if(activator.voicescript != self)
1486         {
1487                 activator.voicescript = self;
1488                 activator.voicescript_index = 0;
1489                 activator.voicescript_nextthink = time + self.delay;
1490         }
1491 }
1492
1493 void target_voicescript_next(entity pl)
1494 {
1495         entity vs;
1496         float i, n;
1497
1498         vs = pl.voicescript;
1499         if(!vs)
1500                 return;
1501         if(vs.message == "")
1502                 return;
1503         if(pl.classname != "player")
1504                 return;
1505         if(gameover)
1506                 return;
1507
1508         if(time >= pl.voicescript_voiceend)
1509         {
1510                 if(time >= pl.voicescript_nextthink)
1511                 {
1512                         // get the next voice...
1513                         n = tokenize_sane(vs.message);
1514
1515                         if(pl.voicescript_index < vs.cnt)
1516                                 i = pl.voicescript_index * 2;
1517                         else if(n > vs.cnt * 2)
1518                                 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1519                         else
1520                                 i = -1;
1521
1522                         if(i >= 0)
1523                         {
1524                                 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1525                                 pl.voicescript_voiceend = time + stof(argv(i + 1));
1526                         }
1527                         else
1528                                 pl.voicescript = world;
1529
1530                         pl.voicescript_index += 1;
1531                         pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1532                 }
1533         }
1534 }
1535
1536 void spawnfunc_target_voicescript()
1537 {
1538         // netname: directory of the sound files
1539         // message: list of "sound file" duration "sound file" duration, a *, and again a list
1540         //          foo1 4.1 foo2 4.0 foo3 3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1541         // wait: average time between messages
1542         // delay: initial delay before the first message
1543         
1544         float i, n;
1545         self.use = target_voicescript_use;
1546
1547         n = tokenize_sane(self.message);
1548         self.cnt = n / 2;
1549         for(i = 0; i+1 < n; i += 2)
1550         {
1551                 if(argv(i) == "*")
1552                 {
1553                         self.cnt = i / 2;
1554                         ++i;
1555                 }
1556                 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1557         }
1558 }
1559
1560
1561
1562 void trigger_relay_teamcheck_use()
1563 {
1564         if(activator.team)
1565         {
1566                 if(self.spawnflags & 2)
1567                 {
1568                         if(activator.team != self.team)
1569                                 SUB_UseTargets();
1570                 }
1571                 else
1572                 {
1573                         if(activator.team == self.team)
1574                                 SUB_UseTargets();
1575                 }
1576         }
1577         else
1578         {
1579                 if(self.spawnflags & 1)
1580                         SUB_UseTargets();
1581         }
1582 }
1583
1584 void trigger_relay_teamcheck_reset()
1585 {
1586         self.team = self.team_saved;
1587 }
1588
1589 void spawnfunc_trigger_relay_teamcheck()
1590 {
1591         self.team_saved = self.team;
1592         self.use = trigger_relay_teamcheck_use;
1593         self.reset = trigger_relay_teamcheck_reset;
1594 }
1595
1596
1597
1598 void trigger_disablerelay_use()
1599 {
1600         entity e;
1601
1602         float a, b;
1603         a = b = 0;
1604
1605         for(e = world; (e = find(e, targetname, self.target)); )
1606         {
1607                 if(e.use == SUB_UseTargets)
1608                 {
1609                         e.use = SUB_DontUseTargets;
1610                         ++a;
1611                 }
1612                 else if(e.use == SUB_DontUseTargets)
1613                 {
1614                         e.use = SUB_UseTargets;
1615                         ++b;
1616                 }
1617         }
1618
1619         if(!a == !b)
1620                 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1621 }
1622
1623 void spawnfunc_trigger_disablerelay()
1624 {
1625         self.use = trigger_disablerelay_use;
1626 }