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