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