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