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