(untested) make rain and snow client side entities
[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)
489 {
490         WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
491         if(self.modelindex != 4.2)
492                 WriteShort(MSG_ENTITY, self.modelindex);
493         else
494                 WriteShort(MSG_ENTITY, 0);
495         WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
496         WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
497         WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
498         WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
499         WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
500         WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
501         WriteShort(MSG_ENTITY, self.cnt);
502         if(self.state)
503                 WriteCoord(MSG_ENTITY, self.impulse);
504         else
505                 WriteCoord(MSG_ENTITY, 0); // off
506         WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
507         WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
508         WriteCoord(MSG_ENTITY, self.waterlevel);
509         WriteCoord(MSG_ENTITY, self.count);
510         WriteByte(MSG_ENTITY, self.glow_color);
511         WriteString(MSG_ENTITY, self.noise);
512         return 1;
513 }
514
515 void pointparticles_use()
516 {
517         if(self.wait && time < self.nextthink)
518         {
519                 self.nextthink = time + self.wait; // extend the time
520                 return;
521         }
522
523         if(self.wait)
524                 self.nextthink = time + self.wait; // toggle back after a delay
525         self.state = !self.state;
526         self.Version += 1;
527 }
528
529 void pointparticles_think()
530 {
531         self.state = !self.state;
532         self.Version += 1;
533 }
534
535 void spawnfunc_func_pointparticles()
536 {
537         if(self.model != "")
538                 setmodel(self, self.model);
539         if(self.noise != "")
540                 precache_sound (self.noise);
541
542         self.effects = EF_NODEPTHTEST;
543         self.SendEntity = pointparticles_SendEntity;
544         self.Version = 1;
545         if(!self.modelindex)
546                 self.modelindex = 4.2;
547         self.model = "net_entity";
548         if(!self.cnt)
549                 self.cnt = particleeffectnum(self.mdl);
550         if(self.targetname != "")
551         {
552                 self.use = pointparticles_use;
553                 if(self.spawnflags & 1)
554                         self.state = 1;
555                 else
556                         self.state = 0;
557                 self.think = pointparticles_think;
558         }
559         else
560                 self.state = 1;
561 }
562
563 void spawnfunc_func_sparks()
564 {
565         // self.cnt is the amount of sparks that one burst will spawn
566         if(self.cnt < 1) {
567                 self.cnt = 25.0; // nice default value
568         }
569
570         // self.wait is the probability that a sparkthink will spawn a spark shower
571         // range: 0 - 1, but 0 makes little sense, so...
572         if(self.wait < 0.05) {
573                 self.wait = 0.25; // nice default value
574         }
575
576         self.count = self.cnt;
577         self.mins = '0 0 0';
578         self.maxs = '0 0 0';
579         self.velocity = '0 0 -1';
580         self.mdl = "func_sparks";
581         self.impulse = 0.1 / self.wait;
582         self.wait = 0;
583
584         spawnfunc_func_pointparticles();
585 }
586
587 float rainsnow_SendEntity(float to)
588 {
589         WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
590         WriteByte(MSG_ENTITY, self.state);
591         WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
592         WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
593         WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
594         WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
595         WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
596         WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
597         WriteShort(MSG_ENTITY, compressShortVector(self.dest));
598         WriteShort(MSG_ENTITY, self.count);
599         WriteByte(MSG_ENTITY, self.cnt);
600         return 1;
601 };
602
603 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
604 This is an invisible area like a trigger, which rain falls inside of.
605
606 Keys:
607 "velocity"
608  falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
609 "cnt"
610  sets color of rain (default 12 - white)
611 "count"
612  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
613 */
614 void spawnfunc_func_rain()
615 {
616         self.dest = self.velocity;
617         self.velocity = '0 0 0';
618         if (!self.dest)
619                 self.dest = '0 0 -700';
620         self.angles = '0 0 0';
621         self.movetype = MOVETYPE_NONE;
622         self.solid = SOLID_NOT;
623         SetBrushEntityModel();
624         self.model = "";
625         if (!self.cnt)
626                 self.cnt = 12;
627         if (!self.count)
628                 self.count = 2000;
629         self.count = 0.1 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
630         if (self.count < 1)
631         {
632                 remove(self);
633                 return;
634         }
635         if(self.count > 65535)
636                 self.count = 65535;
637
638         self.state = 1; // 1 is rain, 0 is snow
639         self.effects = EF_NODEPTHTEST;
640         self.SendEntity = rainsnow_SendEntity;
641         self.Version = 1;
642         self.modelindex = 1;
643         self.model = "net_entity";
644 };
645
646
647 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
648 This is an invisible area like a trigger, which snow falls inside of.
649
650 Keys:
651 "velocity"
652  falling direction (should be something like '0 0 -300', 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_snow()
659 {
660         self.dest = self.velocity;
661         self.velocity = '0 0 0';
662         if (!self.dest)
663                 self.dest = '0 0 -300';
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         {
676                 remove(self);
677                 return;
678         }
679         if(self.count > 65535)
680                 self.count = 65535;
681
682         self.state = 0; // 1 is rain, 0 is snow
683         self.effects = EF_NODEPTHTEST;
684         self.SendEntity = rainsnow_SendEntity;
685         self.Version = 1;
686         self.modelindex = 1;
687         self.model = "net_entity";
688 };
689
690
691 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
692 void misc_laser_think()
693 {
694         vector o;
695         if(!self.state)
696         {
697                 self.enemy = find(world, targetname, self.target);
698                 self.state = 1;
699         }
700         if(self.enemy)
701         {
702                 o = self.enemy.origin;
703         }
704         else
705         {
706                 makevectors(self.angles);
707                 o = self.origin + v_forward * MAX_SHOT_DISTANCE;
708         }
709
710         if(self.dmg)
711         {
712                 if(self.dmg < 0)
713                         FireRailgunBullet(self.origin, o, 100000, 0, DEATH_HURTTRIGGER);
714                 else
715                         FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, DEATH_HURTTRIGGER);
716         }
717
718         if(time > self.ltime)
719         {
720                 traceline(self.origin, o, MOVE_WORLDONLY, self);
721                 trailparticles(self, self.cnt, self.origin, trace_endpos);
722                 pointparticles(self.lip, trace_endpos, trace_plane_normal, 256 * frametime);
723                 self.ltime = time + self.wait;
724         }
725         self.nextthink = time;
726 }
727 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ?
728 Any object touching the beam will be hurt
729 Keys:
730 "target"
731  spawnfunc_target_position where the laser ends
732 "mdl"
733  name of beam effect to use
734 "dmg"
735  damage per second (-1 for a laser that kills immediately)
736 "wait"
737  delay between sending the particle effect
738 */
739 void spawnfunc_misc_laser()
740 {
741         if(self.mdl)
742         {
743                 self.cnt = particleeffectnum(self.mdl);
744                 self.lip = particleeffectnum(strcat(self.mdl, "_end"));
745         }
746         else
747         {
748                 self.cnt = particleeffectnum("misc_laser_beam");
749                 self.lip = particleeffectnum("misc_laser_beam_end");
750         }
751         if(!self.wait)
752                 self.wait = 1;
753         if(!self.message)
754                 self.message = "saw the light";
755         self.think = misc_laser_think;
756         self.nextthink = time;
757 }
758
759 // tZorks trigger impulse / gravity
760 .float radius;
761 .float falloff;
762 .float strength;
763 .float lastpushtime;
764
765 // targeted (directional) mode
766 void trigger_impulse_touch1()
767 {
768         entity targ;
769     float pushdeltatime;
770     float str;
771
772         // FIXME: Better checking for what to push and not.
773         if (other.classname != "player")
774         if (other.classname != "corpse")
775         if (other.classname != "body")
776         if (other.classname != "gib")
777         if (other.classname != "missile")
778         if (other.classname != "casing")
779         if (other.classname != "grenade")
780         if (other.classname != "plasma")
781         if (other.classname != "plasma_prim")
782         if (other.classname != "plasma_chain")
783         if (other.classname != "droppedweapon")
784                 return;
785
786         if (other.deadflag && other.classname == "player")
787                 return;
788
789         EXACTTRIGGER_TOUCH;
790
791     targ = find(world, targetname, self.target);
792     if(!targ)
793     {
794         objerror("trigger_force without a (valid) .target!\n");
795         remove(self);
796         return;
797     }
798
799     if(self.falloff == 1)
800         str = (str / self.radius) * self.strength;
801     else if(self.falloff == 2)
802         str = (1 - (str / self.radius)) * self.strength;
803     else
804         str = self.strength;
805
806     pushdeltatime = time - other.lastpushtime;
807     if (pushdeltatime > 0.15) pushdeltatime = 0;
808     other.lastpushtime = time;
809     if(!pushdeltatime) return;
810
811     other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
812 }
813
814 // Directionless (accelerator/decelerator) mode
815 void trigger_impulse_touch2()
816 {
817     float pushdeltatime;
818
819         // FIXME: Better checking for what to push and not.
820         if (other.classname != "player")
821         if (other.classname != "corpse")
822         if (other.classname != "body")
823         if (other.classname != "gib")
824         if (other.classname != "missile")
825         if (other.classname != "casing")
826         if (other.classname != "grenade")
827         if (other.classname != "plasma")
828         if (other.classname != "plasma_prim")
829         if (other.classname != "plasma_chain")
830         if (other.classname != "droppedweapon")
831                 return;
832
833         if (other.deadflag && other.classname == "player")
834                 return;
835
836         EXACTTRIGGER_TOUCH;
837
838     pushdeltatime = time - other.lastpushtime;
839     if (pushdeltatime > 0.15) pushdeltatime = 0;
840     other.lastpushtime = time;
841     if(!pushdeltatime) return;
842
843     //if(self.strength > 1)
844         other.velocity = other.velocity * (self.strength * pushdeltatime);
845     //else
846     //    other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
847 }
848
849 // Spherical (gravity/repulsor) mode
850 void trigger_impulse_touch3()
851 {
852     float pushdeltatime;
853     float str;
854
855         // FIXME: Better checking for what to push and not.
856         if (other.classname != "player")
857         if (other.classname != "corpse")
858         if (other.classname != "body")
859         if (other.classname != "gib")
860         if (other.classname != "missile")
861         if (other.classname != "casing")
862         if (other.classname != "grenade")
863         if (other.classname != "plasma")
864         if (other.classname != "plasma_prim")
865         if (other.classname != "plasma_chain")
866         if (other.classname != "droppedweapon")
867                 return;
868
869         if (other.deadflag && other.classname == "player")
870                 return;
871
872         EXACTTRIGGER_TOUCH;
873
874     pushdeltatime = time - other.lastpushtime;
875     if (pushdeltatime > 0.15) pushdeltatime = 0;
876     other.lastpushtime = time;
877     if(!pushdeltatime) return;
878
879     setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
880
881         str = min(self.radius, vlen(self.origin - other.origin));
882
883     if(self.falloff == 1)
884         str = (1 - str / self.radius) * self.strength; // 1 in the inside
885     else if(self.falloff == 2)
886         str = (str / self.radius) * self.strength; // 0 in the inside
887     else
888         str = self.strength;
889
890     other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
891 }
892
893 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
894 -------- KEYS --------
895 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
896          If not, this trigger acts like a damper/accelerator field.
897
898 strength : This is how mutch force to add in the direction of .target each second
899            when .target is set. If not, this is hoe mutch to slow down/accelerate
900            someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
901
902 radius   : If set, act as a spherical device rather then a liniar one.
903
904 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
905
906 -------- NOTES --------
907 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
908 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
909 */
910
911 void spawnfunc_trigger_impulse()
912 {
913         EXACTTRIGGER_INIT;
914     if(self.radius)
915     {
916         if(!self.strength) self.strength = 2000;
917         setorigin(self, self.origin);
918         setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
919         self.touch = trigger_impulse_touch3;
920     }
921     else
922     {
923         if(self.target)
924         {
925             if(!self.strength) self.strength = 950;
926             self.touch = trigger_impulse_touch1;
927         }
928         else
929         {
930             if(!self.strength) self.strength = 0.9;
931             self.touch = trigger_impulse_touch2;
932         }
933     }
934 }
935