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