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