]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/g_triggers.qc
r5359 | div0 | 2008-12-30 13:56:25 +0100 (Tue, 30 Dec 2008) | 2 lines
[divverent/nexuiz.git] / data / qcsrc / server / g_triggers.qc
1
2 void() SUB_UseTargets;
3
4 void DelayThink()
5 {
6         activator = self.enemy;
7         SUB_UseTargets ();
8         remove(self);
9 };
10
11 /*
12 ==============================
13 SUB_UseTargets
14
15 the global "activator" should be set to the entity that initiated the firing.
16
17 If self.delay is set, a DelayedUse entity will be created that will actually
18 do the SUB_UseTargets after that many seconds have passed.
19
20 Centerprints any self.message to the activator.
21
22 Removes all entities with a targetname that match self.killtarget,
23 and removes them, so some events can remove other triggers.
24
25 Search for (string)targetname in all entities that
26 match (string)self.target and call their .use function
27
28 ==============================
29 */
30 void SUB_UseTargets()
31 {
32         local entity t, stemp, otemp, act;
33         string s;
34         float i;
35
36 //
37 // check for a delay
38 //
39         if (self.delay)
40         {
41         // create a temp object to fire at a later time
42                 t = spawn();
43                 t.classname = "DelayedUse";
44                 t.nextthink = time + self.delay;
45                 t.think = DelayThink;
46                 t.enemy = activator;
47                 t.message = self.message;
48                 t.killtarget = self.killtarget;
49                 t.target = self.target;
50                 return;
51         }
52
53
54 //
55 // print the message
56 //
57         if (activator.classname == "player" && self.message != "")
58         {
59                 if(clienttype(activator) == CLIENTTYPE_REAL)
60                 {
61                         centerprint (activator, self.message);
62                         if (!self.noise)
63                                 play2(activator, "misc/talk.wav");
64                 }
65         }
66
67 //
68 // kill the killtagets
69 //
70         s = self.killtarget;
71         if (s != "")
72         {
73                 for(t = world; (t = find(t, targetname, s)); )
74                         remove(t);
75         }
76
77 //
78 // fire targets
79 //
80         act = activator;
81         stemp = self;
82         otemp = other;
83
84         for(i = 0; i < 4; ++i)
85         {
86                 switch(i)
87                 {
88                         default:
89                         case 0: s = stemp.target; break;
90                         case 1: s = stemp.target2; break;
91                         case 2: s = stemp.target3; break;
92                         case 3: s = stemp.target4; break;
93                 }
94                 if (s != "")
95                 {
96                         for(t = world; (t = find(t, targetname, s)); )
97                         if(t.use)
98                         {
99                                 //print(stemp.classname, " ", stemp.targetname, " -> ", t.classname, " ", t.targetname, "\n");
100                                 self = t;
101                                 other = stemp;
102                                 activator = act;
103                                 self.use();
104                         }
105                 }
106         }
107
108         activator = act;
109         self = stemp;
110         other = otemp;
111 };
112
113
114 //=============================================================================
115
116 float   SPAWNFLAG_NOMESSAGE = 1;
117 float   SPAWNFLAG_NOTOUCH = 1;
118
119 // the wait time has passed, so set back up for another activation
120 void multi_wait()
121 {
122         if (self.max_health)
123         {
124                 self.health = self.max_health;
125                 self.takedamage = DAMAGE_YES;
126                 self.solid = SOLID_BBOX;
127         }
128 };
129
130
131 // the trigger was just touched/killed/used
132 // self.enemy should be set to the activator so it can be held through a delay
133 // so wait for the delay time before firing
134 void multi_trigger()
135 {
136         if (self.nextthink > time)
137         {
138                 return;         // allready been triggered
139         }
140
141         if (self.classname == "trigger_secret")
142         {
143                 if (self.enemy.classname != "player")
144                         return;
145                 found_secrets = found_secrets + 1;
146                 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
147         }
148
149         if (self.noise)
150                 sound (self.enemy, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
151
152 // don't trigger again until reset
153         self.takedamage = DAMAGE_NO;
154
155         activator = self.enemy;
156
157         SUB_UseTargets();
158
159         if (self.wait > 0)
160         {
161                 self.think = multi_wait;
162                 self.nextthink = time + self.wait;
163         }
164         else
165         {       // we can't just remove (self) here, because this is a touch function
166                 // called wheil C code is looping through area links...
167                 self.touch = SUB_Null;
168
169                 self.nextthink = time + 0.1;
170                 self.think = SUB_Remove;
171         }
172 };
173
174 void multi_use()
175 {
176         self.enemy = activator;
177         multi_trigger();
178 };
179
180 void multi_touch()
181 {
182         if not(self.spawnflags & 2)
183         {
184                 if not(other.iscreature)
185                         return;
186
187                 if(self.team)
188                 if(self.team == other.team)
189                         return;
190         }
191
192 // if the trigger has an angles field, check player's facing direction
193         if (self.movedir != '0 0 0')
194         {
195                 makevectors (other.angles);
196                 if (v_forward * self.movedir < 0)
197                         return;         // not facing the right way
198         }
199
200         EXACTTRIGGER_TOUCH;
201
202         self.enemy = other;
203         multi_trigger ();
204 };
205
206 void multi_eventdamage (vector hitloc, float damage, entity inflictor, entity attacker, float deathtype)
207 {
208         if (!self.takedamage)
209                 return;
210         self.health = self.health - damage;
211         if (self.health <= 0)
212         {
213                 self.enemy = attacker;
214                 multi_trigger();
215         }
216 }
217
218 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
219 Variable sized repeatable trigger.  Must be targeted at one or more entities.  If "health" is set, the trigger must be killed to activate each time.
220 If "delay" is set, the trigger waits some time after activating before firing.
221 "wait" : Seconds between triggerings. (.2 default)
222 If notouch is set, the trigger is only fired by other entities, not by touching.
223 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
224 sounds
225 1)      secret
226 2)      beep beep
227 3)      large switch
228 4)
229 set "message" to text string
230 */
231 void spawnfunc_trigger_multiple()
232 {
233         if (self.sounds == 1)
234         {
235                 precache_sound ("misc/secret.wav");
236                 self.noise = "misc/secret.wav";
237         }
238         else if (self.sounds == 2)
239         {
240                 precache_sound ("misc/talk.wav");
241                 self.noise = "misc/talk.wav";
242         }
243         else if (self.sounds == 3)
244         {
245                 precache_sound ("misc/trigger1.wav");
246                 self.noise = "misc/trigger1.wav";
247         }
248
249         if (!self.wait)
250                 self.wait = 0.2;
251         self.use = multi_use;
252
253         EXACTTRIGGER_INIT;
254
255         if (self.health)
256         {
257                 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
258                         objerror ("health and notouch don't make sense\n");
259                 self.max_health = self.health;
260                 self.event_damage = multi_eventdamage;
261                 self.takedamage = DAMAGE_YES;
262                 self.solid = SOLID_BBOX;
263                 setorigin (self, self.origin);  // make sure it links into the world
264         }
265         else
266         {
267                 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
268                 {
269                         self.touch = multi_touch;
270                         setorigin (self, self.origin);  // make sure it links into the world
271                 }
272         }
273 };
274
275
276 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
277 Variable sized trigger. Triggers once, then removes itself.  You must set the key "target" to the name of another object in the level that has a matching
278 "targetname".  If "health" is set, the trigger must be killed to activate.
279 If notouch is set, the trigger is only fired by other entities, not by touching.
280 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
281 if "angle" is set, the trigger will only fire when someone is facing the direction of the angle.  Use "360" for an angle of 0.
282 sounds
283 1)      secret
284 2)      beep beep
285 3)      large switch
286 4)
287 set "message" to text string
288 */
289 void spawnfunc_trigger_once()
290 {
291         self.wait = -1;
292         spawnfunc_trigger_multiple();
293 };
294
295 //=============================================================================
296
297 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
298 This fixed size trigger cannot be touched, it can only be fired by other events.  It can contain killtargets, targets, delays, and messages.
299 */
300 void spawnfunc_trigger_relay()
301 {
302         self.use = SUB_UseTargets;
303 };
304
305 void delay_use()
306 {
307     self.think = SUB_UseTargets;
308     self.nextthink = self.wait;
309 }
310
311 void spawnfunc_trigger_delay()
312 {
313     if(!self.wait)
314         self.wait = 1;
315
316     self.use = delay_use;
317 }
318
319 //=============================================================================
320
321
322 void counter_use()
323 {
324         self.count = self.count - 1;
325         if (self.count < 0)
326                 return;
327
328         if (self.count != 0)
329         {
330                 if (activator.classname == "player"
331                 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
332                 {
333                         if (self.count >= 4)
334                                 centerprint (activator, "There are more to go...");
335                         else if (self.count == 3)
336                                 centerprint (activator, "Only 3 more to go...");
337                         else if (self.count == 2)
338                                 centerprint (activator, "Only 2 more to go...");
339                         else
340                                 centerprint (activator, "Only 1 more to go...");
341                 }
342                 return;
343         }
344
345         if (activator.classname == "player"
346         && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
347                 centerprint(activator, "Sequence completed!");
348         self.enemy = activator;
349         multi_trigger ();
350 };
351
352 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
353 Acts as an intermediary for an action that takes multiple inputs.
354
355 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
356
357 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
358 */
359 void spawnfunc_trigger_counter()
360 {
361         self.wait = -1;
362         if (!self.count)
363                 self.count = 2;
364
365         self.use = counter_use;
366 };
367
368 .float triggerhurttime;
369 void trigger_hurt_touch()
370 {
371         // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
372         if (other.iscreature)
373         {
374                 if (other.takedamage)
375                 if (other.triggerhurttime < time)
376                 {
377                         EXACTTRIGGER_TOUCH;
378                         other.triggerhurttime = time + 1;
379                         Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
380                 }
381         }
382         else
383         {
384                 if (!other.owner)
385                 {
386                         if (other.items & IT_KEY1 || other.items & IT_KEY2)     // reset flag
387                         {
388                                 EXACTTRIGGER_TOUCH;
389                                 other.pain_finished = min(other.pain_finished, time + 2);
390                         }
391                         else if (other.classname == "rune")                     // reset runes
392                         {
393                                 EXACTTRIGGER_TOUCH;
394                                 other.nextthink = min(other.nextthink, time + 1);
395                         }
396                 }
397         }
398
399         return;
400 };
401
402 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
403 Any object touching this will be hurt
404 set dmg to damage amount
405 defalt dmg = 5
406 */
407 .entity trigger_hurt_next;
408 entity trigger_hurt_last;
409 entity trigger_hurt_first;
410 void spawnfunc_trigger_hurt()
411 {
412         EXACTTRIGGER_INIT;
413         self.touch = trigger_hurt_touch;
414         if (!self.dmg)
415                 self.dmg = 1000;
416         if (!self.message)
417                 self.message = "was in the wrong place";
418         if (!self.message2)
419                 self.message2 = "was thrown into a world of hurt by";
420
421         if(!trigger_hurt_first)
422                 trigger_hurt_first = self;
423         if(trigger_hurt_last)
424                 trigger_hurt_last.trigger_hurt_next = self;
425         trigger_hurt_last = self;
426 };
427
428 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
429 {
430         entity th;
431
432         for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
433                 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
434                         return TRUE;
435
436         return FALSE;
437 }
438
439 //////////////////////////////////////////////////////////////
440 //
441 //
442 //
443 //Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
444 //
445 //////////////////////////////////////////////////////////////
446
447 .float triggerhealtime;
448 void trigger_heal_touch()
449 {
450         // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
451         if (other.iscreature)
452         {
453                 if (other.takedamage)
454                 if (other.triggerhealtime < time)
455                 {
456                         EXACTTRIGGER_TOUCH;
457                         other.triggerhealtime = time + 1;
458                         
459                         if (other.health < self.max_health)
460                         {
461                                 other.health = min(other.health + self.health, self.max_health);
462                                 other.pauserothealth_finished = max(other.pauserothealth_finished, time + cvar("g_balance_pause_health_rot"));
463                                 sound (other, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
464                         }
465                 }
466         }
467 };
468
469 void spawnfunc_trigger_heal()
470 {
471         EXACTTRIGGER_INIT;
472         self.touch = trigger_heal_touch;
473         if (!self.health)
474                 self.health = 10;
475         if (!self.max_health)
476                 self.max_health = 200; //Max health topoff for field
477         if(self.noise == "")
478                 self.noise = "misc/mediumhealth.wav";
479         precache_sound(self.noise);
480 };
481
482
483 //////////////////////////////////////////////////////////////
484 //
485 //
486 //
487 //End trigger_heal
488 //
489 //////////////////////////////////////////////////////////////
490
491
492
493 // TODO add a way to do looped sounds with sound(); then complete this entity
494 .float volume, atten;
495 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
496
497 void spawnfunc_target_speaker()
498 {
499         if(self.noise)
500                 precache_sound (self.noise);
501         IFTARGETED
502         {
503                 if(!self.atten)
504                         self.atten = ATTN_NORM;
505                 else if(self.atten < 0)
506                         self.atten = 0;
507                 if(!self.volume)
508                         self.volume = 1;
509                 self.use = target_speaker_use;
510         }
511         else
512         {
513                 if(!self.atten)
514                         self.atten = ATTN_STATIC;
515                 else if(self.atten < 0)
516                         self.atten = 0;
517                 if(!self.volume)
518                         self.volume = 1;
519                 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
520         }
521 };
522
523
524 void spawnfunc_func_stardust() {
525         self.effects = EF_STARDUST;
526 }
527
528 float pointparticles_SendEntity(entity to, float fl)
529 {
530         WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
531         WriteByte(MSG_ENTITY, fl);
532         if(fl & 2)
533         {
534                 if(self.state)
535                         WriteCoord(MSG_ENTITY, self.impulse);
536                 else
537                         WriteCoord(MSG_ENTITY, 0); // off
538         }
539         if(fl & 4)
540         {
541                 WriteCoord(MSG_ENTITY, self.origin_x);
542                 WriteCoord(MSG_ENTITY, self.origin_y);
543                 WriteCoord(MSG_ENTITY, self.origin_z);
544         }
545         if(fl & 1)
546         {
547                 if(self.model != "null")
548                 {
549                         WriteShort(MSG_ENTITY, self.modelindex);
550                         WriteCoord(MSG_ENTITY, self.mins_x);
551                         WriteCoord(MSG_ENTITY, self.mins_y);
552                         WriteCoord(MSG_ENTITY, self.mins_z);
553                         WriteCoord(MSG_ENTITY, self.maxs_x);
554                         WriteCoord(MSG_ENTITY, self.maxs_y);
555                         WriteCoord(MSG_ENTITY, self.maxs_z);
556                 }
557                 else
558                 {
559                         WriteShort(MSG_ENTITY, 0);
560                         WriteCoord(MSG_ENTITY, self.maxs_x);
561                         WriteCoord(MSG_ENTITY, self.maxs_y);
562                         WriteCoord(MSG_ENTITY, self.maxs_z);
563                 }
564                 WriteShort(MSG_ENTITY, self.cnt);
565                 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
566                 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
567                 WriteCoord(MSG_ENTITY, self.waterlevel);
568                 WriteCoord(MSG_ENTITY, self.count);
569                 WriteByte(MSG_ENTITY, self.glow_color);
570                 WriteString(MSG_ENTITY, self.noise);
571         }
572         return 1;
573 }
574
575 void pointparticles_use()
576 {
577         self.state = !self.state;
578         self.SendFlags |= 2;
579 }
580
581 void pointparticles_think()
582 {
583         if(self.origin != self.oldorigin)
584         {
585                 self.SendFlags |= 4;
586                 self.oldorigin = self.origin;
587         }
588         self.nextthink = time;
589 }
590
591 void spawnfunc_func_pointparticles()
592 {
593         if(self.model != "")
594                 setmodel(self, self.model);
595         if(self.noise != "")
596                 precache_sound (self.noise);
597
598         self.effects = EF_NODEPTHTEST;
599         self.SendEntity = pointparticles_SendEntity;
600         self.SendFlags = 7;
601         if(!self.modelindex)
602         {
603                 vector misave, masave;
604                 misave = self.mins;
605                 masave = self.maxs;
606                 setmodel(self, "null");
607                 setorigin(self, self.origin + misave);
608                 setsize(self, '0 0 0', masave - misave);
609         }
610         if(!self.cnt)
611                 self.cnt = particleeffectnum(self.mdl);
612         IFTARGETED
613         {
614                 self.use = pointparticles_use;
615                 if(self.spawnflags & 1)
616                         self.state = 1;
617                 else
618                         self.state = 0;
619         }
620         else
621                 self.state = 1;
622         self.think = pointparticles_think;
623         self.nextthink = time;
624 }
625
626 void spawnfunc_func_sparks()
627 {
628         // self.cnt is the amount of sparks that one burst will spawn
629         if(self.cnt < 1) {
630                 self.cnt = 25.0; // nice default value
631         }
632
633         // self.wait is the probability that a sparkthink will spawn a spark shower
634         // range: 0 - 1, but 0 makes little sense, so...
635         if(self.wait < 0.05) {
636                 self.wait = 0.25; // nice default value
637         }
638
639         self.count = self.cnt;
640         self.mins = '0 0 0';
641         self.maxs = '0 0 0';
642         self.velocity = '0 0 -1';
643         self.mdl = "TE_SPARK";
644         self.impulse = 10 * self.wait; // by default 2.5/sec
645         self.wait = 0;
646         self.cnt = 0; // use mdl
647
648         spawnfunc_func_pointparticles();
649 }
650
651 float rainsnow_SendEntity(float to)
652 {
653         WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
654         WriteByte(MSG_ENTITY, self.state);
655         WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
656         WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
657         WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
658         WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
659         WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
660         WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
661         WriteShort(MSG_ENTITY, compressShortVector(self.dest));
662         WriteShort(MSG_ENTITY, self.count);
663         WriteByte(MSG_ENTITY, self.cnt);
664         return 1;
665 };
666
667 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
668 This is an invisible area like a trigger, which rain falls inside of.
669
670 Keys:
671 "velocity"
672  falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
673 "cnt"
674  sets color of rain (default 12 - white)
675 "count"
676  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
677 */
678 void spawnfunc_func_rain()
679 {
680         self.dest = self.velocity;
681         self.velocity = '0 0 0';
682         if (!self.dest)
683                 self.dest = '0 0 -700';
684         self.angles = '0 0 0';
685         self.movetype = MOVETYPE_NONE;
686         self.solid = SOLID_NOT;
687         SetBrushEntityModel();
688         if (!self.cnt)
689                 self.cnt = 12;
690         if (!self.count)
691                 self.count = 2000;
692         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
693         if (self.count < 1)
694                 self.count = 1;
695         if(self.count > 65535)
696                 self.count = 65535;
697
698         self.state = 1; // 1 is rain, 0 is snow
699         self.effects = EF_NODEPTHTEST;
700         self.SendEntity = rainsnow_SendEntity;
701         self.Version = 1;
702
703         if(!self.modelindex)
704         {
705                 vector misave, masave;
706                 misave = self.mins;
707                 masave = self.maxs;
708                 setmodel(self, "null");
709                 setsize(self, misave, masave);
710         }
711 };
712
713
714 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
715 This is an invisible area like a trigger, which snow falls inside of.
716
717 Keys:
718 "velocity"
719  falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
720 "cnt"
721  sets color of rain (default 12 - white)
722 "count"
723  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
724 */
725 void spawnfunc_func_snow()
726 {
727         self.dest = self.velocity;
728         self.velocity = '0 0 0';
729         if (!self.dest)
730                 self.dest = '0 0 -300';
731         self.angles = '0 0 0';
732         self.movetype = MOVETYPE_NONE;
733         self.solid = SOLID_NOT;
734         SetBrushEntityModel();
735         self.model = "";
736         if (!self.cnt)
737                 self.cnt = 12;
738         if (!self.count)
739                 self.count = 2000;
740         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
741         if (self.count < 1)
742                 self.count = 1;
743         if(self.count > 65535)
744                 self.count = 65535;
745
746         self.state = 0; // 1 is rain, 0 is snow
747         self.effects = EF_NODEPTHTEST;
748         self.SendEntity = rainsnow_SendEntity;
749         self.Version = 1;
750
751         if(!self.modelindex)
752         {
753                 vector misave, masave;
754                 misave = self.mins;
755                 masave = self.maxs;
756                 setmodel(self, "null");
757                 setsize(self, misave, masave);
758         }
759 };
760
761
762 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
763
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 & 0xC0); // use that bit to indicate finite length laser
879         if(self.spawnflags & 2)
880                 fl |= 0x80;
881         if(self.alpha)
882                 fl |= 0x40;
883         WriteByte(MSG_ENTITY, fl);
884         if(fl & 1)
885         {
886                 WriteCoord(MSG_ENTITY, self.origin_x);
887                 WriteCoord(MSG_ENTITY, self.origin_y);
888                 WriteCoord(MSG_ENTITY, self.origin_z);
889         }
890         if(fl & 8)
891         {
892                 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
893                 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
894                 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
895                 if(fl & 0x40)
896                         WriteByte(MSG_ENTITY, self.alpha * 255.0);
897                 WriteShort(MSG_ENTITY, self.cnt + 1);
898         }
899         if(fl & 2)
900         {
901                 if(fl & 0x80)
902                 {
903                         WriteCoord(MSG_ENTITY, self.enemy.origin_x);
904                         WriteCoord(MSG_ENTITY, self.enemy.origin_y);
905                         WriteCoord(MSG_ENTITY, self.enemy.origin_z);
906                 }
907                 else
908                 {
909                         WriteCoord(MSG_ENTITY, self.mangle_x);
910                         WriteCoord(MSG_ENTITY, self.mangle_y);
911                 }
912         }
913         if(fl & 4)
914                 WriteByte(MSG_ENTITY, self.state);
915         return 1;
916 }
917
918 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
919 Any object touching the beam will be hurt
920 Keys:
921 "target"
922  spawnfunc_target_position where the laser ends
923 "mdl"
924  name of beam end effect to use
925 "colormod"
926  color of the beam (default: red)
927 "dmg"
928  damage per second (-1 for a laser that kills immediately)
929 */
930 void laser_use()
931 {
932         self.state = !self.state;
933         self.SendFlags |= 4;
934         misc_laser_aim();
935 }
936
937 void spawnfunc_misc_laser()
938 {
939         if(self.mdl)
940         {
941                 if(self.mdl == "none")
942                         self.cnt = -1;
943                 else
944                 {
945                         self.cnt = particleeffectnum(self.mdl);
946                         if(self.cnt < 0)
947                                 if(self.dmg)
948                                         self.cnt = particleeffectnum("laser_deadly");
949                 }
950         }
951         else if(!self.cnt)
952         {
953                 if(self.dmg)
954                         self.cnt = particleeffectnum("laser_deadly");
955                 else
956                         self.cnt = -1;
957         }
958         if(self.cnt < 0)
959                 self.cnt = -1;
960
961         if(self.colormod == '0 0 0')
962                 if(!self.alpha)
963                         self.colormod = '1 0 0';
964         if(!self.message)
965                 self.message = "saw the light";
966         if (!self.message2)
967                 self.message2 = "was pushed into a laser by";
968         self.think = misc_laser_think;
969         self.nextthink = time;
970         InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
971
972         self.effects = EF_NODEPTHTEST;
973         self.SendEntity = laser_SendEntity;
974         self.SendFlags = 15;
975         setmodel(self, "null");
976         self.mangle = self.angles;
977
978         IFTARGETED
979         {
980                 self.use = laser_use;
981                 if(self.spawnflags & 1)
982                         self.state = 1;
983                 else
984                         self.state = 0;
985         }
986         else
987                 self.state = 1;
988 }
989
990 // tZorks trigger impulse / gravity
991 .float radius;
992 .float falloff;
993 .float strength;
994 .float lastpushtime;
995
996 // targeted (directional) mode
997 void trigger_impulse_touch1()
998 {
999         entity targ;
1000     float pushdeltatime;
1001     float str;
1002
1003         // FIXME: Better checking for what to push and not.
1004         if not(other.iscreature)
1005         if (other.classname != "corpse")
1006         if (other.classname != "body")
1007         if (other.classname != "gib")
1008         if (other.classname != "missile")
1009         if (other.classname != "rocket")
1010         if (other.classname != "casing")
1011         if (other.classname != "grenade")
1012         if (other.classname != "plasma")
1013         if (other.classname != "plasma_prim")
1014         if (other.classname != "plasma_chain")
1015         if (other.classname != "droppedweapon")
1016                 return;
1017
1018         if (other.deadflag && other.iscreature)
1019                 return;
1020
1021         EXACTTRIGGER_TOUCH;
1022
1023     targ = find(world, targetname, self.target);
1024     if(!targ)
1025     {
1026         objerror("trigger_force without a (valid) .target!\n");
1027         remove(self);
1028         return;
1029     }
1030
1031     if(self.falloff == 1)
1032         str = (str / self.radius) * self.strength;
1033     else if(self.falloff == 2)
1034         str = (1 - (str / self.radius)) * self.strength;
1035     else
1036         str = self.strength;
1037
1038     pushdeltatime = time - other.lastpushtime;
1039     if (pushdeltatime > 0.15) pushdeltatime = 0;
1040     other.lastpushtime = time;
1041     if(!pushdeltatime) return;
1042
1043     other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
1044 }
1045
1046 // Directionless (accelerator/decelerator) mode
1047 void trigger_impulse_touch2()
1048 {
1049     float pushdeltatime;
1050
1051         // FIXME: Better checking for what to push and not.
1052         if not(other.iscreature)
1053         if (other.classname != "corpse")
1054         if (other.classname != "body")
1055         if (other.classname != "gib")
1056         if (other.classname != "missile")
1057         if (other.classname != "rocket")
1058         if (other.classname != "casing")
1059         if (other.classname != "grenade")
1060         if (other.classname != "plasma")
1061         if (other.classname != "plasma_prim")
1062         if (other.classname != "plasma_chain")
1063         if (other.classname != "droppedweapon")
1064                 return;
1065
1066         if (other.deadflag && other.iscreature)
1067                 return;
1068
1069         EXACTTRIGGER_TOUCH;
1070
1071     pushdeltatime = time - other.lastpushtime;
1072     if (pushdeltatime > 0.15) pushdeltatime = 0;
1073     other.lastpushtime = time;
1074     if(!pushdeltatime) return;
1075
1076     //if(self.strength > 1)
1077         other.velocity = other.velocity * (self.strength * pushdeltatime);
1078     //else
1079     //    other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
1080 }
1081
1082 // Spherical (gravity/repulsor) mode
1083 void trigger_impulse_touch3()
1084 {
1085     float pushdeltatime;
1086     float str;
1087
1088         // FIXME: Better checking for what to push and not.
1089         if not(other.iscreature)
1090         if (other.classname != "corpse")
1091         if (other.classname != "body")
1092         if (other.classname != "gib")
1093         if (other.classname != "missile")
1094         if (other.classname != "rocket")
1095         if (other.classname != "casing")
1096         if (other.classname != "grenade")
1097         if (other.classname != "plasma")
1098         if (other.classname != "plasma_prim")
1099         if (other.classname != "plasma_chain")
1100         if (other.classname != "droppedweapon")
1101                 return;
1102
1103         if (other.deadflag && other.iscreature)
1104                 return;
1105
1106         EXACTTRIGGER_TOUCH;
1107
1108     pushdeltatime = time - other.lastpushtime;
1109     if (pushdeltatime > 0.15) pushdeltatime = 0;
1110     other.lastpushtime = time;
1111     if(!pushdeltatime) return;
1112
1113     setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1114
1115         str = min(self.radius, vlen(self.origin - other.origin));
1116
1117     if(self.falloff == 1)
1118         str = (1 - str / self.radius) * self.strength; // 1 in the inside
1119     else if(self.falloff == 2)
1120         str = (str / self.radius) * self.strength; // 0 in the inside
1121     else
1122         str = self.strength;
1123
1124     other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1125 }
1126
1127 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1128 -------- KEYS --------
1129 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1130          If not, this trigger acts like a damper/accelerator field.
1131
1132 strength : This is how mutch force to add in the direction of .target each second
1133            when .target is set. If not, this is hoe mutch to slow down/accelerate
1134            someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1135
1136 radius   : If set, act as a spherical device rather then a liniar one.
1137
1138 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1139
1140 -------- NOTES --------
1141 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1142 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1143 */
1144
1145 void spawnfunc_trigger_impulse()
1146 {
1147         EXACTTRIGGER_INIT;
1148     if(self.radius)
1149     {
1150         if(!self.strength) self.strength = 2000;
1151         setorigin(self, self.origin);
1152         setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1153         self.touch = trigger_impulse_touch3;
1154     }
1155     else
1156     {
1157         if(self.target)
1158         {
1159             if(!self.strength) self.strength = 950;
1160             self.touch = trigger_impulse_touch1;
1161         }
1162         else
1163         {
1164             if(!self.strength) self.strength = 0.9;
1165             self.touch = trigger_impulse_touch2;
1166         }
1167     }
1168 }
1169
1170 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1171 "Flip-flop" trigger gate... lets only every second trigger event through
1172 */
1173 void flipflop_use()
1174 {
1175         self.state = !self.state;
1176         if(self.state)
1177                 SUB_UseTargets();
1178 }
1179
1180 void spawnfunc_trigger_flipflop()
1181 {
1182         if(self.spawnflags & 1)
1183                 self.state = 1;
1184     self.use = flipflop_use;
1185 }
1186
1187 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1188 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1189 */
1190 void monoflop_use()
1191 {
1192         self.nextthink = time + self.wait;
1193         self.enemy = activator;
1194         if(self.state)
1195                 return;
1196         self.state = 1;
1197         SUB_UseTargets();
1198 }
1199 void monoflop_fixed_use()
1200 {
1201         if(self.state)
1202                 return;
1203         self.nextthink = time + self.wait;
1204         self.state = 1;
1205         self.enemy = activator;
1206         SUB_UseTargets();
1207 }
1208
1209 void monoflop_think()
1210 {
1211         self.state = 0;
1212         activator = self.enemy;
1213         SUB_UseTargets();
1214 }
1215
1216 void spawnfunc_trigger_monoflop()
1217 {
1218         if(!self.wait)
1219                 self.wait = 1;
1220         if(self.spawnflags & 1)
1221                 self.use = monoflop_fixed_use;
1222         else
1223                 self.use = monoflop_use;
1224         self.think = monoflop_think;
1225         self.state = 0;
1226 }
1227
1228 void multivibrator_send()
1229 {
1230         float newstate;
1231         float cyclestart;
1232
1233         cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1234
1235         newstate = (time < cyclestart + self.wait);
1236
1237         activator = self;
1238         if(self.state != newstate)
1239                 SUB_UseTargets();
1240         self.state = newstate;
1241
1242         if(self.state)
1243                 self.nextthink = cyclestart + self.wait + 0.01;
1244         else
1245                 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1246 }
1247
1248 void multivibrator_toggle()
1249 {
1250         if(self.nextthink == 0)
1251         {
1252                 multivibrator_send();
1253         }
1254         else
1255         {
1256                 if(self.state)
1257                 {
1258                         SUB_UseTargets();
1259                         self.state = 0;
1260                 }
1261                 self.nextthink = 0;
1262         }
1263 }
1264
1265 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1266 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1267 -------- KEYS --------
1268 target: trigger all entities with this targetname when it goes off
1269 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1270 phase: offset of the timing
1271 wait: "on" cycle time (default: 1)
1272 respawntime: "off" cycle time (default: same as wait)
1273 -------- SPAWNFLAGS --------
1274 START_ON: assume it is already turned on (when targeted)
1275 */
1276 void spawnfunc_trigger_multivibrator()
1277 {
1278         if(!self.wait)
1279                 self.wait = 1;
1280         if(!self.respawntime)
1281                 self.respawntime = self.wait;
1282
1283         self.state = 0;
1284         self.use = multivibrator_toggle;
1285         self.think = multivibrator_send;
1286         self.nextthink = time;
1287
1288         IFTARGETED
1289         {
1290                 if(!(self.spawnflags & 1))
1291                         self.nextthink = 0; // wait for a trigger event
1292         }
1293         else
1294                 self.nextthink = time;
1295 }
1296
1297
1298 void follow_init()
1299 {
1300         entity src, dst;
1301         src = find(world, targetname, self.killtarget);
1302         dst = find(world, targetname, self.target);
1303
1304         if(!src || !dst)
1305         {
1306                 objerror("follow: could not find target/killtarget");
1307                 return;
1308         }
1309
1310         dst.movetype = MOVETYPE_FOLLOW;
1311         dst.aiment = src;
1312         dst.punchangle = src.angles;
1313         dst.view_ofs = dst.origin - src.origin;
1314         dst.v_angle = dst.angles - src.angles;
1315
1316         remove(self);
1317 }
1318
1319 void spawnfunc_misc_follow()
1320 {
1321         InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1322 }
1323
1324
1325
1326
1327 .entity voicescript; // attached voice script
1328 .float voicescript_index; // index of next voice, or -1 to use the randomized ones
1329 .float voicescript_nextthink; // time to play next voice
1330 .float voicescript_voiceend; // time when this voice ends
1331
1332 void target_voicescript_clear(entity pl)
1333 {
1334         pl.voicescript = world;
1335 }
1336
1337 void target_voicescript_use()
1338 {
1339         if(activator.voicescript != self)
1340         {
1341                 activator.voicescript = self;
1342                 activator.voicescript_index = 0;
1343                 activator.voicescript_nextthink = time + self.delay;
1344         }
1345 }
1346
1347 void target_voicescript_next(entity pl)
1348 {
1349         entity vs;
1350         float i, n;
1351
1352         vs = pl.voicescript;
1353         if(!vs)
1354                 return;
1355         if(vs.message == "")
1356                 return;
1357         if(pl.classname != "player")
1358                 return;
1359         if(gameover)
1360                 return;
1361
1362         if(time >= pl.voicescript_voiceend)
1363         {
1364                 if(time >= pl.voicescript_nextthink)
1365                 {
1366                         // get the next voice...
1367                         n = tokenize_sane(vs.message);
1368
1369                         if(pl.voicescript_index < vs.cnt)
1370                                 i = pl.voicescript_index * 2;
1371                         else if(n > vs.cnt * 2)
1372                                 i = mod(pl.voicescript_index - vs.cnt, (n - vs.cnt * 2 - 1) / 2) * 2 + vs.cnt * 2 + 1;
1373                         else
1374                                 i = -1;
1375
1376                         if(i >= 0)
1377                         {
1378                                 play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
1379                                 pl.voicescript_voiceend = time + stof(argv(i + 1));
1380                         }
1381                         else
1382                                 pl.voicescript = world;
1383
1384                         pl.voicescript_index += 1;
1385                         pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
1386                 }
1387         }
1388 }
1389
1390 void spawnfunc_target_voicescript()
1391 {
1392         // netname: directory of the sound files
1393         // message: list of "sound file" duration "sound file" duration, a *, and again a list
1394         //          foo1 4.1 foo2 4.0 foo3 3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
1395         // wait: average time between messages
1396         // delay: initial delay before the first message
1397         
1398         float i, n;
1399         self.use = target_voicescript_use;
1400
1401         n = tokenize_sane(self.message);
1402         self.cnt = n / 2;
1403         for(i = 0; i+1 < n; i += 2)
1404         {
1405                 if(argv(i) == "*")
1406                 {
1407                         self.cnt = i / 2;
1408                         ++i;
1409                 }
1410                 precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
1411         }
1412 }