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