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