]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/t_plats.qc
item_key initial commit
[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         
980         if (self.spawnflags & (SPAWNFLAGS_GOLD_KEY | SPAWNFLAGS_SILVER_KEY)) {
981                 // this door require a key
982                 // only a player can have a key
983                 if (other.classname != "player")
984                         return;
985                 
986                 // check gold key
987                 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
988                         if (!(other.itemkeys & KEYS_GOLD_KEY)) {
989                                 centerprint(other, "You don't have the gold key");
990                                 return;
991                         }
992                         
993                 // check silver key
994                 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
995                         if (!(other.itemkeys & KEYS_SILVER_KEY)) {
996                                 centerprint(other, "You don't have the silver key");
997                                 return;
998                         }
999         }
1000         
1001         self.attack_finished_single = time + 1;
1002
1003         activator = other;
1004
1005         self = self.owner;
1006         door_use ();
1007 };
1008
1009
1010 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1011 {
1012         local entity oself;
1013         if(self.spawnflags & DOOR_NOSPLASH)
1014                 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
1015                         return;
1016         self.health = self.health - damage;
1017         
1018         if (self.spawnflags & SPAWNFLAGS_GOLD_KEY || self.spawnflags & SPAWNFLAGS_SILVER_KEY) {
1019                 // don't allow opening doors through damage if keys are required
1020                 return;
1021         }
1022         
1023         if (self.health <= 0)
1024         {
1025                 oself = self;
1026                 self = self.owner;
1027                 self.health = self.max_health;
1028                 self.takedamage = DAMAGE_NO;    // wil be reset upon return
1029                 door_use ();
1030                 self = oself;
1031         }
1032 };
1033
1034
1035 /*
1036 ================
1037 door_touch
1038
1039 Prints messages
1040 ================
1041 */
1042 void door_touch()
1043 {
1044         if(other.classname != "player")
1045                 return;
1046         if (self.owner.attack_finished_single > time)
1047                 return;
1048
1049         self.owner.attack_finished_single = time + 2;
1050
1051         if (!(self.owner.dmg) && (self.owner.message != ""))
1052         {
1053                 if (other.flags & FL_CLIENT)
1054                         centerprint (other, self.owner.message);
1055                 play2(other, "misc/talk.wav");
1056         }
1057 };
1058
1059
1060 void door_generic_plat_blocked()
1061 {
1062
1063     if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1064         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1065     } else {
1066
1067         if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?
1068             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1069
1070          //Dont chamge direction for dead or dying stuff
1071         if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1072             if (self.wait >= 0)
1073             {
1074                 if (self.state == STATE_DOWN)
1075                     door_rotating_go_up ();
1076                 else
1077                     door_rotating_go_down ();
1078             }
1079         } else {
1080             //gib dying stuff just to make sure
1081             if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?
1082                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1083         }
1084     }
1085
1086         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1087 // if a door has a negative wait, it would never come back if blocked,
1088 // so let it just squash the object to death real fast
1089 /*      if (self.wait >= 0)
1090         {
1091                 if (self.state == STATE_DOWN)
1092                         door_rotating_go_up ();
1093                 else
1094                         door_rotating_go_down ();
1095         }
1096 */
1097 };
1098
1099
1100 void door_rotating_hit_top()
1101 {
1102         if (self.noise1 != "")
1103                 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1104         self.state = STATE_TOP;
1105         if (self.spawnflags & DOOR_TOGGLE)
1106                 return;         // don't come down automatically
1107         self.think = door_rotating_go_down;
1108         self.nextthink = self.ltime + self.wait;
1109 };
1110
1111 void door_rotating_hit_bottom()
1112 {
1113         if (self.noise1 != "")
1114                 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1115         if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1116         {
1117                 self.pos2 = '0 0 0' - self.pos2;
1118                 self.lip = 0;
1119         }
1120         self.state = STATE_BOTTOM;
1121 };
1122
1123 void door_rotating_go_down()
1124 {
1125         if (self.noise2 != "")
1126                 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1127         if (self.max_health)
1128         {
1129                 self.takedamage = DAMAGE_YES;
1130                 self.health = self.max_health;
1131         }
1132
1133         self.state = STATE_DOWN;
1134         SUB_CalcAngleMove (self.pos1, self.speed, door_rotating_hit_bottom);
1135 };
1136
1137 void door_rotating_go_up()
1138 {
1139         if (self.state == STATE_UP)
1140                 return;         // already going up
1141
1142         if (self.state == STATE_TOP)
1143         {       // reset top wait time
1144                 self.nextthink = self.ltime + self.wait;
1145                 return;
1146         }
1147         if (self.noise2 != "")
1148                 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1149         self.state = STATE_UP;
1150         SUB_CalcAngleMove (self.pos2, self.speed, door_rotating_hit_top);
1151
1152         string oldmessage;
1153         oldmessage = self.message;
1154         self.message = "";
1155         SUB_UseTargets();
1156         self.message = oldmessage;
1157 };
1158
1159
1160
1161
1162 /*
1163 =============================================================================
1164
1165 SPAWNING FUNCTIONS
1166
1167 =============================================================================
1168 */
1169
1170
1171 entity spawn_field(vector fmins, vector fmaxs)
1172 {
1173         local entity    trigger;
1174         local   vector  t1, t2;
1175
1176         trigger = spawn();
1177         trigger.classname = "doortriggerfield";
1178         trigger.movetype = MOVETYPE_NONE;
1179         trigger.solid = SOLID_TRIGGER;
1180         trigger.owner = self;
1181         trigger.touch = door_trigger_touch;
1182
1183         t1 = fmins;
1184         t2 = fmaxs;
1185         setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1186         return (trigger);
1187 };
1188
1189
1190 float EntitiesTouching(entity e1, entity e2)
1191 {
1192         if (e1.absmin_x > e2.absmax_x)
1193                 return FALSE;
1194         if (e1.absmin_y > e2.absmax_y)
1195                 return FALSE;
1196         if (e1.absmin_z > e2.absmax_z)
1197                 return FALSE;
1198         if (e1.absmax_x < e2.absmin_x)
1199                 return FALSE;
1200         if (e1.absmax_y < e2.absmin_y)
1201                 return FALSE;
1202         if (e1.absmax_z < e2.absmin_z)
1203                 return FALSE;
1204         return TRUE;
1205 };
1206
1207
1208 /*
1209 =============
1210 LinkDoors
1211
1212
1213 =============
1214 */
1215 void LinkDoors()
1216 {
1217         local entity    t, starte;
1218         local vector    cmins, cmaxs;
1219
1220         if (self.enemy)
1221                 return;         // already linked by another door
1222         if (self.spawnflags & 4)
1223         {
1224                 self.owner = self.enemy = self;
1225
1226                 if (self.health)
1227                         return;
1228                 IFTARGETED
1229                         return;
1230                 if (self.items)
1231                         return;
1232                 self.trigger_field = spawn_field(self.absmin, self.absmax);
1233
1234                 return;         // don't want to link this door
1235         }
1236
1237         cmins = self.absmin;
1238         cmaxs = self.absmax;
1239
1240         starte = self;
1241         t = self;
1242
1243         do
1244         {
1245                 self.owner = starte;                    // master door
1246
1247                 if (self.health)
1248                         starte.health = self.health;
1249                 IFTARGETED
1250                         starte.targetname = self.targetname;
1251                 if (self.message != "")
1252                         starte.message = self.message;
1253
1254                 t = find(t, classname, self.classname);
1255                 if (!t)
1256                 {
1257                         self.enemy = starte;            // make the chain a loop
1258
1259                 // shootable, or triggered doors just needed the owner/enemy links,
1260                 // they don't spawn a field
1261
1262                         self = self.owner;
1263
1264                         if (self.health)
1265                                 return;
1266                         IFTARGETED
1267                                 return;
1268                         if (self.items)
1269                                 return;
1270
1271                         self.owner.trigger_field = spawn_field(cmins, cmaxs);
1272
1273                         return;
1274                 }
1275
1276                 if (EntitiesTouching(self,t))
1277                 {
1278                         if (t.enemy)
1279                                 objerror ("cross connected doors");
1280
1281                         self.enemy = t;
1282                         self = t;
1283
1284                         if (t.absmin_x < cmins_x)
1285                                 cmins_x = t.absmin_x;
1286                         if (t.absmin_y < cmins_y)
1287                                 cmins_y = t.absmin_y;
1288                         if (t.absmin_z < cmins_z)
1289                                 cmins_z = t.absmin_z;
1290                         if (t.absmax_x > cmaxs_x)
1291                                 cmaxs_x = t.absmax_x;
1292                         if (t.absmax_y > cmaxs_y)
1293                                 cmaxs_y = t.absmax_y;
1294                         if (t.absmax_z > cmaxs_z)
1295                                 cmaxs_z = t.absmax_z;
1296                 }
1297         } while (1 );
1298
1299 };
1300
1301
1302 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
1303 if two doors touch, they are assumed to be connected and operate as a unit.
1304
1305 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1306
1307 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).
1308
1309 GOLD_KEY causes the door to open only if the activator holds a gold key.
1310
1311 SILVER_KEY causes the door to open only if the activator holds a silver key.
1312
1313 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1314 "angle"         determines the opening direction
1315 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1316 "health"        if set, door must be shot open
1317 "speed"         movement speed (100 default)
1318 "wait"          wait before returning (3 default, -1 = never return)
1319 "lip"           lip remaining at end of move (8 default)
1320 "dmg"           damage to inflict when blocked (2 default)
1321 "sounds"
1322 0)      no sound
1323 1)      stone
1324 2)      base
1325 3)      stone chain
1326 4)      screechy metal
1327 FIXME: only one sound set available at the time being
1328
1329 */
1330
1331 void door_init_startopen()
1332 {
1333         setorigin (self, self.pos2);
1334         self.pos2 = self.pos1;
1335         self.pos1 = self.origin;
1336 }
1337
1338 void door_reset()
1339 {
1340         setorigin(self, self.pos1);
1341         self.velocity = '0 0 0';
1342         self.state = STATE_BOTTOM;
1343         self.think = SUB_Null;
1344 }
1345
1346 void spawnfunc_func_door()
1347 {
1348         //if (!self.deathtype) // map makers can override this
1349         //      self.deathtype = " got in the way";
1350         SetMovedir ();
1351
1352         self.max_health = self.health;
1353         if not(InitMovingBrushTrigger())
1354                 return;
1355         self.effects |= EF_LOWPRECISION;
1356         self.classname = "door";
1357
1358         self.blocked = door_blocked;
1359         self.use = door_use;
1360
1361     if(self.spawnflags & 8)
1362         self.dmg = 10000;
1363
1364     if(self.dmg && (!self.message))
1365                 self.message = "was squished";
1366     if(self.dmg && (!self.message2))
1367                 self.message2 = "was squished by";
1368
1369         if (self.sounds > 0)
1370         {
1371                 precache_sound ("plats/medplat1.wav");
1372                 precache_sound ("plats/medplat2.wav");
1373                 self.noise2 = "plats/medplat1.wav";
1374                 self.noise1 = "plats/medplat2.wav";
1375         }
1376
1377         if (!self.speed)
1378                 self.speed = 100;
1379         if (!self.wait)
1380                 self.wait = 3;
1381         if (!self.lip)
1382                 self.lip = 8;
1383
1384         self.pos1 = self.origin;
1385         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1386
1387 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1388 // but spawn in the open position
1389         if (self.spawnflags & DOOR_START_OPEN)
1390                 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1391
1392         self.state = STATE_BOTTOM;
1393
1394         if (self.health)
1395         {
1396                 self.takedamage = DAMAGE_YES;
1397                 self.event_damage = door_damage;
1398         }
1399
1400         if (self.items)
1401                 self.wait = -1;
1402
1403         self.touch = door_touch;
1404
1405 // LinkDoors can't be done until all of the doors have been spawned, so
1406 // the sizes can be detected properly.
1407         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1408
1409         self.reset = door_reset;
1410 };
1411
1412 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1413 if two doors touch, they are assumed to be connected and operate as a unit.
1414
1415 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1416
1417 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1418 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1419 must have set trigger_reverse to 1.
1420 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1421
1422 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).
1423
1424 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1425 "angle"         determines the destination angle for opening. negative values reverse the direction.
1426 "targetname"    if set, no touch field will be spawned and a remote button or trigger field activates the door.
1427 "health"        if set, door must be shot open
1428 "speed"         movement speed (100 default)
1429 "wait"          wait before returning (3 default, -1 = never return)
1430 "dmg"           damage to inflict when blocked (2 default)
1431 "sounds"
1432 0)      no sound
1433 1)      stone
1434 2)      base
1435 3)      stone chain
1436 4)      screechy metal
1437 FIXME: only one sound set available at the time being
1438 */
1439
1440 void door_rotating_reset()
1441 {
1442         self.angles = self.pos1;
1443         self.avelocity = '0 0 0';
1444         self.state = STATE_BOTTOM;
1445         self.think = SUB_Null;
1446 }
1447
1448 void door_rotating_init_startopen()
1449 {
1450         self.angles = self.movedir;
1451         self.pos2 = '0 0 0';
1452         self.pos1 = self.movedir;
1453 }
1454
1455
1456 void spawnfunc_func_door_rotating()
1457 {
1458
1459         //if (!self.deathtype) // map makers can override this
1460         //      self.deathtype = " got in the way";
1461
1462         // I abuse "movedir" for denoting the axis for now
1463         if (self.spawnflags & 64) // X (untested)
1464                 self.movedir = '0 0 1';
1465         else if (self.spawnflags & 128) // Y (untested)
1466                 self.movedir = '1 0 0';
1467         else // Z
1468                 self.movedir = '0 1 0';
1469
1470         if (self.angles_y==0) self.angles_y = 90;
1471
1472         self.movedir = self.movedir * self.angles_y;
1473         self.angles = '0 0 0';
1474
1475         self.max_health = self.health;
1476         self.avelocity = self.movedir;
1477         if not(InitMovingBrushTrigger())
1478                 return;
1479         self.velocity = '0 0 0';
1480         //self.effects |= EF_LOWPRECISION;
1481         self.classname = "door_rotating";
1482
1483         self.blocked = door_blocked;
1484         self.use = door_use;
1485
1486     if(self.spawnflags & 8)
1487         self.dmg = 10000;
1488
1489     if(self.dmg && (!self.message))
1490                 self.message = "was squished";
1491     if(self.dmg && (!self.message2))
1492                 self.message2 = "was squished by";
1493
1494     if (self.sounds > 0)
1495         {
1496                 precache_sound ("plats/medplat1.wav");
1497                 precache_sound ("plats/medplat2.wav");
1498                 self.noise2 = "plats/medplat1.wav";
1499                 self.noise1 = "plats/medplat2.wav";
1500         }
1501
1502         if (!self.speed)
1503                 self.speed = 50;
1504         if (!self.wait)
1505                 self.wait = 1;
1506         self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1507
1508         self.pos1 = '0 0 0';
1509         self.pos2 = self.movedir;
1510
1511 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1512 // but spawn in the open position
1513         if (self.spawnflags & DOOR_START_OPEN)
1514                 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1515
1516         self.state = STATE_BOTTOM;
1517
1518         if (self.health)
1519         {
1520                 self.takedamage = DAMAGE_YES;
1521                 self.event_damage = door_damage;
1522         }
1523
1524         if (self.items)
1525                 self.wait = -1;
1526
1527         self.touch = door_touch;
1528
1529 // LinkDoors can't be done until all of the doors have been spawned, so
1530 // the sizes can be detected properly.
1531         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1532
1533         self.reset = door_rotating_reset;
1534 };
1535
1536 /*
1537 =============================================================================
1538
1539 SECRET DOORS
1540
1541 =============================================================================
1542 */
1543
1544 void() fd_secret_move1;
1545 void() fd_secret_move2;
1546 void() fd_secret_move3;
1547 void() fd_secret_move4;
1548 void() fd_secret_move5;
1549 void() fd_secret_move6;
1550 void() fd_secret_done;
1551
1552 float SECRET_OPEN_ONCE = 1;             // stays open
1553 float SECRET_1ST_LEFT = 2;              // 1st move is left of arrow
1554 float SECRET_1ST_DOWN = 4;              // 1st move is down from arrow
1555 float SECRET_NO_SHOOT = 8;              // only opened by trigger
1556 float SECRET_YES_SHOOT = 16;    // shootable even if targeted
1557
1558
1559 void fd_secret_use()
1560 {
1561         local float temp;
1562         string message_save;
1563
1564         self.health = 10000;
1565         self.bot_attack = TRUE;
1566
1567         // exit if still moving around...
1568         if (self.origin != self.oldorigin)
1569                 return;
1570
1571         message_save = self.message;
1572         self.message = ""; // no more message
1573         SUB_UseTargets();                               // fire all targets / killtargets
1574         self.message = message_save;
1575
1576         self.velocity = '0 0 0';
1577
1578         // Make a sound, wait a little...
1579
1580         if (self.noise1 != "")
1581                 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1582         self.nextthink = self.ltime + 0.1;
1583
1584         temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1585         makevectors(self.mangle);
1586
1587         if (!self.t_width)
1588         {
1589                 if (self.spawnflags & SECRET_1ST_DOWN)
1590                         self.t_width = fabs(v_up * self.size);
1591                 else
1592                         self.t_width = fabs(v_right * self.size);
1593         }
1594
1595         if (!self.t_length)
1596                 self.t_length = fabs(v_forward * self.size);
1597
1598         if (self.spawnflags & SECRET_1ST_DOWN)
1599                 self.dest1 = self.origin - v_up * self.t_width;
1600         else
1601                 self.dest1 = self.origin + v_right * (self.t_width * temp);
1602
1603         self.dest2 = self.dest1 + v_forward * self.t_length;
1604         SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1605         if (self.noise2 != "")
1606                 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1607 };
1608
1609 // Wait after first movement...
1610 void fd_secret_move1()
1611 {
1612         self.nextthink = self.ltime + 1.0;
1613         self.think = fd_secret_move2;
1614         if (self.noise3 != "")
1615                 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1616 };
1617
1618 // Start moving sideways w/sound...
1619 void fd_secret_move2()
1620 {
1621         if (self.noise2 != "")
1622                 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1623         SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1624 };
1625
1626 // Wait here until time to go back...
1627 void fd_secret_move3()
1628 {
1629         if (self.noise3 != "")
1630                 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1631         if (!(self.spawnflags & SECRET_OPEN_ONCE))
1632         {
1633                 self.nextthink = self.ltime + self.wait;
1634                 self.think = fd_secret_move4;
1635         }
1636 };
1637
1638 // Move backward...
1639 void fd_secret_move4()
1640 {
1641         if (self.noise2 != "")
1642                 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1643         SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1644 };
1645
1646 // Wait 1 second...
1647 void fd_secret_move5()
1648 {
1649         self.nextthink = self.ltime + 1.0;
1650         self.think = fd_secret_move6;
1651         if (self.noise3 != "")
1652                 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1653 };
1654
1655 void fd_secret_move6()
1656 {
1657         if (self.noise2 != "")
1658                 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1659         SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1660 };
1661
1662 void fd_secret_done()
1663 {
1664         if (self.spawnflags&SECRET_YES_SHOOT)
1665         {
1666                 self.health = 10000;
1667                 self.takedamage = DAMAGE_YES;
1668                 //self.th_pain = fd_secret_use;
1669         }
1670         if (self.noise3 != "")
1671                 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1672 };
1673
1674 void secret_blocked()
1675 {
1676         if (time < self.attack_finished_single)
1677                 return;
1678         self.attack_finished_single = time + 0.5;
1679         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1680 };
1681
1682 /*
1683 ==============
1684 secret_touch
1685
1686 Prints messages
1687 ================
1688 */
1689 void secret_touch()
1690 {
1691         if not(other.iscreature)
1692                 return;
1693         if (self.attack_finished_single > time)
1694                 return;
1695
1696         self.attack_finished_single = time + 2;
1697
1698         if (self.message)
1699         {
1700                 if (other.flags & FL_CLIENT)
1701                         centerprint (other, self.message);
1702                 play2(other, "misc/talk.wav");
1703         }
1704 };
1705
1706 void secret_reset()
1707 {
1708         if (self.spawnflags&SECRET_YES_SHOOT)
1709         {
1710                 self.health = 10000;
1711                 self.takedamage = DAMAGE_YES;
1712         }
1713         setorigin(self, self.oldorigin);
1714         self.think = SUB_Null;
1715 }
1716
1717 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1718 Basic secret door. Slides back, then to the side. Angle determines direction.
1719 wait  = # of seconds before coming back
1720 1st_left = 1st move is left of arrow
1721 1st_down = 1st move is down from arrow
1722 always_shoot = even if targeted, keep shootable
1723 t_width = override WIDTH to move back (or height if going down)
1724 t_length = override LENGTH to move sideways
1725 "dmg"           damage to inflict when blocked (2 default)
1726
1727 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1728 "sounds"
1729 1) medieval
1730 2) metal
1731 3) base
1732 */
1733
1734 void spawnfunc_func_door_secret()
1735 {
1736         /*if (!self.deathtype) // map makers can override this
1737                 self.deathtype = " got in the way";*/
1738
1739         if (!self.dmg)
1740                 self.dmg = 2;
1741
1742         // Magic formula...
1743         self.mangle = self.angles;
1744         self.angles = '0 0 0';
1745         self.classname = "door";
1746         if not(InitMovingBrushTrigger())
1747                 return;
1748         self.effects |= EF_LOWPRECISION;
1749
1750         self.touch = secret_touch;
1751         self.blocked = secret_blocked;
1752         self.speed = 50;
1753         self.use = fd_secret_use;
1754         IFTARGETED
1755         {
1756         }
1757         else
1758                 self.spawnflags |= SECRET_YES_SHOOT;
1759
1760         if(self.spawnflags&SECRET_YES_SHOOT)
1761         {
1762                 self.health = 10000;
1763                 self.takedamage = DAMAGE_YES;
1764                 self.event_damage = fd_secret_use;
1765         }
1766         self.oldorigin = self.origin;
1767         if (!self.wait)
1768                 self.wait = 5;          // 5 seconds before closing
1769
1770         self.reset = secret_reset;
1771         secret_reset();
1772 };
1773
1774 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1775 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1776 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
1777 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1778 height: amplitude modifier (default 32)
1779 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1780 noise: path/name of looping .wav file to play.
1781 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1782 dmgtime: See above.
1783 */
1784
1785 void func_fourier_controller_think()
1786 {
1787         local vector v;
1788         float n, i, t;
1789
1790         self.nextthink = time + 0.1;
1791         if not (self.owner.active == ACTIVE_ACTIVE)
1792         {
1793                 self.owner.velocity = '0 0 0';          
1794                 return;
1795         }
1796
1797
1798         n = floor((tokenize_console(self.owner.netname)) / 5);
1799         t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1800
1801         v = self.owner.destvec;
1802
1803         for(i = 0; i < n; ++i)
1804         {
1805                 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1806                 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;
1807         }
1808
1809         if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1810                 // * 10 so it will arrive in 0.1 sec
1811                 self.owner.velocity = (v - self.owner.origin) * 10;
1812 };
1813
1814 void spawnfunc_func_fourier()
1815 {
1816         local entity controller;
1817         if (self.noise != "")
1818         {
1819                 precache_sound(self.noise);
1820                 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
1821         }
1822
1823         if (!self.speed)
1824                 self.speed = 4;
1825         if (!self.height)
1826                 self.height = 32;
1827         self.destvec = self.origin;
1828         self.cnt = 360 / self.speed;
1829
1830         self.blocked = generic_plat_blocked;
1831         if(self.dmg & (!self.message))
1832                 self.message = " was squished";
1833     if(self.dmg && (!self.message2))
1834                 self.message2 = "was squished by";
1835         if(self.dmg && (!self.dmgtime))
1836                 self.dmgtime = 0.25;
1837         self.dmgtime2 = time;
1838
1839         if(self.netname == "")
1840                 self.netname = "1 0 0 0 1";
1841
1842         if not(InitMovingBrushTrigger())
1843                 return;
1844
1845         self.active = ACTIVE_ACTIVE;
1846
1847         // wait for targets to spawn
1848         controller = spawn();
1849         controller.classname = "func_fourier_controller";
1850         controller.owner = self;
1851         controller.nextthink = time + 1;
1852         controller.think = func_fourier_controller_think;
1853         self.nextthink = self.ltime + 999999999;
1854         self.think = SUB_Null;
1855
1856         // Savage: Reduce bandwith, critical on e.g. nexdm02
1857         self.effects |= EF_LOWPRECISION;
1858
1859         // TODO make a reset function for this one
1860 };
1861
1862 // reusing some fields havocbots declared
1863 .entity wp00, wp01, wp02, wp03;
1864
1865 .float targetfactor, target2factor, target3factor, target4factor;
1866 .vector targetnormal, target2normal, target3normal, target4normal;
1867
1868 vector func_vectormamamam_origin(entity o, float t)
1869 {
1870         vector v, p;
1871         float f;
1872         entity e;
1873
1874         f = o.spawnflags;
1875         v = '0 0 0';
1876
1877         e = o.wp00;
1878         if(e)
1879         {
1880                 p = e.origin + t * e.velocity;
1881                 if(f & 1)
1882                         v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
1883                 else
1884                         v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
1885         }
1886
1887         e = o.wp01;
1888         if(e)
1889         {
1890                 p = e.origin + t * e.velocity;
1891                 if(f & 2)
1892                         v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
1893                 else
1894                         v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
1895         }
1896
1897         e = o.wp02;
1898         if(e)
1899         {
1900                 p = e.origin + t * e.velocity;
1901                 if(f & 4)
1902                         v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
1903                 else
1904                         v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
1905         }
1906
1907         e = o.wp03;
1908         if(e)
1909         {
1910                 p = e.origin + t * e.velocity;
1911                 if(f & 8)
1912                         v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
1913                 else
1914                         v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
1915         }
1916
1917         return v;
1918 }
1919
1920 void func_vectormamamam_controller_think()
1921 {
1922         self.nextthink = time + 0.1;
1923
1924         if not (self.owner.active == ACTIVE_ACTIVE)
1925         {
1926                 self.owner.velocity = '0 0 0';          
1927                 return;
1928         }
1929
1930         if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
1931                 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
1932 }
1933
1934 void func_vectormamamam_findtarget()
1935 {
1936         if(self.target != "")
1937                 self.wp00 = find(world, targetname, self.target);
1938
1939         if(self.target2 != "")
1940                 self.wp01 = find(world, targetname, self.target2);
1941
1942         if(self.target3 != "")
1943                 self.wp02 = find(world, targetname, self.target3);
1944
1945         if(self.target4 != "")
1946                 self.wp03 = find(world, targetname, self.target4);
1947
1948         if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
1949                 objerror("No reference entity found, so there is nothing to move. Aborting.");
1950
1951         self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
1952
1953         local entity controller;
1954         controller = spawn();
1955         controller.classname = "func_vectormamamam_controller";
1956         controller.owner = self;
1957         controller.nextthink = time + 1;
1958         controller.think = func_vectormamamam_controller_think;
1959 }
1960
1961 void spawnfunc_func_vectormamamam()
1962 {
1963         if (self.noise != "")
1964         {
1965                 precache_sound(self.noise);
1966                 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
1967         }
1968
1969         if(!self.targetfactor)
1970                 self.targetfactor = 1;
1971
1972         if(!self.target2factor)
1973                 self.target2factor = 1;
1974
1975         if(!self.target3factor)
1976                 self.target3factor = 1;
1977
1978         if(!self.target4factor)
1979                 self.target4factor = 1;
1980
1981         if(vlen(self.targetnormal))
1982                 self.targetnormal = normalize(self.targetnormal);
1983
1984         if(vlen(self.target2normal))
1985                 self.target2normal = normalize(self.target2normal);
1986
1987         if(vlen(self.target3normal))
1988                 self.target3normal = normalize(self.target3normal);
1989
1990         if(vlen(self.target4normal))
1991                 self.target4normal = normalize(self.target4normal);
1992
1993         self.blocked = generic_plat_blocked;
1994         if(self.dmg & (!self.message))
1995                 self.message = " was squished";
1996     if(self.dmg && (!self.message2))
1997                 self.message2 = "was squished by";
1998         if(self.dmg && (!self.dmgtime))
1999                 self.dmgtime = 0.25;
2000         self.dmgtime2 = time;
2001
2002         if(self.netname == "")
2003                 self.netname = "1 0 0 0 1";
2004
2005         if not(InitMovingBrushTrigger())
2006                 return;
2007
2008         // wait for targets to spawn
2009         self.nextthink = self.ltime + 999999999;
2010         self.think = SUB_Null;
2011
2012         // Savage: Reduce bandwith, critical on e.g. nexdm02
2013         self.effects |= EF_LOWPRECISION;
2014
2015         self.active = ACTIVE_ACTIVE;
2016
2017         InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
2018 }