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