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