tZork's platform damage patch. Needs testing, then perhaps can go into
[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_VOICE, self.noise1, 1, 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_VOICE, self.noise1, 1, ATTN_NORM);
61         self.state = 2;
62 };
63
64 void() plat_go_down =
65 {
66         sound (self, CHAN_VOICE, self.noise, 1, 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_VOICE, self.noise, 1, 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() func_plat =
147 {
148         if (!self.t_length)
149                 self.t_length = 80;
150         if (!self.t_width)
151                 self.t_width = 10;
152
153         if (self.sounds == 0)
154                 self.sounds = 2;
155
156     if(self.spawnflags == 4)
157         self.dmg = 10000;
158
159     if(self.dmg && (!self.message))
160                 self.message = "was in the wrong place.";
161
162         if (self.sounds == 1)
163         {
164                 precache_sound ("plats/plat1.wav");
165                 precache_sound ("plats/plat2.wav");
166                 self.noise = "plats/plat1.wav";
167                 self.noise1 = "plats/plat2.wav";
168         }
169
170         if (self.sounds == 2)
171         {
172                 precache_sound ("plats/medplat1.wav");
173                 precache_sound ("plats/medplat2.wav");
174                 self.noise = "plats/medplat1.wav";
175                 self.noise1 = "plats/medplat2.wav";
176         }
177
178         if (self.sound1)
179         {
180                 precache_sound (self.sound1);
181                 self.noise = self.sound1;
182         }
183         if (self.sound2)
184         {
185                 precache_sound (self.sound2);
186                 self.noise1 = self.sound2;
187         }
188
189         self.mangle = self.angles;
190         self.angles = '0 0 0';
191
192         self.classname = "plat";
193         self.solid = SOLID_BSP;
194         self.movetype = MOVETYPE_PUSH;
195         setorigin (self, self.origin);
196         setmodel (self, self.model);
197         setsize (self, self.mins , self.maxs);
198
199         self.blocked = plat_crush;
200
201         if (!self.speed)
202                 self.speed = 150;
203
204         self.pos1 = self.origin;
205         self.pos2 = self.origin;
206         self.pos2_z = self.origin_z - self.size_z + 8;
207
208         self.use = plat_trigger_use;
209
210         plat_spawn_inside_trigger ();   // the "start moving" trigger
211
212         if (self.targetname)
213         {
214                 self.state = 4;
215                 self.use = plat_use;
216         }
217         else
218         {
219                 setorigin (self, self.pos2);
220                 self.state = 2;
221         }
222 };
223
224
225 /*
226 void() train_next;
227 void() func_train_find;
228
229 void() train_blocked =
230 {
231         if (time < self.attack_finished)
232                 return;
233         self.attack_finished = time + 0.5;
234 };
235 void() train_use =
236 {
237         if (self.think != func_train_find)
238                 return;
239         train_next();
240 };
241
242 void() train_wait =
243 {
244         if (self.wait)
245         {
246                 self.nextthink = self.ltime + self.wait;
247                 sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
248         }
249         else
250                 self.nextthink = self.ltime + 0.1;
251
252         self.think = train_next;
253 };
254
255 void() train_next =
256 {
257         local entity targ;
258
259         targ = find (world, targetname, self.target);
260         self.target = targ.target;
261         if (!self.target)
262                 objerror ("train_next: no next target");
263         if (targ.wait)
264                 self.wait = targ.wait;
265         else
266                 self.wait = 0;
267         sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
268         SUB_CalcMove (targ.origin - self.mins, self.speed, train_wait);
269 };
270
271 void() func_train_find =
272 {
273         local entity targ;
274
275         targ = find (world, targetname, self.target);
276         self.target = targ.target;
277         setorigin (self, targ.origin - self.mins);
278         if (!self.targetname)
279         {       // not triggered, so start immediately
280                 self.nextthink = self.ltime + 0.1;
281                 self.think = train_next;
282         }
283 };
284
285
286 void() func_train =
287 {
288         if (!self.speed)
289                 self.speed = 100;
290         if (!self.target)
291                 objerror ("func_train without a target");
292
293         if (self.sounds == 0)
294         {
295                 self.noise = ("misc/null.wav");
296                 precache_sound ("misc/null.wav");
297                 self.noise1 = ("misc/null.wav");
298                 precache_sound ("misc/null.wav");
299         }
300
301         if (self.sounds == 1)
302         {
303                 self.noise = ("plats/train2.wav");
304                 precache_sound ("plats/train2.wav");
305                 self.noise1 = ("plats/train1.wav");
306                 precache_sound ("plats/train1.wav");
307         }
308
309         self.solid = SOLID_BSP;
310         self.movetype = MOVETYPE_PUSH;
311         self.blocked = train_blocked;
312         self.use = train_use;
313         self.classname = "train";
314
315         setmodel (self, self.model);
316         setsize (self, self.mins , self.maxs);
317         setorigin (self, self.origin);
318         self.nextthink = self.ltime + 0.1;
319         self.think = func_train_find;
320 };
321 */
322
323 void() train_next;
324 void() train_wait =
325 {
326         self.think = train_next;
327         self.nextthink = self.ltime + self.wait;
328 };
329
330 void() train_next =
331 {
332         local entity targ;
333         targ = find(world, targetname, self.target);
334         self.target = targ.target;
335         if (!self.target)
336                 objerror("train_next: no next target");
337         self.wait = targ.wait;
338         if (!self.wait)
339                 self.wait = 0.1;
340         if (targ.speed)
341                 SUB_CalcMove(targ.origin - self.mins, targ.speed, train_wait);
342         else
343                 SUB_CalcMove(targ.origin - self.mins, self.speed, train_wait);
344 };
345
346 void() func_train_find =
347 {
348         local entity targ;
349         targ = find(world, targetname, self.target);
350         self.target = targ.target;
351         setorigin(self, targ.origin - self.mins);
352         self.nextthink = self.ltime + 1;
353         self.think = train_next;
354 };
355
356 /*QUAKED func_train (0 .5 .8) ?
357 Ridable platform, targets path_corner path to follow.
358 speed : speed the train moves (can be overridden by each path_corner)
359 target : targetname of first path_corner (starts here)
360 */
361 void() func_train =
362 {
363         if (!self.target)
364                 objerror("func_train without a target");
365         if (!self.speed)
366                 self.speed = 100;
367
368         self.solid = SOLID_BSP;
369         self.movetype = MOVETYPE_PUSH;
370
371         setmodel(self, self.model);
372         setsize(self, self.mins, self.maxs);
373         setorigin(self, self.origin);
374
375         // wait for targets to spawn
376         self.nextthink = self.ltime + 0.1;
377         self.think = func_train_find;
378 };
379
380
381
382 void() rotating_blocked =
383 {
384
385     if(self.dmg && other.takedamage != DAMAGE_NO) {
386         if(self.dmgtime2 < time) {
387             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
388             self.dmgtime2 = time + self.dmgtime;
389         }
390
391         // Gib dead/dying stuff
392         if(other.deadflag != DEAD_NO)
393             Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
394     }
395
396
397 }
398
399 /*QUAKED func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
400 Brush model that spins in place on one axis (default Z).
401 speed   : speed to rotate (in degrees per second)
402 noise   : path/name of looping .wav file to play.
403 dmg     : Do this mutch dmg every .dmgtime intervall when blocked
404 dmgtime : See above.
405 */
406
407 void() func_rotating =
408 {
409         if (self.noise)
410         {
411                 precache_sound(self.noise);
412                 ambientsound(self.origin, self.noise, 1, ATTN_IDLE);
413         }
414         if (!self.speed)
415                 self.speed = 100;
416         // FIXME: test if this turns the right way, then remove this comment (negate as needed)
417         if (self.spawnflags & 4) // X (untested)
418                 self.avelocity = '0 0 1' * self.speed;
419         // FIXME: test if this turns the right way, then remove this comment (negate as needed)
420         else if (self.spawnflags & 8) // Y (untested)
421                 self.avelocity = '1 0 0' * self.speed;
422         // FIXME: test if this turns the right way, then remove this comment (negate as needed)
423         else // Z
424                 self.avelocity = '0 1 0' * self.speed;
425
426     if(self.dmg & (!self.message))
427         self.message = " was in the wrong place.";
428
429
430     if(self.dmg && (!self.dmgtime))
431         self.dmgtime = 0.25;
432
433     self.dmgtime2 = time;
434
435
436         self.solid = SOLID_BSP;
437         self.movetype = MOVETYPE_PUSH;
438
439         setmodel(self, self.model);
440         setsize(self, self.mins, self.maxs);
441         setorigin(self, self.origin);
442
443     self.blocked = rotating_blocked;
444
445         // wait for targets to spawn
446         self.nextthink = self.ltime + 999999999;
447         self.think = SUB_Null;
448 };
449
450 .float height;
451 .float phase;
452 void() func_bobbing_controller_think =
453 {
454         local vector v;
455         self.nextthink = time + 0.1;
456         // calculate sinewave using makevectors
457         makevectors((time * self.owner.cnt + self.owner.phase) * '0 1 0');
458         v = self.owner.destvec + self.owner.movedir * v_forward_y;
459         // * 10 so it will arrive in 0.1 sec
460         self.owner.velocity = (v - self.owner.origin) * 10;
461 };
462
463 /*QUAKED func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
464 Brush model that moves back and forth on one axis (default Z).
465 speed : how long one cycle takes in seconds (default 4)
466 height : how far the cycle moves (default 32)
467 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
468 noise : path/name of looping .wav file to play.
469 */
470 void() func_bobbing =
471 {
472         local entity controller;
473         if (self.noise)
474         {
475                 precache_sound(self.noise);
476                 ambientsound(self.origin, self.noise, 1, ATTN_IDLE);
477         }
478         if (!self.speed)
479                 self.speed = 4;
480         if (!self.height)
481                 self.height = 32;
482         // center of bobbing motion
483         self.destvec = self.origin;
484         // time scale to get degrees
485         self.cnt = 360 / self.speed;
486         // how far to bob
487         if (self.spawnflags & 1) // X
488                 self.movedir = '1 0 0' * self.height;
489         else if (self.spawnflags & 2) // Y
490                 self.movedir = '0 1 0' * self.height;
491         else // Z
492                 self.movedir = '0 0 1' * self.height;
493
494         self.solid = SOLID_BSP;
495         self.movetype = MOVETYPE_PUSH;
496
497         setmodel(self, self.model);
498         setsize(self, self.mins, self.maxs);
499         setorigin(self, self.origin);
500
501         // wait for targets to spawn
502         controller = spawn();
503         controller.classname = "func_bobbing_controller";
504         controller.owner = self;
505         controller.nextthink = time + 1;
506         controller.think = func_bobbing_controller_think;
507         self.nextthink = self.ltime + 999999999;
508         self.think = SUB_Null;
509
510         // Savage: Reduce bandwith, critical on e.g. nexdm02
511         self.effects |= EF_LOWPRECISION;
512 };
513
514 // button and multiple button
515
516 void() button_wait;
517 void() button_return;
518
519 void() button_wait =
520 {
521         self.state = STATE_TOP;
522         self.nextthink = self.ltime + self.wait;
523         self.think = button_return;
524         activator = self.enemy;
525         SUB_UseTargets();
526         self.frame = 1;                 // use alternate textures
527 };
528
529 void() button_done =
530 {
531         self.state = STATE_BOTTOM;
532 };
533
534 void() button_return =
535 {
536         self.state = STATE_DOWN;
537         SUB_CalcMove (self.pos1, self.speed, button_done);
538         self.frame = 0;                 // use normal textures
539         if (self.health)
540                 self.takedamage = DAMAGE_YES;   // can be shot again
541 };
542
543
544 void() button_blocked =
545 {
546         // do nothing, just don't come all the way back out
547 };
548
549
550 void() button_fire =
551 {
552         self.health = self.max_health;
553         self.takedamage = DAMAGE_NO;    // will be reset upon return
554
555         if (self.state == STATE_UP || self.state == STATE_TOP)
556                 return;
557
558         if (self.noise != "")
559                 sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
560
561         self.state = STATE_UP;
562         SUB_CalcMove (self.pos2, self.speed, button_wait);
563 };
564
565
566 void() button_use =
567 {
568 //      if (activator.classname != "player")
569 //      {
570 //              dprint(activator.classname);
571 //              dprint(" triggered a button\n");
572 //      }
573         self.enemy = activator;
574         button_fire ();
575 };
576
577 void() button_touch =
578 {
579 //      if (activator.classname != "player")
580 //      {
581 //              dprint(activator.classname);
582 //              dprint(" touched a button\n");
583 //      }
584         if (!other)
585                 return;
586         if (other.classname != "player")
587                 return;
588         self.enemy = other;
589         if (other.owner)
590                 self.enemy = other.owner;
591         button_fire ();
592 };
593
594 void(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) button_damage =
595 {
596         self.health = self.health - damage;
597         if (self.health <= 0)
598         {
599         //      if (activator.classname != "player")
600         //      {
601         //              dprint(activator.classname);
602         //              dprint(" killed a button\n");
603         //      }
604                 self.enemy = damage_attacker;
605                 button_fire ();
606         }
607 };
608
609
610 /*QUAKED func_button (0 .5 .8) ?
611 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.
612
613 "angle"         determines the opening direction
614 "target"        all entities with a matching targetname will be used
615 "speed"         override the default 40 speed
616 "wait"          override the default 1 second wait (-1 = never return)
617 "lip"           override the default 4 pixel lip remaining at end of move
618 "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
619 "sounds"
620 0) steam metal
621 1) wooden clunk
622 2) metallic click
623 3) in-out
624 */
625 void() func_button =
626 {
627         SetMovedir ();
628
629         self.movetype = MOVETYPE_PUSH;
630         self.solid = SOLID_BSP;
631         setmodel (self, self.model);
632
633         self.blocked = button_blocked;
634         self.use = button_use;
635
636 //      if (self.health == 0) // all buttons are now shootable
637 //              self.health = 10;
638         if (self.health)
639         {
640                 self.max_health = self.health;
641                 self.event_damage = button_damage;
642                 self.takedamage = DAMAGE_YES;
643         }
644         else
645                 self.touch = button_touch;
646
647         if (!self.speed)
648                 self.speed = 40;
649         if (!self.wait)
650                 self.wait = 1;
651         if (!self.lip)
652                 self.lip = 4;
653
654         self.state = STATE_BOTTOM;
655
656         self.pos1 = self.origin;
657         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
658 };
659
660
661 float DOOR_START_OPEN = 1;
662 float DOOR_DONT_LINK = 4;
663 float DOOR_TOGGLE = 32;
664
665 /*
666
667 Doors are similar to buttons, but can spawn a fat trigger field around them
668 to open without a touch, and they link together to form simultanious
669 double/quad doors.
670
671 Door.owner is the master door.  If there is only one door, it points to itself.
672 If multiple doors, all will point to a single one.
673
674 Door.enemy chains from the master door through all doors linked in the chain.
675
676 */
677
678 /*
679 =============================================================================
680
681 THINK FUNCTIONS
682
683 =============================================================================
684 */
685
686 void() door_go_down;
687 void() door_go_up;
688
689 void() door_blocked =
690 {
691
692     if((self.spawnflags == 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
693         bprint("blabla2\n");
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_VOICE, self.noise1, 1, 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_VOICE, self.noise1, 1, ATTN_NORM);
745         self.state = STATE_BOTTOM;
746 };
747
748 void() door_go_down =
749 {
750         if (self.noise2 != "")
751                 sound (self, CHAN_VOICE, self.noise2, 1, 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_VOICE, self.noise2, 1, 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)
853                 return;
854         self.attack_finished = time + 1;
855
856         activator = other;
857
858         self = self.owner;
859         door_use ();
860 };
861
862
863 void(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) door_damage =
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 > time)
891                 return;
892
893         self.owner.attack_finished = time + 2;
894
895         if (self.owner.message != "")
896         {
897                 if (other.flags & FL_CLIENT)
898                         centermsg_setfor (other, CENTERMSG_TRIGGER, self.owner.message);
899                 sound (other, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NORM);
900         }
901 };
902
903 /*
904 =============================================================================
905
906 SPAWNING FUNCTIONS
907
908 =============================================================================
909 */
910
911
912 entity(vector fmins, vector fmaxs) spawn_field =
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 (entity e1, entity e2) EntitiesTouching =
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 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() 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         self.solid = SOLID_BSP;
1067         self.movetype = MOVETYPE_PUSH;
1068         setorigin (self, self.origin);
1069         setmodel (self, self.model);
1070         self.classname = "door";
1071
1072         self.blocked = door_blocked;
1073         self.use = door_use;
1074
1075     if(self.targetname == "") {
1076         self.spawnflags = 0;
1077         self.dmg = 0;
1078     }
1079
1080     if(self.spawnflags == 4)
1081         self.dmg = 10000;
1082
1083     if(self.dmg & (!self.message))
1084                 self.message = "was in the wrong place.";
1085
1086
1087
1088         if (self.sounds > 0)
1089         {
1090                 precache_sound ("plats/medplat1.wav");
1091                 precache_sound ("plats/medplat2.wav");
1092                 self.noise2 = "plats/medplat1.wav";
1093                 self.noise1 = "plats/medplat2.wav";
1094         }
1095
1096         if (!self.speed)
1097                 self.speed = 100;
1098         if (!self.wait)
1099                 self.wait = 3;
1100         if (!self.lip)
1101                 self.lip = 8;
1102         if (!self.dmg)
1103                 self.dmg = 2;
1104
1105         self.pos1 = self.origin;
1106         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1107
1108 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1109 // but spawn in the open position
1110         if (self.spawnflags & DOOR_START_OPEN)
1111         {
1112                 setorigin (self, self.pos2);
1113                 self.pos2 = self.pos1;
1114                 self.pos1 = self.origin;
1115         }
1116
1117         self.state = STATE_BOTTOM;
1118
1119         if (self.health)
1120         {
1121                 self.takedamage = DAMAGE_YES;
1122                 self.event_damage = door_damage;
1123         }
1124
1125         if (self.items)
1126                 self.wait = -1;
1127
1128         self.touch = door_touch;
1129
1130 // LinkDoors can't be done until all of the doors have been spawned, so
1131 // the sizes can be detected properly.
1132         self.think = LinkDoors;
1133         self.nextthink = self.ltime + 0.1;
1134 };
1135
1136 /*
1137 =============================================================================
1138
1139 SECRET DOORS
1140
1141 =============================================================================
1142 */
1143
1144 void() fd_secret_move1;
1145 void() fd_secret_move2;
1146 void() fd_secret_move3;
1147 void() fd_secret_move4;
1148 void() fd_secret_move5;
1149 void() fd_secret_move6;
1150 void() fd_secret_done;
1151
1152 float SECRET_OPEN_ONCE = 1;             // stays open
1153 float SECRET_1ST_LEFT = 2;              // 1st move is left of arrow
1154 float SECRET_1ST_DOWN = 4;              // 1st move is down from arrow
1155 float SECRET_NO_SHOOT = 8;              // only opened by trigger
1156 float SECRET_YES_SHOOT = 16;    // shootable even if targeted
1157
1158
1159 void () fd_secret_use =
1160 {
1161         local float temp;
1162
1163         self.health = 10000;
1164         self.bot_attack = TRUE;
1165
1166         // exit if still moving around...
1167         if (self.origin != self.oldorigin)
1168                 return;
1169
1170         self.message = ""; // no more message
1171
1172         SUB_UseTargets();                               // fire all targets / killtargets
1173
1174         self.velocity = '0 0 0';
1175
1176         // Make a sound, wait a little...
1177
1178         if (self.noise1 != "")
1179                 sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
1180         self.nextthink = self.ltime + 0.1;
1181
1182         temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1183         makevectors(self.mangle);
1184
1185         if (!self.t_width)
1186         {
1187                 if (self.spawnflags & SECRET_1ST_DOWN)
1188                         self.t_width = fabs(v_up * self.size);
1189                 else
1190                         self.t_width = fabs(v_right * self.size);
1191         }
1192
1193         if (!self.t_length)
1194                 self.t_length = fabs(v_forward * self.size);
1195
1196         if (self.spawnflags & SECRET_1ST_DOWN)
1197                 self.dest1 = self.origin - v_up * self.t_width;
1198         else
1199                 self.dest1 = self.origin + v_right * (self.t_width * temp);
1200
1201         self.dest2 = self.dest1 + v_forward * self.t_length;
1202         SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1203         if (self.noise2 != "")
1204                 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
1205 };
1206
1207 // Wait after first movement...
1208 void () fd_secret_move1 =
1209 {
1210         self.nextthink = self.ltime + 1.0;
1211         self.think = fd_secret_move2;
1212         if (self.noise3 != "")
1213                 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
1214 };
1215
1216 // Start moving sideways w/sound...
1217 void () fd_secret_move2 =
1218 {
1219         if (self.noise2 != "")
1220                 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
1221         SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1222 };
1223
1224 // Wait here until time to go back...
1225 void () fd_secret_move3 =
1226 {
1227         if (self.noise3 != "")
1228                 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
1229         if (!(self.spawnflags & SECRET_OPEN_ONCE))
1230         {
1231                 self.nextthink = self.ltime + self.wait;
1232                 self.think = fd_secret_move4;
1233         }
1234 };
1235
1236 // Move backward...
1237 void () fd_secret_move4 =
1238 {
1239         if (self.noise2 != "")
1240                 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
1241         SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1242 };
1243
1244 // Wait 1 second...
1245 void () fd_secret_move5 =
1246 {
1247         self.nextthink = self.ltime + 1.0;
1248         self.think = fd_secret_move6;
1249         if (self.noise3 != "")
1250                 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
1251 };
1252
1253 void () fd_secret_move6 =
1254 {
1255         if (self.noise2 != "")
1256                 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
1257         SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1258 };
1259
1260 void () fd_secret_done =
1261 {
1262         if (!self.targetname || self.spawnflags&SECRET_YES_SHOOT)
1263         {
1264                 self.health = 10000;
1265                 self.takedamage = DAMAGE_YES;
1266                 //self.th_pain = fd_secret_use;
1267         }
1268         if (self.noise3 != "")
1269                 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
1270 };
1271
1272 void () secret_blocked =
1273 {
1274         if (time < self.attack_finished)
1275                 return;
1276         self.attack_finished = time + 0.5;
1277         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1278 };
1279
1280 /*
1281 ==============
1282 secret_touch
1283
1284 Prints messages
1285 ================
1286 */
1287 void() secret_touch =
1288 {
1289         if (activator.classname != "player")
1290                 return;
1291         if (self.attack_finished > time)
1292                 return;
1293
1294         self.attack_finished = time + 2;
1295
1296         if (self.message)
1297         {
1298                 if (other.flags & FL_CLIENT)
1299                         centermsg_setfor (other, CENTERMSG_TRIGGER, self.message);
1300                 sound (other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM);
1301         }
1302 };
1303
1304
1305 /*QUAKED func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1306 Basic secret door. Slides back, then to the side. Angle determines direction.
1307 wait  = # of seconds before coming back
1308 1st_left = 1st move is left of arrow
1309 1st_down = 1st move is down from arrow
1310 always_shoot = even if targeted, keep shootable
1311 t_width = override WIDTH to move back (or height if going down)
1312 t_length = override LENGTH to move sideways
1313 "dmg"           damage to inflict when blocked (2 default)
1314
1315 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1316 "sounds"
1317 1) medieval
1318 2) metal
1319 3) base
1320 */
1321
1322 void () func_door_secret =
1323 {
1324         /*if (!self.deathtype) // map makers can override this
1325                 self.deathtype = " got in the way";*/
1326
1327         if (!self.dmg)
1328                 self.dmg = 2;
1329
1330         // Magic formula...
1331         self.mangle = self.angles;
1332         self.angles = '0 0 0';
1333         self.solid = SOLID_BSP;
1334         self.movetype = MOVETYPE_PUSH;
1335         self.classname = "door";
1336         setmodel (self, self.model);
1337         setorigin (self, self.origin);
1338
1339         self.touch = secret_touch;
1340         self.blocked = secret_blocked;
1341         self.speed = 50;
1342         self.use = fd_secret_use;
1343         if ( !self.targetname || self.spawnflags&SECRET_YES_SHOOT)
1344         {
1345                 self.health = 10000;
1346                 self.takedamage = DAMAGE_YES;
1347                 self.event_damage = fd_secret_use;
1348         }
1349         self.oldorigin = self.origin;
1350         if (!self.wait)
1351                 self.wait = 5;          // 5 seconds before closing
1352 };
1353