]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/t_plats.qc
ae4f035e10c44e7c20cd0d59d5dcc2b9b64e999f
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / t_plats.qc
1 .float dmgtime2;
2 void generic_plat_blocked()
3 {
4     if(self.dmg && other.takedamage != DAMAGE_NO) {
5         if(self.dmgtime2 < time) {
6             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
7             self.dmgtime2 = time + self.dmgtime;
8         }
9
10         // Gib dead/dying stuff
11         if(other.deadflag != DEAD_NO)
12             Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
13     }
14 }
15
16
17 float   STATE_TOP               = 0;
18 float   STATE_BOTTOM    = 1;
19 float   STATE_UP                = 2;
20 float   STATE_DOWN              = 3;
21
22 .entity trigger_field;
23
24 void() plat_center_touch;
25 void() plat_outside_touch;
26 void() plat_trigger_use;
27 void() plat_go_up;
28 void() plat_go_down;
29 void() plat_crush;
30 float PLAT_LOW_TRIGGER = 1;
31
32 void plat_spawn_inside_trigger()
33 {
34         entity trigger;
35         vector tmin, tmax;
36
37         trigger = spawn();
38         trigger.touch = plat_center_touch;
39         trigger.movetype = MOVETYPE_NONE;
40         trigger.solid = SOLID_TRIGGER;
41         trigger.enemy = self;
42
43         tmin = self.absmin + '25 25 0';
44         tmax = self.absmax - '25 25 -8';
45         tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8);
46         if (self.spawnflags & PLAT_LOW_TRIGGER)
47                 tmax_z = tmin_z + 8;
48
49         if (self.size_x <= 50)
50         {
51                 tmin_x = (self.mins_x + self.maxs_x) / 2;
52                 tmax_x = tmin_x + 1;
53         }
54         if (self.size_y <= 50)
55         {
56                 tmin_y = (self.mins_y + self.maxs_y) / 2;
57                 tmax_y = tmin_y + 1;
58         }
59
60         if(tmin_x < tmax_x)
61                 if(tmin_y < tmax_y)
62                         if(tmin_z < tmax_z)
63                         {
64                                 setsize (trigger, tmin, tmax);
65                                 return;
66                         }
67
68         // otherwise, something is fishy...
69         remove(trigger);
70         objerror("plat_spawn_inside_trigger: platform has odd size or lip, can't spawn");
71 }
72
73 void plat_hit_top()
74 {
75         sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
76         self.state = 1;
77         self.think = plat_go_down;
78         self.nextthink = self.ltime + 3;
79 }
80
81 void plat_hit_bottom()
82 {
83         sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
84         self.state = 2;
85 }
86
87 void plat_go_down()
88 {
89         sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_NORM);
90         self.state = 3;
91         SUB_CalcMove (self.pos2, self.speed, plat_hit_bottom);
92 }
93
94 void plat_go_up()
95 {
96         sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_NORM);
97         self.state = 4;
98         SUB_CalcMove (self.pos1, self.speed, plat_hit_top);
99 }
100
101 void plat_center_touch()
102 {
103         if not(other.iscreature)
104                 return;
105
106         if (other.health <= 0)
107                 return;
108
109         self = self.enemy;
110         if (self.state == 2)
111                 plat_go_up ();
112         else if (self.state == 1)
113                 self.nextthink = self.ltime + 1;        // delay going down
114 }
115
116 void plat_outside_touch()
117 {
118         if not(other.iscreature)
119                 return;
120
121         if (other.health <= 0)
122                 return;
123
124         self = self.enemy;
125         if (self.state == 1)
126                 plat_go_down ();
127 }
128
129 void plat_trigger_use()
130 {
131         if (self.think)
132                 return;         // already activated
133         plat_go_down();
134 }
135
136
137 void plat_crush()
138 {
139     if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
140         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
141     } else {
142         if((self.dmg) && (other.takedamage != DAMAGE_NO)) {   // Shall we bite?
143             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
144             // Gib dead/dying stuff
145             if(other.deadflag != DEAD_NO)
146                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
147         }
148
149         if (self.state == 4)
150             plat_go_down ();
151         else if (self.state == 3)
152             plat_go_up ();
153         // when in other states, then the plat_crush event came delayed after
154         // plat state already had changed
155         // this isn't a bug per se!
156     }
157 }
158
159 void plat_use()
160 {
161         self.use = SUB_Null;
162         if (self.state != 4)
163                 objerror ("plat_use: not in up state");
164         plat_go_down();
165 }
166
167 .string sound1, sound2;
168
169 void plat_reset()
170 {
171         IFTARGETED
172         {
173                 setorigin (self, self.pos1);
174                 self.state = 4;
175                 self.use = plat_use;
176         }
177         else
178         {
179                 setorigin (self, self.pos2);
180                 self.state = 2;
181                 self.use = plat_trigger_use;
182         }
183 }
184
185 void spawnfunc_path_corner() { }
186 void spawnfunc_func_plat()
187 {
188         if (self.sounds == 0)
189                 self.sounds = 2;
190
191     if(self.spawnflags & 4)
192         self.dmg = 10000;
193
194     if(self.dmg && (!self.message))
195                 self.message = "was squished";
196     if(self.dmg && (!self.message2))
197                 self.message2 = "was squished by";
198
199         if (self.sounds == 1)
200         {
201                 precache_sound ("plats/plat1.wav");
202                 precache_sound ("plats/plat2.wav");
203                 self.noise = "plats/plat1.wav";
204                 self.noise1 = "plats/plat2.wav";
205         }
206
207         if (self.sounds == 2)
208         {
209                 precache_sound ("plats/medplat1.wav");
210                 precache_sound ("plats/medplat2.wav");
211                 self.noise = "plats/medplat1.wav";
212                 self.noise1 = "plats/medplat2.wav";
213         }
214
215         if (self.sound1)
216         {
217                 precache_sound (self.sound1);
218                 self.noise = self.sound1;
219         }
220         if (self.sound2)
221         {
222                 precache_sound (self.sound2);
223                 self.noise1 = self.sound2;
224         }
225
226         self.mangle = self.angles;
227         self.angles = '0 0 0';
228
229         self.classname = "plat";
230         if not(InitMovingBrushTrigger())
231                 return;
232         self.effects |= EF_LOWPRECISION;
233         setsize (self, self.mins , self.maxs);
234
235         self.blocked = plat_crush;
236
237         if (!self.speed)
238                 self.speed = 150;
239         if (!self.lip)
240                 self.lip = 16;
241         if (!self.height)
242                 self.height = self.size_z - self.lip;
243
244         self.pos1 = self.origin;
245         self.pos2 = self.origin;
246         self.pos2_z = self.origin_z - self.height;
247
248         self.reset = plat_reset;
249         plat_reset();
250
251         plat_spawn_inside_trigger ();   // the "start moving" trigger
252 }
253
254
255 void() train_next;
256 void train_wait()
257 {
258         if(self.noise != "")
259                 stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
260
261         if(self.wait < 0)
262         {
263                 train_next();
264         }
265         else
266         {
267                 self.think = train_next;
268                 self.nextthink = self.ltime + self.wait;
269         }
270
271         entity oldself;
272         oldself = self;
273         self = self.enemy;
274         SUB_UseTargets();
275         self = oldself;
276         self.enemy = world;
277 }
278
279 void train_next()
280 {
281         entity targ, cp;
282         targ = find(world, targetname, self.target);
283         self.target = targ.target;
284         if (self.spawnflags & 1)
285         {
286                 cp = find(world, target, targ.targetname); // get the previous corner first
287                 cp = find(world, targetname, cp.target2); // now get its second target
288                 if(cp.targetname == "") // none found
289                 {
290                         // when using bezier curves, you must have a control point for each corner in the train's path
291                         if(autocvar_developer)
292                                 dprint("Warning: func_train using beizer curves reached a path_corner without a control point. Please add a target2 for each path_corner used by this train!\n");
293                         cp = targ; // assume a straight line to the destination as fallback
294                 }
295         }
296         if (!self.target)
297                 objerror("train_next: no next target");
298         self.wait = targ.wait;
299         if (!self.wait)
300                 self.wait = 0.1;
301
302         if (targ.speed)
303         {
304                 if (self.spawnflags & 1)
305                         SUB_CalcMove_Bezier(cp.origin, targ.origin - self.mins, targ.speed, train_wait);
306                 else
307                         SUB_CalcMove(targ.origin - self.mins, targ.speed, train_wait);
308         }
309         else
310         {
311                 if (self.spawnflags & 1)
312                         SUB_CalcMove_Bezier(cp.origin, targ.origin - self.mins, self.speed, train_wait);
313                 else
314                         SUB_CalcMove(targ.origin - self.mins, self.speed, train_wait);
315         }
316
317         if(self.noise != "")
318                 sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
319 }
320
321 void func_train_find()
322 {
323         entity targ;
324         targ = find(world, targetname, self.target);
325         self.target = targ.target;
326         if (!self.target)
327                 objerror("func_train_find: no next target");
328         setorigin(self, targ.origin - self.mins);
329         self.nextthink = self.ltime + 1;
330         self.think = train_next;
331 }
332
333 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
334 Ridable platform, targets spawnfunc_path_corner path to follow.
335 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
336 target : targetname of first spawnfunc_path_corner (starts here)
337 */
338 void spawnfunc_func_train()
339 {
340         if (self.noise != "")
341                 precache_sound(self.noise);
342
343         if (!self.target)
344                 objerror("func_train without a target");
345         if (!self.speed)
346                 self.speed = 100;
347
348         if not(InitMovingBrushTrigger())
349                 return;
350         self.effects |= EF_LOWPRECISION;
351
352         // wait for targets to spawn
353         InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
354
355         self.blocked = generic_plat_blocked;
356         if(self.dmg & (!self.message))
357                 self.message = " was squished";
358     if(self.dmg && (!self.message2))
359                 self.message2 = "was squished by";
360         if(self.dmg && (!self.dmgtime))
361                 self.dmgtime = 0.25;
362         self.dmgtime2 = time;
363
364         // TODO make a reset function for this one
365 }
366
367 void func_rotating_setactive(float astate)
368 {
369         
370         if (astate == ACTIVE_TOGGLE)
371         {               
372                 if(self.active == ACTIVE_ACTIVE)
373                         self.active = ACTIVE_NOT;
374                 else
375                         self.active = ACTIVE_ACTIVE;
376         }
377         else
378                 self.active = astate;
379                 
380         if(self.active  == ACTIVE_NOT)          
381                 self.avelocity = '0 0 0';
382         else
383                 self.avelocity = self.pos1;
384 }
385
386 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
387 Brush model that spins in place on one axis (default Z).
388 speed   : speed to rotate (in degrees per second)
389 noise   : path/name of looping .wav file to play.
390 dmg     : Do this mutch dmg every .dmgtime intervall when blocked
391 dmgtime : See above.
392 */
393
394 void spawnfunc_func_rotating()
395 {
396         if (self.noise != "")
397         {
398                 precache_sound(self.noise);
399                 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
400         }
401         
402         self.active = ACTIVE_ACTIVE;
403         self.setactive = func_rotating_setactive;
404         
405         if (!self.speed)
406                 self.speed = 100;
407         // FIXME: test if this turns the right way, then remove this comment (negate as needed)
408         if (self.spawnflags & 4) // X (untested)
409                 self.avelocity = '0 0 1' * self.speed;
410         // FIXME: test if this turns the right way, then remove this comment (negate as needed)
411         else if (self.spawnflags & 8) // Y (untested)
412                 self.avelocity = '1 0 0' * self.speed;
413         // FIXME: test if this turns the right way, then remove this comment (negate as needed)
414         else // Z
415                 self.avelocity = '0 1 0' * self.speed;
416         
417         self.pos1 = self.avelocity;
418     
419     if(self.dmg & (!self.message))
420         self.message = " was squished";
421     if(self.dmg && (!self.message2))
422                 self.message2 = "was squished by";
423
424
425     if(self.dmg && (!self.dmgtime))
426         self.dmgtime = 0.25;
427
428     self.dmgtime2 = time;
429
430         if not(InitMovingBrushTrigger())
431                 return;
432         // no EF_LOWPRECISION here, as rounding angles is bad
433
434     self.blocked = generic_plat_blocked;
435
436         // wait for targets to spawn
437         self.nextthink = self.ltime + 999999999;
438         self.think = SUB_Null;
439
440         // TODO make a reset function for this one
441 }
442
443 .float height;
444 void func_bobbing_controller_think()
445 {
446         vector v;
447         self.nextthink = time + 0.1;
448         
449         if not (self.owner.active == ACTIVE_ACTIVE)
450         {
451                 self.owner.velocity = '0 0 0';          
452                 return;
453         }
454                 
455         // calculate sinewave using makevectors
456         makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
457         v = self.owner.destvec + self.owner.movedir * v_forward_y;
458         if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
459                 // * 10 so it will arrive in 0.1 sec
460                 self.owner.velocity = (v - self.owner.origin) * 10;
461 }
462
463 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
464 Brush model that moves back and forth on one axis (default Z).
465 speed : how long one cycle takes in seconds (default 4)
466 height : how far the cycle moves (default 32)
467 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
468 noise : path/name of looping .wav file to play.
469 dmg : Do this mutch dmg every .dmgtime intervall when blocked
470 dmgtime : See above.
471 */
472 void spawnfunc_func_bobbing()
473 {
474         entity controller;
475         if (self.noise != "")
476         {
477                 precache_sound(self.noise);
478                 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
479         }
480         if (!self.speed)
481                 self.speed = 4;
482         if (!self.height)
483                 self.height = 32;
484         // center of bobbing motion
485         self.destvec = self.origin;
486         // time scale to get degrees
487         self.cnt = 360 / self.speed;
488
489         self.active = ACTIVE_ACTIVE;
490
491         // damage when blocked
492         self.blocked = generic_plat_blocked;
493         if(self.dmg & (!self.message))
494                 self.message = " was squished";
495     if(self.dmg && (!self.message2))
496                 self.message2 = "was squished by";
497         if(self.dmg && (!self.dmgtime))
498                 self.dmgtime = 0.25;
499         self.dmgtime2 = time;
500
501         // how far to bob
502         if (self.spawnflags & 1) // X
503                 self.movedir = '1 0 0' * self.height;
504         else if (self.spawnflags & 2) // Y
505                 self.movedir = '0 1 0' * self.height;
506         else // Z
507                 self.movedir = '0 0 1' * self.height;
508
509         if not(InitMovingBrushTrigger())
510                 return;
511
512         // wait for targets to spawn
513         controller = spawn();
514         controller.classname = "func_bobbing_controller";
515         controller.owner = self;
516         controller.nextthink = time + 1;
517         controller.think = func_bobbing_controller_think;
518         self.nextthink = self.ltime + 999999999;
519         self.think = SUB_Null;
520
521         // Savage: Reduce bandwith, critical on e.g. nexdm02
522         self.effects |= EF_LOWPRECISION;
523
524         // TODO make a reset function for this one
525 }
526
527 .float freq;
528 void func_pendulum_controller_think()
529 {
530         float v;
531         self.nextthink = time + 0.1;
532
533         if not (self.owner.active == ACTIVE_ACTIVE)
534         {
535                 self.owner.avelocity_x = 0;
536                 return;
537         }
538
539         // calculate sinewave using makevectors
540         makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
541         v = self.owner.speed * v_forward_y + self.cnt;
542         if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
543         {
544                 // * 10 so it will arrive in 0.1 sec
545                 self.owner.avelocity_z = (remainder(v - self.owner.angles_z, 360)) * 10;
546         }
547 }
548
549 void spawnfunc_func_pendulum()
550 {
551         entity controller;
552         if (self.noise != "")
553         {
554                 precache_sound(self.noise);
555                 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
556         }
557
558         self.active = ACTIVE_ACTIVE;
559
560         // keys: angle, speed, phase, noise, freq
561
562         if(!self.speed)
563                 self.speed = 30;
564         // not initializing self.dmg to 2, to allow damageless pendulum
565
566         if(self.dmg & (!self.message))
567                 self.message = " was squished";
568         if(self.dmg && (!self.message2))
569                 self.message2 = "was squished by";
570         if(self.dmg && (!self.dmgtime))
571                 self.dmgtime = 0.25;
572         self.dmgtime2 = time;
573
574         self.blocked = generic_plat_blocked;
575
576         self.avelocity_z = 0.0000001;
577         if not(InitMovingBrushTrigger())
578                 return;
579
580         if(!self.freq)
581         {
582                 // find pendulum length (same formula as Q3A)
583                 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins_z))));
584         }
585
586         // copy initial angle
587         self.cnt = self.angles_z;
588
589         // wait for targets to spawn
590         controller = spawn();
591         controller.classname = "func_pendulum_controller";
592         controller.owner = self;
593         controller.nextthink = time + 1;
594         controller.think = func_pendulum_controller_think;
595         self.nextthink = self.ltime + 999999999;
596         self.think = SUB_Null;
597
598         //self.effects |= EF_LOWPRECISION;
599
600         // TODO make a reset function for this one
601 }
602
603 // button and multiple button
604
605 void() button_wait;
606 void() button_return;
607
608 void button_wait()
609 {
610         self.state = STATE_TOP;
611         self.nextthink = self.ltime + self.wait;
612         self.think = button_return;
613         activator = self.enemy;
614         SUB_UseTargets();
615         self.frame = 1;                 // use alternate textures
616 }
617
618 void button_done()
619 {
620         self.state = STATE_BOTTOM;
621 }
622
623 void button_return()
624 {
625         self.state = STATE_DOWN;
626         SUB_CalcMove (self.pos1, self.speed, button_done);
627         self.frame = 0;                 // use normal textures
628         if (self.health)
629                 self.takedamage = DAMAGE_YES;   // can be shot again
630 }
631
632
633 void button_blocked()
634 {
635         // do nothing, just don't come all the way back out
636 }
637
638
639 void button_fire()
640 {
641         self.health = self.max_health;
642         self.takedamage = DAMAGE_NO;    // will be reset upon return
643
644         if (self.state == STATE_UP || self.state == STATE_TOP)
645                 return;
646
647         if (self.noise != "")
648                 sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
649
650         self.state = STATE_UP;
651         SUB_CalcMove (self.pos2, self.speed, button_wait);
652 }
653
654 void button_reset()
655 {
656         self.health = self.max_health;
657         setorigin(self, self.pos1);
658         self.frame = 0;                 // use normal textures
659         self.state = STATE_BOTTOM;
660         if (self.health)
661                 self.takedamage = DAMAGE_YES;   // can be shot again
662 }
663
664 void button_use()
665 {
666 //      if (activator.classname != "player")
667 //      {
668 //              dprint(activator.classname);
669 //              dprint(" triggered a button\n");
670 //      }
671
672         if not (self.active == ACTIVE_ACTIVE)
673                 return;
674
675         self.enemy = activator;
676         button_fire ();
677 }
678
679 void button_touch()
680 {
681 //      if (activator.classname != "player")
682 //      {
683 //              dprint(activator.classname);
684 //              dprint(" touched a button\n");
685 //      }
686         if (!other)
687                 return;
688         if not(other.iscreature)
689                 return;
690         if(other.velocity * self.movedir < 0)
691                 return;
692         self.enemy = other;
693         if (other.owner)
694                 self.enemy = other.owner;
695         button_fire ();
696 }
697
698 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
699 {
700         if(self.spawnflags & DOOR_NOSPLASH)
701                 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
702                         return;
703         self.health = self.health - damage;
704         if (self.health <= 0)
705         {
706         //      if (activator.classname != "player")
707         //      {
708         //              dprint(activator.classname);
709         //              dprint(" killed a button\n");
710         //      }
711                 self.enemy = damage_attacker;
712                 button_fire ();
713         }
714 }
715
716
717 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
718 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.
719
720 "angle"         determines the opening direction
721 "target"        all entities with a matching targetname will be used
722 "speed"         override the default 40 speed
723 "wait"          override the default 1 second wait (-1 = never return)
724 "lip"           override the default 4 pixel lip remaining at end of move
725 "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
726 "sounds"
727 0) steam metal
728 1) wooden clunk
729 2) metallic click
730 3) in-out
731 */
732 void spawnfunc_func_button()
733 {
734         SetMovedir ();
735
736         if not(InitMovingBrushTrigger())
737                 return;
738         self.effects |= EF_LOWPRECISION;
739
740         self.blocked = button_blocked;
741         self.use = button_use;
742
743 //      if (self.health == 0) // all buttons are now shootable
744 //              self.health = 10;
745         if (self.health)
746         {
747                 self.max_health = self.health;
748                 self.event_damage = button_damage;
749                 self.takedamage = DAMAGE_YES;
750         }
751         else
752                 self.touch = button_touch;
753
754         if (!self.speed)
755                 self.speed = 40;
756         if (!self.wait)
757                 self.wait = 1;
758         if (!self.lip)
759                 self.lip = 4;
760
761     if(self.noise != "")
762         precache_sound(self.noise);
763
764         self.active = ACTIVE_ACTIVE;
765
766         self.pos1 = self.origin;
767         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
768     self.flags |= FL_NOTARGET;
769
770         button_reset();
771 }
772
773
774 float DOOR_START_OPEN = 1;
775 float DOOR_DONT_LINK = 4;
776 float DOOR_TOGGLE = 32;
777
778 /*
779
780 Doors are similar to buttons, but can spawn a fat trigger field around them
781 to open without a touch, and they link together to form simultanious
782 double/quad doors.
783
784 Door.owner is the master door.  If there is only one door, it points to itself.
785 If multiple doors, all will point to a single one.
786
787 Door.enemy chains from the master door through all doors linked in the chain.
788
789 */
790
791 /*
792 =============================================================================
793
794 THINK FUNCTIONS
795
796 =============================================================================
797 */
798
799 void() door_go_down;
800 void() door_go_up;
801 void() door_rotating_go_down;
802 void() door_rotating_go_up;
803
804 void door_blocked()
805 {
806
807     if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
808         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
809     } else {
810
811         if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?
812             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
813
814          //Dont chamge direction for dead or dying stuff
815         if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
816             if (self.wait >= 0)
817             {
818                 if (self.state == STATE_DOWN)
819                         if (self.classname == "door")
820                         {
821                                 door_go_up ();
822                         } else
823                         {
824                                 door_rotating_go_up ();
825                         }
826                 else
827                         if (self.classname == "door")
828                         {
829                                 door_go_down ();
830                         } else
831                         {
832                                 door_rotating_go_down ();
833                         }
834             }
835         } else {
836             //gib dying stuff just to make sure
837             if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?
838                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
839         }
840     }
841
842         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
843 // if a door has a negative wait, it would never come back if blocked,
844 // so let it just squash the object to death real fast
845 /*      if (self.wait >= 0)
846         {
847                 if (self.state == STATE_DOWN)
848                         door_go_up ();
849                 else
850                         door_go_down ();
851         }
852 */
853 }
854
855
856 void door_hit_top()
857 {
858         if (self.noise1 != "")
859                 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
860         self.state = STATE_TOP;
861         if (self.spawnflags & DOOR_TOGGLE)
862                 return;         // don't come down automatically
863         if (self.classname == "door")
864         {
865                 self.think = door_go_down;
866         } else
867         {
868                 self.think = door_rotating_go_down;
869         }
870         self.nextthink = self.ltime + self.wait;
871 }
872
873 void door_hit_bottom()
874 {
875         if (self.noise1 != "")
876                 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
877         self.state = STATE_BOTTOM;
878 }
879
880 void door_go_down()
881 {
882         if (self.noise2 != "")
883                 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
884         if (self.max_health)
885         {
886                 self.takedamage = DAMAGE_YES;
887                 self.health = self.max_health;
888         }
889
890         self.state = STATE_DOWN;
891         SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
892 }
893
894 void door_go_up()
895 {
896         if (self.state == STATE_UP)
897                 return;         // already going up
898
899         if (self.state == STATE_TOP)
900         {       // reset top wait time
901                 self.nextthink = self.ltime + self.wait;
902                 return;
903         }
904
905         if (self.noise2 != "")
906                 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
907         self.state = STATE_UP;
908         SUB_CalcMove (self.pos2, self.speed, door_hit_top);
909
910         string oldmessage;
911         oldmessage = self.message;
912         self.message = "";
913         SUB_UseTargets();
914         self.message = oldmessage;
915 }
916
917
918
919 /*
920 =============================================================================
921
922 ACTIVATION FUNCTIONS
923
924 =============================================================================
925 */
926
927 float door_check_keys(void) {
928         local entity door;
929         
930         
931         if (self.owner)
932                 door = self.owner;
933         else
934                 door = self;
935         
936         // no key needed
937         if not(door.itemkeys)
938                 return TRUE;
939
940         // this door require a key
941         // only a player can have a key
942         if (other.classname != "player")
943                 return FALSE;
944         
945         if (item_keys_usekey(door, other)) {
946                 // some keys were used
947                 if (other.key_door_messagetime <= time) {
948                         play2(other, "misc/talk.wav");
949                         centerprint(other, strcat("You also need ", item_keys_keylist(door.itemkeys), "!"));
950                         other.key_door_messagetime = time + 2;
951                 }
952         } else {
953                 // no keys were used
954                 if (other.key_door_messagetime <= time) {
955                         play2(other, "misc/talk.wav");
956                         centerprint(other, strcat("You need ", item_keys_keylist(door.itemkeys), "!"));
957                         other.key_door_messagetime = time + 2;
958                 }
959         }
960
961         if (door.itemkeys) {
962                 // door is now unlocked
963                 play2(other, "misc/talk.wav");
964                 centerprint(other, "Door unlocked!");
965                 return TRUE;
966         } else
967                 return FALSE;
968 }
969
970
971 void door_fire()
972 {
973         entity  oself;
974         entity  starte;
975
976         if (self.owner != self)
977                 objerror ("door_fire: self.owner != self");
978
979         oself = self;
980
981         if (self.spawnflags & DOOR_TOGGLE)
982         {
983                 if (self.state == STATE_UP || self.state == STATE_TOP)
984                 {
985                         starte = self;
986                         do
987                         {
988                                 if (self.classname == "door")
989                                 {
990                                         door_go_down ();
991                                 }
992                                 else
993                                 {
994                                         door_rotating_go_down ();
995                                 }
996                                 self = self.enemy;
997                         } while ( (self != starte) && (self != world) );
998                         self = oself;
999                         return;
1000                 }
1001         }
1002
1003 // trigger all paired doors
1004         starte = self;
1005         do
1006         {
1007                 if (self.classname == "door")
1008                 {
1009                         door_go_up ();
1010                 } else
1011                 {
1012                         // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
1013                         if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
1014                         {
1015                                 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
1016                                 self.pos2 = '0 0 0' - self.pos2;
1017                         }
1018                         // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
1019                         if (!((self.spawnflags & 2) &&  (self.spawnflags & 8) && self.state == STATE_DOWN
1020                             && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
1021                         {
1022                                 door_rotating_go_up ();
1023                         }
1024                 }
1025                 self = self.enemy;
1026         } while ( (self != starte) && (self != world) );
1027         self = oself;
1028 }
1029
1030
1031 void door_use()
1032 {
1033         entity oself;
1034
1035         //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
1036         
1037         if (self.owner)
1038         {
1039                 oself = self;
1040                 self = self.owner;
1041                 door_fire ();
1042                 self = oself;
1043         }
1044 }
1045
1046
1047 void door_trigger_touch()
1048 {
1049         if (other.health < 1)
1050                 if not(other.iscreature && other.deadflag == DEAD_NO)
1051                         return;
1052
1053         if (time < self.attack_finished_single)
1054                 return;
1055         
1056         // check if door is locked
1057         if (!door_check_keys())
1058                 return;
1059         
1060         self.attack_finished_single = time + 1;
1061
1062         activator = other;
1063
1064         self = self.owner;
1065         door_use ();
1066 }
1067
1068
1069 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1070 {
1071         entity oself;
1072         if(self.spawnflags & DOOR_NOSPLASH)
1073                 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
1074                         return;
1075         self.health = self.health - damage;
1076         
1077         if (self.itemkeys) {
1078                 // don't allow opening doors through damage if keys are required
1079                 return;
1080         }
1081         
1082         if (self.health <= 0)
1083         {
1084                 oself = self;
1085                 self = self.owner;
1086                 self.health = self.max_health;
1087                 self.takedamage = DAMAGE_NO;    // wil be reset upon return
1088                 door_use ();
1089                 self = oself;
1090         }
1091 }
1092
1093
1094 /*
1095 ================
1096 door_touch
1097
1098 Prints messages
1099 ================
1100 */
1101 void door_touch()
1102 {
1103         if(other.classname != "player")
1104                 return;
1105         if (self.owner.attack_finished_single > time)
1106                 return;
1107
1108         self.owner.attack_finished_single = time + 2;
1109
1110         if (!(self.owner.dmg) && (self.owner.message != ""))
1111         {
1112                 if (other.flags & FL_CLIENT)
1113                         centerprint (other, self.owner.message);
1114                 play2(other, "misc/talk.wav");
1115         }
1116 }
1117
1118
1119 void door_generic_plat_blocked()
1120 {
1121
1122     if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1123         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1124     } else {
1125
1126         if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?
1127             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1128
1129          //Dont chamge direction for dead or dying stuff
1130         if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1131             if (self.wait >= 0)
1132             {
1133                 if (self.state == STATE_DOWN)
1134                     door_rotating_go_up ();
1135                 else
1136                     door_rotating_go_down ();
1137             }
1138         } else {
1139             //gib dying stuff just to make sure
1140             if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?
1141                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1142         }
1143     }
1144
1145         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1146 // if a door has a negative wait, it would never come back if blocked,
1147 // so let it just squash the object to death real fast
1148 /*      if (self.wait >= 0)
1149         {
1150                 if (self.state == STATE_DOWN)
1151                         door_rotating_go_up ();
1152                 else
1153                         door_rotating_go_down ();
1154         }
1155 */
1156 }
1157
1158
1159 void door_rotating_hit_top()
1160 {
1161         if (self.noise1 != "")
1162                 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1163         self.state = STATE_TOP;
1164         if (self.spawnflags & DOOR_TOGGLE)
1165                 return;         // don't come down automatically
1166         self.think = door_rotating_go_down;
1167         self.nextthink = self.ltime + self.wait;
1168 }
1169
1170 void door_rotating_hit_bottom()
1171 {
1172         if (self.noise1 != "")
1173                 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1174         if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1175         {
1176                 self.pos2 = '0 0 0' - self.pos2;
1177                 self.lip = 0;
1178         }
1179         self.state = STATE_BOTTOM;
1180 }
1181
1182 void door_rotating_go_down()
1183 {
1184         if (self.noise2 != "")
1185                 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1186         if (self.max_health)
1187         {
1188                 self.takedamage = DAMAGE_YES;
1189                 self.health = self.max_health;
1190         }
1191
1192         self.state = STATE_DOWN;
1193         SUB_CalcAngleMove (self.pos1, self.speed, door_rotating_hit_bottom);
1194 }
1195
1196 void door_rotating_go_up()
1197 {
1198         if (self.state == STATE_UP)
1199                 return;         // already going up
1200
1201         if (self.state == STATE_TOP)
1202         {       // reset top wait time
1203                 self.nextthink = self.ltime + self.wait;
1204                 return;
1205         }
1206         if (self.noise2 != "")
1207                 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1208         self.state = STATE_UP;
1209         SUB_CalcAngleMove (self.pos2, self.speed, door_rotating_hit_top);
1210
1211         string oldmessage;
1212         oldmessage = self.message;
1213         self.message = "";
1214         SUB_UseTargets();
1215         self.message = oldmessage;
1216 }
1217
1218
1219
1220
1221 /*
1222 =============================================================================
1223
1224 SPAWNING FUNCTIONS
1225
1226 =============================================================================
1227 */
1228
1229
1230 entity spawn_field(vector fmins, vector fmaxs)
1231 {
1232         entity  trigger;
1233         vector  t1, t2;
1234
1235         trigger = spawn();
1236         trigger.classname = "doortriggerfield";
1237         trigger.movetype = MOVETYPE_NONE;
1238         trigger.solid = SOLID_TRIGGER;
1239         trigger.owner = self;
1240         trigger.touch = door_trigger_touch;
1241
1242         t1 = fmins;
1243         t2 = fmaxs;
1244         setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1245         return (trigger);
1246 }
1247
1248
1249 float EntitiesTouching(entity e1, entity e2)
1250 {
1251         if (e1.absmin_x > e2.absmax_x)
1252                 return FALSE;
1253         if (e1.absmin_y > e2.absmax_y)
1254                 return FALSE;
1255         if (e1.absmin_z > e2.absmax_z)
1256                 return FALSE;
1257         if (e1.absmax_x < e2.absmin_x)
1258                 return FALSE;
1259         if (e1.absmax_y < e2.absmin_y)
1260                 return FALSE;
1261         if (e1.absmax_z < e2.absmin_z)
1262                 return FALSE;
1263         return TRUE;
1264 }
1265
1266
1267 /*
1268 =============
1269 LinkDoors
1270
1271
1272 =============
1273 */
1274 void LinkDoors()
1275 {
1276         entity  t, starte;
1277         vector  cmins, cmaxs;
1278
1279         if (self.enemy)
1280                 return;         // already linked by another door
1281         if (self.spawnflags & 4)
1282         {
1283                 self.owner = self.enemy = self;
1284
1285                 if (self.health)
1286                         return;
1287                 IFTARGETED
1288                         return;
1289                 if (self.items)
1290                         return;
1291                 self.trigger_field = spawn_field(self.absmin, self.absmax);
1292
1293                 return;         // don't want to link this door
1294         }
1295
1296         cmins = self.absmin;
1297         cmaxs = self.absmax;
1298
1299         starte = self;
1300         t = self;
1301
1302         do
1303         {
1304                 self.owner = starte;                    // master door
1305
1306                 if (self.health)
1307                         starte.health = self.health;
1308                 IFTARGETED
1309                         starte.targetname = self.targetname;
1310                 if (self.message != "")
1311                         starte.message = self.message;
1312
1313                 t = find(t, classname, self.classname);
1314                 if (!t)
1315                 {
1316                         self.enemy = starte;            // make the chain a loop
1317
1318                 // shootable, or triggered doors just needed the owner/enemy links,
1319                 // they don't spawn a field
1320
1321                         self = self.owner;
1322
1323                         if (self.health)
1324                                 return;
1325                         IFTARGETED
1326                                 return;
1327                         if (self.items)
1328                                 return;
1329
1330                         self.owner.trigger_field = spawn_field(cmins, cmaxs);
1331
1332                         return;
1333                 }
1334
1335                 if (EntitiesTouching(self,t))
1336                 {
1337                         if (t.enemy)
1338                                 objerror ("cross connected doors");
1339
1340                         self.enemy = t;
1341                         self = t;
1342
1343                         if (t.absmin_x < cmins_x)
1344                                 cmins_x = t.absmin_x;
1345                         if (t.absmin_y < cmins_y)
1346                                 cmins_y = t.absmin_y;
1347                         if (t.absmin_z < cmins_z)
1348                                 cmins_z = t.absmin_z;
1349                         if (t.absmax_x > cmaxs_x)
1350                                 cmaxs_x = t.absmax_x;
1351                         if (t.absmax_y > cmaxs_y)
1352                                 cmaxs_y = t.absmax_y;
1353                         if (t.absmax_z > cmaxs_z)
1354                                 cmaxs_z = t.absmax_z;
1355                 }
1356         } while (1 );
1357
1358 }
1359
1360
1361 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
1362 if two doors touch, they are assumed to be connected and operate as a unit.
1363
1364 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1365
1366 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).
1367
1368 GOLD_KEY causes the door to open only if the activator holds a gold key.
1369
1370 SILVER_KEY causes the door to open only if the activator holds a silver key.
1371
1372 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1373 "angle"         determines the opening direction
1374 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1375 "health"        if set, door must be shot open
1376 "speed"         movement speed (100 default)
1377 "wait"          wait before returning (3 default, -1 = never return)
1378 "lip"           lip remaining at end of move (8 default)
1379 "dmg"           damage to inflict when blocked (2 default)
1380 "sounds"
1381 0)      no sound
1382 1)      stone
1383 2)      base
1384 3)      stone chain
1385 4)      screechy metal
1386 FIXME: only one sound set available at the time being
1387
1388 */
1389
1390 void door_init_startopen()
1391 {
1392         setorigin (self, self.pos2);
1393         self.pos2 = self.pos1;
1394         self.pos1 = self.origin;
1395 }
1396
1397 void door_reset()
1398 {
1399         setorigin(self, self.pos1);
1400         self.velocity = '0 0 0';
1401         self.state = STATE_BOTTOM;
1402         self.think = SUB_Null;
1403 }
1404
1405 // spawnflags require key (for now only func_door)
1406 #define SPAWNFLAGS_GOLD_KEY 8
1407 #define SPAWNFLAGS_SILVER_KEY 16
1408 void spawnfunc_func_door()
1409 {
1410         // Quake 1 keys compatibility
1411         if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
1412                 self.itemkeys |= ITEM_KEY_BIT(0);
1413         if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
1414                 self.itemkeys |= ITEM_KEY_BIT(1);
1415                 
1416         //if (!self.deathtype) // map makers can override this
1417         //      self.deathtype = " got in the way";
1418         SetMovedir ();
1419
1420         self.max_health = self.health;
1421         if not(InitMovingBrushTrigger())
1422                 return;
1423         self.effects |= EF_LOWPRECISION;
1424         self.classname = "door";
1425
1426         self.blocked = door_blocked;
1427         self.use = door_use;
1428
1429         // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
1430         // if(self.spawnflags & 8)
1431         //      self.dmg = 10000;
1432
1433     if(self.dmg && (!self.message))
1434                 self.message = "was squished";
1435     if(self.dmg && (!self.message2))
1436                 self.message2 = "was squished by";
1437
1438         if (self.sounds > 0)
1439         {
1440                 precache_sound ("plats/medplat1.wav");
1441                 precache_sound ("plats/medplat2.wav");
1442                 self.noise2 = "plats/medplat1.wav";
1443                 self.noise1 = "plats/medplat2.wav";
1444         }
1445
1446         if (!self.speed)
1447                 self.speed = 100;
1448         if (!self.wait)
1449                 self.wait = 3;
1450         if (!self.lip)
1451                 self.lip = 8;
1452
1453         self.pos1 = self.origin;
1454         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1455
1456 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1457 // but spawn in the open position
1458         if (self.spawnflags & DOOR_START_OPEN)
1459                 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1460
1461         self.state = STATE_BOTTOM;
1462
1463         if (self.health)
1464         {
1465                 self.takedamage = DAMAGE_YES;
1466                 self.event_damage = door_damage;
1467         }
1468
1469         if (self.items)
1470                 self.wait = -1;
1471
1472         self.touch = door_touch;
1473
1474 // LinkDoors can't be done until all of the doors have been spawned, so
1475 // the sizes can be detected properly.
1476         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1477
1478         self.reset = door_reset;
1479 }
1480
1481 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1482 if two doors touch, they are assumed to be connected and operate as a unit.
1483
1484 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1485
1486 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1487 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1488 must have set trigger_reverse to 1.
1489 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1490
1491 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).
1492
1493 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1494 "angle"         determines the destination angle for opening. negative values reverse the direction.
1495 "targetname"    if set, no touch field will be spawned and a remote button or trigger field activates the door.
1496 "health"        if set, door must be shot open
1497 "speed"         movement speed (100 default)
1498 "wait"          wait before returning (3 default, -1 = never return)
1499 "dmg"           damage to inflict when blocked (2 default)
1500 "sounds"
1501 0)      no sound
1502 1)      stone
1503 2)      base
1504 3)      stone chain
1505 4)      screechy metal
1506 FIXME: only one sound set available at the time being
1507 */
1508
1509 void door_rotating_reset()
1510 {
1511         self.angles = self.pos1;
1512         self.avelocity = '0 0 0';
1513         self.state = STATE_BOTTOM;
1514         self.think = SUB_Null;
1515 }
1516
1517 void door_rotating_init_startopen()
1518 {
1519         self.angles = self.movedir;
1520         self.pos2 = '0 0 0';
1521         self.pos1 = self.movedir;
1522 }
1523
1524
1525 void spawnfunc_func_door_rotating()
1526 {
1527
1528         //if (!self.deathtype) // map makers can override this
1529         //      self.deathtype = " got in the way";
1530
1531         // I abuse "movedir" for denoting the axis for now
1532         if (self.spawnflags & 64) // X (untested)
1533                 self.movedir = '0 0 1';
1534         else if (self.spawnflags & 128) // Y (untested)
1535                 self.movedir = '1 0 0';
1536         else // Z
1537                 self.movedir = '0 1 0';
1538
1539         if (self.angles_y==0) self.angles_y = 90;
1540
1541         self.movedir = self.movedir * self.angles_y;
1542         self.angles = '0 0 0';
1543
1544         self.max_health = self.health;
1545         self.avelocity = self.movedir;
1546         if not(InitMovingBrushTrigger())
1547                 return;
1548         self.velocity = '0 0 0';
1549         //self.effects |= EF_LOWPRECISION;
1550         self.classname = "door_rotating";
1551
1552         self.blocked = door_blocked;
1553         self.use = door_use;
1554
1555     if(self.spawnflags & 8)
1556         self.dmg = 10000;
1557
1558     if(self.dmg && (!self.message))
1559                 self.message = "was squished";
1560     if(self.dmg && (!self.message2))
1561                 self.message2 = "was squished by";
1562
1563     if (self.sounds > 0)
1564         {
1565                 precache_sound ("plats/medplat1.wav");
1566                 precache_sound ("plats/medplat2.wav");
1567                 self.noise2 = "plats/medplat1.wav";
1568                 self.noise1 = "plats/medplat2.wav";
1569         }
1570
1571         if (!self.speed)
1572                 self.speed = 50;
1573         if (!self.wait)
1574                 self.wait = 1;
1575         self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1576
1577         self.pos1 = '0 0 0';
1578         self.pos2 = self.movedir;
1579
1580 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1581 // but spawn in the open position
1582         if (self.spawnflags & DOOR_START_OPEN)
1583                 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1584
1585         self.state = STATE_BOTTOM;
1586
1587         if (self.health)
1588         {
1589                 self.takedamage = DAMAGE_YES;
1590                 self.event_damage = door_damage;
1591         }
1592
1593         if (self.items)
1594                 self.wait = -1;
1595
1596         self.touch = door_touch;
1597
1598 // LinkDoors can't be done until all of the doors have been spawned, so
1599 // the sizes can be detected properly.
1600         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1601
1602         self.reset = door_rotating_reset;
1603 }
1604
1605 /*
1606 =============================================================================
1607
1608 SECRET DOORS
1609
1610 =============================================================================
1611 */
1612
1613 void() fd_secret_move1;
1614 void() fd_secret_move2;
1615 void() fd_secret_move3;
1616 void() fd_secret_move4;
1617 void() fd_secret_move5;
1618 void() fd_secret_move6;
1619 void() fd_secret_done;
1620
1621 float SECRET_OPEN_ONCE = 1;             // stays open
1622 float SECRET_1ST_LEFT = 2;              // 1st move is left of arrow
1623 float SECRET_1ST_DOWN = 4;              // 1st move is down from arrow
1624 float SECRET_NO_SHOOT = 8;              // only opened by trigger
1625 float SECRET_YES_SHOOT = 16;    // shootable even if targeted
1626
1627
1628 void fd_secret_use()
1629 {
1630         float temp;
1631         string message_save;
1632
1633         self.health = 10000;
1634         self.bot_attack = TRUE;
1635
1636         // exit if still moving around...
1637         if (self.origin != self.oldorigin)
1638                 return;
1639
1640         message_save = self.message;
1641         self.message = ""; // no more message
1642         SUB_UseTargets();                               // fire all targets / killtargets
1643         self.message = message_save;
1644
1645         self.velocity = '0 0 0';
1646
1647         // Make a sound, wait a little...
1648
1649         if (self.noise1 != "")
1650                 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1651         self.nextthink = self.ltime + 0.1;
1652
1653         temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1654         makevectors(self.mangle);
1655
1656         if (!self.t_width)
1657         {
1658                 if (self.spawnflags & SECRET_1ST_DOWN)
1659                         self.t_width = fabs(v_up * self.size);
1660                 else
1661                         self.t_width = fabs(v_right * self.size);
1662         }
1663
1664         if (!self.t_length)
1665                 self.t_length = fabs(v_forward * self.size);
1666
1667         if (self.spawnflags & SECRET_1ST_DOWN)
1668                 self.dest1 = self.origin - v_up * self.t_width;
1669         else
1670                 self.dest1 = self.origin + v_right * (self.t_width * temp);
1671
1672         self.dest2 = self.dest1 + v_forward * self.t_length;
1673         SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1674         if (self.noise2 != "")
1675                 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1676 }
1677
1678 // Wait after first movement...
1679 void fd_secret_move1()
1680 {
1681         self.nextthink = self.ltime + 1.0;
1682         self.think = fd_secret_move2;
1683         if (self.noise3 != "")
1684                 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1685 }
1686
1687 // Start moving sideways w/sound...
1688 void fd_secret_move2()
1689 {
1690         if (self.noise2 != "")
1691                 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1692         SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1693 }
1694
1695 // Wait here until time to go back...
1696 void fd_secret_move3()
1697 {
1698         if (self.noise3 != "")
1699                 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1700         if (!(self.spawnflags & SECRET_OPEN_ONCE))
1701         {
1702                 self.nextthink = self.ltime + self.wait;
1703                 self.think = fd_secret_move4;
1704         }
1705 }
1706
1707 // Move backward...
1708 void fd_secret_move4()
1709 {
1710         if (self.noise2 != "")
1711                 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1712         SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1713 }
1714
1715 // Wait 1 second...
1716 void fd_secret_move5()
1717 {
1718         self.nextthink = self.ltime + 1.0;
1719         self.think = fd_secret_move6;
1720         if (self.noise3 != "")
1721                 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1722 }
1723
1724 void fd_secret_move6()
1725 {
1726         if (self.noise2 != "")
1727                 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1728         SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1729 }
1730
1731 void fd_secret_done()
1732 {
1733         if (self.spawnflags&SECRET_YES_SHOOT)
1734         {
1735                 self.health = 10000;
1736                 self.takedamage = DAMAGE_YES;
1737                 //self.th_pain = fd_secret_use;
1738         }
1739         if (self.noise3 != "")
1740                 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1741 }
1742
1743 void secret_blocked()
1744 {
1745         if (time < self.attack_finished_single)
1746                 return;
1747         self.attack_finished_single = time + 0.5;
1748         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1749 }
1750
1751 /*
1752 ==============
1753 secret_touch
1754
1755 Prints messages
1756 ================
1757 */
1758 void secret_touch()
1759 {
1760         if not(other.iscreature)
1761                 return;
1762         if (self.attack_finished_single > time)
1763                 return;
1764
1765         self.attack_finished_single = time + 2;
1766
1767         if (self.message)
1768         {
1769                 if (other.flags & FL_CLIENT)
1770                         centerprint (other, self.message);
1771                 play2(other, "misc/talk.wav");
1772         }
1773 }
1774
1775 void secret_reset()
1776 {
1777         if (self.spawnflags&SECRET_YES_SHOOT)
1778         {
1779                 self.health = 10000;
1780                 self.takedamage = DAMAGE_YES;
1781         }
1782         setorigin(self, self.oldorigin);
1783         self.think = SUB_Null;
1784 }
1785
1786 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1787 Basic secret door. Slides back, then to the side. Angle determines direction.
1788 wait  = # of seconds before coming back
1789 1st_left = 1st move is left of arrow
1790 1st_down = 1st move is down from arrow
1791 always_shoot = even if targeted, keep shootable
1792 t_width = override WIDTH to move back (or height if going down)
1793 t_length = override LENGTH to move sideways
1794 "dmg"           damage to inflict when blocked (2 default)
1795
1796 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1797 "sounds"
1798 1) medieval
1799 2) metal
1800 3) base
1801 */
1802
1803 void spawnfunc_func_door_secret()
1804 {
1805         /*if (!self.deathtype) // map makers can override this
1806                 self.deathtype = " got in the way";*/
1807
1808         if (!self.dmg)
1809                 self.dmg = 2;
1810
1811         // Magic formula...
1812         self.mangle = self.angles;
1813         self.angles = '0 0 0';
1814         self.classname = "door";
1815         if not(InitMovingBrushTrigger())
1816                 return;
1817         self.effects |= EF_LOWPRECISION;
1818
1819         self.touch = secret_touch;
1820         self.blocked = secret_blocked;
1821         self.speed = 50;
1822         self.use = fd_secret_use;
1823         IFTARGETED
1824         {
1825         }
1826         else
1827                 self.spawnflags |= SECRET_YES_SHOOT;
1828
1829         if(self.spawnflags&SECRET_YES_SHOOT)
1830         {
1831                 self.health = 10000;
1832                 self.takedamage = DAMAGE_YES;
1833                 self.event_damage = fd_secret_use;
1834         }
1835         self.oldorigin = self.origin;
1836         if (!self.wait)
1837                 self.wait = 5;          // 5 seconds before closing
1838
1839         self.reset = secret_reset;
1840         secret_reset();
1841 }
1842
1843 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1844 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1845 netname: list of <frequencymultiplier> <phase> <x> <y> <z> quadruples, separated by spaces; note that phase 0 represents a sine wave, and phase 0.25 a cosine wave (by default, it uses 1 0 0 0 1, to match func_bobbing's defaults
1846 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1847 height: amplitude modifier (default 32)
1848 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1849 noise: path/name of looping .wav file to play.
1850 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1851 dmgtime: See above.
1852 */
1853
1854 void func_fourier_controller_think()
1855 {
1856         vector v;
1857         float n, i, t;
1858
1859         self.nextthink = time + 0.1;
1860         if not (self.owner.active == ACTIVE_ACTIVE)
1861         {
1862                 self.owner.velocity = '0 0 0';          
1863                 return;
1864         }
1865
1866
1867         n = floor((tokenize_console(self.owner.netname)) / 5);
1868         t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1869
1870         v = self.owner.destvec;
1871
1872         for(i = 0; i < n; ++i)
1873         {
1874                 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1875                 v = v + ('1 0 0' * stof(argv(i*5+2)) + '0 1 0' * stof(argv(i*5+3)) + '0 0 1' * stof(argv(i*5+4))) * self.owner.height * v_forward_y;
1876         }
1877
1878         if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1879                 // * 10 so it will arrive in 0.1 sec
1880                 self.owner.velocity = (v - self.owner.origin) * 10;
1881 }
1882
1883 void spawnfunc_func_fourier()
1884 {
1885         entity controller;
1886         if (self.noise != "")
1887         {
1888                 precache_sound(self.noise);
1889                 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
1890         }
1891
1892         if (!self.speed)
1893                 self.speed = 4;
1894         if (!self.height)
1895                 self.height = 32;
1896         self.destvec = self.origin;
1897         self.cnt = 360 / self.speed;
1898
1899         self.blocked = generic_plat_blocked;
1900         if(self.dmg & (!self.message))
1901                 self.message = " was squished";
1902     if(self.dmg && (!self.message2))
1903                 self.message2 = "was squished by";
1904         if(self.dmg && (!self.dmgtime))
1905                 self.dmgtime = 0.25;
1906         self.dmgtime2 = time;
1907
1908         if(self.netname == "")
1909                 self.netname = "1 0 0 0 1";
1910
1911         if not(InitMovingBrushTrigger())
1912                 return;
1913
1914         self.active = ACTIVE_ACTIVE;
1915
1916         // wait for targets to spawn
1917         controller = spawn();
1918         controller.classname = "func_fourier_controller";
1919         controller.owner = self;
1920         controller.nextthink = time + 1;
1921         controller.think = func_fourier_controller_think;
1922         self.nextthink = self.ltime + 999999999;
1923         self.think = SUB_Null;
1924
1925         // Savage: Reduce bandwith, critical on e.g. nexdm02
1926         self.effects |= EF_LOWPRECISION;
1927
1928         // TODO make a reset function for this one
1929 }
1930
1931 // reusing some fields havocbots declared
1932 .entity wp00, wp01, wp02, wp03;
1933
1934 .float targetfactor, target2factor, target3factor, target4factor;
1935 .vector targetnormal, target2normal, target3normal, target4normal;
1936
1937 vector func_vectormamamam_origin(entity o, float t)
1938 {
1939         vector v, p;
1940         float f;
1941         entity e;
1942
1943         f = o.spawnflags;
1944         v = '0 0 0';
1945
1946         e = o.wp00;
1947         if(e)
1948         {
1949                 p = e.origin + t * e.velocity;
1950                 if(f & 1)
1951                         v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
1952                 else
1953                         v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
1954         }
1955
1956         e = o.wp01;
1957         if(e)
1958         {
1959                 p = e.origin + t * e.velocity;
1960                 if(f & 2)
1961                         v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
1962                 else
1963                         v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
1964         }
1965
1966         e = o.wp02;
1967         if(e)
1968         {
1969                 p = e.origin + t * e.velocity;
1970                 if(f & 4)
1971                         v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
1972                 else
1973                         v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
1974         }
1975
1976         e = o.wp03;
1977         if(e)
1978         {
1979                 p = e.origin + t * e.velocity;
1980                 if(f & 8)
1981                         v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
1982                 else
1983                         v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
1984         }
1985
1986         return v;
1987 }
1988
1989 void func_vectormamamam_controller_think()
1990 {
1991         self.nextthink = time + 0.1;
1992
1993         if not (self.owner.active == ACTIVE_ACTIVE)
1994         {
1995                 self.owner.velocity = '0 0 0';          
1996                 return;
1997         }
1998
1999         if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
2000                 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
2001 }
2002
2003 void func_vectormamamam_findtarget()
2004 {
2005         if(self.target != "")
2006                 self.wp00 = find(world, targetname, self.target);
2007
2008         if(self.target2 != "")
2009                 self.wp01 = find(world, targetname, self.target2);
2010
2011         if(self.target3 != "")
2012                 self.wp02 = find(world, targetname, self.target3);
2013
2014         if(self.target4 != "")
2015                 self.wp03 = find(world, targetname, self.target4);
2016
2017         if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
2018                 objerror("No reference entity found, so there is nothing to move. Aborting.");
2019
2020         self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
2021
2022         entity controller;
2023         controller = spawn();
2024         controller.classname = "func_vectormamamam_controller";
2025         controller.owner = self;
2026         controller.nextthink = time + 1;
2027         controller.think = func_vectormamamam_controller_think;
2028 }
2029
2030 void spawnfunc_func_vectormamamam()
2031 {
2032         if (self.noise != "")
2033         {
2034                 precache_sound(self.noise);
2035                 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
2036         }
2037
2038         if(!self.targetfactor)
2039                 self.targetfactor = 1;
2040
2041         if(!self.target2factor)
2042                 self.target2factor = 1;
2043
2044         if(!self.target3factor)
2045                 self.target3factor = 1;
2046
2047         if(!self.target4factor)
2048                 self.target4factor = 1;
2049
2050         if(vlen(self.targetnormal))
2051                 self.targetnormal = normalize(self.targetnormal);
2052
2053         if(vlen(self.target2normal))
2054                 self.target2normal = normalize(self.target2normal);
2055
2056         if(vlen(self.target3normal))
2057                 self.target3normal = normalize(self.target3normal);
2058
2059         if(vlen(self.target4normal))
2060                 self.target4normal = normalize(self.target4normal);
2061
2062         self.blocked = generic_plat_blocked;
2063         if(self.dmg & (!self.message))
2064                 self.message = " was squished";
2065     if(self.dmg && (!self.message2))
2066                 self.message2 = "was squished by";
2067         if(self.dmg && (!self.dmgtime))
2068                 self.dmgtime = 0.25;
2069         self.dmgtime2 = time;
2070
2071         if(self.netname == "")
2072                 self.netname = "1 0 0 0 1";
2073
2074         if not(InitMovingBrushTrigger())
2075                 return;
2076
2077         // wait for targets to spawn
2078         self.nextthink = self.ltime + 999999999;
2079         self.think = SUB_Null;
2080
2081         // Savage: Reduce bandwith, critical on e.g. nexdm02
2082         self.effects |= EF_LOWPRECISION;
2083
2084         self.active = ACTIVE_ACTIVE;
2085
2086         InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
2087 }
2088
2089 void conveyor_think()
2090 {
2091         entity e;
2092
2093         // set myself as current conveyor where possible
2094         for(e = world; (e = findentity(e, conveyor, self)); )
2095                 e.conveyor = world;
2096
2097         if(self.state)
2098         {
2099                 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2100                         if(!e.conveyor.state)
2101                                 if(isPushable(e))
2102                                 {
2103                                         vector emin = e.absmin;
2104                                         vector emax = e.absmax;
2105                                         if(self.solid == SOLID_BSP)
2106                                         {
2107                                                 emin -= '1 1 1';
2108                                                 emax += '1 1 1';
2109                                         }
2110                                         if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2111                                                 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2112                                                         e.conveyor = self;
2113                                 }
2114
2115                 for(e = world; (e = findentity(e, conveyor, self)); )
2116                 {
2117                         if(e.flags & FL_CLIENT) // doing it via velocity has quite some advantages
2118                                 continue; // done in SV_PlayerPhysics
2119
2120                         setorigin(e, e.origin + self.movedir * sys_frametime);
2121                         move_out_of_solid(e);
2122                         UpdateCSQCProjectile(e);
2123                         /*
2124                         // stupid conveyor code
2125                         tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2126                         if(trace_fraction > 0)
2127                                 setorigin(e, trace_endpos);
2128                         */
2129                 }
2130         }
2131
2132         self.nextthink = time;
2133 }
2134
2135 void conveyor_use()
2136 {
2137         self.state = !self.state;
2138 }
2139
2140 void conveyor_reset()
2141 {
2142         self.state = (self.spawnflags & 1);
2143 }
2144
2145 void conveyor_init()
2146 {
2147         if (!self.speed)
2148                 self.speed = 200;
2149         self.movedir = self.movedir * self.speed;
2150         self.think = conveyor_think;
2151         self.nextthink = time;
2152         IFTARGETED
2153         {
2154                 self.use = conveyor_use;
2155                 self.reset = conveyor_reset;
2156                 conveyor_reset();
2157         }
2158         else
2159                 self.state = 1;
2160 }
2161
2162 void spawnfunc_trigger_conveyor()
2163 {
2164         SetMovedir();
2165         EXACTTRIGGER_INIT;
2166         conveyor_init();
2167 }
2168
2169 void spawnfunc_func_conveyor()
2170 {
2171         SetMovedir();
2172         InitMovingBrushTrigger();
2173         self.movetype = MOVETYPE_NONE;
2174         conveyor_init();
2175 }