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