]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/g_triggers.qc
fix fog handling a bit
[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
407         if(!trigger_hurt_first)
408                 trigger_hurt_first = self;
409         if(trigger_hurt_last)
410                 trigger_hurt_last.trigger_hurt_next = self;
411         trigger_hurt_last = self;
412 };
413
414 float trace_hits_box_a0, trace_hits_box_a1;
415
416 float trace_hits_box_1d(float end, float thmi, float thma)
417 {
418         if(end == 0)
419         {
420                 // just check if x is in range
421                 if(0 < thmi)
422                         return FALSE;
423                 if(0 > thma)
424                         return FALSE;
425         }
426         else
427         {
428                 // do the trace with respect to x
429                 // 0 -> end has to stay in thmi -> thma
430                 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
431                 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
432                 if(trace_hits_box_a0 > trace_hits_box_a1)
433                         return FALSE;
434         }
435         return TRUE;
436 }
437
438 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
439 {
440         end -= start;
441         thmi -= start;
442         thma -= start;
443         // now it is a trace from 0 to end
444
445         trace_hits_box_a0 = 0;
446         trace_hits_box_a1 = 1;
447
448         if(!trace_hits_box_1d(end_x, thmi_x, thma_x))
449                 return FALSE;
450         if(!trace_hits_box_1d(end_y, thmi_y, thma_y))
451                 return FALSE;
452         if(!trace_hits_box_1d(end_z, thmi_z, thma_z))
453                 return FALSE;
454
455         return TRUE;
456 }
457
458 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
459 {
460         return trace_hits_box(start, end, thmi - ma, thma - mi);
461 }
462
463 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
464 {
465         entity th;
466
467         for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
468                 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
469                         return TRUE;
470
471         return FALSE;
472 }
473
474
475 // TODO add a way to do looped sounds with sound(); then complete this entity
476 .float volume, atten;
477 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE * self.volume, self.atten);}
478
479 void spawnfunc_target_speaker()
480 {
481         if(self.noise)
482                 precache_sound (self.noise);
483         IFTARGETED
484         {
485                 if(!self.atten)
486                         self.atten = ATTN_NORM;
487                 else if(self.atten < 0)
488                         self.atten = 0;
489                 if(!self.volume)
490                         self.volume = 1;
491                 self.use = target_speaker_use;
492         }
493         else
494         {
495                 if(!self.atten)
496                         self.atten = ATTN_STATIC;
497                 else if(self.atten < 0)
498                         self.atten = 0;
499                 if(!self.volume)
500                         self.volume = 1;
501                 ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
502         }
503 };
504
505
506 void spawnfunc_func_stardust() {
507         self.effects = EF_STARDUST;
508 }
509
510 float pointparticles_SendEntity(entity to, float fl)
511 {
512         WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
513         WriteByte(MSG_ENTITY, fl);
514         if(fl & 2)
515         {
516                 if(self.state)
517                         WriteCoord(MSG_ENTITY, self.impulse);
518                 else
519                         WriteCoord(MSG_ENTITY, 0); // off
520         }
521         if(fl & 4)
522         {
523                 WriteCoord(MSG_ENTITY, self.origin_x);
524                 WriteCoord(MSG_ENTITY, self.origin_y);
525                 WriteCoord(MSG_ENTITY, self.origin_z);
526         }
527         if(fl & 1)
528         {
529                 if(self.modelindex != 4.2)
530                 {
531                         WriteShort(MSG_ENTITY, self.modelindex);
532                         WriteCoord(MSG_ENTITY, self.mins_x);
533                         WriteCoord(MSG_ENTITY, self.mins_y);
534                         WriteCoord(MSG_ENTITY, self.mins_z);
535                         WriteCoord(MSG_ENTITY, self.maxs_x);
536                         WriteCoord(MSG_ENTITY, self.maxs_y);
537                         WriteCoord(MSG_ENTITY, self.maxs_z);
538                 }
539                 else
540                 {
541                         WriteShort(MSG_ENTITY, 0);
542                         WriteCoord(MSG_ENTITY, self.maxs_x);
543                         WriteCoord(MSG_ENTITY, self.maxs_y);
544                         WriteCoord(MSG_ENTITY, self.maxs_z);
545                 }
546                 WriteShort(MSG_ENTITY, self.cnt);
547                 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
548                 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
549                 WriteCoord(MSG_ENTITY, self.waterlevel);
550                 WriteCoord(MSG_ENTITY, self.count);
551                 WriteByte(MSG_ENTITY, self.glow_color);
552                 WriteString(MSG_ENTITY, self.noise);
553         }
554         return 1;
555 }
556
557 void pointparticles_use()
558 {
559         self.state = !self.state;
560         self.SendFlags |= 2;
561 }
562
563 void pointparticles_think()
564 {
565         if(self.origin != self.oldorigin)
566         {
567                 self.SendFlags |= 4;
568                 self.oldorigin = self.origin;
569         }
570         self.nextthink = time;
571 }
572
573 void spawnfunc_func_pointparticles()
574 {
575         if(self.model != "")
576                 setmodel(self, self.model);
577         if(self.noise != "")
578                 precache_sound (self.noise);
579
580         self.effects = EF_NODEPTHTEST;
581         self.SendEntity = pointparticles_SendEntity;
582         self.SendFlags = 7;
583         if(!self.modelindex)
584         {
585                 self.modelindex = 4.2;
586                 self.origin += self.mins;
587                 self.maxs -= self.mins;
588         }
589         self.model = "net_entity";
590         if(!self.cnt)
591                 self.cnt = particleeffectnum(self.mdl);
592         IFTARGETED
593         {
594                 self.use = pointparticles_use;
595                 if(self.spawnflags & 1)
596                         self.state = 1;
597                 else
598                         self.state = 0;
599         }
600         else
601                 self.state = 1;
602         self.think = pointparticles_think;
603         self.nextthink = time;
604 }
605
606 void spawnfunc_func_sparks()
607 {
608         // self.cnt is the amount of sparks that one burst will spawn
609         if(self.cnt < 1) {
610                 self.cnt = 25.0; // nice default value
611         }
612
613         // self.wait is the probability that a sparkthink will spawn a spark shower
614         // range: 0 - 1, but 0 makes little sense, so...
615         if(self.wait < 0.05) {
616                 self.wait = 0.25; // nice default value
617         }
618
619         self.count = self.cnt;
620         self.mins = '0 0 0';
621         self.maxs = '0 0 0';
622         self.velocity = '0 0 -1';
623         self.mdl = "TE_SPARK";
624         self.impulse = 0.1 / self.wait;
625         self.wait = 0;
626
627         spawnfunc_func_pointparticles();
628 }
629
630 float rainsnow_SendEntity(float to)
631 {
632         WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
633         WriteByte(MSG_ENTITY, self.state);
634         WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
635         WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
636         WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
637         WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
638         WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
639         WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
640         WriteShort(MSG_ENTITY, compressShortVector(self.dest));
641         WriteShort(MSG_ENTITY, self.count);
642         WriteByte(MSG_ENTITY, self.cnt);
643         return 1;
644 };
645
646 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
647 This is an invisible area like a trigger, which rain falls inside of.
648
649 Keys:
650 "velocity"
651  falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
652 "cnt"
653  sets color of rain (default 12 - white)
654 "count"
655  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
656 */
657 void spawnfunc_func_rain()
658 {
659         self.dest = self.velocity;
660         self.velocity = '0 0 0';
661         if (!self.dest)
662                 self.dest = '0 0 -700';
663         self.angles = '0 0 0';
664         self.movetype = MOVETYPE_NONE;
665         self.solid = SOLID_NOT;
666         SetBrushEntityModel();
667         self.model = "";
668         if (!self.cnt)
669                 self.cnt = 12;
670         if (!self.count)
671                 self.count = 2000;
672         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
673         if (self.count < 1)
674                 self.count = 1;
675         if(self.count > 65535)
676                 self.count = 65535;
677
678         self.state = 1; // 1 is rain, 0 is snow
679         self.effects = EF_NODEPTHTEST;
680         self.SendEntity = rainsnow_SendEntity;
681         self.Version = 1;
682         self.modelindex = 1;
683         self.model = "net_entity";
684 };
685
686
687 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
688 This is an invisible area like a trigger, which snow falls inside of.
689
690 Keys:
691 "velocity"
692  falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
693 "cnt"
694  sets color of rain (default 12 - white)
695 "count"
696  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
697 */
698 void spawnfunc_func_snow()
699 {
700         self.dest = self.velocity;
701         self.velocity = '0 0 0';
702         if (!self.dest)
703                 self.dest = '0 0 -300';
704         self.angles = '0 0 0';
705         self.movetype = MOVETYPE_NONE;
706         self.solid = SOLID_NOT;
707         SetBrushEntityModel();
708         self.model = "";
709         if (!self.cnt)
710                 self.cnt = 12;
711         if (!self.count)
712                 self.count = 2000;
713         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
714         if (self.count < 1)
715                 self.count = 1;
716         if(self.count > 65535)
717                 self.count = 65535;
718
719         self.state = 0; // 1 is rain, 0 is snow
720         self.effects = EF_NODEPTHTEST;
721         self.SendEntity = rainsnow_SendEntity;
722         self.Version = 1;
723         self.modelindex = 1;
724         self.model = "net_entity";
725 };
726
727
728 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
729
730 void misc_laser_aim()
731 {
732         vector a;
733         if(self.enemy)
734         {
735                 if(self.spawnflags & 2)
736                 {
737                         if(self.enemy.origin != self.mangle)
738                         {
739                                 self.mangle = self.enemy.origin;
740                                 self.SendFlags |= 2;
741                         }
742                 }
743                 else
744                 {
745                         a = vectoangles(self.enemy.origin - self.origin);
746                         a_x = -a_x;
747                         if(a != self.mangle)
748                         {
749                                 self.mangle = a;
750                                 self.SendFlags |= 2;
751                         }
752                 }
753         }
754         else
755         {
756                 if(self.angles != self.mangle)
757                 {
758                         self.mangle = self.angles;
759                         self.SendFlags |= 2;
760                 }
761         }
762         if(self.origin != self.oldorigin)
763         {
764                 self.SendFlags |= 1;
765                 self.oldorigin = self.origin;
766         }
767 }
768
769 void misc_laser_init()
770 {
771         if(self.target != "")
772                 self.enemy = find(world, targetname, self.target);
773 }
774
775 .entity pusher;
776 void misc_laser_think()
777 {
778         vector o;
779         entity oldself;
780
781         self.nextthink = time;
782
783         if(!self.state)
784                 return;
785
786         misc_laser_aim();
787
788         if(self.enemy)
789         {
790                 o = self.enemy.origin;
791                 if not(self.spawnflags & 2)
792                         o = self.origin + normalize(o - self.origin) * 32768;
793         }
794         else
795         {
796                 makevectors(self.mangle);
797                 o = self.origin + v_forward * 32768;
798         }
799
800         if(self.dmg)
801         {
802                 if(self.dmg < 0)
803                         FireRailgunBullet(self.origin, o, 100000, 0, DEATH_HURTTRIGGER);
804                 else
805                         FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, DEATH_HURTTRIGGER);
806         }
807
808         if(self.enemy.target != "") // DETECTOR laser
809         {
810                 traceline(self.origin, o, MOVE_NORMAL, self);
811                 if(trace_ent.classname == "player")
812                 {
813                         self.pusher = trace_ent;
814                         if(!self.count)
815                         {
816                                 self.count = 1;
817
818                                 oldself = self;
819                                 self = self.enemy;
820                                 activator = self.pusher;
821                                 SUB_UseTargets();
822                                 self = oldself;
823                         }
824                 }
825                 else
826                 {
827                         if(self.count)
828                         {
829                                 self.count = 0;
830
831                                 oldself = self;
832                                 self = self.enemy;
833                                 activator = self.pusher;
834                                 SUB_UseTargets();
835                                 self = oldself;
836                         }
837                 }
838         }
839 }
840
841 float laser_SendEntity(entity to, float fl)
842 {
843         WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
844         fl = fl - (fl & 0xC0); // use that bit to indicate finite length laser
845         if(self.spawnflags & 2)
846                 fl |= 0x80;
847         if(self.alpha)
848                 fl |= 0x40;
849         WriteByte(MSG_ENTITY, fl);
850         if(fl & 1)
851         {
852                 WriteCoord(MSG_ENTITY, self.origin_x);
853                 WriteCoord(MSG_ENTITY, self.origin_y);
854                 WriteCoord(MSG_ENTITY, self.origin_z);
855         }
856         if(fl & 8)
857         {
858                 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
859                 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
860                 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
861                 if(fl & 0x40)
862                         WriteByte(MSG_ENTITY, self.alpha * 255.0);
863                 WriteShort(MSG_ENTITY, self.cnt + 1);
864         }
865         if(fl & 2)
866         {
867                 if(fl & 0x80)
868                 {
869                         WriteCoord(MSG_ENTITY, self.enemy.origin_x);
870                         WriteCoord(MSG_ENTITY, self.enemy.origin_y);
871                         WriteCoord(MSG_ENTITY, self.enemy.origin_z);
872                 }
873                 else
874                 {
875                         WriteCoord(MSG_ENTITY, self.mangle_x);
876                         WriteCoord(MSG_ENTITY, self.mangle_y);
877                 }
878         }
879         if(fl & 4)
880                 WriteByte(MSG_ENTITY, self.state);
881         return 1;
882 }
883
884 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
885 Any object touching the beam will be hurt
886 Keys:
887 "target"
888  spawnfunc_target_position where the laser ends
889 "mdl"
890  name of beam end effect to use
891 "colormod"
892  color of the beam (default: red)
893 "dmg"
894  damage per second (-1 for a laser that kills immediately)
895 */
896 void laser_use()
897 {
898         self.state = !self.state;
899         self.SendFlags |= 4;
900         misc_laser_aim();
901 }
902
903 void spawnfunc_misc_laser()
904 {
905         if(self.mdl)
906         {
907                 if(self.mdl == "none")
908                         self.cnt = -1;
909                 else
910                         self.cnt = particleeffectnum(self.mdl);
911         }
912         else if(!self.cnt)
913         {
914                 if(self.dmg)
915                         self.cnt = particleeffectnum("laser_deadly");
916                 else
917                         self.cnt = -1;
918         }
919
920         if(self.colormod == '0 0 0')
921                 if(!self.alpha)
922                         self.colormod = '1 0 0';
923         if(!self.message)
924                 self.message = "saw the light";
925         self.think = misc_laser_think;
926         self.nextthink = time;
927         InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
928
929         self.effects = EF_NODEPTHTEST;
930         self.SendEntity = laser_SendEntity;
931         self.SendFlags = 15;
932         self.modelindex = 1;
933         self.model = "net_entity";
934         self.mangle = self.angles;
935
936         IFTARGETED
937         {
938                 self.use = laser_use;
939                 if(self.spawnflags & 1)
940                         self.state = 1;
941                 else
942                         self.state = 0;
943         }
944         else
945                 self.state = 1;
946 }
947
948 // tZorks trigger impulse / gravity
949 .float radius;
950 .float falloff;
951 .float strength;
952 .float lastpushtime;
953
954 // targeted (directional) mode
955 void trigger_impulse_touch1()
956 {
957         entity targ;
958     float pushdeltatime;
959     float str;
960
961         // FIXME: Better checking for what to push and not.
962         if (other.classname != "player")
963         if (other.classname != "corpse")
964         if (other.classname != "body")
965         if (other.classname != "gib")
966         if (other.classname != "missile")
967         if (other.classname != "casing")
968         if (other.classname != "grenade")
969         if (other.classname != "plasma")
970         if (other.classname != "plasma_prim")
971         if (other.classname != "plasma_chain")
972         if (other.classname != "droppedweapon")
973                 return;
974
975         if (other.deadflag && other.classname == "player")
976                 return;
977
978         EXACTTRIGGER_TOUCH;
979
980     targ = find(world, targetname, self.target);
981     if(!targ)
982     {
983         objerror("trigger_force without a (valid) .target!\n");
984         remove(self);
985         return;
986     }
987
988     if(self.falloff == 1)
989         str = (str / self.radius) * self.strength;
990     else if(self.falloff == 2)
991         str = (1 - (str / self.radius)) * self.strength;
992     else
993         str = self.strength;
994
995     pushdeltatime = time - other.lastpushtime;
996     if (pushdeltatime > 0.15) pushdeltatime = 0;
997     other.lastpushtime = time;
998     if(!pushdeltatime) return;
999
1000     other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
1001 }
1002
1003 // Directionless (accelerator/decelerator) mode
1004 void trigger_impulse_touch2()
1005 {
1006     float pushdeltatime;
1007
1008         // FIXME: Better checking for what to push and not.
1009         if (other.classname != "player")
1010         if (other.classname != "corpse")
1011         if (other.classname != "body")
1012         if (other.classname != "gib")
1013         if (other.classname != "missile")
1014         if (other.classname != "casing")
1015         if (other.classname != "grenade")
1016         if (other.classname != "plasma")
1017         if (other.classname != "plasma_prim")
1018         if (other.classname != "plasma_chain")
1019         if (other.classname != "droppedweapon")
1020                 return;
1021
1022         if (other.deadflag && other.classname == "player")
1023                 return;
1024
1025         EXACTTRIGGER_TOUCH;
1026
1027     pushdeltatime = time - other.lastpushtime;
1028     if (pushdeltatime > 0.15) pushdeltatime = 0;
1029     other.lastpushtime = time;
1030     if(!pushdeltatime) return;
1031
1032     //if(self.strength > 1)
1033         other.velocity = other.velocity * (self.strength * pushdeltatime);
1034     //else
1035     //    other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
1036 }
1037
1038 // Spherical (gravity/repulsor) mode
1039 void trigger_impulse_touch3()
1040 {
1041     float pushdeltatime;
1042     float str;
1043
1044         // FIXME: Better checking for what to push and not.
1045         if (other.classname != "player")
1046         if (other.classname != "corpse")
1047         if (other.classname != "body")
1048         if (other.classname != "gib")
1049         if (other.classname != "missile")
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.classname == "player")
1059                 return;
1060
1061         EXACTTRIGGER_TOUCH;
1062
1063     pushdeltatime = time - other.lastpushtime;
1064     if (pushdeltatime > 0.15) pushdeltatime = 0;
1065     other.lastpushtime = time;
1066     if(!pushdeltatime) return;
1067
1068     setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1069
1070         str = min(self.radius, vlen(self.origin - other.origin));
1071
1072     if(self.falloff == 1)
1073         str = (1 - str / self.radius) * self.strength; // 1 in the inside
1074     else if(self.falloff == 2)
1075         str = (str / self.radius) * self.strength; // 0 in the inside
1076     else
1077         str = self.strength;
1078
1079     other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1080 }
1081
1082 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1083 -------- KEYS --------
1084 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1085          If not, this trigger acts like a damper/accelerator field.
1086
1087 strength : This is how mutch force to add in the direction of .target each second
1088            when .target is set. If not, this is hoe mutch to slow down/accelerate
1089            someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1090
1091 radius   : If set, act as a spherical device rather then a liniar one.
1092
1093 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1094
1095 -------- NOTES --------
1096 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1097 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1098 */
1099
1100 void spawnfunc_trigger_impulse()
1101 {
1102         EXACTTRIGGER_INIT;
1103     if(self.radius)
1104     {
1105         if(!self.strength) self.strength = 2000;
1106         setorigin(self, self.origin);
1107         setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1108         self.touch = trigger_impulse_touch3;
1109     }
1110     else
1111     {
1112         if(self.target)
1113         {
1114             if(!self.strength) self.strength = 950;
1115             self.touch = trigger_impulse_touch1;
1116         }
1117         else
1118         {
1119             if(!self.strength) self.strength = 0.9;
1120             self.touch = trigger_impulse_touch2;
1121         }
1122     }
1123 }
1124
1125 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1126 "Flip-flop" trigger gate... lets only every second trigger event through
1127 */
1128 void flipflop_use()
1129 {
1130         self.state = !self.state;
1131         if(self.state)
1132                 SUB_UseTargets();
1133 }
1134
1135 void spawnfunc_trigger_flipflop()
1136 {
1137         if(self.spawnflags & 1)
1138                 self.state = 1;
1139     self.use = flipflop_use;
1140 }
1141
1142 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1143 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1144 */
1145 void monoflop_use()
1146 {
1147         self.nextthink = time + self.wait;
1148         if(self.state)
1149                 return;
1150         self.state = 1;
1151         SUB_UseTargets();
1152 }
1153 void monoflop_fixed_use()
1154 {
1155         if(self.state)
1156                 return;
1157         self.nextthink = time + self.wait;
1158         self.state = 1;
1159         SUB_UseTargets();
1160 }
1161
1162 void monoflop_think()
1163 {
1164         self.state = 0;
1165         SUB_UseTargets();
1166 }
1167
1168 void spawnfunc_trigger_monoflop()
1169 {
1170         if(!self.wait)
1171                 self.wait = 1;
1172         if(self.spawnflags & 1)
1173                 self.use = monoflop_fixed_use;
1174         else
1175                 self.use = monoflop_use;
1176         self.think = monoflop_think;
1177         self.state = 0;
1178 }
1179
1180 void multivibrator_send()
1181 {
1182         float newstate;
1183         float cyclestart;
1184
1185         cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1186
1187         newstate = (time < cyclestart + self.wait);
1188
1189         if(self.state != newstate)
1190                 SUB_UseTargets();
1191         self.state = newstate;
1192
1193         if(self.state)
1194                 self.nextthink = cyclestart + self.wait + 0.01;
1195         else
1196                 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1197 }
1198
1199 void multivibrator_toggle()
1200 {
1201         if(self.nextthink == 0)
1202         {
1203                 multivibrator_send();
1204         }
1205         else
1206         {
1207                 if(self.state)
1208                 {
1209                         SUB_UseTargets();
1210                         self.state = 0;
1211                 }
1212                 self.nextthink = 0;
1213         }
1214 }
1215
1216 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1217 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1218 -------- KEYS --------
1219 target: trigger all entities with this targetname when it goes off
1220 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1221 phase: offset of the timing
1222 wait: "on" cycle time (default: 1)
1223 respawntime: "off" cycle time (default: same as wait)
1224 -------- SPAWNFLAGS --------
1225 START_ON: assume it is already turned on (when targeted)
1226 */
1227 void spawnfunc_trigger_multivibrator()
1228 {
1229         if(!self.wait)
1230                 self.wait = 1;
1231         if(!self.respawntime)
1232                 self.respawntime = self.wait;
1233
1234         self.state = 0;
1235         self.use = multivibrator_toggle;
1236         self.think = multivibrator_send;
1237         self.nextthink = time;
1238
1239         IFTARGETED
1240         {
1241                 if(!(self.spawnflags & 1))
1242                         self.nextthink = 0; // wait for a trigger event
1243         }
1244         else
1245                 self.nextthink = time;
1246 }
1247
1248
1249 void follow_init()
1250 {
1251         entity src, dst;
1252         src = find(world, targetname, self.killtarget);
1253         dst = find(world, targetname, self.target);
1254
1255         if(!src || !dst)
1256         {
1257                 objerror("follow: could not find target/killtarget");
1258                 return;
1259         }
1260
1261         dst.movetype = MOVETYPE_FOLLOW;
1262         dst.aiment = src;
1263         dst.punchangle = src.angles;
1264         dst.view_ofs = dst.origin - src.origin;
1265         dst.v_angle = dst.angles - src.angles;
1266
1267         remove(self);
1268 }
1269
1270 void spawnfunc_misc_follow()
1271 {
1272         InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1273 }