]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/g_triggers.qc
support for announcer/male/impressive and announcer/male/headshot ;)
[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 not(self.spawnflags & 2)
176         {
177                 if (other.classname != "player")
178                         return;
179
180                 if(self.team)
181                 if(self.team == other.team)
182                         return;
183         }
184
185 // if the trigger has an angles field, check player's facing direction
186         if (self.movedir != '0 0 0')
187         {
188                 makevectors (other.angles);
189                 if (v_forward * self.movedir < 0)
190                         return;         // not facing the right way
191         }
192
193         EXACTTRIGGER_TOUCH;
194
195         self.enemy = other;
196         multi_trigger ();
197 };
198
199 void multi_eventdamage (vector hitloc, float damage, entity inflictor, entity attacker, float deathtype)
200 {
201         if (!self.takedamage)
202                 return;
203         self.health = self.health - damage;
204         if (self.health <= 0)
205         {
206                 self.enemy = attacker;
207                 multi_trigger();
208         }
209 }
210
211 /*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
212 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.
213 If "delay" is set, the trigger waits some time after activating before firing.
214 "wait" : Seconds between triggerings. (.2 default)
215 If notouch is set, the trigger is only fired by other entities, not by touching.
216 NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
217 sounds
218 1)      secret
219 2)      beep beep
220 3)      large switch
221 4)
222 set "message" to text string
223 */
224 void spawnfunc_trigger_multiple()
225 {
226         if (self.sounds == 1)
227         {
228                 precache_sound ("misc/secret.wav");
229                 self.noise = "misc/secret.wav";
230         }
231         else if (self.sounds == 2)
232         {
233                 precache_sound ("misc/talk.wav");
234                 self.noise = "misc/talk.wav";
235         }
236         else if (self.sounds == 3)
237         {
238                 precache_sound ("misc/trigger1.wav");
239                 self.noise = "misc/trigger1.wav";
240         }
241
242         if (!self.wait)
243                 self.wait = 0.2;
244         self.use = multi_use;
245
246         EXACTTRIGGER_INIT;
247
248         if (self.health)
249         {
250                 if (self.spawnflags & SPAWNFLAG_NOTOUCH)
251                         objerror ("health and notouch don't make sense\n");
252                 self.max_health = self.health;
253                 self.event_damage = multi_eventdamage;
254                 self.takedamage = DAMAGE_YES;
255                 self.solid = SOLID_BBOX;
256                 setorigin (self, self.origin);  // make sure it links into the world
257         }
258         else
259         {
260                 if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
261                 {
262                         self.touch = multi_touch;
263                         setorigin (self, self.origin);  // make sure it links into the world
264                 }
265         }
266 };
267
268
269 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
270 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
271 "targetname".  If "health" is set, the trigger must be killed to activate.
272 If notouch is set, the trigger is only fired by other entities, not by touching.
273 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
274 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.
275 sounds
276 1)      secret
277 2)      beep beep
278 3)      large switch
279 4)
280 set "message" to text string
281 */
282 void spawnfunc_trigger_once()
283 {
284         self.wait = -1;
285         spawnfunc_trigger_multiple();
286 };
287
288 //=============================================================================
289
290 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
291 This fixed size trigger cannot be touched, it can only be fired by other events.  It can contain killtargets, targets, delays, and messages.
292 */
293 void spawnfunc_trigger_relay()
294 {
295         self.use = SUB_UseTargets;
296 };
297
298 void delay_use()
299 {
300     self.think = SUB_UseTargets;
301     self.nextthink = self.wait;
302 }
303
304 void spawnfunc_trigger_delay()
305 {
306     if(!self.wait)
307         self.wait = 1;
308
309     self.use = delay_use;
310 }
311
312 //=============================================================================
313
314
315 void counter_use()
316 {
317         self.count = self.count - 1;
318         if (self.count < 0)
319                 return;
320
321         if (self.count != 0)
322         {
323                 if (activator.classname == "player"
324                 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
325                 {
326                         if (self.count >= 4)
327                                 centerprint (activator, "There are more to go...");
328                         else if (self.count == 3)
329                                 centerprint (activator, "Only 3 more to go...");
330                         else if (self.count == 2)
331                                 centerprint (activator, "Only 2 more to go...");
332                         else
333                                 centerprint (activator, "Only 1 more to go...");
334                 }
335                 return;
336         }
337
338         if (activator.classname == "player"
339         && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
340                 centerprint(activator, "Sequence completed!");
341         self.enemy = activator;
342         multi_trigger ();
343 };
344
345 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
346 Acts as an intermediary for an action that takes multiple inputs.
347
348 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
349
350 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
351 */
352 void spawnfunc_trigger_counter()
353 {
354         self.wait = -1;
355         if (!self.count)
356                 self.count = 2;
357
358         self.use = counter_use;
359 };
360
361 .float triggerhurttime;
362 void trigger_hurt_touch()
363 {
364         // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
365         if (!other.owner)
366         {
367                 if (other.items & IT_KEY1 || other.items & IT_KEY2)     // reset flag
368                 {
369                         EXACTTRIGGER_TOUCH;
370                         other.pain_finished = min(other.pain_finished, time + 2);
371                 }
372                 else if (other.classname == "rune")                     // reset runes
373                 {
374                         EXACTTRIGGER_TOUCH;
375                         other.nextthink = min(other.nextthink, time + 1);
376                 }
377         }
378
379         if (other.takedamage)
380         if (other.triggerhurttime < time)
381         {
382                 EXACTTRIGGER_TOUCH;
383                 other.triggerhurttime = time + 1;
384                 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
385         }
386
387         return;
388 };
389
390 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
391 Any object touching this will be hurt
392 set dmg to damage amount
393 defalt dmg = 5
394 */
395 .entity trigger_hurt_next;
396 entity trigger_hurt_last;
397 entity trigger_hurt_first;
398 void spawnfunc_trigger_hurt()
399 {
400         EXACTTRIGGER_INIT;
401         self.touch = trigger_hurt_touch;
402         if (!self.dmg)
403                 self.dmg = 1000;
404         if (!self.message)
405                 self.message = "was in the wrong place";
406         if (!self.message2)
407                 self.message2 = "was thrown into a world of hurt by";
408
409         if(!trigger_hurt_first)
410                 trigger_hurt_first = self;
411         if(trigger_hurt_last)
412                 trigger_hurt_last.trigger_hurt_next = self;
413         trigger_hurt_last = self;
414 };
415
416 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
417 {
418         entity th;
419
420         for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
421                 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
422                         return TRUE;
423
424         return FALSE;
425 }
426
427
428 // TODO add a way to do looped sounds with sound(); then complete this entity
429 .float volume, atten;
430 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
431
432 void spawnfunc_target_speaker()
433 {
434         if(self.noise)
435                 precache_sound (self.noise);
436         IFTARGETED
437         {
438                 if(!self.atten)
439                         self.atten = ATTN_NORM;
440                 else if(self.atten < 0)
441                         self.atten = 0;
442                 if(!self.volume)
443                         self.volume = 1;
444                 self.use = target_speaker_use;
445         }
446         else
447         {
448                 if(!self.atten)
449                         self.atten = ATTN_STATIC;
450                 else if(self.atten < 0)
451                         self.atten = 0;
452                 if(!self.volume)
453                         self.volume = 1;
454                 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
455         }
456 };
457
458
459 void spawnfunc_func_stardust() {
460         self.effects = EF_STARDUST;
461 }
462
463 float pointparticles_SendEntity(entity to, float fl)
464 {
465         WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
466         WriteByte(MSG_ENTITY, fl);
467         if(fl & 2)
468         {
469                 if(self.state)
470                         WriteCoord(MSG_ENTITY, self.impulse);
471                 else
472                         WriteCoord(MSG_ENTITY, 0); // off
473         }
474         if(fl & 4)
475         {
476                 WriteCoord(MSG_ENTITY, self.origin_x);
477                 WriteCoord(MSG_ENTITY, self.origin_y);
478                 WriteCoord(MSG_ENTITY, self.origin_z);
479         }
480         if(fl & 1)
481         {
482                 if(self.modelindex != 4.2)
483                 {
484                         WriteShort(MSG_ENTITY, self.modelindex);
485                         WriteCoord(MSG_ENTITY, self.mins_x);
486                         WriteCoord(MSG_ENTITY, self.mins_y);
487                         WriteCoord(MSG_ENTITY, self.mins_z);
488                         WriteCoord(MSG_ENTITY, self.maxs_x);
489                         WriteCoord(MSG_ENTITY, self.maxs_y);
490                         WriteCoord(MSG_ENTITY, self.maxs_z);
491                 }
492                 else
493                 {
494                         WriteShort(MSG_ENTITY, 0);
495                         WriteCoord(MSG_ENTITY, self.maxs_x);
496                         WriteCoord(MSG_ENTITY, self.maxs_y);
497                         WriteCoord(MSG_ENTITY, self.maxs_z);
498                 }
499                 WriteShort(MSG_ENTITY, self.cnt);
500                 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
501                 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
502                 WriteCoord(MSG_ENTITY, self.waterlevel);
503                 WriteCoord(MSG_ENTITY, self.count);
504                 WriteByte(MSG_ENTITY, self.glow_color);
505                 WriteString(MSG_ENTITY, self.noise);
506         }
507         return 1;
508 }
509
510 void pointparticles_use()
511 {
512         self.state = !self.state;
513         self.SendFlags |= 2;
514 }
515
516 void pointparticles_think()
517 {
518         if(self.origin != self.oldorigin)
519         {
520                 self.SendFlags |= 4;
521                 self.oldorigin = self.origin;
522         }
523         self.nextthink = time;
524 }
525
526 void spawnfunc_func_pointparticles()
527 {
528         if(self.model != "")
529                 setmodel(self, self.model);
530         if(self.noise != "")
531                 precache_sound (self.noise);
532
533         self.effects = EF_NODEPTHTEST;
534         self.SendEntity = pointparticles_SendEntity;
535         self.SendFlags = 7;
536         if(!self.modelindex)
537         {
538                 self.modelindex = 4.2;
539                 self.origin += self.mins;
540                 self.maxs -= self.mins;
541         }
542         self.model = "net_entity";
543         if(!self.cnt)
544                 self.cnt = particleeffectnum(self.mdl);
545         IFTARGETED
546         {
547                 self.use = pointparticles_use;
548                 if(self.spawnflags & 1)
549                         self.state = 1;
550                 else
551                         self.state = 0;
552         }
553         else
554                 self.state = 1;
555         self.think = pointparticles_think;
556         self.nextthink = time;
557 }
558
559 void spawnfunc_func_sparks()
560 {
561         // self.cnt is the amount of sparks that one burst will spawn
562         if(self.cnt < 1) {
563                 self.cnt = 25.0; // nice default value
564         }
565
566         // self.wait is the probability that a sparkthink will spawn a spark shower
567         // range: 0 - 1, but 0 makes little sense, so...
568         if(self.wait < 0.05) {
569                 self.wait = 0.25; // nice default value
570         }
571
572         self.count = self.cnt;
573         self.mins = '0 0 0';
574         self.maxs = '0 0 0';
575         self.velocity = '0 0 -1';
576         self.mdl = "TE_SPARK";
577         self.impulse = 10 * self.wait; // by default 2.5/sec
578         self.wait = 0;
579         self.cnt = 0; // use mdl
580
581         spawnfunc_func_pointparticles();
582 }
583
584 float rainsnow_SendEntity(float to)
585 {
586         WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
587         WriteByte(MSG_ENTITY, self.state);
588         WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
589         WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
590         WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
591         WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
592         WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
593         WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
594         WriteShort(MSG_ENTITY, compressShortVector(self.dest));
595         WriteShort(MSG_ENTITY, self.count);
596         WriteByte(MSG_ENTITY, self.cnt);
597         return 1;
598 };
599
600 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
601 This is an invisible area like a trigger, which rain falls inside of.
602
603 Keys:
604 "velocity"
605  falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
606 "cnt"
607  sets color of rain (default 12 - white)
608 "count"
609  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
610 */
611 void spawnfunc_func_rain()
612 {
613         self.dest = self.velocity;
614         self.velocity = '0 0 0';
615         if (!self.dest)
616                 self.dest = '0 0 -700';
617         self.angles = '0 0 0';
618         self.movetype = MOVETYPE_NONE;
619         self.solid = SOLID_NOT;
620         SetBrushEntityModel();
621         self.model = "";
622         if (!self.cnt)
623                 self.cnt = 12;
624         if (!self.count)
625                 self.count = 2000;
626         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
627         if (self.count < 1)
628                 self.count = 1;
629         if(self.count > 65535)
630                 self.count = 65535;
631
632         self.state = 1; // 1 is rain, 0 is snow
633         self.effects = EF_NODEPTHTEST;
634         self.SendEntity = rainsnow_SendEntity;
635         self.Version = 1;
636         self.modelindex = 1;
637         self.model = "net_entity";
638 };
639
640
641 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
642 This is an invisible area like a trigger, which snow falls inside of.
643
644 Keys:
645 "velocity"
646  falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
647 "cnt"
648  sets color of rain (default 12 - white)
649 "count"
650  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
651 */
652 void spawnfunc_func_snow()
653 {
654         self.dest = self.velocity;
655         self.velocity = '0 0 0';
656         if (!self.dest)
657                 self.dest = '0 0 -300';
658         self.angles = '0 0 0';
659         self.movetype = MOVETYPE_NONE;
660         self.solid = SOLID_NOT;
661         SetBrushEntityModel();
662         self.model = "";
663         if (!self.cnt)
664                 self.cnt = 12;
665         if (!self.count)
666                 self.count = 2000;
667         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
668         if (self.count < 1)
669                 self.count = 1;
670         if(self.count > 65535)
671                 self.count = 65535;
672
673         self.state = 0; // 1 is rain, 0 is snow
674         self.effects = EF_NODEPTHTEST;
675         self.SendEntity = rainsnow_SendEntity;
676         self.Version = 1;
677         self.modelindex = 1;
678         self.model = "net_entity";
679 };
680
681
682 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
683
684 void misc_laser_aim()
685 {
686         vector a;
687         if(self.enemy)
688         {
689                 if(self.spawnflags & 2)
690                 {
691                         if(self.enemy.origin != self.mangle)
692                         {
693                                 self.mangle = self.enemy.origin;
694                                 self.SendFlags |= 2;
695                         }
696                 }
697                 else
698                 {
699                         a = vectoangles(self.enemy.origin - self.origin);
700                         a_x = -a_x;
701                         if(a != self.mangle)
702                         {
703                                 self.mangle = a;
704                                 self.SendFlags |= 2;
705                         }
706                 }
707         }
708         else
709         {
710                 if(self.angles != self.mangle)
711                 {
712                         self.mangle = self.angles;
713                         self.SendFlags |= 2;
714                 }
715         }
716         if(self.origin != self.oldorigin)
717         {
718                 self.SendFlags |= 1;
719                 self.oldorigin = self.origin;
720         }
721 }
722
723 void misc_laser_init()
724 {
725         if(self.target != "")
726                 self.enemy = find(world, targetname, self.target);
727 }
728
729 .entity pusher;
730 void misc_laser_think()
731 {
732         vector o;
733         entity oldself;
734
735         self.nextthink = time;
736
737         if(!self.state)
738                 return;
739
740         misc_laser_aim();
741
742         if(self.enemy)
743         {
744                 o = self.enemy.origin;
745                 if not(self.spawnflags & 2)
746                         o = self.origin + normalize(o - self.origin) * 32768;
747         }
748         else
749         {
750                 makevectors(self.mangle);
751                 o = self.origin + v_forward * 32768;
752         }
753
754         if(self.dmg)
755         {
756                 if(self.dmg < 0)
757                         FireRailgunBullet(self.origin, o, 100000, 0, DEATH_HURTTRIGGER);
758                 else
759                         FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, DEATH_HURTTRIGGER);
760         }
761
762         if(self.enemy.target != "") // DETECTOR laser
763         {
764                 traceline(self.origin, o, MOVE_NORMAL, self);
765                 if(trace_ent.classname == "player")
766                 {
767                         self.pusher = trace_ent;
768                         if(!self.count)
769                         {
770                                 self.count = 1;
771
772                                 oldself = self;
773                                 self = self.enemy;
774                                 activator = self.pusher;
775                                 SUB_UseTargets();
776                                 self = oldself;
777                         }
778                 }
779                 else
780                 {
781                         if(self.count)
782                         {
783                                 self.count = 0;
784
785                                 oldself = self;
786                                 self = self.enemy;
787                                 activator = self.pusher;
788                                 SUB_UseTargets();
789                                 self = oldself;
790                         }
791                 }
792         }
793 }
794
795 float laser_SendEntity(entity to, float fl)
796 {
797         WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
798         fl = fl - (fl & 0xC0); // use that bit to indicate finite length laser
799         if(self.spawnflags & 2)
800                 fl |= 0x80;
801         if(self.alpha)
802                 fl |= 0x40;
803         WriteByte(MSG_ENTITY, fl);
804         if(fl & 1)
805         {
806                 WriteCoord(MSG_ENTITY, self.origin_x);
807                 WriteCoord(MSG_ENTITY, self.origin_y);
808                 WriteCoord(MSG_ENTITY, self.origin_z);
809         }
810         if(fl & 8)
811         {
812                 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
813                 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
814                 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
815                 if(fl & 0x40)
816                         WriteByte(MSG_ENTITY, self.alpha * 255.0);
817                 WriteShort(MSG_ENTITY, self.cnt + 1);
818         }
819         if(fl & 2)
820         {
821                 if(fl & 0x80)
822                 {
823                         WriteCoord(MSG_ENTITY, self.enemy.origin_x);
824                         WriteCoord(MSG_ENTITY, self.enemy.origin_y);
825                         WriteCoord(MSG_ENTITY, self.enemy.origin_z);
826                 }
827                 else
828                 {
829                         WriteCoord(MSG_ENTITY, self.mangle_x);
830                         WriteCoord(MSG_ENTITY, self.mangle_y);
831                 }
832         }
833         if(fl & 4)
834                 WriteByte(MSG_ENTITY, self.state);
835         return 1;
836 }
837
838 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
839 Any object touching the beam will be hurt
840 Keys:
841 "target"
842  spawnfunc_target_position where the laser ends
843 "mdl"
844  name of beam end effect to use
845 "colormod"
846  color of the beam (default: red)
847 "dmg"
848  damage per second (-1 for a laser that kills immediately)
849 */
850 void laser_use()
851 {
852         self.state = !self.state;
853         self.SendFlags |= 4;
854         misc_laser_aim();
855 }
856
857 void spawnfunc_misc_laser()
858 {
859         if(self.mdl)
860         {
861                 if(self.mdl == "none")
862                         self.cnt = -1;
863                 else
864                         self.cnt = particleeffectnum(self.mdl);
865         }
866         else if(!self.cnt)
867         {
868                 if(self.dmg)
869                         self.cnt = particleeffectnum("laser_deadly");
870                 else
871                         self.cnt = -1;
872         }
873
874         if(self.colormod == '0 0 0')
875                 if(!self.alpha)
876                         self.colormod = '1 0 0';
877         if(!self.message)
878                 self.message = "saw the light";
879         if (!self.message2)
880                 self.message2 = "was pushed into a laser by";
881         self.think = misc_laser_think;
882         self.nextthink = time;
883         InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
884
885         self.effects = EF_NODEPTHTEST;
886         self.SendEntity = laser_SendEntity;
887         self.SendFlags = 15;
888         self.modelindex = 1;
889         self.model = "net_entity";
890         self.mangle = self.angles;
891
892         IFTARGETED
893         {
894                 self.use = laser_use;
895                 if(self.spawnflags & 1)
896                         self.state = 1;
897                 else
898                         self.state = 0;
899         }
900         else
901                 self.state = 1;
902 }
903
904 // tZorks trigger impulse / gravity
905 .float radius;
906 .float falloff;
907 .float strength;
908 .float lastpushtime;
909
910 // targeted (directional) mode
911 void trigger_impulse_touch1()
912 {
913         entity targ;
914     float pushdeltatime;
915     float str;
916
917         // FIXME: Better checking for what to push and not.
918         if (other.classname != "player")
919         if (other.classname != "corpse")
920         if (other.classname != "body")
921         if (other.classname != "gib")
922         if (other.classname != "missile")
923         if (other.classname != "casing")
924         if (other.classname != "grenade")
925         if (other.classname != "plasma")
926         if (other.classname != "plasma_prim")
927         if (other.classname != "plasma_chain")
928         if (other.classname != "droppedweapon")
929                 return;
930
931         if (other.deadflag && other.classname == "player")
932                 return;
933
934         EXACTTRIGGER_TOUCH;
935
936     targ = find(world, targetname, self.target);
937     if(!targ)
938     {
939         objerror("trigger_force without a (valid) .target!\n");
940         remove(self);
941         return;
942     }
943
944     if(self.falloff == 1)
945         str = (str / self.radius) * self.strength;
946     else if(self.falloff == 2)
947         str = (1 - (str / self.radius)) * self.strength;
948     else
949         str = self.strength;
950
951     pushdeltatime = time - other.lastpushtime;
952     if (pushdeltatime > 0.15) pushdeltatime = 0;
953     other.lastpushtime = time;
954     if(!pushdeltatime) return;
955
956     other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
957 }
958
959 // Directionless (accelerator/decelerator) mode
960 void trigger_impulse_touch2()
961 {
962     float pushdeltatime;
963
964         // FIXME: Better checking for what to push and not.
965         if (other.classname != "player")
966         if (other.classname != "corpse")
967         if (other.classname != "body")
968         if (other.classname != "gib")
969         if (other.classname != "missile")
970         if (other.classname != "casing")
971         if (other.classname != "grenade")
972         if (other.classname != "plasma")
973         if (other.classname != "plasma_prim")
974         if (other.classname != "plasma_chain")
975         if (other.classname != "droppedweapon")
976                 return;
977
978         if (other.deadflag && other.classname == "player")
979                 return;
980
981         EXACTTRIGGER_TOUCH;
982
983     pushdeltatime = time - other.lastpushtime;
984     if (pushdeltatime > 0.15) pushdeltatime = 0;
985     other.lastpushtime = time;
986     if(!pushdeltatime) return;
987
988     //if(self.strength > 1)
989         other.velocity = other.velocity * (self.strength * pushdeltatime);
990     //else
991     //    other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
992 }
993
994 // Spherical (gravity/repulsor) mode
995 void trigger_impulse_touch3()
996 {
997     float pushdeltatime;
998     float str;
999
1000         // FIXME: Better checking for what to push and not.
1001         if (other.classname != "player")
1002         if (other.classname != "corpse")
1003         if (other.classname != "body")
1004         if (other.classname != "gib")
1005         if (other.classname != "missile")
1006         if (other.classname != "casing")
1007         if (other.classname != "grenade")
1008         if (other.classname != "plasma")
1009         if (other.classname != "plasma_prim")
1010         if (other.classname != "plasma_chain")
1011         if (other.classname != "droppedweapon")
1012                 return;
1013
1014         if (other.deadflag && other.classname == "player")
1015                 return;
1016
1017         EXACTTRIGGER_TOUCH;
1018
1019     pushdeltatime = time - other.lastpushtime;
1020     if (pushdeltatime > 0.15) pushdeltatime = 0;
1021     other.lastpushtime = time;
1022     if(!pushdeltatime) return;
1023
1024     setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1025
1026         str = min(self.radius, vlen(self.origin - other.origin));
1027
1028     if(self.falloff == 1)
1029         str = (1 - str / self.radius) * self.strength; // 1 in the inside
1030     else if(self.falloff == 2)
1031         str = (str / self.radius) * self.strength; // 0 in the inside
1032     else
1033         str = self.strength;
1034
1035     other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1036 }
1037
1038 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1039 -------- KEYS --------
1040 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1041          If not, this trigger acts like a damper/accelerator field.
1042
1043 strength : This is how mutch force to add in the direction of .target each second
1044            when .target is set. If not, this is hoe mutch to slow down/accelerate
1045            someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1046
1047 radius   : If set, act as a spherical device rather then a liniar one.
1048
1049 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1050
1051 -------- NOTES --------
1052 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1053 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1054 */
1055
1056 void spawnfunc_trigger_impulse()
1057 {
1058         EXACTTRIGGER_INIT;
1059     if(self.radius)
1060     {
1061         if(!self.strength) self.strength = 2000;
1062         setorigin(self, self.origin);
1063         setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1064         self.touch = trigger_impulse_touch3;
1065     }
1066     else
1067     {
1068         if(self.target)
1069         {
1070             if(!self.strength) self.strength = 950;
1071             self.touch = trigger_impulse_touch1;
1072         }
1073         else
1074         {
1075             if(!self.strength) self.strength = 0.9;
1076             self.touch = trigger_impulse_touch2;
1077         }
1078     }
1079 }
1080
1081 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1082 "Flip-flop" trigger gate... lets only every second trigger event through
1083 */
1084 void flipflop_use()
1085 {
1086         self.state = !self.state;
1087         if(self.state)
1088                 SUB_UseTargets();
1089 }
1090
1091 void spawnfunc_trigger_flipflop()
1092 {
1093         if(self.spawnflags & 1)
1094                 self.state = 1;
1095     self.use = flipflop_use;
1096 }
1097
1098 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1099 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1100 */
1101 void monoflop_use()
1102 {
1103         self.nextthink = time + self.wait;
1104         if(self.state)
1105                 return;
1106         self.state = 1;
1107         SUB_UseTargets();
1108 }
1109 void monoflop_fixed_use()
1110 {
1111         if(self.state)
1112                 return;
1113         self.nextthink = time + self.wait;
1114         self.state = 1;
1115         SUB_UseTargets();
1116 }
1117
1118 void monoflop_think()
1119 {
1120         self.state = 0;
1121         SUB_UseTargets();
1122 }
1123
1124 void spawnfunc_trigger_monoflop()
1125 {
1126         if(!self.wait)
1127                 self.wait = 1;
1128         if(self.spawnflags & 1)
1129                 self.use = monoflop_fixed_use;
1130         else
1131                 self.use = monoflop_use;
1132         self.think = monoflop_think;
1133         self.state = 0;
1134 }
1135
1136 void multivibrator_send()
1137 {
1138         float newstate;
1139         float cyclestart;
1140
1141         cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1142
1143         newstate = (time < cyclestart + self.wait);
1144
1145         if(self.state != newstate)
1146                 SUB_UseTargets();
1147         self.state = newstate;
1148
1149         if(self.state)
1150                 self.nextthink = cyclestart + self.wait + 0.01;
1151         else
1152                 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1153 }
1154
1155 void multivibrator_toggle()
1156 {
1157         if(self.nextthink == 0)
1158         {
1159                 multivibrator_send();
1160         }
1161         else
1162         {
1163                 if(self.state)
1164                 {
1165                         SUB_UseTargets();
1166                         self.state = 0;
1167                 }
1168                 self.nextthink = 0;
1169         }
1170 }
1171
1172 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1173 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1174 -------- KEYS --------
1175 target: trigger all entities with this targetname when it goes off
1176 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1177 phase: offset of the timing
1178 wait: "on" cycle time (default: 1)
1179 respawntime: "off" cycle time (default: same as wait)
1180 -------- SPAWNFLAGS --------
1181 START_ON: assume it is already turned on (when targeted)
1182 */
1183 void spawnfunc_trigger_multivibrator()
1184 {
1185         if(!self.wait)
1186                 self.wait = 1;
1187         if(!self.respawntime)
1188                 self.respawntime = self.wait;
1189
1190         self.state = 0;
1191         self.use = multivibrator_toggle;
1192         self.think = multivibrator_send;
1193         self.nextthink = time;
1194
1195         IFTARGETED
1196         {
1197                 if(!(self.spawnflags & 1))
1198                         self.nextthink = 0; // wait for a trigger event
1199         }
1200         else
1201                 self.nextthink = time;
1202 }
1203
1204
1205 void follow_init()
1206 {
1207         entity src, dst;
1208         src = find(world, targetname, self.killtarget);
1209         dst = find(world, targetname, self.target);
1210
1211         if(!src || !dst)
1212         {
1213                 objerror("follow: could not find target/killtarget");
1214                 return;
1215         }
1216
1217         dst.movetype = MOVETYPE_FOLLOW;
1218         dst.aiment = src;
1219         dst.punchangle = src.angles;
1220         dst.view_ofs = dst.origin - src.origin;
1221         dst.v_angle = dst.angles - src.angles;
1222
1223         remove(self);
1224 }
1225
1226 void spawnfunc_misc_follow()
1227 {
1228         InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1229 }