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