]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/t_plats.qc
Merge branch 'master' into terencehill/music_player
[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 (self.spawnflags & 2)
418         {
419                 self.platmovetype_turn = TRUE;
420                 self.view_ofs = '0 0 0'; // don't offset a rotating train, origin works differently now
421         }
422         else
423                 self.view_ofs = self.mins;
424
425         if (!InitMovingBrushTrigger())
426                 return;
427         self.effects |= EF_LOWPRECISION;
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 MinstaGib 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         local entity door;
995
996
997         if (self.owner)
998                 door = self.owner;
999         else
1000                 door = self;
1001
1002         // no key needed
1003         if (!door.itemkeys)
1004                 return TRUE;
1005
1006         // this door require a key
1007         // only a player can have a key
1008         if (!IS_PLAYER(other))
1009                 return FALSE;
1010
1011         if (item_keys_usekey(door, other)) {
1012                 // some keys were used
1013                 if (other.key_door_messagetime <= time) {
1014                         play2(other, "misc/talk.wav");
1015                         centerprint(other, strcat("You also need ", item_keys_keylist(door.itemkeys), "!"));
1016                         other.key_door_messagetime = time + 2;
1017                 }
1018         } else {
1019                 // no keys were used
1020                 if (other.key_door_messagetime <= time) {
1021                         play2(other, "misc/talk.wav");
1022                         centerprint(other, strcat("You need ", item_keys_keylist(door.itemkeys), "!"));
1023                         other.key_door_messagetime = time + 2;
1024                 }
1025         }
1026
1027         if (door.itemkeys) {
1028                 // door is now unlocked
1029                 play2(other, "misc/talk.wav");
1030                 centerprint(other, "Door unlocked!");
1031                 return TRUE;
1032         } else
1033                 return FALSE;
1034 }
1035
1036
1037 void door_fire()
1038 {
1039         entity  oself;
1040         entity  starte;
1041
1042         if (self.owner != self)
1043                 objerror ("door_fire: self.owner != self");
1044
1045         oself = self;
1046
1047         if (self.spawnflags & DOOR_TOGGLE)
1048         {
1049                 if (self.state == STATE_UP || self.state == STATE_TOP)
1050                 {
1051                         starte = self;
1052                         do
1053                         {
1054                                 if (self.classname == "door")
1055                                 {
1056                                         door_go_down ();
1057                                 }
1058                                 else
1059                                 {
1060                                         door_rotating_go_down ();
1061                                 }
1062                                 self = self.enemy;
1063                         } while ( (self != starte) && (self != world) );
1064                         self = oself;
1065                         return;
1066                 }
1067         }
1068
1069 // trigger all paired doors
1070         starte = self;
1071         do
1072         {
1073                 if (self.classname == "door")
1074                 {
1075                         door_go_up ();
1076                 } else
1077                 {
1078                         // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
1079                         if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
1080                         {
1081                                 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
1082                                 self.pos2 = '0 0 0' - self.pos2;
1083                         }
1084                         // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
1085                         if (!((self.spawnflags & 2) &&  (self.spawnflags & 8) && self.state == STATE_DOWN
1086                             && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
1087                         {
1088                                 door_rotating_go_up ();
1089                         }
1090                 }
1091                 self = self.enemy;
1092         } while ( (self != starte) && (self != world) );
1093         self = oself;
1094 }
1095
1096
1097 void door_use()
1098 {
1099         entity oself;
1100
1101         //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
1102
1103         if (self.owner)
1104         {
1105                 oself = self;
1106                 self = self.owner;
1107                 door_fire ();
1108                 self = oself;
1109         }
1110 }
1111
1112
1113 void door_trigger_touch()
1114 {
1115         if (other.health < 1)
1116                 if (!(other.iscreature && other.deadflag == DEAD_NO))
1117                         return;
1118
1119         if (time < self.attack_finished_single)
1120                 return;
1121
1122         // check if door is locked
1123         if (!door_check_keys())
1124                 return;
1125
1126         self.attack_finished_single = time + 1;
1127
1128         activator = other;
1129
1130         self = self.owner;
1131         door_use ();
1132 }
1133
1134
1135 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1136 {
1137         entity oself;
1138         if(self.spawnflags & DOOR_NOSPLASH)
1139                 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
1140                         return;
1141         self.health = self.health - damage;
1142
1143         if (self.itemkeys) {
1144                 // don't allow opening doors through damage if keys are required
1145                 return;
1146         }
1147
1148         if (self.health <= 0)
1149         {
1150                 oself = self;
1151                 self = self.owner;
1152                 self.health = self.max_health;
1153                 self.takedamage = DAMAGE_NO;    // wil be reset upon return
1154                 door_use ();
1155                 self = oself;
1156         }
1157 }
1158
1159
1160 /*
1161 ================
1162 door_touch
1163
1164 Prints messages
1165 ================
1166 */
1167 void door_touch()
1168 {
1169         if (!IS_PLAYER(other))
1170                 return;
1171         if (self.owner.attack_finished_single > time)
1172                 return;
1173
1174         self.owner.attack_finished_single = time + 2;
1175
1176         if (!(self.owner.dmg) && (self.owner.message != ""))
1177         {
1178                 if (IS_CLIENT(other))
1179                         centerprint (other, self.owner.message);
1180                 play2(other, "misc/talk.wav");
1181         }
1182 }
1183
1184
1185 void door_generic_plat_blocked()
1186 {
1187
1188     if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1189         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1190     } else {
1191
1192         if((self.dmg) && (other.takedamage == DAMAGE_YES))    // Shall we bite?
1193             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1194
1195          //Dont chamge direction for dead or dying stuff
1196         if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1197             if (self.wait >= 0)
1198             {
1199                 if (self.state == STATE_DOWN)
1200                     door_rotating_go_up ();
1201                 else
1202                     door_rotating_go_down ();
1203             }
1204         } else {
1205             //gib dying stuff just to make sure
1206             if((self.dmg) && (other.takedamage != DAMAGE_NO))    // Shall we bite?
1207                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1208         }
1209     }
1210
1211         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1212 // if a door has a negative wait, it would never come back if blocked,
1213 // so let it just squash the object to death real fast
1214 /*      if (self.wait >= 0)
1215         {
1216                 if (self.state == STATE_DOWN)
1217                         door_rotating_go_up ();
1218                 else
1219                         door_rotating_go_down ();
1220         }
1221 */
1222 }
1223
1224
1225 void door_rotating_hit_top()
1226 {
1227         if (self.noise1 != "")
1228                 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1229         self.state = STATE_TOP;
1230         if (self.spawnflags & DOOR_TOGGLE)
1231                 return;         // don't come down automatically
1232         self.think = door_rotating_go_down;
1233         self.nextthink = self.ltime + self.wait;
1234 }
1235
1236 void door_rotating_hit_bottom()
1237 {
1238         if (self.noise1 != "")
1239                 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1240         if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1241         {
1242                 self.pos2 = '0 0 0' - self.pos2;
1243                 self.lip = 0;
1244         }
1245         self.state = STATE_BOTTOM;
1246 }
1247
1248 void door_rotating_go_down()
1249 {
1250         if (self.noise2 != "")
1251                 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1252         if (self.max_health)
1253         {
1254                 self.takedamage = DAMAGE_YES;
1255                 self.health = self.max_health;
1256         }
1257
1258         self.state = STATE_DOWN;
1259         SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
1260 }
1261
1262 void door_rotating_go_up()
1263 {
1264         if (self.state == STATE_UP)
1265                 return;         // already going up
1266
1267         if (self.state == STATE_TOP)
1268         {       // reset top wait time
1269                 self.nextthink = self.ltime + self.wait;
1270                 return;
1271         }
1272         if (self.noise2 != "")
1273                 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1274         self.state = STATE_UP;
1275         SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
1276
1277         string oldmessage;
1278         oldmessage = self.message;
1279         self.message = "";
1280         SUB_UseTargets();
1281         self.message = oldmessage;
1282 }
1283
1284
1285
1286
1287 /*
1288 =============================================================================
1289
1290 SPAWNING FUNCTIONS
1291
1292 =============================================================================
1293 */
1294
1295
1296 entity spawn_field(vector fmins, vector fmaxs)
1297 {
1298         entity  trigger;
1299         vector  t1, t2;
1300
1301         trigger = spawn();
1302         trigger.classname = "doortriggerfield";
1303         trigger.movetype = MOVETYPE_NONE;
1304         trigger.solid = SOLID_TRIGGER;
1305         trigger.owner = self;
1306         trigger.touch = door_trigger_touch;
1307
1308         t1 = fmins;
1309         t2 = fmaxs;
1310         setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1311         return (trigger);
1312 }
1313
1314
1315 entity LinkDoors_nextent(entity cur, entity near, entity pass)
1316 {
1317         while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
1318         {
1319         }
1320         return cur;
1321 }
1322
1323 float LinkDoors_isconnected(entity e1, entity e2, entity pass)
1324 {
1325         float DELTA = 4;
1326         if (e1.absmin_x > e2.absmax_x + DELTA)
1327                 return FALSE;
1328         if (e1.absmin_y > e2.absmax_y + DELTA)
1329                 return FALSE;
1330         if (e1.absmin_z > e2.absmax_z + DELTA)
1331                 return FALSE;
1332         if (e2.absmin_x > e1.absmax_x + DELTA)
1333                 return FALSE;
1334         if (e2.absmin_y > e1.absmax_y + DELTA)
1335                 return FALSE;
1336         if (e2.absmin_z > e1.absmax_z + DELTA)
1337                 return FALSE;
1338         return TRUE;
1339 }
1340
1341 /*
1342 =============
1343 LinkDoors
1344
1345
1346 =============
1347 */
1348 void LinkDoors()
1349 {
1350         entity  t;
1351         vector  cmins, cmaxs;
1352
1353         if (self.enemy)
1354                 return;         // already linked by another door
1355         if (self.spawnflags & 4)
1356         {
1357                 self.owner = self.enemy = self;
1358
1359                 if (self.health)
1360                         return;
1361                 IFTARGETED
1362                         return;
1363                 if (self.items)
1364                         return;
1365                 self.trigger_field = spawn_field(self.absmin, self.absmax);
1366
1367                 return;         // don't want to link this door
1368         }
1369
1370         FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
1371
1372         // set owner, and make a loop of the chain
1373         dprint("LinkDoors: linking doors:");
1374         for(t = self; ; t = t.enemy)
1375         {
1376                 dprint(" ", etos(t));
1377                 t.owner = self;
1378                 if(t.enemy == world)
1379                 {
1380                         t.enemy = self;
1381                         break;
1382                 }
1383         }
1384         dprint("\n");
1385
1386         // collect health, targetname, message, size
1387         cmins = self.absmin;
1388         cmaxs = self.absmax;
1389         for(t = self; ; t = t.enemy)
1390         {
1391                 if(t.health && !self.health)
1392                         self.health = t.health;
1393                 if((t.targetname != "") && (self.targetname == ""))
1394                         self.targetname = t.targetname;
1395                 if((t.message != "") && (self.message == ""))
1396                         self.message = t.message;
1397                 if (t.absmin_x < cmins_x)
1398                         cmins_x = t.absmin_x;
1399                 if (t.absmin_y < cmins_y)
1400                         cmins_y = t.absmin_y;
1401                 if (t.absmin_z < cmins_z)
1402                         cmins_z = t.absmin_z;
1403                 if (t.absmax_x > cmaxs_x)
1404                         cmaxs_x = t.absmax_x;
1405                 if (t.absmax_y > cmaxs_y)
1406                         cmaxs_y = t.absmax_y;
1407                 if (t.absmax_z > cmaxs_z)
1408                         cmaxs_z = t.absmax_z;
1409                 if(t.enemy == self)
1410                         break;
1411         }
1412
1413         // distribute health, targetname, message
1414         for(t = self; t; t = t.enemy)
1415         {
1416                 t.health = self.health;
1417                 t.targetname = self.targetname;
1418                 t.message = self.message;
1419                 if(t.enemy == self)
1420                         break;
1421         }
1422
1423         // shootable, or triggered doors just needed the owner/enemy links,
1424         // they don't spawn a field
1425
1426         if (self.health)
1427                 return;
1428         IFTARGETED
1429                 return;
1430         if (self.items)
1431                 return;
1432
1433         self.trigger_field = spawn_field(cmins, cmaxs);
1434 }
1435
1436
1437 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
1438 if two doors touch, they are assumed to be connected and operate as a unit.
1439
1440 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1441
1442 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).
1443
1444 GOLD_KEY causes the door to open only if the activator holds a gold key.
1445
1446 SILVER_KEY causes the door to open only if the activator holds a silver key.
1447
1448 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1449 "angle"         determines the opening direction
1450 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1451 "health"        if set, door must be shot open
1452 "speed"         movement speed (100 default)
1453 "wait"          wait before returning (3 default, -1 = never return)
1454 "lip"           lip remaining at end of move (8 default)
1455 "dmg"           damage to inflict when blocked (2 default)
1456 "sounds"
1457 0)      no sound
1458 1)      stone
1459 2)      base
1460 3)      stone chain
1461 4)      screechy metal
1462 FIXME: only one sound set available at the time being
1463
1464 */
1465
1466 void door_init_startopen()
1467 {
1468         setorigin (self, self.pos2);
1469         self.pos2 = self.pos1;
1470         self.pos1 = self.origin;
1471 }
1472
1473 void door_reset()
1474 {
1475         setorigin(self, self.pos1);
1476         self.velocity = '0 0 0';
1477         self.state = STATE_BOTTOM;
1478         self.think = func_null;
1479         self.nextthink = 0;
1480 }
1481
1482 // spawnflags require key (for now only func_door)
1483 #define SPAWNFLAGS_GOLD_KEY 8
1484 #define SPAWNFLAGS_SILVER_KEY 16
1485 void spawnfunc_func_door()
1486 {
1487         // Quake 1 keys compatibility
1488         if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
1489                 self.itemkeys |= ITEM_KEY_BIT(0);
1490         if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
1491                 self.itemkeys |= ITEM_KEY_BIT(1);
1492
1493         //if (!self.deathtype) // map makers can override this
1494         //      self.deathtype = " got in the way";
1495         SetMovedir ();
1496
1497         self.max_health = self.health;
1498         if (!InitMovingBrushTrigger())
1499                 return;
1500         self.effects |= EF_LOWPRECISION;
1501         self.classname = "door";
1502
1503         self.blocked = door_blocked;
1504         self.use = door_use;
1505
1506         // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
1507         // if(self.spawnflags & 8)
1508         //      self.dmg = 10000;
1509
1510     if(self.dmg && (self.message == ""))
1511                 self.message = "was squished";
1512     if(self.dmg && (self.message2 == ""))
1513                 self.message2 = "was squished by";
1514
1515         if (self.sounds > 0)
1516         {
1517                 precache_sound ("plats/medplat1.wav");
1518                 precache_sound ("plats/medplat2.wav");
1519                 self.noise2 = "plats/medplat1.wav";
1520                 self.noise1 = "plats/medplat2.wav";
1521         }
1522
1523         if (!self.speed)
1524                 self.speed = 100;
1525         if (!self.wait)
1526                 self.wait = 3;
1527         if (!self.lip)
1528                 self.lip = 8;
1529
1530         self.pos1 = self.origin;
1531         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1532
1533 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1534 // but spawn in the open position
1535         if (self.spawnflags & DOOR_START_OPEN)
1536                 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1537
1538         self.state = STATE_BOTTOM;
1539
1540         if (self.health)
1541         {
1542                 self.takedamage = DAMAGE_YES;
1543                 self.event_damage = door_damage;
1544         }
1545
1546         if (self.items)
1547                 self.wait = -1;
1548
1549         self.touch = door_touch;
1550
1551 // LinkDoors can't be done until all of the doors have been spawned, so
1552 // the sizes can be detected properly.
1553         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1554
1555         self.reset = door_reset;
1556 }
1557
1558 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1559 if two doors touch, they are assumed to be connected and operate as a unit.
1560
1561 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1562
1563 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1564 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1565 must have set trigger_reverse to 1.
1566 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1567
1568 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).
1569
1570 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1571 "angle"         determines the destination angle for opening. negative values reverse the direction.
1572 "targetname"    if set, no touch field will be spawned and a remote button or trigger field activates the door.
1573 "health"        if set, door must be shot open
1574 "speed"         movement speed (100 default)
1575 "wait"          wait before returning (3 default, -1 = never return)
1576 "dmg"           damage to inflict when blocked (2 default)
1577 "sounds"
1578 0)      no sound
1579 1)      stone
1580 2)      base
1581 3)      stone chain
1582 4)      screechy metal
1583 FIXME: only one sound set available at the time being
1584 */
1585
1586 void door_rotating_reset()
1587 {
1588         self.angles = self.pos1;
1589         self.avelocity = '0 0 0';
1590         self.state = STATE_BOTTOM;
1591         self.think = func_null;
1592         self.nextthink = 0;
1593 }
1594
1595 void door_rotating_init_startopen()
1596 {
1597         self.angles = self.movedir;
1598         self.pos2 = '0 0 0';
1599         self.pos1 = self.movedir;
1600 }
1601
1602
1603 void spawnfunc_func_door_rotating()
1604 {
1605
1606         //if (!self.deathtype) // map makers can override this
1607         //      self.deathtype = " got in the way";
1608
1609         // I abuse "movedir" for denoting the axis for now
1610         if (self.spawnflags & 64) // X (untested)
1611                 self.movedir = '0 0 1';
1612         else if (self.spawnflags & 128) // Y (untested)
1613                 self.movedir = '1 0 0';
1614         else // Z
1615                 self.movedir = '0 1 0';
1616
1617         if (self.angles_y==0) self.angles_y = 90;
1618
1619         self.movedir = self.movedir * self.angles_y;
1620         self.angles = '0 0 0';
1621
1622         self.max_health = self.health;
1623         self.avelocity = self.movedir;
1624         if (!InitMovingBrushTrigger())
1625                 return;
1626         self.velocity = '0 0 0';
1627         //self.effects |= EF_LOWPRECISION;
1628         self.classname = "door_rotating";
1629
1630         self.blocked = door_blocked;
1631         self.use = door_use;
1632
1633     if(self.spawnflags & 8)
1634         self.dmg = 10000;
1635
1636     if(self.dmg && (self.message == ""))
1637                 self.message = "was squished";
1638     if(self.dmg && (self.message2 == ""))
1639                 self.message2 = "was squished by";
1640
1641     if (self.sounds > 0)
1642         {
1643                 precache_sound ("plats/medplat1.wav");
1644                 precache_sound ("plats/medplat2.wav");
1645                 self.noise2 = "plats/medplat1.wav";
1646                 self.noise1 = "plats/medplat2.wav";
1647         }
1648
1649         if (!self.speed)
1650                 self.speed = 50;
1651         if (!self.wait)
1652                 self.wait = 1;
1653         self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1654
1655         self.pos1 = '0 0 0';
1656         self.pos2 = self.movedir;
1657
1658 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1659 // but spawn in the open position
1660         if (self.spawnflags & DOOR_START_OPEN)
1661                 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1662
1663         self.state = STATE_BOTTOM;
1664
1665         if (self.health)
1666         {
1667                 self.takedamage = DAMAGE_YES;
1668                 self.event_damage = door_damage;
1669         }
1670
1671         if (self.items)
1672                 self.wait = -1;
1673
1674         self.touch = door_touch;
1675
1676 // LinkDoors can't be done until all of the doors have been spawned, so
1677 // the sizes can be detected properly.
1678         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1679
1680         self.reset = door_rotating_reset;
1681 }
1682
1683 /*
1684 =============================================================================
1685
1686 SECRET DOORS
1687
1688 =============================================================================
1689 */
1690
1691 void() fd_secret_move1;
1692 void() fd_secret_move2;
1693 void() fd_secret_move3;
1694 void() fd_secret_move4;
1695 void() fd_secret_move5;
1696 void() fd_secret_move6;
1697 void() fd_secret_done;
1698
1699 const float SECRET_OPEN_ONCE = 1;               // stays open
1700 const float SECRET_1ST_LEFT = 2;                // 1st move is left of arrow
1701 const float SECRET_1ST_DOWN = 4;                // 1st move is down from arrow
1702 const float SECRET_NO_SHOOT = 8;                // only opened by trigger
1703 const float SECRET_YES_SHOOT = 16;      // shootable even if targeted
1704
1705 void fd_secret_use()
1706 {
1707         float temp;
1708         string message_save;
1709
1710         self.health = 10000;
1711         self.bot_attack = TRUE;
1712
1713         // exit if still moving around...
1714         if (self.origin != self.oldorigin)
1715                 return;
1716
1717         message_save = self.message;
1718         self.message = ""; // no more message
1719         SUB_UseTargets();                               // fire all targets / killtargets
1720         self.message = message_save;
1721
1722         self.velocity = '0 0 0';
1723
1724         // Make a sound, wait a little...
1725
1726         if (self.noise1 != "")
1727                 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1728         self.nextthink = self.ltime + 0.1;
1729
1730         temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1731         makevectors(self.mangle);
1732
1733         if (!self.t_width)
1734         {
1735                 if (self.spawnflags & SECRET_1ST_DOWN)
1736                         self.t_width = fabs(v_up * self.size);
1737                 else
1738                         self.t_width = fabs(v_right * self.size);
1739         }
1740
1741         if (!self.t_length)
1742                 self.t_length = fabs(v_forward * self.size);
1743
1744         if (self.spawnflags & SECRET_1ST_DOWN)
1745                 self.dest1 = self.origin - v_up * self.t_width;
1746         else
1747                 self.dest1 = self.origin + v_right * (self.t_width * temp);
1748
1749         self.dest2 = self.dest1 + v_forward * self.t_length;
1750         SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
1751         if (self.noise2 != "")
1752                 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1753 }
1754
1755 void fd_secret_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1756 {
1757         fd_secret_use();
1758 }
1759
1760 // Wait after first movement...
1761 void fd_secret_move1()
1762 {
1763         self.nextthink = self.ltime + 1.0;
1764         self.think = fd_secret_move2;
1765         if (self.noise3 != "")
1766                 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1767 }
1768
1769 // Start moving sideways w/sound...
1770 void fd_secret_move2()
1771 {
1772         if (self.noise2 != "")
1773                 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1774         SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
1775 }
1776
1777 // Wait here until time to go back...
1778 void fd_secret_move3()
1779 {
1780         if (self.noise3 != "")
1781                 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1782         if (!(self.spawnflags & SECRET_OPEN_ONCE))
1783         {
1784                 self.nextthink = self.ltime + self.wait;
1785                 self.think = fd_secret_move4;
1786         }
1787 }
1788
1789 // Move backward...
1790 void fd_secret_move4()
1791 {
1792         if (self.noise2 != "")
1793                 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1794         SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
1795 }
1796
1797 // Wait 1 second...
1798 void fd_secret_move5()
1799 {
1800         self.nextthink = self.ltime + 1.0;
1801         self.think = fd_secret_move6;
1802         if (self.noise3 != "")
1803                 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1804 }
1805
1806 void fd_secret_move6()
1807 {
1808         if (self.noise2 != "")
1809                 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1810         SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
1811 }
1812
1813 void fd_secret_done()
1814 {
1815         if (self.spawnflags&SECRET_YES_SHOOT)
1816         {
1817                 self.health = 10000;
1818                 self.takedamage = DAMAGE_YES;
1819                 //self.th_pain = fd_secret_use;
1820         }
1821         if (self.noise3 != "")
1822                 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1823 }
1824
1825 void secret_blocked()
1826 {
1827         if (time < self.attack_finished_single)
1828                 return;
1829         self.attack_finished_single = time + 0.5;
1830         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1831 }
1832
1833 /*
1834 ==============
1835 secret_touch
1836
1837 Prints messages
1838 ================
1839 */
1840 void secret_touch()
1841 {
1842         if (!other.iscreature)
1843                 return;
1844         if (self.attack_finished_single > time)
1845                 return;
1846
1847         self.attack_finished_single = time + 2;
1848
1849         if (self.message)
1850         {
1851                 if (IS_CLIENT(other))
1852                         centerprint (other, self.message);
1853                 play2(other, "misc/talk.wav");
1854         }
1855 }
1856
1857 void secret_reset()
1858 {
1859         if (self.spawnflags&SECRET_YES_SHOOT)
1860         {
1861                 self.health = 10000;
1862                 self.takedamage = DAMAGE_YES;
1863         }
1864         setorigin(self, self.oldorigin);
1865         self.think = func_null;
1866         self.nextthink = 0;
1867 }
1868
1869 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1870 Basic secret door. Slides back, then to the side. Angle determines direction.
1871 wait  = # of seconds before coming back
1872 1st_left = 1st move is left of arrow
1873 1st_down = 1st move is down from arrow
1874 always_shoot = even if targeted, keep shootable
1875 t_width = override WIDTH to move back (or height if going down)
1876 t_length = override LENGTH to move sideways
1877 "dmg"           damage to inflict when blocked (2 default)
1878
1879 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1880 "sounds"
1881 1) medieval
1882 2) metal
1883 3) base
1884 */
1885
1886 void spawnfunc_func_door_secret()
1887 {
1888         /*if (!self.deathtype) // map makers can override this
1889                 self.deathtype = " got in the way";*/
1890
1891         if (!self.dmg)
1892                 self.dmg = 2;
1893
1894         // Magic formula...
1895         self.mangle = self.angles;
1896         self.angles = '0 0 0';
1897         self.classname = "door";
1898         if (!InitMovingBrushTrigger())
1899                 return;
1900         self.effects |= EF_LOWPRECISION;
1901
1902         self.touch = secret_touch;
1903         self.blocked = secret_blocked;
1904         self.speed = 50;
1905         self.use = fd_secret_use;
1906         IFTARGETED
1907         {
1908         }
1909         else
1910                 self.spawnflags |= SECRET_YES_SHOOT;
1911
1912         if(self.spawnflags&SECRET_YES_SHOOT)
1913         {
1914                 self.health = 10000;
1915                 self.takedamage = DAMAGE_YES;
1916                 self.event_damage = fd_secret_damage;
1917         }
1918         self.oldorigin = self.origin;
1919         if (!self.wait)
1920                 self.wait = 5;          // 5 seconds before closing
1921
1922         self.reset = secret_reset;
1923         secret_reset();
1924 }
1925
1926 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1927 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1928 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
1929 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1930 height: amplitude modifier (default 32)
1931 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1932 noise: path/name of looping .wav file to play.
1933 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1934 dmgtime: See above.
1935 */
1936
1937 void func_fourier_controller_think()
1938 {
1939         vector v;
1940         float n, i, t;
1941
1942         self.nextthink = time + 0.1;
1943         if (!(self.owner.active == ACTIVE_ACTIVE))
1944         {
1945                 self.owner.velocity = '0 0 0';
1946                 return;
1947         }
1948
1949
1950         n = floor((tokenize_console(self.owner.netname)) / 5);
1951         t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1952
1953         v = self.owner.destvec;
1954
1955         for(i = 0; i < n; ++i)
1956         {
1957                 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1958                 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;
1959         }
1960
1961         if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1962                 // * 10 so it will arrive in 0.1 sec
1963                 self.owner.velocity = (v - self.owner.origin) * 10;
1964 }
1965
1966 void spawnfunc_func_fourier()
1967 {
1968         entity controller;
1969         if (self.noise != "")
1970         {
1971                 precache_sound(self.noise);
1972                 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
1973         }
1974
1975         if (!self.speed)
1976                 self.speed = 4;
1977         if (!self.height)
1978                 self.height = 32;
1979         self.destvec = self.origin;
1980         self.cnt = 360 / self.speed;
1981
1982         self.blocked = generic_plat_blocked;
1983         if(self.dmg && (self.message == ""))
1984                 self.message = " was squished";
1985     if(self.dmg && (self.message2 == ""))
1986                 self.message2 = "was squished by";
1987         if(self.dmg && (!self.dmgtime))
1988                 self.dmgtime = 0.25;
1989         self.dmgtime2 = time;
1990
1991         if(self.netname == "")
1992                 self.netname = "1 0 0 0 1";
1993
1994         if (!InitMovingBrushTrigger())
1995                 return;
1996
1997         self.active = ACTIVE_ACTIVE;
1998
1999         // wait for targets to spawn
2000         controller = spawn();
2001         controller.classname = "func_fourier_controller";
2002         controller.owner = self;
2003         controller.nextthink = time + 1;
2004         controller.think = func_fourier_controller_think;
2005         self.nextthink = self.ltime + 999999999;
2006         self.think = SUB_NullThink; // for PushMove
2007
2008         // Savage: Reduce bandwith, critical on e.g. nexdm02
2009         self.effects |= EF_LOWPRECISION;
2010
2011         // TODO make a reset function for this one
2012 }
2013
2014 // reusing some fields havocbots declared
2015 .entity wp00, wp01, wp02, wp03;
2016
2017 .float targetfactor, target2factor, target3factor, target4factor;
2018 .vector targetnormal, target2normal, target3normal, target4normal;
2019
2020 vector func_vectormamamam_origin(entity o, float t)
2021 {
2022         vector v, p;
2023         float f;
2024         entity e;
2025
2026         f = o.spawnflags;
2027         v = '0 0 0';
2028
2029         e = o.wp00;
2030         if(e)
2031         {
2032                 p = e.origin + t * e.velocity;
2033                 if(f & 1)
2034                         v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
2035                 else
2036                         v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
2037         }
2038
2039         e = o.wp01;
2040         if(e)
2041         {
2042                 p = e.origin + t * e.velocity;
2043                 if(f & 2)
2044                         v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
2045                 else
2046                         v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
2047         }
2048
2049         e = o.wp02;
2050         if(e)
2051         {
2052                 p = e.origin + t * e.velocity;
2053                 if(f & 4)
2054                         v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
2055                 else
2056                         v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
2057         }
2058
2059         e = o.wp03;
2060         if(e)
2061         {
2062                 p = e.origin + t * e.velocity;
2063                 if(f & 8)
2064                         v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
2065                 else
2066                         v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
2067         }
2068
2069         return v;
2070 }
2071
2072 void func_vectormamamam_controller_think()
2073 {
2074         self.nextthink = time + 0.1;
2075
2076         if (!(self.owner.active == ACTIVE_ACTIVE))
2077         {
2078                 self.owner.velocity = '0 0 0';
2079                 return;
2080         }
2081
2082         if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
2083                 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
2084 }
2085
2086 void func_vectormamamam_findtarget()
2087 {
2088         if(self.target != "")
2089                 self.wp00 = find(world, targetname, self.target);
2090
2091         if(self.target2 != "")
2092                 self.wp01 = find(world, targetname, self.target2);
2093
2094         if(self.target3 != "")
2095                 self.wp02 = find(world, targetname, self.target3);
2096
2097         if(self.target4 != "")
2098                 self.wp03 = find(world, targetname, self.target4);
2099
2100         if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
2101                 objerror("No reference entity found, so there is nothing to move. Aborting.");
2102
2103         self.destvec = self.origin - func_vectormamamam_origin(self, 0);
2104
2105         entity controller;
2106         controller = spawn();
2107         controller.classname = "func_vectormamamam_controller";
2108         controller.owner = self;
2109         controller.nextthink = time + 1;
2110         controller.think = func_vectormamamam_controller_think;
2111 }
2112
2113 void spawnfunc_func_vectormamamam()
2114 {
2115         if (self.noise != "")
2116         {
2117                 precache_sound(self.noise);
2118                 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
2119         }
2120
2121         if(!self.targetfactor)
2122                 self.targetfactor = 1;
2123
2124         if(!self.target2factor)
2125                 self.target2factor = 1;
2126
2127         if(!self.target3factor)
2128                 self.target3factor = 1;
2129
2130         if(!self.target4factor)
2131                 self.target4factor = 1;
2132
2133         if(vlen(self.targetnormal))
2134                 self.targetnormal = normalize(self.targetnormal);
2135
2136         if(vlen(self.target2normal))
2137                 self.target2normal = normalize(self.target2normal);
2138
2139         if(vlen(self.target3normal))
2140                 self.target3normal = normalize(self.target3normal);
2141
2142         if(vlen(self.target4normal))
2143                 self.target4normal = normalize(self.target4normal);
2144
2145         self.blocked = generic_plat_blocked;
2146         if(self.dmg && (self.message == ""))
2147                 self.message = " was squished";
2148     if(self.dmg && (self.message == ""))
2149                 self.message2 = "was squished by";
2150         if(self.dmg && (!self.dmgtime))
2151                 self.dmgtime = 0.25;
2152         self.dmgtime2 = time;
2153
2154         if(self.netname == "")
2155                 self.netname = "1 0 0 0 1";
2156
2157         if (!InitMovingBrushTrigger())
2158                 return;
2159
2160         // wait for targets to spawn
2161         self.nextthink = self.ltime + 999999999;
2162         self.think = SUB_NullThink; // for PushMove
2163
2164         // Savage: Reduce bandwith, critical on e.g. nexdm02
2165         self.effects |= EF_LOWPRECISION;
2166
2167         self.active = ACTIVE_ACTIVE;
2168
2169         InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
2170 }
2171
2172 void conveyor_think()
2173 {
2174         entity e;
2175
2176         // set myself as current conveyor where possible
2177         for(e = world; (e = findentity(e, conveyor, self)); )
2178                 e.conveyor = world;
2179
2180         if(self.state)
2181         {
2182                 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2183                         if(!e.conveyor.state)
2184                                 if(isPushable(e))
2185                                 {
2186                                         vector emin = e.absmin;
2187                                         vector emax = e.absmax;
2188                                         if(self.solid == SOLID_BSP)
2189                                         {
2190                                                 emin -= '1 1 1';
2191                                                 emax += '1 1 1';
2192                                         }
2193                                         if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2194                                                 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2195                                                         e.conveyor = self;
2196                                 }
2197
2198                 for(e = world; (e = findentity(e, conveyor, self)); )
2199                 {
2200                         if(IS_CLIENT(e)) // doing it via velocity has quite some advantages
2201                                 continue; // done in SV_PlayerPhysics
2202
2203                         setorigin(e, e.origin + self.movedir * sys_frametime);
2204                         move_out_of_solid(e);
2205                         UpdateCSQCProjectile(e);
2206                         /*
2207                         // stupid conveyor code
2208                         tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2209                         if(trace_fraction > 0)
2210                                 setorigin(e, trace_endpos);
2211                         */
2212                 }
2213         }
2214
2215         self.nextthink = time;
2216 }
2217
2218 void conveyor_use()
2219 {
2220         self.state = !self.state;
2221 }
2222
2223 void conveyor_reset()
2224 {
2225         self.state = (self.spawnflags & 1);
2226 }
2227
2228 void conveyor_init()
2229 {
2230         if (!self.speed)
2231                 self.speed = 200;
2232         self.movedir = self.movedir * self.speed;
2233         self.think = conveyor_think;
2234         self.nextthink = time;
2235         IFTARGETED
2236         {
2237                 self.use = conveyor_use;
2238                 self.reset = conveyor_reset;
2239                 conveyor_reset();
2240         }
2241         else
2242                 self.state = 1;
2243 }
2244
2245 void spawnfunc_trigger_conveyor()
2246 {
2247         SetMovedir();
2248         EXACTTRIGGER_INIT;
2249         conveyor_init();
2250 }
2251
2252 void spawnfunc_func_conveyor()
2253 {
2254         SetMovedir();
2255         InitMovingBrushTrigger();
2256         self.movetype = MOVETYPE_NONE;
2257         conveyor_init();
2258 }