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