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