]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/t_plats.qc
Patches #434 from terencehill
[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     self.flags |= FL_NOTARGET;
618
619         button_reset();
620 };
621
622
623 float DOOR_START_OPEN = 1;
624 float DOOR_DONT_LINK = 4;
625 float DOOR_TOGGLE = 32;
626
627 /*
628
629 Doors are similar to buttons, but can spawn a fat trigger field around them
630 to open without a touch, and they link together to form simultanious
631 double/quad doors.
632
633 Door.owner is the master door.  If there is only one door, it points to itself.
634 If multiple doors, all will point to a single one.
635
636 Door.enemy chains from the master door through all doors linked in the chain.
637
638 */
639
640 /*
641 =============================================================================
642
643 THINK FUNCTIONS
644
645 =============================================================================
646 */
647
648 void() door_go_down;
649 void() door_go_up;
650 void() door_rotating_go_down;
651 void() door_rotating_go_up;
652
653 void door_blocked()
654 {
655
656     if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
657         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
658     } else {
659
660         if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?
661             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
662
663          //Dont chamge direction for dead or dying stuff
664         if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
665             if (self.wait >= 0)
666             {
667                 if (self.state == STATE_DOWN)
668                         if (self.classname == "door")
669                         {
670                                 door_go_up ();
671                         } else
672                         {
673                                 door_rotating_go_up ();
674                         }
675                 else
676                         if (self.classname == "door")
677                         {
678                                 door_go_down ();
679                         } else
680                         {
681                                 door_rotating_go_down ();
682                         }
683             }
684         } else {
685             //gib dying stuff just to make sure
686             if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?
687                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
688         }
689     }
690
691         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
692 // if a door has a negative wait, it would never come back if blocked,
693 // so let it just squash the object to death real fast
694 /*      if (self.wait >= 0)
695         {
696                 if (self.state == STATE_DOWN)
697                         door_go_up ();
698                 else
699                         door_go_down ();
700         }
701 */
702 };
703
704
705 void door_hit_top()
706 {
707         if (self.noise1 != "")
708                 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
709         self.state = STATE_TOP;
710         if (self.spawnflags & DOOR_TOGGLE)
711                 return;         // don't come down automatically
712         if (self.classname == "door")
713         {
714                 self.think = door_go_down;
715         } else
716         {
717                 self.think = door_rotating_go_down;
718         }
719         self.nextthink = self.ltime + self.wait;
720 };
721
722 void door_hit_bottom()
723 {
724         if (self.noise1 != "")
725                 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
726         self.state = STATE_BOTTOM;
727 };
728
729 void door_go_down()
730 {
731         if (self.noise2 != "")
732                 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
733         if (self.max_health)
734         {
735                 self.takedamage = DAMAGE_YES;
736                 self.health = self.max_health;
737         }
738
739         self.state = STATE_DOWN;
740         SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
741 };
742
743 void door_go_up()
744 {
745         if (self.state == STATE_UP)
746                 return;         // already going up
747
748         if (self.state == STATE_TOP)
749         {       // reset top wait time
750                 self.nextthink = self.ltime + self.wait;
751                 return;
752         }
753
754         if (self.noise2 != "")
755                 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
756         self.state = STATE_UP;
757         SUB_CalcMove (self.pos2, self.speed, door_hit_top);
758
759         string oldmessage;
760         oldmessage = self.message;
761         self.message = "";
762         SUB_UseTargets();
763         self.message = oldmessage;
764 };
765
766
767 /*
768 =============================================================================
769
770 ACTIVATION FUNCTIONS
771
772 =============================================================================
773 */
774
775 void door_fire()
776 {
777         local entity    oself;
778         local entity    starte;
779
780         if (self.owner != self)
781                 objerror ("door_fire: self.owner != self");
782
783         oself = self;
784
785         if (self.spawnflags & DOOR_TOGGLE)
786         {
787                 if (self.state == STATE_UP || self.state == STATE_TOP)
788                 {
789                         starte = self;
790                         do
791                         {
792                                 if (self.classname == "door")
793                                 {
794                                         door_go_down ();
795                                 }
796                                 else
797                                 {
798                                         door_rotating_go_down ();
799                                 }
800                                 self = self.enemy;
801                         } while ( (self != starte) && (self != world) );
802                         self = oself;
803                         return;
804                 }
805         }
806
807 // trigger all paired doors
808         starte = self;
809         do
810         {
811                 if (self.classname == "door")
812                 {
813                         door_go_up ();
814                 } else
815                 {
816                         // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
817                         if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
818                         {
819                                 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
820                                 self.pos2 = '0 0 0' - self.pos2;
821                         }
822                         // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
823                         if (!((self.spawnflags & 2) &&  (self.spawnflags & 8) && self.state == STATE_DOWN
824                             && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
825                         {
826                                 door_rotating_go_up ();
827                         }
828                 }
829                 self = self.enemy;
830         } while ( (self != starte) && (self != world) );
831         self = oself;
832 };
833
834
835 void door_use()
836 {
837         local entity oself;
838
839         //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
840         if (self.owner)
841         {
842                 oself = self;
843                 self = self.owner;
844                 door_fire ();
845                 self = oself;
846         }
847 };
848
849
850 void door_trigger_touch()
851 {
852         if (other.health < 1)
853         if not(other.iscreature && other.deadflag == DEAD_NO)
854                 return;
855
856         if (time < self.attack_finished_single)
857                 return;
858         self.attack_finished_single = time + 1;
859
860         activator = other;
861
862         self = self.owner;
863         door_use ();
864 };
865
866
867 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
868 {
869         local entity oself;
870         self.health = self.health - damage;
871         if (self.health <= 0)
872         {
873                 oself = self;
874                 self = self.owner;
875                 self.health = self.max_health;
876                 self.takedamage = DAMAGE_NO;    // wil be reset upon return
877                 door_use ();
878                 self = oself;
879         }
880 };
881
882
883 /*
884 ================
885 door_touch
886
887 Prints messages
888 ================
889 */
890 void door_touch()
891 {
892         if(other.classname != "player")
893                 return;
894         if (self.owner.attack_finished_single > time)
895                 return;
896
897         self.owner.attack_finished_single = time + 2;
898
899         if (!(self.owner.dmg) && (self.owner.message != ""))
900         {
901                 if (other.flags & FL_CLIENT)
902                         centerprint (other, self.owner.message);
903                 play2(other, "misc/talk.wav");
904         }
905 };
906
907
908 void door_generic_plat_blocked()
909 {
910
911     if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
912         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
913     } else {
914
915         if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?
916             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
917
918          //Dont chamge direction for dead or dying stuff
919         if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
920             if (self.wait >= 0)
921             {
922                 if (self.state == STATE_DOWN)
923                     door_rotating_go_up ();
924                 else
925                     door_rotating_go_down ();
926             }
927         } else {
928             //gib dying stuff just to make sure
929             if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?
930                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
931         }
932     }
933
934         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
935 // if a door has a negative wait, it would never come back if blocked,
936 // so let it just squash the object to death real fast
937 /*      if (self.wait >= 0)
938         {
939                 if (self.state == STATE_DOWN)
940                         door_rotating_go_up ();
941                 else
942                         door_rotating_go_down ();
943         }
944 */
945 };
946
947
948 void door_rotating_hit_top()
949 {
950         if (self.noise1 != "")
951                 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
952         self.state = STATE_TOP;
953         if (self.spawnflags & DOOR_TOGGLE)
954                 return;         // don't come down automatically
955         self.think = door_rotating_go_down;
956         self.nextthink = self.ltime + self.wait;
957 };
958
959 void door_rotating_hit_bottom()
960 {
961         if (self.noise1 != "")
962                 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
963         if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
964         {
965                 self.pos2 = '0 0 0' - self.pos2;
966                 self.lip = 0;
967         }
968         self.state = STATE_BOTTOM;
969 };
970
971 void door_rotating_go_down()
972 {
973         if (self.noise2 != "")
974                 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
975         if (self.max_health)
976         {
977                 self.takedamage = DAMAGE_YES;
978                 self.health = self.max_health;
979         }
980
981         self.state = STATE_DOWN;
982         SUB_CalcAngleMove (self.pos1, self.speed, door_rotating_hit_bottom);
983 };
984
985 void door_rotating_go_up()
986 {
987         if (self.state == STATE_UP)
988                 return;         // already going up
989
990         if (self.state == STATE_TOP)
991         {       // reset top wait time
992                 self.nextthink = self.ltime + self.wait;
993                 return;
994         }
995         if (self.noise2 != "")
996                 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
997         self.state = STATE_UP;
998         SUB_CalcAngleMove (self.pos2, self.speed, door_rotating_hit_top);
999
1000         string oldmessage;
1001         oldmessage = self.message;
1002         self.message = "";
1003         SUB_UseTargets();
1004         self.message = oldmessage;
1005 };
1006
1007
1008
1009
1010 /*
1011 =============================================================================
1012
1013 SPAWNING FUNCTIONS
1014
1015 =============================================================================
1016 */
1017
1018
1019 entity spawn_field(vector fmins, vector fmaxs)
1020 {
1021         local entity    trigger;
1022         local   vector  t1, t2;
1023
1024         trigger = spawn();
1025         trigger.classname = "doortriggerfield";
1026         trigger.movetype = MOVETYPE_NONE;
1027         trigger.solid = SOLID_TRIGGER;
1028         trigger.owner = self;
1029         trigger.touch = door_trigger_touch;
1030
1031         t1 = fmins;
1032         t2 = fmaxs;
1033         setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1034         return (trigger);
1035 };
1036
1037
1038 float EntitiesTouching(entity e1, entity e2)
1039 {
1040         if (e1.absmin_x > e2.absmax_x)
1041                 return FALSE;
1042         if (e1.absmin_y > e2.absmax_y)
1043                 return FALSE;
1044         if (e1.absmin_z > e2.absmax_z)
1045                 return FALSE;
1046         if (e1.absmax_x < e2.absmin_x)
1047                 return FALSE;
1048         if (e1.absmax_y < e2.absmin_y)
1049                 return FALSE;
1050         if (e1.absmax_z < e2.absmin_z)
1051                 return FALSE;
1052         return TRUE;
1053 };
1054
1055
1056 /*
1057 =============
1058 LinkDoors
1059
1060
1061 =============
1062 */
1063 void LinkDoors()
1064 {
1065         local entity    t, starte;
1066         local vector    cmins, cmaxs;
1067
1068         if (self.enemy)
1069                 return;         // already linked by another door
1070         if (self.spawnflags & 4)
1071         {
1072                 self.owner = self.enemy = self;
1073
1074                 if (self.health)
1075                         return;
1076                 IFTARGETED
1077                         return;
1078                 if (self.items)
1079                         return;
1080                 self.trigger_field = spawn_field(self.absmin, self.absmax);
1081
1082                 return;         // don't want to link this door
1083         }
1084
1085         cmins = self.absmin;
1086         cmaxs = self.absmax;
1087
1088         starte = self;
1089         t = self;
1090
1091         do
1092         {
1093                 self.owner = starte;                    // master door
1094
1095                 if (self.health)
1096                         starte.health = self.health;
1097                 IFTARGETED
1098                         starte.targetname = self.targetname;
1099                 if (self.message != "")
1100                         starte.message = self.message;
1101
1102                 t = find(t, classname, self.classname);
1103                 if (!t)
1104                 {
1105                         self.enemy = starte;            // make the chain a loop
1106
1107                 // shootable, or triggered doors just needed the owner/enemy links,
1108                 // they don't spawn a field
1109
1110                         self = self.owner;
1111
1112                         if (self.health)
1113                                 return;
1114                         IFTARGETED
1115                                 return;
1116                         if (self.items)
1117                                 return;
1118
1119                         self.owner.trigger_field = spawn_field(cmins, cmaxs);
1120
1121                         return;
1122                 }
1123
1124                 if (EntitiesTouching(self,t))
1125                 {
1126                         if (t.enemy)
1127                                 objerror ("cross connected doors");
1128
1129                         self.enemy = t;
1130                         self = t;
1131
1132                         if (t.absmin_x < cmins_x)
1133                                 cmins_x = t.absmin_x;
1134                         if (t.absmin_y < cmins_y)
1135                                 cmins_y = t.absmin_y;
1136                         if (t.absmin_z < cmins_z)
1137                                 cmins_z = t.absmin_z;
1138                         if (t.absmax_x > cmaxs_x)
1139                                 cmaxs_x = t.absmax_x;
1140                         if (t.absmax_y > cmaxs_y)
1141                                 cmaxs_y = t.absmax_y;
1142                         if (t.absmax_z > cmaxs_z)
1143                                 cmaxs_z = t.absmax_z;
1144                 }
1145         } while (1 );
1146
1147 };
1148
1149
1150 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
1151 if two doors touch, they are assumed to be connected and operate as a unit.
1152
1153 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1154
1155 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).
1156
1157 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1158 "angle"         determines the opening direction
1159 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1160 "health"        if set, door must be shot open
1161 "speed"         movement speed (100 default)
1162 "wait"          wait before returning (3 default, -1 = never return)
1163 "lip"           lip remaining at end of move (8 default)
1164 "dmg"           damage to inflict when blocked (2 default)
1165 "sounds"
1166 0)      no sound
1167 1)      stone
1168 2)      base
1169 3)      stone chain
1170 4)      screechy metal
1171 FIXME: only one sound set available at the time being
1172
1173 */
1174
1175 void door_init_startopen()
1176 {
1177         setorigin (self, self.pos2);
1178         self.pos2 = self.pos1;
1179         self.pos1 = self.origin;
1180 }
1181
1182 void door_reset()
1183 {
1184         setorigin(self, self.pos1);
1185         self.velocity = '0 0 0';
1186         self.state = STATE_BOTTOM;
1187         self.think = SUB_Null;
1188 }
1189
1190 void spawnfunc_func_door()
1191 {
1192         //if (!self.deathtype) // map makers can override this
1193         //      self.deathtype = " got in the way";
1194         SetMovedir ();
1195
1196         self.max_health = self.health;
1197         if not(InitMovingBrushTrigger())
1198                 return;
1199         self.effects |= EF_LOWPRECISION;
1200         self.classname = "door";
1201
1202         self.blocked = door_blocked;
1203         self.use = door_use;
1204
1205     if(self.spawnflags & 8)
1206         self.dmg = 10000;
1207
1208     if(self.dmg && (!self.message))
1209                 self.message = "was squished";
1210     if(self.dmg && (!self.message2))
1211                 self.message2 = "was squished by";
1212
1213         if (self.sounds > 0)
1214         {
1215                 precache_sound ("plats/medplat1.wav");
1216                 precache_sound ("plats/medplat2.wav");
1217                 self.noise2 = "plats/medplat1.wav";
1218                 self.noise1 = "plats/medplat2.wav";
1219         }
1220
1221         if (!self.speed)
1222                 self.speed = 100;
1223         if (!self.wait)
1224                 self.wait = 3;
1225         if (!self.lip)
1226                 self.lip = 8;
1227
1228         self.pos1 = self.origin;
1229         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1230
1231 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1232 // but spawn in the open position
1233         if (self.spawnflags & DOOR_START_OPEN)
1234                 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1235
1236         self.state = STATE_BOTTOM;
1237
1238         if (self.health)
1239         {
1240                 self.takedamage = DAMAGE_YES;
1241                 self.event_damage = door_damage;
1242         }
1243
1244         if (self.items)
1245                 self.wait = -1;
1246
1247         self.touch = door_touch;
1248
1249 // LinkDoors can't be done until all of the doors have been spawned, so
1250 // the sizes can be detected properly.
1251         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1252
1253         self.reset = door_reset;
1254 };
1255
1256 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1257 if two doors touch, they are assumed to be connected and operate as a unit.
1258
1259 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1260
1261 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1262 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1263 must have set trigger_reverse to 1.
1264 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1265
1266 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).
1267
1268 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1269 "angle"         determines the destination angle for opening. negative values reverse the direction.
1270 "targetname"    if set, no touch field will be spawned and a remote button or trigger field activates the door.
1271 "health"        if set, door must be shot open
1272 "speed"         movement speed (100 default)
1273 "wait"          wait before returning (3 default, -1 = never return)
1274 "dmg"           damage to inflict when blocked (2 default)
1275 "sounds"
1276 0)      no sound
1277 1)      stone
1278 2)      base
1279 3)      stone chain
1280 4)      screechy metal
1281 FIXME: only one sound set available at the time being
1282 */
1283
1284 void door_rotating_reset()
1285 {
1286         self.angles = self.pos1;
1287         self.avelocity = '0 0 0';
1288         self.state = STATE_BOTTOM;
1289         self.think = SUB_Null;
1290 }
1291
1292 void door_rotating_init_startopen()
1293 {
1294         self.angles = self.movedir;
1295         self.pos2 = '0 0 0';
1296         self.pos1 = self.movedir;
1297 }
1298
1299
1300 void spawnfunc_func_door_rotating()
1301 {
1302
1303         //if (!self.deathtype) // map makers can override this
1304         //      self.deathtype = " got in the way";
1305
1306         // I abuse "movedir" for denoting the axis for now
1307         if (self.spawnflags & 64) // X (untested)
1308                 self.movedir = '0 0 1';
1309         else if (self.spawnflags & 128) // Y (untested)
1310                 self.movedir = '1 0 0';
1311         else // Z
1312                 self.movedir = '0 1 0';
1313
1314         if (self.angles_y==0) self.angles_y = 90;
1315
1316         self.movedir = self.movedir * self.angles_y;
1317         self.angles = '0 0 0';
1318
1319         self.max_health = self.health;
1320         if not(InitMovingBrushTrigger())
1321                 return;
1322         //self.effects |= EF_LOWPRECISION;
1323         self.classname = "door_rotating";
1324
1325         self.blocked = door_blocked;
1326         self.use = door_use;
1327
1328     if(self.spawnflags & 8)
1329         self.dmg = 10000;
1330
1331     if(self.dmg && (!self.message))
1332                 self.message = "was squished";
1333     if(self.dmg && (!self.message2))
1334                 self.message2 = "was squished by";
1335
1336     if (self.sounds > 0)
1337         {
1338                 precache_sound ("plats/medplat1.wav");
1339                 precache_sound ("plats/medplat2.wav");
1340                 self.noise2 = "plats/medplat1.wav";
1341                 self.noise1 = "plats/medplat2.wav";
1342         }
1343
1344         if (!self.speed)
1345                 self.speed = 50;
1346         if (!self.wait)
1347                 self.wait = 1;
1348         self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1349
1350         self.pos1 = '0 0 0';
1351         self.pos2 = self.movedir;
1352
1353 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1354 // but spawn in the open position
1355         if (self.spawnflags & DOOR_START_OPEN)
1356                 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1357
1358         self.state = STATE_BOTTOM;
1359
1360         if (self.health)
1361         {
1362                 self.takedamage = DAMAGE_YES;
1363                 self.event_damage = door_damage;
1364         }
1365
1366         if (self.items)
1367                 self.wait = -1;
1368
1369         self.touch = door_touch;
1370
1371 // LinkDoors can't be done until all of the doors have been spawned, so
1372 // the sizes can be detected properly.
1373         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1374
1375         self.reset = door_rotating_reset;
1376 };
1377
1378 /*
1379 =============================================================================
1380
1381 SECRET DOORS
1382
1383 =============================================================================
1384 */
1385
1386 void() fd_secret_move1;
1387 void() fd_secret_move2;
1388 void() fd_secret_move3;
1389 void() fd_secret_move4;
1390 void() fd_secret_move5;
1391 void() fd_secret_move6;
1392 void() fd_secret_done;
1393
1394 float SECRET_OPEN_ONCE = 1;             // stays open
1395 float SECRET_1ST_LEFT = 2;              // 1st move is left of arrow
1396 float SECRET_1ST_DOWN = 4;              // 1st move is down from arrow
1397 float SECRET_NO_SHOOT = 8;              // only opened by trigger
1398 float SECRET_YES_SHOOT = 16;    // shootable even if targeted
1399
1400
1401 void fd_secret_use()
1402 {
1403         local float temp;
1404         string message_save;
1405
1406         self.health = 10000;
1407         self.bot_attack = TRUE;
1408
1409         // exit if still moving around...
1410         if (self.origin != self.oldorigin)
1411                 return;
1412
1413         message_save = self.message;
1414         self.message = ""; // no more message
1415         SUB_UseTargets();                               // fire all targets / killtargets
1416         self.message = message_save;
1417
1418         self.velocity = '0 0 0';
1419
1420         // Make a sound, wait a little...
1421
1422         if (self.noise1 != "")
1423                 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1424         self.nextthink = self.ltime + 0.1;
1425
1426         temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1427         makevectors(self.mangle);
1428
1429         if (!self.t_width)
1430         {
1431                 if (self.spawnflags & SECRET_1ST_DOWN)
1432                         self.t_width = fabs(v_up * self.size);
1433                 else
1434                         self.t_width = fabs(v_right * self.size);
1435         }
1436
1437         if (!self.t_length)
1438                 self.t_length = fabs(v_forward * self.size);
1439
1440         if (self.spawnflags & SECRET_1ST_DOWN)
1441                 self.dest1 = self.origin - v_up * self.t_width;
1442         else
1443                 self.dest1 = self.origin + v_right * (self.t_width * temp);
1444
1445         self.dest2 = self.dest1 + v_forward * self.t_length;
1446         SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1447         if (self.noise2 != "")
1448                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1449 };
1450
1451 // Wait after first movement...
1452 void fd_secret_move1()
1453 {
1454         self.nextthink = self.ltime + 1.0;
1455         self.think = fd_secret_move2;
1456         if (self.noise3 != "")
1457                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1458 };
1459
1460 // Start moving sideways w/sound...
1461 void fd_secret_move2()
1462 {
1463         if (self.noise2 != "")
1464                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1465         SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1466 };
1467
1468 // Wait here until time to go back...
1469 void fd_secret_move3()
1470 {
1471         if (self.noise3 != "")
1472                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1473         if (!(self.spawnflags & SECRET_OPEN_ONCE))
1474         {
1475                 self.nextthink = self.ltime + self.wait;
1476                 self.think = fd_secret_move4;
1477         }
1478 };
1479
1480 // Move backward...
1481 void fd_secret_move4()
1482 {
1483         if (self.noise2 != "")
1484                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1485         SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1486 };
1487
1488 // Wait 1 second...
1489 void fd_secret_move5()
1490 {
1491         self.nextthink = self.ltime + 1.0;
1492         self.think = fd_secret_move6;
1493         if (self.noise3 != "")
1494                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1495 };
1496
1497 void fd_secret_move6()
1498 {
1499         if (self.noise2 != "")
1500                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1501         SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1502 };
1503
1504 void fd_secret_done()
1505 {
1506         if (self.spawnflags&SECRET_YES_SHOOT)
1507         {
1508                 self.health = 10000;
1509                 self.takedamage = DAMAGE_YES;
1510                 //self.th_pain = fd_secret_use;
1511         }
1512         if (self.noise3 != "")
1513                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1514 };
1515
1516 void secret_blocked()
1517 {
1518         if (time < self.attack_finished_single)
1519                 return;
1520         self.attack_finished_single = time + 0.5;
1521         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1522 };
1523
1524 /*
1525 ==============
1526 secret_touch
1527
1528 Prints messages
1529 ================
1530 */
1531 void secret_touch()
1532 {
1533         if not(other.iscreature)
1534                 return;
1535         if (self.attack_finished_single > time)
1536                 return;
1537
1538         self.attack_finished_single = time + 2;
1539
1540         if (self.message)
1541         {
1542                 if (other.flags & FL_CLIENT)
1543                         centerprint (other, self.message);
1544                 play2(other, "misc/talk.wav");
1545         }
1546 };
1547
1548 void secret_reset()
1549 {
1550         if (self.spawnflags&SECRET_YES_SHOOT)
1551         {
1552                 self.health = 10000;
1553                 self.takedamage = DAMAGE_YES;
1554         }
1555         setorigin(self, self.oldorigin);
1556         self.think = SUB_Null;
1557 }
1558
1559 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1560 Basic secret door. Slides back, then to the side. Angle determines direction.
1561 wait  = # of seconds before coming back
1562 1st_left = 1st move is left of arrow
1563 1st_down = 1st move is down from arrow
1564 always_shoot = even if targeted, keep shootable
1565 t_width = override WIDTH to move back (or height if going down)
1566 t_length = override LENGTH to move sideways
1567 "dmg"           damage to inflict when blocked (2 default)
1568
1569 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1570 "sounds"
1571 1) medieval
1572 2) metal
1573 3) base
1574 */
1575
1576 void spawnfunc_func_door_secret()
1577 {
1578         /*if (!self.deathtype) // map makers can override this
1579                 self.deathtype = " got in the way";*/
1580
1581         if (!self.dmg)
1582                 self.dmg = 2;
1583
1584         // Magic formula...
1585         self.mangle = self.angles;
1586         self.angles = '0 0 0';
1587         self.classname = "door";
1588         if not(InitMovingBrushTrigger())
1589                 return;
1590         self.effects |= EF_LOWPRECISION;
1591
1592         self.touch = secret_touch;
1593         self.blocked = secret_blocked;
1594         self.speed = 50;
1595         self.use = fd_secret_use;
1596         IFTARGETED
1597         {
1598         }
1599         else
1600                 self.spawnflags |= SECRET_YES_SHOOT;
1601
1602         if(self.spawnflags&SECRET_YES_SHOOT)
1603         {
1604                 self.health = 10000;
1605                 self.takedamage = DAMAGE_YES;
1606                 self.event_damage = fd_secret_use;
1607         }
1608         self.oldorigin = self.origin;
1609         if (!self.wait)
1610                 self.wait = 5;          // 5 seconds before closing
1611
1612         self.reset = secret_reset;
1613         secret_reset();
1614 };
1615
1616 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1617 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1618 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
1619 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1620 height: amplitude modifier (default 32)
1621 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1622 noise: path/name of looping .wav file to play.
1623 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1624 dmgtime: See above.
1625 */
1626
1627 void func_fourier_controller_think()
1628 {
1629         local vector v;
1630         float n, i, t;
1631
1632         self.nextthink = time + 0.1;
1633
1634         n = floor((tokenize_console(self.owner.netname)) / 5);
1635         t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1636
1637         v = self.owner.destvec;
1638
1639         for(i = 0; i < n; ++i)
1640         {
1641                 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1642                 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;
1643         }
1644
1645         // * 10 so it will arrive in 0.1 sec
1646         self.owner.velocity = (v - self.owner.origin) * 10;
1647 };
1648
1649 void spawnfunc_func_fourier()
1650 {
1651         local entity controller;
1652         if (self.noise != "")
1653         {
1654                 precache_sound(self.noise);
1655                 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1656         }
1657
1658         if (!self.speed)
1659                 self.speed = 4;
1660         if (!self.height)
1661                 self.height = 32;
1662         self.destvec = self.origin;
1663         self.cnt = 360 / self.speed;
1664
1665         self.blocked = generic_plat_blocked;
1666         if(self.dmg & (!self.message))
1667                 self.message = " was squished";
1668     if(self.dmg && (!self.message2))
1669                 self.message2 = "was squished by";
1670         if(self.dmg && (!self.dmgtime))
1671                 self.dmgtime = 0.25;
1672         self.dmgtime2 = time;
1673
1674         if(self.netname == "")
1675                 self.netname = "1 0 0 0 1";
1676
1677         if not(InitMovingBrushTrigger())
1678                 return;
1679
1680         // wait for targets to spawn
1681         controller = spawn();
1682         controller.classname = "func_fourier_controller";
1683         controller.owner = self;
1684         controller.nextthink = time + 1;
1685         controller.think = func_fourier_controller_think;
1686         self.nextthink = self.ltime + 999999999;
1687         self.think = SUB_Null;
1688
1689         // Savage: Reduce bandwith, critical on e.g. nexdm02
1690         self.effects |= EF_LOWPRECISION;
1691
1692         // TODO make a reset function for this one
1693 };
1694
1695 // reusing some fields havocbots declared
1696 .entity wp00, wp01, wp02, wp03;
1697
1698 .float targetfactor, target2factor, target3factor, target4factor;
1699 .vector targetnormal, target2normal, target3normal, target4normal;
1700
1701 vector func_vectormamamam_origin(entity o, float t)
1702 {
1703         vector v, p;
1704         float f;
1705         entity e;
1706
1707         f = o.spawnflags;
1708         v = '0 0 0';
1709
1710         e = o.wp00;
1711         if(e)
1712         {
1713                 p = e.origin + t * e.velocity;
1714                 if(f & 1)
1715                         v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
1716                 else
1717                         v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
1718         }
1719
1720         e = o.wp01;
1721         if(e)
1722         {
1723                 p = e.origin + t * e.velocity;
1724                 if(f & 2)
1725                         v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
1726                 else
1727                         v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
1728         }
1729
1730         e = o.wp02;
1731         if(e)
1732         {
1733                 p = e.origin + t * e.velocity;
1734                 if(f & 4)
1735                         v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
1736                 else
1737                         v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
1738         }
1739
1740         e = o.wp03;
1741         if(e)
1742         {
1743                 p = e.origin + t * e.velocity;
1744                 if(f & 8)
1745                         v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
1746                 else
1747                         v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
1748         }
1749
1750         return v;
1751 }
1752
1753 void func_vectormamamam_controller_think()
1754 {
1755         self.nextthink = time + 0.1;
1756         self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
1757 }
1758
1759 void func_vectormamamam_findtarget()
1760 {
1761         if(self.target != "")
1762                 self.wp00 = find(world, targetname, self.target);
1763
1764         if(self.target2 != "")
1765                 self.wp01 = find(world, targetname, self.target2);
1766
1767         if(self.target3 != "")
1768                 self.wp02 = find(world, targetname, self.target3);
1769
1770         if(self.target4 != "")
1771                 self.wp03 = find(world, targetname, self.target4);
1772
1773         if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
1774                 objerror("No reference entity found, so there is nothing to move. Aborting.");
1775
1776         self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
1777
1778         local entity controller;
1779         controller = spawn();
1780         controller.classname = "func_vectormamamam_controller";
1781         controller.owner = self;
1782         controller.nextthink = time + 1;
1783         controller.think = func_vectormamamam_controller_think;
1784 }
1785
1786 void spawnfunc_func_vectormamamam()
1787 {
1788         if (self.noise != "")
1789         {
1790                 precache_sound(self.noise);
1791                 soundto(MSG_INIT, self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_IDLE);
1792         }
1793
1794         if(!self.targetfactor)
1795                 self.targetfactor = 1;
1796
1797         if(!self.target2factor)
1798                 self.target2factor = 1;
1799
1800         if(!self.target3factor)
1801                 self.target3factor = 1;
1802
1803         if(!self.target4factor)
1804                 self.target4factor = 1;
1805
1806         if(vlen(self.targetnormal))
1807                 self.targetnormal = normalize(self.targetnormal);
1808
1809         if(vlen(self.target2normal))
1810                 self.target2normal = normalize(self.target2normal);
1811
1812         if(vlen(self.target3normal))
1813                 self.target3normal = normalize(self.target3normal);
1814
1815         if(vlen(self.target4normal))
1816                 self.target4normal = normalize(self.target4normal);
1817
1818         self.blocked = generic_plat_blocked;
1819         if(self.dmg & (!self.message))
1820                 self.message = " was squished";
1821     if(self.dmg && (!self.message2))
1822                 self.message2 = "was squished by";
1823         if(self.dmg && (!self.dmgtime))
1824                 self.dmgtime = 0.25;
1825         self.dmgtime2 = time;
1826
1827         if(self.netname == "")
1828                 self.netname = "1 0 0 0 1";
1829
1830         if not(InitMovingBrushTrigger())
1831                 return;
1832
1833         // wait for targets to spawn
1834         self.nextthink = self.ltime + 999999999;
1835         self.think = SUB_Null;
1836
1837         // Savage: Reduce bandwith, critical on e.g. nexdm02
1838         self.effects |= EF_LOWPRECISION;
1839
1840         InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
1841 }