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