]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/t_plats.qc
Merge remote-tracking branch 'origin/master' into samual/notification_rewrite
[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 (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 = func_null;
1392         self.nextthink = 0;
1393 }
1394
1395 // spawnflags require key (for now only func_door)
1396 #define SPAWNFLAGS_GOLD_KEY 8
1397 #define SPAWNFLAGS_SILVER_KEY 16
1398 void spawnfunc_func_door()
1399 {
1400         // Quake 1 keys compatibility
1401         if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
1402                 self.itemkeys |= ITEM_KEY_BIT(0);
1403         if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
1404                 self.itemkeys |= ITEM_KEY_BIT(1);
1405                 
1406         //if (!self.deathtype) // map makers can override this
1407         //      self.deathtype = " got in the way";
1408         SetMovedir ();
1409
1410         self.max_health = self.health;
1411         if not(InitMovingBrushTrigger())
1412                 return;
1413         self.effects |= EF_LOWPRECISION;
1414         self.classname = "door";
1415
1416         self.blocked = door_blocked;
1417         self.use = door_use;
1418
1419         // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
1420         // if(self.spawnflags & 8)
1421         //      self.dmg = 10000;
1422
1423     if(self.dmg && (!self.message))
1424                 self.message = "was squished";
1425     if(self.dmg && (!self.message2))
1426                 self.message2 = "was squished by";
1427
1428         if (self.sounds > 0)
1429         {
1430                 precache_sound ("plats/medplat1.wav");
1431                 precache_sound ("plats/medplat2.wav");
1432                 self.noise2 = "plats/medplat1.wav";
1433                 self.noise1 = "plats/medplat2.wav";
1434         }
1435
1436         if (!self.speed)
1437                 self.speed = 100;
1438         if (!self.wait)
1439                 self.wait = 3;
1440         if (!self.lip)
1441                 self.lip = 8;
1442
1443         self.pos1 = self.origin;
1444         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1445
1446 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1447 // but spawn in the open position
1448         if (self.spawnflags & DOOR_START_OPEN)
1449                 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1450
1451         self.state = STATE_BOTTOM;
1452
1453         if (self.health)
1454         {
1455                 self.takedamage = DAMAGE_YES;
1456                 self.event_damage = door_damage;
1457         }
1458
1459         if (self.items)
1460                 self.wait = -1;
1461
1462         self.touch = door_touch;
1463
1464 // LinkDoors can't be done until all of the doors have been spawned, so
1465 // the sizes can be detected properly.
1466         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1467
1468         self.reset = door_reset;
1469 }
1470
1471 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1472 if two doors touch, they are assumed to be connected and operate as a unit.
1473
1474 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1475
1476 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1477 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1478 must have set trigger_reverse to 1.
1479 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1480
1481 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).
1482
1483 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1484 "angle"         determines the destination angle for opening. negative values reverse the direction.
1485 "targetname"    if set, no touch field will be spawned and a remote button or trigger field activates the door.
1486 "health"        if set, door must be shot open
1487 "speed"         movement speed (100 default)
1488 "wait"          wait before returning (3 default, -1 = never return)
1489 "dmg"           damage to inflict when blocked (2 default)
1490 "sounds"
1491 0)      no sound
1492 1)      stone
1493 2)      base
1494 3)      stone chain
1495 4)      screechy metal
1496 FIXME: only one sound set available at the time being
1497 */
1498
1499 void door_rotating_reset()
1500 {
1501         self.angles = self.pos1;
1502         self.avelocity = '0 0 0';
1503         self.state = STATE_BOTTOM;
1504         self.think = func_null;
1505         self.nextthink = 0;
1506 }
1507
1508 void door_rotating_init_startopen()
1509 {
1510         self.angles = self.movedir;
1511         self.pos2 = '0 0 0';
1512         self.pos1 = self.movedir;
1513 }
1514
1515
1516 void spawnfunc_func_door_rotating()
1517 {
1518
1519         //if (!self.deathtype) // map makers can override this
1520         //      self.deathtype = " got in the way";
1521
1522         // I abuse "movedir" for denoting the axis for now
1523         if (self.spawnflags & 64) // X (untested)
1524                 self.movedir = '0 0 1';
1525         else if (self.spawnflags & 128) // Y (untested)
1526                 self.movedir = '1 0 0';
1527         else // Z
1528                 self.movedir = '0 1 0';
1529
1530         if (self.angles_y==0) self.angles_y = 90;
1531
1532         self.movedir = self.movedir * self.angles_y;
1533         self.angles = '0 0 0';
1534
1535         self.max_health = self.health;
1536         self.avelocity = self.movedir;
1537         if not(InitMovingBrushTrigger())
1538                 return;
1539         self.velocity = '0 0 0';
1540         //self.effects |= EF_LOWPRECISION;
1541         self.classname = "door_rotating";
1542
1543         self.blocked = door_blocked;
1544         self.use = door_use;
1545
1546     if(self.spawnflags & 8)
1547         self.dmg = 10000;
1548
1549     if(self.dmg && (!self.message))
1550                 self.message = "was squished";
1551     if(self.dmg && (!self.message2))
1552                 self.message2 = "was squished by";
1553
1554     if (self.sounds > 0)
1555         {
1556                 precache_sound ("plats/medplat1.wav");
1557                 precache_sound ("plats/medplat2.wav");
1558                 self.noise2 = "plats/medplat1.wav";
1559                 self.noise1 = "plats/medplat2.wav";
1560         }
1561
1562         if (!self.speed)
1563                 self.speed = 50;
1564         if (!self.wait)
1565                 self.wait = 1;
1566         self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1567
1568         self.pos1 = '0 0 0';
1569         self.pos2 = self.movedir;
1570
1571 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1572 // but spawn in the open position
1573         if (self.spawnflags & DOOR_START_OPEN)
1574                 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1575
1576         self.state = STATE_BOTTOM;
1577
1578         if (self.health)
1579         {
1580                 self.takedamage = DAMAGE_YES;
1581                 self.event_damage = door_damage;
1582         }
1583
1584         if (self.items)
1585                 self.wait = -1;
1586
1587         self.touch = door_touch;
1588
1589 // LinkDoors can't be done until all of the doors have been spawned, so
1590 // the sizes can be detected properly.
1591         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1592
1593         self.reset = door_rotating_reset;
1594 }
1595
1596 /*
1597 =============================================================================
1598
1599 SECRET DOORS
1600
1601 =============================================================================
1602 */
1603
1604 void() fd_secret_move1;
1605 void() fd_secret_move2;
1606 void() fd_secret_move3;
1607 void() fd_secret_move4;
1608 void() fd_secret_move5;
1609 void() fd_secret_move6;
1610 void() fd_secret_done;
1611
1612 float SECRET_OPEN_ONCE = 1;             // stays open
1613 float SECRET_1ST_LEFT = 2;              // 1st move is left of arrow
1614 float SECRET_1ST_DOWN = 4;              // 1st move is down from arrow
1615 float SECRET_NO_SHOOT = 8;              // only opened by trigger
1616 float SECRET_YES_SHOOT = 16;    // shootable even if targeted
1617
1618 void fd_secret_use()
1619 {
1620         float temp;
1621         string message_save;
1622
1623         self.health = 10000;
1624         self.bot_attack = TRUE;
1625
1626         // exit if still moving around...
1627         if (self.origin != self.oldorigin)
1628                 return;
1629
1630         message_save = self.message;
1631         self.message = ""; // no more message
1632         SUB_UseTargets();                               // fire all targets / killtargets
1633         self.message = message_save;
1634
1635         self.velocity = '0 0 0';
1636
1637         // Make a sound, wait a little...
1638
1639         if (self.noise1 != "")
1640                 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1641         self.nextthink = self.ltime + 0.1;
1642
1643         temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1644         makevectors(self.mangle);
1645
1646         if (!self.t_width)
1647         {
1648                 if (self.spawnflags & SECRET_1ST_DOWN)
1649                         self.t_width = fabs(v_up * self.size);
1650                 else
1651                         self.t_width = fabs(v_right * self.size);
1652         }
1653
1654         if (!self.t_length)
1655                 self.t_length = fabs(v_forward * self.size);
1656
1657         if (self.spawnflags & SECRET_1ST_DOWN)
1658                 self.dest1 = self.origin - v_up * self.t_width;
1659         else
1660                 self.dest1 = self.origin + v_right * (self.t_width * temp);
1661
1662         self.dest2 = self.dest1 + v_forward * self.t_length;
1663         SUB_CalcMove(self.dest1, self.speed, fd_secret_move1);
1664         if (self.noise2 != "")
1665                 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1666 }
1667
1668 void fd_secret_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1669 {
1670         fd_secret_use();
1671 }
1672
1673 // Wait after first movement...
1674 void fd_secret_move1()
1675 {
1676         self.nextthink = self.ltime + 1.0;
1677         self.think = fd_secret_move2;
1678         if (self.noise3 != "")
1679                 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1680 }
1681
1682 // Start moving sideways w/sound...
1683 void fd_secret_move2()
1684 {
1685         if (self.noise2 != "")
1686                 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1687         SUB_CalcMove(self.dest2, self.speed, fd_secret_move3);
1688 }
1689
1690 // Wait here until time to go back...
1691 void fd_secret_move3()
1692 {
1693         if (self.noise3 != "")
1694                 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1695         if (!(self.spawnflags & SECRET_OPEN_ONCE))
1696         {
1697                 self.nextthink = self.ltime + self.wait;
1698                 self.think = fd_secret_move4;
1699         }
1700 }
1701
1702 // Move backward...
1703 void fd_secret_move4()
1704 {
1705         if (self.noise2 != "")
1706                 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1707         SUB_CalcMove(self.dest1, self.speed, fd_secret_move5);
1708 }
1709
1710 // Wait 1 second...
1711 void fd_secret_move5()
1712 {
1713         self.nextthink = self.ltime + 1.0;
1714         self.think = fd_secret_move6;
1715         if (self.noise3 != "")
1716                 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1717 }
1718
1719 void fd_secret_move6()
1720 {
1721         if (self.noise2 != "")
1722                 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1723         SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done);
1724 }
1725
1726 void fd_secret_done()
1727 {
1728         if (self.spawnflags&SECRET_YES_SHOOT)
1729         {
1730                 self.health = 10000;
1731                 self.takedamage = DAMAGE_YES;
1732                 //self.th_pain = fd_secret_use;
1733         }
1734         if (self.noise3 != "")
1735                 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1736 }
1737
1738 void secret_blocked()
1739 {
1740         if (time < self.attack_finished_single)
1741                 return;
1742         self.attack_finished_single = time + 0.5;
1743         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1744 }
1745
1746 /*
1747 ==============
1748 secret_touch
1749
1750 Prints messages
1751 ================
1752 */
1753 void secret_touch()
1754 {
1755         if not(other.iscreature)
1756                 return;
1757         if (self.attack_finished_single > time)
1758                 return;
1759
1760         self.attack_finished_single = time + 2;
1761
1762         if (self.message)
1763         {
1764                 if (other.flags & FL_CLIENT)
1765                         centerprint (other, self.message);
1766                 play2(other, "misc/talk.wav");
1767         }
1768 }
1769
1770 void secret_reset()
1771 {
1772         if (self.spawnflags&SECRET_YES_SHOOT)
1773         {
1774                 self.health = 10000;
1775                 self.takedamage = DAMAGE_YES;
1776         }
1777         setorigin(self, self.oldorigin);
1778         self.think = func_null;
1779         self.nextthink = 0;
1780 }
1781
1782 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1783 Basic secret door. Slides back, then to the side. Angle determines direction.
1784 wait  = # of seconds before coming back
1785 1st_left = 1st move is left of arrow
1786 1st_down = 1st move is down from arrow
1787 always_shoot = even if targeted, keep shootable
1788 t_width = override WIDTH to move back (or height if going down)
1789 t_length = override LENGTH to move sideways
1790 "dmg"           damage to inflict when blocked (2 default)
1791
1792 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1793 "sounds"
1794 1) medieval
1795 2) metal
1796 3) base
1797 */
1798
1799 void spawnfunc_func_door_secret()
1800 {
1801         /*if (!self.deathtype) // map makers can override this
1802                 self.deathtype = " got in the way";*/
1803
1804         if (!self.dmg)
1805                 self.dmg = 2;
1806
1807         // Magic formula...
1808         self.mangle = self.angles;
1809         self.angles = '0 0 0';
1810         self.classname = "door";
1811         if not(InitMovingBrushTrigger())
1812                 return;
1813         self.effects |= EF_LOWPRECISION;
1814
1815         self.touch = secret_touch;
1816         self.blocked = secret_blocked;
1817         self.speed = 50;
1818         self.use = fd_secret_use;
1819         IFTARGETED
1820         {
1821         }
1822         else
1823                 self.spawnflags |= SECRET_YES_SHOOT;
1824
1825         if(self.spawnflags&SECRET_YES_SHOOT)
1826         {
1827                 self.health = 10000;
1828                 self.takedamage = DAMAGE_YES;
1829                 self.event_damage = fd_secret_damage;
1830         }
1831         self.oldorigin = self.origin;
1832         if (!self.wait)
1833                 self.wait = 5;          // 5 seconds before closing
1834
1835         self.reset = secret_reset;
1836         secret_reset();
1837 }
1838
1839 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1840 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1841 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
1842 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1843 height: amplitude modifier (default 32)
1844 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1845 noise: path/name of looping .wav file to play.
1846 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1847 dmgtime: See above.
1848 */
1849
1850 void func_fourier_controller_think()
1851 {
1852         vector v;
1853         float n, i, t;
1854
1855         self.nextthink = time + 0.1;
1856         if not (self.owner.active == ACTIVE_ACTIVE)
1857         {
1858                 self.owner.velocity = '0 0 0';          
1859                 return;
1860         }
1861
1862
1863         n = floor((tokenize_console(self.owner.netname)) / 5);
1864         t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1865
1866         v = self.owner.destvec;
1867
1868         for(i = 0; i < n; ++i)
1869         {
1870                 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1871                 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;
1872         }
1873
1874         if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1875                 // * 10 so it will arrive in 0.1 sec
1876                 self.owner.velocity = (v - self.owner.origin) * 10;
1877 }
1878
1879 void spawnfunc_func_fourier()
1880 {
1881         entity controller;
1882         if (self.noise != "")
1883         {
1884                 precache_sound(self.noise);
1885                 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
1886         }
1887
1888         if (!self.speed)
1889                 self.speed = 4;
1890         if (!self.height)
1891                 self.height = 32;
1892         self.destvec = self.origin;
1893         self.cnt = 360 / self.speed;
1894
1895         self.blocked = generic_plat_blocked;
1896         if(self.dmg & (!self.message))
1897                 self.message = " was squished";
1898     if(self.dmg && (!self.message2))
1899                 self.message2 = "was squished by";
1900         if(self.dmg && (!self.dmgtime))
1901                 self.dmgtime = 0.25;
1902         self.dmgtime2 = time;
1903
1904         if(self.netname == "")
1905                 self.netname = "1 0 0 0 1";
1906
1907         if not(InitMovingBrushTrigger())
1908                 return;
1909
1910         self.active = ACTIVE_ACTIVE;
1911
1912         // wait for targets to spawn
1913         controller = spawn();
1914         controller.classname = "func_fourier_controller";
1915         controller.owner = self;
1916         controller.nextthink = time + 1;
1917         controller.think = func_fourier_controller_think;
1918         self.nextthink = self.ltime + 999999999;
1919         self.think = SUB_NullThink; // for PushMove
1920
1921         // Savage: Reduce bandwith, critical on e.g. nexdm02
1922         self.effects |= EF_LOWPRECISION;
1923
1924         // TODO make a reset function for this one
1925 }
1926
1927 // reusing some fields havocbots declared
1928 .entity wp00, wp01, wp02, wp03;
1929
1930 .float targetfactor, target2factor, target3factor, target4factor;
1931 .vector targetnormal, target2normal, target3normal, target4normal;
1932
1933 vector func_vectormamamam_origin(entity o, float t)
1934 {
1935         vector v, p;
1936         float f;
1937         entity e;
1938
1939         f = o.spawnflags;
1940         v = '0 0 0';
1941
1942         e = o.wp00;
1943         if(e)
1944         {
1945                 p = e.origin + t * e.velocity;
1946                 if(f & 1)
1947                         v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
1948                 else
1949                         v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
1950         }
1951
1952         e = o.wp01;
1953         if(e)
1954         {
1955                 p = e.origin + t * e.velocity;
1956                 if(f & 2)
1957                         v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
1958                 else
1959                         v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
1960         }
1961
1962         e = o.wp02;
1963         if(e)
1964         {
1965                 p = e.origin + t * e.velocity;
1966                 if(f & 4)
1967                         v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
1968                 else
1969                         v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
1970         }
1971
1972         e = o.wp03;
1973         if(e)
1974         {
1975                 p = e.origin + t * e.velocity;
1976                 if(f & 8)
1977                         v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
1978                 else
1979                         v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
1980         }
1981
1982         return v;
1983 }
1984
1985 void func_vectormamamam_controller_think()
1986 {
1987         self.nextthink = time + 0.1;
1988
1989         if not (self.owner.active == ACTIVE_ACTIVE)
1990         {
1991                 self.owner.velocity = '0 0 0';          
1992                 return;
1993         }
1994
1995         if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
1996                 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
1997 }
1998
1999 void func_vectormamamam_findtarget()
2000 {
2001         if(self.target != "")
2002                 self.wp00 = find(world, targetname, self.target);
2003
2004         if(self.target2 != "")
2005                 self.wp01 = find(world, targetname, self.target2);
2006
2007         if(self.target3 != "")
2008                 self.wp02 = find(world, targetname, self.target3);
2009
2010         if(self.target4 != "")
2011                 self.wp03 = find(world, targetname, self.target4);
2012
2013         if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
2014                 objerror("No reference entity found, so there is nothing to move. Aborting.");
2015
2016         self.destvec = self.origin - func_vectormamamam_origin(self, 0);
2017
2018         entity controller;
2019         controller = spawn();
2020         controller.classname = "func_vectormamamam_controller";
2021         controller.owner = self;
2022         controller.nextthink = time + 1;
2023         controller.think = func_vectormamamam_controller_think;
2024 }
2025
2026 void spawnfunc_func_vectormamamam()
2027 {
2028         if (self.noise != "")
2029         {
2030                 precache_sound(self.noise);
2031                 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
2032         }
2033
2034         if(!self.targetfactor)
2035                 self.targetfactor = 1;
2036
2037         if(!self.target2factor)
2038                 self.target2factor = 1;
2039
2040         if(!self.target3factor)
2041                 self.target3factor = 1;
2042
2043         if(!self.target4factor)
2044                 self.target4factor = 1;
2045
2046         if(vlen(self.targetnormal))
2047                 self.targetnormal = normalize(self.targetnormal);
2048
2049         if(vlen(self.target2normal))
2050                 self.target2normal = normalize(self.target2normal);
2051
2052         if(vlen(self.target3normal))
2053                 self.target3normal = normalize(self.target3normal);
2054
2055         if(vlen(self.target4normal))
2056                 self.target4normal = normalize(self.target4normal);
2057
2058         self.blocked = generic_plat_blocked;
2059         if(self.dmg & (!self.message))
2060                 self.message = " was squished";
2061     if(self.dmg && (!self.message2))
2062                 self.message2 = "was squished by";
2063         if(self.dmg && (!self.dmgtime))
2064                 self.dmgtime = 0.25;
2065         self.dmgtime2 = time;
2066
2067         if(self.netname == "")
2068                 self.netname = "1 0 0 0 1";
2069
2070         if not(InitMovingBrushTrigger())
2071                 return;
2072
2073         // wait for targets to spawn
2074         self.nextthink = self.ltime + 999999999;
2075         self.think = SUB_NullThink; // for PushMove
2076
2077         // Savage: Reduce bandwith, critical on e.g. nexdm02
2078         self.effects |= EF_LOWPRECISION;
2079
2080         self.active = ACTIVE_ACTIVE;
2081
2082         InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
2083 }
2084
2085 void conveyor_think()
2086 {
2087         entity e;
2088
2089         // set myself as current conveyor where possible
2090         for(e = world; (e = findentity(e, conveyor, self)); )
2091                 e.conveyor = world;
2092
2093         if(self.state)
2094         {
2095                 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2096                         if(!e.conveyor.state)
2097                                 if(isPushable(e))
2098                                 {
2099                                         vector emin = e.absmin;
2100                                         vector emax = e.absmax;
2101                                         if(self.solid == SOLID_BSP)
2102                                         {
2103                                                 emin -= '1 1 1';
2104                                                 emax += '1 1 1';
2105                                         }
2106                                         if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2107                                                 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2108                                                         e.conveyor = self;
2109                                 }
2110
2111                 for(e = world; (e = findentity(e, conveyor, self)); )
2112                 {
2113                         if(e.flags & FL_CLIENT) // doing it via velocity has quite some advantages
2114                                 continue; // done in SV_PlayerPhysics
2115
2116                         setorigin(e, e.origin + self.movedir * sys_frametime);
2117                         move_out_of_solid(e);
2118                         UpdateCSQCProjectile(e);
2119                         /*
2120                         // stupid conveyor code
2121                         tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2122                         if(trace_fraction > 0)
2123                                 setorigin(e, trace_endpos);
2124                         */
2125                 }
2126         }
2127
2128         self.nextthink = time;
2129 }
2130
2131 void conveyor_use()
2132 {
2133         self.state = !self.state;
2134 }
2135
2136 void conveyor_reset()
2137 {
2138         self.state = (self.spawnflags & 1);
2139 }
2140
2141 void conveyor_init()
2142 {
2143         if (!self.speed)
2144                 self.speed = 200;
2145         self.movedir = self.movedir * self.speed;
2146         self.think = conveyor_think;
2147         self.nextthink = time;
2148         IFTARGETED
2149         {
2150                 self.use = conveyor_use;
2151                 self.reset = conveyor_reset;
2152                 conveyor_reset();
2153         }
2154         else
2155                 self.state = 1;
2156 }
2157
2158 void spawnfunc_trigger_conveyor()
2159 {
2160         SetMovedir();
2161         EXACTTRIGGER_INIT;
2162         conveyor_init();
2163 }
2164
2165 void spawnfunc_func_conveyor()
2166 {
2167         SetMovedir();
2168         InitMovingBrushTrigger();
2169         self.movetype = MOVETYPE_NONE;
2170         conveyor_init();
2171 }