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