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