]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/g_triggers.qc
more particles 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                 }
261         }
262 };
263
264
265 /*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
266 Variable sized trigger. Triggers once, then removes itself.  You must set the key "target" to the name of another object in the level that has a matching
267 "targetname".  If "health" is set, the trigger must be killed to activate.
268 If notouch is set, the trigger is only fired by other entities, not by touching.
269 if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
270 if "angle" is set, the trigger will only fire when someone is facing the direction of the angle.  Use "360" for an angle of 0.
271 sounds
272 1)      secret
273 2)      beep beep
274 3)      large switch
275 4)
276 set "message" to text string
277 */
278 void spawnfunc_trigger_once()
279 {
280         self.wait = -1;
281         spawnfunc_trigger_multiple();
282 };
283
284 //=============================================================================
285
286 /*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
287 This fixed size trigger cannot be touched, it can only be fired by other events.  It can contain killtargets, targets, delays, and messages.
288 */
289 void spawnfunc_trigger_relay()
290 {
291         self.use = SUB_UseTargets;
292 };
293
294 void delay_use()
295 {
296     self.think = SUB_UseTargets;
297     self.nextthink = self.wait;
298 }
299
300 void spawnfunc_trigger_delay()
301 {
302     if(!self.wait)
303         self.wait = 1;
304
305     self.use = delay_use;
306 }
307
308 //=============================================================================
309
310
311 void counter_use()
312 {
313         self.count = self.count - 1;
314         if (self.count < 0)
315                 return;
316
317         if (self.count != 0)
318         {
319                 if (activator.classname == "player"
320                 && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
321                 {
322                         if (self.count >= 4)
323                                 centerprint (activator, "There are more to go...");
324                         else if (self.count == 3)
325                                 centerprint (activator, "Only 3 more to go...");
326                         else if (self.count == 2)
327                                 centerprint (activator, "Only 2 more to go...");
328                         else
329                                 centerprint (activator, "Only 1 more to go...");
330                 }
331                 return;
332         }
333
334         if (activator.classname == "player"
335         && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
336                 centerprint(activator, "Sequence completed!");
337         self.enemy = activator;
338         multi_trigger ();
339 };
340
341 /*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
342 Acts as an intermediary for an action that takes multiple inputs.
343
344 If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
345
346 After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
347 */
348 void spawnfunc_trigger_counter()
349 {
350         self.wait = -1;
351         if (!self.count)
352                 self.count = 2;
353
354         self.use = counter_use;
355 };
356
357 .float triggerhurttime;
358 void trigger_hurt_touch()
359 {
360         // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
361         if (!other.owner)
362         {
363                 if (other.items & IT_KEY1 || other.items & IT_KEY2)     // reset flag
364                 {
365                         EXACTTRIGGER_TOUCH;
366                         other.pain_finished = min(other.pain_finished, time + 2);
367                 }
368                 else if (other.classname == "rune")                     // reset runes
369                 {
370                         EXACTTRIGGER_TOUCH;
371                         other.nextthink = min(other.nextthink, time + 1);
372                 }
373         }
374
375         if (other.takedamage)
376         if (other.triggerhurttime < time)
377         {
378                 EXACTTRIGGER_TOUCH;
379                 other.triggerhurttime = time + 1;
380                 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
381         }
382
383         return;
384 };
385
386 /*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
387 Any object touching this will be hurt
388 set dmg to damage amount
389 defalt dmg = 5
390 */
391 .entity trigger_hurt_next;
392 entity trigger_hurt_last;
393 entity trigger_hurt_first;
394 void spawnfunc_trigger_hurt()
395 {
396         EXACTTRIGGER_INIT;
397         self.touch = trigger_hurt_touch;
398         if (!self.dmg)
399                 self.dmg = 1000;
400         if (!self.message)
401                 self.message = "was in the wrong place.";
402
403         if(!trigger_hurt_first)
404                 trigger_hurt_first = self;
405         if(trigger_hurt_last)
406                 trigger_hurt_last.trigger_hurt_next = self;
407         trigger_hurt_last = self;
408 };
409
410 float trace_hits_box_a0, trace_hits_box_a1;
411
412 float trace_hits_box_1d(float end, float thmi, float thma)
413 {
414         if(end == 0)
415         {
416                 // just check if x is in range
417                 if(0 < thmi)
418                         return FALSE;
419                 if(0 > thma)
420                         return FALSE;
421         }
422         else
423         {
424                 // do the trace with respect to x
425                 // 0 -> end has to stay in thmi -> thma
426                 trace_hits_box_a0 = max(trace_hits_box_a0, min(thmi / end, thma / end));
427                 trace_hits_box_a1 = min(trace_hits_box_a1, max(thmi / end, thma / end));
428                 if(trace_hits_box_a0 > trace_hits_box_a1)
429                         return FALSE;
430         }
431         return TRUE;
432 }
433
434 float trace_hits_box(vector start, vector end, vector thmi, vector thma)
435 {
436         end -= start;
437         thmi -= start;
438         thma -= start;
439         // now it is a trace from 0 to end
440
441         trace_hits_box_a0 = 0;
442         trace_hits_box_a1 = 1;
443
444         if(!trace_hits_box_1d(end_x, thmi_x, thma_x))
445                 return FALSE;
446         if(!trace_hits_box_1d(end_y, thmi_y, thma_y))
447                 return FALSE;
448         if(!trace_hits_box_1d(end_z, thmi_z, thma_z))
449                 return FALSE;
450
451         return TRUE;
452 }
453
454 float tracebox_hits_box(vector start, vector mi, vector ma, vector end, vector thmi, vector thma)
455 {
456         return trace_hits_box(start, end, thmi - ma, thma - mi);
457 }
458
459 float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
460 {
461         entity th;
462
463         for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
464                 if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
465                         return TRUE;
466
467         return FALSE;
468 }
469
470
471 void target_speaker_use() {sound(self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);}
472
473 void spawnfunc_target_speaker()
474 {
475         if(self.noise)
476                 precache_sound (self.noise);
477         if(self.targetname)
478                 self.use = target_speaker_use;
479         else
480                 ambientsound (self.origin, self.noise, VOL_BASE, ATTN_STATIC);
481 };
482
483
484 void spawnfunc_func_stardust() {
485         self.effects = EF_STARDUST;
486 }
487
488 float pointparticles_SendEntity(entity to, float fl)
489 {
490         WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
491         WriteByte(MSG_ENTITY, fl);
492         if(fl & 2)
493         {
494                 print("state changed, sending: ", ftos(self.state), "\n");
495                 if(self.state)
496                         WriteCoord(MSG_ENTITY, self.impulse);
497                 else
498                         WriteCoord(MSG_ENTITY, 0); // off
499         }
500         if(fl & 4)
501         {
502                 WriteCoord(MSG_ENTITY, self.origin_x);
503                 WriteCoord(MSG_ENTITY, self.origin_y);
504                 WriteCoord(MSG_ENTITY, self.origin_z);
505         }
506         if(fl & 1)
507         {
508                 if(self.modelindex != 4.2)
509                 {
510                         WriteShort(MSG_ENTITY, self.modelindex);
511                         WriteCoord(MSG_ENTITY, self.mins_x);
512                         WriteCoord(MSG_ENTITY, self.mins_y);
513                         WriteCoord(MSG_ENTITY, self.mins_z);
514                         WriteCoord(MSG_ENTITY, self.maxs_x);
515                         WriteCoord(MSG_ENTITY, self.maxs_y);
516                         WriteCoord(MSG_ENTITY, self.maxs_z);
517                 }
518                 else
519                 {
520                         WriteShort(MSG_ENTITY, 0);
521                         WriteCoord(MSG_ENTITY, self.maxs_x);
522                         WriteCoord(MSG_ENTITY, self.maxs_y);
523                         WriteCoord(MSG_ENTITY, self.maxs_z);
524                 }
525                 WriteShort(MSG_ENTITY, self.cnt);
526                 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
527                 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
528                 WriteCoord(MSG_ENTITY, self.waterlevel);
529                 WriteCoord(MSG_ENTITY, self.count);
530                 WriteByte(MSG_ENTITY, self.glow_color);
531                 WriteString(MSG_ENTITY, self.noise);
532         }
533         return 1;
534 }
535
536 void pointparticles_use()
537 {
538         self.state = !self.state;
539         self.SendFlags |= 2;
540 }
541
542 void pointparticles_think()
543 {
544         if(self.origin != self.oldorigin)
545         {
546                 self.SendFlags |= 4;
547                 self.oldorigin = self.origin;
548         }
549         self.nextthink = time;
550 }
551
552 void spawnfunc_func_pointparticles()
553 {
554         if(self.model != "")
555                 setmodel(self, self.model);
556         if(self.noise != "")
557                 precache_sound (self.noise);
558
559         self.effects = EF_NODEPTHTEST;
560         self.SendEntity = pointparticles_SendEntity;
561         self.SendFlags = 7;
562         if(!self.modelindex)
563         {
564                 self.modelindex = 4.2;
565                 self.origin += self.mins;
566                 self.maxs -= self.mins;
567         }
568         self.model = "net_entity";
569         if(!self.cnt)
570                 self.cnt = particleeffectnum(self.mdl);
571         if(self.targetname != "")
572         {
573                 self.use = pointparticles_use;
574                 if(self.spawnflags & 1)
575                         self.state = 1;
576                 else
577                         self.state = 0;
578         }
579         else
580                 self.state = 1;
581         self.think = pointparticles_think;
582         self.nextthink = time;
583 }
584
585 void spawnfunc_func_sparks()
586 {
587         // self.cnt is the amount of sparks that one burst will spawn
588         if(self.cnt < 1) {
589                 self.cnt = 25.0; // nice default value
590         }
591
592         // self.wait is the probability that a sparkthink will spawn a spark shower
593         // range: 0 - 1, but 0 makes little sense, so...
594         if(self.wait < 0.05) {
595                 self.wait = 0.25; // nice default value
596         }
597
598         self.count = self.cnt;
599         self.mins = '0 0 0';
600         self.maxs = '0 0 0';
601         self.velocity = '0 0 -1';
602         self.mdl = "func_sparks";
603         self.impulse = 0.1 / self.wait;
604         self.wait = 0;
605
606         spawnfunc_func_pointparticles();
607 }
608
609 float rainsnow_SendEntity(float to)
610 {
611         WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
612         WriteByte(MSG_ENTITY, self.state);
613         WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
614         WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
615         WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
616         WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
617         WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
618         WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
619         WriteShort(MSG_ENTITY, compressShortVector(self.dest));
620         WriteShort(MSG_ENTITY, self.count);
621         WriteByte(MSG_ENTITY, self.cnt);
622         return 1;
623 };
624
625 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
626 This is an invisible area like a trigger, which rain falls inside of.
627
628 Keys:
629 "velocity"
630  falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
631 "cnt"
632  sets color of rain (default 12 - white)
633 "count"
634  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
635 */
636 void spawnfunc_func_rain()
637 {
638         self.dest = self.velocity;
639         self.velocity = '0 0 0';
640         if (!self.dest)
641                 self.dest = '0 0 -700';
642         self.angles = '0 0 0';
643         self.movetype = MOVETYPE_NONE;
644         self.solid = SOLID_NOT;
645         SetBrushEntityModel();
646         self.model = "";
647         if (!self.cnt)
648                 self.cnt = 12;
649         if (!self.count)
650                 self.count = 2000;
651         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
652         if (self.count < 1)
653                 self.count = 1;
654         if(self.count > 65535)
655                 self.count = 65535;
656
657         self.state = 1; // 1 is rain, 0 is snow
658         self.effects = EF_NODEPTHTEST;
659         self.SendEntity = rainsnow_SendEntity;
660         self.Version = 1;
661         self.modelindex = 1;
662         self.model = "net_entity";
663 };
664
665
666 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
667 This is an invisible area like a trigger, which snow falls inside of.
668
669 Keys:
670 "velocity"
671  falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
672 "cnt"
673  sets color of rain (default 12 - white)
674 "count"
675  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
676 */
677 void spawnfunc_func_snow()
678 {
679         self.dest = self.velocity;
680         self.velocity = '0 0 0';
681         if (!self.dest)
682                 self.dest = '0 0 -300';
683         self.angles = '0 0 0';
684         self.movetype = MOVETYPE_NONE;
685         self.solid = SOLID_NOT;
686         SetBrushEntityModel();
687         self.model = "";
688         if (!self.cnt)
689                 self.cnt = 12;
690         if (!self.count)
691                 self.count = 2000;
692         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
693         if (self.count < 1)
694                 self.count = 1;
695         if(self.count > 65535)
696                 self.count = 65535;
697
698         self.state = 0; // 1 is rain, 0 is snow
699         self.effects = EF_NODEPTHTEST;
700         self.SendEntity = rainsnow_SendEntity;
701         self.Version = 1;
702         self.modelindex = 1;
703         self.model = "net_entity";
704 };
705
706
707 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
708
709 void misc_laser_aim()
710 {
711         vector a;
712         if(self.enemy)
713         {
714                 a = vectoangles(self.enemy.origin - self.origin);
715                 a_x = -a_x;
716                 if(a != self.angles)
717                 {
718                         self.angles = a;
719                         self.SendFlags |= 2;
720                 }
721         }
722         if(self.origin != self.oldorigin)
723                 self.SendFlags |= 1;
724 }
725
726 void misc_laser_think()
727 {
728         vector o;
729
730         self.nextthink = time;
731
732         if(!self.count)
733         {
734                 self.enemy = find(world, targetname, self.target);
735                 self.count = 1;
736         }
737
738         if(!self.state)
739                 return;
740
741         misc_laser_aim();
742
743         makevectors(self.angles);
744         o = self.origin + 32768 * v_forward;
745
746         if(self.dmg)
747         {
748                 if(self.dmg < 0)
749                         FireRailgunBullet(self.origin, o, 100000, 0, DEATH_HURTTRIGGER);
750                 else
751                         FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, DEATH_HURTTRIGGER);
752         }
753 }
754
755 float laser_SendEntity(entity to, float fl)
756 {
757         WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
758         WriteByte(MSG_ENTITY, fl);
759         if(fl & 1)
760         {
761                 WriteCoord(MSG_ENTITY, self.origin_x);
762                 WriteCoord(MSG_ENTITY, self.origin_y);
763                 WriteCoord(MSG_ENTITY, self.origin_z);
764         }
765         if(fl & 8)
766         {
767                 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
768                 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
769                 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
770                 WriteShort(MSG_ENTITY, self.cnt);
771         }
772         if(fl & 2)
773         {
774                 WriteCoord(MSG_ENTITY, self.angles_x);
775                 WriteCoord(MSG_ENTITY, self.angles_y);
776         }
777         if(fl & 4)
778                 WriteByte(MSG_ENTITY, self.state);
779         return 1;
780 }
781
782 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON
783 Any object touching the beam will be hurt
784 Keys:
785 "target"
786  spawnfunc_target_position where the laser ends
787 "mdl"
788  name of beam end effect to use
789 "colormod"
790  color of the beam (default: red)
791 "dmg"
792  damage per second (-1 for a laser that kills immediately)
793 */
794 void laser_use()
795 {
796         self.state = !self.state;
797         self.SendFlags |= 4;
798         misc_laser_aim();
799 }
800
801 void spawnfunc_misc_laser()
802 {
803         if(self.mdl)
804         {
805                 self.cnt = particleeffectnum(strcat(self.mdl, "_end"));
806                 if(self.cnt < 0)
807                         self.cnt = particleeffectnum(self.mdl);
808         }
809         else
810         {
811                 self.cnt = particleeffectnum("misc_laser_beam_end");
812         }
813         if(self.colormod == '0 0 0')
814                 self.colormod = '1 0 0';
815         if(!self.message)
816                 self.message = "saw the light";
817         self.think = misc_laser_think;
818         self.nextthink = time;
819
820         self.effects = EF_NODEPTHTEST;
821         self.SendEntity = laser_SendEntity;
822         self.SendFlags = 15;
823         self.modelindex = 1;
824         self.model = "net_entity";
825
826         if(self.targetname != "")
827         {
828                 self.use = laser_use;
829                 if(self.spawnflags & 1)
830                         self.state = 1;
831                 else
832                         self.state = 0;
833         }
834         else
835                 self.state = 1;
836 }
837
838 // tZorks trigger impulse / gravity
839 .float radius;
840 .float falloff;
841 .float strength;
842 .float lastpushtime;
843
844 // targeted (directional) mode
845 void trigger_impulse_touch1()
846 {
847         entity targ;
848     float pushdeltatime;
849     float str;
850
851         // FIXME: Better checking for what to push and not.
852         if (other.classname != "player")
853         if (other.classname != "corpse")
854         if (other.classname != "body")
855         if (other.classname != "gib")
856         if (other.classname != "missile")
857         if (other.classname != "casing")
858         if (other.classname != "grenade")
859         if (other.classname != "plasma")
860         if (other.classname != "plasma_prim")
861         if (other.classname != "plasma_chain")
862         if (other.classname != "droppedweapon")
863                 return;
864
865         if (other.deadflag && other.classname == "player")
866                 return;
867
868         EXACTTRIGGER_TOUCH;
869
870     targ = find(world, targetname, self.target);
871     if(!targ)
872     {
873         objerror("trigger_force without a (valid) .target!\n");
874         remove(self);
875         return;
876     }
877
878     if(self.falloff == 1)
879         str = (str / self.radius) * self.strength;
880     else if(self.falloff == 2)
881         str = (1 - (str / self.radius)) * self.strength;
882     else
883         str = self.strength;
884
885     pushdeltatime = time - other.lastpushtime;
886     if (pushdeltatime > 0.15) pushdeltatime = 0;
887     other.lastpushtime = time;
888     if(!pushdeltatime) return;
889
890     other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
891 }
892
893 // Directionless (accelerator/decelerator) mode
894 void trigger_impulse_touch2()
895 {
896     float pushdeltatime;
897
898         // FIXME: Better checking for what to push and not.
899         if (other.classname != "player")
900         if (other.classname != "corpse")
901         if (other.classname != "body")
902         if (other.classname != "gib")
903         if (other.classname != "missile")
904         if (other.classname != "casing")
905         if (other.classname != "grenade")
906         if (other.classname != "plasma")
907         if (other.classname != "plasma_prim")
908         if (other.classname != "plasma_chain")
909         if (other.classname != "droppedweapon")
910                 return;
911
912         if (other.deadflag && other.classname == "player")
913                 return;
914
915         EXACTTRIGGER_TOUCH;
916
917     pushdeltatime = time - other.lastpushtime;
918     if (pushdeltatime > 0.15) pushdeltatime = 0;
919     other.lastpushtime = time;
920     if(!pushdeltatime) return;
921
922     //if(self.strength > 1)
923         other.velocity = other.velocity * (self.strength * pushdeltatime);
924     //else
925     //    other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
926 }
927
928 // Spherical (gravity/repulsor) mode
929 void trigger_impulse_touch3()
930 {
931     float pushdeltatime;
932     float str;
933
934         // FIXME: Better checking for what to push and not.
935         if (other.classname != "player")
936         if (other.classname != "corpse")
937         if (other.classname != "body")
938         if (other.classname != "gib")
939         if (other.classname != "missile")
940         if (other.classname != "casing")
941         if (other.classname != "grenade")
942         if (other.classname != "plasma")
943         if (other.classname != "plasma_prim")
944         if (other.classname != "plasma_chain")
945         if (other.classname != "droppedweapon")
946                 return;
947
948         if (other.deadflag && other.classname == "player")
949                 return;
950
951         EXACTTRIGGER_TOUCH;
952
953     pushdeltatime = time - other.lastpushtime;
954     if (pushdeltatime > 0.15) pushdeltatime = 0;
955     other.lastpushtime = time;
956     if(!pushdeltatime) return;
957
958     setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
959
960         str = min(self.radius, vlen(self.origin - other.origin));
961
962     if(self.falloff == 1)
963         str = (1 - str / self.radius) * self.strength; // 1 in the inside
964     else if(self.falloff == 2)
965         str = (str / self.radius) * self.strength; // 0 in the inside
966     else
967         str = self.strength;
968
969     other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
970 }
971
972 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
973 -------- KEYS --------
974 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
975          If not, this trigger acts like a damper/accelerator field.
976
977 strength : This is how mutch force to add in the direction of .target each second
978            when .target is set. If not, this is hoe mutch to slow down/accelerate
979            someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
980
981 radius   : If set, act as a spherical device rather then a liniar one.
982
983 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
984
985 -------- NOTES --------
986 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
987 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
988 */
989
990 void spawnfunc_trigger_impulse()
991 {
992         EXACTTRIGGER_INIT;
993     if(self.radius)
994     {
995         if(!self.strength) self.strength = 2000;
996         setorigin(self, self.origin);
997         setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
998         self.touch = trigger_impulse_touch3;
999     }
1000     else
1001     {
1002         if(self.target)
1003         {
1004             if(!self.strength) self.strength = 950;
1005             self.touch = trigger_impulse_touch1;
1006         }
1007         else
1008         {
1009             if(!self.strength) self.strength = 0.9;
1010             self.touch = trigger_impulse_touch2;
1011         }
1012     }
1013 }
1014
1015 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
1016 "Flip-flop" trigger gate... lets only every second trigger event through
1017 */
1018 void flipflop_use()
1019 {
1020         self.state = !self.state;
1021         if(self.state)
1022                 SUB_UseTargets();
1023 }
1024
1025 void spawnfunc_trigger_flipflop()
1026 {
1027         if(self.spawnflags & 1)
1028                 self.state = 1;
1029     self.use = flipflop_use;
1030 }
1031
1032 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
1033 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
1034 */
1035 void monoflop_use()
1036 {
1037         self.nextthink = time + self.wait;
1038         if(self.state)
1039                 return;
1040         self.state = 1;
1041         SUB_UseTargets();
1042 }
1043 void monoflop_fixed_use()
1044 {
1045         if(self.state)
1046                 return;
1047         self.nextthink = time + self.wait;
1048         self.state = 1;
1049         SUB_UseTargets();
1050 }
1051
1052 void monoflop_think()
1053 {
1054         self.state = 0;
1055         SUB_UseTargets();
1056 }
1057
1058 void spawnfunc_trigger_monoflop()
1059 {
1060         if(!self.wait)
1061                 self.wait = 1;
1062         if(self.spawnflags & 1)
1063                 self.use = monoflop_fixed_use;
1064         else
1065                 self.use = monoflop_use;
1066         self.think = monoflop_think;
1067         self.state = 0;
1068 }
1069
1070 void follow_think()
1071 {
1072         entity src, dst;
1073         src = find(world, targetname, self.killtarget);
1074         dst = find(world, targetname, self.target);
1075
1076         if(!src || !dst)
1077         {
1078                 objerror("follow: could not find target/killtarget");
1079                 return;
1080         }
1081
1082         dst.movetype = MOVETYPE_FOLLOW;
1083         dst.aiment = src;
1084         dst.punchangle = src.angles;
1085         dst.view_ofs = dst.origin - src.origin;
1086         dst.v_angle = dst.angles - src.angles;
1087
1088         remove(self);
1089 }
1090
1091 void spawnfunc_misc_follow()
1092 {
1093         self.think = follow_think;
1094         self.nextthink = time;
1095 }