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