]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/g_triggers.qc
now REALLY remove dead players
[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 = 10 * self.wait; // by default 2.5/sec
625         self.wait = 0;
626         self.cnt = 0; // use mdl
627
628         spawnfunc_func_pointparticles();
629 }
630
631 float rainsnow_SendEntity(float to)
632 {
633         WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
634         WriteByte(MSG_ENTITY, self.state);
635         WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
636         WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
637         WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
638         WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
639         WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
640         WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
641         WriteShort(MSG_ENTITY, compressShortVector(self.dest));
642         WriteShort(MSG_ENTITY, self.count);
643         WriteByte(MSG_ENTITY, self.cnt);
644         return 1;
645 };
646
647 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
648 This is an invisible area like a trigger, which rain falls inside of.
649
650 Keys:
651 "velocity"
652  falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
653 "cnt"
654  sets color of rain (default 12 - white)
655 "count"
656  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
657 */
658 void spawnfunc_func_rain()
659 {
660         self.dest = self.velocity;
661         self.velocity = '0 0 0';
662         if (!self.dest)
663                 self.dest = '0 0 -700';
664         self.angles = '0 0 0';
665         self.movetype = MOVETYPE_NONE;
666         self.solid = SOLID_NOT;
667         SetBrushEntityModel();
668         self.model = "";
669         if (!self.cnt)
670                 self.cnt = 12;
671         if (!self.count)
672                 self.count = 2000;
673         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
674         if (self.count < 1)
675                 self.count = 1;
676         if(self.count > 65535)
677                 self.count = 65535;
678
679         self.state = 1; // 1 is rain, 0 is snow
680         self.effects = EF_NODEPTHTEST;
681         self.SendEntity = rainsnow_SendEntity;
682         self.Version = 1;
683         self.modelindex = 1;
684         self.model = "net_entity";
685 };
686
687
688 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
689 This is an invisible area like a trigger, which snow falls inside of.
690
691 Keys:
692 "velocity"
693  falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
694 "cnt"
695  sets color of rain (default 12 - white)
696 "count"
697  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
698 */
699 void spawnfunc_func_snow()
700 {
701         self.dest = self.velocity;
702         self.velocity = '0 0 0';
703         if (!self.dest)
704                 self.dest = '0 0 -300';
705         self.angles = '0 0 0';
706         self.movetype = MOVETYPE_NONE;
707         self.solid = SOLID_NOT;
708         SetBrushEntityModel();
709         self.model = "";
710         if (!self.cnt)
711                 self.cnt = 12;
712         if (!self.count)
713                 self.count = 2000;
714         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
715         if (self.count < 1)
716                 self.count = 1;
717         if(self.count > 65535)
718                 self.count = 65535;
719
720         self.state = 0; // 1 is rain, 0 is snow
721         self.effects = EF_NODEPTHTEST;
722         self.SendEntity = rainsnow_SendEntity;
723         self.Version = 1;
724         self.modelindex = 1;
725         self.model = "net_entity";
726 };
727
728
729 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
730
731 void misc_laser_aim()
732 {
733         vector a;
734         if(self.enemy)
735         {
736                 if(self.spawnflags & 2)
737                 {
738                         if(self.enemy.origin != self.mangle)
739                         {
740                                 self.mangle = self.enemy.origin;
741                                 self.SendFlags |= 2;
742                         }
743                 }
744                 else
745                 {
746                         a = vectoangles(self.enemy.origin - self.origin);
747                         a_x = -a_x;
748                         if(a != self.mangle)
749                         {
750                                 self.mangle = a;
751                                 self.SendFlags |= 2;
752                         }
753                 }
754         }
755         else
756         {
757                 if(self.angles != self.mangle)
758                 {
759                         self.mangle = self.angles;
760                         self.SendFlags |= 2;
761                 }
762         }
763         if(self.origin != self.oldorigin)
764         {
765                 self.SendFlags |= 1;
766                 self.oldorigin = self.origin;
767         }
768 }
769
770 void misc_laser_init()
771 {
772         if(self.target != "")
773                 self.enemy = find(world, targetname, self.target);
774 }
775
776 .entity pusher;
777 void misc_laser_think()
778 {
779         vector o;
780         entity oldself;
781
782         self.nextthink = time;
783
784         if(!self.state)
785                 return;
786
787         misc_laser_aim();
788
789         if(self.enemy)
790         {
791                 o = self.enemy.origin;
792                 if not(self.spawnflags & 2)
793                         o = self.origin + normalize(o - self.origin) * 32768;
794         }
795         else
796         {
797                 makevectors(self.mangle);
798                 o = self.origin + v_forward * 32768;
799         }
800
801         if(self.dmg)
802         {
803                 if(self.dmg < 0)
804                         FireRailgunBullet(self.origin, o, 100000, 0, DEATH_HURTTRIGGER);
805                 else
806                         FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, DEATH_HURTTRIGGER);
807         }
808
809         if(self.enemy.target != "") // DETECTOR laser
810         {
811                 traceline(self.origin, o, MOVE_NORMAL, self);
812                 if(trace_ent.classname == "player")
813                 {
814                         self.pusher = trace_ent;
815                         if(!self.count)
816                         {
817                                 self.count = 1;
818
819                                 oldself = self;
820                                 self = self.enemy;
821                                 activator = self.pusher;
822                                 SUB_UseTargets();
823                                 self = oldself;
824                         }
825                 }
826                 else
827                 {
828                         if(self.count)
829                         {
830                                 self.count = 0;
831
832                                 oldself = self;
833                                 self = self.enemy;
834                                 activator = self.pusher;
835                                 SUB_UseTargets();
836                                 self = oldself;
837                         }
838                 }
839         }
840 }
841
842 float laser_SendEntity(entity to, float fl)
843 {
844         WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
845         fl = fl - (fl & 0xC0); // use that bit to indicate finite length laser
846         if(self.spawnflags & 2)
847                 fl |= 0x80;
848         if(self.alpha)
849                 fl |= 0x40;
850         WriteByte(MSG_ENTITY, fl);
851         if(fl & 1)
852         {
853                 WriteCoord(MSG_ENTITY, self.origin_x);
854                 WriteCoord(MSG_ENTITY, self.origin_y);
855                 WriteCoord(MSG_ENTITY, self.origin_z);
856         }
857         if(fl & 8)
858         {
859                 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
860                 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
861                 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
862                 if(fl & 0x40)
863                         WriteByte(MSG_ENTITY, self.alpha * 255.0);
864                 WriteShort(MSG_ENTITY, self.cnt + 1);
865         }
866         if(fl & 2)
867         {
868                 if(fl & 0x80)
869                 {
870                         WriteCoord(MSG_ENTITY, self.enemy.origin_x);
871                         WriteCoord(MSG_ENTITY, self.enemy.origin_y);
872                         WriteCoord(MSG_ENTITY, self.enemy.origin_z);
873                 }
874                 else
875                 {
876                         WriteCoord(MSG_ENTITY, self.mangle_x);
877                         WriteCoord(MSG_ENTITY, self.mangle_y);
878                 }
879         }
880         if(fl & 4)
881                 WriteByte(MSG_ENTITY, self.state);
882         return 1;
883 }
884
885 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
886 Any object touching the beam will be hurt
887 Keys:
888 "target"
889  spawnfunc_target_position where the laser ends
890 "mdl"
891  name of beam end effect to use
892 "colormod"
893  color of the beam (default: red)
894 "dmg"
895  damage per second (-1 for a laser that kills immediately)
896 */
897 void laser_use()
898 {
899         self.state = !self.state;
900         self.SendFlags |= 4;
901         misc_laser_aim();
902 }
903
904 void spawnfunc_misc_laser()
905 {
906         if(self.mdl)
907         {
908                 if(self.mdl == "none")
909                         self.cnt = -1;
910                 else
911                         self.cnt = particleeffectnum(self.mdl);
912         }
913         else if(!self.cnt)
914         {
915                 if(self.dmg)
916                         self.cnt = particleeffectnum("laser_deadly");
917                 else
918                         self.cnt = -1;
919         }
920
921         if(self.colormod == '0 0 0')
922                 if(!self.alpha)
923                         self.colormod = '1 0 0';
924         if(!self.message)
925                 self.message = "saw the light";
926         self.think = misc_laser_think;
927         self.nextthink = time;
928         InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
929
930         self.effects = EF_NODEPTHTEST;
931         self.SendEntity = laser_SendEntity;
932         self.SendFlags = 15;
933         self.modelindex = 1;
934         self.model = "net_entity";
935         self.mangle = self.angles;
936
937         IFTARGETED
938         {
939                 self.use = laser_use;
940                 if(self.spawnflags & 1)
941                         self.state = 1;
942                 else
943                         self.state = 0;
944         }
945         else
946                 self.state = 1;
947 }
948
949 // tZorks trigger impulse / gravity
950 .float radius;
951 .float falloff;
952 .float strength;
953 .float lastpushtime;
954
955 // targeted (directional) mode
956 void trigger_impulse_touch1()
957 {
958         entity targ;
959     float pushdeltatime;
960     float str;
961
962         // FIXME: Better checking for what to push and not.
963         if (other.classname != "player")
964         if (other.classname != "corpse")
965         if (other.classname != "body")
966         if (other.classname != "gib")
967         if (other.classname != "missile")
968         if (other.classname != "casing")
969         if (other.classname != "grenade")
970         if (other.classname != "plasma")
971         if (other.classname != "plasma_prim")
972         if (other.classname != "plasma_chain")
973         if (other.classname != "droppedweapon")
974                 return;
975
976         if (other.deadflag && other.classname == "player")
977                 return;
978
979         EXACTTRIGGER_TOUCH;
980
981     targ = find(world, targetname, self.target);
982     if(!targ)
983     {
984         objerror("trigger_force without a (valid) .target!\n");
985         remove(self);
986         return;
987     }
988
989     if(self.falloff == 1)
990         str = (str / self.radius) * self.strength;
991     else if(self.falloff == 2)
992         str = (1 - (str / self.radius)) * self.strength;
993     else
994         str = self.strength;
995
996     pushdeltatime = time - other.lastpushtime;
997     if (pushdeltatime > 0.15) pushdeltatime = 0;
998     other.lastpushtime = time;
999     if(!pushdeltatime) return;
1000
1001     other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
1002 }
1003
1004 // Directionless (accelerator/decelerator) mode
1005 void trigger_impulse_touch2()
1006 {
1007     float pushdeltatime;
1008
1009         // FIXME: Better checking for what to push and not.
1010         if (other.classname != "player")
1011         if (other.classname != "corpse")
1012         if (other.classname != "body")
1013         if (other.classname != "gib")
1014         if (other.classname != "missile")
1015         if (other.classname != "casing")
1016         if (other.classname != "grenade")
1017         if (other.classname != "plasma")
1018         if (other.classname != "plasma_prim")
1019         if (other.classname != "plasma_chain")
1020         if (other.classname != "droppedweapon")
1021                 return;
1022
1023         if (other.deadflag && other.classname == "player")
1024                 return;
1025
1026         EXACTTRIGGER_TOUCH;
1027
1028     pushdeltatime = time - other.lastpushtime;
1029     if (pushdeltatime > 0.15) pushdeltatime = 0;
1030     other.lastpushtime = time;
1031     if(!pushdeltatime) return;
1032
1033     //if(self.strength > 1)
1034         other.velocity = other.velocity * (self.strength * pushdeltatime);
1035     //else
1036     //    other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
1037 }
1038
1039 // Spherical (gravity/repulsor) mode
1040 void trigger_impulse_touch3()
1041 {
1042     float pushdeltatime;
1043     float str;
1044
1045         // FIXME: Better checking for what to push and not.
1046         if (other.classname != "player")
1047         if (other.classname != "corpse")
1048         if (other.classname != "body")
1049         if (other.classname != "gib")
1050         if (other.classname != "missile")
1051         if (other.classname != "casing")
1052         if (other.classname != "grenade")
1053         if (other.classname != "plasma")
1054         if (other.classname != "plasma_prim")
1055         if (other.classname != "plasma_chain")
1056         if (other.classname != "droppedweapon")
1057                 return;
1058
1059         if (other.deadflag && other.classname == "player")
1060                 return;
1061
1062         EXACTTRIGGER_TOUCH;
1063
1064     pushdeltatime = time - other.lastpushtime;
1065     if (pushdeltatime > 0.15) pushdeltatime = 0;
1066     other.lastpushtime = time;
1067     if(!pushdeltatime) return;
1068
1069     setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1070
1071         str = min(self.radius, vlen(self.origin - other.origin));
1072
1073     if(self.falloff == 1)
1074         str = (1 - str / self.radius) * self.strength; // 1 in the inside
1075     else if(self.falloff == 2)
1076         str = (str / self.radius) * self.strength; // 0 in the inside
1077     else
1078         str = self.strength;
1079
1080     other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
1081 }
1082
1083 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
1084 -------- KEYS --------
1085 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
1086          If not, this trigger acts like a damper/accelerator field.
1087
1088 strength : This is how mutch force to add in the direction of .target each second
1089            when .target is set. If not, this is hoe mutch to slow down/accelerate
1090            someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
1091
1092 radius   : If set, act as a spherical device rather then a liniar one.
1093
1094 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
1095
1096 -------- NOTES --------
1097 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
1098 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
1099 */
1100
1101 void spawnfunc_trigger_impulse()
1102 {
1103         EXACTTRIGGER_INIT;
1104     if(self.radius)
1105     {
1106         if(!self.strength) self.strength = 2000;
1107         setorigin(self, self.origin);
1108         setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
1109         self.touch = trigger_impulse_touch3;
1110     }
1111     else
1112     {
1113         if(self.target)
1114         {
1115             if(!self.strength) self.strength = 950;
1116             self.touch = trigger_impulse_touch1;
1117         }
1118         else
1119         {
1120             if(!self.strength) self.strength = 0.9;
1121             self.touch = trigger_impulse_touch2;
1122         }
1123     }
1124 }
1125
1126 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1127 "Flip-flop" trigger gate... lets only every second trigger event through
1128 */
1129 void flipflop_use()
1130 {
1131         self.state = !self.state;
1132         if(self.state)
1133                 SUB_UseTargets();
1134 }
1135
1136 void spawnfunc_trigger_flipflop()
1137 {
1138         if(self.spawnflags & 1)
1139                 self.state = 1;
1140     self.use = flipflop_use;
1141 }
1142
1143 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1144 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1145 */
1146 void monoflop_use()
1147 {
1148         self.nextthink = time + self.wait;
1149         if(self.state)
1150                 return;
1151         self.state = 1;
1152         SUB_UseTargets();
1153 }
1154 void monoflop_fixed_use()
1155 {
1156         if(self.state)
1157                 return;
1158         self.nextthink = time + self.wait;
1159         self.state = 1;
1160         SUB_UseTargets();
1161 }
1162
1163 void monoflop_think()
1164 {
1165         self.state = 0;
1166         SUB_UseTargets();
1167 }
1168
1169 void spawnfunc_trigger_monoflop()
1170 {
1171         if(!self.wait)
1172                 self.wait = 1;
1173         if(self.spawnflags & 1)
1174                 self.use = monoflop_fixed_use;
1175         else
1176                 self.use = monoflop_use;
1177         self.think = monoflop_think;
1178         self.state = 0;
1179 }
1180
1181 void multivibrator_send()
1182 {
1183         float newstate;
1184         float cyclestart;
1185
1186         cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
1187
1188         newstate = (time < cyclestart + self.wait);
1189
1190         if(self.state != newstate)
1191                 SUB_UseTargets();
1192         self.state = newstate;
1193
1194         if(self.state)
1195                 self.nextthink = cyclestart + self.wait + 0.01;
1196         else
1197                 self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
1198 }
1199
1200 void multivibrator_toggle()
1201 {
1202         if(self.nextthink == 0)
1203         {
1204                 multivibrator_send();
1205         }
1206         else
1207         {
1208                 if(self.state)
1209                 {
1210                         SUB_UseTargets();
1211                         self.state = 0;
1212                 }
1213                 self.nextthink = 0;
1214         }
1215 }
1216
1217 /*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
1218 "Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
1219 -------- KEYS --------
1220 target: trigger all entities with this targetname when it goes off
1221 targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
1222 phase: offset of the timing
1223 wait: "on" cycle time (default: 1)
1224 respawntime: "off" cycle time (default: same as wait)
1225 -------- SPAWNFLAGS --------
1226 START_ON: assume it is already turned on (when targeted)
1227 */
1228 void spawnfunc_trigger_multivibrator()
1229 {
1230         if(!self.wait)
1231                 self.wait = 1;
1232         if(!self.respawntime)
1233                 self.respawntime = self.wait;
1234
1235         self.state = 0;
1236         self.use = multivibrator_toggle;
1237         self.think = multivibrator_send;
1238         self.nextthink = time;
1239
1240         IFTARGETED
1241         {
1242                 if(!(self.spawnflags & 1))
1243                         self.nextthink = 0; // wait for a trigger event
1244         }
1245         else
1246                 self.nextthink = time;
1247 }
1248
1249
1250 void follow_init()
1251 {
1252         entity src, dst;
1253         src = find(world, targetname, self.killtarget);
1254         dst = find(world, targetname, self.target);
1255
1256         if(!src || !dst)
1257         {
1258                 objerror("follow: could not find target/killtarget");
1259                 return;
1260         }
1261
1262         dst.movetype = MOVETYPE_FOLLOW;
1263         dst.aiment = src;
1264         dst.punchangle = src.angles;
1265         dst.view_ofs = dst.origin - src.origin;
1266         dst.v_angle = dst.angles - src.angles;
1267
1268         remove(self);
1269 }
1270
1271 void spawnfunc_misc_follow()
1272 {
1273         InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
1274 }