make doors/plats not change their position in the spawn function (but defer that...
[divverent/nexuiz.git] / data / qcsrc / server / t_plats.qc
1
2 float   STATE_TOP               = 0;
3 float   STATE_BOTTOM    = 1;
4 float   STATE_UP                = 2;
5 float   STATE_DOWN              = 3;
6
7 .entity trigger_field;
8 //.float dmgtime;
9 .float dmgtime2;
10
11 void() plat_center_touch;
12 void() plat_outside_touch;
13 void() plat_trigger_use;
14 void() plat_go_up;
15 void() plat_go_down;
16 void() plat_crush;
17 float PLAT_LOW_TRIGGER = 1;
18
19 void plat_spawn_inside_trigger()
20 {
21         local entity trigger;
22         local vector tmin, tmax;
23
24         trigger = spawn();
25         trigger.touch = plat_center_touch;
26         trigger.movetype = MOVETYPE_NONE;
27         trigger.solid = SOLID_TRIGGER;
28         trigger.enemy = self;
29
30         tmin = self.mins + '25 25 0';
31         tmax = self.maxs - '25 25 -8';
32         tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8);
33         if (self.spawnflags & PLAT_LOW_TRIGGER)
34                 tmax_z = tmin_z + 8;
35
36         if (self.size_x <= 50)
37         {
38                 tmin_x = (self.mins_x + self.maxs_x) / 2;
39                 tmax_x = tmin_x + 1;
40         }
41         if (self.size_y <= 50)
42         {
43                 tmin_y = (self.mins_y + self.maxs_y) / 2;
44                 tmax_y = tmin_y + 1;
45         }
46
47         setsize (trigger, tmin, tmax);
48 };
49
50 void plat_hit_top()
51 {
52         sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
53         self.state = 1;
54         self.think = plat_go_down;
55         self.nextthink = self.ltime + 3;
56 };
57
58 void plat_hit_bottom()
59 {
60         sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
61         self.state = 2;
62 };
63
64 void plat_go_down()
65 {
66         sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
67         self.state = 3;
68         SUB_CalcMove (self.pos2, self.speed, plat_hit_bottom);
69 };
70
71 void plat_go_up()
72 {
73         sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
74         self.state = 4;
75         SUB_CalcMove (self.pos1, self.speed, plat_hit_top);
76 };
77
78 void plat_center_touch()
79 {
80         if (other.classname != "player")
81                 return;
82
83         if (other.health <= 0)
84                 return;
85
86         self = self.enemy;
87         if (self.state == 2)
88                 plat_go_up ();
89         else if (self.state == 1)
90                 self.nextthink = self.ltime + 1;        // delay going down
91 };
92
93 void plat_outside_touch()
94 {
95         if (other.classname != "player")
96                 return;
97
98         if (other.health <= 0)
99                 return;
100
101         self = self.enemy;
102         if (self.state == 1)
103                 plat_go_down ();
104 };
105
106 void plat_trigger_use()
107 {
108         if (self.think)
109                 return;         // allready activated
110         plat_go_down();
111 };
112
113
114 void plat_crush()
115 {
116     if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
117         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
118     } else {
119         if((self.dmg) && (other.takedamage != DAMAGE_NO)) {   // Shall we bite?
120             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
121             // Gib dead/dying stuff
122             if(other.deadflag != DEAD_NO)
123                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
124         }
125
126         if (self.state == 4)
127             plat_go_down ();
128         else if (self.state == 3)
129             plat_go_up ();
130         else
131             objerror ("plat_crush: bad self.state\n");
132     }
133 };
134
135 void plat_use()
136 {
137         self.use = SUB_Null;
138         if (self.state != 4)
139                 objerror ("plat_use: not in up state");
140         plat_go_down();
141 };
142
143 void plat_init_movedown()
144 {
145         setorigin (self, self.pos2);
146         self.state = 2;
147 }
148
149 .string sound1, sound2;
150
151 void spawnfunc_path_corner() { };
152 void spawnfunc_func_plat()
153 {
154         if (!self.t_length)
155                 self.t_length = 80;
156         if (!self.t_width)
157                 self.t_width = 10;
158
159         if (self.sounds == 0)
160                 self.sounds = 2;
161
162     if(self.spawnflags & 4)
163         self.dmg = 10000;
164
165     if(self.dmg && (!self.message))
166                 self.message = "was in the wrong place.";
167
168         if (self.sounds == 1)
169         {
170                 precache_sound ("plats/plat1.wav");
171                 precache_sound ("plats/plat2.wav");
172                 self.noise = "plats/plat1.wav";
173                 self.noise1 = "plats/plat2.wav";
174         }
175
176         if (self.sounds == 2)
177         {
178                 precache_sound ("plats/medplat1.wav");
179                 precache_sound ("plats/medplat2.wav");
180                 self.noise = "plats/medplat1.wav";
181                 self.noise1 = "plats/medplat2.wav";
182         }
183
184         if (self.sound1)
185         {
186                 precache_sound (self.sound1);
187                 self.noise = self.sound1;
188         }
189         if (self.sound2)
190         {
191                 precache_sound (self.sound2);
192                 self.noise1 = self.sound2;
193         }
194
195         self.mangle = self.angles;
196         self.angles = '0 0 0';
197
198         self.classname = "plat";
199         InitMovingBrushTrigger();
200         self.effects |= EF_LOWPRECISION;
201         setsize (self, self.mins , self.maxs);
202
203         self.blocked = plat_crush;
204
205         if (!self.speed)
206                 self.speed = 150;
207
208         self.pos1 = self.origin;
209         self.pos2 = self.origin;
210         self.pos2_z = self.origin_z - self.size_z + 8;
211
212         self.use = plat_trigger_use;
213
214         plat_spawn_inside_trigger ();   // the "start moving" trigger
215
216         IFTARGETED
217         {
218                 self.state = 4;
219                 self.use = plat_use;
220         }
221         else
222                 InitializeEntity(self, plat_init_movedown, INITPRIO_SETLOCATION);
223 };
224
225
226 void() train_next;
227 void train_wait()
228 {
229         self.think = train_next;
230         self.nextthink = self.ltime + self.wait;
231 };
232
233 void train_next()
234 {
235         local entity targ;
236         targ = find(world, targetname, self.target);
237         self.target = targ.target;
238         if (!self.target)
239                 objerror("train_next: no next target");
240         self.wait = targ.wait;
241         if (!self.wait)
242                 self.wait = 0.1;
243         if (targ.speed)
244                 SUB_CalcMove(targ.origin - self.mins, targ.speed, train_wait);
245         else
246                 SUB_CalcMove(targ.origin - self.mins, self.speed, train_wait);
247 };
248
249 void func_train_find()
250 {
251         local entity targ;
252         targ = find(world, targetname, self.target);
253         self.target = targ.target;
254         if (!self.target)
255                 objerror("func_train_find: no next target");
256         setorigin(self, targ.origin - self.mins);
257         self.nextthink = self.ltime + 1;
258         self.think = train_next;
259 };
260
261 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
262 Ridable platform, targets spawnfunc_path_corner path to follow.
263 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
264 target : targetname of first spawnfunc_path_corner (starts here)
265 */
266 void spawnfunc_func_train()
267 {
268         if (!self.target)
269                 objerror("func_train without a target");
270         if (!self.speed)
271                 self.speed = 100;
272
273         InitMovingBrushTrigger();
274         self.effects |= EF_LOWPRECISION;
275
276         // wait for targets to spawn
277         InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
278 };
279
280
281
282 void rotating_blocked()
283 {
284
285     if(self.dmg && other.takedamage != DAMAGE_NO) {
286         if(self.dmgtime2 < time) {
287             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
288             self.dmgtime2 = time + self.dmgtime;
289         }
290
291         // Gib dead/dying stuff
292         if(other.deadflag != DEAD_NO)
293             Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
294     }
295
296
297 }
298
299 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
300 Brush model that spins in place on one axis (default Z).
301 speed   : speed to rotate (in degrees per second)
302 noise   : path/name of looping .wav file to play.
303 dmg     : Do this mutch dmg every .dmgtime intervall when blocked
304 dmgtime : See above.
305 */
306
307 void spawnfunc_func_rotating()
308 {
309         if (self.noise)
310         {
311                 precache_sound(self.noise);
312                 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
313         }
314         if (!self.speed)
315                 self.speed = 100;
316         // FIXME: test if this turns the right way, then remove this comment (negate as needed)
317         if (self.spawnflags & 4) // X (untested)
318                 self.avelocity = '0 0 1' * self.speed;
319         // FIXME: test if this turns the right way, then remove this comment (negate as needed)
320         else if (self.spawnflags & 8) // Y (untested)
321                 self.avelocity = '1 0 0' * self.speed;
322         // FIXME: test if this turns the right way, then remove this comment (negate as needed)
323         else // Z
324                 self.avelocity = '0 1 0' * self.speed;
325
326     if(self.dmg & (!self.message))
327         self.message = " was in the wrong place.";
328
329
330     if(self.dmg && (!self.dmgtime))
331         self.dmgtime = 0.25;
332
333     self.dmgtime2 = time;
334
335         InitMovingBrushTrigger();
336         // no EF_LOWPRECISION here, as rounding angles is bad
337
338     self.blocked = rotating_blocked;
339
340         // wait for targets to spawn
341         self.nextthink = self.ltime + 999999999;
342         self.think = SUB_Null;
343 };
344
345 .float height;
346 .float phase;
347 void func_bobbing_controller_think()
348 {
349         local vector v;
350         self.nextthink = time + 0.1;
351         // calculate sinewave using makevectors
352         makevectors((time * self.owner.cnt + self.owner.phase) * '0 1 0');
353         v = self.owner.destvec + self.owner.movedir * v_forward_y;
354         // * 10 so it will arrive in 0.1 sec
355         self.owner.velocity = (v - self.owner.origin) * 10;
356 };
357
358 void bobbing_blocked()
359 {
360         // no need to duplicate code
361         rotating_blocked();
362 }
363
364 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
365 Brush model that moves back and forth on one axis (default Z).
366 speed : how long one cycle takes in seconds (default 4)
367 height : how far the cycle moves (default 32)
368 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
369 noise : path/name of looping .wav file to play.
370 dmg : Do this mutch dmg every .dmgtime intervall when blocked
371 dmgtime : See above.
372 */
373 void spawnfunc_func_bobbing()
374 {
375         local entity controller;
376         if (self.noise)
377         {
378                 precache_sound(self.noise);
379                 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
380         }
381         if (!self.speed)
382                 self.speed = 4;
383         if (!self.height)
384                 self.height = 32;
385         // center of bobbing motion
386         self.destvec = self.origin;
387         // time scale to get degrees
388         self.cnt = 360 / self.speed;
389
390         // damage when blocked
391         self.blocked = bobbing_blocked;
392         if(self.dmg & (!self.message))
393                 self.message = " was in the wrong place.";
394         if(self.dmg && (!self.dmgtime))
395                 self.dmgtime = 0.25;
396         self.dmgtime2 = time;
397
398         // how far to bob
399         if (self.spawnflags & 1) // X
400                 self.movedir = '1 0 0' * self.height;
401         else if (self.spawnflags & 2) // Y
402                 self.movedir = '0 1 0' * self.height;
403         else // Z
404                 self.movedir = '0 0 1' * self.height;
405
406         InitMovingBrushTrigger();
407
408         // wait for targets to spawn
409         controller = spawn();
410         controller.classname = "func_bobbing_controller";
411         controller.owner = self;
412         controller.nextthink = time + 1;
413         controller.think = func_bobbing_controller_think;
414         self.nextthink = self.ltime + 999999999;
415         self.think = SUB_Null;
416
417         // Savage: Reduce bandwith, critical on e.g. nexdm02
418         self.effects |= EF_LOWPRECISION;
419 };
420
421 // button and multiple button
422
423 void() button_wait;
424 void() button_return;
425
426 void button_wait()
427 {
428         self.state = STATE_TOP;
429         self.nextthink = self.ltime + self.wait;
430         self.think = button_return;
431         activator = self.enemy;
432         SUB_UseTargets();
433         self.frame = 1;                 // use alternate textures
434 };
435
436 void button_done()
437 {
438         self.state = STATE_BOTTOM;
439 };
440
441 void button_return()
442 {
443         self.state = STATE_DOWN;
444         SUB_CalcMove (self.pos1, self.speed, button_done);
445         self.frame = 0;                 // use normal textures
446         if (self.health)
447                 self.takedamage = DAMAGE_YES;   // can be shot again
448 };
449
450
451 void button_blocked()
452 {
453         // do nothing, just don't come all the way back out
454 };
455
456
457 void button_fire()
458 {
459         self.health = self.max_health;
460         self.takedamage = DAMAGE_NO;    // will be reset upon return
461
462         if (self.state == STATE_UP || self.state == STATE_TOP)
463                 return;
464
465         if (self.noise != "")
466                 sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
467
468         self.state = STATE_UP;
469         SUB_CalcMove (self.pos2, self.speed, button_wait);
470 };
471
472
473 void button_use()
474 {
475 //      if (activator.classname != "player")
476 //      {
477 //              dprint(activator.classname);
478 //              dprint(" triggered a button\n");
479 //      }
480         self.enemy = activator;
481         button_fire ();
482 };
483
484 void button_touch()
485 {
486 //      if (activator.classname != "player")
487 //      {
488 //              dprint(activator.classname);
489 //              dprint(" touched a button\n");
490 //      }
491         if (!other)
492                 return;
493         if (other.classname != "player")
494                 return;
495         self.enemy = other;
496         if (other.owner)
497                 self.enemy = other.owner;
498         button_fire ();
499 };
500
501 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
502 {
503         self.health = self.health - damage;
504         if (self.health <= 0)
505         {
506         //      if (activator.classname != "player")
507         //      {
508         //              dprint(activator.classname);
509         //              dprint(" killed a button\n");
510         //      }
511                 self.enemy = damage_attacker;
512                 button_fire ();
513         }
514 };
515
516
517 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
518 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.
519
520 "angle"         determines the opening direction
521 "target"        all entities with a matching targetname will be used
522 "speed"         override the default 40 speed
523 "wait"          override the default 1 second wait (-1 = never return)
524 "lip"           override the default 4 pixel lip remaining at end of move
525 "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
526 "sounds"
527 0) steam metal
528 1) wooden clunk
529 2) metallic click
530 3) in-out
531 */
532 void spawnfunc_func_button()
533 {
534         SetMovedir ();
535
536         InitMovingBrushTrigger();
537         self.effects |= EF_LOWPRECISION;
538
539         self.blocked = button_blocked;
540         self.use = button_use;
541
542 //      if (self.health == 0) // all buttons are now shootable
543 //              self.health = 10;
544         if (self.health)
545         {
546                 self.max_health = self.health;
547                 self.event_damage = button_damage;
548                 self.takedamage = DAMAGE_YES;
549         }
550         else
551                 self.touch = button_touch;
552
553         if (!self.speed)
554                 self.speed = 40;
555         if (!self.wait)
556                 self.wait = 1;
557         if (!self.lip)
558                 self.lip = 4;
559
560     if(self.noise != "")
561         precache_sound(self.noise);
562
563         self.state = STATE_BOTTOM;
564
565         self.pos1 = self.origin;
566         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
567 };
568
569
570 float DOOR_START_OPEN = 1;
571 float DOOR_DONT_LINK = 4;
572 float DOOR_TOGGLE = 32;
573
574 /*
575
576 Doors are similar to buttons, but can spawn a fat trigger field around them
577 to open without a touch, and they link together to form simultanious
578 double/quad doors.
579
580 Door.owner is the master door.  If there is only one door, it points to itself.
581 If multiple doors, all will point to a single one.
582
583 Door.enemy chains from the master door through all doors linked in the chain.
584
585 */
586
587 /*
588 =============================================================================
589
590 THINK FUNCTIONS
591
592 =============================================================================
593 */
594
595 void() door_go_down;
596 void() door_go_up;
597
598 void door_blocked()
599 {
600
601     if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
602         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
603     } else {
604
605         if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?
606             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
607
608          //Dont chamge direction for dead or dying stuff
609         if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
610             if (self.wait >= 0)
611             {
612                 if (self.state == STATE_DOWN)
613                     door_go_up ();
614                 else
615                     door_go_down ();
616             }
617         } else {
618             //gib dying stuff just to make sure
619             if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?
620                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
621         }
622     }
623
624         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
625 // if a door has a negative wait, it would never come back if blocked,
626 // so let it just squash the object to death real fast
627 /*      if (self.wait >= 0)
628         {
629                 if (self.state == STATE_DOWN)
630                         door_go_up ();
631                 else
632                         door_go_down ();
633         }
634 */
635 };
636
637
638 void door_hit_top()
639 {
640         if (self.noise1 != "")
641                 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
642         self.state = STATE_TOP;
643         if (self.spawnflags & DOOR_TOGGLE)
644                 return;         // don't come down automatically
645         self.think = door_go_down;
646         self.nextthink = self.ltime + self.wait;
647 };
648
649 void door_hit_bottom()
650 {
651         if (self.noise1 != "")
652                 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
653         self.state = STATE_BOTTOM;
654 };
655
656 void door_go_down()
657 {
658         if (self.noise2 != "")
659                 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
660         if (self.max_health)
661         {
662                 self.takedamage = DAMAGE_YES;
663                 self.health = self.max_health;
664         }
665
666         self.state = STATE_DOWN;
667         SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
668 };
669
670 void door_go_up()
671 {
672         if (self.state == STATE_UP)
673                 return;         // allready going up
674
675         if (self.state == STATE_TOP)
676         {       // reset top wait time
677                 self.nextthink = self.ltime + self.wait;
678                 return;
679         }
680
681         if (self.noise2 != "")
682                 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
683         self.state = STATE_UP;
684         SUB_CalcMove (self.pos2, self.speed, door_hit_top);
685
686         SUB_UseTargets();
687 };
688
689
690 /*
691 =============================================================================
692
693 ACTIVATION FUNCTIONS
694
695 =============================================================================
696 */
697
698 void door_fire()
699 {
700         local entity    oself;
701         local entity    starte;
702         string oldmessage;
703
704         if (self.owner != self)
705                 objerror ("door_fire: self.owner != self");
706
707         oldmessage = self.message;
708         self.message = ""; // no more message
709         oself = self;
710
711         if (self.spawnflags & DOOR_TOGGLE)
712         {
713                 if (self.state == STATE_UP || self.state == STATE_TOP)
714                 {
715                         starte = self;
716                         do
717                         {
718                                 door_go_down ();
719                                 self = self.enemy;
720                         } while ( (self != starte) && (self != world) );
721                         self = oself;
722                         return;
723                 }
724         }
725
726 // trigger all paired doors
727         starte = self;
728         do
729         {
730                 door_go_up ();
731                 self = self.enemy;
732         } while ( (self != starte) && (self != world) );
733         self = oself;
734
735         self.message = oldmessage;
736 };
737
738
739 void door_use()
740 {
741         local entity oself;
742
743         //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
744         if (self.owner)
745         {
746                 oself = self;
747                 self = self.owner;
748                 door_fire ();
749                 self = oself;
750         }
751 };
752
753
754 void door_trigger_touch()
755 {
756         if (other.health < 1)
757                 return;
758
759         if (time < self.attack_finished_single)
760                 return;
761         self.attack_finished_single = time + 1;
762
763         activator = other;
764
765         self = self.owner;
766         door_use ();
767 };
768
769
770 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
771 {
772         local entity oself;
773         self.health = self.health - damage;
774         if (self.health <= 0)
775         {
776                 oself = self;
777                 self = self.owner;
778                 self.health = self.max_health;
779                 self.takedamage = DAMAGE_NO;    // wil be reset upon return
780                 door_use ();
781                 self = oself;
782         }
783 };
784
785
786 /*
787 ================
788 door_touch
789
790 Prints messages
791 ================
792 */
793 void door_touch()
794 {
795         if (other.classname != "player")
796                 return;
797         if (self.owner.attack_finished_single > time)
798                 return;
799
800         self.owner.attack_finished_single = time + 2;
801
802         if (self.owner.message != "")
803         {
804                 if (other.flags & FL_CLIENT)
805                         centerprint (other, self.owner.message);
806                 play2(other, "misc/talk.wav");
807         }
808 };
809
810 /*
811 =============================================================================
812
813 SPAWNING FUNCTIONS
814
815 =============================================================================
816 */
817
818
819 entity spawn_field(vector fmins, vector fmaxs)
820 {
821         local entity    trigger;
822         local   vector  t1, t2;
823
824         trigger = spawn();
825         trigger.classname = "doortriggerfield";
826         trigger.movetype = MOVETYPE_NONE;
827         trigger.solid = SOLID_TRIGGER;
828         trigger.owner = self;
829         trigger.touch = door_trigger_touch;
830
831         t1 = fmins;
832         t2 = fmaxs;
833         setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
834         return (trigger);
835 };
836
837
838 float EntitiesTouching(entity e1, entity e2)
839 {
840         if (e1.mins_x > e2.maxs_x)
841                 return FALSE;
842         if (e1.mins_y > e2.maxs_y)
843                 return FALSE;
844         if (e1.mins_z > e2.maxs_z)
845                 return FALSE;
846         if (e1.maxs_x < e2.mins_x)
847                 return FALSE;
848         if (e1.maxs_y < e2.mins_y)
849                 return FALSE;
850         if (e1.maxs_z < e2.mins_z)
851                 return FALSE;
852         return TRUE;
853 };
854
855
856 /*
857 =============
858 LinkDoors
859
860
861 =============
862 */
863 void LinkDoors()
864 {
865         local entity    t, starte;
866         local vector    cmins, cmaxs;
867
868         if (self.enemy)
869                 return;         // already linked by another door
870         if (self.spawnflags & 4)
871         {
872                 self.owner = self.enemy = self;
873                 return;         // don't want to link this door
874         }
875
876         cmins = self.mins;
877         cmaxs = self.maxs;
878
879         starte = self;
880         t = self;
881
882         do
883         {
884                 self.owner = starte;                    // master door
885
886                 if (self.health)
887                         starte.health = self.health;
888                 IFTARGETED
889                         starte.targetname = self.targetname;
890                 if (self.message != "")
891                         starte.message = self.message;
892
893                 t = find(t, classname, self.classname);
894                 if (!t)
895                 {
896                         self.enemy = starte;            // make the chain a loop
897
898                 // shootable, or triggered doors just needed the owner/enemy links,
899                 // they don't spawn a field
900
901                         self = self.owner;
902
903                         if (self.health)
904                                 return;
905                         IFTARGETED
906                                 return;
907                         if (self.items)
908                                 return;
909
910                         self.owner.trigger_field = spawn_field(cmins, cmaxs);
911
912                         return;
913                 }
914
915                 if (EntitiesTouching(self,t))
916                 {
917                         if (t.enemy)
918                                 objerror ("cross connected doors");
919
920                         self.enemy = t;
921                         self = t;
922
923                         if (t.mins_x < cmins_x)
924                                 cmins_x = t.mins_x;
925                         if (t.mins_y < cmins_y)
926                                 cmins_y = t.mins_y;
927                         if (t.mins_z < cmins_z)
928                                 cmins_z = t.mins_z;
929                         if (t.maxs_x > cmaxs_x)
930                                 cmaxs_x = t.maxs_x;
931                         if (t.maxs_y > cmaxs_y)
932                                 cmaxs_y = t.maxs_y;
933                         if (t.maxs_z > cmaxs_z)
934                                 cmaxs_z = t.maxs_z;
935                 }
936         } while (1 );
937
938 };
939
940
941 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
942 if two doors touch, they are assumed to be connected and operate as a unit.
943
944 TOGGLE causes the door to wait in both the start and end states for a trigger event.
945
946 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).
947
948 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
949 "angle"         determines the opening direction
950 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
951 "health"        if set, door must be shot open
952 "speed"         movement speed (100 default)
953 "wait"          wait before returning (3 default, -1 = never return)
954 "lip"           lip remaining at end of move (8 default)
955 "dmg"           damage to inflict when blocked (2 default)
956 "sounds"
957 0)      no sound
958 1)      stone
959 2)      base
960 3)      stone chain
961 4)      screechy metal
962 FIXME: only one sound set available at the time being
963
964 */
965
966 void door_init_startopen()
967 {
968         setorigin (self, self.pos2);
969         self.pos2 = self.pos1;
970         self.pos1 = self.origin;
971 }
972
973 void spawnfunc_func_door()
974 {
975         //if (!self.deathtype) // map makers can override this
976         //      self.deathtype = " got in the way";
977         SetMovedir ();
978
979         self.max_health = self.health;
980         InitMovingBrushTrigger();
981         self.effects |= EF_LOWPRECISION;
982         self.classname = "door";
983
984         self.blocked = door_blocked;
985         self.use = door_use;
986
987     if(self.spawnflags & 8)
988         self.dmg = 10000;
989
990     if(self.dmg & (!self.message))
991                 self.message = "was in the wrong place.";
992
993
994
995         if (self.sounds > 0)
996         {
997                 precache_sound ("plats/medplat1.wav");
998                 precache_sound ("plats/medplat2.wav");
999                 self.noise2 = "plats/medplat1.wav";
1000                 self.noise1 = "plats/medplat2.wav";
1001         }
1002
1003         if (!self.speed)
1004                 self.speed = 100;
1005         if (!self.wait)
1006                 self.wait = 3;
1007         if (!self.lip)
1008                 self.lip = 8;
1009         if (!self.dmg)
1010                 self.dmg = 2;
1011
1012         self.pos1 = self.origin;
1013         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1014
1015 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1016 // but spawn in the open position
1017         if (self.spawnflags & DOOR_START_OPEN)
1018                 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1019
1020         self.state = STATE_BOTTOM;
1021
1022         if (self.health)
1023         {
1024                 self.takedamage = DAMAGE_YES;
1025                 self.event_damage = door_damage;
1026         }
1027
1028         if (self.items)
1029                 self.wait = -1;
1030
1031         self.touch = door_touch;
1032
1033 // LinkDoors can't be done until all of the doors have been spawned, so
1034 // the sizes can be detected properly.
1035         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1036 };
1037
1038 /*
1039 =============================================================================
1040
1041 SECRET DOORS
1042
1043 =============================================================================
1044 */
1045
1046 void() fd_secret_move1;
1047 void() fd_secret_move2;
1048 void() fd_secret_move3;
1049 void() fd_secret_move4;
1050 void() fd_secret_move5;
1051 void() fd_secret_move6;
1052 void() fd_secret_done;
1053
1054 float SECRET_OPEN_ONCE = 1;             // stays open
1055 float SECRET_1ST_LEFT = 2;              // 1st move is left of arrow
1056 float SECRET_1ST_DOWN = 4;              // 1st move is down from arrow
1057 float SECRET_NO_SHOOT = 8;              // only opened by trigger
1058 float SECRET_YES_SHOOT = 16;    // shootable even if targeted
1059
1060
1061 void fd_secret_use()
1062 {
1063         local float temp;
1064         string message_save;
1065
1066         self.health = 10000;
1067         self.bot_attack = TRUE;
1068
1069         // exit if still moving around...
1070         if (self.origin != self.oldorigin)
1071                 return;
1072
1073         message_save = self.message;
1074         self.message = ""; // no more message
1075         SUB_UseTargets();                               // fire all targets / killtargets
1076         self.message = message_save;
1077
1078         self.velocity = '0 0 0';
1079
1080         // Make a sound, wait a little...
1081
1082         if (self.noise1 != "")
1083                 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1084         self.nextthink = self.ltime + 0.1;
1085
1086         temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1087         makevectors(self.mangle);
1088
1089         if (!self.t_width)
1090         {
1091                 if (self.spawnflags & SECRET_1ST_DOWN)
1092                         self.t_width = fabs(v_up * self.size);
1093                 else
1094                         self.t_width = fabs(v_right * self.size);
1095         }
1096
1097         if (!self.t_length)
1098                 self.t_length = fabs(v_forward * self.size);
1099
1100         if (self.spawnflags & SECRET_1ST_DOWN)
1101                 self.dest1 = self.origin - v_up * self.t_width;
1102         else
1103                 self.dest1 = self.origin + v_right * (self.t_width * temp);
1104
1105         self.dest2 = self.dest1 + v_forward * self.t_length;
1106         SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1107         if (self.noise2 != "")
1108                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1109 };
1110
1111 // Wait after first movement...
1112 void fd_secret_move1()
1113 {
1114         self.nextthink = self.ltime + 1.0;
1115         self.think = fd_secret_move2;
1116         if (self.noise3 != "")
1117                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1118 };
1119
1120 // Start moving sideways w/sound...
1121 void fd_secret_move2()
1122 {
1123         if (self.noise2 != "")
1124                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1125         SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1126 };
1127
1128 // Wait here until time to go back...
1129 void fd_secret_move3()
1130 {
1131         if (self.noise3 != "")
1132                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1133         if (!(self.spawnflags & SECRET_OPEN_ONCE))
1134         {
1135                 self.nextthink = self.ltime + self.wait;
1136                 self.think = fd_secret_move4;
1137         }
1138 };
1139
1140 // Move backward...
1141 void fd_secret_move4()
1142 {
1143         if (self.noise2 != "")
1144                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1145         SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1146 };
1147
1148 // Wait 1 second...
1149 void fd_secret_move5()
1150 {
1151         self.nextthink = self.ltime + 1.0;
1152         self.think = fd_secret_move6;
1153         if (self.noise3 != "")
1154                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1155 };
1156
1157 void fd_secret_move6()
1158 {
1159         if (self.noise2 != "")
1160                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1161         SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1162 };
1163
1164 void fd_secret_done()
1165 {
1166         if (self.spawnflags&SECRET_YES_SHOOT)
1167         {
1168                 self.health = 10000;
1169                 self.takedamage = DAMAGE_YES;
1170                 //self.th_pain = fd_secret_use;
1171         }
1172         if (self.noise3 != "")
1173                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1174 };
1175
1176 void secret_blocked()
1177 {
1178         if (time < self.attack_finished_single)
1179                 return;
1180         self.attack_finished_single = time + 0.5;
1181         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1182 };
1183
1184 /*
1185 ==============
1186 secret_touch
1187
1188 Prints messages
1189 ================
1190 */
1191 void secret_touch()
1192 {
1193         if (activator.classname != "player")
1194                 return;
1195         if (self.attack_finished_single > time)
1196                 return;
1197
1198         self.attack_finished_single = time + 2;
1199
1200         if (self.message)
1201         {
1202                 if (other.flags & FL_CLIENT)
1203                         centerprint (other, self.message);
1204                 play2(other, "misc/talk.wav");
1205         }
1206 };
1207
1208
1209 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1210 Basic secret door. Slides back, then to the side. Angle determines direction.
1211 wait  = # of seconds before coming back
1212 1st_left = 1st move is left of arrow
1213 1st_down = 1st move is down from arrow
1214 always_shoot = even if targeted, keep shootable
1215 t_width = override WIDTH to move back (or height if going down)
1216 t_length = override LENGTH to move sideways
1217 "dmg"           damage to inflict when blocked (2 default)
1218
1219 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1220 "sounds"
1221 1) medieval
1222 2) metal
1223 3) base
1224 */
1225
1226 void spawnfunc_func_door_secret()
1227 {
1228         /*if (!self.deathtype) // map makers can override this
1229                 self.deathtype = " got in the way";*/
1230
1231         if (!self.dmg)
1232                 self.dmg = 2;
1233
1234         // Magic formula...
1235         self.mangle = self.angles;
1236         self.angles = '0 0 0';
1237         self.classname = "door";
1238         InitMovingBrushTrigger();
1239         self.effects |= EF_LOWPRECISION;
1240
1241         self.touch = secret_touch;
1242         self.blocked = secret_blocked;
1243         self.speed = 50;
1244         self.use = fd_secret_use;
1245         IFTARGETED
1246                 self.spawnflags |= SECRET_YES_SHOOT;
1247
1248         if(self.spawnflags&SECRET_YES_SHOOT)
1249         {
1250                 self.health = 10000;
1251                 self.takedamage = DAMAGE_YES;
1252                 self.event_damage = fd_secret_use;
1253         }
1254         self.oldorigin = self.origin;
1255         if (!self.wait)
1256                 self.wait = 5;          // 5 seconds before closing
1257 };
1258