new moving platforms func_vectormamamam (adds up origins of other movers) and func_fo...
[divverent/nexuiz.git] / data / qcsrc / server / t_plats.qc
1
2 float   STATE_TOP               = 0;
3 float   STATE_BOTTOM    = 1;
4 float   STATE_UP                = 2;
5 float   STATE_DOWN              = 3;
6
7 .entity trigger_field;
8 //.float dmgtime;
9 .float dmgtime2;
10
11 void() plat_center_touch;
12 void() plat_outside_touch;
13 void() plat_trigger_use;
14 void() plat_go_up;
15 void() plat_go_down;
16 void() plat_crush;
17 float PLAT_LOW_TRIGGER = 1;
18
19 void plat_spawn_inside_trigger()
20 {
21         local entity trigger;
22         local vector tmin, tmax;
23
24         trigger = spawn();
25         trigger.touch = plat_center_touch;
26         trigger.movetype = MOVETYPE_NONE;
27         trigger.solid = SOLID_TRIGGER;
28         trigger.enemy = self;
29
30         tmin = self.absmin + '25 25 0';
31         tmax = self.absmax - '25 25 -8';
32         tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8);
33         if (self.spawnflags & PLAT_LOW_TRIGGER)
34                 tmax_z = tmin_z + 8;
35
36         if (self.size_x <= 50)
37         {
38                 tmin_x = (self.mins_x + self.maxs_x) / 2;
39                 tmax_x = tmin_x + 1;
40         }
41         if (self.size_y <= 50)
42         {
43                 tmin_y = (self.mins_y + self.maxs_y) / 2;
44                 tmax_y = tmin_y + 1;
45         }
46
47         setsize (trigger, tmin, tmax);
48 };
49
50 void plat_hit_top()
51 {
52         sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
53         self.state = 1;
54         self.think = plat_go_down;
55         self.nextthink = self.ltime + 3;
56 };
57
58 void plat_hit_bottom()
59 {
60         sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
61         self.state = 2;
62 };
63
64 void plat_go_down()
65 {
66         sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
67         self.state = 3;
68         SUB_CalcMove (self.pos2, self.speed, plat_hit_bottom);
69 };
70
71 void plat_go_up()
72 {
73         sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
74         self.state = 4;
75         SUB_CalcMove (self.pos1, self.speed, plat_hit_top);
76 };
77
78 void plat_center_touch()
79 {
80         if not(other.iscreature)
81                 return;
82
83         if (other.health <= 0)
84                 return;
85
86         self = self.enemy;
87         if (self.state == 2)
88                 plat_go_up ();
89         else if (self.state == 1)
90                 self.nextthink = self.ltime + 1;        // delay going down
91 };
92
93 void plat_outside_touch()
94 {
95         if not(other.iscreature)
96                 return;
97
98         if (other.health <= 0)
99                 return;
100
101         self = self.enemy;
102         if (self.state == 1)
103                 plat_go_down ();
104 };
105
106 void plat_trigger_use()
107 {
108         if (self.think)
109                 return;         // already activated
110         plat_go_down();
111 };
112
113
114 void plat_crush()
115 {
116     if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
117         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
118     } else {
119         if((self.dmg) && (other.takedamage != DAMAGE_NO)) {   // Shall we bite?
120             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
121             // Gib dead/dying stuff
122             if(other.deadflag != DEAD_NO)
123                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
124         }
125
126         if (self.state == 4)
127             plat_go_down ();
128         else if (self.state == 3)
129             plat_go_up ();
130         else
131             objerror ("plat_crush: bad self.state\n");
132     }
133 };
134
135 void plat_use()
136 {
137         self.use = SUB_Null;
138         if (self.state != 4)
139                 objerror ("plat_use: not in up state");
140         plat_go_down();
141 };
142
143 void plat_init_movedown()
144 {
145         setorigin (self, self.pos2);
146         self.state = 2;
147 }
148
149 .string sound1, sound2;
150
151 void spawnfunc_path_corner() { };
152 void spawnfunc_func_plat()
153 {
154         if (!self.t_length)
155                 self.t_length = 80;
156         if (!self.t_width)
157                 self.t_width = 10;
158
159         if (self.sounds == 0)
160                 self.sounds = 2;
161
162     if(self.spawnflags & 4)
163         self.dmg = 10000;
164
165     if(self.dmg && (!self.message))
166                 self.message = "was squished";
167     if(self.dmg && (!self.message2))
168                 self.message2 = "was squished by";
169
170         if (self.sounds == 1)
171         {
172                 precache_sound ("plats/plat1.wav");
173                 precache_sound ("plats/plat2.wav");
174                 self.noise = "plats/plat1.wav";
175                 self.noise1 = "plats/plat2.wav";
176         }
177
178         if (self.sounds == 2)
179         {
180                 precache_sound ("plats/medplat1.wav");
181                 precache_sound ("plats/medplat2.wav");
182                 self.noise = "plats/medplat1.wav";
183                 self.noise1 = "plats/medplat2.wav";
184         }
185
186         if (self.sound1)
187         {
188                 precache_sound (self.sound1);
189                 self.noise = self.sound1;
190         }
191         if (self.sound2)
192         {
193                 precache_sound (self.sound2);
194                 self.noise1 = self.sound2;
195         }
196
197         self.mangle = self.angles;
198         self.angles = '0 0 0';
199
200         self.classname = "plat";
201         if not(InitMovingBrushTrigger())
202                 return;
203         self.effects |= EF_LOWPRECISION;
204         setsize (self, self.mins , self.maxs);
205
206         self.blocked = plat_crush;
207
208         if (!self.speed)
209                 self.speed = 150;
210
211         self.pos1 = self.origin;
212         self.pos2 = self.origin;
213         self.pos2_z = self.origin_z - self.size_z + 8;
214
215         self.use = plat_trigger_use;
216
217         plat_spawn_inside_trigger ();   // the "start moving" trigger
218
219         IFTARGETED
220         {
221                 self.state = 4;
222                 self.use = plat_use;
223         }
224         else
225                 InitializeEntity(self, plat_init_movedown, INITPRIO_SETLOCATION);
226 };
227
228
229 void() train_next;
230 void train_wait()
231 {
232         self.think = train_next;
233         self.nextthink = self.ltime + self.wait;
234
235         if(self.noise != "")
236                 sound(self, CHAN_TRIGGER, STR_MISC_NULL_WAV, VOL_BASE, ATTN_NORM);
237 };
238
239 void train_next()
240 {
241         local entity targ;
242         targ = find(world, targetname, self.target);
243         self.target = targ.target;
244         if (!self.target)
245                 objerror("train_next: no next target");
246         self.wait = targ.wait;
247         if (!self.wait)
248                 self.wait = 0.1;
249         if(self.wait < 0)
250         {
251                 if (targ.speed)
252                         SUB_CalcMove(targ.origin - self.mins, targ.speed, train_next);
253                 else
254                         SUB_CalcMove(targ.origin - self.mins, self.speed, train_next);
255         }
256         else
257         {
258                 if (targ.speed)
259                         SUB_CalcMove(targ.origin - self.mins, targ.speed, train_wait);
260                 else
261                         SUB_CalcMove(targ.origin - self.mins, self.speed, train_wait);
262         }
263
264         if(self.noise != "")
265                 sound(self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
266 };
267
268 void func_train_find()
269 {
270         local entity targ;
271         targ = find(world, targetname, self.target);
272         self.target = targ.target;
273         if (!self.target)
274                 objerror("func_train_find: no next target");
275         setorigin(self, targ.origin - self.mins);
276         self.nextthink = self.ltime + 1;
277         self.think = train_next;
278 };
279
280 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
281 Ridable platform, targets spawnfunc_path_corner path to follow.
282 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
283 target : targetname of first spawnfunc_path_corner (starts here)
284 */
285 void spawnfunc_func_train()
286 {
287         if (self.noise != "")
288                 precache_sound(self.noise);
289
290         if (!self.target)
291                 objerror("func_train without a target");
292         if (!self.speed)
293                 self.speed = 100;
294
295         if not(InitMovingBrushTrigger())
296                 return;
297         self.effects |= EF_LOWPRECISION;
298
299         // wait for targets to spawn
300         InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
301 };
302
303
304
305 void rotating_blocked()
306 {
307
308     if(self.dmg && other.takedamage != DAMAGE_NO) {
309         if(self.dmgtime2 < time) {
310             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
311             self.dmgtime2 = time + self.dmgtime;
312         }
313
314         // Gib dead/dying stuff
315         if(other.deadflag != DEAD_NO)
316             Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
317     }
318
319
320 }
321
322 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
323 Brush model that spins in place on one axis (default Z).
324 speed   : speed to rotate (in degrees per second)
325 noise   : path/name of looping .wav file to play.
326 dmg     : Do this mutch dmg every .dmgtime intervall when blocked
327 dmgtime : See above.
328 */
329
330 void spawnfunc_func_rotating()
331 {
332         if (self.noise != "")
333         {
334                 precache_sound(self.noise);
335                 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
336         }
337         if (!self.speed)
338                 self.speed = 100;
339         // FIXME: test if this turns the right way, then remove this comment (negate as needed)
340         if (self.spawnflags & 4) // X (untested)
341                 self.avelocity = '0 0 1' * self.speed;
342         // FIXME: test if this turns the right way, then remove this comment (negate as needed)
343         else if (self.spawnflags & 8) // Y (untested)
344                 self.avelocity = '1 0 0' * self.speed;
345         // FIXME: test if this turns the right way, then remove this comment (negate as needed)
346         else // Z
347                 self.avelocity = '0 1 0' * self.speed;
348
349     if(self.dmg & (!self.message))
350         self.message = " was squished";
351     if(self.dmg && (!self.message2))
352                 self.message2 = "was squished by";
353
354
355     if(self.dmg && (!self.dmgtime))
356         self.dmgtime = 0.25;
357
358     self.dmgtime2 = time;
359
360         if not(InitMovingBrushTrigger())
361                 return;
362         // no EF_LOWPRECISION here, as rounding angles is bad
363
364     self.blocked = rotating_blocked;
365
366         // wait for targets to spawn
367         self.nextthink = self.ltime + 999999999;
368         self.think = SUB_Null;
369 };
370
371 .float height;
372 void func_bobbing_controller_think()
373 {
374         local vector v;
375         self.nextthink = time + 0.1;
376         // calculate sinewave using makevectors
377         makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
378         v = self.owner.destvec + self.owner.movedir * v_forward_y;
379         // * 10 so it will arrive in 0.1 sec
380         self.owner.velocity = (v - self.owner.origin) * 10;
381 };
382
383 void bobbing_blocked()
384 {
385         // no need to duplicate code
386         rotating_blocked();
387 }
388
389 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
390 Brush model that moves back and forth on one axis (default Z).
391 speed : how long one cycle takes in seconds (default 4)
392 height : how far the cycle moves (default 32)
393 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
394 noise : path/name of looping .wav file to play.
395 dmg : Do this mutch dmg every .dmgtime intervall when blocked
396 dmgtime : See above.
397 */
398 void spawnfunc_func_bobbing()
399 {
400         local entity controller;
401         if (self.noise != "")
402         {
403                 precache_sound(self.noise);
404                 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
405         }
406         if (!self.speed)
407                 self.speed = 4;
408         if (!self.height)
409                 self.height = 32;
410         // center of bobbing motion
411         self.destvec = self.origin;
412         // time scale to get degrees
413         self.cnt = 360 / self.speed;
414
415         // damage when blocked
416         self.blocked = bobbing_blocked;
417         if(self.dmg & (!self.message))
418                 self.message = " was squished";
419     if(self.dmg && (!self.message2))
420                 self.message2 = "was squished by";
421         if(self.dmg && (!self.dmgtime))
422                 self.dmgtime = 0.25;
423         self.dmgtime2 = time;
424
425         // how far to bob
426         if (self.spawnflags & 1) // X
427                 self.movedir = '1 0 0' * self.height;
428         else if (self.spawnflags & 2) // Y
429                 self.movedir = '0 1 0' * self.height;
430         else // Z
431                 self.movedir = '0 0 1' * self.height;
432
433         if not(InitMovingBrushTrigger())
434                 return;
435
436         // wait for targets to spawn
437         controller = spawn();
438         controller.classname = "func_bobbing_controller";
439         controller.owner = self;
440         controller.nextthink = time + 1;
441         controller.think = func_bobbing_controller_think;
442         self.nextthink = self.ltime + 999999999;
443         self.think = SUB_Null;
444
445         // Savage: Reduce bandwith, critical on e.g. nexdm02
446         self.effects |= EF_LOWPRECISION;
447 };
448
449 // button and multiple button
450
451 void() button_wait;
452 void() button_return;
453
454 void button_wait()
455 {
456         self.state = STATE_TOP;
457         self.nextthink = self.ltime + self.wait;
458         self.think = button_return;
459         activator = self.enemy;
460         SUB_UseTargets();
461         self.frame = 1;                 // use alternate textures
462 };
463
464 void button_done()
465 {
466         self.state = STATE_BOTTOM;
467 };
468
469 void button_return()
470 {
471         self.state = STATE_DOWN;
472         SUB_CalcMove (self.pos1, self.speed, button_done);
473         self.frame = 0;                 // use normal textures
474         if (self.health)
475                 self.takedamage = DAMAGE_YES;   // can be shot again
476 };
477
478
479 void button_blocked()
480 {
481         // do nothing, just don't come all the way back out
482 };
483
484
485 void button_fire()
486 {
487         self.health = self.max_health;
488         self.takedamage = DAMAGE_NO;    // will be reset upon return
489
490         if (self.state == STATE_UP || self.state == STATE_TOP)
491                 return;
492
493         if (self.noise != "")
494                 sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
495
496         self.state = STATE_UP;
497         SUB_CalcMove (self.pos2, self.speed, button_wait);
498 };
499
500
501 void button_use()
502 {
503 //      if (activator.classname != "player")
504 //      {
505 //              dprint(activator.classname);
506 //              dprint(" triggered a button\n");
507 //      }
508         self.enemy = activator;
509         button_fire ();
510 };
511
512 void button_touch()
513 {
514 //      if (activator.classname != "player")
515 //      {
516 //              dprint(activator.classname);
517 //              dprint(" touched a button\n");
518 //      }
519         if (!other)
520                 return;
521         if not(other.iscreature)
522                 return;
523         if(other.velocity * self.movedir < 0)
524                 return;
525         self.enemy = other;
526         if (other.owner)
527                 self.enemy = other.owner;
528         button_fire ();
529 };
530
531 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
532 {
533         self.health = self.health - damage;
534         if (self.health <= 0)
535         {
536         //      if (activator.classname != "player")
537         //      {
538         //              dprint(activator.classname);
539         //              dprint(" killed a button\n");
540         //      }
541                 self.enemy = damage_attacker;
542                 button_fire ();
543         }
544 };
545
546
547 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
548 When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again.
549
550 "angle"         determines the opening direction
551 "target"        all entities with a matching targetname will be used
552 "speed"         override the default 40 speed
553 "wait"          override the default 1 second wait (-1 = never return)
554 "lip"           override the default 4 pixel lip remaining at end of move
555 "health"        if set, the button must be killed instead of touched. If set to -1, the button will fire on ANY attack, even damageless ones like the MinstaGib laser
556 "sounds"
557 0) steam metal
558 1) wooden clunk
559 2) metallic click
560 3) in-out
561 */
562 void spawnfunc_func_button()
563 {
564         SetMovedir ();
565
566         if not(InitMovingBrushTrigger())
567                 return;
568         self.effects |= EF_LOWPRECISION;
569
570         self.blocked = button_blocked;
571         self.use = button_use;
572
573 //      if (self.health == 0) // all buttons are now shootable
574 //              self.health = 10;
575         if (self.health)
576         {
577                 self.max_health = self.health;
578                 self.event_damage = button_damage;
579                 self.takedamage = DAMAGE_YES;
580         }
581         else
582                 self.touch = button_touch;
583
584         if (!self.speed)
585                 self.speed = 40;
586         if (!self.wait)
587                 self.wait = 1;
588         if (!self.lip)
589                 self.lip = 4;
590
591     if(self.noise != "")
592         precache_sound(self.noise);
593
594         self.state = STATE_BOTTOM;
595
596         self.pos1 = self.origin;
597         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
598 };
599
600
601 float DOOR_START_OPEN = 1;
602 float DOOR_DONT_LINK = 4;
603 float DOOR_TOGGLE = 32;
604
605 /*
606
607 Doors are similar to buttons, but can spawn a fat trigger field around them
608 to open without a touch, and they link together to form simultanious
609 double/quad doors.
610
611 Door.owner is the master door.  If there is only one door, it points to itself.
612 If multiple doors, all will point to a single one.
613
614 Door.enemy chains from the master door through all doors linked in the chain.
615
616 */
617
618 /*
619 =============================================================================
620
621 THINK FUNCTIONS
622
623 =============================================================================
624 */
625
626 void() door_go_down;
627 void() door_go_up;
628 void() door_rotating_go_down;
629 void() door_rotating_go_up;
630
631 void door_blocked()
632 {
633
634     if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
635         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
636     } else {
637
638         if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?
639             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
640
641          //Dont chamge direction for dead or dying stuff
642         if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
643             if (self.wait >= 0)
644             {
645                 if (self.state == STATE_DOWN)
646                         if (self.classname == "door")
647                         {
648                                 door_go_up ();
649                         } else 
650                         {
651                                 door_rotating_go_up ();
652                         }
653                 else
654                         if (self.classname == "door")
655                         {
656                                 door_go_down ();
657                         } else 
658                         {
659                                 door_rotating_go_down ();
660                         }
661             }
662         } else {
663             //gib dying stuff just to make sure
664             if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?
665                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
666         }
667     }
668
669         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
670 // if a door has a negative wait, it would never come back if blocked,
671 // so let it just squash the object to death real fast
672 /*      if (self.wait >= 0)
673         {
674                 if (self.state == STATE_DOWN)
675                         door_go_up ();
676                 else
677                         door_go_down ();
678         }
679 */
680 };
681
682
683 void door_hit_top()
684 {
685         if (self.noise1 != "")
686                 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
687         self.state = STATE_TOP;
688         if (self.spawnflags & DOOR_TOGGLE)
689                 return;         // don't come down automatically
690         if (self.classname == "door")
691         {
692                 self.think = door_go_down;
693         } else 
694         {
695                 self.think = door_rotating_go_down;
696         }
697         self.nextthink = self.ltime + self.wait;
698 };
699
700 void door_hit_bottom()
701 {
702         if (self.noise1 != "")
703                 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
704         self.state = STATE_BOTTOM;
705 };
706
707 void door_go_down()
708 {
709         if (self.noise2 != "")
710                 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
711         if (self.max_health)
712         {
713                 self.takedamage = DAMAGE_YES;
714                 self.health = self.max_health;
715         }
716
717         self.state = STATE_DOWN;
718         SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
719 };
720
721 void door_go_up()
722 {
723         if (self.state == STATE_UP)
724                 return;         // already going up
725
726         if (self.state == STATE_TOP)
727         {       // reset top wait time
728                 self.nextthink = self.ltime + self.wait;
729                 return;
730         }
731
732         if (self.noise2 != "")
733                 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
734         self.state = STATE_UP;
735         SUB_CalcMove (self.pos2, self.speed, door_hit_top);
736
737         string oldmessage;
738         oldmessage = self.message;
739         self.message = "";
740         SUB_UseTargets();
741         self.message = oldmessage;
742 };
743
744
745 /*
746 =============================================================================
747
748 ACTIVATION FUNCTIONS
749
750 =============================================================================
751 */
752
753 void door_fire()
754 {
755         local entity    oself;
756         local entity    starte;
757
758         if (self.owner != self)
759                 objerror ("door_fire: self.owner != self");
760
761         oself = self;
762
763         if (self.spawnflags & DOOR_TOGGLE)
764         {
765                 if (self.state == STATE_UP || self.state == STATE_TOP)
766                 {
767                         starte = self;
768                         do
769                         {
770                                 if (self.classname == "door") 
771                                 {
772                                         door_go_down ();
773                                 }
774                                 else
775                                 {
776                                         door_rotating_go_down ();
777                                 }
778                                 self = self.enemy;
779                         } while ( (self != starte) && (self != world) );
780                         self = oself;
781                         return;
782                 }
783         }
784
785 // trigger all paired doors
786         starte = self;
787         do
788         {
789                 if (self.classname == "door")
790                 {
791                         door_go_up ();
792                 } else 
793                 {
794                         // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
795                         if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM) 
796                         { 
797                                 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
798                                 self.pos2 = '0 0 0' - self.pos2; 
799                         }
800                         // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
801                         if (!((self.spawnflags & 2) &&  (self.spawnflags & 8) && self.state == STATE_DOWN
802                             && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
803                         {
804                                 door_rotating_go_up ();
805                         }
806                 }
807                 self = self.enemy;
808         } while ( (self != starte) && (self != world) );
809         self = oself;
810 };
811
812
813 void door_use()
814 {
815         local entity oself;
816
817         //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
818         if (self.owner)
819         {
820                 oself = self;
821                 self = self.owner;
822                 door_fire ();
823                 self = oself;
824         }
825 };
826
827
828 void door_trigger_touch()
829 {
830         if (other.health < 1)
831         if not(other.iscreature && other.deadflag == DEAD_NO)
832                 return;
833
834         if (time < self.attack_finished_single)
835                 return;
836         self.attack_finished_single = time + 1;
837
838         activator = other;
839
840         self = self.owner;
841         door_use ();
842 };
843
844
845 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
846 {
847         local entity oself;
848         self.health = self.health - damage;
849         if (self.health <= 0)
850         {
851                 oself = self;
852                 self = self.owner;
853                 self.health = self.max_health;
854                 self.takedamage = DAMAGE_NO;    // wil be reset upon return
855                 door_use ();
856                 self = oself;
857         }
858 };
859
860
861 /*
862 ================
863 door_touch
864
865 Prints messages
866 ================
867 */
868 void door_touch()
869 {
870         if(other.classname != "player")
871                 return;
872         if (self.owner.attack_finished_single > time)
873                 return;
874
875         self.owner.attack_finished_single = time + 2;
876
877         if (!(self.owner.dmg) && (self.owner.message != ""))
878         {
879                 if (other.flags & FL_CLIENT)
880                         centerprint (other, self.owner.message);
881                 play2(other, "misc/talk.wav");
882         }
883 };
884
885
886 void door_rotating_blocked()
887 {
888
889     if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
890         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
891     } else {
892
893         if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?
894             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
895
896          //Dont chamge direction for dead or dying stuff
897         if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
898             if (self.wait >= 0)
899             {
900                 if (self.state == STATE_DOWN)
901                     door_rotating_go_up ();
902                 else
903                     door_rotating_go_down ();
904             }
905         } else {
906             //gib dying stuff just to make sure
907             if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?
908                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
909         }
910     }
911
912         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
913 // if a door has a negative wait, it would never come back if blocked,
914 // so let it just squash the object to death real fast
915 /*      if (self.wait >= 0)
916         {
917                 if (self.state == STATE_DOWN)
918                         door_rotating_go_up ();
919                 else
920                         door_rotating_go_down ();
921         }
922 */
923 };
924
925
926 void door_rotating_hit_top()
927 {
928         if (self.noise1 != "")
929                 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
930         self.state = STATE_TOP;
931         if (self.spawnflags & DOOR_TOGGLE)
932                 return;         // don't come down automatically
933         self.think = door_rotating_go_down;
934         self.nextthink = self.ltime + self.wait;
935 };
936
937 void door_rotating_hit_bottom()
938 {
939         if (self.noise1 != "")
940                 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
941         if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
942         { 
943                 self.pos2 = '0 0 0' - self.pos2; 
944                 self.lip = 0;
945         }
946         self.state = STATE_BOTTOM;
947 };
948
949 void door_rotating_go_down()
950 {
951         if (self.noise2 != "")
952                 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
953         if (self.max_health)
954         {
955                 self.takedamage = DAMAGE_YES;
956                 self.health = self.max_health;
957         }
958
959         self.state = STATE_DOWN;
960         SUB_CalcAngleMove (self.pos1, self.speed, door_rotating_hit_bottom);
961 };
962
963 void door_rotating_go_up()
964 {
965         if (self.state == STATE_UP)
966                 return;         // already going up
967
968         if (self.state == STATE_TOP)
969         {       // reset top wait time
970                 self.nextthink = self.ltime + self.wait;
971                 return;
972         }
973         if (self.noise2 != "")
974                 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
975         self.state = STATE_UP;
976         SUB_CalcAngleMove (self.pos2, self.speed, door_rotating_hit_top);
977
978         string oldmessage;
979         oldmessage = self.message;
980         self.message = "";
981         SUB_UseTargets();
982         self.message = oldmessage;
983 };
984
985
986
987
988 /*
989 =============================================================================
990
991 SPAWNING FUNCTIONS
992
993 =============================================================================
994 */
995
996
997 entity spawn_field(vector fmins, vector fmaxs)
998 {
999         local entity    trigger;
1000         local   vector  t1, t2;
1001
1002         trigger = spawn();
1003         trigger.classname = "doortriggerfield";
1004         trigger.movetype = MOVETYPE_NONE;
1005         trigger.solid = SOLID_TRIGGER;
1006         trigger.owner = self;
1007         trigger.touch = door_trigger_touch;
1008
1009         t1 = fmins;
1010         t2 = fmaxs;
1011         setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1012         return (trigger);
1013 };
1014
1015
1016 float EntitiesTouching(entity e1, entity e2)
1017 {
1018         if (e1.absmin_x > e2.absmax_x)
1019                 return FALSE;
1020         if (e1.absmin_y > e2.absmax_y)
1021                 return FALSE;
1022         if (e1.absmin_z > e2.absmax_z)
1023                 return FALSE;
1024         if (e1.absmax_x < e2.absmin_x)
1025                 return FALSE;
1026         if (e1.absmax_y < e2.absmin_y)
1027                 return FALSE;
1028         if (e1.absmax_z < e2.absmin_z)
1029                 return FALSE;
1030         return TRUE;
1031 };
1032
1033
1034 /*
1035 =============
1036 LinkDoors
1037
1038
1039 =============
1040 */
1041 void LinkDoors()
1042 {
1043         local entity    t, starte;
1044         local vector    cmins, cmaxs;
1045
1046         if (self.enemy)
1047                 return;         // already linked by another door
1048         if (self.spawnflags & 4)
1049         {
1050                 self.owner = self.enemy = self;
1051
1052                 if (self.health)
1053                         return;
1054                 IFTARGETED
1055                         return;
1056                 if (self.items)
1057                         return;
1058                 self.trigger_field = spawn_field(self.absmin, self.absmax);
1059
1060                 return;         // don't want to link this door
1061         }
1062
1063         cmins = self.absmin;                      
1064         cmaxs = self.absmax;
1065
1066         starte = self;
1067         t = self;
1068
1069         do
1070         {
1071                 self.owner = starte;                    // master door
1072
1073                 if (self.health)
1074                         starte.health = self.health;
1075                 IFTARGETED
1076                         starte.targetname = self.targetname;
1077                 if (self.message != "")
1078                         starte.message = self.message;
1079
1080                 t = find(t, classname, self.classname);
1081                 if (!t)
1082                 {
1083                         self.enemy = starte;            // make the chain a loop
1084
1085                 // shootable, or triggered doors just needed the owner/enemy links,
1086                 // they don't spawn a field
1087
1088                         self = self.owner;
1089
1090                         if (self.health)
1091                                 return;
1092                         IFTARGETED
1093                                 return;
1094                         if (self.items)
1095                                 return;
1096
1097                         self.owner.trigger_field = spawn_field(cmins, cmaxs);
1098
1099                         return;
1100                 }
1101
1102                 if (EntitiesTouching(self,t))
1103                 {
1104                         if (t.enemy)
1105                                 objerror ("cross connected doors");
1106
1107                         self.enemy = t;
1108                         self = t;
1109
1110                         if (t.absmin_x < cmins_x)
1111                                 cmins_x = t.absmin_x;
1112                         if (t.absmin_y < cmins_y)
1113                                 cmins_y = t.absmin_y;
1114                         if (t.absmin_z < cmins_z)
1115                                 cmins_z = t.absmin_z;
1116                         if (t.absmax_x > cmaxs_x)
1117                                 cmaxs_x = t.absmax_x;
1118                         if (t.absmax_y > cmaxs_y)
1119                                 cmaxs_y = t.absmax_y;
1120                         if (t.absmax_z > cmaxs_z)
1121                                 cmaxs_z = t.absmax_z;
1122                 }
1123         } while (1 );
1124
1125 };
1126
1127
1128 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
1129 if two doors touch, they are assumed to be connected and operate as a unit.
1130
1131 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1132
1133 START_OPEN causes the door to move to its destination when spawned, and operate in reverse.  It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors).
1134
1135 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1136 "angle"         determines the opening direction
1137 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1138 "health"        if set, door must be shot open
1139 "speed"         movement speed (100 default)
1140 "wait"          wait before returning (3 default, -1 = never return)
1141 "lip"           lip remaining at end of move (8 default)
1142 "dmg"           damage to inflict when blocked (2 default)
1143 "sounds"
1144 0)      no sound
1145 1)      stone
1146 2)      base
1147 3)      stone chain
1148 4)      screechy metal
1149 FIXME: only one sound set available at the time being
1150
1151 */
1152
1153 void door_init_startopen()
1154 {
1155         setorigin (self, self.pos2);
1156         self.pos2 = self.pos1;
1157         self.pos1 = self.origin;
1158 }
1159
1160 void spawnfunc_func_door()
1161 {
1162         //if (!self.deathtype) // map makers can override this
1163         //      self.deathtype = " got in the way";
1164         SetMovedir ();
1165
1166         self.max_health = self.health;
1167         if not(InitMovingBrushTrigger())
1168                 return;
1169         self.effects |= EF_LOWPRECISION;
1170         self.classname = "door";
1171
1172         self.blocked = door_blocked;
1173         self.use = door_use;
1174
1175     if(self.spawnflags & 8)
1176         self.dmg = 10000;
1177
1178     if(self.dmg && (!self.message))
1179                 self.message = "was squished";
1180     if(self.dmg && (!self.message2))
1181                 self.message2 = "was squished by";
1182
1183         if (self.sounds > 0)
1184         {
1185                 precache_sound ("plats/medplat1.wav");
1186                 precache_sound ("plats/medplat2.wav");
1187                 self.noise2 = "plats/medplat1.wav";
1188                 self.noise1 = "plats/medplat2.wav";
1189         }
1190
1191         if (!self.speed)
1192                 self.speed = 100;
1193         if (!self.wait)
1194                 self.wait = 3;
1195         if (!self.lip)
1196                 self.lip = 8;
1197
1198         self.pos1 = self.origin;
1199         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1200
1201 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1202 // but spawn in the open position
1203         if (self.spawnflags & DOOR_START_OPEN)
1204                 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1205
1206         self.state = STATE_BOTTOM;
1207
1208         if (self.health)
1209         {
1210                 self.takedamage = DAMAGE_YES;
1211                 self.event_damage = door_damage;
1212         }
1213
1214         if (self.items)
1215                 self.wait = -1;
1216
1217         self.touch = door_touch;
1218
1219 // LinkDoors can't be done until all of the doors have been spawned, so
1220 // the sizes can be detected properly.
1221         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1222 };
1223
1224 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1225 if two doors touch, they are assumed to be connected and operate as a unit.
1226
1227 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1228
1229 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1230 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1231 must have set trigger_reverse to 1.
1232 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1233
1234 START_OPEN causes the door to move to its destination when spawned, and operate in reverse.  It is used to temporarily or permanently close off an area when triggered (not usefull for touch or takedamage doors).
1235
1236 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1237 "angle"         determines the destination angle for opening. negative values reverse the direction.
1238 "targetname"    if set, no touch field will be spawned and a remote button or trigger field activates the door.
1239 "health"        if set, door must be shot open
1240 "speed"         movement speed (100 default)
1241 "wait"          wait before returning (3 default, -1 = never return)
1242 "dmg"           damage to inflict when blocked (2 default)
1243 "sounds"
1244 0)      no sound
1245 1)      stone
1246 2)      base
1247 3)      stone chain
1248 4)      screechy metal
1249 FIXME: only one sound set available at the time being
1250 */
1251
1252 void door_rotating_init_startopen()
1253 {
1254         self.angles = self.movedir;
1255         self.pos2 = '0 0 0';
1256         self.pos1 = self.movedir;
1257 }
1258
1259
1260 void spawnfunc_func_door_rotating()
1261 {
1262         
1263         //if (!self.deathtype) // map makers can override this
1264         //      self.deathtype = " got in the way";
1265
1266         // I abuse "movedir" for denoting the axis for now
1267         if (self.spawnflags & 64) // X (untested)
1268                 self.movedir = '0 0 1';
1269         else if (self.spawnflags & 128) // Y (untested)
1270                 self.movedir = '1 0 0';
1271         else // Z
1272                 self.movedir = '0 1 0';
1273
1274         if (self.angles_y==0) self.angles_y = 90;
1275
1276         self.movedir = self.movedir * self.angles_y;
1277         self.angles = '0 0 0';
1278
1279         self.max_health = self.health;
1280         if not(InitMovingBrushTrigger())
1281                 return;
1282         //self.effects |= EF_LOWPRECISION;
1283         self.classname = "door_rotating";
1284
1285         self.blocked = door_blocked;
1286         self.use = door_use;
1287
1288     if(self.spawnflags & 8)
1289         self.dmg = 10000;
1290
1291     if(self.dmg && (!self.message))
1292                 self.message = "was squished";
1293     if(self.dmg && (!self.message2))
1294                 self.message2 = "was squished by";
1295
1296     if (self.sounds > 0)
1297         {
1298                 precache_sound ("plats/medplat1.wav");
1299                 precache_sound ("plats/medplat2.wav");
1300                 self.noise2 = "plats/medplat1.wav";
1301                 self.noise1 = "plats/medplat2.wav";
1302         }
1303
1304         if (!self.speed)
1305                 self.speed = 50;
1306         if (!self.wait)
1307                 self.wait = 1;
1308         self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1309
1310         self.pos1 = '0 0 0';
1311         self.pos2 = self.movedir;
1312
1313 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1314 // but spawn in the open position
1315         if (self.spawnflags & DOOR_START_OPEN)
1316                 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1317
1318         self.state = STATE_BOTTOM;
1319
1320         if (self.health)
1321         {
1322                 self.takedamage = DAMAGE_YES;
1323                 self.event_damage = door_damage;
1324         }
1325
1326         if (self.items)
1327                 self.wait = -1;
1328
1329         self.touch = door_touch;
1330
1331 // LinkDoors can't be done until all of the doors have been spawned, so
1332 // the sizes can be detected properly.
1333         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1334 };
1335
1336 /*
1337 =============================================================================
1338
1339 SECRET DOORS
1340
1341 =============================================================================
1342 */
1343
1344 void() fd_secret_move1;
1345 void() fd_secret_move2;
1346 void() fd_secret_move3;
1347 void() fd_secret_move4;
1348 void() fd_secret_move5;
1349 void() fd_secret_move6;
1350 void() fd_secret_done;
1351
1352 float SECRET_OPEN_ONCE = 1;             // stays open
1353 float SECRET_1ST_LEFT = 2;              // 1st move is left of arrow
1354 float SECRET_1ST_DOWN = 4;              // 1st move is down from arrow
1355 float SECRET_NO_SHOOT = 8;              // only opened by trigger
1356 float SECRET_YES_SHOOT = 16;    // shootable even if targeted
1357
1358
1359 void fd_secret_use()
1360 {
1361         local float temp;
1362         string message_save;
1363
1364         self.health = 10000;
1365         self.bot_attack = TRUE;
1366
1367         // exit if still moving around...
1368         if (self.origin != self.oldorigin)
1369                 return;
1370
1371         message_save = self.message;
1372         self.message = ""; // no more message
1373         SUB_UseTargets();                               // fire all targets / killtargets
1374         self.message = message_save;
1375
1376         self.velocity = '0 0 0';
1377
1378         // Make a sound, wait a little...
1379
1380         if (self.noise1 != "")
1381                 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1382         self.nextthink = self.ltime + 0.1;
1383
1384         temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1385         makevectors(self.mangle);
1386
1387         if (!self.t_width)
1388         {
1389                 if (self.spawnflags & SECRET_1ST_DOWN)
1390                         self.t_width = fabs(v_up * self.size);
1391                 else
1392                         self.t_width = fabs(v_right * self.size);
1393         }
1394
1395         if (!self.t_length)
1396                 self.t_length = fabs(v_forward * self.size);
1397
1398         if (self.spawnflags & SECRET_1ST_DOWN)
1399                 self.dest1 = self.origin - v_up * self.t_width;
1400         else
1401                 self.dest1 = self.origin + v_right * (self.t_width * temp);
1402
1403         self.dest2 = self.dest1 + v_forward * self.t_length;
1404         SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1405         if (self.noise2 != "")
1406                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1407 };
1408
1409 // Wait after first movement...
1410 void fd_secret_move1()
1411 {
1412         self.nextthink = self.ltime + 1.0;
1413         self.think = fd_secret_move2;
1414         if (self.noise3 != "")
1415                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1416 };
1417
1418 // Start moving sideways w/sound...
1419 void fd_secret_move2()
1420 {
1421         if (self.noise2 != "")
1422                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1423         SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1424 };
1425
1426 // Wait here until time to go back...
1427 void fd_secret_move3()
1428 {
1429         if (self.noise3 != "")
1430                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1431         if (!(self.spawnflags & SECRET_OPEN_ONCE))
1432         {
1433                 self.nextthink = self.ltime + self.wait;
1434                 self.think = fd_secret_move4;
1435         }
1436 };
1437
1438 // Move backward...
1439 void fd_secret_move4()
1440 {
1441         if (self.noise2 != "")
1442                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1443         SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1444 };
1445
1446 // Wait 1 second...
1447 void fd_secret_move5()
1448 {
1449         self.nextthink = self.ltime + 1.0;
1450         self.think = fd_secret_move6;
1451         if (self.noise3 != "")
1452                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1453 };
1454
1455 void fd_secret_move6()
1456 {
1457         if (self.noise2 != "")
1458                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1459         SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1460 };
1461
1462 void fd_secret_done()
1463 {
1464         if (self.spawnflags&SECRET_YES_SHOOT)
1465         {
1466                 self.health = 10000;
1467                 self.takedamage = DAMAGE_YES;
1468                 //self.th_pain = fd_secret_use;
1469         }
1470         if (self.noise3 != "")
1471                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1472 };
1473
1474 void secret_blocked()
1475 {
1476         if (time < self.attack_finished_single)
1477                 return;
1478         self.attack_finished_single = time + 0.5;
1479         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1480 };
1481
1482 /*
1483 ==============
1484 secret_touch
1485
1486 Prints messages
1487 ================
1488 */
1489 void secret_touch()
1490 {
1491         if not(other.iscreature)
1492                 return;
1493         if (self.attack_finished_single > time)
1494                 return;
1495
1496         self.attack_finished_single = time + 2;
1497
1498         if (self.message)
1499         {
1500                 if (other.flags & FL_CLIENT)
1501                         centerprint (other, self.message);
1502                 play2(other, "misc/talk.wav");
1503         }
1504 };
1505
1506
1507 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1508 Basic secret door. Slides back, then to the side. Angle determines direction.
1509 wait  = # of seconds before coming back
1510 1st_left = 1st move is left of arrow
1511 1st_down = 1st move is down from arrow
1512 always_shoot = even if targeted, keep shootable
1513 t_width = override WIDTH to move back (or height if going down)
1514 t_length = override LENGTH to move sideways
1515 "dmg"           damage to inflict when blocked (2 default)
1516
1517 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1518 "sounds"
1519 1) medieval
1520 2) metal
1521 3) base
1522 */
1523
1524 void spawnfunc_func_door_secret()
1525 {
1526         /*if (!self.deathtype) // map makers can override this
1527                 self.deathtype = " got in the way";*/
1528
1529         if (!self.dmg)
1530                 self.dmg = 2;
1531
1532         // Magic formula...
1533         self.mangle = self.angles;
1534         self.angles = '0 0 0';
1535         self.classname = "door";
1536         if not(InitMovingBrushTrigger())
1537                 return;
1538         self.effects |= EF_LOWPRECISION;
1539
1540         self.touch = secret_touch;
1541         self.blocked = secret_blocked;
1542         self.speed = 50;
1543         self.use = fd_secret_use;
1544         IFTARGETED
1545         {
1546         }
1547         else
1548                 self.spawnflags |= SECRET_YES_SHOOT;
1549
1550         if(self.spawnflags&SECRET_YES_SHOOT)
1551         {
1552                 self.health = 10000;
1553                 self.takedamage = DAMAGE_YES;
1554                 self.event_damage = fd_secret_use;
1555         }
1556         self.oldorigin = self.origin;
1557         if (!self.wait)
1558                 self.wait = 5;          // 5 seconds before closing
1559 };
1560
1561 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1562 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1563 netname: list of <frequencymultiplier> <phase> <x> <y> <z> quadruples, separated by spaces; note that phase 0 represents a sine wave, and phase 0.25 a cosine wave (by default, it uses 1 0 0 0 1, to match func_bobbing's defaults
1564 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1565 height: amplitude modifier (default 1)
1566 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1567 noise: path/name of looping .wav file to play.
1568 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1569 dmgtime: See above.
1570 */
1571
1572 void func_fourier_controller_think()
1573 {
1574         local vector v;
1575         float n, i, t;
1576
1577         self.nextthink = time + 0.1;
1578
1579         n = floor((tokenize_sane(self.owner.netname)) / 5);
1580         t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1581
1582         v = self.owner.destvec;
1583
1584         for(i = 0; i < n; ++i)
1585         {
1586                 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1587                 v = v + ('1 0 0' * stof(argv(i*5+2)) + '0 1 0' * stof(argv(i*5+3)) + '0 0 1' * stof(argv(i*5+4))) * self.owner.height * v_forward_y;
1588         }
1589
1590         // * 10 so it will arrive in 0.1 sec
1591         self.owner.velocity = (v - self.owner.origin) * 10;
1592 };
1593
1594 void spawnfunc_func_fourier()
1595 {
1596         local entity controller;
1597         if (self.noise != "")
1598         {
1599                 precache_sound(self.noise);
1600                 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
1601         }
1602
1603         if (!self.speed)
1604                 self.speed = 4;
1605         if (!self.height)
1606                 self.height = 1;
1607         self.destvec = self.origin;
1608         self.cnt = 360 / self.speed;
1609
1610         self.blocked = rotating_blocked;
1611         if(self.dmg & (!self.message))
1612                 self.message = " was squished";
1613     if(self.dmg && (!self.message2))
1614                 self.message2 = "was squished by";
1615         if(self.dmg && (!self.dmgtime))
1616                 self.dmgtime = 0.25;
1617         self.dmgtime2 = time;
1618
1619         if(self.netname == "")
1620                 self.netname = "1 0 0 0 1";
1621
1622         if not(InitMovingBrushTrigger())
1623                 return;
1624
1625         // wait for targets to spawn
1626         controller = spawn();
1627         controller.classname = "func_fourier_controller";
1628         controller.owner = self;
1629         controller.nextthink = time + 1;
1630         controller.think = func_fourier_controller_think;
1631         self.nextthink = self.ltime + 999999999;
1632         self.think = SUB_Null;
1633
1634         // Savage: Reduce bandwith, critical on e.g. nexdm02
1635         self.effects |= EF_LOWPRECISION;
1636 };
1637
1638 // reusing some fields havocbots declared
1639 .entity wp00, wp01, wp02, wp03;
1640
1641 .float targetfactor, target2factor, target3factor, target4factor;
1642
1643 void func_vectormamamam_controller_think()
1644 {
1645         vector v;
1646         entity e;
1647
1648         self.nextthink = time + 0.1;
1649
1650         v = self.owner.destvec;
1651         
1652         e = self.owner.wp00;
1653         if(e)
1654                 v = v + e.origin + 0.1 * e.velocity;
1655
1656         e = self.owner.wp01;
1657         if(e)
1658                 v = v + e.origin + 0.1 * e.velocity;
1659
1660         e = self.owner.wp02;
1661         if(e)
1662                 v = v + e.origin + 0.1 * e.velocity;
1663
1664         e = self.owner.wp03;
1665         if(e)
1666                 v = v + e.origin + 0.1 * e.velocity;
1667
1668         self.owner.velocity = (v - self.owner.origin) * 10;
1669 }
1670
1671 void func_vectormamamam_findtarget()
1672 {
1673         vector s0;
1674
1675         s0 = '0 0 0';
1676
1677         if(self.target != "")
1678         {
1679                 self.wp00 = find(world, targetname, self.target);
1680                 if(self.wp00)
1681                         s0 = s0 + self.wp00.origin * self.targetfactor;
1682         }
1683
1684         if(self.target2 != "")
1685         {
1686                 self.wp01 = find(world, targetname, self.target2);
1687                 if(self.wp01)
1688                         s0 = s0 + self.wp01.origin * self.target2factor;
1689         }
1690
1691         if(self.target3 != "")
1692         {
1693                 self.wp02 = find(world, targetname, self.target3);
1694                 if(self.wp02)
1695                         s0 = s0 + self.wp02.origin * self.target3factor;
1696         }
1697
1698         if(self.target4 != "")
1699         {
1700                 self.wp03 = find(world, targetname, self.target4);
1701                 if(self.wp03)
1702                         s0 = s0 + self.wp03.origin * self.target4factor;
1703         }
1704
1705         if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
1706                 objerror("No reference entity found, so there is nothing to move. Aborting.");
1707
1708         self.destvec = self.origin - s0;
1709
1710         local entity controller;
1711         controller = spawn();
1712         controller.classname = "func_vectormamamam_controller";
1713         controller.owner = self;
1714         controller.nextthink = time + 1;
1715         controller.think = func_vectormamamam_controller_think;
1716 }
1717
1718 void spawnfunc_func_vectormamamam()
1719 {
1720         if (self.noise != "")
1721         {
1722                 precache_sound(self.noise);
1723                 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
1724         }
1725
1726         if(!self.targetfactor)
1727                 self.targetfactor = 1;
1728
1729         if(!self.target2factor)
1730                 self.target2factor = 1;
1731
1732         if(!self.target3factor)
1733                 self.target3factor = 1;
1734
1735         if(!self.target4factor)
1736                 self.target4factor = 1;
1737
1738         self.blocked = rotating_blocked;
1739         if(self.dmg & (!self.message))
1740                 self.message = " was squished";
1741     if(self.dmg && (!self.message2))
1742                 self.message2 = "was squished by";
1743         if(self.dmg && (!self.dmgtime))
1744                 self.dmgtime = 0.25;
1745         self.dmgtime2 = time;
1746
1747         if(self.netname == "")
1748                 self.netname = "1 0 0 0 1";
1749
1750         if not(InitMovingBrushTrigger())
1751                 return;
1752
1753         // wait for targets to spawn
1754         self.nextthink = self.ltime + 999999999;
1755         self.think = SUB_Null;
1756
1757         // Savage: Reduce bandwith, critical on e.g. nexdm02
1758         self.effects |= EF_LOWPRECISION;
1759
1760         InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
1761 }