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