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