]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/g_triggers.qc
r4978 | div0 | 2008-11-04 12:57:51 +0100 (Tue, 04 Nov 2008) | 2 lines
[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                         self.cnt = particleeffectnum(self.mdl);
930         }
931         else if(!self.cnt)
932         {
933                 if(self.dmg)
934                         self.cnt = particleeffectnum("laser_deadly");
935                 else
936                         self.cnt = -1;
937         }
938
939         if(self.colormod == '0 0 0')
940                 if(!self.alpha)
941                         self.colormod = '1 0 0';
942         if(!self.message)
943                 self.message = "saw the light";
944         if (!self.message2)
945                 self.message2 = "was pushed into a laser by";
946         self.think = misc_laser_think;
947         self.nextthink = time;
948         InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
949
950         self.effects = EF_NODEPTHTEST;
951         self.SendEntity = laser_SendEntity;
952         self.SendFlags = 15;
953         self.modelindex = 1;
954         self.model = "net_entity";
955         self.mangle = self.angles;
956
957         IFTARGETED
958         {
959                 self.use = laser_use;
960                 if(self.spawnflags & 1)
961                         self.state = 1;
962                 else
963                         self.state = 0;
964         }
965         else
966                 self.state = 1;
967 }
968
969 // tZorks trigger impulse / gravity
970 .float radius;
971 .float falloff;
972 .float strength;
973 .float lastpushtime;
974
975 // targeted (directional) mode
976 void trigger_impulse_touch1()
977 {
978         entity targ;
979     float pushdeltatime;
980     float str;
981
982         // FIXME: Better checking for what to push and not.
983         if not(other.iscreature)
984         if (other.classname != "corpse")
985         if (other.classname != "body")
986         if (other.classname != "gib")
987         if (other.classname != "missile")
988         if (other.classname != "rocket")
989         if (other.classname != "casing")
990         if (other.classname != "grenade")
991         if (other.classname != "plasma")
992         if (other.classname != "plasma_prim")
993         if (other.classname != "plasma_chain")
994         if (other.classname != "droppedweapon")
995                 return;
996
997         if (other.deadflag && other.iscreature)
998                 return;
999
1000         EXACTTRIGGER_TOUCH;
1001
1002     targ = find(world, targetname, self.target);
1003     if(!targ)
1004     {
1005         objerror("trigger_force without a (valid) .target!\n");
1006         remove(self);
1007         return;
1008     }
1009
1010     if(self.falloff == 1)
1011         str = (str / self.radius) * self.strength;
1012     else if(self.falloff == 2)
1013         str = (1 - (str / self.radius)) * self.strength;
1014     else
1015         str = self.strength;
1016
1017     pushdeltatime = time - other.lastpushtime;
1018     if (pushdeltatime > 0.15) pushdeltatime = 0;
1019     other.lastpushtime = time;
1020     if(!pushdeltatime) return;
1021
1022     other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
1023 }
1024
1025 // Directionless (accelerator/decelerator) mode
1026 void trigger_impulse_touch2()
1027 {
1028     float pushdeltatime;
1029
1030         // FIXME: Better checking for what to push and not.
1031         if not(other.iscreature)
1032         if (other.classname != "corpse")
1033         if (other.classname != "body")
1034         if (other.classname != "gib")
1035         if (other.classname != "missile")
1036         if (other.classname != "rocket")
1037         if (other.classname != "casing")
1038         if (other.classname != "grenade")
1039         if (other.classname != "plasma")
1040         if (other.classname != "plasma_prim")
1041         if (other.classname != "plasma_chain")
1042         if (other.classname != "droppedweapon")
1043                 return;
1044
1045         if (other.deadflag && other.iscreature)
1046                 return;
1047
1048         EXACTTRIGGER_TOUCH;
1049
1050     pushdeltatime = time - other.lastpushtime;
1051     if (pushdeltatime > 0.15) pushdeltatime = 0;
1052     other.lastpushtime = time;
1053     if(!pushdeltatime) return;
1054
1055     //if(self.strength > 1)
1056         other.velocity = other.velocity * (self.strength * pushdeltatime);
1057     //else
1058     //    other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
1059 }
1060
1061 // Spherical (gravity/repulsor) mode
1062 void trigger_impulse_touch3()
1063 {
1064     float pushdeltatime;
1065     float str;
1066
1067         // FIXME: Better checking for what to push and not.
1068         if not(other.iscreature)
1069         if (other.classname != "corpse")
1070         if (other.classname != "body")
1071         if (other.classname != "gib")
1072         if (other.classname != "missile")
1073         if (other.classname != "rocket")
1074         if (other.classname != "casing")
1075         if (other.classname != "grenade")
1076         if (other.classname != "plasma")
1077         if (other.classname != "plasma_prim")
1078         if (other.classname != "plasma_chain")
1079         if (other.classname != "droppedweapon")
1080                 return;
1081
1082         if (other.deadflag && other.iscreature)
1083                 return;
1084
1085         EXACTTRIGGER_TOUCH;
1086
1087     pushdeltatime = time - other.lastpushtime;
1088     if (pushdeltatime > 0.15) pushdeltatime = 0;
1089     other.lastpushtime = time;
1090     if(!pushdeltatime) return;
1091
1092     setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1093
1094         str = min(self.radius, vlen(self.origin - other.origin));
1095
1096     if(self.falloff == 1)
1097         str = (1 - str / self.radius) * self.strength; // 1 in the inside
1098     else if(self.falloff == 2)
1099         str = (str / self.radius) * self.strength; // 0 in the inside
1100     else
1101         str = self.strength;
1102
1103     other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1104 }
1105
1106 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1107 -------- KEYS --------
1108 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1109          If not, this trigger acts like a damper/accelerator field.
1110
1111 strength : This is how mutch force to add in the direction of .target each second
1112            when .target is set. If not, this is hoe mutch to slow down/accelerate
1113            someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1114
1115 radius   : If set, act as a spherical device rather then a liniar one.
1116
1117 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1118
1119 -------- NOTES --------
1120 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1121 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1122 */
1123
1124 void spawnfunc_trigger_impulse()
1125 {
1126         EXACTTRIGGER_INIT;
1127     if(self.radius)
1128     {
1129         if(!self.strength) self.strength = 2000;
1130         setorigin(self, self.origin);
1131         setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1132         self.touch = trigger_impulse_touch3;
1133     }
1134     else
1135     {
1136         if(self.target)
1137         {
1138             if(!self.strength) self.strength = 950;
1139             self.touch = trigger_impulse_touch1;
1140         }
1141         else
1142         {
1143             if(!self.strength) self.strength = 0.9;
1144             self.touch = trigger_impulse_touch2;
1145         }
1146     }
1147 }
1148
1149 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1150 "Flip-flop" trigger gate... lets only every second trigger event through
1151 */
1152 void flipflop_use()
1153 {
1154         self.state = !self.state;
1155         if(self.state)
1156                 SUB_UseTargets();
1157 }
1158
1159 void spawnfunc_trigger_flipflop()
1160 {
1161         if(self.spawnflags & 1)
1162                 self.state = 1;
1163     self.use = flipflop_use;
1164 }
1165
1166 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1167 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1168 */
1169 void monoflop_use()
1170 {
1171         self.nextthink = time + self.wait;
1172         self.enemy = activator;
1173         if(self.state)
1174                 return;
1175         self.state = 1;
1176         SUB_UseTargets();
1177 }
1178 void monoflop_fixed_use()
1179 {
1180         if(self.state)
1181                 return;
1182         self.nextthink = time + self.wait;
1183         self.state = 1;
1184         self.enemy = activator;
1185         SUB_UseTargets();
1186 }
1187
1188 void monoflop_think()
1189 {
1190         self.state = 0;
1191         activator = self.enemy;
1192         SUB_UseTargets();
1193 }
1194
1195 void spawnfunc_trigger_monoflop()
1196 {
1197         if(!self.wait)
1198                 self.wait = 1;
1199         if(self.spawnflags & 1)
1200                 self.use = monoflop_fixed_use;
1201         else
1202                 self.use = monoflop_use;
1203         self.think = monoflop_think;
1204         self.state = 0;
1205 }
1206
1207 void multivibrator_send()
1208 {
1209         float newstate;
1210         float cyclestart;
1211
1212         cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1213
1214         newstate = (time < cyclestart + self.wait);
1215
1216         activator = self;
1217         if(self.state != newstate)
1218                 SUB_UseTargets();
1219         self.state = newstate;
1220
1221         if(self.state)
1222                 self.nextthink = cyclestart + self.wait + 0.01;
1223         else
1224                 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1225 }
1226
1227 void multivibrator_toggle()
1228 {
1229         if(self.nextthink == 0)
1230         {
1231                 multivibrator_send();
1232         }
1233         else
1234         {
1235                 if(self.state)
1236                 {
1237                         SUB_UseTargets();
1238                         self.state = 0;
1239                 }
1240                 self.nextthink = 0;
1241         }
1242 }
1243
1244 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1245 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1246 -------- KEYS --------
1247 target: trigger all entities with this targetname when it goes off
1248 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1249 phase: offset of the timing
1250 wait: "on" cycle time (default: 1)
1251 respawntime: "off" cycle time (default: same as wait)
1252 -------- SPAWNFLAGS --------
1253 START_ON: assume it is already turned on (when targeted)
1254 */
1255 void spawnfunc_trigger_multivibrator()
1256 {
1257         if(!self.wait)
1258                 self.wait = 1;
1259         if(!self.respawntime)
1260                 self.respawntime = self.wait;
1261
1262         self.state = 0;
1263         self.use = multivibrator_toggle;
1264         self.think = multivibrator_send;
1265         self.nextthink = time;
1266
1267         IFTARGETED
1268         {
1269                 if(!(self.spawnflags & 1))
1270                         self.nextthink = 0; // wait for a trigger event
1271         }
1272         else
1273                 self.nextthink = time;
1274 }
1275
1276
1277 void follow_init()
1278 {
1279         entity src, dst;
1280         src = find(world, targetname, self.killtarget);
1281         dst = find(world, targetname, self.target);
1282
1283         if(!src || !dst)
1284         {
1285                 objerror("follow: could not find target/killtarget");
1286                 return;
1287         }
1288
1289         dst.movetype = MOVETYPE_FOLLOW;
1290         dst.aiment = src;
1291         dst.punchangle = src.angles;
1292         dst.view_ofs = dst.origin - src.origin;
1293         dst.v_angle = dst.angles - src.angles;
1294
1295         remove(self);
1296 }
1297
1298 void spawnfunc_misc_follow()
1299 {
1300         InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1301 }
1302
1303
1304
1305
1306 .entity voicescript; // attached voice script
1307 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1308 .float voicescript_nextthink; // time to play next voice
1309 .float voicescript_voiceend; // time when this voice ends
1310
1311 void target_voicescript_clear(entity pl)
1312 {
1313         pl.voicescript = world;
1314 }
1315
1316 void target_voicescript_use()
1317 {
1318         if(activator.voicescript != self)
1319         {
1320                 activator.voicescript = self;
1321                 activator.voicescript_index = 0;
1322                 activator.voicescript_nextthink = time + self.delay;
1323         }
1324 }
1325
1326 void target_voicescript_next(entity pl)
1327 {
1328         entity vs;
1329         float i, n;
1330
1331         vs = pl.voicescript;
1332         if(!vs)
1333                 return;
1334         if(vs.message == "")
1335                 return;
1336         if(pl.classname != "player")
1337                 return;
1338         if(gameover)
1339                 return;
1340
1341         if(time >= pl.voicescript_voiceend)
1342         {
1343                 if(time >= pl.voicescript_nextthink)
1344                 {
1345                         // get the next voice...
1346                         n = tokenize_sane(vs.message);
1347
1348                         if(pl.voicescript_index < vs.cnt)
1349                                 i = pl.voicescript_index * 2;
1350                         else if(n > vs.cnt * 2)
1351                                 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1352                         else
1353                                 i = -1;
1354
1355                         if(i >= 0)
1356                         {
1357                                 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1358                                 pl.voicescript_voiceend = time + stof(argv(i + 1));
1359                         }
1360                         else
1361                                 pl.voicescript = world;
1362
1363                         pl.voicescript_index += 1;
1364                         pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1365                 }
1366         }
1367 }
1368
1369 void spawnfunc_target_voicescript()
1370 {
1371         // netname: directory of the sound files
1372         // message: list of "sound file" duration "sound file" duration, a *, and again a list
1373         //          foo1 4.1 foo2 4.0 foo3 3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1374         // wait: average time between messages
1375         // delay: initial delay before the first message
1376         
1377         float i, n;
1378         self.use = target_voicescript_use;
1379
1380         n = tokenize_sane(self.message);
1381         self.cnt = n / 2;
1382         for(i = 0; i+1 < n; i += 2)
1383         {
1384                 if(argv(i) == "*")
1385                 {
1386                         self.cnt = i / 2;
1387                         ++i;
1388                 }
1389                 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1390         }
1391 }