]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/t_plats.qc
Fix (add) precache_sound for .noisy buttons.
[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 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         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 spawnfunc_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 spawnfunc_func_train (0 .5 .8) ?
360 Ridable platform, targets spawnfunc_path_corner path to follow.
361 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
362 target : targetname of first spawnfunc_path_corner (starts here)
363 */
364 void spawnfunc_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 spawnfunc_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 spawnfunc_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 spawnfunc_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 spawnfunc_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 button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
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 spawnfunc_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 spawnfunc_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     if(self.noise != "")
677         precache_sound(self.noise);
678
679         self.state = STATE_BOTTOM;
680
681         self.pos1 = self.origin;
682         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
683 };
684
685
686 float DOOR_START_OPEN = 1;
687 float DOOR_DONT_LINK = 4;
688 float DOOR_TOGGLE = 32;
689
690 /*
691
692 Doors are similar to buttons, but can spawn a fat trigger field around them
693 to open without a touch, and they link together to form simultanious
694 double/quad doors.
695
696 Door.owner is the master door.  If there is only one door, it points to itself.
697 If multiple doors, all will point to a single one.
698
699 Door.enemy chains from the master door through all doors linked in the chain.
700
701 */
702
703 /*
704 =============================================================================
705
706 THINK FUNCTIONS
707
708 =============================================================================
709 */
710
711 void() door_go_down;
712 void() door_go_up;
713
714 void door_blocked()
715 {
716
717     if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
718         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
719     } else {
720
721         if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?
722             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
723
724          //Dont chamge direction for dead or dying stuff
725         if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
726             if (self.wait >= 0)
727             {
728                 if (self.state == STATE_DOWN)
729                     door_go_up ();
730                 else
731                     door_go_down ();
732             }
733         } else {
734             //gib dying stuff just to make sure
735             if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?
736                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
737         }
738     }
739
740         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
741 // if a door has a negative wait, it would never come back if blocked,
742 // so let it just squash the object to death real fast
743 /*      if (self.wait >= 0)
744         {
745                 if (self.state == STATE_DOWN)
746                         door_go_up ();
747                 else
748                         door_go_down ();
749         }
750 */
751 };
752
753
754 void door_hit_top()
755 {
756         if (self.noise1 != "")
757                 sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
758         self.state = STATE_TOP;
759         if (self.spawnflags & DOOR_TOGGLE)
760                 return;         // don't come down automatically
761         self.think = door_go_down;
762         self.nextthink = self.ltime + self.wait;
763 };
764
765 void door_hit_bottom()
766 {
767         if (self.noise1 != "")
768                 sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
769         self.state = STATE_BOTTOM;
770 };
771
772 void door_go_down()
773 {
774         if (self.noise2 != "")
775                 sound (self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
776         if (self.max_health)
777         {
778                 self.takedamage = DAMAGE_YES;
779                 self.health = self.max_health;
780         }
781
782         self.state = STATE_DOWN;
783         SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
784 };
785
786 void door_go_up()
787 {
788         if (self.state == STATE_UP)
789                 return;         // allready going up
790
791         if (self.state == STATE_TOP)
792         {       // reset top wait time
793                 self.nextthink = self.ltime + self.wait;
794                 return;
795         }
796
797         if (self.noise2 != "")
798                 sound (self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
799         self.state = STATE_UP;
800         SUB_CalcMove (self.pos2, self.speed, door_hit_top);
801
802         SUB_UseTargets();
803 };
804
805
806 /*
807 =============================================================================
808
809 ACTIVATION FUNCTIONS
810
811 =============================================================================
812 */
813
814 void door_fire()
815 {
816         local entity    oself;
817         local entity    starte;
818
819         if (self.owner != self)
820                 objerror ("door_fire: self.owner != self");
821
822         self.message = ""; // no more message
823         oself = self;
824
825         if (self.spawnflags & DOOR_TOGGLE)
826         {
827                 if (self.state == STATE_UP || self.state == STATE_TOP)
828                 {
829                         starte = self;
830                         do
831                         {
832                                 door_go_down ();
833                                 self = self.enemy;
834                         } while ( (self != starte) && (self != world) );
835                         self = oself;
836                         return;
837                 }
838         }
839
840 // trigger all paired doors
841         starte = self;
842         do
843         {
844                 door_go_up ();
845                 self = self.enemy;
846         } while ( (self != starte) && (self != world) );
847         self = oself;
848 };
849
850
851 void door_use()
852 {
853         local entity oself;
854
855         //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
856         self.message = "";                      // door message are for touch only
857         if (self.owner)
858                 self.owner.message = "";
859         if (self.enemy)
860                 self.enemy.message = "";
861         if (self.owner)
862         {
863                 oself = self;
864                 self = self.owner;
865                 door_fire ();
866                 self = oself;
867         }
868 };
869
870
871 void door_trigger_touch()
872 {
873         if (other.health < 1)
874                 return;
875
876         if (time < self.attack_finished_single)
877                 return;
878         self.attack_finished_single = time + 1;
879
880         activator = other;
881
882         self = self.owner;
883         door_use ();
884 };
885
886
887 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
888 {
889         local entity oself;
890         self.health = self.health - damage;
891         if (self.health <= 0)
892         {
893                 oself = self;
894                 self = self.owner;
895                 self.health = self.max_health;
896                 self.takedamage = DAMAGE_NO;    // wil be reset upon return
897                 door_use ();
898                 self = oself;
899         }
900 };
901
902
903 /*
904 ================
905 door_touch
906
907 Prints messages
908 ================
909 */
910 void door_touch()
911 {
912         if (other.classname != "player")
913                 return;
914         if (self.owner.attack_finished_single > time)
915                 return;
916
917         self.owner.attack_finished_single = time + 2;
918
919         if (self.owner.message != "")
920         {
921                 if (other.flags & FL_CLIENT)
922                         centerprint (other, self.owner.message);
923                 sound (other, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NORM);
924         }
925 };
926
927 /*
928 =============================================================================
929
930 SPAWNING FUNCTIONS
931
932 =============================================================================
933 */
934
935
936 entity spawn_field(vector fmins, vector fmaxs)
937 {
938         local entity    trigger;
939         local   vector  t1, t2;
940
941         trigger = spawn();
942         trigger.classname = "doortriggerfield";
943         trigger.movetype = MOVETYPE_NONE;
944         trigger.solid = SOLID_TRIGGER;
945         trigger.owner = self;
946         trigger.touch = door_trigger_touch;
947
948         t1 = fmins;
949         t2 = fmaxs;
950         setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
951         return (trigger);
952 };
953
954
955 float EntitiesTouching(entity e1, entity e2)
956 {
957         if (e1.mins_x > e2.maxs_x)
958                 return FALSE;
959         if (e1.mins_y > e2.maxs_y)
960                 return FALSE;
961         if (e1.mins_z > e2.maxs_z)
962                 return FALSE;
963         if (e1.maxs_x < e2.mins_x)
964                 return FALSE;
965         if (e1.maxs_y < e2.mins_y)
966                 return FALSE;
967         if (e1.maxs_z < e2.mins_z)
968                 return FALSE;
969         return TRUE;
970 };
971
972
973 /*
974 =============
975 LinkDoors
976
977
978 =============
979 */
980 void LinkDoors()
981 {
982         local entity    t, starte;
983         local vector    cmins, cmaxs;
984
985         if (self.enemy)
986                 return;         // already linked by another door
987         if (self.spawnflags & 4)
988         {
989                 self.owner = self.enemy = self;
990                 return;         // don't want to link this door
991         }
992
993         cmins = self.mins;
994         cmaxs = self.maxs;
995
996         starte = self;
997         t = self;
998
999         do
1000         {
1001                 self.owner = starte;                    // master door
1002
1003                 if (self.health)
1004                         starte.health = self.health;
1005                 if (self.targetname)
1006                         starte.targetname = self.targetname;
1007                 if (self.message != "")
1008                         starte.message = self.message;
1009
1010                 t = find(t, classname, self.classname);
1011                 if (!t)
1012                 {
1013                         self.enemy = starte;            // make the chain a loop
1014
1015                 // shootable, or triggered doors just needed the owner/enemy links,
1016                 // they don't spawn a field
1017
1018                         self = self.owner;
1019
1020                         if (self.health)
1021                                 return;
1022                         if (self.targetname)
1023                                 return;
1024                         if (self.items)
1025                                 return;
1026
1027                         self.owner.trigger_field = spawn_field(cmins, cmaxs);
1028
1029                         return;
1030                 }
1031
1032                 if (EntitiesTouching(self,t))
1033                 {
1034                         if (t.enemy)
1035                                 objerror ("cross connected doors");
1036
1037                         self.enemy = t;
1038                         self = t;
1039
1040                         if (t.mins_x < cmins_x)
1041                                 cmins_x = t.mins_x;
1042                         if (t.mins_y < cmins_y)
1043                                 cmins_y = t.mins_y;
1044                         if (t.mins_z < cmins_z)
1045                                 cmins_z = t.mins_z;
1046                         if (t.maxs_x > cmaxs_x)
1047                                 cmaxs_x = t.maxs_x;
1048                         if (t.maxs_y > cmaxs_y)
1049                                 cmaxs_y = t.maxs_y;
1050                         if (t.maxs_z > cmaxs_z)
1051                                 cmaxs_z = t.maxs_z;
1052                 }
1053         } while (1 );
1054
1055 };
1056
1057
1058 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
1059 if two doors touch, they are assumed to be connected and operate as a unit.
1060
1061 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1062
1063 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).
1064
1065 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1066 "angle"         determines the opening direction
1067 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1068 "health"        if set, door must be shot open
1069 "speed"         movement speed (100 default)
1070 "wait"          wait before returning (3 default, -1 = never return)
1071 "lip"           lip remaining at end of move (8 default)
1072 "dmg"           damage to inflict when blocked (2 default)
1073 "sounds"
1074 0)      no sound
1075 1)      stone
1076 2)      base
1077 3)      stone chain
1078 4)      screechy metal
1079 FIXME: only one sound set available at the time being
1080
1081 */
1082
1083 void spawnfunc_func_door()
1084 {
1085         //if (!self.deathtype) // map makers can override this
1086         //      self.deathtype = " got in the way";
1087         SetMovedir ();
1088
1089         self.max_health = self.health;
1090         self.solid = SOLID_BSP;
1091         self.movetype = MOVETYPE_PUSH;
1092         setorigin (self, self.origin);
1093         setmodel (self, self.model); // precision set below
1094         self.effects |= EF_LOWPRECISION;
1095         self.classname = "door";
1096
1097         self.blocked = door_blocked;
1098         self.use = door_use;
1099
1100     if(self.targetname == "") {
1101         self.spawnflags = 0;
1102         self.dmg = 0;
1103     }
1104
1105     if(self.spawnflags & 8)
1106         self.dmg = 10000;
1107
1108     if(self.dmg & (!self.message))
1109                 self.message = "was in the wrong place.";
1110
1111
1112
1113         if (self.sounds > 0)
1114         {
1115                 precache_sound ("plats/medplat1.wav");
1116                 precache_sound ("plats/medplat2.wav");
1117                 self.noise2 = "plats/medplat1.wav";
1118                 self.noise1 = "plats/medplat2.wav";
1119         }
1120
1121         if (!self.speed)
1122                 self.speed = 100;
1123         if (!self.wait)
1124                 self.wait = 3;
1125         if (!self.lip)
1126                 self.lip = 8;
1127         if (!self.dmg)
1128                 self.dmg = 2;
1129
1130         self.pos1 = self.origin;
1131         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1132
1133 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1134 // but spawn in the open position
1135         if (self.spawnflags & DOOR_START_OPEN)
1136         {
1137                 setorigin (self, self.pos2);
1138                 self.pos2 = self.pos1;
1139                 self.pos1 = self.origin;
1140         }
1141
1142         self.state = STATE_BOTTOM;
1143
1144         if (self.health)
1145         {
1146                 self.takedamage = DAMAGE_YES;
1147                 self.event_damage = door_damage;
1148         }
1149
1150         if (self.items)
1151                 self.wait = -1;
1152
1153         self.touch = door_touch;
1154
1155 // LinkDoors can't be done until all of the doors have been spawned, so
1156 // the sizes can be detected properly.
1157         self.think = LinkDoors;
1158         self.nextthink = self.ltime + 0.1;
1159 };
1160
1161 /*
1162 =============================================================================
1163
1164 SECRET DOORS
1165
1166 =============================================================================
1167 */
1168
1169 void() fd_secret_move1;
1170 void() fd_secret_move2;
1171 void() fd_secret_move3;
1172 void() fd_secret_move4;
1173 void() fd_secret_move5;
1174 void() fd_secret_move6;
1175 void() fd_secret_done;
1176
1177 float SECRET_OPEN_ONCE = 1;             // stays open
1178 float SECRET_1ST_LEFT = 2;              // 1st move is left of arrow
1179 float SECRET_1ST_DOWN = 4;              // 1st move is down from arrow
1180 float SECRET_NO_SHOOT = 8;              // only opened by trigger
1181 float SECRET_YES_SHOOT = 16;    // shootable even if targeted
1182
1183
1184 void fd_secret_use()
1185 {
1186         local float temp;
1187
1188         self.health = 10000;
1189         self.bot_attack = TRUE;
1190
1191         // exit if still moving around...
1192         if (self.origin != self.oldorigin)
1193                 return;
1194
1195         self.message = ""; // no more message
1196
1197         SUB_UseTargets();                               // fire all targets / killtargets
1198
1199         self.velocity = '0 0 0';
1200
1201         // Make a sound, wait a little...
1202
1203         if (self.noise1 != "")
1204                 sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
1205         self.nextthink = self.ltime + 0.1;
1206
1207         temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1208         makevectors(self.mangle);
1209
1210         if (!self.t_width)
1211         {
1212                 if (self.spawnflags & SECRET_1ST_DOWN)
1213                         self.t_width = fabs(v_up * self.size);
1214                 else
1215                         self.t_width = fabs(v_right * self.size);
1216         }
1217
1218         if (!self.t_length)
1219                 self.t_length = fabs(v_forward * self.size);
1220
1221         if (self.spawnflags & SECRET_1ST_DOWN)
1222                 self.dest1 = self.origin - v_up * self.t_width;
1223         else
1224                 self.dest1 = self.origin + v_right * (self.t_width * temp);
1225
1226         self.dest2 = self.dest1 + v_forward * self.t_length;
1227         SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1228         if (self.noise2 != "")
1229                 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
1230 };
1231
1232 // Wait after first movement...
1233 void fd_secret_move1()
1234 {
1235         self.nextthink = self.ltime + 1.0;
1236         self.think = fd_secret_move2;
1237         if (self.noise3 != "")
1238                 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
1239 };
1240
1241 // Start moving sideways w/sound...
1242 void fd_secret_move2()
1243 {
1244         if (self.noise2 != "")
1245                 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
1246         SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1247 };
1248
1249 // Wait here until time to go back...
1250 void fd_secret_move3()
1251 {
1252         if (self.noise3 != "")
1253                 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
1254         if (!(self.spawnflags & SECRET_OPEN_ONCE))
1255         {
1256                 self.nextthink = self.ltime + self.wait;
1257                 self.think = fd_secret_move4;
1258         }
1259 };
1260
1261 // Move backward...
1262 void fd_secret_move4()
1263 {
1264         if (self.noise2 != "")
1265                 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
1266         SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1267 };
1268
1269 // Wait 1 second...
1270 void fd_secret_move5()
1271 {
1272         self.nextthink = self.ltime + 1.0;
1273         self.think = fd_secret_move6;
1274         if (self.noise3 != "")
1275                 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
1276 };
1277
1278 void fd_secret_move6()
1279 {
1280         if (self.noise2 != "")
1281                 sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
1282         SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1283 };
1284
1285 void fd_secret_done()
1286 {
1287         if (!self.targetname || self.spawnflags&SECRET_YES_SHOOT)
1288         {
1289                 self.health = 10000;
1290                 self.takedamage = DAMAGE_YES;
1291                 //self.th_pain = fd_secret_use;
1292         }
1293         if (self.noise3 != "")
1294                 sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
1295 };
1296
1297 void secret_blocked()
1298 {
1299         if (time < self.attack_finished_single)
1300                 return;
1301         self.attack_finished_single = time + 0.5;
1302         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1303 };
1304
1305 /*
1306 ==============
1307 secret_touch
1308
1309 Prints messages
1310 ================
1311 */
1312 void secret_touch()
1313 {
1314         if (activator.classname != "player")
1315                 return;
1316         if (self.attack_finished_single > time)
1317                 return;
1318
1319         self.attack_finished_single = time + 2;
1320
1321         if (self.message)
1322         {
1323                 if (other.flags & FL_CLIENT)
1324                         centerprint (other, self.message);
1325                 sound (other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM);
1326         }
1327 };
1328
1329
1330 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1331 Basic secret door. Slides back, then to the side. Angle determines direction.
1332 wait  = # of seconds before coming back
1333 1st_left = 1st move is left of arrow
1334 1st_down = 1st move is down from arrow
1335 always_shoot = even if targeted, keep shootable
1336 t_width = override WIDTH to move back (or height if going down)
1337 t_length = override LENGTH to move sideways
1338 "dmg"           damage to inflict when blocked (2 default)
1339
1340 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1341 "sounds"
1342 1) medieval
1343 2) metal
1344 3) base
1345 */
1346
1347 void spawnfunc_func_door_secret()
1348 {
1349         /*if (!self.deathtype) // map makers can override this
1350                 self.deathtype = " got in the way";*/
1351
1352         if (!self.dmg)
1353                 self.dmg = 2;
1354
1355         // Magic formula...
1356         self.mangle = self.angles;
1357         self.angles = '0 0 0';
1358         self.solid = SOLID_BSP;
1359         self.movetype = MOVETYPE_PUSH;
1360         self.classname = "door";
1361         setmodel (self, self.model); // precision set below
1362         self.effects |= EF_LOWPRECISION;
1363         setorigin (self, self.origin);
1364
1365         self.touch = secret_touch;
1366         self.blocked = secret_blocked;
1367         self.speed = 50;
1368         self.use = fd_secret_use;
1369         if ( !self.targetname || self.spawnflags&SECRET_YES_SHOOT)
1370         {
1371                 self.health = 10000;
1372                 self.takedamage = DAMAGE_YES;
1373                 self.event_damage = fd_secret_use;
1374         }
1375         self.oldorigin = self.origin;
1376         if (!self.wait)
1377                 self.wait = 5;          // 5 seconds before closing
1378 };
1379