func_door_rotating by RoKenn
[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.absmin + '25 25 0';
31         tmax = self.absmax - '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 not(other.iscreature)
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 not(other.iscreature)
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 not(other.iscreature)
509                 return;
510         if(other.velocity * self.movedir < 0)
511                 return;
512         self.enemy = other;
513         if (other.owner)
514                 self.enemy = other.owner;
515         button_fire ();
516 };
517
518 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
519 {
520         self.health = self.health - damage;
521         if (self.health <= 0)
522         {
523         //      if (activator.classname != "player")
524         //      {
525         //              dprint(activator.classname);
526         //              dprint(" killed a button\n");
527         //      }
528                 self.enemy = damage_attacker;
529                 button_fire ();
530         }
531 };
532
533
534 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
535 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.
536
537 "angle"         determines the opening direction
538 "target"        all entities with a matching targetname will be used
539 "speed"         override the default 40 speed
540 "wait"          override the default 1 second wait (-1 = never return)
541 "lip"           override the default 4 pixel lip remaining at end of move
542 "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
543 "sounds"
544 0) steam metal
545 1) wooden clunk
546 2) metallic click
547 3) in-out
548 */
549 void spawnfunc_func_button()
550 {
551         SetMovedir ();
552
553         InitMovingBrushTrigger();
554         self.effects |= EF_LOWPRECISION;
555
556         self.blocked = button_blocked;
557         self.use = button_use;
558
559 //      if (self.health == 0) // all buttons are now shootable
560 //              self.health = 10;
561         if (self.health)
562         {
563                 self.max_health = self.health;
564                 self.event_damage = button_damage;
565                 self.takedamage = DAMAGE_YES;
566         }
567         else
568                 self.touch = button_touch;
569
570         if (!self.speed)
571                 self.speed = 40;
572         if (!self.wait)
573                 self.wait = 1;
574         if (!self.lip)
575                 self.lip = 4;
576
577     if(self.noise != "")
578         precache_sound(self.noise);
579
580         self.state = STATE_BOTTOM;
581
582         self.pos1 = self.origin;
583         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
584 };
585
586
587 float DOOR_START_OPEN = 1;
588 float DOOR_DONT_LINK = 4;
589 float DOOR_TOGGLE = 32;
590
591 /*
592
593 Doors are similar to buttons, but can spawn a fat trigger field around them
594 to open without a touch, and they link together to form simultanious
595 double/quad doors.
596
597 Door.owner is the master door.  If there is only one door, it points to itself.
598 If multiple doors, all will point to a single one.
599
600 Door.enemy chains from the master door through all doors linked in the chain.
601
602 */
603
604 /*
605 =============================================================================
606
607 THINK FUNCTIONS
608
609 =============================================================================
610 */
611
612 void() door_go_down;
613 void() door_go_up;
614 void() door_rotating_go_down;
615 void() door_rotating_go_up;
616
617 void door_blocked()
618 {
619
620     if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
621         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
622     } else {
623
624         if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?
625             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
626
627          //Dont chamge direction for dead or dying stuff
628         if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
629             if (self.wait >= 0)
630             {
631                 if (self.state == STATE_DOWN)
632                         if (self.classname == "door")
633                         {
634                                 door_go_up ();
635                         } else 
636                         {
637                                 door_rotating_go_up ();
638                         }
639                 else
640                         if (self.classname == "door")
641                         {
642                                 door_go_down ();
643                         } else 
644                         {
645                                 door_rotating_go_down ();
646                         }
647             }
648         } else {
649             //gib dying stuff just to make sure
650             if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?
651                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
652         }
653     }
654
655         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
656 // if a door has a negative wait, it would never come back if blocked,
657 // so let it just squash the object to death real fast
658 /*      if (self.wait >= 0)
659         {
660                 if (self.state == STATE_DOWN)
661                         door_go_up ();
662                 else
663                         door_go_down ();
664         }
665 */
666 };
667
668
669 void door_hit_top()
670 {
671         if (self.noise1 != "")
672                 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
673         self.state = STATE_TOP;
674         if (self.spawnflags & DOOR_TOGGLE)
675                 return;         // don't come down automatically
676         if (self.classname == "door")
677         {
678                 self.think = door_go_down;
679         } else 
680         {
681                 self.think = door_rotating_go_down;
682         }
683         self.nextthink = self.ltime + self.wait;
684 };
685
686 void door_hit_bottom()
687 {
688         if (self.noise1 != "")
689                 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
690         self.state = STATE_BOTTOM;
691 };
692
693 void door_go_down()
694 {
695         if (self.noise2 != "")
696                 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
697         if (self.max_health)
698         {
699                 self.takedamage = DAMAGE_YES;
700                 self.health = self.max_health;
701         }
702
703         self.state = STATE_DOWN;
704         SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
705 };
706
707 void door_go_up()
708 {
709         if (self.state == STATE_UP)
710                 return;         // allready going up
711
712         if (self.state == STATE_TOP)
713         {       // reset top wait time
714                 self.nextthink = self.ltime + self.wait;
715                 return;
716         }
717
718         if (self.noise2 != "")
719                 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
720         self.state = STATE_UP;
721         SUB_CalcMove (self.pos2, self.speed, door_hit_top);
722
723         string oldmessage;
724         oldmessage = self.message;
725         self.message = "";
726         SUB_UseTargets();
727         self.message = oldmessage;
728 };
729
730
731 /*
732 =============================================================================
733
734 ACTIVATION FUNCTIONS
735
736 =============================================================================
737 */
738
739 void door_fire()
740 {
741         local entity    oself;
742         local entity    starte;
743
744         if (self.owner != self)
745                 objerror ("door_fire: self.owner != self");
746
747         oself = self;
748
749         if (self.spawnflags & DOOR_TOGGLE)
750         {
751                 if (self.state == STATE_UP || self.state == STATE_TOP)
752                 {
753                         starte = self;
754                         do
755                         {
756                                 if (self.classname == "door") 
757                                 {
758                                         door_go_down ();
759                                 }
760                                 else
761                                 {
762                                         door_rotating_go_down ();
763                                 }
764                                 self = self.enemy;
765                         } while ( (self != starte) && (self != world) );
766                         self = oself;
767                         return;
768                 }
769         }
770
771 // trigger all paired doors
772         starte = self;
773         do
774         {
775                 if (self.classname == "door")
776                 {
777                         door_go_up ();
778                 } else 
779                 {
780                         door_rotating_go_up ();
781                 }
782                 self = self.enemy;
783         } while ( (self != starte) && (self != world) );
784         self = oself;
785 };
786
787
788 void door_use()
789 {
790         local entity oself;
791
792         //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
793         if (self.owner)
794         {
795                 oself = self;
796                 self = self.owner;
797                 door_fire ();
798                 self = oself;
799         }
800 };
801
802
803 void door_trigger_touch()
804 {
805         if (other.health < 1)
806         if not(other.iscreature && other.deadflag == DEAD_NO)
807                 return;
808
809         if (time < self.attack_finished_single)
810                 return;
811         self.attack_finished_single = time + 1;
812
813         activator = other;
814
815         self = self.owner;
816         door_use ();
817 };
818
819
820 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
821 {
822         local entity oself;
823         self.health = self.health - damage;
824         if (self.health <= 0)
825         {
826                 oself = self;
827                 self = self.owner;
828                 self.health = self.max_health;
829                 self.takedamage = DAMAGE_NO;    // wil be reset upon return
830                 door_use ();
831                 self = oself;
832         }
833 };
834
835
836 /*
837 ================
838 door_touch
839
840 Prints messages
841 ================
842 */
843 void door_touch()
844 {
845         if(other.classname != "player")
846                 return;
847         if (self.owner.attack_finished_single > time)
848                 return;
849
850         self.owner.attack_finished_single = time + 2;
851
852         if (!(self.owner.dmg) && (self.owner.message != ""))
853         {
854                 if (other.flags & FL_CLIENT)
855                         centerprint (other, self.owner.message);
856                 play2(other, "misc/talk.wav");
857         }
858 };
859
860
861 void door_rotating_blocked()
862 {
863
864     if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
865         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
866     } else {
867
868         if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?
869             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
870
871          //Dont chamge direction for dead or dying stuff
872         if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
873             if (self.wait >= 0)
874             {
875                 if (self.state == STATE_DOWN)
876                     door_rotating_go_up ();
877                 else
878                     door_rotating_go_down ();
879             }
880         } else {
881             //gib dying stuff just to make sure
882             if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?
883                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
884         }
885     }
886
887         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
888 // if a door has a negative wait, it would never come back if blocked,
889 // so let it just squash the object to death real fast
890 /*      if (self.wait >= 0)
891         {
892                 if (self.state == STATE_DOWN)
893                         door_rotating_go_up ();
894                 else
895                         door_rotating_go_down ();
896         }
897 */
898 };
899
900
901 void door_rotating_hit_top()
902 {
903         if (self.noise1 != "")
904                 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
905         self.state = STATE_TOP;
906         if (self.spawnflags & DOOR_TOGGLE)
907                 return;         // don't come down automatically
908         self.think = door_rotating_go_down;
909         self.nextthink = self.ltime + self.wait;
910 };
911
912 void door_rotating_hit_bottom()
913 {
914         if (self.noise1 != "")
915                 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
916         self.state = STATE_BOTTOM;
917 };
918
919 void door_rotating_go_down()
920 {
921         if (self.noise2 != "")
922                 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
923         if (self.max_health)
924         {
925                 self.takedamage = DAMAGE_YES;
926                 self.health = self.max_health;
927         }
928
929         self.state = STATE_DOWN;
930         SUB_CalcAngleMove (self.pos1, self.speed, door_hit_bottom);
931 };
932
933 void door_rotating_go_up()
934 {
935         if (self.state == STATE_UP)
936                 return;         // already going up
937
938         if (self.state == STATE_TOP)
939         {       // reset top wait time
940                 self.nextthink = self.ltime + self.wait;
941                 return;
942         }
943         if (self.noise2 != "")
944                 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
945         self.state = STATE_UP;
946         SUB_CalcAngleMove (self.pos2, self.speed, door_rotating_hit_top);
947
948         string oldmessage;
949         oldmessage = self.message;
950         self.message = "";
951         SUB_UseTargets();
952         self.message = oldmessage;
953 };
954
955
956
957
958 /*
959 =============================================================================
960
961 SPAWNING FUNCTIONS
962
963 =============================================================================
964 */
965
966
967 entity spawn_field(vector fmins, vector fmaxs)
968 {
969         local entity    trigger;
970         local   vector  t1, t2;
971
972         trigger = spawn();
973         trigger.classname = "doortriggerfield";
974         trigger.movetype = MOVETYPE_NONE;
975         trigger.solid = SOLID_TRIGGER;
976         trigger.owner = self;
977         trigger.touch = door_trigger_touch;
978
979         t1 = fmins;
980         t2 = fmaxs;
981         setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
982         return (trigger);
983 };
984
985
986 float EntitiesTouching(entity e1, entity e2)
987 {
988         if (e1.absmin_x > e2.absmax_x)
989                 return FALSE;
990         if (e1.absmin_y > e2.absmax_y)
991                 return FALSE;
992         if (e1.absmin_z > e2.absmax_z)
993                 return FALSE;
994         if (e1.absmax_x < e2.absmin_x)
995                 return FALSE;
996         if (e1.absmax_y < e2.absmin_y)
997                 return FALSE;
998         if (e1.absmax_z < e2.absmin_z)
999                 return FALSE;
1000         return TRUE;
1001 };
1002
1003
1004 /*
1005 =============
1006 LinkDoors
1007
1008
1009 =============
1010 */
1011 void LinkDoors()
1012 {
1013         local entity    t, starte;
1014         local vector    cmins, cmaxs;
1015
1016         if (self.enemy)
1017                 return;         // already linked by another door
1018         if (self.spawnflags & 4)
1019         {
1020                 self.owner = self.enemy = self;
1021
1022                 if (self.health)
1023                         return;
1024                 IFTARGETED
1025                         return;
1026                 if (self.items)
1027                         return;
1028                 self.trigger_field = spawn_field(self.absmin, self.absmax);
1029
1030                 return;         // don't want to link this door
1031         }
1032
1033         cmins = self.absmin;                      
1034         cmaxs = self.absmax;
1035
1036         starte = self;
1037         t = self;
1038
1039         do
1040         {
1041                 self.owner = starte;                    // master door
1042
1043                 if (self.health)
1044                         starte.health = self.health;
1045                 IFTARGETED
1046                         starte.targetname = self.targetname;
1047                 if (self.message != "")
1048                         starte.message = self.message;
1049
1050                 t = find(t, classname, self.classname);
1051                 if (!t)
1052                 {
1053                         self.enemy = starte;            // make the chain a loop
1054
1055                 // shootable, or triggered doors just needed the owner/enemy links,
1056                 // they don't spawn a field
1057
1058                         self = self.owner;
1059
1060                         if (self.health)
1061                                 return;
1062                         IFTARGETED
1063                                 return;
1064                         if (self.items)
1065                                 return;
1066
1067                         self.owner.trigger_field = spawn_field(cmins, cmaxs);
1068
1069                         return;
1070                 }
1071
1072                 if (EntitiesTouching(self,t))
1073                 {
1074                         if (t.enemy)
1075                                 objerror ("cross connected doors");
1076
1077                         self.enemy = t;
1078                         self = t;
1079
1080                         if (t.absmin_x < cmins_x)
1081                                 cmins_x = t.absmin_x;
1082                         if (t.absmin_y < cmins_y)
1083                                 cmins_y = t.absmin_y;
1084                         if (t.absmin_z < cmins_z)
1085                                 cmins_z = t.absmin_z;
1086                         if (t.absmax_x > cmaxs_x)
1087                                 cmaxs_x = t.absmax_x;
1088                         if (t.absmax_y > cmaxs_y)
1089                                 cmaxs_y = t.absmax_y;
1090                         if (t.absmax_z > cmaxs_z)
1091                                 cmaxs_z = t.absmax_z;
1092                 }
1093         } while (1 );
1094
1095 };
1096
1097
1098 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
1099 if two doors touch, they are assumed to be connected and operate as a unit.
1100
1101 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1102
1103 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 useful for touch or takedamage doors).
1104
1105 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1106 "angle"         determines the opening direction
1107 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1108 "health"        if set, door must be shot open
1109 "speed"         movement speed (100 default)
1110 "wait"          wait before returning (3 default, -1 = never return)
1111 "lip"           lip remaining at end of move (8 default)
1112 "dmg"           damage to inflict when blocked (2 default)
1113 "sounds"
1114 0)      no sound
1115 1)      stone
1116 2)      base
1117 3)      stone chain
1118 4)      screechy metal
1119 FIXME: only one sound set available at the time being
1120
1121 */
1122
1123 void door_init_startopen()
1124 {
1125         setorigin (self, self.pos2);
1126         self.pos2 = self.pos1;
1127         self.pos1 = self.origin;
1128 }
1129
1130 void spawnfunc_func_door()
1131 {
1132         //if (!self.deathtype) // map makers can override this
1133         //      self.deathtype = " got in the way";
1134         SetMovedir ();
1135
1136         self.max_health = self.health;
1137         InitMovingBrushTrigger();
1138         self.effects |= EF_LOWPRECISION;
1139         self.classname = "door";
1140
1141         self.blocked = door_blocked;
1142         self.use = door_use;
1143
1144     if(self.spawnflags & 8)
1145         self.dmg = 10000;
1146
1147     if(self.dmg && (!self.message))
1148                 self.message = "was squished";
1149     if(self.dmg && (!self.message2))
1150                 self.message2 = "was squished by";
1151
1152         if (self.sounds > 0)
1153         {
1154                 precache_sound ("plats/medplat1.wav");
1155                 precache_sound ("plats/medplat2.wav");
1156                 self.noise2 = "plats/medplat1.wav";
1157                 self.noise1 = "plats/medplat2.wav";
1158         }
1159
1160         if (!self.speed)
1161                 self.speed = 100;
1162         if (!self.wait)
1163                 self.wait = 3;
1164         if (!self.lip)
1165                 self.lip = 8;
1166
1167         self.pos1 = self.origin;
1168         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1169
1170 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1171 // but spawn in the open position
1172         if (self.spawnflags & DOOR_START_OPEN)
1173                 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1174
1175         self.state = STATE_BOTTOM;
1176
1177         if (self.health)
1178         {
1179                 self.takedamage = DAMAGE_YES;
1180                 self.event_damage = door_damage;
1181         }
1182
1183         if (self.items)
1184                 self.wait = -1;
1185
1186         self.touch = door_touch;
1187
1188 // LinkDoors can't be done until all of the doors have been spawned, so
1189 // the sizes can be detected properly.
1190         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1191 };
1192
1193 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE X_AXIS Y_AXIS BIDIR
1194 if two doors touch, they are assumed to be connected and operate as a unit.
1195
1196 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1197
1198 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor. 
1199 This feature is NOT YET IMPLEMENTED!
1200
1201 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).
1202
1203 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1204 "angle"         determines the destination angle for opening. negative values reverse the direction.
1205 "targetname"    if set, no touch field will be spawned and a remote button or trigger field activates the door.
1206 "health"        if set, door must be shot open
1207 "speed"         movement speed (100 default)
1208 "wait"          wait before returning (3 default, -1 = never return)
1209 "dmg"           damage to inflict when blocked (2 default)
1210 "sounds"
1211 0)      no sound
1212 1)      stone
1213 2)      base
1214 3)      stone chain
1215 4)      screechy metal
1216 FIXME: only one sound set available at the time being
1217 */
1218
1219 void door_rotating_init_startopen()
1220 {
1221         self.angles = self.movedir;
1222         self.pos2 = '0 0 0';
1223         self.pos1 = self.movedir;
1224 }
1225
1226
1227 void spawnfunc_func_door_rotating()
1228 {
1229         
1230         //if (!self.deathtype) // map makers can override this
1231         //      self.deathtype = " got in the way";
1232
1233         // I abuse "movedir" for denoting the axis for now
1234         if (self.spawnflags & 64) // X (untested)
1235                 self.movedir = '0 0 1';
1236         else if (self.spawnflags & 128) // Y (untested)
1237                 self.movedir = '1 0 0';
1238         else // Z
1239                 self.movedir = '0 1 0';
1240
1241         if (self.angles_y==0) self.angles_y = 90;
1242
1243         self.movedir = self.movedir * self.angles_y;
1244         self.angles = '0 0 0';
1245
1246         self.max_health = self.health;
1247         InitMovingBrushTrigger();
1248         //self.effects |= EF_LOWPRECISION;
1249         self.classname = "door_rotating";
1250
1251         self.blocked = door_blocked;
1252         self.use = door_use;
1253
1254     if(self.spawnflags & 8)
1255         self.dmg = 10000;
1256
1257     if(self.dmg && (!self.message))
1258                 self.message = "was squished";
1259     if(self.dmg && (!self.message2))
1260                 self.message2 = "was squished by";
1261
1262     if (self.sounds > 0)
1263         {
1264                 precache_sound ("plats/medplat1.wav");
1265                 precache_sound ("plats/medplat2.wav");
1266                 self.noise2 = "plats/medplat1.wav";
1267                 self.noise1 = "plats/medplat2.wav";
1268         }
1269
1270         if (!self.speed)
1271                 self.speed = 50;
1272         if (!self.wait)
1273                 self.wait = 1;
1274
1275         self.pos1 = '0 0 0';
1276         self.pos2 = self.movedir;
1277
1278 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1279 // but spawn in the open position
1280         if (self.spawnflags & DOOR_START_OPEN)
1281                 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1282
1283         self.state = STATE_BOTTOM;
1284
1285         if (self.health)
1286         {
1287                 self.takedamage = DAMAGE_YES;
1288                 self.event_damage = door_damage;
1289         }
1290
1291         if (self.items)
1292                 self.wait = -1;
1293
1294         self.touch = door_touch;
1295
1296 // LinkDoors can't be done until all of the doors have been spawned, so
1297 // the sizes can be detected properly.
1298         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1299 };
1300
1301 /*
1302 =============================================================================
1303
1304 SECRET DOORS
1305
1306 =============================================================================
1307 */
1308
1309 void() fd_secret_move1;
1310 void() fd_secret_move2;
1311 void() fd_secret_move3;
1312 void() fd_secret_move4;
1313 void() fd_secret_move5;
1314 void() fd_secret_move6;
1315 void() fd_secret_done;
1316
1317 float SECRET_OPEN_ONCE = 1;             // stays open
1318 float SECRET_1ST_LEFT = 2;              // 1st move is left of arrow
1319 float SECRET_1ST_DOWN = 4;              // 1st move is down from arrow
1320 float SECRET_NO_SHOOT = 8;              // only opened by trigger
1321 float SECRET_YES_SHOOT = 16;    // shootable even if targeted
1322
1323
1324 void fd_secret_use()
1325 {
1326         local float temp;
1327         string message_save;
1328
1329         self.health = 10000;
1330         self.bot_attack = TRUE;
1331
1332         // exit if still moving around...
1333         if (self.origin != self.oldorigin)
1334                 return;
1335
1336         message_save = self.message;
1337         self.message = ""; // no more message
1338         SUB_UseTargets();                               // fire all targets / killtargets
1339         self.message = message_save;
1340
1341         self.velocity = '0 0 0';
1342
1343         // Make a sound, wait a little...
1344
1345         if (self.noise1 != "")
1346                 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1347         self.nextthink = self.ltime + 0.1;
1348
1349         temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1350         makevectors(self.mangle);
1351
1352         if (!self.t_width)
1353         {
1354                 if (self.spawnflags & SECRET_1ST_DOWN)
1355                         self.t_width = fabs(v_up * self.size);
1356                 else
1357                         self.t_width = fabs(v_right * self.size);
1358         }
1359
1360         if (!self.t_length)
1361                 self.t_length = fabs(v_forward * self.size);
1362
1363         if (self.spawnflags & SECRET_1ST_DOWN)
1364                 self.dest1 = self.origin - v_up * self.t_width;
1365         else
1366                 self.dest1 = self.origin + v_right * (self.t_width * temp);
1367
1368         self.dest2 = self.dest1 + v_forward * self.t_length;
1369         SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1370         if (self.noise2 != "")
1371                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1372 };
1373
1374 // Wait after first movement...
1375 void fd_secret_move1()
1376 {
1377         self.nextthink = self.ltime + 1.0;
1378         self.think = fd_secret_move2;
1379         if (self.noise3 != "")
1380                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1381 };
1382
1383 // Start moving sideways w/sound...
1384 void fd_secret_move2()
1385 {
1386         if (self.noise2 != "")
1387                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1388         SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1389 };
1390
1391 // Wait here until time to go back...
1392 void fd_secret_move3()
1393 {
1394         if (self.noise3 != "")
1395                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1396         if (!(self.spawnflags & SECRET_OPEN_ONCE))
1397         {
1398                 self.nextthink = self.ltime + self.wait;
1399                 self.think = fd_secret_move4;
1400         }
1401 };
1402
1403 // Move backward...
1404 void fd_secret_move4()
1405 {
1406         if (self.noise2 != "")
1407                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1408         SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1409 };
1410
1411 // Wait 1 second...
1412 void fd_secret_move5()
1413 {
1414         self.nextthink = self.ltime + 1.0;
1415         self.think = fd_secret_move6;
1416         if (self.noise3 != "")
1417                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1418 };
1419
1420 void fd_secret_move6()
1421 {
1422         if (self.noise2 != "")
1423                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1424         SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1425 };
1426
1427 void fd_secret_done()
1428 {
1429         if (self.spawnflags&SECRET_YES_SHOOT)
1430         {
1431                 self.health = 10000;
1432                 self.takedamage = DAMAGE_YES;
1433                 //self.th_pain = fd_secret_use;
1434         }
1435         if (self.noise3 != "")
1436                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1437 };
1438
1439 void secret_blocked()
1440 {
1441         if (time < self.attack_finished_single)
1442                 return;
1443         self.attack_finished_single = time + 0.5;
1444         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1445 };
1446
1447 /*
1448 ==============
1449 secret_touch
1450
1451 Prints messages
1452 ================
1453 */
1454 void secret_touch()
1455 {
1456         if not(other.iscreature)
1457                 return;
1458         if (self.attack_finished_single > time)
1459                 return;
1460
1461         self.attack_finished_single = time + 2;
1462
1463         if (self.message)
1464         {
1465                 if (other.flags & FL_CLIENT)
1466                         centerprint (other, self.message);
1467                 play2(other, "misc/talk.wav");
1468         }
1469 };
1470
1471
1472 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1473 Basic secret door. Slides back, then to the side. Angle determines direction.
1474 wait  = # of seconds before coming back
1475 1st_left = 1st move is left of arrow
1476 1st_down = 1st move is down from arrow
1477 always_shoot = even if targeted, keep shootable
1478 t_width = override WIDTH to move back (or height if going down)
1479 t_length = override LENGTH to move sideways
1480 "dmg"           damage to inflict when blocked (2 default)
1481
1482 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1483 "sounds"
1484 1) medieval
1485 2) metal
1486 3) base
1487 */
1488
1489 void spawnfunc_func_door_secret()
1490 {
1491         /*if (!self.deathtype) // map makers can override this
1492                 self.deathtype = " got in the way";*/
1493
1494         if (!self.dmg)
1495                 self.dmg = 2;
1496
1497         // Magic formula...
1498         self.mangle = self.angles;
1499         self.angles = '0 0 0';
1500         self.classname = "door";
1501         InitMovingBrushTrigger();
1502         self.effects |= EF_LOWPRECISION;
1503
1504         self.touch = secret_touch;
1505         self.blocked = secret_blocked;
1506         self.speed = 50;
1507         self.use = fd_secret_use;
1508         IFTARGETED
1509         {
1510         }
1511         else
1512                 self.spawnflags |= SECRET_YES_SHOOT;
1513
1514         if(self.spawnflags&SECRET_YES_SHOOT)
1515         {
1516                 self.health = 10000;
1517                 self.takedamage = DAMAGE_YES;
1518                 self.event_damage = fd_secret_use;
1519         }
1520         self.oldorigin = self.origin;
1521         if (!self.wait)
1522                 self.wait = 5;          // 5 seconds before closing
1523 };
1524