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