]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/t_plats.qc
Begin making triggers common code
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / t_plats.qc
1 #ifdef SVQC
2
3 .float dmgtime2;
4 void generic_plat_blocked()
5 {
6     if(self.dmg && other.takedamage != DAMAGE_NO) {
7         if(self.dmgtime2 < time) {
8             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
9             self.dmgtime2 = time + self.dmgtime;
10         }
11
12         // Gib dead/dying stuff
13         if(other.deadflag != DEAD_NO)
14             Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
15     }
16 }
17
18
19 void() plat_center_touch;
20 void() plat_outside_touch;
21 void() plat_trigger_use;
22 void() plat_go_up;
23 void() plat_go_down;
24 void() plat_crush;
25 const float PLAT_LOW_TRIGGER = 1;
26
27 void plat_spawn_inside_trigger()
28 {
29         entity trigger;
30         vector tmin, tmax;
31
32         trigger = spawn();
33         trigger.touch = plat_center_touch;
34         trigger.movetype = MOVETYPE_NONE;
35         trigger.solid = SOLID_TRIGGER;
36         trigger.enemy = self;
37
38         tmin = self.absmin + '25 25 0';
39         tmax = self.absmax - '25 25 -8';
40         tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8);
41         if (self.spawnflags & PLAT_LOW_TRIGGER)
42                 tmax_z = tmin_z + 8;
43
44         if (self.size_x <= 50)
45         {
46                 tmin_x = (self.mins_x + self.maxs_x) / 2;
47                 tmax_x = tmin_x + 1;
48         }
49         if (self.size_y <= 50)
50         {
51                 tmin_y = (self.mins_y + self.maxs_y) / 2;
52                 tmax_y = tmin_y + 1;
53         }
54
55         if(tmin_x < tmax_x)
56                 if(tmin_y < tmax_y)
57                         if(tmin_z < tmax_z)
58                         {
59                                 setsize (trigger, tmin, tmax);
60                                 return;
61                         }
62
63         // otherwise, something is fishy...
64         remove(trigger);
65         objerror("plat_spawn_inside_trigger: platform has odd size or lip, can't spawn");
66 }
67
68 void plat_hit_top()
69 {
70         sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
71         self.state = 1;
72         self.think = plat_go_down;
73         self.nextthink = self.ltime + 3;
74 }
75
76 void plat_hit_bottom()
77 {
78         sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
79         self.state = 2;
80 }
81
82 void plat_go_down()
83 {
84         sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
85         self.state = 3;
86         SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, plat_hit_bottom);
87 }
88
89 void plat_go_up()
90 {
91         sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
92         self.state = 4;
93         SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, plat_hit_top);
94 }
95
96 void plat_center_touch()
97 {
98         if (!other.iscreature)
99                 return;
100
101         if (other.health <= 0)
102                 return;
103
104         self = self.enemy;
105         if (self.state == 2)
106                 plat_go_up ();
107         else if (self.state == 1)
108                 self.nextthink = self.ltime + 1;        // delay going down
109 }
110
111 void plat_outside_touch()
112 {
113         if (!other.iscreature)
114                 return;
115
116         if (other.health <= 0)
117                 return;
118
119         self = self.enemy;
120         if (self.state == 1)
121                 plat_go_down ();
122 }
123
124 void plat_trigger_use()
125 {
126         if (self.think)
127                 return;         // already activated
128         plat_go_down();
129 }
130
131
132 void plat_crush()
133 {
134     if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
135         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
136     } else {
137         if((self.dmg) && (other.takedamage != DAMAGE_NO)) {   // Shall we bite?
138             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
139             // Gib dead/dying stuff
140             if(other.deadflag != DEAD_NO)
141                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
142         }
143
144         if (self.state == 4)
145             plat_go_down ();
146         else if (self.state == 3)
147             plat_go_up ();
148         // when in other states, then the plat_crush event came delayed after
149         // plat state already had changed
150         // this isn't a bug per se!
151     }
152 }
153
154 void plat_use()
155 {
156         self.use = func_null;
157         if (self.state != 4)
158                 objerror ("plat_use: not in up state");
159         plat_go_down();
160 }
161
162 .string sound1, sound2;
163
164 void plat_reset()
165 {
166         IFTARGETED
167         {
168                 setorigin (self, self.pos1);
169                 self.state = 4;
170                 self.use = plat_use;
171         }
172         else
173         {
174                 setorigin (self, self.pos2);
175                 self.state = 2;
176                 self.use = plat_trigger_use;
177         }
178 }
179
180 .float platmovetype_start_default, platmovetype_end_default;
181 float set_platmovetype(entity e, string s)
182 {
183         // sets platmovetype_start and platmovetype_end based on a string consisting of two values
184
185         float n;
186         n = tokenize_console(s);
187         if(n > 0)
188                 e.platmovetype_start = stof(argv(0));
189         else
190                 e.platmovetype_start = 0;
191
192         if(n > 1)
193                 e.platmovetype_end = stof(argv(1));
194         else
195                 e.platmovetype_end = e.platmovetype_start;
196
197         if(n > 2)
198                 if(argv(2) == "force")
199                         return TRUE; // no checking, return immediately
200
201         if(!cubic_speedfunc_is_sane(e.platmovetype_start, e.platmovetype_end))
202         {
203                 objerror("Invalid platform move type; platform would go in reverse, which is not allowed.");
204                 return FALSE;
205         }
206
207         return TRUE;
208 }
209
210 void spawnfunc_path_corner()
211 {
212         // setup values for overriding train movement
213         // if a second value does not exist, both start and end speeds are the single value specified
214         if(!set_platmovetype(self, self.platmovetype))
215                 return;
216 }
217 void spawnfunc_func_plat()
218 {
219         if (self.sounds == 0)
220                 self.sounds = 2;
221
222     if(self.spawnflags & 4)
223         self.dmg = 10000;
224
225     if(self.dmg && (self.message == ""))
226                 self.message = "was squished";
227     if(self.dmg && (self.message2 == ""))
228                 self.message2 = "was squished by";
229
230         if (self.sounds == 1)
231         {
232                 precache_sound ("plats/plat1.wav");
233                 precache_sound ("plats/plat2.wav");
234                 self.noise = "plats/plat1.wav";
235                 self.noise1 = "plats/plat2.wav";
236         }
237
238         if (self.sounds == 2)
239         {
240                 precache_sound ("plats/medplat1.wav");
241                 precache_sound ("plats/medplat2.wav");
242                 self.noise = "plats/medplat1.wav";
243                 self.noise1 = "plats/medplat2.wav";
244         }
245
246         if (self.sound1)
247         {
248                 precache_sound (self.sound1);
249                 self.noise = self.sound1;
250         }
251         if (self.sound2)
252         {
253                 precache_sound (self.sound2);
254                 self.noise1 = self.sound2;
255         }
256
257         self.mangle = self.angles;
258         self.angles = '0 0 0';
259
260         self.classname = "plat";
261         if (!InitMovingBrushTrigger())
262                 return;
263         self.effects |= EF_LOWPRECISION;
264         setsize (self, self.mins , self.maxs);
265
266         self.blocked = plat_crush;
267
268         if (!self.speed)
269                 self.speed = 150;
270         if (!self.lip)
271                 self.lip = 16;
272         if (!self.height)
273                 self.height = self.size_z - self.lip;
274
275         self.pos1 = self.origin;
276         self.pos2 = self.origin;
277         self.pos2_z = self.origin_z - self.height;
278
279         self.reset = plat_reset;
280         plat_reset();
281
282         plat_spawn_inside_trigger ();   // the "start moving" trigger
283 }
284
285 .float train_wait_turning;
286 void() train_next;
287 void train_wait()
288 {
289         entity oldself;
290         oldself = self;
291         self = self.enemy;
292         SUB_UseTargets();
293         self = oldself;
294         self.enemy = world;
295
296         // if turning is enabled, the train will turn toward the next point while waiting
297         if(self.platmovetype_turn && !self.train_wait_turning)
298         {
299                 entity targ, cp;
300                 vector ang;
301                 targ = find(world, targetname, self.target);
302                 if((self.spawnflags & 1) && targ.curvetarget)
303                         cp = find(world, targetname, targ.curvetarget);
304                 else
305                         cp = world;
306
307                 if(cp) // bezier curves movement
308                         ang = cp.origin - (self.origin - self.view_ofs); // use the origin of the control point of the next path_corner
309                 else // linear movement
310                         ang = targ.origin - (self.origin - self.view_ofs); // use the origin of the next path_corner
311                 ang = vectoangles(ang);
312                 ang_x = -ang_x; // flip up / down orientation
313
314                 if(self.wait > 0) // slow turning
315                         SUB_CalcAngleMove(ang, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
316                 else // instant turning
317                         SUB_CalcAngleMove(ang, TSPEED_TIME, 0.0000001, train_wait);
318                 self.train_wait_turning = TRUE;
319                 return;
320         }
321
322         if(self.noise != "")
323                 stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
324
325         if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
326         {
327                 self.train_wait_turning = FALSE;
328                 train_next();
329         }
330         else
331         {
332                 self.think = train_next;
333                 self.nextthink = self.ltime + self.wait;
334         }
335 }
336
337 void train_next()
338 {
339         entity targ, cp = world;
340         vector cp_org = '0 0 0';
341
342         targ = find(world, targetname, self.target);
343         self.target = targ.target;
344         if (self.spawnflags & 1)
345         {
346                 if(targ.curvetarget)
347                 {
348                         cp = find(world, targetname, targ.curvetarget); // get its second target (the control point)
349                         cp_org = cp.origin - self.view_ofs; // no control point found, assume a straight line to the destination
350                 }
351         }
352         if (self.target == "")
353                 objerror("train_next: no next target");
354         self.wait = targ.wait;
355         if (!self.wait)
356                 self.wait = 0.1;
357
358         if(targ.platmovetype)
359         {
360                 // this path_corner contains a movetype overrider, apply it
361                 self.platmovetype_start = targ.platmovetype_start;
362                 self.platmovetype_end = targ.platmovetype_end;
363         }
364         else
365         {
366                 // this path_corner doesn't contain a movetype overrider, use the train's defaults
367                 self.platmovetype_start = self.platmovetype_start_default;
368                 self.platmovetype_end = self.platmovetype_end_default;
369         }
370
371         if (targ.speed)
372         {
373                 if (cp)
374                         SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
375                 else
376                         SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
377         }
378         else
379         {
380                 if (cp)
381                         SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
382                 else
383                         SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
384         }
385
386         if(self.noise != "")
387                 sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
388 }
389
390 void func_train_find()
391 {
392         entity targ;
393         targ = find(world, targetname, self.target);
394         self.target = targ.target;
395         if (self.target == "")
396                 objerror("func_train_find: no next target");
397         setorigin(self, targ.origin - self.view_ofs);
398         self.nextthink = self.ltime + 1;
399         self.think = train_next;
400 }
401
402 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
403 Ridable platform, targets spawnfunc_path_corner path to follow.
404 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
405 target : targetname of first spawnfunc_path_corner (starts here)
406 */
407 void spawnfunc_func_train()
408 {
409         if (self.noise != "")
410                 precache_sound(self.noise);
411
412         if (self.target == "")
413                 objerror("func_train without a target");
414         if (!self.speed)
415                 self.speed = 100;
416
417         if (!InitMovingBrushTrigger())
418                 return;
419         self.effects |= EF_LOWPRECISION;
420         
421         if (self.spawnflags & 2)
422         {
423                 self.platmovetype_turn = TRUE;
424                 self.view_ofs = '0 0 0'; // don't offset a rotating train, origin works differently now
425         }
426         else
427                 self.view_ofs = self.mins;
428
429         // wait for targets to spawn
430         InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
431
432         self.blocked = generic_plat_blocked;
433         if(self.dmg && (self.message == ""))
434                 self.message = " was squished";
435     if(self.dmg && (self.message2 == ""))
436                 self.message2 = "was squished by";
437         if(self.dmg && (!self.dmgtime))
438                 self.dmgtime = 0.25;
439         self.dmgtime2 = time;
440
441         if(!set_platmovetype(self, self.platmovetype))
442                 return;
443         self.platmovetype_start_default = self.platmovetype_start;
444         self.platmovetype_end_default = self.platmovetype_end;
445
446         // TODO make a reset function for this one
447 }
448
449 void func_rotating_setactive(float astate)
450 {
451
452         if (astate == ACTIVE_TOGGLE)
453         {
454                 if(self.active == ACTIVE_ACTIVE)
455                         self.active = ACTIVE_NOT;
456                 else
457                         self.active = ACTIVE_ACTIVE;
458         }
459         else
460                 self.active = astate;
461
462         if(self.active  == ACTIVE_NOT)
463                 self.avelocity = '0 0 0';
464         else
465                 self.avelocity = self.pos1;
466 }
467
468 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
469 Brush model that spins in place on one axis (default Z).
470 speed   : speed to rotate (in degrees per second)
471 noise   : path/name of looping .wav file to play.
472 dmg     : Do this mutch dmg every .dmgtime intervall when blocked
473 dmgtime : See above.
474 */
475
476 void spawnfunc_func_rotating()
477 {
478         if (self.noise != "")
479         {
480                 precache_sound(self.noise);
481                 ambientsound(self.origin, self.noise, VOL_BASE, ATTEN_IDLE);
482         }
483
484         self.active = ACTIVE_ACTIVE;
485         self.setactive = func_rotating_setactive;
486
487         if (!self.speed)
488                 self.speed = 100;
489         // FIXME: test if this turns the right way, then remove this comment (negate as needed)
490         if (self.spawnflags & 4) // X (untested)
491                 self.avelocity = '0 0 1' * self.speed;
492         // FIXME: test if this turns the right way, then remove this comment (negate as needed)
493         else if (self.spawnflags & 8) // Y (untested)
494                 self.avelocity = '1 0 0' * self.speed;
495         // FIXME: test if this turns the right way, then remove this comment (negate as needed)
496         else // Z
497                 self.avelocity = '0 1 0' * self.speed;
498
499         self.pos1 = self.avelocity;
500
501     if(self.dmg && (self.message == ""))
502         self.message = " was squished";
503     if(self.dmg && (self.message2 == ""))
504                 self.message2 = "was squished by";
505
506
507     if(self.dmg && (!self.dmgtime))
508         self.dmgtime = 0.25;
509
510     self.dmgtime2 = time;
511
512         if (!InitMovingBrushTrigger())
513                 return;
514         // no EF_LOWPRECISION here, as rounding angles is bad
515
516     self.blocked = generic_plat_blocked;
517
518         // wait for targets to spawn
519         self.nextthink = self.ltime + 999999999;
520         self.think = SUB_NullThink; // for PushMove
521
522         // TODO make a reset function for this one
523 }
524
525 .float height;
526 void func_bobbing_controller_think()
527 {
528         vector v;
529         self.nextthink = time + 0.1;
530
531         if(self.owner.active != ACTIVE_ACTIVE)
532         {
533                 self.owner.velocity = '0 0 0';
534                 return;
535         }
536
537         // calculate sinewave using makevectors
538         makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
539         v = self.owner.destvec + self.owner.movedir * v_forward_y;
540         if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
541                 // * 10 so it will arrive in 0.1 sec
542                 self.owner.velocity = (v - self.owner.origin) * 10;
543 }
544
545 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
546 Brush model that moves back and forth on one axis (default Z).
547 speed : how long one cycle takes in seconds (default 4)
548 height : how far the cycle moves (default 32)
549 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
550 noise : path/name of looping .wav file to play.
551 dmg : Do this mutch dmg every .dmgtime intervall when blocked
552 dmgtime : See above.
553 */
554 void spawnfunc_func_bobbing()
555 {
556         entity controller;
557         if (self.noise != "")
558         {
559                 precache_sound(self.noise);
560                 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
561         }
562         if (!self.speed)
563                 self.speed = 4;
564         if (!self.height)
565                 self.height = 32;
566         // center of bobbing motion
567         self.destvec = self.origin;
568         // time scale to get degrees
569         self.cnt = 360 / self.speed;
570
571         self.active = ACTIVE_ACTIVE;
572
573         // damage when blocked
574         self.blocked = generic_plat_blocked;
575         if(self.dmg && (self.message == ""))
576                 self.message = " was squished";
577     if(self.dmg && (self.message2 == ""))
578                 self.message2 = "was squished by";
579         if(self.dmg && (!self.dmgtime))
580                 self.dmgtime = 0.25;
581         self.dmgtime2 = time;
582
583         // how far to bob
584         if (self.spawnflags & 1) // X
585                 self.movedir = '1 0 0' * self.height;
586         else if (self.spawnflags & 2) // Y
587                 self.movedir = '0 1 0' * self.height;
588         else // Z
589                 self.movedir = '0 0 1' * self.height;
590
591         if (!InitMovingBrushTrigger())
592                 return;
593
594         // wait for targets to spawn
595         controller = spawn();
596         controller.classname = "func_bobbing_controller";
597         controller.owner = self;
598         controller.nextthink = time + 1;
599         controller.think = func_bobbing_controller_think;
600         self.nextthink = self.ltime + 999999999;
601         self.think = SUB_NullThink; // for PushMove
602
603         // Savage: Reduce bandwith, critical on e.g. nexdm02
604         self.effects |= EF_LOWPRECISION;
605
606         // TODO make a reset function for this one
607 }
608
609 .float freq;
610 void func_pendulum_controller_think()
611 {
612         float v;
613         self.nextthink = time + 0.1;
614
615         if (!(self.owner.active == ACTIVE_ACTIVE))
616         {
617                 self.owner.avelocity_x = 0;
618                 return;
619         }
620
621         // calculate sinewave using makevectors
622         makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
623         v = self.owner.speed * v_forward_y + self.cnt;
624         if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
625         {
626                 // * 10 so it will arrive in 0.1 sec
627                 self.owner.avelocity_z = (remainder(v - self.owner.angles_z, 360)) * 10;
628         }
629 }
630
631 void spawnfunc_func_pendulum()
632 {
633         entity controller;
634         if (self.noise != "")
635         {
636                 precache_sound(self.noise);
637                 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
638         }
639
640         self.active = ACTIVE_ACTIVE;
641
642         // keys: angle, speed, phase, noise, freq
643
644         if(!self.speed)
645                 self.speed = 30;
646         // not initializing self.dmg to 2, to allow damageless pendulum
647
648         if(self.dmg && (self.message == ""))
649                 self.message = " was squished";
650         if(self.dmg && (self.message2 == ""))
651                 self.message2 = "was squished by";
652         if(self.dmg && (!self.dmgtime))
653                 self.dmgtime = 0.25;
654         self.dmgtime2 = time;
655
656         self.blocked = generic_plat_blocked;
657
658         self.avelocity_z = 0.0000001;
659         if (!InitMovingBrushTrigger())
660                 return;
661
662         if(!self.freq)
663         {
664                 // find pendulum length (same formula as Q3A)
665                 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins_z))));
666         }
667
668         // copy initial angle
669         self.cnt = self.angles_z;
670
671         // wait for targets to spawn
672         controller = spawn();
673         controller.classname = "func_pendulum_controller";
674         controller.owner = self;
675         controller.nextthink = time + 1;
676         controller.think = func_pendulum_controller_think;
677         self.nextthink = self.ltime + 999999999;
678         self.think = SUB_NullThink; // for PushMove
679
680         //self.effects |= EF_LOWPRECISION;
681
682         // TODO make a reset function for this one
683 }
684
685 // button and multiple button
686
687 void() button_wait;
688 void() button_return;
689
690 void button_wait()
691 {
692         self.state = STATE_TOP;
693         self.nextthink = self.ltime + self.wait;
694         self.think = button_return;
695         activator = self.enemy;
696         SUB_UseTargets();
697         self.frame = 1;                 // use alternate textures
698 }
699
700 void button_done()
701 {
702         self.state = STATE_BOTTOM;
703 }
704
705 void button_return()
706 {
707         self.state = STATE_DOWN;
708         SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
709         self.frame = 0;                 // use normal textures
710         if (self.health)
711                 self.takedamage = DAMAGE_YES;   // can be shot again
712 }
713
714
715 void button_blocked()
716 {
717         // do nothing, just don't come all the way back out
718 }
719
720
721 void button_fire()
722 {
723         self.health = self.max_health;
724         self.takedamage = DAMAGE_NO;    // will be reset upon return
725
726         if (self.state == STATE_UP || self.state == STATE_TOP)
727                 return;
728
729         if (self.noise != "")
730                 sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
731
732         self.state = STATE_UP;
733         SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
734 }
735
736 void button_reset()
737 {
738         self.health = self.max_health;
739         setorigin(self, self.pos1);
740         self.frame = 0;                 // use normal textures
741         self.state = STATE_BOTTOM;
742         if (self.health)
743                 self.takedamage = DAMAGE_YES;   // can be shot again
744 }
745
746 void button_use()
747 {
748         if(self.active != ACTIVE_ACTIVE)
749                 return;
750
751         self.enemy = activator;
752         button_fire ();
753 }
754
755 void button_touch()
756 {
757         if (!other)
758                 return;
759         if (!other.iscreature)
760                 return;
761         if(other.velocity * self.movedir < 0)
762                 return;
763         self.enemy = other;
764         if (other.owner)
765                 self.enemy = other.owner;
766         button_fire ();
767 }
768
769 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
770 {
771         if(self.spawnflags & DOOR_NOSPLASH)
772                 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
773                         return;
774         self.health = self.health - damage;
775         if (self.health <= 0)
776         {
777                 self.enemy = damage_attacker;
778                 button_fire ();
779         }
780 }
781
782
783 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
784 When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again.
785
786 "angle"         determines the opening direction
787 "target"        all entities with a matching targetname will be used
788 "speed"         override the default 40 speed
789 "wait"          override the default 1 second wait (-1 = never return)
790 "lip"           override the default 4 pixel lip remaining at end of move
791 "health"        if set, the button must be killed instead of touched. If set to -1, the button will fire on ANY attack, even damageless ones like the InstaGib laser
792 "sounds"
793 0) steam metal
794 1) wooden clunk
795 2) metallic click
796 3) in-out
797 */
798 void spawnfunc_func_button()
799 {
800         SetMovedir ();
801
802         if (!InitMovingBrushTrigger())
803                 return;
804         self.effects |= EF_LOWPRECISION;
805
806         self.blocked = button_blocked;
807         self.use = button_use;
808
809 //      if (self.health == 0) // all buttons are now shootable
810 //              self.health = 10;
811         if (self.health)
812         {
813                 self.max_health = self.health;
814                 self.event_damage = button_damage;
815                 self.takedamage = DAMAGE_YES;
816         }
817         else
818                 self.touch = button_touch;
819
820         if (!self.speed)
821                 self.speed = 40;
822         if (!self.wait)
823                 self.wait = 1;
824         if (!self.lip)
825                 self.lip = 4;
826
827     if(self.noise != "")
828         precache_sound(self.noise);
829
830         self.active = ACTIVE_ACTIVE;
831
832         self.pos1 = self.origin;
833         self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
834     self.flags |= FL_NOTARGET;
835
836         button_reset();
837 }
838
839 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
840 if two doors touch, they are assumed to be connected and operate as a unit.
841
842 TOGGLE causes the door to wait in both the start and end states for a trigger event.
843
844 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
845 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
846 must have set trigger_reverse to 1.
847 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
848
849 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).
850
851 "message"       is printed when the door is touched if it is a trigger door and it hasn't been fired yet
852 "angle"         determines the destination angle for opening. negative values reverse the direction.
853 "targetname"    if set, no touch field will be spawned and a remote button or trigger field activates the door.
854 "health"        if set, door must be shot open
855 "speed"         movement speed (100 default)
856 "wait"          wait before returning (3 default, -1 = never return)
857 "dmg"           damage to inflict when blocked (2 default)
858 "sounds"
859 0)      no sound
860 1)      stone
861 2)      base
862 3)      stone chain
863 4)      screechy metal
864 FIXME: only one sound set available at the time being
865 */
866
867 void door_rotating_reset()
868 {
869         self.angles = self.pos1;
870         self.avelocity = '0 0 0';
871         self.state = STATE_BOTTOM;
872         self.think = func_null;
873         self.nextthink = 0;
874 }
875
876 void door_rotating_init_startopen()
877 {
878         self.angles = self.movedir;
879         self.pos2 = '0 0 0';
880         self.pos1 = self.movedir;
881 }
882
883
884 void spawnfunc_func_door_rotating()
885 {
886
887         //if (!self.deathtype) // map makers can override this
888         //      self.deathtype = " got in the way";
889
890         // I abuse "movedir" for denoting the axis for now
891         if (self.spawnflags & 64) // X (untested)
892                 self.movedir = '0 0 1';
893         else if (self.spawnflags & 128) // Y (untested)
894                 self.movedir = '1 0 0';
895         else // Z
896                 self.movedir = '0 1 0';
897
898         if (self.angles_y==0) self.angles_y = 90;
899
900         self.movedir = self.movedir * self.angles_y;
901         self.angles = '0 0 0';
902
903         self.max_health = self.health;
904         self.avelocity = self.movedir;
905         if (!InitMovingBrushTrigger())
906                 return;
907         self.velocity = '0 0 0';
908         //self.effects |= EF_LOWPRECISION;
909         self.classname = "door_rotating";
910
911         self.blocked = door_blocked;
912         self.use = door_use;
913
914     if(self.spawnflags & 8)
915         self.dmg = 10000;
916
917     if(self.dmg && (self.message == ""))
918                 self.message = "was squished";
919     if(self.dmg && (self.message2 == ""))
920                 self.message2 = "was squished by";
921
922     if (self.sounds > 0)
923         {
924                 precache_sound ("plats/medplat1.wav");
925                 precache_sound ("plats/medplat2.wav");
926                 self.noise2 = "plats/medplat1.wav";
927                 self.noise1 = "plats/medplat2.wav";
928         }
929
930         if (!self.speed)
931                 self.speed = 50;
932         if (!self.wait)
933                 self.wait = 1;
934         self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
935
936         self.pos1 = '0 0 0';
937         self.pos2 = self.movedir;
938
939 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
940 // but spawn in the open position
941         if (self.spawnflags & DOOR_START_OPEN)
942                 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
943
944         self.state = STATE_BOTTOM;
945
946         if (self.health)
947         {
948                 self.takedamage = DAMAGE_YES;
949                 self.event_damage = door_damage;
950         }
951
952         if (self.items)
953                 self.wait = -1;
954
955         self.touch = door_touch;
956
957 // LinkDoors can't be done until all of the doors have been spawned, so
958 // the sizes can be detected properly.
959         InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
960
961         self.reset = door_rotating_reset;
962 }
963
964 /*
965 =============================================================================
966
967 SECRET DOORS
968
969 =============================================================================
970 */
971
972 void() fd_secret_move1;
973 void() fd_secret_move2;
974 void() fd_secret_move3;
975 void() fd_secret_move4;
976 void() fd_secret_move5;
977 void() fd_secret_move6;
978 void() fd_secret_done;
979
980 const float SECRET_OPEN_ONCE = 1;               // stays open
981 const float SECRET_1ST_LEFT = 2;                // 1st move is left of arrow
982 const float SECRET_1ST_DOWN = 4;                // 1st move is down from arrow
983 const float SECRET_NO_SHOOT = 8;                // only opened by trigger
984 const float SECRET_YES_SHOOT = 16;      // shootable even if targeted
985
986 void fd_secret_use()
987 {
988         float temp;
989         string message_save;
990
991         self.health = 10000;
992         self.bot_attack = TRUE;
993
994         // exit if still moving around...
995         if (self.origin != self.oldorigin)
996                 return;
997
998         message_save = self.message;
999         self.message = ""; // no more message
1000         SUB_UseTargets();                               // fire all targets / killtargets
1001         self.message = message_save;
1002
1003         self.velocity = '0 0 0';
1004
1005         // Make a sound, wait a little...
1006
1007         if (self.noise1 != "")
1008                 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1009         self.nextthink = self.ltime + 0.1;
1010
1011         temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1012         makevectors(self.mangle);
1013
1014         if (!self.t_width)
1015         {
1016                 if (self.spawnflags & SECRET_1ST_DOWN)
1017                         self.t_width = fabs(v_up * self.size);
1018                 else
1019                         self.t_width = fabs(v_right * self.size);
1020         }
1021
1022         if (!self.t_length)
1023                 self.t_length = fabs(v_forward * self.size);
1024
1025         if (self.spawnflags & SECRET_1ST_DOWN)
1026                 self.dest1 = self.origin - v_up * self.t_width;
1027         else
1028                 self.dest1 = self.origin + v_right * (self.t_width * temp);
1029
1030         self.dest2 = self.dest1 + v_forward * self.t_length;
1031         SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
1032         if (self.noise2 != "")
1033                 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1034 }
1035
1036 void fd_secret_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1037 {
1038         fd_secret_use();
1039 }
1040
1041 // Wait after first movement...
1042 void fd_secret_move1()
1043 {
1044         self.nextthink = self.ltime + 1.0;
1045         self.think = fd_secret_move2;
1046         if (self.noise3 != "")
1047                 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1048 }
1049
1050 // Start moving sideways w/sound...
1051 void fd_secret_move2()
1052 {
1053         if (self.noise2 != "")
1054                 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1055         SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
1056 }
1057
1058 // Wait here until time to go back...
1059 void fd_secret_move3()
1060 {
1061         if (self.noise3 != "")
1062                 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1063         if (!(self.spawnflags & SECRET_OPEN_ONCE))
1064         {
1065                 self.nextthink = self.ltime + self.wait;
1066                 self.think = fd_secret_move4;
1067         }
1068 }
1069
1070 // Move backward...
1071 void fd_secret_move4()
1072 {
1073         if (self.noise2 != "")
1074                 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1075         SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
1076 }
1077
1078 // Wait 1 second...
1079 void fd_secret_move5()
1080 {
1081         self.nextthink = self.ltime + 1.0;
1082         self.think = fd_secret_move6;
1083         if (self.noise3 != "")
1084                 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1085 }
1086
1087 void fd_secret_move6()
1088 {
1089         if (self.noise2 != "")
1090                 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1091         SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
1092 }
1093
1094 void fd_secret_done()
1095 {
1096         if (self.spawnflags&SECRET_YES_SHOOT)
1097         {
1098                 self.health = 10000;
1099                 self.takedamage = DAMAGE_YES;
1100                 //self.th_pain = fd_secret_use;
1101         }
1102         if (self.noise3 != "")
1103                 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1104 }
1105
1106 void secret_blocked()
1107 {
1108         if (time < self.attack_finished_single)
1109                 return;
1110         self.attack_finished_single = time + 0.5;
1111         //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1112 }
1113
1114 /*
1115 ==============
1116 secret_touch
1117
1118 Prints messages
1119 ================
1120 */
1121 void secret_touch()
1122 {
1123         if (!other.iscreature)
1124                 return;
1125         if (self.attack_finished_single > time)
1126                 return;
1127
1128         self.attack_finished_single = time + 2;
1129
1130         if (self.message)
1131         {
1132                 if (IS_CLIENT(other))
1133                         centerprint(other, self.message);
1134                 play2(other, "misc/talk.wav");
1135         }
1136 }
1137
1138 void secret_reset()
1139 {
1140         if (self.spawnflags&SECRET_YES_SHOOT)
1141         {
1142                 self.health = 10000;
1143                 self.takedamage = DAMAGE_YES;
1144         }
1145         setorigin(self, self.oldorigin);
1146         self.think = func_null;
1147         self.nextthink = 0;
1148 }
1149
1150 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1151 Basic secret door. Slides back, then to the side. Angle determines direction.
1152 wait  = # of seconds before coming back
1153 1st_left = 1st move is left of arrow
1154 1st_down = 1st move is down from arrow
1155 always_shoot = even if targeted, keep shootable
1156 t_width = override WIDTH to move back (or height if going down)
1157 t_length = override LENGTH to move sideways
1158 "dmg"           damage to inflict when blocked (2 default)
1159
1160 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1161 "sounds"
1162 1) medieval
1163 2) metal
1164 3) base
1165 */
1166
1167 void spawnfunc_func_door_secret()
1168 {
1169         /*if (!self.deathtype) // map makers can override this
1170                 self.deathtype = " got in the way";*/
1171
1172         if (!self.dmg)
1173                 self.dmg = 2;
1174
1175         // Magic formula...
1176         self.mangle = self.angles;
1177         self.angles = '0 0 0';
1178         self.classname = "door";
1179         if (!InitMovingBrushTrigger())
1180                 return;
1181         self.effects |= EF_LOWPRECISION;
1182
1183         self.touch = secret_touch;
1184         self.blocked = secret_blocked;
1185         self.speed = 50;
1186         self.use = fd_secret_use;
1187         IFTARGETED
1188         {
1189         }
1190         else
1191                 self.spawnflags |= SECRET_YES_SHOOT;
1192
1193         if(self.spawnflags&SECRET_YES_SHOOT)
1194         {
1195                 self.health = 10000;
1196                 self.takedamage = DAMAGE_YES;
1197                 self.event_damage = fd_secret_damage;
1198         }
1199         self.oldorigin = self.origin;
1200         if (!self.wait)
1201                 self.wait = 5;          // 5 seconds before closing
1202
1203         self.reset = secret_reset;
1204         secret_reset();
1205 }
1206
1207 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1208 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1209 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
1210 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1211 height: amplitude modifier (default 32)
1212 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1213 noise: path/name of looping .wav file to play.
1214 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1215 dmgtime: See above.
1216 */
1217
1218 void func_fourier_controller_think()
1219 {
1220         vector v;
1221         float n, i, t;
1222
1223         self.nextthink = time + 0.1;
1224         if(self.owner.active != ACTIVE_ACTIVE)
1225         {
1226                 self.owner.velocity = '0 0 0';
1227                 return;
1228         }
1229
1230
1231         n = floor((tokenize_console(self.owner.netname)) / 5);
1232         t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1233
1234         v = self.owner.destvec;
1235
1236         for(i = 0; i < n; ++i)
1237         {
1238                 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1239                 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;
1240         }
1241
1242         if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1243                 // * 10 so it will arrive in 0.1 sec
1244                 self.owner.velocity = (v - self.owner.origin) * 10;
1245 }
1246
1247 void spawnfunc_func_fourier()
1248 {
1249         entity controller;
1250         if (self.noise != "")
1251         {
1252                 precache_sound(self.noise);
1253                 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
1254         }
1255
1256         if (!self.speed)
1257                 self.speed = 4;
1258         if (!self.height)
1259                 self.height = 32;
1260         self.destvec = self.origin;
1261         self.cnt = 360 / self.speed;
1262
1263         self.blocked = generic_plat_blocked;
1264         if(self.dmg && (self.message == ""))
1265                 self.message = " was squished";
1266     if(self.dmg && (self.message2 == ""))
1267                 self.message2 = "was squished by";
1268         if(self.dmg && (!self.dmgtime))
1269                 self.dmgtime = 0.25;
1270         self.dmgtime2 = time;
1271
1272         if(self.netname == "")
1273                 self.netname = "1 0 0 0 1";
1274
1275         if (!InitMovingBrushTrigger())
1276                 return;
1277
1278         self.active = ACTIVE_ACTIVE;
1279
1280         // wait for targets to spawn
1281         controller = spawn();
1282         controller.classname = "func_fourier_controller";
1283         controller.owner = self;
1284         controller.nextthink = time + 1;
1285         controller.think = func_fourier_controller_think;
1286         self.nextthink = self.ltime + 999999999;
1287         self.think = SUB_NullThink; // for PushMove
1288
1289         // Savage: Reduce bandwith, critical on e.g. nexdm02
1290         self.effects |= EF_LOWPRECISION;
1291
1292         // TODO make a reset function for this one
1293 }
1294
1295 // reusing some fields havocbots declared
1296 .entity wp00, wp01, wp02, wp03;
1297
1298 .float targetfactor, target2factor, target3factor, target4factor;
1299 .vector targetnormal, target2normal, target3normal, target4normal;
1300
1301 vector func_vectormamamam_origin(entity o, float t)
1302 {
1303         vector v, p;
1304         float f;
1305         entity e;
1306
1307         f = o.spawnflags;
1308         v = '0 0 0';
1309
1310         e = o.wp00;
1311         if(e)
1312         {
1313                 p = e.origin + t * e.velocity;
1314                 if(f & 1)
1315                         v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
1316                 else
1317                         v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
1318         }
1319
1320         e = o.wp01;
1321         if(e)
1322         {
1323                 p = e.origin + t * e.velocity;
1324                 if(f & 2)
1325                         v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
1326                 else
1327                         v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
1328         }
1329
1330         e = o.wp02;
1331         if(e)
1332         {
1333                 p = e.origin + t * e.velocity;
1334                 if(f & 4)
1335                         v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
1336                 else
1337                         v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
1338         }
1339
1340         e = o.wp03;
1341         if(e)
1342         {
1343                 p = e.origin + t * e.velocity;
1344                 if(f & 8)
1345                         v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
1346                 else
1347                         v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
1348         }
1349
1350         return v;
1351 }
1352
1353 void func_vectormamamam_controller_think()
1354 {
1355         self.nextthink = time + 0.1;
1356
1357         if(self.owner.active != ACTIVE_ACTIVE)
1358         {
1359                 self.owner.velocity = '0 0 0';
1360                 return;
1361         }
1362
1363         if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
1364                 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
1365 }
1366
1367 void func_vectormamamam_findtarget()
1368 {
1369         if(self.target != "")
1370                 self.wp00 = find(world, targetname, self.target);
1371
1372         if(self.target2 != "")
1373                 self.wp01 = find(world, targetname, self.target2);
1374
1375         if(self.target3 != "")
1376                 self.wp02 = find(world, targetname, self.target3);
1377
1378         if(self.target4 != "")
1379                 self.wp03 = find(world, targetname, self.target4);
1380
1381         if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
1382                 objerror("No reference entity found, so there is nothing to move. Aborting.");
1383
1384         self.destvec = self.origin - func_vectormamamam_origin(self, 0);
1385
1386         entity controller;
1387         controller = spawn();
1388         controller.classname = "func_vectormamamam_controller";
1389         controller.owner = self;
1390         controller.nextthink = time + 1;
1391         controller.think = func_vectormamamam_controller_think;
1392 }
1393
1394 void spawnfunc_func_vectormamamam()
1395 {
1396         if (self.noise != "")
1397         {
1398                 precache_sound(self.noise);
1399                 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
1400         }
1401
1402         if(!self.targetfactor)
1403                 self.targetfactor = 1;
1404
1405         if(!self.target2factor)
1406                 self.target2factor = 1;
1407
1408         if(!self.target3factor)
1409                 self.target3factor = 1;
1410
1411         if(!self.target4factor)
1412                 self.target4factor = 1;
1413
1414         if(vlen(self.targetnormal))
1415                 self.targetnormal = normalize(self.targetnormal);
1416
1417         if(vlen(self.target2normal))
1418                 self.target2normal = normalize(self.target2normal);
1419
1420         if(vlen(self.target3normal))
1421                 self.target3normal = normalize(self.target3normal);
1422
1423         if(vlen(self.target4normal))
1424                 self.target4normal = normalize(self.target4normal);
1425
1426         self.blocked = generic_plat_blocked;
1427         if(self.dmg && (self.message == ""))
1428                 self.message = " was squished";
1429     if(self.dmg && (self.message == ""))
1430                 self.message2 = "was squished by";
1431         if(self.dmg && (!self.dmgtime))
1432                 self.dmgtime = 0.25;
1433         self.dmgtime2 = time;
1434
1435         if(self.netname == "")
1436                 self.netname = "1 0 0 0 1";
1437
1438         if (!InitMovingBrushTrigger())
1439                 return;
1440
1441         // wait for targets to spawn
1442         self.nextthink = self.ltime + 999999999;
1443         self.think = SUB_NullThink; // for PushMove
1444
1445         // Savage: Reduce bandwith, critical on e.g. nexdm02
1446         self.effects |= EF_LOWPRECISION;
1447
1448         self.active = ACTIVE_ACTIVE;
1449
1450         InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
1451 }
1452
1453 #endif
1454
1455 void conveyor_think()
1456 {
1457 #ifdef CSQC
1458         // TODO: check if this is what is causing the glitchiness when switching between them
1459         float dt = time - self.move_time;
1460         self.move_time = time;
1461         if(dt <= 0) { return; }
1462 #endif
1463         entity e;
1464
1465         // set myself as current conveyor where possible
1466         for(e = world; (e = findentity(e, conveyor, self)); )
1467                 e.conveyor = world;
1468
1469         if(self.state)
1470         {
1471                 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
1472                         if(!e.conveyor.state)
1473                                 if(isPushable(e))
1474                                 {
1475                                         vector emin = e.absmin;
1476                                         vector emax = e.absmax;
1477                                         if(self.solid == SOLID_BSP)
1478                                         {
1479                                                 emin -= '1 1 1';
1480                                                 emax += '1 1 1';
1481                                         }
1482                                         if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
1483                                                 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
1484                                                         e.conveyor = self;
1485                                 }
1486
1487                 for(e = world; (e = findentity(e, conveyor, self)); )
1488                 {
1489                         if(IS_CLIENT(e)) // doing it via velocity has quite some advantages
1490                                 continue; // done in SV_PlayerPhysics   continue;
1491
1492                         setorigin(e, e.origin + self.movedir * PHYS_INPUT_FRAMETIME);
1493                         move_out_of_solid(e);
1494 #ifdef SVQC
1495                         UpdateCSQCProjectile(e);
1496 #endif
1497                         /*
1498                         // stupid conveyor code
1499                         tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
1500                         if(trace_fraction > 0)
1501                                 setorigin(e, trace_endpos);
1502                         */
1503                 }
1504         }
1505
1506 #ifdef SVQC
1507         self.nextthink = time;
1508 #endif
1509 }
1510
1511 #ifdef SVQC
1512
1513 void conveyor_use()
1514 {
1515         self.state = !self.state;
1516
1517         self.SendFlags |= 2;
1518 }
1519
1520 void conveyor_reset()
1521 {
1522         self.state = (self.spawnflags & 1);
1523
1524         self.SendFlags |= 2;
1525 }
1526
1527 float conveyor_send(entity to, float sf)
1528 {
1529         WriteByte(MSG_ENTITY, ENT_CLIENT_CONVEYOR);
1530         WriteByte(MSG_ENTITY, sf);
1531
1532         if(sf & 1)
1533         {
1534                 WriteByte(MSG_ENTITY, self.warpzone_isboxy);
1535                 WriteCoord(MSG_ENTITY, self.origin_x);
1536                 WriteCoord(MSG_ENTITY, self.origin_y);
1537                 WriteCoord(MSG_ENTITY, self.origin_z);
1538
1539                 WriteCoord(MSG_ENTITY, self.mins_x);
1540                 WriteCoord(MSG_ENTITY, self.mins_y);
1541                 WriteCoord(MSG_ENTITY, self.mins_z);
1542                 WriteCoord(MSG_ENTITY, self.maxs_x);
1543                 WriteCoord(MSG_ENTITY, self.maxs_y);
1544                 WriteCoord(MSG_ENTITY, self.maxs_z);
1545
1546                 WriteCoord(MSG_ENTITY, self.movedir_x);
1547                 WriteCoord(MSG_ENTITY, self.movedir_y);
1548                 WriteCoord(MSG_ENTITY, self.movedir_z);
1549
1550                 WriteByte(MSG_ENTITY, self.speed);
1551                 WriteByte(MSG_ENTITY, self.state);
1552
1553                 WriteString(MSG_ENTITY, self.targetname);
1554                 WriteString(MSG_ENTITY, self.target);
1555         }
1556
1557         if(sf & 2)
1558                 WriteByte(MSG_ENTITY, self.state);
1559
1560         return TRUE;
1561 }
1562
1563 void conveyor_init()
1564 {
1565         if (!self.speed)
1566                 self.speed = 200;
1567         self.movedir = self.movedir * self.speed;
1568         self.think = conveyor_think;
1569         self.nextthink = time;
1570         IFTARGETED
1571         {
1572                 self.use = conveyor_use;
1573                 self.reset = conveyor_reset;
1574                 conveyor_reset();
1575         }
1576         else
1577                 self.state = 1;
1578
1579         Net_LinkEntity(self, 0, FALSE, conveyor_send);
1580
1581         self.SendFlags |= 1;
1582 }
1583
1584 void spawnfunc_trigger_conveyor()
1585 {
1586         SetMovedir();
1587         EXACTTRIGGER_INIT;
1588         conveyor_init();
1589 }
1590
1591 void spawnfunc_func_conveyor()
1592 {
1593         SetMovedir();
1594         InitMovingBrushTrigger();
1595         self.movetype = MOVETYPE_NONE;
1596         conveyor_init();
1597 }
1598
1599 #elif defined(CSQC)
1600
1601 void conveyor_init()
1602 {
1603         self.draw = conveyor_think;
1604         self.drawmask = MASK_NORMAL;
1605
1606         self.movetype = MOVETYPE_NONE;
1607         self.model = "";
1608         self.solid = SOLID_TRIGGER;
1609         self.move_origin = self.origin;
1610         self.move_time = time;
1611 }
1612
1613 void ent_conveyor()
1614 {
1615         float sf = ReadByte();
1616
1617         if(sf & 1)
1618         {
1619                 self.warpzone_isboxy = ReadByte();
1620                 self.origin_x = ReadCoord();
1621                 self.origin_y = ReadCoord();
1622                 self.origin_z = ReadCoord();
1623                 setorigin(self, self.origin);
1624
1625                 self.mins_x = ReadCoord();
1626                 self.mins_y = ReadCoord();
1627                 self.mins_z = ReadCoord();
1628                 self.maxs_x = ReadCoord();
1629                 self.maxs_y = ReadCoord();
1630                 self.maxs_z = ReadCoord();
1631                 setsize(self, self.mins, self.maxs);
1632
1633                 self.movedir_x = ReadCoord();
1634                 self.movedir_y = ReadCoord();
1635                 self.movedir_z = ReadCoord();
1636
1637                 self.speed = ReadByte();
1638                 self.state = ReadByte();
1639
1640                 self.targetname = strzone(ReadString());
1641                 self.target = strzone(ReadString());
1642
1643                 conveyor_init();
1644         }
1645
1646         if(sf & 2)
1647                 self.state = ReadByte();
1648 }
1649
1650 #endif