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