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