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