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