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