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