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