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