]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/g_triggers.qc
Clean up/optimize racer code a bit. Make it use a simpler phys path when idle. Tweak...
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / g_triggers.qc
1 void SUB_DontUseTargets()
2 {
3 }
4
5
6 void() SUB_UseTargets;
7
8 void DelayThink()
9 {
10         activator = self.enemy;
11         SUB_UseTargets ();
12         remove(self);
13 }
14
15 /*
16 ==============================
17 SUB_UseTargets
18
19 the global "activator" should be set to the entity that initiated the firing.
20
21 If self.delay is set, a DelayedUse entity will be created that will actually
22 do the SUB_UseTargets after that many seconds have passed.
23
24 Centerprints any self.message to the activator.
25
26 Removes all entities with a targetname that match self.killtarget,
27 and removes them, so some events can remove other triggers.
28
29 Search for (string)targetname in all entities that
30 match (string)self.target and call their .use function
31
32 ==============================
33 */
34 void SUB_UseTargets()
35 {
36         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                 t.target2 = self.target2;
55                 t.target3 = self.target3;
56                 t.target4 = self.target4;
57                 return;
58         }
59
60
61 //
62 // print the message
63 //
64         if (activator.classname == "player" && self.message != "")
65         {
66                 if(clienttype(activator) == CLIENTTYPE_REAL)
67                 {
68                         centerprint (activator, self.message);
69                         if (!self.noise)
70                                 play2(activator, "misc/talk.wav");
71                 }
72         }
73
74 //
75 // kill the killtagets
76 //
77         s = self.killtarget;
78         if (s != "")
79         {
80                 for(t = world; (t = find(t, targetname, s)); )
81                         remove(t);
82         }
83
84 //
85 // fire targets
86 //
87         act = activator;
88         stemp = self;
89         otemp = other;
90
91         if(stemp.target_random)
92                 RandomSelection_Init();
93
94         for(i = 0; i < 4; ++i)
95         {
96                 switch(i)
97                 {
98                         default:
99                         case 0: s = stemp.target; break;
100                         case 1: s = stemp.target2; break;
101                         case 2: s = stemp.target3; break;
102                         case 3: s = stemp.target4; break;
103                 }
104                 if (s != "")
105                 {
106                         for(t = world; (t = find(t, targetname, s)); )
107                         if(t.use)
108                         {
109                                 if(stemp.target_random)
110                                 {
111                                         RandomSelection_Add(t, 0, string_null, 1, 0);
112                                 }
113                                 else
114                                 {
115                                         self = t;
116                                         other = stemp;
117                                         activator = act;
118                                         self.use();
119                                 }
120                         }
121                 }
122         }
123
124         if(stemp.target_random && RandomSelection_chosen_ent)
125         {
126                 self = RandomSelection_chosen_ent;
127                 other = stemp;
128                 activator = act;
129                 self.use();
130         }
131
132         activator = act;
133         self = stemp;
134         other = otemp;
135 }
136
137
138 //=============================================================================
139
140 float   SPAWNFLAG_NOMESSAGE = 1;
141 float   SPAWNFLAG_NOTOUCH = 1;
142
143 // the wait time has passed, so set back up for another activation
144 void multi_wait()
145 {
146         if (self.max_health)
147         {
148                 self.health = self.max_health;
149                 self.takedamage = DAMAGE_YES;
150                 self.solid = SOLID_BBOX;
151         }
152 }
153
154
155 // the trigger was just touched/killed/used
156 // self.enemy should be set to the activator so it can be held through a delay
157 // so wait for the delay time before firing
158 void multi_trigger()
159 {
160         if (self.nextthink > time)
161         {
162                 return;         // allready been triggered
163         }
164
165         if (self.classname == "trigger_secret")
166         {
167                 if (self.enemy.classname != "player")
168                         return;
169                 found_secrets = found_secrets + 1;
170                 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
171         }
172
173         if (self.noise)
174                 sound (self.enemy, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
175
176 // don't trigger again until reset
177         self.takedamage = DAMAGE_NO;
178
179         activator = self.enemy;
180         other = self.goalentity;
181         SUB_UseTargets();
182
183         if (self.wait > 0)
184         {
185                 self.think = multi_wait;
186                 self.nextthink = time + self.wait;
187         }
188         else if (self.wait == 0)
189         {
190                 multi_wait(); // waiting finished
191         }
192         else
193         {       // we can't just remove (self) here, because this is a touch function
194                 // called wheil C code is looping through area links...
195                 self.touch = SUB_Null;
196         }
197 }
198
199 void multi_use()
200 {
201         self.goalentity = other;
202         self.enemy = activator;
203         multi_trigger();
204 }
205
206 void multi_touch()
207 {
208         if not(self.spawnflags & 2)
209                 if not(other.iscreature)
210                         return;
211
212         if(self.team)
213                 if((self.spawnflags & 4 == 0) == (self.team != other.team))
214                         return;
215
216 // if the trigger has an angles field, check player's facing direction
217         if (self.movedir != '0 0 0')
218         {
219                 makevectors (other.angles);
220                 if (v_forward * self.movedir < 0)
221                         return;         // not facing the right way
222         }
223
224         EXACTTRIGGER_TOUCH;
225
226         self.enemy = other;
227         self.goalentity = other;
228         multi_trigger ();
229 }
230
231 void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
232 {
233         if (!self.takedamage)
234                 return;
235         if(self.spawnflags & DOOR_NOSPLASH)
236                 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
237                         return;
238         self.health = self.health - damage;
239         if (self.health <= 0)
240         {
241                 self.enemy = attacker;
242                 self.goalentity = inflictor;
243                 multi_trigger();
244         }
245 }
246
247 void multi_reset()
248 {
249         if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
250                 self.touch = multi_touch;
251         if (self.max_health)
252         {
253                 self.health = self.max_health;
254                 self.takedamage = DAMAGE_YES;
255                 self.solid = SOLID_BBOX;
256         }
257         self.think = SUB_Null;
258         self.team = self.team_saved;
259 }
260
261 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
262 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.
263 If "delay" is set, the trigger waits some time after activating before firing.
264 "wait" : Seconds between triggerings. (.2 default)
265 If notouch is set, the trigger is only fired by other entities, not by touching.
266 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
267 sounds
268 1)      secret
269 2)      beep beep
270 3)      large switch
271 4)
272 set "message" to text string
273 */
274 void spawnfunc_trigger_multiple()
275 {
276         self.reset = multi_reset;
277         if (self.sounds == 1)
278         {
279                 precache_sound ("misc/secret.wav");
280                 self.noise = "misc/secret.wav";
281         }
282         else if (self.sounds == 2)
283         {
284                 precache_sound ("misc/talk.wav");
285                 self.noise = "misc/talk.wav";
286         }
287         else if (self.sounds == 3)
288         {
289                 precache_sound ("misc/trigger1.wav");
290                 self.noise = "misc/trigger1.wav";
291         }
292
293         if (!self.wait)
294                 self.wait = 0.2;
295         else if(self.wait < -1)
296                 self.wait = 0;
297         self.use = multi_use;
298
299         EXACTTRIGGER_INIT;
300
301         self.team_saved = self.team;
302
303         if (self.health)
304         {
305                 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
306                         objerror ("health and notouch don't make sense\n");
307                 self.max_health = self.health;
308                 self.event_damage = multi_eventdamage;
309                 self.takedamage = DAMAGE_YES;
310                 self.solid = SOLID_BBOX;
311                 setorigin (self, self.origin);  // make sure it links into the world
312         }
313         else
314         {
315                 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
316                 {
317                         self.touch = multi_touch;
318                         setorigin (self, self.origin);  // make sure it links into the world
319                 }
320         }
321 }
322
323
324 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
325 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
326 "targetname".  If "health" is set, the trigger must be killed to activate.
327 If notouch is set, the trigger is only fired by other entities, not by touching.
328 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
329 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.
330 sounds
331 1)      secret
332 2)      beep beep
333 3)      large switch
334 4)
335 set "message" to text string
336 */
337 void spawnfunc_trigger_once()
338 {
339         self.wait = -1;
340         spawnfunc_trigger_multiple();
341 }
342
343 //=============================================================================
344
345 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
346 This fixed size trigger cannot be touched, it can only be fired by other events.  It can contain killtargets, targets, delays, and messages.
347 */
348 void spawnfunc_trigger_relay()
349 {
350         self.use = SUB_UseTargets;
351         self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
352 }
353
354 void delay_use()
355 {
356     self.think = SUB_UseTargets;
357     self.nextthink = self.wait;
358 }
359
360 void delay_reset()
361 {
362         self.think = SUB_Null;
363 }
364
365 void spawnfunc_trigger_delay()
366 {
367     if(!self.wait)
368         self.wait = 1;
369
370     self.use = delay_use;
371     self.reset = delay_reset;
372 }
373
374 //=============================================================================
375
376
377 void counter_use()
378 {
379         self.count = self.count - 1;
380         if (self.count < 0)
381                 return;
382
383         if (self.count != 0)
384         {
385                 if (activator.classname == "player"
386                 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
387                 {
388                         if (self.count >= 4)
389                                 centerprint (activator, "There are more to go...");
390                         else if (self.count == 3)
391                                 centerprint (activator, "Only 3 more to go...");
392                         else if (self.count == 2)
393                                 centerprint (activator, "Only 2 more to go...");
394                         else
395                                 centerprint (activator, "Only 1 more to go...");
396                 }
397                 return;
398         }
399
400         if (activator.classname == "player"
401         && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
402                 centerprint(activator, "Sequence completed!");
403         self.enemy = activator;
404         multi_trigger ();
405 }
406
407 void counter_reset()
408 {
409         self.count = self.cnt;
410         multi_reset();
411 }
412
413 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
414 Acts as an intermediary for an action that takes multiple inputs.
415
416 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
417
418 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
419 */
420 void spawnfunc_trigger_counter()
421 {
422         self.wait = -1;
423         if (!self.count)
424                 self.count = 2;
425         self.cnt = self.count;
426
427         self.use = counter_use;
428         self.reset = counter_reset;
429 }
430
431 void trigger_hurt_use()
432 {
433         if(activator.classname == "player")
434                 self.enemy = activator;
435         else
436                 self.enemy = world; // let's just destroy it, if taking over is too much work
437 }
438
439 .float triggerhurttime;
440 void trigger_hurt_touch()
441 {
442         if (self.active != ACTIVE_ACTIVE) 
443                 return;
444
445         if(self.team)
446                 if((self.spawnflags & 4 == 0) == (self.team != other.team))
447                         return;
448
449         // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
450         if (other.iscreature)
451         {
452                 if (other.takedamage)
453                 if (other.triggerhurttime < time)
454                 {
455                         EXACTTRIGGER_TOUCH;
456                         other.triggerhurttime = time + 1;
457
458                         entity own;
459                         own = self.enemy;
460                         if(own.classname != "player")
461                         {
462                                 own = self;
463                                 self.enemy = world; // I still hate you all
464                         }
465
466                         Damage (other, self, own, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
467                 }
468         }
469         else
470         {
471                 if (!other.owner)
472                 {
473                         if (other.items & IT_KEY1 || other.items & IT_KEY2)     // reset flag
474                         {
475                                 EXACTTRIGGER_TOUCH;
476                                 other.pain_finished = min(other.pain_finished, time + 2);
477                         }
478                         else if (other.classname == "rune")                     // reset runes
479                         {
480                                 EXACTTRIGGER_TOUCH;
481                                 other.nextthink = min(other.nextthink, time + 1);
482                         }
483                 }
484         }
485
486         return;
487 }
488
489 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
490 Any object touching this will be hurt
491 set dmg to damage amount
492 defalt dmg = 5
493 */
494 .entity trigger_hurt_next;
495 entity trigger_hurt_last;
496 entity trigger_hurt_first;
497 void spawnfunc_trigger_hurt()
498 {
499         EXACTTRIGGER_INIT;
500         self.active = ACTIVE_ACTIVE;
501         self.touch = trigger_hurt_touch;
502         self.use = trigger_hurt_use;
503         self.enemy = world; // I hate you all
504         if (!self.dmg)
505                 self.dmg = 1000;
506         if (!self.message)
507                 self.message = "was in the wrong place";
508         if (!self.message2)
509                 self.message2 = "was thrown into a world of hurt by";
510         // self.message = "someone like %s always gets wrongplaced";
511
512         if(!trigger_hurt_first)
513                 trigger_hurt_first = self;
514         if(trigger_hurt_last)
515                 trigger_hurt_last.trigger_hurt_next = self;
516         trigger_hurt_last = self;
517 }
518
519 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
520 {
521         entity th;
522
523         for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
524                 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
525                         return TRUE;
526
527         return FALSE;
528 }
529
530 //////////////////////////////////////////////////////////////
531 //
532 //
533 //
534 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
535 //
536 //////////////////////////////////////////////////////////////
537
538 .float triggerhealtime;
539 void trigger_heal_touch()
540 {
541         if (self.active != ACTIVE_ACTIVE) 
542                 return;
543         
544         // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
545         if (other.iscreature)
546         {
547                 if (other.takedamage)
548                 if (other.triggerhealtime < time)
549                 {
550                         EXACTTRIGGER_TOUCH;
551                         other.triggerhealtime = time + 1;
552                         
553                         if (other.health < self.max_health)
554                         {
555                                 other.health = min(other.health + self.health, self.max_health);
556                                 other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
557                                 sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
558                         }
559                 }
560         }
561 }
562
563 void spawnfunc_trigger_heal()
564 {
565         self.active = ACTIVE_ACTIVE;
566         
567         EXACTTRIGGER_INIT;
568         self.touch = trigger_heal_touch;
569         if (!self.health)
570                 self.health = 10;
571         if (!self.max_health)
572                 self.max_health = 200; //Max health topoff for field
573         if(self.noise == "")
574                 self.noise = "misc/mediumhealth.wav";
575         precache_sound(self.noise);
576 }
577
578
579 //////////////////////////////////////////////////////////////
580 //
581 //
582 //
583 //End trigger_heal
584 //
585 //////////////////////////////////////////////////////////////
586
587 .entity trigger_gravity_check;
588 void trigger_gravity_remove(entity own)
589 {
590         if(own.trigger_gravity_check.owner == own)
591         {
592                 UpdateCSQCProjectile(own);
593                 own.gravity = own.trigger_gravity_check.gravity;
594                 remove(own.trigger_gravity_check);
595         }
596         else
597                 backtrace("Removing a trigger_gravity_check with no valid owner");
598         own.trigger_gravity_check = world;
599 }
600 void trigger_gravity_check_think()
601 {
602         // This spawns when a player enters the gravity zone and checks if he left.
603         // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
604         // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
605         if(self.count <= 0)
606         {
607                 if(self.owner.trigger_gravity_check == self)
608                         trigger_gravity_remove(self.owner);
609                 else
610                         remove(self);
611                 return;
612         }
613         else
614         {
615                 self.count -= 1;
616                 self.nextthink = time;
617         }
618 }
619
620 void trigger_gravity_use()
621 {
622         self.state = !self.state;
623 }
624
625 void trigger_gravity_touch()
626 {
627         float g;
628
629         if(self.state != TRUE)
630                 return;
631
632         EXACTTRIGGER_TOUCH;
633
634         g = self.gravity;
635
636         if not(self.spawnflags & 1)
637         {
638                 if(other.trigger_gravity_check)
639                 {
640                         if(self == other.trigger_gravity_check.enemy)
641                         {
642                                 // same?
643                                 other.trigger_gravity_check.count = 2; // gravity one more frame...
644                                 return;
645                         }
646
647                         // compare prio
648                         if(self.cnt > other.trigger_gravity_check.enemy.cnt)
649                                 trigger_gravity_remove(other);
650                         else
651                                 return;
652                 }
653                 other.trigger_gravity_check = spawn();
654                 other.trigger_gravity_check.enemy = self;
655                 other.trigger_gravity_check.owner = other;
656                 other.trigger_gravity_check.gravity = other.gravity;
657                 other.trigger_gravity_check.think = trigger_gravity_check_think;
658                 other.trigger_gravity_check.nextthink = time;
659                 other.trigger_gravity_check.count = 2;
660                 if(other.gravity)
661                         g *= other.gravity;
662         }
663
664         if (other.gravity != g)
665         {
666                 other.gravity = g;
667                 if(self.noise != "")
668                         sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
669                 UpdateCSQCProjectile(self.owner);
670         }
671 }
672
673 void spawnfunc_trigger_gravity()
674 {
675         if(self.gravity == 1)
676                 return;
677
678         EXACTTRIGGER_INIT;
679         self.touch = trigger_gravity_touch;
680         if(self.noise != "")
681                 precache_sound(self.noise);
682
683         self.state = TRUE;
684         IFTARGETED
685         {
686                 self.use = trigger_gravity_use;
687                 if(self.spawnflags & 2)
688                         self.state = FALSE;
689         }
690 }
691
692 //=============================================================================
693
694 // TODO add a way to do looped sounds with sound(); then complete this entity
695 .float volume, atten;
696 void target_speaker_use_off();
697 void target_speaker_use_activator()
698 {
699         if(clienttype(activator) != CLIENTTYPE_REAL)
700                 return;
701         string snd;
702         if(substring(self.noise, 0, 1) == "*")
703         {
704                 var .string sample;
705                 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
706                 if(GetPlayerSoundSampleField_notFound)
707                         snd = "misc/null.wav";
708                 else if(activator.sample == "")
709                         snd = "misc/null.wav";
710                 else
711                 {
712                         tokenize_console(activator.sample);
713                         float n;
714                         n = stof(argv(1));
715                         if(n > 0)
716                                 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
717                         else
718                                 snd = strcat(argv(0), ".wav"); // randomization
719                 }
720         }
721         else
722                 snd = self.noise;
723         msg_entity = activator;
724         soundto(MSG_ONE, self, CH_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
725 }
726 void target_speaker_use_on()
727 {
728         string snd;
729         if(substring(self.noise, 0, 1) == "*")
730         {
731                 var .string sample;
732                 sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
733                 if(GetPlayerSoundSampleField_notFound)
734                         snd = "misc/null.wav";
735                 else if(activator.sample == "")
736                         snd = "misc/null.wav";
737                 else
738                 {
739                         tokenize_console(activator.sample);
740                         float n;
741                         n = stof(argv(1));
742                         if(n > 0)
743                                 snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
744                         else
745                                 snd = strcat(argv(0), ".wav"); // randomization
746                 }
747         }
748         else
749                 snd = self.noise;
750         sound(self, CH_TRIGGER_SINGLE, snd, VOL_BASE * self.volume, self.atten);
751         if(self.spawnflags & 3)
752                 self.use = target_speaker_use_off;
753 }
754 void target_speaker_use_off()
755 {
756         sound(self, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_BASE * self.volume, self.atten);
757         self.use = target_speaker_use_on;
758 }
759 void target_speaker_reset()
760 {
761         if(self.spawnflags & 1) // LOOPED_ON
762         {
763                 if(self.use == target_speaker_use_on)
764                         target_speaker_use_on();
765         }
766         else if(self.spawnflags & 2)
767         {
768                 if(self.use == target_speaker_use_off)
769                         target_speaker_use_off();
770         }
771 }
772
773 void spawnfunc_target_speaker()
774 {
775         // TODO: "*" prefix to sound file name
776         // TODO: wait and random (just, HOW? random is not a field)
777         if(self.noise)
778                 precache_sound (self.noise);
779
780         if(!self.atten && !(self.spawnflags & 4))
781         {
782                 IFTARGETED
783                         self.atten = ATTN_NORM;
784                 else
785                         self.atten = ATTN_STATIC;
786         }
787         else if(self.atten < 0)
788                 self.atten = 0;
789
790         if(!self.volume)
791                 self.volume = 1;
792
793         IFTARGETED
794         {
795                 if(self.spawnflags & 8) // ACTIVATOR
796                         self.use = target_speaker_use_activator;
797                 else if(self.spawnflags & 1) // LOOPED_ON
798                 {
799                         target_speaker_use_on();
800                         self.reset = target_speaker_reset;
801                 }
802                 else if(self.spawnflags & 2) // LOOPED_OFF
803                 {
804                         self.use = target_speaker_use_on;
805                         self.reset = target_speaker_reset;
806                 }
807                 else
808                         self.use = target_speaker_use_on;
809         }
810         else if(self.spawnflags & 1) // LOOPED_ON
811         {
812                 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
813                 remove(self);
814         }
815         else if(self.spawnflags & 2) // LOOPED_OFF
816         {
817                 objerror("This sound entity can never be activated");
818         }
819         else
820         {
821                 // Quake/Nexuiz fallback
822                 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
823                 remove(self);
824         }
825 }
826
827
828 void spawnfunc_func_stardust() {
829         self.effects = EF_STARDUST;
830 }
831
832 .string bgmscript;
833 .float bgmscriptattack;
834 .float bgmscriptdecay;
835 .float bgmscriptsustain;
836 .float bgmscriptrelease;
837 float pointparticles_SendEntity(entity to, float fl)
838 {
839         WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
840
841         // optional features to save space
842         fl = fl & 0x0F;
843         if(self.spawnflags & 2)
844                 fl |= 0x10; // absolute count on toggle-on
845         if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
846                 fl |= 0x20; // 4 bytes - saves CPU
847         if(self.waterlevel || self.count != 1)
848                 fl |= 0x40; // 4 bytes - obscure features almost never used
849         if(self.mins != '0 0 0' || self.maxs != '0 0 0')
850                 fl |= 0x80; // 14 bytes - saves lots of space
851
852         WriteByte(MSG_ENTITY, fl);
853         if(fl & 2)
854         {
855                 if(self.state)
856                         WriteCoord(MSG_ENTITY, self.impulse);
857                 else
858                         WriteCoord(MSG_ENTITY, 0); // off
859         }
860         if(fl & 4)
861         {
862                 WriteCoord(MSG_ENTITY, self.origin_x);
863                 WriteCoord(MSG_ENTITY, self.origin_y);
864                 WriteCoord(MSG_ENTITY, self.origin_z);
865         }
866         if(fl & 1)
867         {
868                 if(self.model != "null")
869                 {
870                         WriteShort(MSG_ENTITY, self.modelindex);
871                         if(fl & 0x80)
872                         {
873                                 WriteCoord(MSG_ENTITY, self.mins_x);
874                                 WriteCoord(MSG_ENTITY, self.mins_y);
875                                 WriteCoord(MSG_ENTITY, self.mins_z);
876                                 WriteCoord(MSG_ENTITY, self.maxs_x);
877                                 WriteCoord(MSG_ENTITY, self.maxs_y);
878                                 WriteCoord(MSG_ENTITY, self.maxs_z);
879                         }
880                 }
881                 else
882                 {
883                         WriteShort(MSG_ENTITY, 0);
884                         if(fl & 0x80)
885                         {
886                                 WriteCoord(MSG_ENTITY, self.maxs_x);
887                                 WriteCoord(MSG_ENTITY, self.maxs_y);
888                                 WriteCoord(MSG_ENTITY, self.maxs_z);
889                         }
890                 }
891                 WriteShort(MSG_ENTITY, self.cnt);
892                 if(fl & 0x20)
893                 {
894                         WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
895                         WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
896                 }
897                 if(fl & 0x40)
898                 {
899                         WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
900                         WriteByte(MSG_ENTITY, self.count * 16.0);
901                 }
902                 WriteString(MSG_ENTITY, self.noise);
903                 if(self.noise != "")
904                 {
905                         WriteByte(MSG_ENTITY, floor(self.atten * 64));
906                         WriteByte(MSG_ENTITY, floor(self.volume * 255));
907                 }
908                 WriteString(MSG_ENTITY, self.bgmscript);
909                 if(self.bgmscript != "")
910                 {
911                         WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
912                         WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
913                         WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
914                         WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
915                 }
916         }
917         return 1;
918 }
919
920 void pointparticles_use()
921 {
922         self.state = !self.state;
923         self.SendFlags |= 2;
924 }
925
926 void pointparticles_think()
927 {
928         if(self.origin != self.oldorigin)
929         {
930                 self.SendFlags |= 4;
931                 self.oldorigin = self.origin;
932         }
933         self.nextthink = time;
934 }
935
936 void pointparticles_reset()
937 {
938         if(self.spawnflags & 1)
939                 self.state = 1;
940         else
941                 self.state = 0;
942 }
943
944 void spawnfunc_func_pointparticles()
945 {
946         if(self.model != "")
947                 setmodel(self, self.model);
948         if(self.noise != "")
949                 precache_sound (self.noise);
950         
951         if(!self.bgmscriptsustain)
952                 self.bgmscriptsustain = 1;
953         else if(self.bgmscriptsustain < 0)
954                 self.bgmscriptsustain = 0;
955
956         if(!self.atten)
957                 self.atten = ATTN_NORM;
958         else if(self.atten < 0)
959                 self.atten = 0;
960         if(!self.volume)
961                 self.volume = 1;
962         if(!self.count)
963                 self.count = 1;
964         if(!self.impulse)
965                 self.impulse = 1;
966
967         if(!self.modelindex)
968         {
969                 setorigin(self, self.origin + self.mins);
970                 setsize(self, '0 0 0', self.maxs - self.mins);
971         }
972         if(!self.cnt)
973                 self.cnt = particleeffectnum(self.mdl);
974
975         Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
976
977         IFTARGETED
978         {
979                 self.use = pointparticles_use;
980                 self.reset = pointparticles_reset;
981                 self.reset();
982         }
983         else
984                 self.state = 1;
985         self.think = pointparticles_think;
986         self.nextthink = time;
987 }
988
989 void spawnfunc_func_sparks()
990 {
991         // self.cnt is the amount of sparks that one burst will spawn
992         if(self.cnt < 1) {
993                 self.cnt = 25.0; // nice default value
994         }
995
996         // self.wait is the probability that a sparkthink will spawn a spark shower
997         // range: 0 - 1, but 0 makes little sense, so...
998         if(self.wait < 0.05) {
999                 self.wait = 0.25; // nice default value
1000         }
1001
1002         self.count = self.cnt;
1003         self.mins = '0 0 0';
1004         self.maxs = '0 0 0';
1005         self.velocity = '0 0 -1';
1006         self.mdl = "TE_SPARK";
1007         self.impulse = 10 * self.wait; // by default 2.5/sec
1008         self.wait = 0;
1009         self.cnt = 0; // use mdl
1010
1011         spawnfunc_func_pointparticles();
1012 }
1013
1014 float rainsnow_SendEntity(entity to, float sf)
1015 {
1016         WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
1017         WriteByte(MSG_ENTITY, self.state);
1018         WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
1019         WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
1020         WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
1021         WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
1022         WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
1023         WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
1024         WriteShort(MSG_ENTITY, compressShortVector(self.dest));
1025         WriteShort(MSG_ENTITY, self.count);
1026         WriteByte(MSG_ENTITY, self.cnt);
1027         return 1;
1028 }
1029
1030 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
1031 This is an invisible area like a trigger, which rain falls inside of.
1032
1033 Keys:
1034 "velocity"
1035  falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
1036 "cnt"
1037  sets color of rain (default 12 - white)
1038 "count"
1039  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1040 */
1041 void spawnfunc_func_rain()
1042 {
1043         self.dest = self.velocity;
1044         self.velocity = '0 0 0';
1045         if (!self.dest)
1046                 self.dest = '0 0 -700';
1047         self.angles = '0 0 0';
1048         self.movetype = MOVETYPE_NONE;
1049         self.solid = SOLID_NOT;
1050         SetBrushEntityModel();
1051         if (!self.cnt)
1052                 self.cnt = 12;
1053         if (!self.count)
1054                 self.count = 2000;
1055         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1056         if (self.count < 1)
1057                 self.count = 1;
1058         if(self.count > 65535)
1059                 self.count = 65535;
1060
1061         self.state = 1; // 1 is rain, 0 is snow
1062         self.Version = 1;
1063
1064         Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1065 }
1066
1067
1068 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
1069 This is an invisible area like a trigger, which snow falls inside of.
1070
1071 Keys:
1072 "velocity"
1073  falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
1074 "cnt"
1075  sets color of rain (default 12 - white)
1076 "count"
1077  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
1078 */
1079 void spawnfunc_func_snow()
1080 {
1081         self.dest = self.velocity;
1082         self.velocity = '0 0 0';
1083         if (!self.dest)
1084                 self.dest = '0 0 -300';
1085         self.angles = '0 0 0';
1086         self.movetype = MOVETYPE_NONE;
1087         self.solid = SOLID_NOT;
1088         SetBrushEntityModel();
1089         if (!self.cnt)
1090                 self.cnt = 12;
1091         if (!self.count)
1092                 self.count = 2000;
1093         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
1094         if (self.count < 1)
1095                 self.count = 1;
1096         if(self.count > 65535)
1097                 self.count = 65535;
1098
1099         self.state = 0; // 1 is rain, 0 is snow
1100         self.Version = 1;
1101
1102         Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
1103 }
1104
1105
1106 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float mindist, float maxdist, float halflifedist, float forcehalflifedist, float deathtype);
1107
1108 .float modelscale;
1109 void misc_laser_aim()
1110 {
1111         vector a;
1112         if(self.enemy)
1113         {
1114                 if(self.spawnflags & 2)
1115                 {
1116                         if(self.enemy.origin != self.mangle)
1117                         {
1118                                 self.mangle = self.enemy.origin;
1119                                 self.SendFlags |= 2;
1120                         }
1121                 }
1122                 else
1123                 {
1124                         a = vectoangles(self.enemy.origin - self.origin);
1125                         a_x = -a_x;
1126                         if(a != self.mangle)
1127                         {
1128                                 self.mangle = a;
1129                                 self.SendFlags |= 2;
1130                         }
1131                 }
1132         }
1133         else
1134         {
1135                 if(self.angles != self.mangle)
1136                 {
1137                         self.mangle = self.angles;
1138                         self.SendFlags |= 2;
1139                 }
1140         }
1141         if(self.origin != self.oldorigin)
1142         {
1143                 self.SendFlags |= 1;
1144                 self.oldorigin = self.origin;
1145         }
1146 }
1147
1148 void misc_laser_init()
1149 {
1150         if(self.target != "")
1151                 self.enemy = find(world, targetname, self.target);
1152 }
1153
1154 .entity pusher;
1155 void misc_laser_think()
1156 {
1157         vector o;
1158         entity oldself;
1159         entity hitent;
1160         vector hitloc;
1161
1162         self.nextthink = time;
1163
1164         if(!self.state)
1165                 return;
1166
1167         misc_laser_aim();
1168
1169         if(self.enemy)
1170         {
1171                 o = self.enemy.origin;
1172                 if not(self.spawnflags & 2)
1173                         o = self.origin + normalize(o - self.origin) * 32768;
1174         }
1175         else
1176         {
1177                 makevectors(self.mangle);
1178                 o = self.origin + v_forward * 32768;
1179         }
1180
1181         if(self.dmg || self.enemy.target != "")
1182         {
1183                 traceline(self.origin, o, MOVE_NORMAL, self);
1184         }
1185         hitent = trace_ent;
1186         hitloc = trace_endpos;
1187
1188         if(self.enemy.target != "") // DETECTOR laser
1189         {
1190                 if(trace_ent.iscreature)
1191                 {
1192                         self.pusher = hitent;
1193                         if(!self.count)
1194                         {
1195                                 self.count = 1;
1196
1197                                 oldself = self;
1198                                 self = self.enemy;
1199                                 activator = self.pusher;
1200                                 SUB_UseTargets();
1201                                 self = oldself;
1202                         }
1203                 }
1204                 else
1205                 {
1206                         if(self.count)
1207                         {
1208                                 self.count = 0;
1209
1210                                 oldself = self;
1211                                 self = self.enemy;
1212                                 activator = self.pusher;
1213                                 SUB_UseTargets();
1214                                 self = oldself;
1215                         }
1216                 }
1217         }
1218
1219         if(self.dmg)
1220         {
1221                 if(self.team)
1222                         if((self.spawnflags & 8 == 0) == (self.team != hitent.team))
1223                                 return;
1224                 if(hitent.takedamage)
1225                         Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
1226         }
1227 }
1228
1229 float laser_SendEntity(entity to, float fl)
1230 {
1231         WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
1232         fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
1233         if(self.spawnflags & 2)
1234                 fl |= 0x80;
1235         if(self.alpha)
1236                 fl |= 0x40;
1237         if(self.scale != 1 || self.modelscale != 1)
1238                 fl |= 0x20;
1239         if(self.spawnflags & 4)
1240                 fl |= 0x10;
1241         WriteByte(MSG_ENTITY, fl);
1242         if(fl & 1)
1243         {
1244                 WriteCoord(MSG_ENTITY, self.origin_x);
1245                 WriteCoord(MSG_ENTITY, self.origin_y);
1246                 WriteCoord(MSG_ENTITY, self.origin_z);
1247         }
1248         if(fl & 8)
1249         {
1250                 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
1251                 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
1252                 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
1253                 if(fl & 0x40)
1254                         WriteByte(MSG_ENTITY, self.alpha * 255.0);
1255                 if(fl & 0x20)
1256                 {
1257                         WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
1258                         WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
1259                 }
1260                 if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
1261                         WriteShort(MSG_ENTITY, self.cnt + 1);
1262         }
1263         if(fl & 2)
1264         {
1265                 if(fl & 0x80)
1266                 {
1267                         WriteCoord(MSG_ENTITY, self.enemy.origin_x);
1268                         WriteCoord(MSG_ENTITY, self.enemy.origin_y);
1269                         WriteCoord(MSG_ENTITY, self.enemy.origin_z);
1270                 }
1271                 else
1272                 {
1273                         WriteAngle(MSG_ENTITY, self.mangle_x);
1274                         WriteAngle(MSG_ENTITY, self.mangle_y);
1275                 }
1276         }
1277         if(fl & 4)
1278                 WriteByte(MSG_ENTITY, self.state);
1279         return 1;
1280 }
1281
1282 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
1283 Any object touching the beam will be hurt
1284 Keys:
1285 "target"
1286  spawnfunc_target_position where the laser ends
1287 "mdl"
1288  name of beam end effect to use
1289 "colormod"
1290  color of the beam (default: red)
1291 "dmg"
1292  damage per second (-1 for a laser that kills immediately)
1293 */
1294 void laser_use()
1295 {
1296         self.state = !self.state;
1297         self.SendFlags |= 4;
1298         misc_laser_aim();
1299 }
1300
1301 void laser_reset()
1302 {
1303         if(self.spawnflags & 1)
1304                 self.state = 1;
1305         else
1306                 self.state = 0;
1307 }
1308
1309 void spawnfunc_misc_laser()
1310 {
1311         if(self.mdl)
1312         {
1313                 if(self.mdl == "none")
1314                         self.cnt = -1;
1315                 else
1316                 {
1317                         self.cnt = particleeffectnum(self.mdl);
1318                         if(self.cnt < 0)
1319                                 if(self.dmg)
1320                                         self.cnt = particleeffectnum("laser_deadly");
1321                 }
1322         }
1323         else if(!self.cnt)
1324         {
1325                 if(self.dmg)
1326                         self.cnt = particleeffectnum("laser_deadly");
1327                 else
1328                         self.cnt = -1;
1329         }
1330         if(self.cnt < 0)
1331                 self.cnt = -1;
1332
1333         if(self.colormod == '0 0 0')
1334                 if(!self.alpha)
1335                         self.colormod = '1 0 0';
1336         if(!self.message)
1337                 self.message = "saw the light";
1338         if (!self.message2)
1339                 self.message2 = "was pushed into a laser by";
1340         if(!self.scale)
1341                 self.scale = 1;
1342         if(!self.modelscale)
1343                 self.modelscale = 1;
1344         else if(self.modelscale < 0)
1345                 self.modelscale = 0;
1346         self.think = misc_laser_think;
1347         self.nextthink = time;
1348         InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1349
1350         self.mangle = self.angles;
1351
1352         Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1353
1354         IFTARGETED
1355         {
1356                 self.reset = laser_reset;
1357                 laser_reset();
1358                 self.use = laser_use;
1359         }
1360         else
1361                 self.state = 1;
1362 }
1363
1364 // tZorks trigger impulse / gravity
1365 .float radius;
1366 .float falloff;
1367 .float strength;
1368 .float lastpushtime;
1369
1370 // targeted (directional) mode
1371 void trigger_impulse_touch1()
1372 {
1373         entity targ;
1374     float pushdeltatime;
1375     float str;
1376
1377         if (self.active != ACTIVE_ACTIVE) 
1378                 return;
1379
1380         if (!isPushable(other))
1381                 return;
1382
1383         EXACTTRIGGER_TOUCH;
1384
1385     targ = find(world, targetname, self.target);
1386     if(!targ)
1387     {
1388         objerror("trigger_force without a (valid) .target!\n");
1389         remove(self);
1390         return;
1391     }
1392
1393     str = min(self.radius, vlen(self.origin - other.origin));
1394
1395     if(self.falloff == 1)
1396         str = (str / self.radius) * self.strength;
1397     else if(self.falloff == 2)
1398         str = (1 - (str / self.radius)) * self.strength;
1399     else
1400         str = self.strength;
1401
1402     pushdeltatime = time - other.lastpushtime;
1403     if (pushdeltatime > 0.15) pushdeltatime = 0;
1404     other.lastpushtime = time;
1405     if(!pushdeltatime) return;
1406
1407     other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
1408     other.flags &~= FL_ONGROUND;
1409     UpdateCSQCProjectile(other);
1410 }
1411
1412 // Directionless (accelerator/decelerator) mode
1413 void trigger_impulse_touch2()
1414 {
1415     float pushdeltatime;
1416
1417         if (self.active != ACTIVE_ACTIVE) 
1418                 return;
1419
1420         if (!isPushable(other))
1421                 return;
1422
1423         EXACTTRIGGER_TOUCH;
1424
1425     pushdeltatime = time - other.lastpushtime;
1426     if (pushdeltatime > 0.15) pushdeltatime = 0;
1427     other.lastpushtime = time;
1428     if(!pushdeltatime) return;
1429
1430     // div0: ticrate independent, 1 = identity (not 20)
1431     other.velocity = other.velocity * pow(self.strength, pushdeltatime);
1432     UpdateCSQCProjectile(other);
1433 }
1434
1435 // Spherical (gravity/repulsor) mode
1436 void trigger_impulse_touch3()
1437 {
1438     float pushdeltatime;
1439     float str;
1440
1441         if (self.active != ACTIVE_ACTIVE) 
1442                 return;
1443
1444         if (!isPushable(other))
1445                 return;
1446
1447         EXACTTRIGGER_TOUCH;
1448
1449     pushdeltatime = time - other.lastpushtime;
1450     if (pushdeltatime > 0.15) pushdeltatime = 0;
1451     other.lastpushtime = time;
1452     if(!pushdeltatime) return;
1453
1454     setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1455
1456         str = min(self.radius, vlen(self.origin - other.origin));
1457
1458     if(self.falloff == 1)
1459         str = (1 - str / self.radius) * self.strength; // 1 in the inside
1460     else if(self.falloff == 2)
1461         str = (str / self.radius) * self.strength; // 0 in the inside
1462     else
1463         str = self.strength;
1464
1465     other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1466     UpdateCSQCProjectile(other);
1467 }
1468
1469 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1470 -------- KEYS --------
1471 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1472          If not, this trigger acts like a damper/accelerator field.
1473
1474 strength : This is how mutch force to add in the direction of .target each second
1475            when .target is set. If not, this is hoe mutch to slow down/accelerate
1476            someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1477
1478 radius   : If set, act as a spherical device rather then a liniar one.
1479
1480 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1481
1482 -------- NOTES --------
1483 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1484 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1485 */
1486
1487 void spawnfunc_trigger_impulse()
1488 {
1489         self.active = ACTIVE_ACTIVE;
1490
1491         EXACTTRIGGER_INIT;
1492     if(self.radius)
1493     {
1494         if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
1495         setorigin(self, self.origin);
1496         setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1497         self.touch = trigger_impulse_touch3;
1498     }
1499     else
1500     {
1501         if(self.target)
1502         {
1503             if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
1504             self.touch = trigger_impulse_touch1;
1505         }
1506         else
1507         {
1508             if(!self.strength) self.strength = 0.9;
1509                         self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
1510             self.touch = trigger_impulse_touch2;
1511         }
1512     }
1513 }
1514
1515 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1516 "Flip-flop" trigger gate... lets only every second trigger event through
1517 */
1518 void flipflop_use()
1519 {
1520         self.state = !self.state;
1521         if(self.state)
1522                 SUB_UseTargets();
1523 }
1524
1525 void spawnfunc_trigger_flipflop()
1526 {
1527         if(self.spawnflags & 1)
1528                 self.state = 1;
1529         self.use = flipflop_use;
1530         self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1531 }
1532
1533 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1534 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1535 */
1536 void monoflop_use()
1537 {
1538         self.nextthink = time + self.wait;
1539         self.enemy = activator;
1540         if(self.state)
1541                 return;
1542         self.state = 1;
1543         SUB_UseTargets();
1544 }
1545 void monoflop_fixed_use()
1546 {
1547         if(self.state)
1548                 return;
1549         self.nextthink = time + self.wait;
1550         self.state = 1;
1551         self.enemy = activator;
1552         SUB_UseTargets();
1553 }
1554
1555 void monoflop_think()
1556 {
1557         self.state = 0;
1558         activator = self.enemy;
1559         SUB_UseTargets();
1560 }
1561
1562 void monoflop_reset()
1563 {
1564         self.state = 0;
1565         self.nextthink = 0;
1566 }
1567
1568 void spawnfunc_trigger_monoflop()
1569 {
1570         if(!self.wait)
1571                 self.wait = 1;
1572         if(self.spawnflags & 1)
1573                 self.use = monoflop_fixed_use;
1574         else
1575                 self.use = monoflop_use;
1576         self.think = monoflop_think;
1577         self.state = 0;
1578         self.reset = monoflop_reset;
1579 }
1580
1581 void multivibrator_send()
1582 {
1583         float newstate;
1584         float cyclestart;
1585
1586         cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1587
1588         newstate = (time < cyclestart + self.wait);
1589
1590         activator = self;
1591         if(self.state != newstate)
1592                 SUB_UseTargets();
1593         self.state = newstate;
1594
1595         if(self.state)
1596                 self.nextthink = cyclestart + self.wait + 0.01;
1597         else
1598                 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1599 }
1600
1601 void multivibrator_toggle()
1602 {
1603         if(self.nextthink == 0)
1604         {
1605                 multivibrator_send();
1606         }
1607         else
1608         {
1609                 if(self.state)
1610                 {
1611                         SUB_UseTargets();
1612                         self.state = 0;
1613                 }
1614                 self.nextthink = 0;
1615         }
1616 }
1617
1618 void multivibrator_reset()
1619 {
1620         if(!(self.spawnflags & 1))
1621                 self.nextthink = 0; // wait for a trigger event
1622         else
1623                 self.nextthink = max(1, time);
1624 }
1625
1626 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1627 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1628 -------- KEYS --------
1629 target: trigger all entities with this targetname when it goes off
1630 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1631 phase: offset of the timing
1632 wait: "on" cycle time (default: 1)
1633 respawntime: "off" cycle time (default: same as wait)
1634 -------- SPAWNFLAGS --------
1635 START_ON: assume it is already turned on (when targeted)
1636 */
1637 void spawnfunc_trigger_multivibrator()
1638 {
1639         if(!self.wait)
1640                 self.wait = 1;
1641         if(!self.respawntime)
1642                 self.respawntime = self.wait;
1643
1644         self.state = 0;
1645         self.use = multivibrator_toggle;
1646         self.think = multivibrator_send;
1647         self.nextthink = max(1, time);
1648
1649         IFTARGETED
1650                 multivibrator_reset();
1651 }
1652
1653
1654 void follow_init()
1655 {
1656         entity src, dst;
1657         src = world;
1658         dst = world;
1659         if(self.killtarget != "")
1660                 src = find(world, targetname, self.killtarget);
1661         if(self.target != "")
1662                 dst = find(world, targetname, self.target);
1663
1664         if(!src && !dst)
1665         {
1666                 objerror("follow: could not find target/killtarget");
1667                 return;
1668         }
1669
1670         if(self.jointtype)
1671         {
1672                 // already done :P entity must stay
1673                 self.aiment = src;
1674                 self.enemy = dst;
1675         }
1676         else if(!src || !dst)
1677         {
1678                 objerror("follow: could not find target/killtarget");
1679                 return;
1680         }
1681         else if(self.spawnflags & 1)
1682         {
1683                 // attach
1684                 if(self.spawnflags & 2)
1685                 {
1686                         setattachment(dst, src, self.message);
1687                 }
1688                 else
1689                 {
1690                         attach_sameorigin(dst, src, self.message);
1691                 }
1692
1693                 dst.solid = SOLID_NOT; // solid doesn't work with attachment
1694                 remove(self);
1695         }
1696         else
1697         {
1698                 if(self.spawnflags & 2)
1699                 {
1700                         dst.movetype = MOVETYPE_FOLLOW;
1701                         dst.aiment = src;
1702                         // dst.punchangle = '0 0 0'; // keep unchanged
1703                         dst.view_ofs = dst.origin;
1704                         dst.v_angle = dst.angles;
1705                 }
1706                 else
1707                 {
1708                         follow_sameorigin(dst, src);
1709                 }
1710
1711                 remove(self);
1712         }
1713 }
1714
1715 void spawnfunc_misc_follow()
1716 {
1717         InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1718 }
1719
1720
1721
1722 void gamestart_use() {
1723         activator = self;
1724         SUB_UseTargets();
1725         remove(self);
1726 }
1727
1728 void spawnfunc_trigger_gamestart() {
1729         self.use = gamestart_use;
1730         self.reset2 = spawnfunc_trigger_gamestart;
1731
1732         if(self.wait)
1733         {
1734                 self.think = self.use;
1735                 self.nextthink = game_starttime + self.wait;
1736         }
1737         else
1738                 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1739 }
1740
1741
1742
1743
1744 .entity voicescript; // attached voice script
1745 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1746 .float voicescript_nextthink; // time to play next voice
1747 .float voicescript_voiceend; // time when this voice ends
1748
1749 void target_voicescript_clear(entity pl)
1750 {
1751         pl.voicescript = world;
1752 }
1753
1754 void target_voicescript_use()
1755 {
1756         if(activator.voicescript != self)
1757         {
1758                 activator.voicescript = self;
1759                 activator.voicescript_index = 0;
1760                 activator.voicescript_nextthink = time + self.delay;
1761         }
1762 }
1763
1764 void target_voicescript_next(entity pl)
1765 {
1766         entity vs;
1767         float i, n, dt;
1768
1769         vs = pl.voicescript;
1770         if(!vs)
1771                 return;
1772         if(vs.message == "")
1773                 return;
1774         if(pl.classname != "player")
1775                 return;
1776         if(gameover)
1777                 return;
1778
1779         if(time >= pl.voicescript_voiceend)
1780         {
1781                 if(time >= pl.voicescript_nextthink)
1782                 {
1783                         // get the next voice...
1784                         n = tokenize_console(vs.message);
1785
1786                         if(pl.voicescript_index < vs.cnt)
1787                                 i = pl.voicescript_index * 2;
1788                         else if(n > vs.cnt * 2)
1789                                 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1790                         else
1791                                 i = -1;
1792
1793                         if(i >= 0)
1794                         {
1795                                 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1796                                 dt = stof(argv(i + 1));
1797                                 if(dt >= 0)
1798                                 {
1799                                         pl.voicescript_voiceend = time + dt;
1800                                         pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1801                                 }
1802                                 else
1803                                 {
1804                                         pl.voicescript_voiceend = time - dt;
1805                                         pl.voicescript_nextthink = pl.voicescript_voiceend;
1806                                 }
1807
1808                                 pl.voicescript_index += 1;
1809                         }
1810                         else
1811                         {
1812                                 pl.voicescript = world; // stop trying then
1813                         }
1814                 }
1815         }
1816 }
1817
1818 void spawnfunc_target_voicescript()
1819 {
1820         // netname: directory of the sound files
1821         // message: list of "sound file" duration "sound file" duration, a *, and again a list
1822         //          foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1823         //          Here, a - in front of the duration means that no delay is to be
1824         //          added after this message
1825         // wait: average time between messages
1826         // delay: initial delay before the first message
1827         
1828         float i, n;
1829         self.use = target_voicescript_use;
1830
1831         n = tokenize_console(self.message);
1832         self.cnt = n / 2;
1833         for(i = 0; i+1 < n; i += 2)
1834         {
1835                 if(argv(i) == "*")
1836                 {
1837                         self.cnt = i / 2;
1838                         ++i;
1839                 }
1840                 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1841         }
1842 }
1843
1844
1845
1846 void trigger_relay_teamcheck_use()
1847 {
1848         if(activator.team)
1849         {
1850                 if(self.spawnflags & 2)
1851                 {
1852                         if(activator.team != self.team)
1853                                 SUB_UseTargets();
1854                 }
1855                 else
1856                 {
1857                         if(activator.team == self.team)
1858                                 SUB_UseTargets();
1859                 }
1860         }
1861         else
1862         {
1863                 if(self.spawnflags & 1)
1864                         SUB_UseTargets();
1865         }
1866 }
1867
1868 void trigger_relay_teamcheck_reset()
1869 {
1870         self.team = self.team_saved;
1871 }
1872
1873 void spawnfunc_trigger_relay_teamcheck()
1874 {
1875         self.team_saved = self.team;
1876         self.use = trigger_relay_teamcheck_use;
1877         self.reset = trigger_relay_teamcheck_reset;
1878 }
1879
1880
1881
1882 void trigger_disablerelay_use()
1883 {
1884         entity e;
1885
1886         float a, b;
1887         a = b = 0;
1888
1889         for(e = world; (e = find(e, targetname, self.target)); )
1890         {
1891                 if(e.use == SUB_UseTargets)
1892                 {
1893                         e.use = SUB_DontUseTargets;
1894                         ++a;
1895                 }
1896                 else if(e.use == SUB_DontUseTargets)
1897                 {
1898                         e.use = SUB_UseTargets;
1899                         ++b;
1900                 }
1901         }
1902
1903         if((!a) == (!b))
1904                 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1905 }
1906
1907 void spawnfunc_trigger_disablerelay()
1908 {
1909         self.use = trigger_disablerelay_use;
1910 }
1911
1912 float magicear_matched;
1913 float W_Tuba_HasPlayed(entity pl, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo);
1914 string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
1915 {
1916         float domatch, dotrigger, matchstart, l;
1917         string s, msg;
1918         entity oldself;
1919         string savemessage;
1920
1921         magicear_matched = FALSE;
1922
1923         dotrigger = ((source.classname == "player") && (source.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
1924         domatch = ((ear.spawnflags & 32) || dotrigger);
1925
1926         if not(domatch)
1927                 return msgin;
1928
1929         if not(msgin)
1930         {
1931                 // we are in TUBA mode!
1932                 if not(ear.spawnflags & 256)
1933                         return msgin;
1934
1935                 if(!W_Tuba_HasPlayed(source, ear.message, ear.movedir_x, !(ear.spawnflags & 512), ear.movedir_y, ear.movedir_z))
1936                         return msgin;
1937
1938                 magicear_matched = TRUE;
1939
1940                 if(dotrigger)
1941                 {
1942                         oldself = self;
1943                         activator = source;
1944                         self = ear;
1945                         savemessage = self.message;
1946                         self.message = string_null;
1947                         SUB_UseTargets();
1948                         self.message = savemessage;
1949                         self = oldself;
1950                 }
1951
1952                 if(ear.netname != "")
1953                         return ear.netname;
1954
1955                 return msgin;
1956         }
1957
1958         if(ear.spawnflags & 256) // ENOTUBA
1959                 return msgin;
1960
1961         if(privatesay)
1962         {
1963                 if(ear.spawnflags & 4)
1964                         return msgin;
1965         }
1966         else
1967         {
1968                 if(!teamsay)
1969                         if(ear.spawnflags & 1)
1970                                 return msgin;
1971                 if(teamsay > 0)
1972                         if(ear.spawnflags & 2)
1973                                 return msgin;
1974                 if(teamsay < 0)
1975                         if(ear.spawnflags & 8)
1976                                 return msgin;
1977         }
1978         
1979         matchstart = -1;
1980         l = strlen(ear.message);
1981
1982         if(ear.spawnflags & 128)
1983                 msg = msgin;
1984         else
1985                 msg = strdecolorize(msgin);
1986
1987         if(substring(ear.message, 0, 1) == "*")
1988         {
1989                 if(substring(ear.message, -1, 1) == "*")
1990                 {
1991                         // two wildcards
1992                         // as we need multi-replacement here...
1993                         s = substring(ear.message, 1, -2);
1994                         l -= 2;
1995                         if(strstrofs(msg, s, 0) >= 0)
1996                                 matchstart = -2; // we use strreplace on s
1997                 }
1998                 else
1999                 {
2000                         // match at start
2001                         s = substring(ear.message, 1, -1);
2002                         l -= 1;
2003                         if(substring(msg, -l, l) == s)
2004                                 matchstart = strlen(msg) - l;
2005                 }
2006         }
2007         else
2008         {
2009                 if(substring(ear.message, -1, 1) == "*")
2010                 {
2011                         // match at end
2012                         s = substring(ear.message, 0, -2);
2013                         l -= 1;
2014                         if(substring(msg, 0, l) == s)
2015                                 matchstart = 0;
2016                 }
2017                 else
2018                 {
2019                         // full match
2020                         s = ear.message;
2021                         if(msg == ear.message)
2022                                 matchstart = 0;
2023                 }
2024         }
2025
2026         if(matchstart == -1) // no match
2027                 return msgin;
2028
2029         magicear_matched = TRUE;
2030
2031         if(dotrigger)
2032         {
2033                 oldself = self;
2034                 activator = source;
2035                 self = ear;
2036                 savemessage = self.message;
2037                 self.message = string_null;
2038                 SUB_UseTargets();
2039                 self.message = savemessage;
2040                 self = oldself;
2041         }
2042
2043         if(ear.spawnflags & 16)
2044         {
2045                 return ear.netname;
2046         }
2047         else if(ear.netname != "")
2048         {
2049                 if(matchstart < 0)
2050                         return strreplace(s, ear.netname, msg);
2051                 else
2052                         return strcat(
2053                                 substring(msg, 0, matchstart),
2054                                 ear.netname,
2055                                 substring(msg, matchstart + l, -1)
2056                         );
2057         }
2058         else
2059                 return msgin;
2060 }
2061
2062 entity magicears;
2063 string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
2064 {
2065         entity ear;
2066         string msgout;
2067         for(ear = magicears; ear; ear = ear.enemy)
2068         {
2069                 msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
2070                 if not(ear.spawnflags & 64)
2071                         if(magicear_matched)
2072                                 return msgout;
2073                 msgin = msgout;
2074         }
2075         return msgin;
2076 }
2077
2078 void spawnfunc_trigger_magicear()
2079 {
2080         self.enemy = magicears;
2081         magicears = self;
2082
2083         // actually handled in "say" processing
2084         // spawnflags:
2085         //    1 = ignore say
2086         //    2 = ignore teamsay
2087         //    4 = ignore tell
2088         //    8 = ignore tell to unknown player
2089         //   16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
2090         //   32 = perform the replacement even if outside the radius or dead
2091         //   64 = continue replacing/triggering even if this one matched
2092         //  128 = don't decolorize message before matching
2093         //  256 = message is a tuba note sequence (pitch.duration pitch.duration ...)
2094         //  512 = tuba notes must be exact right pitch, no transposing
2095         // message: either
2096         //   *pattern*
2097         // or
2098         //   *pattern
2099         // or
2100         //   pattern*
2101         // or
2102         //   pattern
2103         // netname:
2104         //   if set, replacement for the matched text
2105         // radius:
2106         //   "hearing distance"
2107         // target:
2108         //   what to trigger
2109         // movedir:
2110         //   for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter)
2111
2112         self.movedir_x -= 1; // map to tuba instrument numbers
2113 }
2114
2115 void relay_activators_use()
2116 {
2117         entity trg, os;
2118         
2119         os = self;
2120         
2121         for(trg = world; (trg = find(trg, targetname, os.target)); )
2122         {
2123                 self = trg;
2124                 if (trg.setactive)
2125                         trg.setactive(os.cnt);
2126                 else
2127                 {
2128                         //bprint("Not using setactive\n");
2129                         if(os.cnt == ACTIVE_TOGGLE)
2130                                 if(trg.active == ACTIVE_ACTIVE)
2131                                         trg.active = ACTIVE_NOT;
2132                                 else    
2133                                         trg.active = ACTIVE_ACTIVE;
2134                         else
2135                                 trg.active = os.cnt;
2136                 }               
2137         }
2138         self = os;
2139 }
2140
2141 void spawnfunc_relay_activate()
2142 {
2143         self.cnt = ACTIVE_ACTIVE;
2144         self.use = relay_activators_use;
2145 }
2146
2147 void spawnfunc_relay_deactivate()
2148 {
2149         self.cnt = ACTIVE_NOT;
2150         self.use = relay_activators_use;        
2151 }
2152
2153 void spawnfunc_relay_activatetoggle()
2154 {
2155         self.cnt = ACTIVE_TOGGLE;
2156         self.use = relay_activators_use;        
2157 }
2158
2159 .string chmap, gametype;
2160 void spawnfunc_target_changelevel_use()
2161 {
2162         if(self.gametype != "")
2163                 MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
2164
2165         if (self.chmap == "")
2166                 localcmd("endmatch\n");
2167         else
2168                 localcmd(strcat("changelevel ", self.chmap, "\n"));
2169 }
2170
2171 void spawnfunc_target_changelevel()
2172 {
2173         self.use = spawnfunc_target_changelevel_use;
2174 }