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