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