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