add csqc skeleton for Nexuiz
[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.classname != "player")
530                 return;
531         self.enemy = other;
532         if (other.owner)
533                 self.enemy = other.owner;
534         button_fire ();
535 };
536
537 void(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) button_damage =
538 {
539         self.health = self.health - damage;
540         if (self.health <= 0)
541         {
542         //      if (activator.classname != "player")
543         //      {
544         //              dprint(activator.classname);
545         //              dprint(" killed a button\n");
546         //      }
547                 self.enemy = damage_attacker;
548                 button_fire ();
549         }
550 };
551
552
553 /*QUAKED func_button (0 .5 .8) ?
554 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.
555
556 "angle"         determines the opening direction
557 "target"        all entities with a matching targetname will be used
558 "speed"         override the default 40 speed
559 "wait"          override the default 1 second wait (-1 = never return)
560 "lip"           override the default 4 pixel lip remaining at end of move
561 "health"        if set, the button must be killed instead of touched
562 "sounds"
563 0) steam metal
564 1) wooden clunk
565 2) metallic click
566 3) in-out
567 */
568 void() func_button =
569 {
570         SetMovedir ();
571
572         self.movetype = MOVETYPE_PUSH;
573         self.solid = SOLID_BSP;
574         setmodel (self, self.model);
575
576         self.blocked = button_blocked;
577         self.use = button_use;
578
579 //      if (self.health == 0) // all buttons are now shootable
580 //              self.health = 10;
581         if (self.health)
582         {
583                 self.max_health = self.health;
584                 self.event_damage = button_damage;
585                 self.takedamage = DAMAGE_YES;
586         }
587         else
588                 self.touch = button_touch;
589
590         if (!self.speed)
591                 self.speed = 40;
592         if (!self.wait)
593                 self.wait = 1;
594         if (!self.lip)
595                 self.lip = 4;
596
597         self.state = STATE_BOTTOM;
598
599         self.pos1 = self.origin;
600         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
601 };
602
603
604 float DOOR_START_OPEN = 1;
605 float DOOR_DONT_LINK = 4;
606 float DOOR_TOGGLE = 32;
607
608 /*
609
610 Doors are similar to buttons, but can spawn a fat trigger field around them
611 to open without a touch, and they link together to form simultanious
612 double/quad doors.
613
614 Door.owner is the master door.  If there is only one door, it points to itself.
615 If multiple doors, all will point to a single one.
616
617 Door.enemy chains from the master door through all doors linked in the chain.
618
619 */
620
621 /*
622 =============================================================================
623
624 THINK FUNCTIONS
625
626 =============================================================================
627 */
628
629 void() door_go_down;
630 void() door_go_up;
631
632 void() door_blocked =
633 {
634         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
635
636 // if a door has a negative wait, it would never come back if blocked,
637 // so let it just squash the object to death real fast
638         if (self.wait >= 0)
639         {
640                 if (self.state == STATE_DOWN)
641                         door_go_up ();
642                 else
643                         door_go_down ();
644         }
645 };
646
647
648 void() door_hit_top =
649 {
650         if (self.noise1 != "")
651                 sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
652         self.state = STATE_TOP;
653         if (self.spawnflags & DOOR_TOGGLE)
654                 return;         // don't come down automatically
655         self.think = door_go_down;
656         self.nextthink = self.ltime + self.wait;
657 };
658
659 void() door_hit_bottom =
660 {
661         if (self.noise1 != "")
662                 sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
663         self.state = STATE_BOTTOM;
664 };
665
666 void() door_go_down =
667 {
668         if (self.noise2 != "")
669                 sound (self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
670         if (self.max_health)
671         {
672                 self.takedamage = DAMAGE_YES;
673                 self.health = self.max_health;
674         }
675
676         self.state = STATE_DOWN;
677         SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
678 };
679
680 void() door_go_up =
681 {
682         if (self.state == STATE_UP)
683                 return;         // allready going up
684
685         if (self.state == STATE_TOP)
686         {       // reset top wait time
687                 self.nextthink = self.ltime + self.wait;
688                 return;
689         }
690
691         if (self.noise2 != "")
692                 sound (self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
693         self.state = STATE_UP;
694         SUB_CalcMove (self.pos2, self.speed, door_hit_top);
695
696         SUB_UseTargets();
697 };
698
699
700 /*
701 =============================================================================
702
703 ACTIVATION FUNCTIONS
704
705 =============================================================================
706 */
707
708 void() door_fire =
709 {
710         local entity    oself;
711         local entity    starte;
712
713         if (self.owner != self)
714                 objerror ("door_fire: self.owner != self");
715
716         self.message = ""; // no more message
717         oself = self;
718
719         if (self.spawnflags & DOOR_TOGGLE)
720         {
721                 if (self.state == STATE_UP || self.state == STATE_TOP)
722                 {
723                         starte = self;
724                         do
725                         {
726                                 door_go_down ();
727                                 self = self.enemy;
728                         } while ( (self != starte) && (self != world) );
729                         self = oself;
730                         return;
731                 }
732         }
733
734 // trigger all paired doors
735         starte = self;
736         do
737         {
738                 door_go_up ();
739                 self = self.enemy;
740         } while ( (self != starte) && (self != world) );
741         self = oself;
742 };
743
744
745 void() door_use =
746 {
747         local entity oself;
748
749         //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
750         self.message = "";                      // door message are for touch only
751         if (self.owner)
752                 self.owner.message = "";
753         if (self.enemy)
754                 self.enemy.message = "";
755         if (self.owner)
756         {
757                 oself = self;
758                 self = self.owner;
759                 door_fire ();
760                 self = oself;
761         }
762 };
763
764
765 void() door_trigger_touch =
766 {
767         if (other.health < 1)
768                 return;
769
770         if (time < self.attack_finished)
771                 return;
772         self.attack_finished = time + 1;
773
774         activator = other;
775
776         self = self.owner;
777         door_use ();
778 };
779
780
781 void(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) door_damage =
782 {
783         local entity oself;
784         self.health = self.health - damage;
785         if (self.health <= 0)
786         {
787                 oself = self;
788                 self = self.owner;
789                 self.health = self.max_health;
790                 self.takedamage = DAMAGE_NO;    // wil be reset upon return
791                 door_use ();
792                 self = oself;
793         }
794 };
795
796
797 /*
798 ================
799 door_touch
800
801 Prints messages
802 ================
803 */
804 void() door_touch =
805 {
806         if (other.classname != "player")
807                 return;
808         if (self.owner.attack_finished > time)
809                 return;
810
811         self.owner.attack_finished = time + 2;
812
813         if (self.owner.message != "")
814         {
815                 if (other.flags & FL_CLIENT)
816                         centerprint (other, self.owner.message);
817                 sound (other, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NORM);
818         }
819 };
820
821 /*
822 =============================================================================
823
824 SPAWNING FUNCTIONS
825
826 =============================================================================
827 */
828
829
830 entity(vector fmins, vector fmaxs) spawn_field =
831 {
832         local entity    trigger;
833         local   vector  t1, t2;
834
835         trigger = spawn();
836         trigger.classname = "doortriggerfield";
837         trigger.movetype = MOVETYPE_NONE;
838         trigger.solid = SOLID_TRIGGER;
839         trigger.owner = self;
840         trigger.touch = door_trigger_touch;
841
842         t1 = fmins;
843         t2 = fmaxs;
844         setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
845         return (trigger);
846 };
847
848
849 float (entity e1, entity e2) EntitiesTouching =
850 {
851         if (e1.mins_x > e2.maxs_x)
852                 return FALSE;
853         if (e1.mins_y > e2.maxs_y)
854                 return FALSE;
855         if (e1.mins_z > e2.maxs_z)
856                 return FALSE;
857         if (e1.maxs_x < e2.mins_x)
858                 return FALSE;
859         if (e1.maxs_y < e2.mins_y)
860                 return FALSE;
861         if (e1.maxs_z < e2.mins_z)
862                 return FALSE;
863         return TRUE;
864 };
865
866
867 /*
868 =============
869 LinkDoors
870
871
872 =============
873 */
874 void() LinkDoors =
875 {
876         local entity    t, starte;
877         local vector    cmins, cmaxs;
878
879         if (self.enemy)
880                 return;         // already linked by another door
881         if (self.spawnflags & 4)
882         {
883                 self.owner = self.enemy = self;
884                 return;         // don't want to link this door
885         }
886
887         cmins = self.mins;
888         cmaxs = self.maxs;
889
890         starte = self;
891         t = self;
892
893         do
894         {
895                 self.owner = starte;                    // master door
896
897                 if (self.health)
898                         starte.health = self.health;
899                 if (self.targetname)
900                         starte.targetname = self.targetname;
901                 if (self.message != "")
902                         starte.message = self.message;
903
904                 t = find(t, classname, self.classname);
905                 if (!t)
906                 {
907                         self.enemy = starte;            // make the chain a loop
908
909                 // shootable, or triggered doors just needed the owner/enemy links,
910                 // they don't spawn a field
911
912                         self = self.owner;
913
914                         if (self.health)
915                                 return;
916                         if (self.targetname)
917                                 return;
918                         if (self.items)
919                                 return;
920
921                         self.owner.trigger_field = spawn_field(cmins, cmaxs);
922
923                         return;
924                 }
925
926                 if (EntitiesTouching(self,t))
927                 {
928                         if (t.enemy)
929                                 objerror ("cross connected doors");
930
931                         self.enemy = t;
932                         self = t;
933
934                         if (t.mins_x < cmins_x)
935                                 cmins_x = t.mins_x;
936                         if (t.mins_y < cmins_y)
937                                 cmins_y = t.mins_y;
938                         if (t.mins_z < cmins_z)
939                                 cmins_z = t.mins_z;
940                         if (t.maxs_x > cmaxs_x)
941                                 cmaxs_x = t.maxs_x;
942                         if (t.maxs_y > cmaxs_y)
943                                 cmaxs_y = t.maxs_y;
944                         if (t.maxs_z > cmaxs_z)
945                                 cmaxs_z = t.maxs_z;
946                 }
947         } while (1 );
948
949 };
950
951
952 /*QUAKED func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
953 if two doors touch, they are assumed to be connected and operate as a unit.
954
955 TOGGLE causes the door to wait in both the start and end states for a trigger event.
956
957 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).
958
959 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
960 "angle"         determines the opening direction
961 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
962 "health"        if set, door must be shot open
963 "speed"         movement speed (100 default)
964 "wait"          wait before returning (3 default, -1 = never return)
965 "lip"           lip remaining at end of move (8 default)
966 "dmg"           damage to inflict when blocked (2 default)
967 "sounds"
968 0)      no sound
969 1)      stone
970 2)      base
971 3)      stone chain
972 4)      screechy metal
973 */
974
975 void() func_door =
976 {
977         //if (!self.deathtype) // map makers can override this
978         //      self.deathtype = " got in the way";
979         SetMovedir ();
980
981         self.max_health = self.health;
982         self.solid = SOLID_BSP;
983         self.movetype = MOVETYPE_PUSH;
984         setorigin (self, self.origin);
985         setmodel (self, self.model);
986         self.classname = "door";
987
988         self.blocked = door_blocked;
989         self.use = door_use;
990
991         if (!self.speed)
992                 self.speed = 100;
993         if (!self.wait)
994                 self.wait = 3;
995         if (!self.lip)
996                 self.lip = 8;
997         if (!self.dmg)
998                 self.dmg = 2;
999
1000         self.pos1 = self.origin;
1001         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1002
1003 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1004 // but spawn in the open position
1005         if (self.spawnflags & DOOR_START_OPEN)
1006         {
1007                 setorigin (self, self.pos2);
1008                 self.pos2 = self.pos1;
1009                 self.pos1 = self.origin;
1010         }
1011
1012         self.state = STATE_BOTTOM;
1013
1014         if (self.health)
1015         {
1016                 self.takedamage = DAMAGE_YES;
1017                 self.event_damage = door_damage;
1018         }
1019
1020         if (self.items)
1021                 self.wait = -1;
1022
1023         self.touch = door_touch;
1024
1025 // LinkDoors can't be done until all of the doors have been spawned, so
1026 // the sizes can be detected properly.
1027         self.think = LinkDoors;
1028         self.nextthink = self.ltime + 0.1;
1029 };
1030
1031 /*
1032 =============================================================================
1033
1034 SECRET DOORS
1035
1036 =============================================================================
1037 */
1038
1039 void() fd_secret_move1;
1040 void() fd_secret_move2;
1041 void() fd_secret_move3;
1042 void() fd_secret_move4;
1043 void() fd_secret_move5;
1044 void() fd_secret_move6;
1045 void() fd_secret_done;
1046
1047 float SECRET_OPEN_ONCE = 1;             // stays open
1048 float SECRET_1ST_LEFT = 2;              // 1st move is left of arrow
1049 float SECRET_1ST_DOWN = 4;              // 1st move is down from arrow
1050 float SECRET_NO_SHOOT = 8;              // only opened by trigger
1051 float SECRET_YES_SHOOT = 16;    // shootable even if targeted
1052
1053
1054 void () fd_secret_use =
1055 {
1056         local float temp;
1057
1058         self.health = 10000;
1059         //self.havocattack = TRUE;
1060
1061         // exit if still moving around...
1062         if (self.origin != self.oldorigin)
1063                 return;
1064
1065         self.message = ""; // no more message
1066
1067         SUB_UseTargets();                               // fire all targets / killtargets
1068
1069         self.velocity = '0 0 0';
1070
1071         // Make a sound, wait a little...
1072
1073         if (self.noise1 != "")
1074                 sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
1075         self.nextthink = self.ltime + 0.1;
1076
1077         temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1078         makevectors(self.mangle);
1079
1080         if (!self.t_width)
1081         {
1082                 if (self.spawnflags & SECRET_1ST_DOWN)
1083                         self.t_width = fabs(v_up * self.size);
1084                 else
1085                         self.t_width = fabs(v_right * self.size);
1086         }
1087
1088         if (!self.t_length)
1089                 self.t_length = fabs(v_forward * self.size);
1090
1091         if (self.spawnflags & SECRET_1ST_DOWN)
1092                 self.dest1 = self.origin - v_up * self.t_width;
1093         else
1094                 self.dest1 = self.origin + v_right * (self.t_width * temp);
1095
1096         self.dest2 = self.dest1 + v_forward * self.t_length;
1097         SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1098         if (self.noise2 != "")
1099                 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
1100 };
1101
1102 // Wait after first movement...
1103 void () fd_secret_move1 =
1104 {
1105         self.nextthink = self.ltime + 1.0;
1106         self.think = fd_secret_move2;
1107         if (self.noise3 != "")
1108                 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
1109 };
1110
1111 // Start moving sideways w/sound...
1112 void () fd_secret_move2 =
1113 {
1114         if (self.noise2 != "")
1115                 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
1116         SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1117 };
1118
1119 // Wait here until time to go back...
1120 void () fd_secret_move3 =
1121 {
1122         if (self.noise3 != "")
1123                 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
1124         if (!(self.spawnflags & SECRET_OPEN_ONCE))
1125         {
1126                 self.nextthink = self.ltime + self.wait;
1127                 self.think = fd_secret_move4;
1128         }
1129 };
1130
1131 // Move backward...
1132 void () fd_secret_move4 =
1133 {
1134         if (self.noise2 != "")
1135                 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
1136         SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1137 };
1138
1139 // Wait 1 second...
1140 void () fd_secret_move5 =
1141 {
1142         self.nextthink = self.ltime + 1.0;
1143         self.think = fd_secret_move6;
1144         if (self.noise3 != "")
1145                 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
1146 };
1147
1148 void () fd_secret_move6 =
1149 {
1150         if (self.noise2 != "")
1151                 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
1152         SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1153 };
1154
1155 void () fd_secret_done =
1156 {
1157         if (!self.targetname || self.spawnflags&SECRET_YES_SHOOT)
1158         {
1159                 self.health = 10000;
1160                 self.takedamage = DAMAGE_YES;
1161                 //self.th_pain = fd_secret_use;
1162         }
1163         if (self.noise3 != "")
1164                 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
1165 };
1166
1167 void () secret_blocked =
1168 {
1169         if (time < self.attack_finished)
1170                 return;
1171         self.attack_finished = time + 0.5;
1172         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1173 };
1174
1175 /*
1176 ==============
1177 secret_touch
1178
1179 Prints messages
1180 ================
1181 */
1182 void() secret_touch =
1183 {
1184         if (activator.classname != "player")
1185                 return;
1186         if (self.attack_finished > time)
1187                 return;
1188
1189         self.attack_finished = time + 2;
1190
1191         if (self.message)
1192         {
1193                 if (other.flags & FL_CLIENT)
1194                         centerprint (other, self.message);
1195                 sound (other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM);
1196         }
1197 };
1198
1199
1200 /*QUAKED func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1201 Basic secret door. Slides back, then to the side. Angle determines direction.
1202 wait  = # of seconds before coming back
1203 1st_left = 1st move is left of arrow
1204 1st_down = 1st move is down from arrow
1205 always_shoot = even if targeted, keep shootable
1206 t_width = override WIDTH to move back (or height if going down)
1207 t_length = override LENGTH to move sideways
1208 "dmg"           damage to inflict when blocked (2 default)
1209
1210 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1211 "sounds"
1212 1) medieval
1213 2) metal
1214 3) base
1215 */
1216
1217 void () func_door_secret =
1218 {
1219         /*if (!self.deathtype) // map makers can override this
1220                 self.deathtype = " got in the way";*/
1221
1222         if (!self.dmg)
1223                 self.dmg = 2;
1224
1225         // Magic formula...
1226         self.mangle = self.angles;
1227         self.angles = '0 0 0';
1228         self.solid = SOLID_BSP;
1229         self.movetype = MOVETYPE_PUSH;
1230         self.classname = "door";
1231         setmodel (self, self.model);
1232         setorigin (self, self.origin);
1233
1234         self.touch = secret_touch;
1235         self.blocked = secret_blocked;
1236         self.speed = 50;
1237         self.use = fd_secret_use;
1238         if ( !self.targetname || self.spawnflags&SECRET_YES_SHOOT)
1239         {
1240                 self.health = 10000;
1241                 self.takedamage = DAMAGE_YES;
1242                 self.event_damage = fd_secret_use;
1243         }
1244         self.oldorigin = self.origin;
1245         if (!self.wait)
1246                 self.wait = 5;          // 5 seconds before closing
1247 };
1248