]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/g_triggers.qc
add field .nottargeted to override targetname checks (for misc_follow on anything)
[divverent/nexuiz.git] / data / qcsrc / server / g_triggers.qc
1
2 void() SUB_UseTargets;
3
4 void DelayThink()
5 {
6         activator = self.enemy;
7         SUB_UseTargets ();
8         remove(self);
9 };
10
11 /*
12 ==============================
13 SUB_UseTargets
14
15 the global "activator" should be set to the entity that initiated the firing.
16
17 If self.delay is set, a DelayedUse entity will be created that will actually
18 do the SUB_UseTargets after that many seconds have passed.
19
20 Centerprints any self.message to the activator.
21
22 Removes all entities with a targetname that match self.killtarget,
23 and removes them, so some events can remove other triggers.
24
25 Search for (string)targetname in all entities that
26 match (string)self.target and call their .use function
27
28 ==============================
29 */
30 void SUB_UseTargets()
31 {
32         local entity t, stemp, otemp, act;
33
34 //
35 // check for a delay
36 //
37         if (self.delay)
38         {
39         // create a temp object to fire at a later time
40                 t = spawn();
41                 t.classname = "DelayedUse";
42                 t.nextthink = time + self.delay;
43                 t.think = DelayThink;
44                 t.enemy = activator;
45                 t.message = self.message;
46                 t.killtarget = self.killtarget;
47                 t.target = self.target;
48                 return;
49         }
50
51
52 //
53 // print the message
54 //
55         if (activator.classname == "player" && self.message != "")
56         {
57                 centerprint (activator, self.message);
58                 if (!self.noise)
59                         play2(activator, "misc/talk.wav");
60         }
61
62 //
63 // kill the killtagets
64 //
65         if (self.killtarget)
66         {
67                 t = world;
68                 do
69                 {
70                         t = find (t, targetname, self.killtarget);
71                         if (!t)
72                                 return;
73                         remove (t);
74                 } while ( 1 );
75         }
76
77 //
78 // fire targets
79 //
80         if (self.target)
81         {
82                 act = activator;
83                 t = world;
84                 do
85                 {
86                         t = find (t, targetname, self.target);
87                         if (!t)
88                         {
89                                 return;
90                         }
91                         stemp = self;
92                         otemp = other;
93                         self = t;
94                         other = stemp;
95                         if (self.use)
96                                 self.use ();
97                         self = stemp;
98                         other = otemp;
99                         activator = act;
100                 } while ( 1 );
101         }
102
103
104 };
105
106
107 //=============================================================================
108
109 float   SPAWNFLAG_NOMESSAGE = 1;
110 float   SPAWNFLAG_NOTOUCH = 1;
111
112 // the wait time has passed, so set back up for another activation
113 void multi_wait()
114 {
115         if (self.max_health)
116         {
117                 self.health = self.max_health;
118                 self.takedamage = DAMAGE_YES;
119                 self.solid = SOLID_BBOX;
120         }
121 };
122
123
124 // the trigger was just touched/killed/used
125 // self.enemy should be set to the activator so it can be held through a delay
126 // so wait for the delay time before firing
127 void multi_trigger()
128 {
129         if (self.nextthink > time)
130         {
131                 return;         // allready been triggered
132         }
133
134         if (self.classname == "trigger_secret")
135         {
136                 if (self.enemy.classname != "player")
137                         return;
138                 found_secrets = found_secrets + 1;
139                 WriteByte (MSG_ALL, SVC_FOUNDSECRET);
140         }
141
142         if (self.noise)
143                 sound (self.enemy, CHAN_AUTO, self.noise, VOL_BASE, ATTN_NORM);
144
145 // don't trigger again until reset
146         self.takedamage = DAMAGE_NO;
147
148         activator = self.enemy;
149
150         SUB_UseTargets();
151
152         if (self.wait > 0)
153         {
154                 self.think = multi_wait;
155                 self.nextthink = time + self.wait;
156         }
157         else
158         {       // we can't just remove (self) here, because this is a touch function
159                 // called wheil C code is looping through area links...
160                 self.touch = SUB_Null;
161
162                 self.nextthink = time + 0.1;
163                 self.think = SUB_Remove;
164         }
165 };
166
167 void multi_use()
168 {
169         self.enemy = activator;
170         multi_trigger();
171 };
172
173 void multi_touch()
174 {
175         if (other.classname != "player")
176                 return;
177
178     if(self.team)
179     if(self.team == other.team)
180         return;
181
182 // if the trigger has an angles field, check player's facing direction
183         if (self.movedir != '0 0 0')
184         {
185                 makevectors (other.angles);
186                 if (v_forward * self.movedir < 0)
187                         return;         // not facing the right way
188         }
189
190         EXACTTRIGGER_TOUCH;
191
192         self.enemy = other;
193         multi_trigger ();
194 };
195
196 void multi_eventdamage (vector hitloc, float damage, entity inflictor, entity attacker, float deathtype)
197 {
198         if (!self.takedamage)
199                 return;
200         self.health = self.health - damage;
201         if (self.health <= 0)
202         {
203                 self.enemy = attacker;
204                 multi_trigger();
205         }
206 }
207
208 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
209 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.
210 If "delay" is set, the trigger waits some time after activating before firing.
211 "wait" : Seconds between triggerings. (.2 default)
212 If notouch is set, the trigger is only fired by other entities, not by touching.
213 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
214 sounds
215 1)      secret
216 2)      beep beep
217 3)      large switch
218 4)
219 set "message" to text string
220 */
221 void spawnfunc_trigger_multiple()
222 {
223         if (self.sounds == 1)
224         {
225                 precache_sound ("misc/secret.wav");
226                 self.noise = "misc/secret.wav";
227         }
228         else if (self.sounds == 2)
229         {
230                 precache_sound ("misc/talk.wav");
231                 self.noise = "misc/talk.wav";
232         }
233         else if (self.sounds == 3)
234         {
235                 precache_sound ("misc/trigger1.wav");
236                 self.noise = "misc/trigger1.wav";
237         }
238
239         if (!self.wait)
240                 self.wait = 0.2;
241         self.use = multi_use;
242
243         EXACTTRIGGER_INIT;
244
245         if (self.health)
246         {
247                 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
248                         objerror ("health and notouch don't make sense\n");
249                 self.max_health = self.health;
250                 self.event_damage = multi_eventdamage;
251                 self.takedamage = DAMAGE_YES;
252                 self.solid = SOLID_BBOX;
253                 setorigin (self, self.origin);  // make sure it links into the world
254         }
255         else
256         {
257                 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
258                 {
259                         self.touch = multi_touch;
260                 }
261         }
262 };
263
264
265 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
266 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
267 "targetname".  If "health" is set, the trigger must be killed to activate.
268 If notouch is set, the trigger is only fired by other entities, not by touching.
269 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
270 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.
271 sounds
272 1)      secret
273 2)      beep beep
274 3)      large switch
275 4)
276 set "message" to text string
277 */
278 void spawnfunc_trigger_once()
279 {
280         self.wait = -1;
281         spawnfunc_trigger_multiple();
282 };
283
284 //=============================================================================
285
286 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
287 This fixed size trigger cannot be touched, it can only be fired by other events.  It can contain killtargets, targets, delays, and messages.
288 */
289 void spawnfunc_trigger_relay()
290 {
291         self.use = SUB_UseTargets;
292 };
293
294 void delay_use()
295 {
296     self.think = SUB_UseTargets;
297     self.nextthink = self.wait;
298 }
299
300 void spawnfunc_trigger_delay()
301 {
302     if(!self.wait)
303         self.wait = 1;
304
305     self.use = delay_use;
306 }
307
308 //=============================================================================
309
310
311 void counter_use()
312 {
313         self.count = self.count - 1;
314         if (self.count < 0)
315                 return;
316
317         if (self.count != 0)
318         {
319                 if (activator.classname == "player"
320                 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
321                 {
322                         if (self.count >= 4)
323                                 centerprint (activator, "There are more to go...");
324                         else if (self.count == 3)
325                                 centerprint (activator, "Only 3 more to go...");
326                         else if (self.count == 2)
327                                 centerprint (activator, "Only 2 more to go...");
328                         else
329                                 centerprint (activator, "Only 1 more to go...");
330                 }
331                 return;
332         }
333
334         if (activator.classname == "player"
335         && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
336                 centerprint(activator, "Sequence completed!");
337         self.enemy = activator;
338         multi_trigger ();
339 };
340
341 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
342 Acts as an intermediary for an action that takes multiple inputs.
343
344 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
345
346 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
347 */
348 void spawnfunc_trigger_counter()
349 {
350         self.wait = -1;
351         if (!self.count)
352                 self.count = 2;
353
354         self.use = counter_use;
355 };
356
357 .float triggerhurttime;
358 void trigger_hurt_touch()
359 {
360         // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
361         if (!other.owner)
362         {
363                 if (other.items & IT_KEY1 || other.items & IT_KEY2)     // reset flag
364                 {
365                         EXACTTRIGGER_TOUCH;
366                         other.pain_finished = min(other.pain_finished, time + 2);
367                 }
368                 else if (other.classname == "rune")                     // reset runes
369                 {
370                         EXACTTRIGGER_TOUCH;
371                         other.nextthink = min(other.nextthink, time + 1);
372                 }
373         }
374
375         if (other.takedamage)
376         if (other.triggerhurttime < time)
377         {
378                 EXACTTRIGGER_TOUCH;
379                 other.triggerhurttime = time + 1;
380                 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
381         }
382
383         return;
384 };
385
386 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
387 Any object touching this will be hurt
388 set dmg to damage amount
389 defalt dmg = 5
390 */
391 .entity trigger_hurt_next;
392 entity trigger_hurt_last;
393 entity trigger_hurt_first;
394 void spawnfunc_trigger_hurt()
395 {
396         EXACTTRIGGER_INIT;
397         self.touch = trigger_hurt_touch;
398         if (!self.dmg)
399                 self.dmg = 1000;
400         if (!self.message)
401                 self.message = "was in the wrong place.";
402
403         if(!trigger_hurt_first)
404                 trigger_hurt_first = self;
405         if(trigger_hurt_last)
406                 trigger_hurt_last.trigger_hurt_next = self;
407         trigger_hurt_last = self;
408 };
409
410 float trace_hits_box_a0, trace_hits_box_a1;
411
412 float trace_hits_box_1d(float end, float thmi, float thma)
413 {
414         if(end == 0)
415         {
416                 // just check if x is in range
417                 if(0 < thmi)
418                         return FALSE;
419                 if(0 > thma)
420                         return FALSE;
421         }
422         else
423         {
424                 // do the trace with respect to x
425                 // 0 -> end has to stay in thmi -> thma
426                 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
427                 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
428                 if(trace_hits_box_a0 > trace_hits_box_a1)
429                         return FALSE;
430         }
431         return TRUE;
432 }
433
434 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
435 {
436         end -= start;
437         thmi -= start;
438         thma -= start;
439         // now it is a trace from 0 to end
440
441         trace_hits_box_a0 = 0;
442         trace_hits_box_a1 = 1;
443
444         if(!trace_hits_box_1d(end_x, thmi_x, thma_x))
445                 return FALSE;
446         if(!trace_hits_box_1d(end_y, thmi_y, thma_y))
447                 return FALSE;
448         if(!trace_hits_box_1d(end_z, thmi_z, thma_z))
449                 return FALSE;
450
451         return TRUE;
452 }
453
454 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
455 {
456         return trace_hits_box(start, end, thmi - ma, thma - mi);
457 }
458
459 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
460 {
461         entity th;
462
463         for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
464                 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
465                         return TRUE;
466
467         return FALSE;
468 }
469
470
471 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);}
472
473 void spawnfunc_target_speaker()
474 {
475         if(self.noise)
476                 precache_sound (self.noise);
477         IFTARGETED
478                 self.use = target_speaker_use;
479         else
480                 ambientsound (self.origin, self.noise, VOL_BASE, ATTN_STATIC);
481 };
482
483
484 void spawnfunc_func_stardust() {
485         self.effects = EF_STARDUST;
486 }
487
488 float pointparticles_SendEntity(entity to, float fl)
489 {
490         WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
491         WriteByte(MSG_ENTITY, fl);
492         if(fl & 2)
493         {
494                 if(self.state)
495                         WriteCoord(MSG_ENTITY, self.impulse);
496                 else
497                         WriteCoord(MSG_ENTITY, 0); // off
498         }
499         if(fl & 4)
500         {
501                 WriteCoord(MSG_ENTITY, self.origin_x);
502                 WriteCoord(MSG_ENTITY, self.origin_y);
503                 WriteCoord(MSG_ENTITY, self.origin_z);
504         }
505         if(fl & 1)
506         {
507                 if(self.modelindex != 4.2)
508                 {
509                         WriteShort(MSG_ENTITY, self.modelindex);
510                         WriteCoord(MSG_ENTITY, self.mins_x);
511                         WriteCoord(MSG_ENTITY, self.mins_y);
512                         WriteCoord(MSG_ENTITY, self.mins_z);
513                         WriteCoord(MSG_ENTITY, self.maxs_x);
514                         WriteCoord(MSG_ENTITY, self.maxs_y);
515                         WriteCoord(MSG_ENTITY, self.maxs_z);
516                 }
517                 else
518                 {
519                         WriteShort(MSG_ENTITY, 0);
520                         WriteCoord(MSG_ENTITY, self.maxs_x);
521                         WriteCoord(MSG_ENTITY, self.maxs_y);
522                         WriteCoord(MSG_ENTITY, self.maxs_z);
523                 }
524                 WriteShort(MSG_ENTITY, self.cnt);
525                 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
526                 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
527                 WriteCoord(MSG_ENTITY, self.waterlevel);
528                 WriteCoord(MSG_ENTITY, self.count);
529                 WriteByte(MSG_ENTITY, self.glow_color);
530                 WriteString(MSG_ENTITY, self.noise);
531         }
532         return 1;
533 }
534
535 void pointparticles_use()
536 {
537         self.state = !self.state;
538         self.SendFlags |= 2;
539 }
540
541 void pointparticles_think()
542 {
543         if(self.origin != self.oldorigin)
544         {
545                 self.SendFlags |= 4;
546                 self.oldorigin = self.origin;
547         }
548         self.nextthink = time;
549 }
550
551 void spawnfunc_func_pointparticles()
552 {
553         if(self.model != "")
554                 setmodel(self, self.model);
555         if(self.noise != "")
556                 precache_sound (self.noise);
557
558         self.effects = EF_NODEPTHTEST;
559         self.SendEntity = pointparticles_SendEntity;
560         self.SendFlags = 7;
561         if(!self.modelindex)
562         {
563                 self.modelindex = 4.2;
564                 self.origin += self.mins;
565                 self.maxs -= self.mins;
566         }
567         self.model = "net_entity";
568         if(!self.cnt)
569                 self.cnt = particleeffectnum(self.mdl);
570         IFTARGETED
571         {
572                 self.use = pointparticles_use;
573                 if(self.spawnflags & 1)
574                         self.state = 1;
575                 else
576                         self.state = 0;
577         }
578         else
579                 self.state = 1;
580         self.think = pointparticles_think;
581         self.nextthink = time;
582 }
583
584 void spawnfunc_func_sparks()
585 {
586         // self.cnt is the amount of sparks that one burst will spawn
587         if(self.cnt < 1) {
588                 self.cnt = 25.0; // nice default value
589         }
590
591         // self.wait is the probability that a sparkthink will spawn a spark shower
592         // range: 0 - 1, but 0 makes little sense, so...
593         if(self.wait < 0.05) {
594                 self.wait = 0.25; // nice default value
595         }
596
597         self.count = self.cnt;
598         self.mins = '0 0 0';
599         self.maxs = '0 0 0';
600         self.velocity = '0 0 -1';
601         self.mdl = "TE_SPARK";
602         self.impulse = 0.1 / self.wait;
603         self.wait = 0;
604
605         spawnfunc_func_pointparticles();
606 }
607
608 float rainsnow_SendEntity(float to)
609 {
610         WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
611         WriteByte(MSG_ENTITY, self.state);
612         WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
613         WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
614         WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
615         WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
616         WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
617         WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
618         WriteShort(MSG_ENTITY, compressShortVector(self.dest));
619         WriteShort(MSG_ENTITY, self.count);
620         WriteByte(MSG_ENTITY, self.cnt);
621         return 1;
622 };
623
624 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
625 This is an invisible area like a trigger, which rain falls inside of.
626
627 Keys:
628 "velocity"
629  falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
630 "cnt"
631  sets color of rain (default 12 - white)
632 "count"
633  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
634 */
635 void spawnfunc_func_rain()
636 {
637         self.dest = self.velocity;
638         self.velocity = '0 0 0';
639         if (!self.dest)
640                 self.dest = '0 0 -700';
641         self.angles = '0 0 0';
642         self.movetype = MOVETYPE_NONE;
643         self.solid = SOLID_NOT;
644         SetBrushEntityModel();
645         self.model = "";
646         if (!self.cnt)
647                 self.cnt = 12;
648         if (!self.count)
649                 self.count = 2000;
650         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
651         if (self.count < 1)
652                 self.count = 1;
653         if(self.count > 65535)
654                 self.count = 65535;
655
656         self.state = 1; // 1 is rain, 0 is snow
657         self.effects = EF_NODEPTHTEST;
658         self.SendEntity = rainsnow_SendEntity;
659         self.Version = 1;
660         self.modelindex = 1;
661         self.model = "net_entity";
662 };
663
664
665 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
666 This is an invisible area like a trigger, which snow falls inside of.
667
668 Keys:
669 "velocity"
670  falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
671 "cnt"
672  sets color of rain (default 12 - white)
673 "count"
674  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
675 */
676 void spawnfunc_func_snow()
677 {
678         self.dest = self.velocity;
679         self.velocity = '0 0 0';
680         if (!self.dest)
681                 self.dest = '0 0 -300';
682         self.angles = '0 0 0';
683         self.movetype = MOVETYPE_NONE;
684         self.solid = SOLID_NOT;
685         SetBrushEntityModel();
686         self.model = "";
687         if (!self.cnt)
688                 self.cnt = 12;
689         if (!self.count)
690                 self.count = 2000;
691         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
692         if (self.count < 1)
693                 self.count = 1;
694         if(self.count > 65535)
695                 self.count = 65535;
696
697         self.state = 0; // 1 is rain, 0 is snow
698         self.effects = EF_NODEPTHTEST;
699         self.SendEntity = rainsnow_SendEntity;
700         self.Version = 1;
701         self.modelindex = 1;
702         self.model = "net_entity";
703 };
704
705
706 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
707
708 void misc_laser_aim()
709 {
710         vector a;
711         if(self.enemy)
712         {
713                 if(self.spawnflags & 2)
714                 {
715                         if(self.enemy.origin != self.mangle)
716                         {
717                                 self.mangle = self.enemy.origin;
718                                 self.SendFlags |= 2;
719                         }
720                 }
721                 else
722                 {
723                         a = vectoangles(self.enemy.origin - self.origin);
724                         a_x = -a_x;
725                         if(a != self.mangle)
726                         {
727                                 self.mangle = a;
728                                 self.SendFlags |= 2;
729                         }
730                 }
731         }
732         if(self.origin != self.oldorigin)
733         {
734                 self.SendFlags |= 1;
735                 self.oldorigin = self.origin;
736         }
737 }
738
739 void misc_laser_init()
740 {
741         self.enemy = find(world, targetname, self.target);
742 }
743
744 void misc_laser_think()
745 {
746         vector o;
747
748         self.nextthink = time;
749
750         if(!self.state)
751                 return;
752
753         misc_laser_aim();
754
755         o = self.enemy.origin;
756         if not(self.spawnflags & 2)
757                 o = self.origin + normalize(o - self.origin) * 32768;
758
759         if(self.dmg)
760         {
761                 if(self.dmg < 0)
762                         FireRailgunBullet(self.origin, o, 100000, 0, DEATH_HURTTRIGGER);
763                 else
764                         FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, DEATH_HURTTRIGGER);
765         }
766 }
767
768 float laser_SendEntity(entity to, float fl)
769 {
770         WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
771         fl = fl - (fl & 0xC0); // use that bit to indicate finite length laser
772         if(self.spawnflags & 2)
773                 fl |= 0x80;
774         if(self.alpha)
775                 fl |= 0x40;
776         WriteByte(MSG_ENTITY, fl);
777         if(fl & 1)
778         {
779                 WriteCoord(MSG_ENTITY, self.origin_x);
780                 WriteCoord(MSG_ENTITY, self.origin_y);
781                 WriteCoord(MSG_ENTITY, self.origin_z);
782         }
783         if(fl & 8)
784         {
785                 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
786                 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
787                 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
788                 if(fl & 0x40)
789                         WriteByte(MSG_ENTITY, self.alpha * 255.0);
790                 WriteShort(MSG_ENTITY, self.cnt);
791         }
792         if(fl & 2)
793         {
794                 if(fl & 0x80)
795                 {
796                         WriteCoord(MSG_ENTITY, self.enemy.origin_x);
797                         WriteCoord(MSG_ENTITY, self.enemy.origin_y);
798                         WriteCoord(MSG_ENTITY, self.enemy.origin_z);
799                 }
800                 else
801                 {
802                         WriteCoord(MSG_ENTITY, self.mangle_x);
803                         WriteCoord(MSG_ENTITY, self.mangle_y);
804                 }
805         }
806         if(fl & 4)
807                 WriteByte(MSG_ENTITY, self.state);
808         return 1;
809 }
810
811 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
812 Any object touching the beam will be hurt
813 Keys:
814 "target"
815  spawnfunc_target_position where the laser ends
816 "mdl"
817  name of beam end effect to use
818 "colormod"
819  color of the beam (default: red)
820 "dmg"
821  damage per second (-1 for a laser that kills immediately)
822 */
823 void laser_use()
824 {
825         self.state = !self.state;
826         self.SendFlags |= 4;
827         misc_laser_aim();
828 }
829
830 void spawnfunc_misc_laser()
831 {
832         if(self.mdl)
833         {
834                 self.cnt = particleeffectnum(strcat(self.mdl, "_end"));
835                 if(self.cnt < 0)
836                         self.cnt = particleeffectnum(self.mdl);
837         }
838         else
839         {
840                 self.cnt = particleeffectnum("misc_laser_beam_end");
841         }
842         if(self.colormod == '0 0 0')
843                 if(!self.alpha)
844                         self.colormod = '1 0 0';
845         if(!self.message)
846                 self.message = "saw the light";
847         self.think = misc_laser_think;
848         self.nextthink = time;
849         InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
850
851         self.effects = EF_NODEPTHTEST;
852         self.SendEntity = laser_SendEntity;
853         self.SendFlags = 15;
854         self.modelindex = 1;
855         self.model = "net_entity";
856
857         IFTARGETED
858         {
859                 self.use = laser_use;
860                 if(self.spawnflags & 1)
861                         self.state = 1;
862                 else
863                         self.state = 0;
864         }
865         else
866                 self.state = 1;
867 }
868
869 // tZorks trigger impulse / gravity
870 .float radius;
871 .float falloff;
872 .float strength;
873 .float lastpushtime;
874
875 // targeted (directional) mode
876 void trigger_impulse_touch1()
877 {
878         entity targ;
879     float pushdeltatime;
880     float str;
881
882         // FIXME: Better checking for what to push and not.
883         if (other.classname != "player")
884         if (other.classname != "corpse")
885         if (other.classname != "body")
886         if (other.classname != "gib")
887         if (other.classname != "missile")
888         if (other.classname != "casing")
889         if (other.classname != "grenade")
890         if (other.classname != "plasma")
891         if (other.classname != "plasma_prim")
892         if (other.classname != "plasma_chain")
893         if (other.classname != "droppedweapon")
894                 return;
895
896         if (other.deadflag && other.classname == "player")
897                 return;
898
899         EXACTTRIGGER_TOUCH;
900
901     targ = find(world, targetname, self.target);
902     if(!targ)
903     {
904         objerror("trigger_force without a (valid) .target!\n");
905         remove(self);
906         return;
907     }
908
909     if(self.falloff == 1)
910         str = (str / self.radius) * self.strength;
911     else if(self.falloff == 2)
912         str = (1 - (str / self.radius)) * self.strength;
913     else
914         str = self.strength;
915
916     pushdeltatime = time - other.lastpushtime;
917     if (pushdeltatime > 0.15) pushdeltatime = 0;
918     other.lastpushtime = time;
919     if(!pushdeltatime) return;
920
921     other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
922 }
923
924 // Directionless (accelerator/decelerator) mode
925 void trigger_impulse_touch2()
926 {
927     float pushdeltatime;
928
929         // FIXME: Better checking for what to push and not.
930         if (other.classname != "player")
931         if (other.classname != "corpse")
932         if (other.classname != "body")
933         if (other.classname != "gib")
934         if (other.classname != "missile")
935         if (other.classname != "casing")
936         if (other.classname != "grenade")
937         if (other.classname != "plasma")
938         if (other.classname != "plasma_prim")
939         if (other.classname != "plasma_chain")
940         if (other.classname != "droppedweapon")
941                 return;
942
943         if (other.deadflag && other.classname == "player")
944                 return;
945
946         EXACTTRIGGER_TOUCH;
947
948     pushdeltatime = time - other.lastpushtime;
949     if (pushdeltatime > 0.15) pushdeltatime = 0;
950     other.lastpushtime = time;
951     if(!pushdeltatime) return;
952
953     //if(self.strength > 1)
954         other.velocity = other.velocity * (self.strength * pushdeltatime);
955     //else
956     //    other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
957 }
958
959 // Spherical (gravity/repulsor) mode
960 void trigger_impulse_touch3()
961 {
962     float pushdeltatime;
963     float str;
964
965         // FIXME: Better checking for what to push and not.
966         if (other.classname != "player")
967         if (other.classname != "corpse")
968         if (other.classname != "body")
969         if (other.classname != "gib")
970         if (other.classname != "missile")
971         if (other.classname != "casing")
972         if (other.classname != "grenade")
973         if (other.classname != "plasma")
974         if (other.classname != "plasma_prim")
975         if (other.classname != "plasma_chain")
976         if (other.classname != "droppedweapon")
977                 return;
978
979         if (other.deadflag && other.classname == "player")
980                 return;
981
982         EXACTTRIGGER_TOUCH;
983
984     pushdeltatime = time - other.lastpushtime;
985     if (pushdeltatime > 0.15) pushdeltatime = 0;
986     other.lastpushtime = time;
987     if(!pushdeltatime) return;
988
989     setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
990
991         str = min(self.radius, vlen(self.origin - other.origin));
992
993     if(self.falloff == 1)
994         str = (1 - str / self.radius) * self.strength; // 1 in the inside
995     else if(self.falloff == 2)
996         str = (str / self.radius) * self.strength; // 0 in the inside
997     else
998         str = self.strength;
999
1000     other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1001 }
1002
1003 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1004 -------- KEYS --------
1005 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1006          If not, this trigger acts like a damper/accelerator field.
1007
1008 strength : This is how mutch force to add in the direction of .target each second
1009            when .target is set. If not, this is hoe mutch to slow down/accelerate
1010            someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1011
1012 radius   : If set, act as a spherical device rather then a liniar one.
1013
1014 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1015
1016 -------- NOTES --------
1017 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1018 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1019 */
1020
1021 void spawnfunc_trigger_impulse()
1022 {
1023         EXACTTRIGGER_INIT;
1024     if(self.radius)
1025     {
1026         if(!self.strength) self.strength = 2000;
1027         setorigin(self, self.origin);
1028         setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1029         self.touch = trigger_impulse_touch3;
1030     }
1031     else
1032     {
1033         if(self.target)
1034         {
1035             if(!self.strength) self.strength = 950;
1036             self.touch = trigger_impulse_touch1;
1037         }
1038         else
1039         {
1040             if(!self.strength) self.strength = 0.9;
1041             self.touch = trigger_impulse_touch2;
1042         }
1043     }
1044 }
1045
1046 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1047 "Flip-flop" trigger gate... lets only every second trigger event through
1048 */
1049 void flipflop_use()
1050 {
1051         self.state = !self.state;
1052         if(self.state)
1053                 SUB_UseTargets();
1054 }
1055
1056 void spawnfunc_trigger_flipflop()
1057 {
1058         if(self.spawnflags & 1)
1059                 self.state = 1;
1060     self.use = flipflop_use;
1061 }
1062
1063 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1064 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1065 */
1066 void monoflop_use()
1067 {
1068         self.nextthink = time + self.wait;
1069         if(self.state)
1070                 return;
1071         self.state = 1;
1072         SUB_UseTargets();
1073 }
1074 void monoflop_fixed_use()
1075 {
1076         if(self.state)
1077                 return;
1078         self.nextthink = time + self.wait;
1079         self.state = 1;
1080         SUB_UseTargets();
1081 }
1082
1083 void monoflop_think()
1084 {
1085         self.state = 0;
1086         SUB_UseTargets();
1087 }
1088
1089 void spawnfunc_trigger_monoflop()
1090 {
1091         if(!self.wait)
1092                 self.wait = 1;
1093         if(self.spawnflags & 1)
1094                 self.use = monoflop_fixed_use;
1095         else
1096                 self.use = monoflop_use;
1097         self.think = monoflop_think;
1098         self.state = 0;
1099 }
1100
1101 void follow_init()
1102 {
1103         entity src, dst;
1104         src = find(world, targetname, self.killtarget);
1105         dst = find(world, targetname, self.target);
1106
1107         if(!src || !dst)
1108         {
1109                 objerror("follow: could not find target/killtarget");
1110                 return;
1111         }
1112
1113         dst.movetype = MOVETYPE_FOLLOW;
1114         dst.aiment = src;
1115         dst.punchangle = src.angles;
1116         dst.view_ofs = dst.origin - src.origin;
1117         dst.v_angle = dst.angles - src.angles;
1118
1119         remove(self);
1120 }
1121
1122 void spawnfunc_misc_follow()
1123 {
1124         InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1125 }