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