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