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