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