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