]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/g_triggers.qc
csqc casings
[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.model != "null")
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         if(!self.modelindex)
601         {
602                 vector misave, masave;
603                 misave = self.mins;
604                 masave = self.maxs;
605                 setmodel(self, "null");
606                 setorigin(self, self.origin + misave);
607                 setsize(self, '0 0 0', masave - misave);
608         }
609         if(!self.cnt)
610                 self.cnt = particleeffectnum(self.mdl);
611         IFTARGETED
612         {
613                 self.use = pointparticles_use;
614                 if(self.spawnflags & 1)
615                         self.state = 1;
616                 else
617                         self.state = 0;
618         }
619         else
620                 self.state = 1;
621         self.think = pointparticles_think;
622         self.nextthink = time;
623 }
624
625 void spawnfunc_func_sparks()
626 {
627         // self.cnt is the amount of sparks that one burst will spawn
628         if(self.cnt < 1) {
629                 self.cnt = 25.0; // nice default value
630         }
631
632         // self.wait is the probability that a sparkthink will spawn a spark shower
633         // range: 0 - 1, but 0 makes little sense, so...
634         if(self.wait < 0.05) {
635                 self.wait = 0.25; // nice default value
636         }
637
638         self.count = self.cnt;
639         self.mins = '0 0 0';
640         self.maxs = '0 0 0';
641         self.velocity = '0 0 -1';
642         self.mdl = "TE_SPARK";
643         self.impulse = 10 * self.wait; // by default 2.5/sec
644         self.wait = 0;
645         self.cnt = 0; // use mdl
646
647         spawnfunc_func_pointparticles();
648 }
649
650 float rainsnow_SendEntity(float to)
651 {
652         WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
653         WriteByte(MSG_ENTITY, self.state);
654         WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
655         WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
656         WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
657         WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
658         WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
659         WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
660         WriteShort(MSG_ENTITY, compressShortVector(self.dest));
661         WriteShort(MSG_ENTITY, self.count);
662         WriteByte(MSG_ENTITY, self.cnt);
663         return 1;
664 };
665
666 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
667 This is an invisible area like a trigger, which rain falls inside of.
668
669 Keys:
670 "velocity"
671  falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
672 "cnt"
673  sets color of rain (default 12 - white)
674 "count"
675  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
676 */
677 void spawnfunc_func_rain()
678 {
679         self.dest = self.velocity;
680         self.velocity = '0 0 0';
681         if (!self.dest)
682                 self.dest = '0 0 -700';
683         self.angles = '0 0 0';
684         self.movetype = MOVETYPE_NONE;
685         self.solid = SOLID_NOT;
686         SetBrushEntityModel();
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
702         if(!self.modelindex)
703         {
704                 vector misave, masave;
705                 misave = self.mins;
706                 masave = self.maxs;
707                 setmodel(self, "null");
708                 setsize(self, misave, masave);
709         }
710 };
711
712
713 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
714 This is an invisible area like a trigger, which snow falls inside of.
715
716 Keys:
717 "velocity"
718  falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
719 "cnt"
720  sets color of rain (default 12 - white)
721 "count"
722  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
723 */
724 void spawnfunc_func_snow()
725 {
726         self.dest = self.velocity;
727         self.velocity = '0 0 0';
728         if (!self.dest)
729                 self.dest = '0 0 -300';
730         self.angles = '0 0 0';
731         self.movetype = MOVETYPE_NONE;
732         self.solid = SOLID_NOT;
733         SetBrushEntityModel();
734         if (!self.cnt)
735                 self.cnt = 12;
736         if (!self.count)
737                 self.count = 2000;
738         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
739         if (self.count < 1)
740                 self.count = 1;
741         if(self.count > 65535)
742                 self.count = 65535;
743
744         self.state = 0; // 1 is rain, 0 is snow
745         self.effects = EF_NODEPTHTEST;
746         self.SendEntity = rainsnow_SendEntity;
747         self.Version = 1;
748
749         if(!self.modelindex)
750         {
751                 vector misave, masave;
752                 misave = self.mins;
753                 masave = self.maxs;
754                 setmodel(self, "null");
755                 setsize(self, misave, masave);
756         }
757 };
758
759
760 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
761
762 .float modelscale;
763 void misc_laser_aim()
764 {
765         vector a;
766         if(self.enemy)
767         {
768                 if(self.spawnflags & 2)
769                 {
770                         if(self.enemy.origin != self.mangle)
771                         {
772                                 self.mangle = self.enemy.origin;
773                                 self.SendFlags |= 2;
774                         }
775                 }
776                 else
777                 {
778                         a = vectoangles(self.enemy.origin - self.origin);
779                         a_x = -a_x;
780                         if(a != self.mangle)
781                         {
782                                 self.mangle = a;
783                                 self.SendFlags |= 2;
784                         }
785                 }
786         }
787         else
788         {
789                 if(self.angles != self.mangle)
790                 {
791                         self.mangle = self.angles;
792                         self.SendFlags |= 2;
793                 }
794         }
795         if(self.origin != self.oldorigin)
796         {
797                 self.SendFlags |= 1;
798                 self.oldorigin = self.origin;
799         }
800 }
801
802 void misc_laser_init()
803 {
804         if(self.target != "")
805                 self.enemy = find(world, targetname, self.target);
806 }
807
808 .entity pusher;
809 void misc_laser_think()
810 {
811         vector o;
812         entity oldself;
813
814         self.nextthink = time;
815
816         if(!self.state)
817                 return;
818
819         misc_laser_aim();
820
821         if(self.enemy)
822         {
823                 o = self.enemy.origin;
824                 if not(self.spawnflags & 2)
825                         o = self.origin + normalize(o - self.origin) * 32768;
826         }
827         else
828         {
829                 makevectors(self.mangle);
830                 o = self.origin + v_forward * 32768;
831         }
832
833         if(self.dmg)
834         {
835                 if(self.dmg < 0)
836                         FireRailgunBullet(self.origin, o, 100000, 0, DEATH_HURTTRIGGER);
837                 else
838                         FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, DEATH_HURTTRIGGER);
839         }
840
841         if(self.enemy.target != "") // DETECTOR laser
842         {
843                 traceline(self.origin, o, MOVE_NORMAL, self);
844                 if(trace_ent.iscreature)
845                 {
846                         self.pusher = trace_ent;
847                         if(!self.count)
848                         {
849                                 self.count = 1;
850
851                                 oldself = self;
852                                 self = self.enemy;
853                                 activator = self.pusher;
854                                 SUB_UseTargets();
855                                 self = oldself;
856                         }
857                 }
858                 else
859                 {
860                         if(self.count)
861                         {
862                                 self.count = 0;
863
864                                 oldself = self;
865                                 self = self.enemy;
866                                 activator = self.pusher;
867                                 SUB_UseTargets();
868                                 self = oldself;
869                         }
870                 }
871         }
872 }
873
874 float laser_SendEntity(entity to, float fl)
875 {
876         WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
877         fl = fl - (fl & 0xE0); // use that bit to indicate finite length laser
878         if(self.spawnflags & 2)
879                 fl |= 0x80;
880         if(self.alpha)
881                 fl |= 0x40;
882         if(self.scale != 1 || self.modelscale != 1)
883                 fl |= 0x20;
884         WriteByte(MSG_ENTITY, fl);
885         if(fl & 1)
886         {
887                 WriteCoord(MSG_ENTITY, self.origin_x);
888                 WriteCoord(MSG_ENTITY, self.origin_y);
889                 WriteCoord(MSG_ENTITY, self.origin_z);
890         }
891         if(fl & 8)
892         {
893                 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
894                 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
895                 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
896                 if(fl & 0x40)
897                         WriteByte(MSG_ENTITY, self.alpha * 255.0);
898                 if(fl & 0x20)
899                 {
900                         WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
901                         WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
902                 }
903                 WriteShort(MSG_ENTITY, self.cnt + 1);
904         }
905         if(fl & 2)
906         {
907                 if(fl & 0x80)
908                 {
909                         WriteCoord(MSG_ENTITY, self.enemy.origin_x);
910                         WriteCoord(MSG_ENTITY, self.enemy.origin_y);
911                         WriteCoord(MSG_ENTITY, self.enemy.origin_z);
912                 }
913                 else
914                 {
915                         WriteCoord(MSG_ENTITY, self.mangle_x);
916                         WriteCoord(MSG_ENTITY, self.mangle_y);
917                 }
918         }
919         if(fl & 4)
920                 WriteByte(MSG_ENTITY, self.state);
921         return 1;
922 }
923
924 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
925 Any object touching the beam will be hurt
926 Keys:
927 "target"
928  spawnfunc_target_position where the laser ends
929 "mdl"
930  name of beam end effect to use
931 "colormod"
932  color of the beam (default: red)
933 "dmg"
934  damage per second (-1 for a laser that kills immediately)
935 */
936 void laser_use()
937 {
938         self.state = !self.state;
939         self.SendFlags |= 4;
940         misc_laser_aim();
941 }
942
943 void spawnfunc_misc_laser()
944 {
945         if(self.mdl)
946         {
947                 if(self.mdl == "none")
948                         self.cnt = -1;
949                 else
950                 {
951                         self.cnt = particleeffectnum(self.mdl);
952                         if(self.cnt < 0)
953                                 if(self.dmg)
954                                         self.cnt = particleeffectnum("laser_deadly");
955                 }
956         }
957         else if(!self.cnt)
958         {
959                 if(self.dmg)
960                         self.cnt = particleeffectnum("laser_deadly");
961                 else
962                         self.cnt = -1;
963         }
964         if(self.cnt < 0)
965                 self.cnt = -1;
966
967         if(self.colormod == '0 0 0')
968                 if(!self.alpha)
969                         self.colormod = '1 0 0';
970         if(!self.message)
971                 self.message = "saw the light";
972         if (!self.message2)
973                 self.message2 = "was pushed into a laser by";
974         if(!self.scale)
975                 self.scale = 1;
976         if(!self.modelscale)
977                 self.modelscale = 1;
978         self.think = misc_laser_think;
979         self.nextthink = time;
980         InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
981
982         self.effects = EF_NODEPTHTEST;
983         self.SendEntity = laser_SendEntity;
984         setmodel(self, "null");
985         self.mangle = self.angles;
986
987         IFTARGETED
988         {
989                 self.use = laser_use;
990                 if(self.spawnflags & 1)
991                         self.state = 1;
992                 else
993                         self.state = 0;
994         }
995         else
996                 self.state = 1;
997 }
998
999 // tZorks trigger impulse / gravity
1000 .float radius;
1001 .float falloff;
1002 .float strength;
1003 .float lastpushtime;
1004
1005 // targeted (directional) mode
1006 void trigger_impulse_touch1()
1007 {
1008         entity targ;
1009     float pushdeltatime;
1010     float str;
1011
1012         // FIXME: Better checking for what to push and not.
1013         if not(other.iscreature)
1014         if (other.classname != "corpse")
1015         if (other.classname != "body")
1016         if (other.classname != "gib")
1017         if (other.classname != "missile")
1018         if (other.classname != "rocket")
1019         if (other.classname != "casing")
1020         if (other.classname != "grenade")
1021         if (other.classname != "plasma")
1022         if (other.classname != "plasma_prim")
1023         if (other.classname != "plasma_chain")
1024         if (other.classname != "droppedweapon")
1025                 return;
1026
1027         if (other.deadflag && other.iscreature)
1028                 return;
1029
1030         EXACTTRIGGER_TOUCH;
1031
1032     targ = find(world, targetname, self.target);
1033     if(!targ)
1034     {
1035         objerror("trigger_force without a (valid) .target!\n");
1036         remove(self);
1037         return;
1038     }
1039
1040     if(self.falloff == 1)
1041         str = (str / self.radius) * self.strength;
1042     else if(self.falloff == 2)
1043         str = (1 - (str / self.radius)) * self.strength;
1044     else
1045         str = self.strength;
1046
1047     pushdeltatime = time - other.lastpushtime;
1048     if (pushdeltatime > 0.15) pushdeltatime = 0;
1049     other.lastpushtime = time;
1050     if(!pushdeltatime) return;
1051
1052     other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
1053         other.flags &~= FL_ONGROUND;
1054 }
1055
1056 // Directionless (accelerator/decelerator) mode
1057 void trigger_impulse_touch2()
1058 {
1059     float pushdeltatime;
1060
1061         // FIXME: Better checking for what to push and not.
1062         if not(other.iscreature)
1063         if (other.classname != "corpse")
1064         if (other.classname != "body")
1065         if (other.classname != "gib")
1066         if (other.classname != "missile")
1067         if (other.classname != "rocket")
1068         if (other.classname != "casing")
1069         if (other.classname != "grenade")
1070         if (other.classname != "plasma")
1071         if (other.classname != "plasma_prim")
1072         if (other.classname != "plasma_chain")
1073         if (other.classname != "droppedweapon")
1074                 return;
1075
1076         if (other.deadflag && other.iscreature)
1077                 return;
1078
1079         EXACTTRIGGER_TOUCH;
1080
1081     pushdeltatime = time - other.lastpushtime;
1082     if (pushdeltatime > 0.15) pushdeltatime = 0;
1083     other.lastpushtime = time;
1084     if(!pushdeltatime) return;
1085
1086     //if(self.strength > 1)
1087         other.velocity = other.velocity * (self.strength * pushdeltatime);
1088     //else
1089     //    other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
1090 }
1091
1092 // Spherical (gravity/repulsor) mode
1093 void trigger_impulse_touch3()
1094 {
1095     float pushdeltatime;
1096     float str;
1097
1098         // FIXME: Better checking for what to push and not.
1099         if not(other.iscreature)
1100         if (other.classname != "corpse")
1101         if (other.classname != "body")
1102         if (other.classname != "gib")
1103         if (other.classname != "missile")
1104         if (other.classname != "rocket")
1105         if (other.classname != "casing")
1106         if (other.classname != "grenade")
1107         if (other.classname != "plasma")
1108         if (other.classname != "plasma_prim")
1109         if (other.classname != "plasma_chain")
1110         if (other.classname != "droppedweapon")
1111                 return;
1112
1113         if (other.deadflag && other.iscreature)
1114                 return;
1115
1116         EXACTTRIGGER_TOUCH;
1117
1118     pushdeltatime = time - other.lastpushtime;
1119     if (pushdeltatime > 0.15) pushdeltatime = 0;
1120     other.lastpushtime = time;
1121     if(!pushdeltatime) return;
1122
1123     setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1124
1125         str = min(self.radius, vlen(self.origin - other.origin));
1126
1127     if(self.falloff == 1)
1128         str = (1 - str / self.radius) * self.strength; // 1 in the inside
1129     else if(self.falloff == 2)
1130         str = (str / self.radius) * self.strength; // 0 in the inside
1131     else
1132         str = self.strength;
1133
1134     other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1135 }
1136
1137 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1138 -------- KEYS --------
1139 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1140          If not, this trigger acts like a damper/accelerator field.
1141
1142 strength : This is how mutch force to add in the direction of .target each second
1143            when .target is set. If not, this is hoe mutch to slow down/accelerate
1144            someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1145
1146 radius   : If set, act as a spherical device rather then a liniar one.
1147
1148 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1149
1150 -------- NOTES --------
1151 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1152 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1153 */
1154
1155 void spawnfunc_trigger_impulse()
1156 {
1157         EXACTTRIGGER_INIT;
1158     if(self.radius)
1159     {
1160         if(!self.strength) self.strength = 2000;
1161         setorigin(self, self.origin);
1162         setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1163         self.touch = trigger_impulse_touch3;
1164     }
1165     else
1166     {
1167         if(self.target)
1168         {
1169             if(!self.strength) self.strength = 950;
1170             self.touch = trigger_impulse_touch1;
1171         }
1172         else
1173         {
1174             if(!self.strength) self.strength = 0.9;
1175             self.touch = trigger_impulse_touch2;
1176         }
1177     }
1178 }
1179
1180 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1181 "Flip-flop" trigger gate... lets only every second trigger event through
1182 */
1183 void flipflop_use()
1184 {
1185         self.state = !self.state;
1186         if(self.state)
1187                 SUB_UseTargets();
1188 }
1189
1190 void spawnfunc_trigger_flipflop()
1191 {
1192         if(self.spawnflags & 1)
1193                 self.state = 1;
1194     self.use = flipflop_use;
1195 }
1196
1197 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1198 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1199 */
1200 void monoflop_use()
1201 {
1202         self.nextthink = time + self.wait;
1203         self.enemy = activator;
1204         if(self.state)
1205                 return;
1206         self.state = 1;
1207         SUB_UseTargets();
1208 }
1209 void monoflop_fixed_use()
1210 {
1211         if(self.state)
1212                 return;
1213         self.nextthink = time + self.wait;
1214         self.state = 1;
1215         self.enemy = activator;
1216         SUB_UseTargets();
1217 }
1218
1219 void monoflop_think()
1220 {
1221         self.state = 0;
1222         activator = self.enemy;
1223         SUB_UseTargets();
1224 }
1225
1226 void spawnfunc_trigger_monoflop()
1227 {
1228         if(!self.wait)
1229                 self.wait = 1;
1230         if(self.spawnflags & 1)
1231                 self.use = monoflop_fixed_use;
1232         else
1233                 self.use = monoflop_use;
1234         self.think = monoflop_think;
1235         self.state = 0;
1236 }
1237
1238 void multivibrator_send()
1239 {
1240         float newstate;
1241         float cyclestart;
1242
1243         cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1244
1245         newstate = (time < cyclestart + self.wait);
1246
1247         activator = self;
1248         if(self.state != newstate)
1249                 SUB_UseTargets();
1250         self.state = newstate;
1251
1252         if(self.state)
1253                 self.nextthink = cyclestart + self.wait + 0.01;
1254         else
1255                 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1256 }
1257
1258 void multivibrator_toggle()
1259 {
1260         if(self.nextthink == 0)
1261         {
1262                 multivibrator_send();
1263         }
1264         else
1265         {
1266                 if(self.state)
1267                 {
1268                         SUB_UseTargets();
1269                         self.state = 0;
1270                 }
1271                 self.nextthink = 0;
1272         }
1273 }
1274
1275 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1276 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1277 -------- KEYS --------
1278 target: trigger all entities with this targetname when it goes off
1279 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1280 phase: offset of the timing
1281 wait: "on" cycle time (default: 1)
1282 respawntime: "off" cycle time (default: same as wait)
1283 -------- SPAWNFLAGS --------
1284 START_ON: assume it is already turned on (when targeted)
1285 */
1286 void spawnfunc_trigger_multivibrator()
1287 {
1288         if(!self.wait)
1289                 self.wait = 1;
1290         if(!self.respawntime)
1291                 self.respawntime = self.wait;
1292
1293         self.state = 0;
1294         self.use = multivibrator_toggle;
1295         self.think = multivibrator_send;
1296         self.nextthink = time;
1297
1298         IFTARGETED
1299         {
1300                 if(!(self.spawnflags & 1))
1301                         self.nextthink = 0; // wait for a trigger event
1302         }
1303         else
1304                 self.nextthink = time;
1305 }
1306
1307
1308 void follow_init()
1309 {
1310         entity src, dst;
1311         src = find(world, targetname, self.killtarget);
1312         dst = find(world, targetname, self.target);
1313
1314         if(!src || !dst)
1315         {
1316                 objerror("follow: could not find target/killtarget");
1317                 return;
1318         }
1319
1320         dst.movetype = MOVETYPE_FOLLOW;
1321         dst.aiment = src;
1322         dst.punchangle = src.angles;
1323         dst.view_ofs = dst.origin - src.origin;
1324         dst.v_angle = dst.angles - src.angles;
1325
1326         remove(self);
1327 }
1328
1329 void spawnfunc_misc_follow()
1330 {
1331         InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1332 }
1333
1334
1335
1336 void gamestart_use() {
1337         activator = self;
1338         SUB_UseTargets();
1339         remove(self);
1340 }
1341
1342 void spawnfunc_trigger_gamestart() {
1343         if(self.wait)
1344         {
1345                 self.think = gamestart_use;
1346                 self.nextthink = self.wait;
1347         }
1348         else
1349                 InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
1350 }
1351
1352
1353
1354
1355 .entity voicescript; // attached voice script
1356 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1357 .float voicescript_nextthink; // time to play next voice
1358 .float voicescript_voiceend; // time when this voice ends
1359
1360 void target_voicescript_clear(entity pl)
1361 {
1362         pl.voicescript = world;
1363 }
1364
1365 void target_voicescript_use()
1366 {
1367         if(activator.voicescript != self)
1368         {
1369                 activator.voicescript = self;
1370                 activator.voicescript_index = 0;
1371                 activator.voicescript_nextthink = time + self.delay;
1372         }
1373 }
1374
1375 void target_voicescript_next(entity pl)
1376 {
1377         entity vs;
1378         float i, n;
1379
1380         vs = pl.voicescript;
1381         if(!vs)
1382                 return;
1383         if(vs.message == "")
1384                 return;
1385         if(pl.classname != "player")
1386                 return;
1387         if(gameover)
1388                 return;
1389
1390         if(time >= pl.voicescript_voiceend)
1391         {
1392                 if(time >= pl.voicescript_nextthink)
1393                 {
1394                         // get the next voice...
1395                         n = tokenize_sane(vs.message);
1396
1397                         if(pl.voicescript_index < vs.cnt)
1398                                 i = pl.voicescript_index * 2;
1399                         else if(n > vs.cnt * 2)
1400                                 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1401                         else
1402                                 i = -1;
1403
1404                         if(i >= 0)
1405                         {
1406                                 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1407                                 pl.voicescript_voiceend = time + stof(argv(i + 1));
1408                         }
1409                         else
1410                                 pl.voicescript = world;
1411
1412                         pl.voicescript_index += 1;
1413                         pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1414                 }
1415         }
1416 }
1417
1418 void spawnfunc_target_voicescript()
1419 {
1420         // netname: directory of the sound files
1421         // message: list of "sound file" duration "sound file" duration, a *, and again a list
1422         //          foo1 4.1 foo2 4.0 foo3 3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1423         // wait: average time between messages
1424         // delay: initial delay before the first message
1425         
1426         float i, n;
1427         self.use = target_voicescript_use;
1428
1429         n = tokenize_sane(self.message);
1430         self.cnt = n / 2;
1431         for(i = 0; i+1 < n; i += 2)
1432         {
1433                 if(argv(i) == "*")
1434                 {
1435                         self.cnt = i / 2;
1436                         ++i;
1437                 }
1438                 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1439         }
1440 }