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