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