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