fix doors with origin brush
[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.absmin + '25 25 0';
31         tmax = self.absmax - '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         string oldmessage;
704         oldmessage = self.message;
705         self.message = "";
706         SUB_UseTargets();
707         self.message = oldmessage;
708 };
709
710
711 /*
712 =============================================================================
713
714 ACTIVATION FUNCTIONS
715
716 =============================================================================
717 */
718
719 void door_fire()
720 {
721         local entity    oself;
722         local entity    starte;
723
724         if (self.owner != self)
725                 objerror ("door_fire: self.owner != self");
726
727         oself = self;
728
729         if (self.spawnflags & DOOR_TOGGLE)
730         {
731                 if (self.state == STATE_UP || self.state == STATE_TOP)
732                 {
733                         starte = self;
734                         do
735                         {
736                                 door_go_down ();
737                                 self = self.enemy;
738                         } while ( (self != starte) && (self != world) );
739                         self = oself;
740                         return;
741                 }
742         }
743
744 // trigger all paired doors
745         starte = self;
746         do
747         {
748                 door_go_up ();
749                 self = self.enemy;
750         } while ( (self != starte) && (self != world) );
751         self = oself;
752 };
753
754
755 void door_use()
756 {
757         local entity oself;
758
759         //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
760         if (self.owner)
761         {
762                 oself = self;
763                 self = self.owner;
764                 door_fire ();
765                 self = oself;
766         }
767 };
768
769
770 void door_trigger_touch()
771 {
772         if (other.health < 1)
773         if not(other.iscreature && other.deadflag == DEAD_NO)
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.owner.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
891                 if (self.health)
892                         return;
893                 IFTARGETED
894                         return;
895                 if (self.items)
896                         return;
897
898                 self.trigger_field = spawn_field(self.absmin, self.absmax);
899
900                 return;         // don't want to link this door
901         }
902
903         cmins = self.absmin;
904         cmaxs = self.absmax;
905
906         starte = self;
907         t = self;
908
909         do
910         {
911                 self.owner = starte;                    // master door
912
913                 if (self.health)
914                         starte.health = self.health;
915                 IFTARGETED
916                         starte.targetname = self.targetname;
917                 if (self.message != "")
918                         starte.message = self.message;
919
920                 t = find(t, classname, self.classname);
921                 if (!t)
922                 {
923                         self.enemy = starte;            // make the chain a loop
924
925                 // shootable, or triggered doors just needed the owner/enemy links,
926                 // they don't spawn a field
927
928                         self = self.owner;
929
930                         if (self.health)
931                                 return;
932                         IFTARGETED
933                                 return;
934                         if (self.items)
935                                 return;
936
937                         self.owner.trigger_field = spawn_field(cmins, cmaxs);
938
939                         return;
940                 }
941
942                 if (EntitiesTouching(self,t))
943                 {
944                         if (t.enemy)
945                                 objerror ("cross connected doors");
946
947                         self.enemy = t;
948                         self = t;
949
950                         if (t.absmin_x < cmins_x)
951                                 cmins_x = t.absmin_x;
952                         if (t.absmin_y < cmins_y)
953                                 cmins_y = t.absmin_y;
954                         if (t.absmin_z < cmins_z)
955                                 cmins_z = t.absmin_z;
956                         if (t.absmax_x > cmaxs_x)
957                                 cmaxs_x = t.absmax_x;
958                         if (t.absmax_y > cmaxs_y)
959                                 cmaxs_y = t.absmax_y;
960                         if (t.absmax_z > cmaxs_z)
961                                 cmaxs_z = t.absmax_z;
962                 }
963         } while (1 );
964
965 };
966
967
968 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
969 if two doors touch, they are assumed to be connected and operate as a unit.
970
971 TOGGLE causes the door to wait in both the start and end states for a trigger event.
972
973 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).
974
975 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
976 "angle"         determines the opening direction
977 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
978 "health"        if set, door must be shot open
979 "speed"         movement speed (100 default)
980 "wait"          wait before returning (3 default, -1 = never return)
981 "lip"           lip remaining at end of move (8 default)
982 "dmg"           damage to inflict when blocked (2 default)
983 "sounds"
984 0)      no sound
985 1)      stone
986 2)      base
987 3)      stone chain
988 4)      screechy metal
989 FIXME: only one sound set available at the time being
990
991 */
992
993 void door_init_startopen()
994 {
995         setorigin (self, self.pos2);
996         self.pos2 = self.pos1;
997         self.pos1 = self.origin;
998 }
999
1000 void spawnfunc_func_door()
1001 {
1002         //if (!self.deathtype) // map makers can override this
1003         //      self.deathtype = " got in the way";
1004         SetMovedir ();
1005
1006         self.max_health = self.health;
1007         InitMovingBrushTrigger();
1008         self.effects |= EF_LOWPRECISION;
1009         self.classname = "door";
1010
1011         self.blocked = door_blocked;
1012         self.use = door_use;
1013
1014     if(self.spawnflags & 8)
1015         self.dmg = 10000;
1016
1017     if(self.dmg && (!self.message))
1018                 self.message = "was squished";
1019     if(self.dmg && (!self.message2))
1020                 self.message2 = "was squished by";
1021
1022         if (self.sounds > 0)
1023         {
1024                 precache_sound ("plats/medplat1.wav");
1025                 precache_sound ("plats/medplat2.wav");
1026                 self.noise2 = "plats/medplat1.wav";
1027                 self.noise1 = "plats/medplat2.wav";
1028         }
1029
1030         if (!self.speed)
1031                 self.speed = 100;
1032         if (!self.wait)
1033                 self.wait = 3;
1034         if (!self.lip)
1035                 self.lip = 8;
1036
1037         self.pos1 = self.origin;
1038         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1039
1040 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1041 // but spawn in the open position
1042         if (self.spawnflags & DOOR_START_OPEN)
1043                 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1044
1045         self.state = STATE_BOTTOM;
1046
1047         if (self.health)
1048         {
1049                 self.takedamage = DAMAGE_YES;
1050                 self.event_damage = door_damage;
1051         }
1052
1053         if (self.items)
1054                 self.wait = -1;
1055
1056         self.touch = door_touch;
1057
1058 // LinkDoors can't be done until all of the doors have been spawned, so
1059 // the sizes can be detected properly.
1060         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1061 };
1062
1063 /*
1064 =============================================================================
1065
1066 SECRET DOORS
1067
1068 =============================================================================
1069 */
1070
1071 void() fd_secret_move1;
1072 void() fd_secret_move2;
1073 void() fd_secret_move3;
1074 void() fd_secret_move4;
1075 void() fd_secret_move5;
1076 void() fd_secret_move6;
1077 void() fd_secret_done;
1078
1079 float SECRET_OPEN_ONCE = 1;             // stays open
1080 float SECRET_1ST_LEFT = 2;              // 1st move is left of arrow
1081 float SECRET_1ST_DOWN = 4;              // 1st move is down from arrow
1082 float SECRET_NO_SHOOT = 8;              // only opened by trigger
1083 float SECRET_YES_SHOOT = 16;    // shootable even if targeted
1084
1085
1086 void fd_secret_use()
1087 {
1088         local float temp;
1089         string message_save;
1090
1091         self.health = 10000;
1092         self.bot_attack = TRUE;
1093
1094         // exit if still moving around...
1095         if (self.origin != self.oldorigin)
1096                 return;
1097
1098         message_save = self.message;
1099         self.message = ""; // no more message
1100         SUB_UseTargets();                               // fire all targets / killtargets
1101         self.message = message_save;
1102
1103         self.velocity = '0 0 0';
1104
1105         // Make a sound, wait a little...
1106
1107         if (self.noise1 != "")
1108                 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1109         self.nextthink = self.ltime + 0.1;
1110
1111         temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1112         makevectors(self.mangle);
1113
1114         if (!self.t_width)
1115         {
1116                 if (self.spawnflags & SECRET_1ST_DOWN)
1117                         self.t_width = fabs(v_up * self.size);
1118                 else
1119                         self.t_width = fabs(v_right * self.size);
1120         }
1121
1122         if (!self.t_length)
1123                 self.t_length = fabs(v_forward * self.size);
1124
1125         if (self.spawnflags & SECRET_1ST_DOWN)
1126                 self.dest1 = self.origin - v_up * self.t_width;
1127         else
1128                 self.dest1 = self.origin + v_right * (self.t_width * temp);
1129
1130         self.dest2 = self.dest1 + v_forward * self.t_length;
1131         SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1132         if (self.noise2 != "")
1133                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1134 };
1135
1136 // Wait after first movement...
1137 void fd_secret_move1()
1138 {
1139         self.nextthink = self.ltime + 1.0;
1140         self.think = fd_secret_move2;
1141         if (self.noise3 != "")
1142                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1143 };
1144
1145 // Start moving sideways w/sound...
1146 void fd_secret_move2()
1147 {
1148         if (self.noise2 != "")
1149                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1150         SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1151 };
1152
1153 // Wait here until time to go back...
1154 void fd_secret_move3()
1155 {
1156         if (self.noise3 != "")
1157                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1158         if (!(self.spawnflags & SECRET_OPEN_ONCE))
1159         {
1160                 self.nextthink = self.ltime + self.wait;
1161                 self.think = fd_secret_move4;
1162         }
1163 };
1164
1165 // Move backward...
1166 void fd_secret_move4()
1167 {
1168         if (self.noise2 != "")
1169                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1170         SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1171 };
1172
1173 // Wait 1 second...
1174 void fd_secret_move5()
1175 {
1176         self.nextthink = self.ltime + 1.0;
1177         self.think = fd_secret_move6;
1178         if (self.noise3 != "")
1179                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1180 };
1181
1182 void fd_secret_move6()
1183 {
1184         if (self.noise2 != "")
1185                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1186         SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1187 };
1188
1189 void fd_secret_done()
1190 {
1191         if (self.spawnflags&SECRET_YES_SHOOT)
1192         {
1193                 self.health = 10000;
1194                 self.takedamage = DAMAGE_YES;
1195                 //self.th_pain = fd_secret_use;
1196         }
1197         if (self.noise3 != "")
1198                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1199 };
1200
1201 void secret_blocked()
1202 {
1203         if (time < self.attack_finished_single)
1204                 return;
1205         self.attack_finished_single = time + 0.5;
1206         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1207 };
1208
1209 /*
1210 ==============
1211 secret_touch
1212
1213 Prints messages
1214 ================
1215 */
1216 void secret_touch()
1217 {
1218         if not(other.iscreature)
1219                 return;
1220         if (self.attack_finished_single > time)
1221                 return;
1222
1223         self.attack_finished_single = time + 2;
1224
1225         if (self.message)
1226         {
1227                 if (other.flags & FL_CLIENT)
1228                         centerprint (other, self.message);
1229                 play2(other, "misc/talk.wav");
1230         }
1231 };
1232
1233
1234 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1235 Basic secret door. Slides back, then to the side. Angle determines direction.
1236 wait  = # of seconds before coming back
1237 1st_left = 1st move is left of arrow
1238 1st_down = 1st move is down from arrow
1239 always_shoot = even if targeted, keep shootable
1240 t_width = override WIDTH to move back (or height if going down)
1241 t_length = override LENGTH to move sideways
1242 "dmg"           damage to inflict when blocked (2 default)
1243
1244 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1245 "sounds"
1246 1) medieval
1247 2) metal
1248 3) base
1249 */
1250
1251 void spawnfunc_func_door_secret()
1252 {
1253         /*if (!self.deathtype) // map makers can override this
1254                 self.deathtype = " got in the way";*/
1255
1256         if (!self.dmg)
1257                 self.dmg = 2;
1258
1259         // Magic formula...
1260         self.mangle = self.angles;
1261         self.angles = '0 0 0';
1262         self.classname = "door";
1263         InitMovingBrushTrigger();
1264         self.effects |= EF_LOWPRECISION;
1265
1266         self.touch = secret_touch;
1267         self.blocked = secret_blocked;
1268         self.speed = 50;
1269         self.use = fd_secret_use;
1270         IFTARGETED
1271         {
1272         }
1273         else
1274                 self.spawnflags |= SECRET_YES_SHOOT;
1275
1276         if(self.spawnflags&SECRET_YES_SHOOT)
1277         {
1278                 self.health = 10000;
1279                 self.takedamage = DAMAGE_YES;
1280                 self.event_damage = fd_secret_use;
1281         }
1282         self.oldorigin = self.origin;
1283         if (!self.wait)
1284                 self.wait = 5;          // 5 seconds before closing
1285 };
1286