func_door_rotating update 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;         // 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         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;         // already 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                         // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
781                         if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM) 
782                         { 
783                                 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
784                                 self.pos2 = '0 0 0' - self.pos2; 
785                         }
786                         // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
787                         if (!((self.spawnflags & 2) &&  (self.spawnflags & 8) && self.state == STATE_DOWN
788                             && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
789                         {
790                                 door_rotating_go_up ();
791                         }
792                 }
793                 self = self.enemy;
794         } while ( (self != starte) && (self != world) );
795         self = oself;
796 };
797
798
799 void door_use()
800 {
801         local entity oself;
802
803         //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
804         if (self.owner)
805         {
806                 oself = self;
807                 self = self.owner;
808                 door_fire ();
809                 self = oself;
810         }
811 };
812
813
814 void door_trigger_touch()
815 {
816         if (other.health < 1)
817         if not(other.iscreature && other.deadflag == DEAD_NO)
818                 return;
819
820         if (time < self.attack_finished_single)
821                 return;
822         self.attack_finished_single = time + 1;
823
824         activator = other;
825
826         self = self.owner;
827         door_use ();
828 };
829
830
831 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
832 {
833         local entity oself;
834         self.health = self.health - damage;
835         if (self.health <= 0)
836         {
837                 oself = self;
838                 self = self.owner;
839                 self.health = self.max_health;
840                 self.takedamage = DAMAGE_NO;    // wil be reset upon return
841                 door_use ();
842                 self = oself;
843         }
844 };
845
846
847 /*
848 ================
849 door_touch
850
851 Prints messages
852 ================
853 */
854 void door_touch()
855 {
856         if(other.classname != "player")
857                 return;
858         if (self.owner.attack_finished_single > time)
859                 return;
860
861         self.owner.attack_finished_single = time + 2;
862
863         if (!(self.owner.dmg) && (self.owner.message != ""))
864         {
865                 if (other.flags & FL_CLIENT)
866                         centerprint (other, self.owner.message);
867                 play2(other, "misc/talk.wav");
868         }
869 };
870
871
872 void door_rotating_blocked()
873 {
874
875     if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
876         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
877     } else {
878
879         if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?
880             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
881
882          //Dont chamge direction for dead or dying stuff
883         if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
884             if (self.wait >= 0)
885             {
886                 if (self.state == STATE_DOWN)
887                     door_rotating_go_up ();
888                 else
889                     door_rotating_go_down ();
890             }
891         } else {
892             //gib dying stuff just to make sure
893             if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?
894                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
895         }
896     }
897
898         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
899 // if a door has a negative wait, it would never come back if blocked,
900 // so let it just squash the object to death real fast
901 /*      if (self.wait >= 0)
902         {
903                 if (self.state == STATE_DOWN)
904                         door_rotating_go_up ();
905                 else
906                         door_rotating_go_down ();
907         }
908 */
909 };
910
911
912 void door_rotating_hit_top()
913 {
914         if (self.noise1 != "")
915                 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
916         self.state = STATE_TOP;
917         if (self.spawnflags & DOOR_TOGGLE)
918                 return;         // don't come down automatically
919         self.think = door_rotating_go_down;
920         self.nextthink = self.ltime + self.wait;
921 };
922
923 void door_rotating_hit_bottom()
924 {
925         if (self.noise1 != "")
926                 sound (self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
927         if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
928         { 
929                 self.pos2 = '0 0 0' - self.pos2; 
930                 self.lip = 0;
931         }
932         self.state = STATE_BOTTOM;
933 };
934
935 void door_rotating_go_down()
936 {
937         if (self.noise2 != "")
938                 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
939         if (self.max_health)
940         {
941                 self.takedamage = DAMAGE_YES;
942                 self.health = self.max_health;
943         }
944
945         self.state = STATE_DOWN;
946         SUB_CalcAngleMove (self.pos1, self.speed, door_rotating_hit_bottom);
947 };
948
949 void door_rotating_go_up()
950 {
951         if (self.state == STATE_UP)
952                 return;         // already going up
953
954         if (self.state == STATE_TOP)
955         {       // reset top wait time
956                 self.nextthink = self.ltime + self.wait;
957                 return;
958         }
959         if (self.noise2 != "")
960                 sound (self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
961         self.state = STATE_UP;
962         SUB_CalcAngleMove (self.pos2, self.speed, door_rotating_hit_top);
963
964         string oldmessage;
965         oldmessage = self.message;
966         self.message = "";
967         SUB_UseTargets();
968         self.message = oldmessage;
969 };
970
971
972
973
974 /*
975 =============================================================================
976
977 SPAWNING FUNCTIONS
978
979 =============================================================================
980 */
981
982
983 entity spawn_field(vector fmins, vector fmaxs)
984 {
985         local entity    trigger;
986         local   vector  t1, t2;
987
988         trigger = spawn();
989         trigger.classname = "doortriggerfield";
990         trigger.movetype = MOVETYPE_NONE;
991         trigger.solid = SOLID_TRIGGER;
992         trigger.owner = self;
993         trigger.touch = door_trigger_touch;
994
995         t1 = fmins;
996         t2 = fmaxs;
997         setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
998         return (trigger);
999 };
1000
1001
1002 float EntitiesTouching(entity e1, entity e2)
1003 {
1004         if (e1.absmin_x > e2.absmax_x)
1005                 return FALSE;
1006         if (e1.absmin_y > e2.absmax_y)
1007                 return FALSE;
1008         if (e1.absmin_z > e2.absmax_z)
1009                 return FALSE;
1010         if (e1.absmax_x < e2.absmin_x)
1011                 return FALSE;
1012         if (e1.absmax_y < e2.absmin_y)
1013                 return FALSE;
1014         if (e1.absmax_z < e2.absmin_z)
1015                 return FALSE;
1016         return TRUE;
1017 };
1018
1019
1020 /*
1021 =============
1022 LinkDoors
1023
1024
1025 =============
1026 */
1027 void LinkDoors()
1028 {
1029         local entity    t, starte;
1030         local vector    cmins, cmaxs;
1031
1032         if (self.enemy)
1033                 return;         // already linked by another door
1034         if (self.spawnflags & 4)
1035         {
1036                 self.owner = self.enemy = self;
1037
1038                 if (self.health)
1039                         return;
1040                 IFTARGETED
1041                         return;
1042                 if (self.items)
1043                         return;
1044                 self.trigger_field = spawn_field(self.absmin, self.absmax);
1045
1046                 return;         // don't want to link this door
1047         }
1048
1049         cmins = self.absmin;                      
1050         cmaxs = self.absmax;
1051
1052         starte = self;
1053         t = self;
1054
1055         do
1056         {
1057                 self.owner = starte;                    // master door
1058
1059                 if (self.health)
1060                         starte.health = self.health;
1061                 IFTARGETED
1062                         starte.targetname = self.targetname;
1063                 if (self.message != "")
1064                         starte.message = self.message;
1065
1066                 t = find(t, classname, self.classname);
1067                 if (!t)
1068                 {
1069                         self.enemy = starte;            // make the chain a loop
1070
1071                 // shootable, or triggered doors just needed the owner/enemy links,
1072                 // they don't spawn a field
1073
1074                         self = self.owner;
1075
1076                         if (self.health)
1077                                 return;
1078                         IFTARGETED
1079                                 return;
1080                         if (self.items)
1081                                 return;
1082
1083                         self.owner.trigger_field = spawn_field(cmins, cmaxs);
1084
1085                         return;
1086                 }
1087
1088                 if (EntitiesTouching(self,t))
1089                 {
1090                         if (t.enemy)
1091                                 objerror ("cross connected doors");
1092
1093                         self.enemy = t;
1094                         self = t;
1095
1096                         if (t.absmin_x < cmins_x)
1097                                 cmins_x = t.absmin_x;
1098                         if (t.absmin_y < cmins_y)
1099                                 cmins_y = t.absmin_y;
1100                         if (t.absmin_z < cmins_z)
1101                                 cmins_z = t.absmin_z;
1102                         if (t.absmax_x > cmaxs_x)
1103                                 cmaxs_x = t.absmax_x;
1104                         if (t.absmax_y > cmaxs_y)
1105                                 cmaxs_y = t.absmax_y;
1106                         if (t.absmax_z > cmaxs_z)
1107                                 cmaxs_z = t.absmax_z;
1108                 }
1109         } while (1 );
1110
1111 };
1112
1113
1114 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK x x TOGGLE
1115 if two doors touch, they are assumed to be connected and operate as a unit.
1116
1117 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1118
1119 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).
1120
1121 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1122 "angle"         determines the opening direction
1123 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1124 "health"        if set, door must be shot open
1125 "speed"         movement speed (100 default)
1126 "wait"          wait before returning (3 default, -1 = never return)
1127 "lip"           lip remaining at end of move (8 default)
1128 "dmg"           damage to inflict when blocked (2 default)
1129 "sounds"
1130 0)      no sound
1131 1)      stone
1132 2)      base
1133 3)      stone chain
1134 4)      screechy metal
1135 FIXME: only one sound set available at the time being
1136
1137 */
1138
1139 void door_init_startopen()
1140 {
1141         setorigin (self, self.pos2);
1142         self.pos2 = self.pos1;
1143         self.pos1 = self.origin;
1144 }
1145
1146 void spawnfunc_func_door()
1147 {
1148         //if (!self.deathtype) // map makers can override this
1149         //      self.deathtype = " got in the way";
1150         SetMovedir ();
1151
1152         self.max_health = self.health;
1153         InitMovingBrushTrigger();
1154         self.effects |= EF_LOWPRECISION;
1155         self.classname = "door";
1156
1157         self.blocked = door_blocked;
1158         self.use = door_use;
1159
1160     if(self.spawnflags & 8)
1161         self.dmg = 10000;
1162
1163     if(self.dmg && (!self.message))
1164                 self.message = "was squished";
1165     if(self.dmg && (!self.message2))
1166                 self.message2 = "was squished by";
1167
1168         if (self.sounds > 0)
1169         {
1170                 precache_sound ("plats/medplat1.wav");
1171                 precache_sound ("plats/medplat2.wav");
1172                 self.noise2 = "plats/medplat1.wav";
1173                 self.noise1 = "plats/medplat2.wav";
1174         }
1175
1176         if (!self.speed)
1177                 self.speed = 100;
1178         if (!self.wait)
1179                 self.wait = 3;
1180         if (!self.lip)
1181                 self.lip = 8;
1182
1183         self.pos1 = self.origin;
1184         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1185
1186 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1187 // but spawn in the open position
1188         if (self.spawnflags & DOOR_START_OPEN)
1189                 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1190
1191         self.state = STATE_BOTTOM;
1192
1193         if (self.health)
1194         {
1195                 self.takedamage = DAMAGE_YES;
1196                 self.event_damage = door_damage;
1197         }
1198
1199         if (self.items)
1200                 self.wait = -1;
1201
1202         self.touch = door_touch;
1203
1204 // LinkDoors can't be done until all of the doors have been spawned, so
1205 // the sizes can be detected properly.
1206         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1207 };
1208
1209 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1210 if two doors touch, they are assumed to be connected and operate as a unit.
1211
1212 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1213
1214 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1215 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1216 must have set trigger_reverse to 1.
1217 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1218
1219 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).
1220
1221 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1222 "angle"         determines the destination angle for opening. negative values reverse the direction.
1223 "targetname"    if set, no touch field will be spawned and a remote button or trigger field activates the door.
1224 "health"        if set, door must be shot open
1225 "speed"         movement speed (100 default)
1226 "wait"          wait before returning (3 default, -1 = never return)
1227 "dmg"           damage to inflict when blocked (2 default)
1228 "sounds"
1229 0)      no sound
1230 1)      stone
1231 2)      base
1232 3)      stone chain
1233 4)      screechy metal
1234 FIXME: only one sound set available at the time being
1235 */
1236
1237 void door_rotating_init_startopen()
1238 {
1239         self.angles = self.movedir;
1240         self.pos2 = '0 0 0';
1241         self.pos1 = self.movedir;
1242 }
1243
1244
1245 void spawnfunc_func_door_rotating()
1246 {
1247         
1248         //if (!self.deathtype) // map makers can override this
1249         //      self.deathtype = " got in the way";
1250
1251         // I abuse "movedir" for denoting the axis for now
1252         if (self.spawnflags & 64) // X (untested)
1253                 self.movedir = '0 0 1';
1254         else if (self.spawnflags & 128) // Y (untested)
1255                 self.movedir = '1 0 0';
1256         else // Z
1257                 self.movedir = '0 1 0';
1258
1259         if (self.angles_y==0) self.angles_y = 90;
1260
1261         self.movedir = self.movedir * self.angles_y;
1262         self.angles = '0 0 0';
1263
1264         self.max_health = self.health;
1265         InitMovingBrushTrigger();
1266         //self.effects |= EF_LOWPRECISION;
1267         self.classname = "door_rotating";
1268
1269         self.blocked = door_blocked;
1270         self.use = door_use;
1271
1272     if(self.spawnflags & 8)
1273         self.dmg = 10000;
1274
1275     if(self.dmg && (!self.message))
1276                 self.message = "was squished";
1277     if(self.dmg && (!self.message2))
1278                 self.message2 = "was squished by";
1279
1280     if (self.sounds > 0)
1281         {
1282                 precache_sound ("plats/medplat1.wav");
1283                 precache_sound ("plats/medplat2.wav");
1284                 self.noise2 = "plats/medplat1.wav";
1285                 self.noise1 = "plats/medplat2.wav";
1286         }
1287
1288         if (!self.speed)
1289                 self.speed = 50;
1290         if (!self.wait)
1291                 self.wait = 1;
1292         self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1293
1294         self.pos1 = '0 0 0';
1295         self.pos2 = self.movedir;
1296
1297 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1298 // but spawn in the open position
1299         if (self.spawnflags & DOOR_START_OPEN)
1300                 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1301
1302         self.state = STATE_BOTTOM;
1303
1304         if (self.health)
1305         {
1306                 self.takedamage = DAMAGE_YES;
1307                 self.event_damage = door_damage;
1308         }
1309
1310         if (self.items)
1311                 self.wait = -1;
1312
1313         self.touch = door_touch;
1314
1315 // LinkDoors can't be done until all of the doors have been spawned, so
1316 // the sizes can be detected properly.
1317         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1318 };
1319
1320 /*
1321 =============================================================================
1322
1323 SECRET DOORS
1324
1325 =============================================================================
1326 */
1327
1328 void() fd_secret_move1;
1329 void() fd_secret_move2;
1330 void() fd_secret_move3;
1331 void() fd_secret_move4;
1332 void() fd_secret_move5;
1333 void() fd_secret_move6;
1334 void() fd_secret_done;
1335
1336 float SECRET_OPEN_ONCE = 1;             // stays open
1337 float SECRET_1ST_LEFT = 2;              // 1st move is left of arrow
1338 float SECRET_1ST_DOWN = 4;              // 1st move is down from arrow
1339 float SECRET_NO_SHOOT = 8;              // only opened by trigger
1340 float SECRET_YES_SHOOT = 16;    // shootable even if targeted
1341
1342
1343 void fd_secret_use()
1344 {
1345         local float temp;
1346         string message_save;
1347
1348         self.health = 10000;
1349         self.bot_attack = TRUE;
1350
1351         // exit if still moving around...
1352         if (self.origin != self.oldorigin)
1353                 return;
1354
1355         message_save = self.message;
1356         self.message = ""; // no more message
1357         SUB_UseTargets();                               // fire all targets / killtargets
1358         self.message = message_save;
1359
1360         self.velocity = '0 0 0';
1361
1362         // Make a sound, wait a little...
1363
1364         if (self.noise1 != "")
1365                 sound(self, CHAN_TRIGGER, self.noise1, VOL_BASE, ATTN_NORM);
1366         self.nextthink = self.ltime + 0.1;
1367
1368         temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1369         makevectors(self.mangle);
1370
1371         if (!self.t_width)
1372         {
1373                 if (self.spawnflags & SECRET_1ST_DOWN)
1374                         self.t_width = fabs(v_up * self.size);
1375                 else
1376                         self.t_width = fabs(v_right * self.size);
1377         }
1378
1379         if (!self.t_length)
1380                 self.t_length = fabs(v_forward * self.size);
1381
1382         if (self.spawnflags & SECRET_1ST_DOWN)
1383                 self.dest1 = self.origin - v_up * self.t_width;
1384         else
1385                 self.dest1 = self.origin + v_right * (self.t_width * temp);
1386
1387         self.dest2 = self.dest1 + v_forward * self.t_length;
1388         SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1389         if (self.noise2 != "")
1390                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1391 };
1392
1393 // Wait after first movement...
1394 void fd_secret_move1()
1395 {
1396         self.nextthink = self.ltime + 1.0;
1397         self.think = fd_secret_move2;
1398         if (self.noise3 != "")
1399                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1400 };
1401
1402 // Start moving sideways w/sound...
1403 void fd_secret_move2()
1404 {
1405         if (self.noise2 != "")
1406                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1407         SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1408 };
1409
1410 // Wait here until time to go back...
1411 void fd_secret_move3()
1412 {
1413         if (self.noise3 != "")
1414                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1415         if (!(self.spawnflags & SECRET_OPEN_ONCE))
1416         {
1417                 self.nextthink = self.ltime + self.wait;
1418                 self.think = fd_secret_move4;
1419         }
1420 };
1421
1422 // Move backward...
1423 void fd_secret_move4()
1424 {
1425         if (self.noise2 != "")
1426                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1427         SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1428 };
1429
1430 // Wait 1 second...
1431 void fd_secret_move5()
1432 {
1433         self.nextthink = self.ltime + 1.0;
1434         self.think = fd_secret_move6;
1435         if (self.noise3 != "")
1436                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1437 };
1438
1439 void fd_secret_move6()
1440 {
1441         if (self.noise2 != "")
1442                 sound(self, CHAN_TRIGGER, self.noise2, VOL_BASE, ATTN_NORM);
1443         SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1444 };
1445
1446 void fd_secret_done()
1447 {
1448         if (self.spawnflags&SECRET_YES_SHOOT)
1449         {
1450                 self.health = 10000;
1451                 self.takedamage = DAMAGE_YES;
1452                 //self.th_pain = fd_secret_use;
1453         }
1454         if (self.noise3 != "")
1455                 sound(self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NORM);
1456 };
1457
1458 void secret_blocked()
1459 {
1460         if (time < self.attack_finished_single)
1461                 return;
1462         self.attack_finished_single = time + 0.5;
1463         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1464 };
1465
1466 /*
1467 ==============
1468 secret_touch
1469
1470 Prints messages
1471 ================
1472 */
1473 void secret_touch()
1474 {
1475         if not(other.iscreature)
1476                 return;
1477         if (self.attack_finished_single > time)
1478                 return;
1479
1480         self.attack_finished_single = time + 2;
1481
1482         if (self.message)
1483         {
1484                 if (other.flags & FL_CLIENT)
1485                         centerprint (other, self.message);
1486                 play2(other, "misc/talk.wav");
1487         }
1488 };
1489
1490
1491 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1492 Basic secret door. Slides back, then to the side. Angle determines direction.
1493 wait  = # of seconds before coming back
1494 1st_left = 1st move is left of arrow
1495 1st_down = 1st move is down from arrow
1496 always_shoot = even if targeted, keep shootable
1497 t_width = override WIDTH to move back (or height if going down)
1498 t_length = override LENGTH to move sideways
1499 "dmg"           damage to inflict when blocked (2 default)
1500
1501 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1502 "sounds"
1503 1) medieval
1504 2) metal
1505 3) base
1506 */
1507
1508 void spawnfunc_func_door_secret()
1509 {
1510         /*if (!self.deathtype) // map makers can override this
1511                 self.deathtype = " got in the way";*/
1512
1513         if (!self.dmg)
1514                 self.dmg = 2;
1515
1516         // Magic formula...
1517         self.mangle = self.angles;
1518         self.angles = '0 0 0';
1519         self.classname = "door";
1520         InitMovingBrushTrigger();
1521         self.effects |= EF_LOWPRECISION;
1522
1523         self.touch = secret_touch;
1524         self.blocked = secret_blocked;
1525         self.speed = 50;
1526         self.use = fd_secret_use;
1527         IFTARGETED
1528         {
1529         }
1530         else
1531                 self.spawnflags |= SECRET_YES_SHOOT;
1532
1533         if(self.spawnflags&SECRET_YES_SHOOT)
1534         {
1535                 self.health = 10000;
1536                 self.takedamage = DAMAGE_YES;
1537                 self.event_damage = fd_secret_use;
1538         }
1539         self.oldorigin = self.origin;
1540         if (!self.wait)
1541                 self.wait = 5;          // 5 seconds before closing
1542 };
1543