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