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