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