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