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