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