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