]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/g_triggers.qc
fix misc_laser aiming; do not remove too small snow/rain ents
[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                 if(self.state)
495                         WriteCoord(MSG_ENTITY, self.impulse);
496                 else
497                         WriteCoord(MSG_ENTITY, 0); // off
498         }
499         if(fl & 1)
500         {
501                 if(self.modelindex != 4.2)
502                         WriteShort(MSG_ENTITY, self.modelindex);
503                 else
504                         WriteShort(MSG_ENTITY, 0);
505                 WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
506                 WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
507                 WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
508                 WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
509                 WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
510                 WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
511                 WriteShort(MSG_ENTITY, self.cnt);
512                 WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
513                 WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
514                 WriteCoord(MSG_ENTITY, self.waterlevel);
515                 WriteCoord(MSG_ENTITY, self.count);
516                 WriteByte(MSG_ENTITY, self.glow_color);
517                 WriteString(MSG_ENTITY, self.noise);
518         }
519         return 1;
520 }
521
522 void pointparticles_use()
523 {
524         self.state = !self.state;
525         self.SendFlags |= 2;
526 }
527
528 void spawnfunc_func_pointparticles()
529 {
530         if(self.model != "")
531                 setmodel(self, self.model);
532         if(self.noise != "")
533                 precache_sound (self.noise);
534
535         self.effects = EF_NODEPTHTEST;
536         self.SendEntity = pointparticles_SendEntity;
537         self.SendFlags = 3;
538         if(!self.modelindex)
539                 self.modelindex = 4.2;
540         self.model = "net_entity";
541         if(!self.cnt)
542                 self.cnt = particleeffectnum(self.mdl);
543         if(self.targetname != "")
544         {
545                 self.use = pointparticles_use;
546                 if(self.spawnflags & 1)
547                         self.state = 1;
548                 else
549                         self.state = 0;
550         }
551         else
552                 self.state = 1;
553 }
554
555 void spawnfunc_func_sparks()
556 {
557         // self.cnt is the amount of sparks that one burst will spawn
558         if(self.cnt < 1) {
559                 self.cnt = 25.0; // nice default value
560         }
561
562         // self.wait is the probability that a sparkthink will spawn a spark shower
563         // range: 0 - 1, but 0 makes little sense, so...
564         if(self.wait < 0.05) {
565                 self.wait = 0.25; // nice default value
566         }
567
568         self.count = self.cnt;
569         self.mins = '0 0 0';
570         self.maxs = '0 0 0';
571         self.velocity = '0 0 -1';
572         self.mdl = "func_sparks";
573         self.impulse = 0.1 / self.wait;
574         self.wait = 0;
575
576         spawnfunc_func_pointparticles();
577 }
578
579 float rainsnow_SendEntity(float to)
580 {
581         WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
582         WriteByte(MSG_ENTITY, self.state);
583         WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
584         WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
585         WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
586         WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
587         WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
588         WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
589         WriteShort(MSG_ENTITY, compressShortVector(self.dest));
590         WriteShort(MSG_ENTITY, self.count);
591         WriteByte(MSG_ENTITY, self.cnt);
592         return 1;
593 };
594
595 /*QUAKED spawnfunc_func_rain (0 .5 .8) ?
596 This is an invisible area like a trigger, which rain falls inside of.
597
598 Keys:
599 "velocity"
600  falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
601 "cnt"
602  sets color of rain (default 12 - white)
603 "count"
604  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
605 */
606 void spawnfunc_func_rain()
607 {
608         self.dest = self.velocity;
609         self.velocity = '0 0 0';
610         if (!self.dest)
611                 self.dest = '0 0 -700';
612         self.angles = '0 0 0';
613         self.movetype = MOVETYPE_NONE;
614         self.solid = SOLID_NOT;
615         SetBrushEntityModel();
616         self.model = "";
617         if (!self.cnt)
618                 self.cnt = 12;
619         if (!self.count)
620                 self.count = 2000;
621         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
622         if (self.count < 1)
623                 self.count = 1;
624         if(self.count > 65535)
625                 self.count = 65535;
626
627         self.state = 1; // 1 is rain, 0 is snow
628         self.effects = EF_NODEPTHTEST;
629         self.SendEntity = rainsnow_SendEntity;
630         self.Version = 1;
631         self.modelindex = 1;
632         self.model = "net_entity";
633 };
634
635
636 /*QUAKED spawnfunc_func_snow (0 .5 .8) ?
637 This is an invisible area like a trigger, which snow falls inside of.
638
639 Keys:
640 "velocity"
641  falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
642 "cnt"
643  sets color of rain (default 12 - white)
644 "count"
645  adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
646 */
647 void spawnfunc_func_snow()
648 {
649         self.dest = self.velocity;
650         self.velocity = '0 0 0';
651         if (!self.dest)
652                 self.dest = '0 0 -300';
653         self.angles = '0 0 0';
654         self.movetype = MOVETYPE_NONE;
655         self.solid = SOLID_NOT;
656         SetBrushEntityModel();
657         self.model = "";
658         if (!self.cnt)
659                 self.cnt = 12;
660         if (!self.count)
661                 self.count = 2000;
662         self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
663         if (self.count < 1)
664                 self.count = 1;
665         if(self.count > 65535)
666                 self.count = 65535;
667
668         self.state = 0; // 1 is rain, 0 is snow
669         self.effects = EF_NODEPTHTEST;
670         self.SendEntity = rainsnow_SendEntity;
671         self.Version = 1;
672         self.modelindex = 1;
673         self.model = "net_entity";
674 };
675
676
677 void FireRailgunBullet (vector start, vector end, float bdamage, float bforce, float deathtype);
678
679 void misc_laser_aim()
680 {
681         vector a;
682         if(self.enemy)
683         {
684                 a = vectoangles(self.enemy.origin - self.origin);
685                 a_x = -a_x;
686                 if(a != self.angles)
687                 {
688                         self.angles = a;
689                         self.SendFlags |= 2;
690                 }
691         }
692 }
693
694 void misc_laser_think()
695 {
696         vector o;
697
698         self.nextthink = time;
699
700         if(!self.count)
701         {
702                 self.enemy = find(world, targetname, self.target);
703                 self.count = 1;
704         }
705
706         if(!self.state)
707                 return;
708
709         misc_laser_aim();
710
711         makevectors(self.angles);
712         o = self.origin + 32768 * v_forward;
713
714         if(self.dmg)
715         {
716                 if(self.dmg < 0)
717                         FireRailgunBullet(self.origin, o, 100000, 0, DEATH_HURTTRIGGER);
718                 else
719                         FireRailgunBullet(self.origin, o, self.dmg * frametime, 0, DEATH_HURTTRIGGER);
720         }
721 }
722
723 float laser_SendEntity(entity to, float fl)
724 {
725         WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
726         WriteByte(MSG_ENTITY, fl);
727         if(fl & 1)
728         {
729                 WriteCoord(MSG_ENTITY, self.origin_x);
730                 WriteCoord(MSG_ENTITY, self.origin_y);
731                 WriteCoord(MSG_ENTITY, self.origin_z);
732                 WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
733                 WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
734                 WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
735                 WriteShort(MSG_ENTITY, self.cnt);
736         }
737         if(fl & 2)
738         {
739                 WriteCoord(MSG_ENTITY, self.angles_x);
740                 WriteCoord(MSG_ENTITY, self.angles_y);
741         }
742         if(fl & 4)
743                 WriteByte(MSG_ENTITY, self.state);
744         return 1;
745 }
746
747 /*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON
748 Any object touching the beam will be hurt
749 Keys:
750 "target"
751  spawnfunc_target_position where the laser ends
752 "mdl"
753  name of beam end effect to use
754 "colormod"
755  color of the beam (default: red)
756 "dmg"
757  damage per second (-1 for a laser that kills immediately)
758 */
759 void laser_use()
760 {
761         self.state = !self.state;
762         self.SendFlags |= 4;
763         misc_laser_aim();
764 }
765
766 void spawnfunc_misc_laser()
767 {
768         if(self.mdl)
769         {
770                 self.cnt = particleeffectnum(strcat(self.mdl, "_end"));
771                 if(self.cnt < 0)
772                         self.cnt = particleeffectnum(self.mdl);
773         }
774         else
775         {
776                 self.cnt = particleeffectnum("misc_laser_beam_end");
777         }
778         if(self.colormod == '0 0 0')
779                 self.colormod = '1 0 0';
780         if(!self.message)
781                 self.message = "saw the light";
782         self.think = misc_laser_think;
783         self.nextthink = time;
784
785         self.effects = EF_NODEPTHTEST;
786         self.SendEntity = laser_SendEntity;
787         self.SendFlags = 7;
788         self.modelindex = 1;
789         self.model = "net_entity";
790
791         if(self.targetname != "")
792         {
793                 self.use = laser_use;
794                 if(self.spawnflags & 1)
795                         self.state = 1;
796                 else
797                         self.state = 0;
798         }
799         else
800                 self.state = 1;
801 }
802
803 // tZorks trigger impulse / gravity
804 .float radius;
805 .float falloff;
806 .float strength;
807 .float lastpushtime;
808
809 // targeted (directional) mode
810 void trigger_impulse_touch1()
811 {
812         entity targ;
813     float pushdeltatime;
814     float str;
815
816         // FIXME: Better checking for what to push and not.
817         if (other.classname != "player")
818         if (other.classname != "corpse")
819         if (other.classname != "body")
820         if (other.classname != "gib")
821         if (other.classname != "missile")
822         if (other.classname != "casing")
823         if (other.classname != "grenade")
824         if (other.classname != "plasma")
825         if (other.classname != "plasma_prim")
826         if (other.classname != "plasma_chain")
827         if (other.classname != "droppedweapon")
828                 return;
829
830         if (other.deadflag && other.classname == "player")
831                 return;
832
833         EXACTTRIGGER_TOUCH;
834
835     targ = find(world, targetname, self.target);
836     if(!targ)
837     {
838         objerror("trigger_force without a (valid) .target!\n");
839         remove(self);
840         return;
841     }
842
843     if(self.falloff == 1)
844         str = (str / self.radius) * self.strength;
845     else if(self.falloff == 2)
846         str = (1 - (str / self.radius)) * self.strength;
847     else
848         str = self.strength;
849
850     pushdeltatime = time - other.lastpushtime;
851     if (pushdeltatime > 0.15) pushdeltatime = 0;
852     other.lastpushtime = time;
853     if(!pushdeltatime) return;
854
855     other.velocity = other.velocity + normalize(targ.origin - self.origin) * self.strength * pushdeltatime;
856 }
857
858 // Directionless (accelerator/decelerator) mode
859 void trigger_impulse_touch2()
860 {
861     float pushdeltatime;
862
863         // FIXME: Better checking for what to push and not.
864         if (other.classname != "player")
865         if (other.classname != "corpse")
866         if (other.classname != "body")
867         if (other.classname != "gib")
868         if (other.classname != "missile")
869         if (other.classname != "casing")
870         if (other.classname != "grenade")
871         if (other.classname != "plasma")
872         if (other.classname != "plasma_prim")
873         if (other.classname != "plasma_chain")
874         if (other.classname != "droppedweapon")
875                 return;
876
877         if (other.deadflag && other.classname == "player")
878                 return;
879
880         EXACTTRIGGER_TOUCH;
881
882     pushdeltatime = time - other.lastpushtime;
883     if (pushdeltatime > 0.15) pushdeltatime = 0;
884     other.lastpushtime = time;
885     if(!pushdeltatime) return;
886
887     //if(self.strength > 1)
888         other.velocity = other.velocity * (self.strength * pushdeltatime);
889     //else
890     //    other.velocity = other.velocity - (other.velocity * self.strength * pushdeltatime);
891 }
892
893 // Spherical (gravity/repulsor) mode
894 void trigger_impulse_touch3()
895 {
896     float pushdeltatime;
897     float str;
898
899         // FIXME: Better checking for what to push and not.
900         if (other.classname != "player")
901         if (other.classname != "corpse")
902         if (other.classname != "body")
903         if (other.classname != "gib")
904         if (other.classname != "missile")
905         if (other.classname != "casing")
906         if (other.classname != "grenade")
907         if (other.classname != "plasma")
908         if (other.classname != "plasma_prim")
909         if (other.classname != "plasma_chain")
910         if (other.classname != "droppedweapon")
911                 return;
912
913         if (other.deadflag && other.classname == "player")
914                 return;
915
916         EXACTTRIGGER_TOUCH;
917
918     pushdeltatime = time - other.lastpushtime;
919     if (pushdeltatime > 0.15) pushdeltatime = 0;
920     other.lastpushtime = time;
921     if(!pushdeltatime) return;
922
923     setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
924
925         str = min(self.radius, vlen(self.origin - other.origin));
926
927     if(self.falloff == 1)
928         str = (1 - str / self.radius) * self.strength; // 1 in the inside
929     else if(self.falloff == 2)
930         str = (str / self.radius) * self.strength; // 0 in the inside
931     else
932         str = self.strength;
933
934     other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
935 }
936
937 /*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
938 -------- KEYS --------
939 target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
940          If not, this trigger acts like a damper/accelerator field.
941
942 strength : This is how mutch force to add in the direction of .target each second
943            when .target is set. If not, this is hoe mutch to slow down/accelerate
944            someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
945
946 radius   : If set, act as a spherical device rather then a liniar one.
947
948 falloff : 0 = none, 1 = liniar, 2 = inverted liniar
949
950 -------- NOTES --------
951 Use a brush textured with common/origin in the trigger entity to determine the origin of the force
952 in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
953 */
954
955 void spawnfunc_trigger_impulse()
956 {
957         EXACTTRIGGER_INIT;
958     if(self.radius)
959     {
960         if(!self.strength) self.strength = 2000;
961         setorigin(self, self.origin);
962         setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
963         self.touch = trigger_impulse_touch3;
964     }
965     else
966     {
967         if(self.target)
968         {
969             if(!self.strength) self.strength = 950;
970             self.touch = trigger_impulse_touch1;
971         }
972         else
973         {
974             if(!self.strength) self.strength = 0.9;
975             self.touch = trigger_impulse_touch2;
976         }
977     }
978 }
979
980 /*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
981 "Flip-flop" trigger gate... lets only every second trigger event through
982 */
983 void flipflop_use()
984 {
985         self.state = !self.state;
986         if(self.state)
987                 SUB_UseTargets();
988 }
989
990 void spawnfunc_trigger_flipflop()
991 {
992         if(self.spawnflags & 1)
993                 self.state = 1;
994     self.use = flipflop_use;
995 }
996
997 /*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
998 "Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
999 */
1000 void monoflop_use()
1001 {
1002         self.nextthink = time + self.wait;
1003         if(self.state)
1004                 return;
1005         self.state = 1;
1006         SUB_UseTargets();
1007 }
1008
1009 void monoflop_think()
1010 {
1011         self.state = 0;
1012         SUB_UseTargets();
1013 }
1014
1015 void spawnfunc_trigger_monoflop()
1016 {
1017         if(!self.wait)
1018                 self.wait = 1;
1019     self.use = monoflop_use;
1020     self.think = monoflop_think;
1021         self.state = 0;
1022 }