]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/g_triggers.qc
friendly fire/team damage/mirror damage change; warnings cleanup
[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                 self.modelindex = 4.2;
604                 self.origin += self.mins;
605                 self.maxs = self.maxs - self.mins;
606         }
607         self.model = "net_entity";
608         if(!self.cnt)
609                 self.cnt = particleeffectnum(self.mdl);
610         IFTARGETED
611         {
612                 self.use = pointparticles_use;
613                 if(self.spawnflags & 1)
614                         self.state = 1;
615                 else
616                         self.state = 0;
617         }
618         else
619                 self.state = 1;
620         self.think = pointparticles_think;
621         self.nextthink = time;
622 }
623
624 void spawnfunc_func_sparks()
625 {
626         // self.cnt is the amount of sparks that one burst will spawn
627         if(self.cnt < 1) {
628                 self.cnt = 25.0; // nice default value
629         }
630
631         // self.wait is the probability that a sparkthink will spawn a spark shower
632         // range: 0 - 1, but 0 makes little sense, so...
633         if(self.wait < 0.05) {
634                 self.wait = 0.25; // nice default value
635         }
636
637         self.count = self.cnt;
638         self.mins = '0 0 0';
639         self.maxs = '0 0 0';
640         self.velocity = '0 0 -1';
641         self.mdl = "TE_SPARK";
642         self.impulse = 10 * self.wait; // by default 2.5/sec
643         self.wait = 0;
644         self.cnt = 0; // use mdl
645
646         spawnfunc_func_pointparticles();
647 }
648
649 float rainsnow_SendEntity(float to)
650 {
651         WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
652         WriteByte(MSG_ENTITY, self.state);
653         WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
654         WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
655         WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
656         WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
657         WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
658         WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
659         WriteShort(MSG_ENTITY, compressShortVector(self.dest));
660         WriteShort(MSG_ENTITY, self.count);
661         WriteByte(MSG_ENTITY, self.cnt);
662         return 1;
663 };
664
665 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
666 This is an invisible area like a trigger, which rain falls inside of.
667
668 Keys:
669 "velocity"
670  falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
671 "cnt"
672  sets color of rain (default 12 - white)
673 "count"
674  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
675 */
676 void spawnfunc_func_rain()
677 {
678         self.dest = self.velocity;
679         self.velocity = '0 0 0';
680         if (!self.dest)
681                 self.dest = '0 0 -700';
682         self.angles = '0 0 0';
683         self.movetype = MOVETYPE_NONE;
684         self.solid = SOLID_NOT;
685         SetBrushEntityModel();
686         self.model = "";
687         if (!self.cnt)
688                 self.cnt = 12;
689         if (!self.count)
690                 self.count = 2000;
691         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
692         if (self.count < 1)
693                 self.count = 1;
694         if(self.count > 65535)
695                 self.count = 65535;
696
697         self.state = 1; // 1 is rain, 0 is snow
698         self.effects = EF_NODEPTHTEST;
699         self.SendEntity = rainsnow_SendEntity;
700         self.Version = 1;
701         self.modelindex = 1;
702         self.model = "net_entity";
703 };
704
705
706 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
707 This is an invisible area like a trigger, which snow falls inside of.
708
709 Keys:
710 "velocity"
711  falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
712 "cnt"
713  sets color of rain (default 12 - white)
714 "count"
715  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
716 */
717 void spawnfunc_func_snow()
718 {
719         self.dest = self.velocity;
720         self.velocity = '0 0 0';
721         if (!self.dest)
722                 self.dest = '0 0 -300';
723         self.angles = '0 0 0';
724         self.movetype = MOVETYPE_NONE;
725         self.solid = SOLID_NOT;
726         SetBrushEntityModel();
727         self.model = "";
728         if (!self.cnt)
729                 self.cnt = 12;
730         if (!self.count)
731                 self.count = 2000;
732         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
733         if (self.count < 1)
734                 self.count = 1;
735         if(self.count > 65535)
736                 self.count = 65535;
737
738         self.state = 0; // 1 is rain, 0 is snow
739         self.effects = EF_NODEPTHTEST;
740         self.SendEntity = rainsnow_SendEntity;
741         self.Version = 1;
742         self.modelindex = 1;
743         self.model = "net_entity";
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         self.modelindex = 1;
961         self.model = "net_entity";
962         self.mangle = self.angles;
963
964         IFTARGETED
965         {
966                 self.use = laser_use;
967                 if(self.spawnflags & 1)
968                         self.state = 1;
969                 else
970                         self.state = 0;
971         }
972         else
973                 self.state = 1;
974 }
975
976 // tZorks trigger impulse / gravity
977 .float radius;
978 .float falloff;
979 .float strength;
980 .float lastpushtime;
981
982 // targeted (directional) mode
983 void trigger_impulse_touch1()
984 {
985         entity targ;
986     float pushdeltatime;
987     float str;
988
989         // FIXME: Better checking for what to push and not.
990         if not(other.iscreature)
991         if (other.classname != "corpse")
992         if (other.classname != "body")
993         if (other.classname != "gib")
994         if (other.classname != "missile")
995         if (other.classname != "rocket")
996         if (other.classname != "casing")
997         if (other.classname != "grenade")
998         if (other.classname != "plasma")
999         if (other.classname != "plasma_prim")
1000         if (other.classname != "plasma_chain")
1001         if (other.classname != "droppedweapon")
1002                 return;
1003
1004         if (other.deadflag && other.iscreature)
1005                 return;
1006
1007         EXACTTRIGGER_TOUCH;
1008
1009     targ = find(world, targetname, self.target);
1010     if(!targ)
1011     {
1012         objerror("trigger_force without a (valid) .target!\n");
1013         remove(self);
1014         return;
1015     }
1016
1017     if(self.falloff == 1)
1018         str = (str / self.radius) * self.strength;
1019     else if(self.falloff == 2)
1020         str = (1 - (str / self.radius)) * self.strength;
1021     else
1022         str = self.strength;
1023
1024     pushdeltatime = time - other.lastpushtime;
1025     if (pushdeltatime > 0.15) pushdeltatime = 0;
1026     other.lastpushtime = time;
1027     if(!pushdeltatime) return;
1028
1029     other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
1030 }
1031
1032 // Directionless (accelerator/decelerator) mode
1033 void trigger_impulse_touch2()
1034 {
1035     float pushdeltatime;
1036
1037         // FIXME: Better checking for what to push and not.
1038         if not(other.iscreature)
1039         if (other.classname != "corpse")
1040         if (other.classname != "body")
1041         if (other.classname != "gib")
1042         if (other.classname != "missile")
1043         if (other.classname != "rocket")
1044         if (other.classname != "casing")
1045         if (other.classname != "grenade")
1046         if (other.classname != "plasma")
1047         if (other.classname != "plasma_prim")
1048         if (other.classname != "plasma_chain")
1049         if (other.classname != "droppedweapon")
1050                 return;
1051
1052         if (other.deadflag && other.iscreature)
1053                 return;
1054
1055         EXACTTRIGGER_TOUCH;
1056
1057     pushdeltatime = time - other.lastpushtime;
1058     if (pushdeltatime > 0.15) pushdeltatime = 0;
1059     other.lastpushtime = time;
1060     if(!pushdeltatime) return;
1061
1062     //if(self.strength > 1)
1063         other.velocity = other.velocity * (self.strength * pushdeltatime);
1064     //else
1065     //    other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
1066 }
1067
1068 // Spherical (gravity/repulsor) mode
1069 void trigger_impulse_touch3()
1070 {
1071     float pushdeltatime;
1072     float str;
1073
1074         // FIXME: Better checking for what to push and not.
1075         if not(other.iscreature)
1076         if (other.classname != "corpse")
1077         if (other.classname != "body")
1078         if (other.classname != "gib")
1079         if (other.classname != "missile")
1080         if (other.classname != "rocket")
1081         if (other.classname != "casing")
1082         if (other.classname != "grenade")
1083         if (other.classname != "plasma")
1084         if (other.classname != "plasma_prim")
1085         if (other.classname != "plasma_chain")
1086         if (other.classname != "droppedweapon")
1087                 return;
1088
1089         if (other.deadflag && other.iscreature)
1090                 return;
1091
1092         EXACTTRIGGER_TOUCH;
1093
1094     pushdeltatime = time - other.lastpushtime;
1095     if (pushdeltatime > 0.15) pushdeltatime = 0;
1096     other.lastpushtime = time;
1097     if(!pushdeltatime) return;
1098
1099     setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1100
1101         str = min(self.radius, vlen(self.origin - other.origin));
1102
1103     if(self.falloff == 1)
1104         str = (1 - str / self.radius) * self.strength; // 1 in the inside
1105     else if(self.falloff == 2)
1106         str = (str / self.radius) * self.strength; // 0 in the inside
1107     else
1108         str = self.strength;
1109
1110     other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1111 }
1112
1113 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1114 -------- KEYS --------
1115 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1116          If not, this trigger acts like a damper/accelerator field.
1117
1118 strength : This is how mutch force to add in the direction of .target each second
1119            when .target is set. If not, this is hoe mutch to slow down/accelerate
1120            someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1121
1122 radius   : If set, act as a spherical device rather then a liniar one.
1123
1124 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1125
1126 -------- NOTES --------
1127 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1128 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1129 */
1130
1131 void spawnfunc_trigger_impulse()
1132 {
1133         EXACTTRIGGER_INIT;
1134     if(self.radius)
1135     {
1136         if(!self.strength) self.strength = 2000;
1137         setorigin(self, self.origin);
1138         setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1139         self.touch = trigger_impulse_touch3;
1140     }
1141     else
1142     {
1143         if(self.target)
1144         {
1145             if(!self.strength) self.strength = 950;
1146             self.touch = trigger_impulse_touch1;
1147         }
1148         else
1149         {
1150             if(!self.strength) self.strength = 0.9;
1151             self.touch = trigger_impulse_touch2;
1152         }
1153     }
1154 }
1155
1156 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1157 "Flip-flop" trigger gate... lets only every second trigger event through
1158 */
1159 void flipflop_use()
1160 {
1161         self.state = !self.state;
1162         if(self.state)
1163                 SUB_UseTargets();
1164 }
1165
1166 void spawnfunc_trigger_flipflop()
1167 {
1168         if(self.spawnflags & 1)
1169                 self.state = 1;
1170     self.use = flipflop_use;
1171 }
1172
1173 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1174 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1175 */
1176 void monoflop_use()
1177 {
1178         self.nextthink = time + self.wait;
1179         self.enemy = activator;
1180         if(self.state)
1181                 return;
1182         self.state = 1;
1183         SUB_UseTargets();
1184 }
1185 void monoflop_fixed_use()
1186 {
1187         if(self.state)
1188                 return;
1189         self.nextthink = time + self.wait;
1190         self.state = 1;
1191         self.enemy = activator;
1192         SUB_UseTargets();
1193 }
1194
1195 void monoflop_think()
1196 {
1197         self.state = 0;
1198         activator = self.enemy;
1199         SUB_UseTargets();
1200 }
1201
1202 void spawnfunc_trigger_monoflop()
1203 {
1204         if(!self.wait)
1205                 self.wait = 1;
1206         if(self.spawnflags & 1)
1207                 self.use = monoflop_fixed_use;
1208         else
1209                 self.use = monoflop_use;
1210         self.think = monoflop_think;
1211         self.state = 0;
1212 }
1213
1214 void multivibrator_send()
1215 {
1216         float newstate;
1217         float cyclestart;
1218
1219         cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1220
1221         newstate = (time < cyclestart + self.wait);
1222
1223         activator = self;
1224         if(self.state != newstate)
1225                 SUB_UseTargets();
1226         self.state = newstate;
1227
1228         if(self.state)
1229                 self.nextthink = cyclestart + self.wait + 0.01;
1230         else
1231                 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1232 }
1233
1234 void multivibrator_toggle()
1235 {
1236         if(self.nextthink == 0)
1237         {
1238                 multivibrator_send();
1239         }
1240         else
1241         {
1242                 if(self.state)
1243                 {
1244                         SUB_UseTargets();
1245                         self.state = 0;
1246                 }
1247                 self.nextthink = 0;
1248         }
1249 }
1250
1251 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1252 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1253 -------- KEYS --------
1254 target: trigger all entities with this targetname when it goes off
1255 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1256 phase: offset of the timing
1257 wait: "on" cycle time (default: 1)
1258 respawntime: "off" cycle time (default: same as wait)
1259 -------- SPAWNFLAGS --------
1260 START_ON: assume it is already turned on (when targeted)
1261 */
1262 void spawnfunc_trigger_multivibrator()
1263 {
1264         if(!self.wait)
1265                 self.wait = 1;
1266         if(!self.respawntime)
1267                 self.respawntime = self.wait;
1268
1269         self.state = 0;
1270         self.use = multivibrator_toggle;
1271         self.think = multivibrator_send;
1272         self.nextthink = time;
1273
1274         IFTARGETED
1275         {
1276                 if(!(self.spawnflags & 1))
1277                         self.nextthink = 0; // wait for a trigger event
1278         }
1279         else
1280                 self.nextthink = time;
1281 }
1282
1283
1284 void follow_init()
1285 {
1286         entity src, dst;
1287         src = find(world, targetname, self.killtarget);
1288         dst = find(world, targetname, self.target);
1289
1290         if(!src || !dst)
1291         {
1292                 objerror("follow: could not find target/killtarget");
1293                 return;
1294         }
1295
1296         dst.movetype = MOVETYPE_FOLLOW;
1297         dst.aiment = src;
1298         dst.punchangle = src.angles;
1299         dst.view_ofs = dst.origin - src.origin;
1300         dst.v_angle = dst.angles - src.angles;
1301
1302         remove(self);
1303 }
1304
1305 void spawnfunc_misc_follow()
1306 {
1307         InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1308 }
1309
1310
1311
1312
1313 .entity voicescript; // attached voice script
1314 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1315 .float voicescript_nextthink; // time to play next voice
1316 .float voicescript_voiceend; // time when this voice ends
1317
1318 void target_voicescript_clear(entity pl)
1319 {
1320         pl.voicescript = world;
1321 }
1322
1323 void target_voicescript_use()
1324 {
1325         if(activator.voicescript != self)
1326         {
1327                 activator.voicescript = self;
1328                 activator.voicescript_index = 0;
1329                 activator.voicescript_nextthink = time + self.delay;
1330         }
1331 }
1332
1333 void target_voicescript_next(entity pl)
1334 {
1335         entity vs;
1336         float i, n;
1337
1338         vs = pl.voicescript;
1339         if(!vs)
1340                 return;
1341         if(vs.message == "")
1342                 return;
1343         if(pl.classname != "player")
1344                 return;
1345         if(gameover)
1346                 return;
1347
1348         if(time >= pl.voicescript_voiceend)
1349         {
1350                 if(time >= pl.voicescript_nextthink)
1351                 {
1352                         // get the next voice...
1353                         n = tokenize_sane(vs.message);
1354
1355                         if(pl.voicescript_index < vs.cnt)
1356                                 i = pl.voicescript_index * 2;
1357                         else if(n > vs.cnt * 2)
1358                                 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1359                         else
1360                                 i = -1;
1361
1362                         if(i >= 0)
1363                         {
1364                                 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1365                                 pl.voicescript_voiceend = time + stof(argv(i + 1));
1366                         }
1367                         else
1368                                 pl.voicescript = world;
1369
1370                         pl.voicescript_index += 1;
1371                         pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1372                 }
1373         }
1374 }
1375
1376 void spawnfunc_target_voicescript()
1377 {
1378         // netname: directory of the sound files
1379         // message: list of "sound file" duration "sound file" duration, a *, and again a list
1380         //          foo1 4.1 foo2 4.0 foo3 3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1381         // wait: average time between messages
1382         // delay: initial delay before the first message
1383         
1384         float i, n;
1385         self.use = target_voicescript_use;
1386
1387         n = tokenize_sane(self.message);
1388         self.cnt = n / 2;
1389         for(i = 0; i+1 < n; i += 2)
1390         {
1391                 if(argv(i) == "*")
1392                 {
1393                         self.cnt = i / 2;
1394                         ++i;
1395                 }
1396                 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1397         }
1398 }