more 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)
774         if not(other.iscreature && other.deadflag == DEAD_NO)
775                 return;
776
777         if (time < self.attack_finished_single)
778                 return;
779         self.attack_finished_single = time + 1;
780
781         activator = other;
782
783         self = self.owner;
784         door_use ();
785 };
786
787
788 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
789 {
790         local entity oself;
791         self.health = self.health - damage;
792         if (self.health <= 0)
793         {
794                 oself = self;
795                 self = self.owner;
796                 self.health = self.max_health;
797                 self.takedamage = DAMAGE_NO;    // wil be reset upon return
798                 door_use ();
799                 self = oself;
800         }
801 };
802
803
804 /*
805 ================
806 door_touch
807
808 Prints messages
809 ================
810 */
811 void door_touch()
812 {
813         if(other.classname != "player")
814                 return;
815         if (self.owner.attack_finished_single > time)
816                 return;
817
818         self.owner.attack_finished_single = time + 2;
819
820         if (!self.dmg && self.owner.message != "")
821         {
822                 if (other.flags & FL_CLIENT)
823                         centerprint (other, self.owner.message);
824                 play2(other, "misc/talk.wav");
825         }
826 };
827
828 /*
829 =============================================================================
830
831 SPAWNING FUNCTIONS
832
833 =============================================================================
834 */
835
836
837 entity spawn_field(vector fmins, vector fmaxs)
838 {
839         local entity    trigger;
840         local   vector  t1, t2;
841
842         trigger = spawn();
843         trigger.classname = "doortriggerfield";
844         trigger.movetype = MOVETYPE_NONE;
845         trigger.solid = SOLID_TRIGGER;
846         trigger.owner = self;
847         trigger.touch = door_trigger_touch;
848
849         t1 = fmins;
850         t2 = fmaxs;
851         setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
852         return (trigger);
853 };
854
855
856 float EntitiesTouching(entity e1, entity e2)
857 {
858         if (e1.mins_x > e2.maxs_x)
859                 return FALSE;
860         if (e1.mins_y > e2.maxs_y)
861                 return FALSE;
862         if (e1.mins_z > e2.maxs_z)
863                 return FALSE;
864         if (e1.maxs_x < e2.mins_x)
865                 return FALSE;
866         if (e1.maxs_y < e2.mins_y)
867                 return FALSE;
868         if (e1.maxs_z < e2.mins_z)
869                 return FALSE;
870         return TRUE;
871 };
872
873
874 /*
875 =============
876 LinkDoors
877
878
879 =============
880 */
881 void LinkDoors()
882 {
883         local entity    t, starte;
884         local vector    cmins, cmaxs;
885
886         if (self.enemy)
887                 return;         // already linked by another door
888         if (self.spawnflags & 4)
889         {
890                 self.owner = self.enemy = self;
891                 return;         // don't want to link this door
892         }
893
894         cmins = self.mins;
895         cmaxs = self.maxs;
896
897         starte = self;
898         t = self;
899
900         do
901         {
902                 self.owner = starte;                    // master door
903
904                 if (self.health)
905                         starte.health = self.health;
906                 IFTARGETED
907                         starte.targetname = self.targetname;
908                 if (self.message != "")
909                         starte.message = self.message;
910
911                 t = find(t, classname, self.classname);
912                 if (!t)
913                 {
914                         self.enemy = starte;            // make the chain a loop
915
916                 // shootable, or triggered doors just needed the owner/enemy links,
917                 // they don't spawn a field
918
919                         self = self.owner;
920
921                         if (self.health)
922                                 return;
923                         IFTARGETED
924                                 return;
925                         if (self.items)
926                                 return;
927
928                         self.owner.trigger_field = spawn_field(cmins, cmaxs);
929
930                         return;
931                 }
932
933                 if (EntitiesTouching(self,t))
934                 {
935                         if (t.enemy)
936                                 objerror ("cross connected doors");
937
938                         self.enemy = t;
939                         self = t;
940
941                         if (t.mins_x < cmins_x)
942                                 cmins_x = t.mins_x;
943                         if (t.mins_y < cmins_y)
944                                 cmins_y = t.mins_y;
945                         if (t.mins_z < cmins_z)
946                                 cmins_z = t.mins_z;
947                         if (t.maxs_x > cmaxs_x)
948                                 cmaxs_x = t.maxs_x;
949                         if (t.maxs_y > cmaxs_y)
950                                 cmaxs_y = t.maxs_y;
951                         if (t.maxs_z > cmaxs_z)
952                                 cmaxs_z = t.maxs_z;
953                 }
954         } while (1 );
955
956 };
957
958
959 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
960 if two doors touch, they are assumed to be connected and operate as a unit.
961
962 TOGGLE causes the door to wait in both the start and end states for a trigger event.
963
964 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).
965
966 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
967 "angle"         determines the opening direction
968 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
969 "health"        if set, door must be shot open
970 "speed"         movement speed (100 default)
971 "wait"          wait before returning (3 default, -1 = never return)
972 "lip"           lip remaining at end of move (8 default)
973 "dmg"           damage to inflict when blocked (2 default)
974 "sounds"
975 0)      no sound
976 1)      stone
977 2)      base
978 3)      stone chain
979 4)      screechy metal
980 FIXME: only one sound set available at the time being
981
982 */
983
984 void door_init_startopen()
985 {
986         setorigin (self, self.pos2);
987         self.pos2 = self.pos1;
988         self.pos1 = self.origin;
989 }
990
991 void spawnfunc_func_door()
992 {
993         //if (!self.deathtype) // map makers can override this
994         //      self.deathtype = " got in the way";
995         SetMovedir ();
996
997         self.max_health = self.health;
998         InitMovingBrushTrigger();
999         self.effects |= EF_LOWPRECISION;
1000         self.classname = "door";
1001
1002         self.blocked = door_blocked;
1003         self.use = door_use;
1004
1005     if(self.spawnflags & 8)
1006         self.dmg = 10000;
1007
1008     if(self.dmg && (!self.message))
1009                 self.message = "was squished";
1010     if(self.dmg && (!self.message2))
1011                 self.message2 = "was squished by";
1012
1013         if (self.sounds > 0)
1014         {
1015                 precache_sound ("plats/medplat1.wav");
1016                 precache_sound ("plats/medplat2.wav");
1017                 self.noise2 = "plats/medplat1.wav";
1018                 self.noise1 = "plats/medplat2.wav";
1019         }
1020
1021         if (!self.speed)
1022                 self.speed = 100;
1023         if (!self.wait)
1024                 self.wait = 3;
1025         if (!self.lip)
1026                 self.lip = 8;
1027         if (!self.dmg)
1028                 self.dmg = 2;
1029
1030         self.pos1 = self.origin;
1031         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1032
1033 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1034 // but spawn in the open position
1035         if (self.spawnflags & DOOR_START_OPEN)
1036                 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1037
1038         self.state = STATE_BOTTOM;
1039
1040         if (self.health)
1041         {
1042                 self.takedamage = DAMAGE_YES;
1043                 self.event_damage = door_damage;
1044         }
1045
1046         if (self.items)
1047                 self.wait = -1;
1048
1049         self.touch = door_touch;
1050
1051 // LinkDoors can't be done until all of the doors have been spawned, so
1052 // the sizes can be detected properly.
1053         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1054 };
1055
1056 /*
1057 =============================================================================
1058
1059 SECRET DOORS
1060
1061 =============================================================================
1062 */
1063
1064 void() fd_secret_move1;
1065 void() fd_secret_move2;
1066 void() fd_secret_move3;
1067 void() fd_secret_move4;
1068 void() fd_secret_move5;
1069 void() fd_secret_move6;
1070 void() fd_secret_done;
1071
1072 float SECRET_OPEN_ONCE = 1;             // stays open
1073 float SECRET_1ST_LEFT = 2;              // 1st move is left of arrow
1074 float SECRET_1ST_DOWN = 4;              // 1st move is down from arrow
1075 float SECRET_NO_SHOOT = 8;              // only opened by trigger
1076 float SECRET_YES_SHOOT = 16;    // shootable even if targeted
1077
1078
1079 void fd_secret_use()
1080 {
1081         local float temp;
1082         string message_save;
1083
1084         self.health = 10000;
1085         self.bot_attack = TRUE;
1086
1087         // exit if still moving around...
1088         if (self.origin != self.oldorigin)
1089                 return;
1090
1091         message_save = self.message;
1092         self.message = ""; // no more message
1093         SUB_UseTargets();                               // fire all targets / killtargets
1094         self.message = message_save;
1095
1096         self.velocity = '0 0 0';
1097
1098         // Make a sound, wait a little...
1099
1100         if (self.noise1 != "")
1101                 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1102         self.nextthink = self.ltime + 0.1;
1103
1104         temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1105         makevectors(self.mangle);
1106
1107         if (!self.t_width)
1108         {
1109                 if (self.spawnflags & SECRET_1ST_DOWN)
1110                         self.t_width = fabs(v_up * self.size);
1111                 else
1112                         self.t_width = fabs(v_right * self.size);
1113         }
1114
1115         if (!self.t_length)
1116                 self.t_length = fabs(v_forward * self.size);
1117
1118         if (self.spawnflags & SECRET_1ST_DOWN)
1119                 self.dest1 = self.origin - v_up * self.t_width;
1120         else
1121                 self.dest1 = self.origin + v_right * (self.t_width * temp);
1122
1123         self.dest2 = self.dest1 + v_forward * self.t_length;
1124         SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1125         if (self.noise2 != "")
1126                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1127 };
1128
1129 // Wait after first movement...
1130 void fd_secret_move1()
1131 {
1132         self.nextthink = self.ltime + 1.0;
1133         self.think = fd_secret_move2;
1134         if (self.noise3 != "")
1135                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1136 };
1137
1138 // Start moving sideways w/sound...
1139 void fd_secret_move2()
1140 {
1141         if (self.noise2 != "")
1142                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1143         SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1144 };
1145
1146 // Wait here until time to go back...
1147 void fd_secret_move3()
1148 {
1149         if (self.noise3 != "")
1150                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1151         if (!(self.spawnflags & SECRET_OPEN_ONCE))
1152         {
1153                 self.nextthink = self.ltime + self.wait;
1154                 self.think = fd_secret_move4;
1155         }
1156 };
1157
1158 // Move backward...
1159 void fd_secret_move4()
1160 {
1161         if (self.noise2 != "")
1162                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1163         SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1164 };
1165
1166 // Wait 1 second...
1167 void fd_secret_move5()
1168 {
1169         self.nextthink = self.ltime + 1.0;
1170         self.think = fd_secret_move6;
1171         if (self.noise3 != "")
1172                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1173 };
1174
1175 void fd_secret_move6()
1176 {
1177         if (self.noise2 != "")
1178                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1179         SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1180 };
1181
1182 void fd_secret_done()
1183 {
1184         if (self.spawnflags&SECRET_YES_SHOOT)
1185         {
1186                 self.health = 10000;
1187                 self.takedamage = DAMAGE_YES;
1188                 //self.th_pain = fd_secret_use;
1189         }
1190         if (self.noise3 != "")
1191                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1192 };
1193
1194 void secret_blocked()
1195 {
1196         if (time < self.attack_finished_single)
1197                 return;
1198         self.attack_finished_single = time + 0.5;
1199         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1200 };
1201
1202 /*
1203 ==============
1204 secret_touch
1205
1206 Prints messages
1207 ================
1208 */
1209 void secret_touch()
1210 {
1211         if not(other.iscreature)
1212                 return;
1213         if (self.attack_finished_single > time)
1214                 return;
1215
1216         self.attack_finished_single = time + 2;
1217
1218         if (self.message)
1219         {
1220                 if (other.flags & FL_CLIENT)
1221                         centerprint (other, self.message);
1222                 play2(other, "misc/talk.wav");
1223         }
1224 };
1225
1226
1227 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1228 Basic secret door. Slides back, then to the side. Angle determines direction.
1229 wait  = # of seconds before coming back
1230 1st_left = 1st move is left of arrow
1231 1st_down = 1st move is down from arrow
1232 always_shoot = even if targeted, keep shootable
1233 t_width = override WIDTH to move back (or height if going down)
1234 t_length = override LENGTH to move sideways
1235 "dmg"           damage to inflict when blocked (2 default)
1236
1237 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1238 "sounds"
1239 1) medieval
1240 2) metal
1241 3) base
1242 */
1243
1244 void spawnfunc_func_door_secret()
1245 {
1246         /*if (!self.deathtype) // map makers can override this
1247                 self.deathtype = " got in the way";*/
1248
1249         if (!self.dmg)
1250                 self.dmg = 2;
1251
1252         // Magic formula...
1253         self.mangle = self.angles;
1254         self.angles = '0 0 0';
1255         self.classname = "door";
1256         InitMovingBrushTrigger();
1257         self.effects |= EF_LOWPRECISION;
1258
1259         self.touch = secret_touch;
1260         self.blocked = secret_blocked;
1261         self.speed = 50;
1262         self.use = fd_secret_use;
1263         IFTARGETED
1264         {
1265         }
1266         else
1267                 self.spawnflags |= SECRET_YES_SHOOT;
1268
1269         if(self.spawnflags&SECRET_YES_SHOOT)
1270         {
1271                 self.health = 10000;
1272                 self.takedamage = DAMAGE_YES;
1273                 self.event_damage = fd_secret_use;
1274         }
1275         self.oldorigin = self.origin;
1276         if (!self.wait)
1277                 self.wait = 5;          // 5 seconds before closing
1278 };
1279