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