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