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