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