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