- get rid of delayed init where I found it and replace it by something that runs...
[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
144 .string sound1, sound2;
145
146 void spawnfunc_path_corner() { };
147 void spawnfunc_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         InitMovingBrushTrigger();
195         self.effects |= EF_LOWPRECISION;
196         setsize (self, self.mins , self.maxs);
197
198         self.blocked = plat_crush;
199
200         if (!self.speed)
201                 self.speed = 150;
202
203         self.pos1 = self.origin;
204         self.pos2 = self.origin;
205         self.pos2_z = self.origin_z - self.size_z + 8;
206
207         self.use = plat_trigger_use;
208
209         plat_spawn_inside_trigger ();   // the "start moving" trigger
210
211         if (self.targetname)
212         {
213                 self.state = 4;
214                 self.use = plat_use;
215         }
216         else
217         {
218                 setorigin (self, self.pos2);
219                 self.state = 2;
220         }
221 };
222
223
224 /*
225 void() train_next;
226 void() func_train_find;
227
228 void train_blocked()
229 {
230         if (time < self.attack_finished_single)
231                 return;
232         self.attack_finished_single = time + 0.5;
233 };
234 void train_use()
235 {
236         if (self.think != func_train_find)
237                 return;
238         train_next();
239 };
240
241 void train_wait()
242 {
243         if (self.wait)
244         {
245                 self.nextthink = self.ltime + self.wait;
246                 sound (self, CHAN_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
247         }
248         else
249                 self.nextthink = self.ltime + 0.1;
250
251         self.think = train_next;
252 };
253
254 void train_next()
255 {
256         local entity targ;
257
258         targ = find (world, targetname, self.target);
259         self.target = targ.target;
260         if (!self.target)
261                 objerror ("train_next: no next target");
262         if (targ.wait)
263                 self.wait = targ.wait;
264         else
265                 self.wait = 0;
266         sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
267         SUB_CalcMove (targ.origin - self.mins, self.speed, train_wait);
268 };
269
270 void func_train_find()
271 {
272         local entity targ;
273
274         targ = find (world, targetname, self.target);
275         self.target = targ.target;
276         setorigin (self, targ.origin - self.mins);
277         if (!self.targetname)
278         {       // not triggered, so start immediately
279                 self.nextthink = self.ltime + 0.1;
280                 self.think = train_next;
281         }
282 };
283
284
285 void spawnfunc_func_train()
286 {
287         if (!self.speed)
288                 self.speed = 100;
289         if (!self.target)
290                 objerror ("func_train without a target");
291
292         if (self.sounds == 0)
293         {
294                 self.noise = ("misc/null.wav");
295                 precache_sound ("misc/null.wav");
296                 self.noise1 = ("misc/null.wav");
297                 precache_sound ("misc/null.wav");
298         }
299
300         if (self.sounds == 1)
301         {
302                 self.noise = ("plats/train2.wav");
303                 precache_sound ("plats/train2.wav");
304                 self.noise1 = ("plats/train1.wav");
305                 precache_sound ("plats/train1.wav");
306         }
307
308         self.blocked = train_blocked;
309         self.use = train_use;
310         self.classname = "train";
311
312         InitMovingBrushTrigger();
313         self.effects |= EF_LOWPRECISION;
314         self.nextthink = self.ltime + 0.1;
315         self.think = func_train_find;
316 };
317 */
318
319 void() train_next;
320 void train_wait()
321 {
322         self.think = train_next;
323         self.nextthink = self.ltime + self.wait;
324 };
325
326 void train_next()
327 {
328         local entity targ;
329         targ = find(world, targetname, self.target);
330         self.target = targ.target;
331         if (!self.target)
332                 objerror("train_next: no next target");
333         self.wait = targ.wait;
334         if (!self.wait)
335                 self.wait = 0.1;
336         if (targ.speed)
337                 SUB_CalcMove(targ.origin - self.mins, targ.speed, train_wait);
338         else
339                 SUB_CalcMove(targ.origin - self.mins, self.speed, train_wait);
340 };
341
342 void func_train_find()
343 {
344         local entity targ;
345         targ = find(world, targetname, self.target);
346         self.target = targ.target;
347         if (!self.target)
348                 objerror("func_train_find: no next target");
349         setorigin(self, targ.origin - self.mins);
350         self.nextthink = self.ltime + 1;
351         self.think = train_next;
352 };
353
354 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
355 Ridable platform, targets spawnfunc_path_corner path to follow.
356 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
357 target : targetname of first spawnfunc_path_corner (starts here)
358 */
359 void spawnfunc_func_train()
360 {
361         if (!self.target)
362                 objerror("func_train without a target");
363         if (!self.speed)
364                 self.speed = 100;
365
366         InitMovingBrushTrigger();
367         self.effects |= EF_LOWPRECISION;
368
369         // wait for targets to spawn
370         InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
371 };
372
373
374
375 void rotating_blocked()
376 {
377
378     if(self.dmg && other.takedamage != DAMAGE_NO) {
379         if(self.dmgtime2 < time) {
380             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
381             self.dmgtime2 = time + self.dmgtime;
382         }
383
384         // Gib dead/dying stuff
385         if(other.deadflag != DEAD_NO)
386             Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
387     }
388
389
390 }
391
392 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
393 Brush model that spins in place on one axis (default Z).
394 speed   : speed to rotate (in degrees per second)
395 noise   : path/name of looping .wav file to play.
396 dmg     : Do this mutch dmg every .dmgtime intervall when blocked
397 dmgtime : See above.
398 */
399
400 void spawnfunc_func_rotating()
401 {
402         if (self.noise)
403         {
404                 precache_sound(self.noise);
405                 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
406         }
407         if (!self.speed)
408                 self.speed = 100;
409         // FIXME: test if this turns the right way, then remove this comment (negate as needed)
410         if (self.spawnflags & 4) // X (untested)
411                 self.avelocity = '0 0 1' * self.speed;
412         // FIXME: test if this turns the right way, then remove this comment (negate as needed)
413         else if (self.spawnflags & 8) // Y (untested)
414                 self.avelocity = '1 0 0' * self.speed;
415         // FIXME: test if this turns the right way, then remove this comment (negate as needed)
416         else // Z
417                 self.avelocity = '0 1 0' * self.speed;
418
419     if(self.dmg & (!self.message))
420         self.message = " was in the wrong place.";
421
422
423     if(self.dmg && (!self.dmgtime))
424         self.dmgtime = 0.25;
425
426     self.dmgtime2 = time;
427
428         InitMovingBrushTrigger();
429         // no EF_LOWPRECISION here, as rounding angles is bad
430
431     self.blocked = rotating_blocked;
432
433         // wait for targets to spawn
434         self.nextthink = self.ltime + 999999999;
435         self.think = SUB_Null;
436 };
437
438 .float height;
439 .float phase;
440 void func_bobbing_controller_think()
441 {
442         local vector v;
443         self.nextthink = time + 0.1;
444         // calculate sinewave using makevectors
445         makevectors((time * self.owner.cnt + self.owner.phase) * '0 1 0');
446         v = self.owner.destvec + self.owner.movedir * v_forward_y;
447         // * 10 so it will arrive in 0.1 sec
448         self.owner.velocity = (v - self.owner.origin) * 10;
449 };
450
451 void bobbing_blocked()
452 {
453         // no need to duplicate code
454         rotating_blocked();
455 }
456
457 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
458 Brush model that moves back and forth on one axis (default Z).
459 speed : how long one cycle takes in seconds (default 4)
460 height : how far the cycle moves (default 32)
461 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
462 noise : path/name of looping .wav file to play.
463 dmg : Do this mutch dmg every .dmgtime intervall when blocked
464 dmgtime : See above.
465 */
466 void spawnfunc_func_bobbing()
467 {
468         local entity controller;
469         if (self.noise)
470         {
471                 precache_sound(self.noise);
472                 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
473         }
474         if (!self.speed)
475                 self.speed = 4;
476         if (!self.height)
477                 self.height = 32;
478         // center of bobbing motion
479         self.destvec = self.origin;
480         // time scale to get degrees
481         self.cnt = 360 / self.speed;
482
483         // damage when blocked
484         self.blocked = bobbing_blocked;
485         if(self.dmg & (!self.message))
486                 self.message = " was in the wrong place.";
487         if(self.dmg && (!self.dmgtime))
488                 self.dmgtime = 0.25;
489         self.dmgtime2 = time;
490
491         // how far to bob
492         if (self.spawnflags & 1) // X
493                 self.movedir = '1 0 0' * self.height;
494         else if (self.spawnflags & 2) // Y
495                 self.movedir = '0 1 0' * self.height;
496         else // Z
497                 self.movedir = '0 0 1' * self.height;
498
499         InitMovingBrushTrigger();
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_TRIGGER, self.noise, VOL_BASE, 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 button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
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 spawnfunc_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 spawnfunc_func_button()
626 {
627         SetMovedir ();
628
629         InitMovingBrushTrigger();
630         self.effects |= EF_LOWPRECISION;
631
632         self.blocked = button_blocked;
633         self.use = button_use;
634
635 //      if (self.health == 0) // all buttons are now shootable
636 //              self.health = 10;
637         if (self.health)
638         {
639                 self.max_health = self.health;
640                 self.event_damage = button_damage;
641                 self.takedamage = DAMAGE_YES;
642         }
643         else
644                 self.touch = button_touch;
645
646         if (!self.speed)
647                 self.speed = 40;
648         if (!self.wait)
649                 self.wait = 1;
650         if (!self.lip)
651                 self.lip = 4;
652
653     if(self.noise != "")
654         precache_sound(self.noise);
655
656         self.state = STATE_BOTTOM;
657
658         self.pos1 = self.origin;
659         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
660 };
661
662
663 float DOOR_START_OPEN = 1;
664 float DOOR_DONT_LINK = 4;
665 float DOOR_TOGGLE = 32;
666
667 /*
668
669 Doors are similar to buttons, but can spawn a fat trigger field around them
670 to open without a touch, and they link together to form simultanious
671 double/quad doors.
672
673 Door.owner is the master door.  If there is only one door, it points to itself.
674 If multiple doors, all will point to a single one.
675
676 Door.enemy chains from the master door through all doors linked in the chain.
677
678 */
679
680 /*
681 =============================================================================
682
683 THINK FUNCTIONS
684
685 =============================================================================
686 */
687
688 void() door_go_down;
689 void() door_go_up;
690
691 void door_blocked()
692 {
693
694     if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
695         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
696     } else {
697
698         if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?
699             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
700
701          //Dont chamge direction for dead or dying stuff
702         if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
703             if (self.wait >= 0)
704             {
705                 if (self.state == STATE_DOWN)
706                     door_go_up ();
707                 else
708                     door_go_down ();
709             }
710         } else {
711             //gib dying stuff just to make sure
712             if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?
713                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
714         }
715     }
716
717         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
718 // if a door has a negative wait, it would never come back if blocked,
719 // so let it just squash the object to death real fast
720 /*      if (self.wait >= 0)
721         {
722                 if (self.state == STATE_DOWN)
723                         door_go_up ();
724                 else
725                         door_go_down ();
726         }
727 */
728 };
729
730
731 void door_hit_top()
732 {
733         if (self.noise1 != "")
734                 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
735         self.state = STATE_TOP;
736         if (self.spawnflags & DOOR_TOGGLE)
737                 return;         // don't come down automatically
738         self.think = door_go_down;
739         self.nextthink = self.ltime + self.wait;
740 };
741
742 void door_hit_bottom()
743 {
744         if (self.noise1 != "")
745                 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
746         self.state = STATE_BOTTOM;
747 };
748
749 void door_go_down()
750 {
751         if (self.noise2 != "")
752                 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
753         if (self.max_health)
754         {
755                 self.takedamage = DAMAGE_YES;
756                 self.health = self.max_health;
757         }
758
759         self.state = STATE_DOWN;
760         SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
761 };
762
763 void door_go_up()
764 {
765         if (self.state == STATE_UP)
766                 return;         // allready going up
767
768         if (self.state == STATE_TOP)
769         {       // reset top wait time
770                 self.nextthink = self.ltime + self.wait;
771                 return;
772         }
773
774         if (self.noise2 != "")
775                 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
776         self.state = STATE_UP;
777         SUB_CalcMove (self.pos2, self.speed, door_hit_top);
778
779         SUB_UseTargets();
780 };
781
782
783 /*
784 =============================================================================
785
786 ACTIVATION FUNCTIONS
787
788 =============================================================================
789 */
790
791 void door_fire()
792 {
793         local entity    oself;
794         local entity    starte;
795         string oldmessage;
796
797         if (self.owner != self)
798                 objerror ("door_fire: self.owner != self");
799
800         oldmessage = self.message;
801         self.message = ""; // no more message
802         oself = self;
803
804         if (self.spawnflags & DOOR_TOGGLE)
805         {
806                 if (self.state == STATE_UP || self.state == STATE_TOP)
807                 {
808                         starte = self;
809                         do
810                         {
811                                 door_go_down ();
812                                 self = self.enemy;
813                         } while ( (self != starte) && (self != world) );
814                         self = oself;
815                         return;
816                 }
817         }
818
819 // trigger all paired doors
820         starte = self;
821         do
822         {
823                 door_go_up ();
824                 self = self.enemy;
825         } while ( (self != starte) && (self != world) );
826         self = oself;
827
828         self.message = oldmessage;
829 };
830
831
832 void door_use()
833 {
834         local entity oself;
835
836         //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
837         if (self.owner)
838         {
839                 oself = self;
840                 self = self.owner;
841                 door_fire ();
842                 self = oself;
843         }
844 };
845
846
847 void door_trigger_touch()
848 {
849         if (other.health < 1)
850                 return;
851
852         if (time < self.attack_finished_single)
853                 return;
854         self.attack_finished_single = time + 1;
855
856         activator = other;
857
858         self = self.owner;
859         door_use ();
860 };
861
862
863 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
864 {
865         local entity oself;
866         self.health = self.health - damage;
867         if (self.health <= 0)
868         {
869                 oself = self;
870                 self = self.owner;
871                 self.health = self.max_health;
872                 self.takedamage = DAMAGE_NO;    // wil be reset upon return
873                 door_use ();
874                 self = oself;
875         }
876 };
877
878
879 /*
880 ================
881 door_touch
882
883 Prints messages
884 ================
885 */
886 void door_touch()
887 {
888         if (other.classname != "player")
889                 return;
890         if (self.owner.attack_finished_single > time)
891                 return;
892
893         self.owner.attack_finished_single = time + 2;
894
895         if (self.owner.message != "")
896         {
897                 if (other.flags & FL_CLIENT)
898                         centerprint (other, self.owner.message);
899                 play2(other, "misc/talk.wav");
900         }
901 };
902
903 /*
904 =============================================================================
905
906 SPAWNING FUNCTIONS
907
908 =============================================================================
909 */
910
911
912 entity spawn_field(vector fmins, vector fmaxs)
913 {
914         local entity    trigger;
915         local   vector  t1, t2;
916
917         trigger = spawn();
918         trigger.classname = "doortriggerfield";
919         trigger.movetype = MOVETYPE_NONE;
920         trigger.solid = SOLID_TRIGGER;
921         trigger.owner = self;
922         trigger.touch = door_trigger_touch;
923
924         t1 = fmins;
925         t2 = fmaxs;
926         setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
927         return (trigger);
928 };
929
930
931 float EntitiesTouching(entity e1, entity e2)
932 {
933         if (e1.mins_x > e2.maxs_x)
934                 return FALSE;
935         if (e1.mins_y > e2.maxs_y)
936                 return FALSE;
937         if (e1.mins_z > e2.maxs_z)
938                 return FALSE;
939         if (e1.maxs_x < e2.mins_x)
940                 return FALSE;
941         if (e1.maxs_y < e2.mins_y)
942                 return FALSE;
943         if (e1.maxs_z < e2.mins_z)
944                 return FALSE;
945         return TRUE;
946 };
947
948
949 /*
950 =============
951 LinkDoors
952
953
954 =============
955 */
956 void LinkDoors()
957 {
958         local entity    t, starte;
959         local vector    cmins, cmaxs;
960
961         if (self.enemy)
962                 return;         // already linked by another door
963         if (self.spawnflags & 4)
964         {
965                 self.owner = self.enemy = self;
966                 return;         // don't want to link this door
967         }
968
969         cmins = self.mins;
970         cmaxs = self.maxs;
971
972         starte = self;
973         t = self;
974
975         do
976         {
977                 self.owner = starte;                    // master door
978
979                 if (self.health)
980                         starte.health = self.health;
981                 if (self.targetname)
982                         starte.targetname = self.targetname;
983                 if (self.message != "")
984                         starte.message = self.message;
985
986                 t = find(t, classname, self.classname);
987                 if (!t)
988                 {
989                         self.enemy = starte;            // make the chain a loop
990
991                 // shootable, or triggered doors just needed the owner/enemy links,
992                 // they don't spawn a field
993
994                         self = self.owner;
995
996                         if (self.health)
997                                 return;
998                         if (self.targetname)
999                                 return;
1000                         if (self.items)
1001                                 return;
1002
1003                         self.owner.trigger_field = spawn_field(cmins, cmaxs);
1004
1005                         return;
1006                 }
1007
1008                 if (EntitiesTouching(self,t))
1009                 {
1010                         if (t.enemy)
1011                                 objerror ("cross connected doors");
1012
1013                         self.enemy = t;
1014                         self = t;
1015
1016                         if (t.mins_x < cmins_x)
1017                                 cmins_x = t.mins_x;
1018                         if (t.mins_y < cmins_y)
1019                                 cmins_y = t.mins_y;
1020                         if (t.mins_z < cmins_z)
1021                                 cmins_z = t.mins_z;
1022                         if (t.maxs_x > cmaxs_x)
1023                                 cmaxs_x = t.maxs_x;
1024                         if (t.maxs_y > cmaxs_y)
1025                                 cmaxs_y = t.maxs_y;
1026                         if (t.maxs_z > cmaxs_z)
1027                                 cmaxs_z = t.maxs_z;
1028                 }
1029         } while (1 );
1030
1031 };
1032
1033
1034 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
1035 if two doors touch, they are assumed to be connected and operate as a unit.
1036
1037 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1038
1039 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).
1040
1041 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1042 "angle"         determines the opening direction
1043 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1044 "health"        if set, door must be shot open
1045 "speed"         movement speed (100 default)
1046 "wait"          wait before returning (3 default, -1 = never return)
1047 "lip"           lip remaining at end of move (8 default)
1048 "dmg"           damage to inflict when blocked (2 default)
1049 "sounds"
1050 0)      no sound
1051 1)      stone
1052 2)      base
1053 3)      stone chain
1054 4)      screechy metal
1055 FIXME: only one sound set available at the time being
1056
1057 */
1058
1059 void spawnfunc_func_door()
1060 {
1061         //if (!self.deathtype) // map makers can override this
1062         //      self.deathtype = " got in the way";
1063         SetMovedir ();
1064
1065         self.max_health = self.health;
1066         InitMovingBrushTrigger();
1067         self.effects |= EF_LOWPRECISION;
1068         self.classname = "door";
1069
1070         self.blocked = door_blocked;
1071         self.use = door_use;
1072
1073     if(self.targetname == "") {
1074         self.spawnflags = 0;
1075         self.dmg = 0;
1076     }
1077
1078     if(self.spawnflags & 8)
1079         self.dmg = 10000;
1080
1081     if(self.dmg & (!self.message))
1082                 self.message = "was in the wrong place.";
1083
1084
1085
1086         if (self.sounds > 0)
1087         {
1088                 precache_sound ("plats/medplat1.wav");
1089                 precache_sound ("plats/medplat2.wav");
1090                 self.noise2 = "plats/medplat1.wav";
1091                 self.noise1 = "plats/medplat2.wav";
1092         }
1093
1094         if (!self.speed)
1095                 self.speed = 100;
1096         if (!self.wait)
1097                 self.wait = 3;
1098         if (!self.lip)
1099                 self.lip = 8;
1100         if (!self.dmg)
1101                 self.dmg = 2;
1102
1103         self.pos1 = self.origin;
1104         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1105
1106 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1107 // but spawn in the open position
1108         if (self.spawnflags & DOOR_START_OPEN)
1109         {
1110                 setorigin (self, self.pos2);
1111                 self.pos2 = self.pos1;
1112                 self.pos1 = self.origin;
1113         }
1114
1115         self.state = STATE_BOTTOM;
1116
1117         if (self.health)
1118         {
1119                 self.takedamage = DAMAGE_YES;
1120                 self.event_damage = door_damage;
1121         }
1122
1123         if (self.items)
1124                 self.wait = -1;
1125
1126         self.touch = door_touch;
1127
1128 // LinkDoors can't be done until all of the doors have been spawned, so
1129 // the sizes can be detected properly.
1130         InitializeEntity(self, LinkDoors, INITPRIO_FINDTARGET);
1131 };
1132
1133 /*
1134 =============================================================================
1135
1136 SECRET DOORS
1137
1138 =============================================================================
1139 */
1140
1141 void() fd_secret_move1;
1142 void() fd_secret_move2;
1143 void() fd_secret_move3;
1144 void() fd_secret_move4;
1145 void() fd_secret_move5;
1146 void() fd_secret_move6;
1147 void() fd_secret_done;
1148
1149 float SECRET_OPEN_ONCE = 1;             // stays open
1150 float SECRET_1ST_LEFT = 2;              // 1st move is left of arrow
1151 float SECRET_1ST_DOWN = 4;              // 1st move is down from arrow
1152 float SECRET_NO_SHOOT = 8;              // only opened by trigger
1153 float SECRET_YES_SHOOT = 16;    // shootable even if targeted
1154
1155
1156 void fd_secret_use()
1157 {
1158         local float temp;
1159         string message_save;
1160
1161         self.health = 10000;
1162         self.bot_attack = TRUE;
1163
1164         // exit if still moving around...
1165         if (self.origin != self.oldorigin)
1166                 return;
1167
1168         message_save = self.message;
1169         self.message = ""; // no more message
1170         SUB_UseTargets();                               // fire all targets / killtargets
1171         self.message = message_save;
1172
1173         self.velocity = '0 0 0';
1174
1175         // Make a sound, wait a little...
1176
1177         if (self.noise1 != "")
1178                 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, 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_TRIGGER, self.noise2, VOL_BASE, 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_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1213 };
1214
1215 // Start moving sideways w/sound...
1216 void fd_secret_move2()
1217 {
1218         if (self.noise2 != "")
1219                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, 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_TRIGGER, self.noise3, VOL_BASE, 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_TRIGGER, self.noise2, VOL_BASE, 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_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1250 };
1251
1252 void fd_secret_move6()
1253 {
1254         if (self.noise2 != "")
1255                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, 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_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1269 };
1270
1271 void secret_blocked()
1272 {
1273         if (time < self.attack_finished_single)
1274                 return;
1275         self.attack_finished_single = 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_single > time)
1291                 return;
1292
1293         self.attack_finished_single = time + 2;
1294
1295         if (self.message)
1296         {
1297                 if (other.flags & FL_CLIENT)
1298                         centerprint (other, self.message);
1299                 play2(other, "misc/talk.wav");
1300         }
1301 };
1302
1303
1304 /*QUAKED spawnfunc_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 spawnfunc_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.classname = "door";
1333         InitMovingBrushTrigger();
1334         self.effects |= EF_LOWPRECISION;
1335
1336         self.touch = secret_touch;
1337         self.blocked = secret_blocked;
1338         self.speed = 50;
1339         self.use = fd_secret_use;
1340         if ( !self.targetname || self.spawnflags&SECRET_YES_SHOOT)
1341         {
1342                 self.health = 10000;
1343                 self.takedamage = DAMAGE_YES;
1344                 self.event_damage = fd_secret_use;
1345         }
1346         self.oldorigin = self.origin;
1347         if (!self.wait)
1348                 self.wait = 5;          // 5 seconds before closing
1349 };
1350