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