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