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