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