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