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