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