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