centerprints now are managed independently per line
[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
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                         centermsg_setfor (other, CENTERMSG_TRIGGER, 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 FIXME: only one sound set available at the time being
976
977 */
978
979 void() func_door =
980 {
981         //if (!self.deathtype) // map makers can override this
982         //      self.deathtype = " got in the way";
983         SetMovedir ();
984
985         self.max_health = self.health;
986         self.solid = SOLID_BSP;
987         self.movetype = MOVETYPE_PUSH;
988         setorigin (self, self.origin);
989         setmodel (self, self.model);
990         self.classname = "door";
991
992         self.blocked = door_blocked;
993         self.use = door_use;
994         
995         if (self.sounds > 0)
996         {
997                 precache_sound ("plats/medplat1.wav");
998                 precache_sound ("plats/medplat2.wav");
999                 self.noise2 = "plats/medplat1.wav";
1000                 self.noise1 = "plats/medplat2.wav";
1001         }       
1002
1003         if (!self.speed)
1004                 self.speed = 100;
1005         if (!self.wait)
1006                 self.wait = 3;
1007         if (!self.lip)
1008                 self.lip = 8;
1009         if (!self.dmg)
1010                 self.dmg = 2;
1011
1012         self.pos1 = self.origin;
1013         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1014
1015 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1016 // but spawn in the open position
1017         if (self.spawnflags & DOOR_START_OPEN)
1018         {
1019                 setorigin (self, self.pos2);
1020                 self.pos2 = self.pos1;
1021                 self.pos1 = self.origin;
1022         }
1023
1024         self.state = STATE_BOTTOM;
1025
1026         if (self.health)
1027         {
1028                 self.takedamage = DAMAGE_YES;
1029                 self.event_damage = door_damage;
1030         }
1031
1032         if (self.items)
1033                 self.wait = -1;
1034
1035         self.touch = door_touch;
1036
1037 // LinkDoors can't be done until all of the doors have been spawned, so
1038 // the sizes can be detected properly.
1039         self.think = LinkDoors;
1040         self.nextthink = self.ltime + 0.1;
1041 };
1042
1043 /*
1044 =============================================================================
1045
1046 SECRET DOORS
1047
1048 =============================================================================
1049 */
1050
1051 void() fd_secret_move1;
1052 void() fd_secret_move2;
1053 void() fd_secret_move3;
1054 void() fd_secret_move4;
1055 void() fd_secret_move5;
1056 void() fd_secret_move6;
1057 void() fd_secret_done;
1058
1059 float SECRET_OPEN_ONCE = 1;             // stays open
1060 float SECRET_1ST_LEFT = 2;              // 1st move is left of arrow
1061 float SECRET_1ST_DOWN = 4;              // 1st move is down from arrow
1062 float SECRET_NO_SHOOT = 8;              // only opened by trigger
1063 float SECRET_YES_SHOOT = 16;    // shootable even if targeted
1064
1065
1066 void () fd_secret_use =
1067 {
1068         local float temp;
1069
1070         self.health = 10000;
1071         self.bot_attack = TRUE;
1072
1073         // exit if still moving around...
1074         if (self.origin != self.oldorigin)
1075                 return;
1076
1077         self.message = ""; // no more message
1078
1079         SUB_UseTargets();                               // fire all targets / killtargets
1080
1081         self.velocity = '0 0 0';
1082
1083         // Make a sound, wait a little...
1084
1085         if (self.noise1 != "")
1086                 sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
1087         self.nextthink = self.ltime + 0.1;
1088
1089         temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1090         makevectors(self.mangle);
1091
1092         if (!self.t_width)
1093         {
1094                 if (self.spawnflags & SECRET_1ST_DOWN)
1095                         self.t_width = fabs(v_up * self.size);
1096                 else
1097                         self.t_width = fabs(v_right * self.size);
1098         }
1099
1100         if (!self.t_length)
1101                 self.t_length = fabs(v_forward * self.size);
1102
1103         if (self.spawnflags & SECRET_1ST_DOWN)
1104                 self.dest1 = self.origin - v_up * self.t_width;
1105         else
1106                 self.dest1 = self.origin + v_right * (self.t_width * temp);
1107
1108         self.dest2 = self.dest1 + v_forward * self.t_length;
1109         SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1110         if (self.noise2 != "")
1111                 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
1112 };
1113
1114 // Wait after first movement...
1115 void () fd_secret_move1 =
1116 {
1117         self.nextthink = self.ltime + 1.0;
1118         self.think = fd_secret_move2;
1119         if (self.noise3 != "")
1120                 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
1121 };
1122
1123 // Start moving sideways w/sound...
1124 void () fd_secret_move2 =
1125 {
1126         if (self.noise2 != "")
1127                 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
1128         SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1129 };
1130
1131 // Wait here until time to go back...
1132 void () fd_secret_move3 =
1133 {
1134         if (self.noise3 != "")
1135                 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
1136         if (!(self.spawnflags & SECRET_OPEN_ONCE))
1137         {
1138                 self.nextthink = self.ltime + self.wait;
1139                 self.think = fd_secret_move4;
1140         }
1141 };
1142
1143 // Move backward...
1144 void () fd_secret_move4 =
1145 {
1146         if (self.noise2 != "")
1147                 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
1148         SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1149 };
1150
1151 // Wait 1 second...
1152 void () fd_secret_move5 =
1153 {
1154         self.nextthink = self.ltime + 1.0;
1155         self.think = fd_secret_move6;
1156         if (self.noise3 != "")
1157                 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
1158 };
1159
1160 void () fd_secret_move6 =
1161 {
1162         if (self.noise2 != "")
1163                 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
1164         SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1165 };
1166
1167 void () fd_secret_done =
1168 {
1169         if (!self.targetname || self.spawnflags&SECRET_YES_SHOOT)
1170         {
1171                 self.health = 10000;
1172                 self.takedamage = DAMAGE_YES;
1173                 //self.th_pain = fd_secret_use;
1174         }
1175         if (self.noise3 != "")
1176                 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
1177 };
1178
1179 void () secret_blocked =
1180 {
1181         if (time < self.attack_finished)
1182                 return;
1183         self.attack_finished = time + 0.5;
1184         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1185 };
1186
1187 /*
1188 ==============
1189 secret_touch
1190
1191 Prints messages
1192 ================
1193 */
1194 void() secret_touch =
1195 {
1196         if (activator.classname != "player")
1197                 return;
1198         if (self.attack_finished > time)
1199                 return;
1200
1201         self.attack_finished = time + 2;
1202
1203         if (self.message)
1204         {
1205                 if (other.flags & FL_CLIENT)
1206                         centermsg_setfor (other, CENTERMSG_TRIGGER, self.message);
1207                 sound (other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM);
1208         }
1209 };
1210
1211
1212 /*QUAKED func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1213 Basic secret door. Slides back, then to the side. Angle determines direction.
1214 wait  = # of seconds before coming back
1215 1st_left = 1st move is left of arrow
1216 1st_down = 1st move is down from arrow
1217 always_shoot = even if targeted, keep shootable
1218 t_width = override WIDTH to move back (or height if going down)
1219 t_length = override LENGTH to move sideways
1220 "dmg"           damage to inflict when blocked (2 default)
1221
1222 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1223 "sounds"
1224 1) medieval
1225 2) metal
1226 3) base
1227 */
1228
1229 void () func_door_secret =
1230 {
1231         /*if (!self.deathtype) // map makers can override this
1232                 self.deathtype = " got in the way";*/
1233
1234         if (!self.dmg)
1235                 self.dmg = 2;
1236
1237         // Magic formula...
1238         self.mangle = self.angles;
1239         self.angles = '0 0 0';
1240         self.solid = SOLID_BSP;
1241         self.movetype = MOVETYPE_PUSH;
1242         self.classname = "door";
1243         setmodel (self, self.model);
1244         setorigin (self, self.origin);
1245
1246         self.touch = secret_touch;
1247         self.blocked = secret_blocked;
1248         self.speed = 50;
1249         self.use = fd_secret_use;
1250         if ( !self.targetname || self.spawnflags&SECRET_YES_SHOOT)
1251         {
1252                 self.health = 10000;
1253                 self.takedamage = DAMAGE_YES;
1254                 self.event_damage = fd_secret_use;
1255         }
1256         self.oldorigin = self.origin;
1257         if (!self.wait)
1258                 self.wait = 5;          // 5 seconds before closing
1259 };
1260