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