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