make all plats reset themselves on ready restart (PLEASE TEST, this may be broken)
[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.state = STATE_BOTTOM;
1185         self.think = SUB_Null;
1186 }
1187
1188 void spawnfunc_func_door()
1189 {
1190         //if (!self.deathtype) // map makers can override this
1191         //      self.deathtype = " got in the way";
1192         SetMovedir ();
1193
1194         self.max_health = self.health;
1195         if not(InitMovingBrushTrigger())
1196                 return;
1197         self.effects |= EF_LOWPRECISION;
1198         self.classname = "door";
1199
1200         self.blocked = door_blocked;
1201         self.use = door_use;
1202
1203     if(self.spawnflags & 8)
1204         self.dmg = 10000;
1205
1206     if(self.dmg && (!self.message))
1207                 self.message = "was squished";
1208     if(self.dmg && (!self.message2))
1209                 self.message2 = "was squished by";
1210
1211         if (self.sounds > 0)
1212         {
1213                 precache_sound ("plats/medplat1.wav");
1214                 precache_sound ("plats/medplat2.wav");
1215                 self.noise2 = "plats/medplat1.wav";
1216                 self.noise1 = "plats/medplat2.wav";
1217         }
1218
1219         if (!self.speed)
1220                 self.speed = 100;
1221         if (!self.wait)
1222                 self.wait = 3;
1223         if (!self.lip)
1224                 self.lip = 8;
1225
1226         self.pos1 = self.origin;
1227         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1228
1229 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1230 // but spawn in the open position
1231         if (self.spawnflags & DOOR_START_OPEN)
1232                 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1233
1234         self.state = STATE_BOTTOM;
1235
1236         if (self.health)
1237         {
1238                 self.takedamage = DAMAGE_YES;
1239                 self.event_damage = door_damage;
1240         }
1241
1242         if (self.items)
1243                 self.wait = -1;
1244
1245         self.touch = door_touch;
1246
1247 // LinkDoors can't be done until all of the doors have been spawned, so
1248 // the sizes can be detected properly.
1249         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1250
1251         self.reset = door_reset;
1252 };
1253
1254 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1255 if two doors touch, they are assumed to be connected and operate as a unit.
1256
1257 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1258
1259 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1260 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1261 must have set trigger_reverse to 1.
1262 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1263
1264 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).
1265
1266 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1267 "angle"         determines the destination angle for opening. negative values reverse the direction.
1268 "targetname"    if set, no touch field will be spawned and a remote button or trigger field activates the door.
1269 "health"        if set, door must be shot open
1270 "speed"         movement speed (100 default)
1271 "wait"          wait before returning (3 default, -1 = never return)
1272 "dmg"           damage to inflict when blocked (2 default)
1273 "sounds"
1274 0)      no sound
1275 1)      stone
1276 2)      base
1277 3)      stone chain
1278 4)      screechy metal
1279 FIXME: only one sound set available at the time being
1280 */
1281
1282 void door_rotating_reset()
1283 {
1284         self.angles = self.pos1;
1285         self.state = STATE_BOTTOM;
1286         self.think = SUB_Null;
1287 }
1288
1289 void door_rotating_init_startopen()
1290 {
1291         self.angles = self.movedir;
1292         self.pos2 = '0 0 0';
1293         self.pos1 = self.movedir;
1294 }
1295
1296
1297 void spawnfunc_func_door_rotating()
1298 {
1299         
1300         //if (!self.deathtype) // map makers can override this
1301         //      self.deathtype = " got in the way";
1302
1303         // I abuse "movedir" for denoting the axis for now
1304         if (self.spawnflags & 64) // X (untested)
1305                 self.movedir = '0 0 1';
1306         else if (self.spawnflags & 128) // Y (untested)
1307                 self.movedir = '1 0 0';
1308         else // Z
1309                 self.movedir = '0 1 0';
1310
1311         if (self.angles_y==0) self.angles_y = 90;
1312
1313         self.movedir = self.movedir * self.angles_y;
1314         self.angles = '0 0 0';
1315
1316         self.max_health = self.health;
1317         if not(InitMovingBrushTrigger())
1318                 return;
1319         //self.effects |= EF_LOWPRECISION;
1320         self.classname = "door_rotating";
1321
1322         self.blocked = door_blocked;
1323         self.use = door_use;
1324
1325     if(self.spawnflags & 8)
1326         self.dmg = 10000;
1327
1328     if(self.dmg && (!self.message))
1329                 self.message = "was squished";
1330     if(self.dmg && (!self.message2))
1331                 self.message2 = "was squished by";
1332
1333     if (self.sounds > 0)
1334         {
1335                 precache_sound ("plats/medplat1.wav");
1336                 precache_sound ("plats/medplat2.wav");
1337                 self.noise2 = "plats/medplat1.wav";
1338                 self.noise1 = "plats/medplat2.wav";
1339         }
1340
1341         if (!self.speed)
1342                 self.speed = 50;
1343         if (!self.wait)
1344                 self.wait = 1;
1345         self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1346
1347         self.pos1 = '0 0 0';
1348         self.pos2 = self.movedir;
1349
1350 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1351 // but spawn in the open position
1352         if (self.spawnflags & DOOR_START_OPEN)
1353                 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1354
1355         self.state = STATE_BOTTOM;
1356
1357         if (self.health)
1358         {
1359                 self.takedamage = DAMAGE_YES;
1360                 self.event_damage = door_damage;
1361         }
1362
1363         if (self.items)
1364                 self.wait = -1;
1365
1366         self.touch = door_touch;
1367
1368 // LinkDoors can't be done until all of the doors have been spawned, so
1369 // the sizes can be detected properly.
1370         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1371
1372         self.reset = door_rotating_reset;
1373 };
1374
1375 /*
1376 =============================================================================
1377
1378 SECRET DOORS
1379
1380 =============================================================================
1381 */
1382
1383 void() fd_secret_move1;
1384 void() fd_secret_move2;
1385 void() fd_secret_move3;
1386 void() fd_secret_move4;
1387 void() fd_secret_move5;
1388 void() fd_secret_move6;
1389 void() fd_secret_done;
1390
1391 float SECRET_OPEN_ONCE = 1;             // stays open
1392 float SECRET_1ST_LEFT = 2;              // 1st move is left of arrow
1393 float SECRET_1ST_DOWN = 4;              // 1st move is down from arrow
1394 float SECRET_NO_SHOOT = 8;              // only opened by trigger
1395 float SECRET_YES_SHOOT = 16;    // shootable even if targeted
1396
1397
1398 void fd_secret_use()
1399 {
1400         local float temp;
1401         string message_save;
1402
1403         self.health = 10000;
1404         self.bot_attack = TRUE;
1405
1406         // exit if still moving around...
1407         if (self.origin != self.oldorigin)
1408                 return;
1409
1410         message_save = self.message;
1411         self.message = ""; // no more message
1412         SUB_UseTargets();                               // fire all targets / killtargets
1413         self.message = message_save;
1414
1415         self.velocity = '0 0 0';
1416
1417         // Make a sound, wait a little...
1418
1419         if (self.noise1 != "")
1420                 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1421         self.nextthink = self.ltime + 0.1;
1422
1423         temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1424         makevectors(self.mangle);
1425
1426         if (!self.t_width)
1427         {
1428                 if (self.spawnflags & SECRET_1ST_DOWN)
1429                         self.t_width = fabs(v_up * self.size);
1430                 else
1431                         self.t_width = fabs(v_right * self.size);
1432         }
1433
1434         if (!self.t_length)
1435                 self.t_length = fabs(v_forward * self.size);
1436
1437         if (self.spawnflags & SECRET_1ST_DOWN)
1438                 self.dest1 = self.origin - v_up * self.t_width;
1439         else
1440                 self.dest1 = self.origin + v_right * (self.t_width * temp);
1441
1442         self.dest2 = self.dest1 + v_forward * self.t_length;
1443         SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1444         if (self.noise2 != "")
1445                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1446 };
1447
1448 // Wait after first movement...
1449 void fd_secret_move1()
1450 {
1451         self.nextthink = self.ltime + 1.0;
1452         self.think = fd_secret_move2;
1453         if (self.noise3 != "")
1454                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1455 };
1456
1457 // Start moving sideways w/sound...
1458 void fd_secret_move2()
1459 {
1460         if (self.noise2 != "")
1461                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1462         SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1463 };
1464
1465 // Wait here until time to go back...
1466 void fd_secret_move3()
1467 {
1468         if (self.noise3 != "")
1469                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1470         if (!(self.spawnflags & SECRET_OPEN_ONCE))
1471         {
1472                 self.nextthink = self.ltime + self.wait;
1473                 self.think = fd_secret_move4;
1474         }
1475 };
1476
1477 // Move backward...
1478 void fd_secret_move4()
1479 {
1480         if (self.noise2 != "")
1481                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1482         SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1483 };
1484
1485 // Wait 1 second...
1486 void fd_secret_move5()
1487 {
1488         self.nextthink = self.ltime + 1.0;
1489         self.think = fd_secret_move6;
1490         if (self.noise3 != "")
1491                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1492 };
1493
1494 void fd_secret_move6()
1495 {
1496         if (self.noise2 != "")
1497                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1498         SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1499 };
1500
1501 void fd_secret_done()
1502 {
1503         if (self.spawnflags&SECRET_YES_SHOOT)
1504         {
1505                 self.health = 10000;
1506                 self.takedamage = DAMAGE_YES;
1507                 //self.th_pain = fd_secret_use;
1508         }
1509         if (self.noise3 != "")
1510                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1511 };
1512
1513 void secret_blocked()
1514 {
1515         if (time < self.attack_finished_single)
1516                 return;
1517         self.attack_finished_single = time + 0.5;
1518         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1519 };
1520
1521 /*
1522 ==============
1523 secret_touch
1524
1525 Prints messages
1526 ================
1527 */
1528 void secret_touch()
1529 {
1530         if not(other.iscreature)
1531                 return;
1532         if (self.attack_finished_single > time)
1533                 return;
1534
1535         self.attack_finished_single = time + 2;
1536
1537         if (self.message)
1538         {
1539                 if (other.flags & FL_CLIENT)
1540                         centerprint (other, self.message);
1541                 play2(other, "misc/talk.wav");
1542         }
1543 };
1544
1545 void secret_reset()
1546 {
1547         if (self.spawnflags&SECRET_YES_SHOOT)
1548         {
1549                 self.health = 10000;
1550                 self.takedamage = DAMAGE_YES;
1551         }
1552         setorigin(self, self.oldorigin);
1553         self.think = SUB_Null;
1554 }
1555
1556 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1557 Basic secret door. Slides back, then to the side. Angle determines direction.
1558 wait  = # of seconds before coming back
1559 1st_left = 1st move is left of arrow
1560 1st_down = 1st move is down from arrow
1561 always_shoot = even if targeted, keep shootable
1562 t_width = override WIDTH to move back (or height if going down)
1563 t_length = override LENGTH to move sideways
1564 "dmg"           damage to inflict when blocked (2 default)
1565
1566 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1567 "sounds"
1568 1) medieval
1569 2) metal
1570 3) base
1571 */
1572
1573 void spawnfunc_func_door_secret()
1574 {
1575         /*if (!self.deathtype) // map makers can override this
1576                 self.deathtype = " got in the way";*/
1577
1578         if (!self.dmg)
1579                 self.dmg = 2;
1580
1581         // Magic formula...
1582         self.mangle = self.angles;
1583         self.angles = '0 0 0';
1584         self.classname = "door";
1585         if not(InitMovingBrushTrigger())
1586                 return;
1587         self.effects |= EF_LOWPRECISION;
1588
1589         self.touch = secret_touch;
1590         self.blocked = secret_blocked;
1591         self.speed = 50;
1592         self.use = fd_secret_use;
1593         IFTARGETED
1594         {
1595         }
1596         else
1597                 self.spawnflags |= SECRET_YES_SHOOT;
1598
1599         if(self.spawnflags&SECRET_YES_SHOOT)
1600         {
1601                 self.health = 10000;
1602                 self.takedamage = DAMAGE_YES;
1603                 self.event_damage = fd_secret_use;
1604         }
1605         self.oldorigin = self.origin;
1606         if (!self.wait)
1607                 self.wait = 5;          // 5 seconds before closing
1608
1609         self.reset = secret_reset;
1610         secret_reset();
1611 };
1612
1613 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1614 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1615 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
1616 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1617 height: amplitude modifier (default 32)
1618 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1619 noise: path/name of looping .wav file to play.
1620 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1621 dmgtime: See above.
1622 */
1623
1624 void func_fourier_controller_think()
1625 {
1626         local vector v;
1627         float n, i, t;
1628
1629         self.nextthink = time + 0.1;
1630
1631         n = floor((tokenize_sane(self.owner.netname)) / 5);
1632         t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1633
1634         v = self.owner.destvec;
1635
1636         for(i = 0; i < n; ++i)
1637         {
1638                 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1639                 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;
1640         }
1641
1642         // * 10 so it will arrive in 0.1 sec
1643         self.owner.velocity = (v - self.owner.origin) * 10;
1644 };
1645
1646 void spawnfunc_func_fourier()
1647 {
1648         local entity controller;
1649         if (self.noise != "")
1650         {
1651                 precache_sound(self.noise);
1652                 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1653         }
1654
1655         if (!self.speed)
1656                 self.speed = 4;
1657         if (!self.height)
1658                 self.height = 32;
1659         self.destvec = self.origin;
1660         self.cnt = 360 / self.speed;
1661
1662         self.blocked = generic_plat_blocked;
1663         if(self.dmg & (!self.message))
1664                 self.message = " was squished";
1665     if(self.dmg && (!self.message2))
1666                 self.message2 = "was squished by";
1667         if(self.dmg && (!self.dmgtime))
1668                 self.dmgtime = 0.25;
1669         self.dmgtime2 = time;
1670
1671         if(self.netname == "")
1672                 self.netname = "1 0 0 0 1";
1673
1674         if not(InitMovingBrushTrigger())
1675                 return;
1676
1677         // wait for targets to spawn
1678         controller = spawn();
1679         controller.classname = "func_fourier_controller";
1680         controller.owner = self;
1681         controller.nextthink = time + 1;
1682         controller.think = func_fourier_controller_think;
1683         self.nextthink = self.ltime + 999999999;
1684         self.think = SUB_Null;
1685
1686         // Savage: Reduce bandwith, critical on e.g. nexdm02
1687         self.effects |= EF_LOWPRECISION;
1688
1689         // TODO make a reset function for this one
1690 };
1691
1692 // reusing some fields havocbots declared
1693 .entity wp00, wp01, wp02, wp03;
1694
1695 .float targetfactor, target2factor, target3factor, target4factor;
1696 .vector targetnormal, target2normal, target3normal, target4normal;
1697
1698 vector func_vectormamamam_origin(entity o, float t)
1699 {
1700         vector v, p;
1701         float f;
1702         entity e;
1703
1704         f = o.spawnflags;
1705         v = '0 0 0';
1706
1707         e = o.wp00;
1708         if(e)
1709         {
1710                 p = e.origin + t * e.velocity;
1711                 if(f & 1)
1712                         v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
1713                 else
1714                         v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
1715         }
1716
1717         e = o.wp01;
1718         if(e)
1719         {
1720                 p = e.origin + t * e.velocity;
1721                 if(f & 2)
1722                         v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
1723                 else
1724                         v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
1725         }
1726
1727         e = o.wp02;
1728         if(e)
1729         {
1730                 p = e.origin + t * e.velocity;
1731                 if(f & 4)
1732                         v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
1733                 else
1734                         v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
1735         }
1736
1737         e = o.wp03;
1738         if(e)
1739         {
1740                 p = e.origin + t * e.velocity;
1741                 if(f & 8)
1742                         v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
1743                 else
1744                         v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
1745         }
1746
1747         return v;
1748 }
1749
1750 void func_vectormamamam_controller_think()
1751 {
1752         self.nextthink = time + 0.1;
1753         self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
1754 }
1755
1756 void func_vectormamamam_findtarget()
1757 {
1758         if(self.target != "")
1759                 self.wp00 = find(world, targetname, self.target);
1760
1761         if(self.target2 != "")
1762                 self.wp01 = find(world, targetname, self.target2);
1763
1764         if(self.target3 != "")
1765                 self.wp02 = find(world, targetname, self.target3);
1766
1767         if(self.target4 != "")
1768                 self.wp03 = find(world, targetname, self.target4);
1769
1770         if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
1771                 objerror("No reference entity found, so there is nothing to move. Aborting.");
1772
1773         self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
1774
1775         local entity controller;
1776         controller = spawn();
1777         controller.classname = "func_vectormamamam_controller";
1778         controller.owner = self;
1779         controller.nextthink = time + 1;
1780         controller.think = func_vectormamamam_controller_think;
1781 }
1782
1783 void spawnfunc_func_vectormamamam()
1784 {
1785         if (self.noise != "")
1786         {
1787                 precache_sound(self.noise);
1788                 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1789         }
1790
1791         if(!self.targetfactor)
1792                 self.targetfactor = 1;
1793
1794         if(!self.target2factor)
1795                 self.target2factor = 1;
1796
1797         if(!self.target3factor)
1798                 self.target3factor = 1;
1799
1800         if(!self.target4factor)
1801                 self.target4factor = 1;
1802
1803         if(vlen(self.targetnormal))
1804                 self.targetnormal = normalize(self.targetnormal);
1805
1806         if(vlen(self.target2normal))
1807                 self.target2normal = normalize(self.target2normal);
1808
1809         if(vlen(self.target3normal))
1810                 self.target3normal = normalize(self.target3normal);
1811
1812         if(vlen(self.target4normal))
1813                 self.target4normal = normalize(self.target4normal);
1814
1815         self.blocked = generic_plat_blocked;
1816         if(self.dmg & (!self.message))
1817                 self.message = " was squished";
1818     if(self.dmg && (!self.message2))
1819                 self.message2 = "was squished by";
1820         if(self.dmg && (!self.dmgtime))
1821                 self.dmgtime = 0.25;
1822         self.dmgtime2 = time;
1823
1824         if(self.netname == "")
1825                 self.netname = "1 0 0 0 1";
1826
1827         if not(InitMovingBrushTrigger())
1828                 return;
1829
1830         // wait for targets to spawn
1831         self.nextthink = self.ltime + 999999999;
1832         self.think = SUB_Null;
1833
1834         // Savage: Reduce bandwith, critical on e.g. nexdm02
1835         self.effects |= EF_LOWPRECISION;
1836
1837         InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
1838 }