]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/g_triggers.qc
some entity field name improvements for bgmscript
[divverent/nexuiz.git] / data / 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
161         SUB_UseTargets();
162
163         if (self.wait > 0)
164         {
165                 self.think = multi_wait;
166                 self.nextthink = time + self.wait;
167         }
168         else
169         {       // we can't just remove (self) here, because this is a touch function
170                 // called wheil C code is looping through area links...
171                 self.touch = SUB_Null;
172         }
173 };
174
175 void multi_use()
176 {
177         self.enemy = activator;
178         multi_trigger();
179 };
180
181 void multi_touch()
182 {
183         if not(self.spawnflags & 2)
184         {
185                 if not(other.iscreature)
186                         return;
187
188                 if(self.team)
189                 if(self.team == other.team)
190                         return;
191         }
192
193 // if the trigger has an angles field, check player's facing direction
194         if (self.movedir != '0 0 0')
195         {
196                 makevectors (other.angles);
197                 if (v_forward * self.movedir < 0)
198                         return;         // not facing the right way
199         }
200
201         EXACTTRIGGER_TOUCH;
202
203         self.enemy = other;
204         multi_trigger ();
205 };
206
207 void multi_eventdamage (vector hitloc, float damage, entity inflictor, entity attacker, float deathtype)
208 {
209         if (!self.takedamage)
210                 return;
211         self.health = self.health - damage;
212         if (self.health <= 0)
213         {
214                 self.enemy = attacker;
215                 multi_trigger();
216         }
217 }
218
219 void multi_reset()
220 {
221         self.touch = multi_touch;
222         self.health = self.max_health;
223         self.takedamage = DAMAGE_YES;
224         self.solid = SOLID_BBOX;
225         self.think = SUB_Null;
226         self.team = self.team_saved;
227 }
228
229 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
230 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.
231 If "delay" is set, the trigger waits some time after activating before firing.
232 "wait" : Seconds between triggerings. (.2 default)
233 If notouch is set, the trigger is only fired by other entities, not by touching.
234 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
235 sounds
236 1)      secret
237 2)      beep beep
238 3)      large switch
239 4)
240 set "message" to text string
241 */
242 void spawnfunc_trigger_multiple()
243 {
244         self.reset = multi_reset;
245         if (self.sounds == 1)
246         {
247                 precache_sound ("misc/secret.wav");
248                 self.noise = "misc/secret.wav";
249         }
250         else if (self.sounds == 2)
251         {
252                 precache_sound ("misc/talk.wav");
253                 self.noise = "misc/talk.wav";
254         }
255         else if (self.sounds == 3)
256         {
257                 precache_sound ("misc/trigger1.wav");
258                 self.noise = "misc/trigger1.wav";
259         }
260
261         if (!self.wait)
262                 self.wait = 0.2;
263         self.use = multi_use;
264
265         EXACTTRIGGER_INIT;
266
267         self.team_saved = self.team;
268
269         if (self.health)
270         {
271                 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
272                         objerror ("health and notouch don't make sense\n");
273                 self.max_health = self.health;
274                 self.event_damage = multi_eventdamage;
275                 self.takedamage = DAMAGE_YES;
276                 self.solid = SOLID_BBOX;
277                 setorigin (self, self.origin);  // make sure it links into the world
278         }
279         else
280         {
281                 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
282                 {
283                         self.touch = multi_touch;
284                         setorigin (self, self.origin);  // make sure it links into the world
285                 }
286         }
287 };
288
289
290 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
291 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
292 "targetname".  If "health" is set, the trigger must be killed to activate.
293 If notouch is set, the trigger is only fired by other entities, not by touching.
294 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
295 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.
296 sounds
297 1)      secret
298 2)      beep beep
299 3)      large switch
300 4)
301 set "message" to text string
302 */
303 void spawnfunc_trigger_once()
304 {
305         self.wait = -1;
306         spawnfunc_trigger_multiple();
307 };
308
309 //=============================================================================
310
311 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
312 This fixed size trigger cannot be touched, it can only be fired by other events.  It can contain killtargets, targets, delays, and messages.
313 */
314 void spawnfunc_trigger_relay()
315 {
316         self.use = SUB_UseTargets;
317         self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
318 };
319
320 void delay_use()
321 {
322     self.think = SUB_UseTargets;
323     self.nextthink = self.wait;
324 }
325
326 void delay_reset()
327 {
328         self.think = SUB_Null;
329 }
330
331 void spawnfunc_trigger_delay()
332 {
333     if(!self.wait)
334         self.wait = 1;
335
336     self.use = delay_use;
337     self.reset = delay_reset;
338 }
339
340 //=============================================================================
341
342
343 void counter_use()
344 {
345         self.count = self.count - 1;
346         if (self.count < 0)
347                 return;
348
349         if (self.count != 0)
350         {
351                 if (activator.classname == "player"
352                 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
353                 {
354                         if (self.count >= 4)
355                                 centerprint (activator, "There are more to go...");
356                         else if (self.count == 3)
357                                 centerprint (activator, "Only 3 more to go...");
358                         else if (self.count == 2)
359                                 centerprint (activator, "Only 2 more to go...");
360                         else
361                                 centerprint (activator, "Only 1 more to go...");
362                 }
363                 return;
364         }
365
366         if (activator.classname == "player"
367         && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
368                 centerprint(activator, "Sequence completed!");
369         self.enemy = activator;
370         multi_trigger ();
371 };
372
373 void counter_reset()
374 {
375         self.count = self.cnt;
376         multi_reset();
377 }
378
379 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
380 Acts as an intermediary for an action that takes multiple inputs.
381
382 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
383
384 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
385 */
386 void spawnfunc_trigger_counter()
387 {
388         self.wait = -1;
389         if (!self.count)
390                 self.count = 2;
391         self.cnt = self.count;
392
393         self.use = counter_use;
394         self.reset = counter_reset;
395 };
396
397 .float triggerhurttime;
398 void trigger_hurt_touch()
399 {
400         // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
401         if (other.iscreature)
402         {
403                 if (other.takedamage)
404                 if (other.triggerhurttime < time)
405                 {
406                         EXACTTRIGGER_TOUCH;
407                         other.triggerhurttime = time + 1;
408                         Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
409                 }
410         }
411         else
412         {
413                 if (!other.owner)
414                 {
415                         if (other.items & IT_KEY1 || other.items & IT_KEY2)     // reset flag
416                         {
417                                 EXACTTRIGGER_TOUCH;
418                                 other.pain_finished = min(other.pain_finished, time + 2);
419                         }
420                         else if (other.classname == "rune")                     // reset runes
421                         {
422                                 EXACTTRIGGER_TOUCH;
423                                 other.nextthink = min(other.nextthink, time + 1);
424                         }
425                 }
426         }
427
428         return;
429 };
430
431 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
432 Any object touching this will be hurt
433 set dmg to damage amount
434 defalt dmg = 5
435 */
436 .entity trigger_hurt_next;
437 entity trigger_hurt_last;
438 entity trigger_hurt_first;
439 void spawnfunc_trigger_hurt()
440 {
441         EXACTTRIGGER_INIT;
442         self.touch = trigger_hurt_touch;
443         if (!self.dmg)
444                 self.dmg = 1000;
445         if (!self.message)
446                 self.message = "was in the wrong place";
447         if (!self.message2)
448                 self.message2 = "was thrown into a world of hurt by";
449
450         if(!trigger_hurt_first)
451                 trigger_hurt_first = self;
452         if(trigger_hurt_last)
453                 trigger_hurt_last.trigger_hurt_next = self;
454         trigger_hurt_last = self;
455 };
456
457 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
458 {
459         entity th;
460
461         for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
462                 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
463                         return TRUE;
464
465         return FALSE;
466 }
467
468 //////////////////////////////////////////////////////////////
469 //
470 //
471 //
472 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
473 //
474 //////////////////////////////////////////////////////////////
475
476 .float triggerhealtime;
477 void trigger_heal_touch()
478 {
479         // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
480         if (other.iscreature)
481         {
482                 if (other.takedamage)
483                 if (other.triggerhealtime < time)
484                 {
485                         EXACTTRIGGER_TOUCH;
486                         other.triggerhealtime = time + 1;
487                         
488                         if (other.health < self.max_health)
489                         {
490                                 other.health = min(other.health + self.health, self.max_health);
491                                 other.pauserothealth_finished = max(other.pauserothealth_finished, time + cvar("g_balance_pause_health_rot"));
492                                 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
493                         }
494                 }
495         }
496 };
497
498 void spawnfunc_trigger_heal()
499 {
500         EXACTTRIGGER_INIT;
501         self.touch = trigger_heal_touch;
502         if (!self.health)
503                 self.health = 10;
504         if (!self.max_health)
505                 self.max_health = 200; //Max health topoff for field
506         if(self.noise == "")
507                 self.noise = "misc/mediumhealth.wav";
508         precache_sound(self.noise);
509 };
510
511
512 //////////////////////////////////////////////////////////////
513 //
514 //
515 //
516 //End trigger_heal
517 //
518 //////////////////////////////////////////////////////////////
519
520
521
522 // TODO add a way to do looped sounds with sound(); then complete this entity
523 .float volume, atten;
524 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
525
526 void spawnfunc_target_speaker()
527 {
528         if(self.noise)
529                 precache_sound (self.noise);
530         IFTARGETED
531         {
532                 if(!self.atten)
533                         self.atten = ATTN_NORM;
534                 else if(self.atten < 0)
535                         self.atten = 0;
536                 if(!self.volume)
537                         self.volume = 1;
538                 self.use = target_speaker_use;
539         }
540         else
541         {
542                 if(!self.atten)
543                         self.atten = ATTN_STATIC;
544                 else if(self.atten < 0)
545                         self.atten = 0;
546                 if(!self.volume)
547                         self.volume = 1;
548                 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
549         }
550 };
551
552
553 void spawnfunc_func_stardust() {
554         self.effects = EF_STARDUST;
555 }
556
557 .string bgmscript;
558 .float bgmscriptdecay;
559 float pointparticles_SendEntity(entity to, float fl)
560 {
561         WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
562         WriteByte(MSG_ENTITY, fl);
563         if(fl & 2)
564         {
565                 if(self.state)
566                         WriteCoord(MSG_ENTITY, self.impulse);
567                 else
568                         WriteCoord(MSG_ENTITY, 0); // off
569         }
570         if(fl & 4)
571         {
572                 WriteCoord(MSG_ENTITY, self.origin_x);
573                 WriteCoord(MSG_ENTITY, self.origin_y);
574                 WriteCoord(MSG_ENTITY, self.origin_z);
575         }
576         if(fl & 1)
577         {
578                 if(self.model != "null")
579                 {
580                         WriteShort(MSG_ENTITY, self.modelindex);
581                         WriteCoord(MSG_ENTITY, self.mins_x);
582                         WriteCoord(MSG_ENTITY, self.mins_y);
583                         WriteCoord(MSG_ENTITY, self.mins_z);
584                         WriteCoord(MSG_ENTITY, self.maxs_x);
585                         WriteCoord(MSG_ENTITY, self.maxs_y);
586                         WriteCoord(MSG_ENTITY, self.maxs_z);
587                 }
588                 else
589                 {
590                         WriteShort(MSG_ENTITY, 0);
591                         WriteCoord(MSG_ENTITY, self.maxs_x);
592                         WriteCoord(MSG_ENTITY, self.maxs_y);
593                         WriteCoord(MSG_ENTITY, self.maxs_z);
594                 }
595                 WriteShort(MSG_ENTITY, self.cnt);
596                 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
597                 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
598                 WriteCoord(MSG_ENTITY, self.waterlevel);
599                 WriteCoord(MSG_ENTITY, self.count);
600                 WriteByte(MSG_ENTITY, self.glow_color);
601                 WriteString(MSG_ENTITY, self.noise);
602                 if(self.noise != "")
603                 {
604                         WriteByte(MSG_ENTITY, floor(self.atten * 64));
605                         WriteByte(MSG_ENTITY, floor(self.volume * 255));
606                 }
607                 WriteString(MSG_ENTITY, self.bgmscript);
608                 if(self.bgmscript != "")
609                         WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 255));
610         }
611         return 1;
612 }
613
614 void pointparticles_use()
615 {
616         self.state = !self.state;
617         self.SendFlags |= 2;
618 }
619
620 void pointparticles_think()
621 {
622         if(self.origin != self.oldorigin)
623         {
624                 self.SendFlags |= 4;
625                 self.oldorigin = self.origin;
626         }
627         self.nextthink = time;
628 }
629
630 void pointparticles_reset()
631 {
632         if(self.spawnflags & 1)
633                 self.state = 1;
634         else
635                 self.state = 0;
636 }
637
638 void spawnfunc_func_pointparticles()
639 {
640         if(self.model != "")
641                 setmodel(self, self.model);
642         if(self.noise != "")
643                 precache_sound (self.noise);
644
645         if(!self.atten)
646                 self.atten = ATTN_NORM;
647         else if(self.atten < 0)
648                 self.atten = 0;
649         if(!self.volume)
650                 self.volume = 1;
651
652         if(!self.modelindex)
653         {
654                 setorigin(self, self.origin + self.mins);
655                 setsize(self, '0 0 0', self.maxs - self.mins);
656         }
657         if(!self.cnt)
658                 self.cnt = particleeffectnum(self.mdl);
659
660         Net_LinkEntity(self, FALSE, 0, pointparticles_SendEntity);
661
662         IFTARGETED
663         {
664                 self.use = pointparticles_use;
665                 self.reset = pointparticles_reset;
666                 self.reset();
667         }
668         else
669                 self.state = 1;
670         self.think = pointparticles_think;
671         self.nextthink = time;
672 }
673
674 void spawnfunc_func_sparks()
675 {
676         // self.cnt is the amount of sparks that one burst will spawn
677         if(self.cnt < 1) {
678                 self.cnt = 25.0; // nice default value
679         }
680
681         // self.wait is the probability that a sparkthink will spawn a spark shower
682         // range: 0 - 1, but 0 makes little sense, so...
683         if(self.wait < 0.05) {
684                 self.wait = 0.25; // nice default value
685         }
686
687         self.count = self.cnt;
688         self.mins = '0 0 0';
689         self.maxs = '0 0 0';
690         self.velocity = '0 0 -1';
691         self.mdl = "TE_SPARK";
692         self.impulse = 10 * self.wait; // by default 2.5/sec
693         self.wait = 0;
694         self.cnt = 0; // use mdl
695
696         spawnfunc_func_pointparticles();
697 }
698
699 float rainsnow_SendEntity(entity to, float sf)
700 {
701         WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
702         WriteByte(MSG_ENTITY, self.state);
703         WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
704         WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
705         WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
706         WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
707         WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
708         WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
709         WriteShort(MSG_ENTITY, compressShortVector(self.dest));
710         WriteShort(MSG_ENTITY, self.count);
711         WriteByte(MSG_ENTITY, self.cnt);
712         return 1;
713 };
714
715 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
716 This is an invisible area like a trigger, which rain falls inside of.
717
718 Keys:
719 "velocity"
720  falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
721 "cnt"
722  sets color of rain (default 12 - white)
723 "count"
724  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
725 */
726 void spawnfunc_func_rain()
727 {
728         self.dest = self.velocity;
729         self.velocity = '0 0 0';
730         if (!self.dest)
731                 self.dest = '0 0 -700';
732         self.angles = '0 0 0';
733         self.movetype = MOVETYPE_NONE;
734         self.solid = SOLID_NOT;
735         SetBrushEntityModel();
736         if (!self.cnt)
737                 self.cnt = 12;
738         if (!self.count)
739                 self.count = 2000;
740         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
741         if (self.count < 1)
742                 self.count = 1;
743         if(self.count > 65535)
744                 self.count = 65535;
745
746         self.state = 1; // 1 is rain, 0 is snow
747         self.Version = 1;
748
749         Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
750 };
751
752
753 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
754 This is an invisible area like a trigger, which snow falls inside of.
755
756 Keys:
757 "velocity"
758  falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
759 "cnt"
760  sets color of rain (default 12 - white)
761 "count"
762  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
763 */
764 void spawnfunc_func_snow()
765 {
766         self.dest = self.velocity;
767         self.velocity = '0 0 0';
768         if (!self.dest)
769                 self.dest = '0 0 -300';
770         self.angles = '0 0 0';
771         self.movetype = MOVETYPE_NONE;
772         self.solid = SOLID_NOT;
773         SetBrushEntityModel();
774         if (!self.cnt)
775                 self.cnt = 12;
776         if (!self.count)
777                 self.count = 2000;
778         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
779         if (self.count < 1)
780                 self.count = 1;
781         if(self.count > 65535)
782                 self.count = 65535;
783
784         self.state = 0; // 1 is rain, 0 is snow
785         self.Version = 1;
786
787         Net_LinkEntity(self, FALSE, 0, rainsnow_SendEntity);
788 };
789
790
791 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
792
793 .float modelscale;
794 void misc_laser_aim()
795 {
796         vector a;
797         if(self.enemy)
798         {
799                 if(self.spawnflags & 2)
800                 {
801                         if(self.enemy.origin != self.mangle)
802                         {
803                                 self.mangle = self.enemy.origin;
804                                 self.SendFlags |= 2;
805                         }
806                 }
807                 else
808                 {
809                         a = vectoangles(self.enemy.origin - self.origin);
810                         a_x = -a_x;
811                         if(a != self.mangle)
812                         {
813                                 self.mangle = a;
814                                 self.SendFlags |= 2;
815                         }
816                 }
817         }
818         else
819         {
820                 if(self.angles != self.mangle)
821                 {
822                         self.mangle = self.angles;
823                         self.SendFlags |= 2;
824                 }
825         }
826         if(self.origin != self.oldorigin)
827         {
828                 self.SendFlags |= 1;
829                 self.oldorigin = self.origin;
830         }
831 }
832
833 void misc_laser_init()
834 {
835         if(self.target != "")
836                 self.enemy = find(world, targetname, self.target);
837 }
838
839 .entity pusher;
840 void misc_laser_think()
841 {
842         vector o;
843         entity oldself;
844
845         self.nextthink = time;
846
847         if(!self.state)
848                 return;
849
850         misc_laser_aim();
851
852         if(self.enemy)
853         {
854                 o = self.enemy.origin;
855                 if not(self.spawnflags & 2)
856                         o = self.origin + normalize(o - self.origin) * 32768;
857         }
858         else
859         {
860                 makevectors(self.mangle);
861                 o = self.origin + v_forward * 32768;
862         }
863
864         if(self.dmg)
865         {
866                 if(self.dmg < 0)
867                         FireRailgunBullet(self.origin, o, 100000, 0, DEATH_HURTTRIGGER);
868                 else
869                         FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, DEATH_HURTTRIGGER);
870         }
871
872         if(self.enemy.target != "") // DETECTOR laser
873         {
874                 traceline(self.origin, o, MOVE_NORMAL, self);
875                 if(trace_ent.iscreature)
876                 {
877                         self.pusher = trace_ent;
878                         if(!self.count)
879                         {
880                                 self.count = 1;
881
882                                 oldself = self;
883                                 self = self.enemy;
884                                 activator = self.pusher;
885                                 SUB_UseTargets();
886                                 self = oldself;
887                         }
888                 }
889                 else
890                 {
891                         if(self.count)
892                         {
893                                 self.count = 0;
894
895                                 oldself = self;
896                                 self = self.enemy;
897                                 activator = self.pusher;
898                                 SUB_UseTargets();
899                                 self = oldself;
900                         }
901                 }
902         }
903 }
904
905 float laser_SendEntity(entity to, float fl)
906 {
907         WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
908         fl = fl - (fl & 0xE0); // use that bit to indicate finite length laser
909         if(self.spawnflags & 2)
910                 fl |= 0x80;
911         if(self.alpha)
912                 fl |= 0x40;
913         if(self.scale != 1 || self.modelscale != 1)
914                 fl |= 0x20;
915         WriteByte(MSG_ENTITY, fl);
916         if(fl & 1)
917         {
918                 WriteCoord(MSG_ENTITY, self.origin_x);
919                 WriteCoord(MSG_ENTITY, self.origin_y);
920                 WriteCoord(MSG_ENTITY, self.origin_z);
921         }
922         if(fl & 8)
923         {
924                 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
925                 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
926                 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
927                 if(fl & 0x40)
928                         WriteByte(MSG_ENTITY, self.alpha * 255.0);
929                 if(fl & 0x20)
930                 {
931                         WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
932                         WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
933                 }
934                 WriteShort(MSG_ENTITY, self.cnt + 1);
935         }
936         if(fl & 2)
937         {
938                 if(fl & 0x80)
939                 {
940                         WriteCoord(MSG_ENTITY, self.enemy.origin_x);
941                         WriteCoord(MSG_ENTITY, self.enemy.origin_y);
942                         WriteCoord(MSG_ENTITY, self.enemy.origin_z);
943                 }
944                 else
945                 {
946                         WriteAngle(MSG_ENTITY, self.mangle_x);
947                         WriteAngle(MSG_ENTITY, self.mangle_y);
948                 }
949         }
950         if(fl & 4)
951                 WriteByte(MSG_ENTITY, self.state);
952         return 1;
953 }
954
955 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
956 Any object touching the beam will be hurt
957 Keys:
958 "target"
959  spawnfunc_target_position where the laser ends
960 "mdl"
961  name of beam end effect to use
962 "colormod"
963  color of the beam (default: red)
964 "dmg"
965  damage per second (-1 for a laser that kills immediately)
966 */
967 void laser_use()
968 {
969         self.state = !self.state;
970         self.SendFlags |= 4;
971         misc_laser_aim();
972 }
973
974 void laser_reset()
975 {
976         if(self.spawnflags & 1)
977                 self.state = 1;
978         else
979                 self.state = 0;
980 }
981
982 void spawnfunc_misc_laser()
983 {
984         if(self.mdl)
985         {
986                 if(self.mdl == "none")
987                         self.cnt = -1;
988                 else
989                 {
990                         self.cnt = particleeffectnum(self.mdl);
991                         if(self.cnt < 0)
992                                 if(self.dmg)
993                                         self.cnt = particleeffectnum("laser_deadly");
994                 }
995         }
996         else if(!self.cnt)
997         {
998                 if(self.dmg)
999                         self.cnt = particleeffectnum("laser_deadly");
1000                 else
1001                         self.cnt = -1;
1002         }
1003         if(self.cnt < 0)
1004                 self.cnt = -1;
1005
1006         if(self.colormod == '0 0 0')
1007                 if(!self.alpha)
1008                         self.colormod = '1 0 0';
1009         if(!self.message)
1010                 self.message = "saw the light";
1011         if (!self.message2)
1012                 self.message2 = "was pushed into a laser by";
1013         if(!self.scale)
1014                 self.scale = 1;
1015         if(!self.modelscale)
1016                 self.modelscale = 1;
1017         self.think = misc_laser_think;
1018         self.nextthink = time;
1019         InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
1020
1021         self.mangle = self.angles;
1022
1023         Net_LinkEntity(self, FALSE, 0, laser_SendEntity);
1024
1025         IFTARGETED
1026         {
1027                 self.reset = laser_reset;
1028                 laser_reset();
1029                 self.use = laser_use;
1030         }
1031         else
1032                 self.state = 1;
1033 }
1034
1035 // tZorks trigger impulse / gravity
1036 .float radius;
1037 .float falloff;
1038 .float strength;
1039 .float lastpushtime;
1040
1041 // targeted (directional) mode
1042 void trigger_impulse_touch1()
1043 {
1044         entity targ;
1045     float pushdeltatime;
1046     float str;
1047
1048         // FIXME: Better checking for what to push and not.
1049         if not(other.iscreature)
1050         if (other.classname != "corpse")
1051         if (other.classname != "body")
1052         if (other.classname != "gib")
1053         if (other.classname != "missile")
1054         if (other.classname != "rocket")
1055         if (other.classname != "casing")
1056         if (other.classname != "grenade")
1057         if (other.classname != "plasma")
1058         if (other.classname != "plasma_prim")
1059         if (other.classname != "plasma_chain")
1060         if (other.classname != "droppedweapon")
1061                 return;
1062
1063         if (other.deadflag && other.iscreature)
1064                 return;
1065
1066         EXACTTRIGGER_TOUCH;
1067
1068     targ = find(world, targetname, self.target);
1069     if(!targ)
1070     {
1071         objerror("trigger_force without a (valid) .target!\n");
1072         remove(self);
1073         return;
1074     }
1075
1076     if(self.falloff == 1)
1077         str = (str / self.radius) * self.strength;
1078     else if(self.falloff == 2)
1079         str = (1 - (str / self.radius)) * self.strength;
1080     else
1081         str = self.strength;
1082
1083     pushdeltatime = time - other.lastpushtime;
1084     if (pushdeltatime > 0.15) pushdeltatime = 0;
1085     other.lastpushtime = time;
1086     if(!pushdeltatime) return;
1087
1088     other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
1089         other.flags &~= FL_ONGROUND;
1090 }
1091
1092 // Directionless (accelerator/decelerator) mode
1093 void trigger_impulse_touch2()
1094 {
1095     float pushdeltatime;
1096
1097         // FIXME: Better checking for what to push and not.
1098         if not(other.iscreature)
1099         if (other.classname != "corpse")
1100         if (other.classname != "body")
1101         if (other.classname != "gib")
1102         if (other.classname != "missile")
1103         if (other.classname != "rocket")
1104         if (other.classname != "casing")
1105         if (other.classname != "grenade")
1106         if (other.classname != "plasma")
1107         if (other.classname != "plasma_prim")
1108         if (other.classname != "plasma_chain")
1109         if (other.classname != "droppedweapon")
1110                 return;
1111
1112         if (other.deadflag && other.iscreature)
1113                 return;
1114
1115         EXACTTRIGGER_TOUCH;
1116
1117     pushdeltatime = time - other.lastpushtime;
1118     if (pushdeltatime > 0.15) pushdeltatime = 0;
1119     other.lastpushtime = time;
1120     if(!pushdeltatime) return;
1121
1122     //if(self.strength > 1)
1123         other.velocity = other.velocity * (self.strength * pushdeltatime);
1124     //else
1125     //    other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
1126 }
1127
1128 // Spherical (gravity/repulsor) mode
1129 void trigger_impulse_touch3()
1130 {
1131     float pushdeltatime;
1132     float str;
1133
1134         // FIXME: Better checking for what to push and not.
1135         if not(other.iscreature)
1136         if (other.classname != "corpse")
1137         if (other.classname != "body")
1138         if (other.classname != "gib")
1139         if (other.classname != "missile")
1140         if (other.classname != "rocket")
1141         if (other.classname != "casing")
1142         if (other.classname != "grenade")
1143         if (other.classname != "plasma")
1144         if (other.classname != "plasma_prim")
1145         if (other.classname != "plasma_chain")
1146         if (other.classname != "droppedweapon")
1147                 return;
1148
1149         if (other.deadflag && other.iscreature)
1150                 return;
1151
1152         EXACTTRIGGER_TOUCH;
1153
1154     pushdeltatime = time - other.lastpushtime;
1155     if (pushdeltatime > 0.15) pushdeltatime = 0;
1156     other.lastpushtime = time;
1157     if(!pushdeltatime) return;
1158
1159     setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1160
1161         str = min(self.radius, vlen(self.origin - other.origin));
1162
1163     if(self.falloff == 1)
1164         str = (1 - str / self.radius) * self.strength; // 1 in the inside
1165     else if(self.falloff == 2)
1166         str = (str / self.radius) * self.strength; // 0 in the inside
1167     else
1168         str = self.strength;
1169
1170     other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1171 }
1172
1173 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1174 -------- KEYS --------
1175 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1176          If not, this trigger acts like a damper/accelerator field.
1177
1178 strength : This is how mutch force to add in the direction of .target each second
1179            when .target is set. If not, this is hoe mutch to slow down/accelerate
1180            someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1181
1182 radius   : If set, act as a spherical device rather then a liniar one.
1183
1184 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1185
1186 -------- NOTES --------
1187 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1188 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1189 */
1190
1191 void spawnfunc_trigger_impulse()
1192 {
1193         EXACTTRIGGER_INIT;
1194     if(self.radius)
1195     {
1196         if(!self.strength) self.strength = 2000;
1197         setorigin(self, self.origin);
1198         setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1199         self.touch = trigger_impulse_touch3;
1200     }
1201     else
1202     {
1203         if(self.target)
1204         {
1205             if(!self.strength) self.strength = 950;
1206             self.touch = trigger_impulse_touch1;
1207         }
1208         else
1209         {
1210             if(!self.strength) self.strength = 0.9;
1211             self.touch = trigger_impulse_touch2;
1212         }
1213     }
1214 }
1215
1216 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1217 "Flip-flop" trigger gate... lets only every second trigger event through
1218 */
1219 void flipflop_use()
1220 {
1221         self.state = !self.state;
1222         if(self.state)
1223                 SUB_UseTargets();
1224 }
1225
1226 void spawnfunc_trigger_flipflop()
1227 {
1228         if(self.spawnflags & 1)
1229                 self.state = 1;
1230         self.use = flipflop_use;
1231         self.reset = spawnfunc_trigger_flipflop; // perfect resetter
1232 }
1233
1234 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1235 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1236 */
1237 void monoflop_use()
1238 {
1239         self.nextthink = time + self.wait;
1240         self.enemy = activator;
1241         if(self.state)
1242                 return;
1243         self.state = 1;
1244         SUB_UseTargets();
1245 }
1246 void monoflop_fixed_use()
1247 {
1248         if(self.state)
1249                 return;
1250         self.nextthink = time + self.wait;
1251         self.state = 1;
1252         self.enemy = activator;
1253         SUB_UseTargets();
1254 }
1255
1256 void monoflop_think()
1257 {
1258         self.state = 0;
1259         activator = self.enemy;
1260         SUB_UseTargets();
1261 }
1262
1263 void monoflop_reset()
1264 {
1265         self.state = 0;
1266         self.nextthink = 0;
1267 }
1268
1269 void spawnfunc_trigger_monoflop()
1270 {
1271         if(!self.wait)
1272                 self.wait = 1;
1273         if(self.spawnflags & 1)
1274                 self.use = monoflop_fixed_use;
1275         else
1276                 self.use = monoflop_use;
1277         self.think = monoflop_think;
1278         self.state = 0;
1279         self.reset = monoflop_reset;
1280 }
1281
1282 void multivibrator_send()
1283 {
1284         float newstate;
1285         float cyclestart;
1286
1287         cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1288
1289         newstate = (time < cyclestart + self.wait);
1290
1291         activator = self;
1292         if(self.state != newstate)
1293                 SUB_UseTargets();
1294         self.state = newstate;
1295
1296         if(self.state)
1297                 self.nextthink = cyclestart + self.wait + 0.01;
1298         else
1299                 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1300 }
1301
1302 void multivibrator_toggle()
1303 {
1304         if(self.nextthink == 0)
1305         {
1306                 multivibrator_send();
1307         }
1308         else
1309         {
1310                 if(self.state)
1311                 {
1312                         SUB_UseTargets();
1313                         self.state = 0;
1314                 }
1315                 self.nextthink = 0;
1316         }
1317 }
1318
1319 void multivibrator_reset()
1320 {
1321         if(!(self.spawnflags & 1))
1322                 self.nextthink = 0; // wait for a trigger event
1323         else
1324                 self.nextthink = max(1, time);
1325 }
1326
1327 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1328 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1329 -------- KEYS --------
1330 target: trigger all entities with this targetname when it goes off
1331 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1332 phase: offset of the timing
1333 wait: "on" cycle time (default: 1)
1334 respawntime: "off" cycle time (default: same as wait)
1335 -------- SPAWNFLAGS --------
1336 START_ON: assume it is already turned on (when targeted)
1337 */
1338 void spawnfunc_trigger_multivibrator()
1339 {
1340         if(!self.wait)
1341                 self.wait = 1;
1342         if(!self.respawntime)
1343                 self.respawntime = self.wait;
1344
1345         self.state = 0;
1346         self.use = multivibrator_toggle;
1347         self.think = multivibrator_send;
1348         self.nextthink = time;
1349
1350         IFTARGETED
1351                 multivibrator_reset();
1352 }
1353
1354
1355 void follow_init()
1356 {
1357         entity src, dst;
1358         src = find(world, targetname, self.killtarget);
1359         dst = find(world, targetname, self.target);
1360
1361         if(!src || !dst)
1362         {
1363                 objerror("follow: could not find target/killtarget");
1364                 return;
1365         }
1366
1367         if(self.spawnflags & 1)
1368         {
1369                 // attach
1370                 if(self.spawnflags & 2)
1371                 {
1372                         setattachment(dst, src, self.message);
1373                 }
1374                 else
1375                 {
1376                         attach_sameorigin(dst, src, self.message);
1377                 }
1378         }
1379         else
1380         {
1381                 if(self.spawnflags & 2)
1382                 {
1383                         dst.movetype = MOVETYPE_FOLLOW;
1384                         dst.aiment = src;
1385                         // dst.punchangle = '0 0 0'; // keep unchanged
1386                         dst.view_ofs = dst.origin;
1387                         dst.v_angle = dst.angles;
1388                 }
1389                 else
1390                 {
1391                         follow_sameorigin(dst, src);
1392                 }
1393         }
1394
1395         remove(self);
1396 }
1397
1398 void spawnfunc_misc_follow()
1399 {
1400         InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1401 }
1402
1403
1404
1405 void gamestart_use() {
1406         activator = self;
1407         SUB_UseTargets();
1408         remove(self);
1409 }
1410
1411 void spawnfunc_trigger_gamestart() {
1412         self.use = gamestart_use;
1413         self.reset2 = spawnfunc_trigger_gamestart;
1414
1415         if(self.wait)
1416         {
1417                 self.think = self.use;
1418                 self.nextthink = game_starttime + self.wait;
1419         }
1420         else
1421                 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1422 }
1423
1424
1425
1426
1427 .entity voicescript; // attached voice script
1428 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1429 .float voicescript_nextthink; // time to play next voice
1430 .float voicescript_voiceend; // time when this voice ends
1431
1432 void target_voicescript_clear(entity pl)
1433 {
1434         pl.voicescript = world;
1435 }
1436
1437 void target_voicescript_use()
1438 {
1439         if(activator.voicescript != self)
1440         {
1441                 activator.voicescript = self;
1442                 activator.voicescript_index = 0;
1443                 activator.voicescript_nextthink = time + self.delay;
1444         }
1445 }
1446
1447 void target_voicescript_next(entity pl)
1448 {
1449         entity vs;
1450         float i, n;
1451
1452         vs = pl.voicescript;
1453         if(!vs)
1454                 return;
1455         if(vs.message == "")
1456                 return;
1457         if(pl.classname != "player")
1458                 return;
1459         if(gameover)
1460                 return;
1461
1462         if(time >= pl.voicescript_voiceend)
1463         {
1464                 if(time >= pl.voicescript_nextthink)
1465                 {
1466                         // get the next voice...
1467                         n = tokenize_sane(vs.message);
1468
1469                         if(pl.voicescript_index < vs.cnt)
1470                                 i = pl.voicescript_index * 2;
1471                         else if(n > vs.cnt * 2)
1472                                 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1473                         else
1474                                 i = -1;
1475
1476                         if(i >= 0)
1477                         {
1478                                 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1479                                 pl.voicescript_voiceend = time + stof(argv(i + 1));
1480                         }
1481                         else
1482                                 pl.voicescript = world;
1483
1484                         pl.voicescript_index += 1;
1485                         pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1486                 }
1487         }
1488 }
1489
1490 void spawnfunc_target_voicescript()
1491 {
1492         // netname: directory of the sound files
1493         // message: list of "sound file" duration "sound file" duration, a *, and again a list
1494         //          foo1 4.1 foo2 4.0 foo3 3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1495         // wait: average time between messages
1496         // delay: initial delay before the first message
1497         
1498         float i, n;
1499         self.use = target_voicescript_use;
1500
1501         n = tokenize_sane(self.message);
1502         self.cnt = n / 2;
1503         for(i = 0; i+1 < n; i += 2)
1504         {
1505                 if(argv(i) == "*")
1506                 {
1507                         self.cnt = i / 2;
1508                         ++i;
1509                 }
1510                 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1511         }
1512 }
1513
1514
1515
1516 void trigger_relay_teamcheck_use()
1517 {
1518         if(activator.team)
1519         {
1520                 if(self.spawnflags & 2)
1521                 {
1522                         if(activator.team != self.team)
1523                                 SUB_UseTargets();
1524                 }
1525                 else
1526                 {
1527                         if(activator.team == self.team)
1528                                 SUB_UseTargets();
1529                 }
1530         }
1531         else
1532         {
1533                 if(self.spawnflags & 1)
1534                         SUB_UseTargets();
1535         }
1536 }
1537
1538 void trigger_relay_teamcheck_reset()
1539 {
1540         self.team = self.team_saved;
1541 }
1542
1543 void spawnfunc_trigger_relay_teamcheck()
1544 {
1545         self.team_saved = self.team;
1546         self.use = trigger_relay_teamcheck_use;
1547         self.reset = trigger_relay_teamcheck_reset;
1548 }
1549
1550
1551
1552 void trigger_disablerelay_use()
1553 {
1554         entity e;
1555
1556         float a, b;
1557         a = b = 0;
1558
1559         for(e = world; (e = find(e, targetname, self.target)); )
1560         {
1561                 if(e.use == SUB_UseTargets)
1562                 {
1563                         e.use = SUB_DontUseTargets;
1564                         ++a;
1565                 }
1566                 else if(e.use == SUB_DontUseTargets)
1567                 {
1568                         e.use = SUB_UseTargets;
1569                         ++b;
1570                 }
1571         }
1572
1573         if(!a == !b)
1574                 print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
1575 }
1576
1577 void spawnfunc_trigger_disablerelay()
1578 {
1579         self.use = trigger_disablerelay_use;
1580 }