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