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