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