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