]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/t_plats.qc
Predict conveyors
[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 .entity trigger_field;
20
21 void() plat_center_touch;
22 void() plat_outside_touch;
23 void() plat_trigger_use;
24 void() plat_go_up;
25 void() plat_go_down;
26 void() plat_crush;
27 const float PLAT_LOW_TRIGGER = 1;
28
29 void plat_spawn_inside_trigger()
30 {
31         entity trigger;
32         vector tmin, tmax;
33
34         trigger = spawn();
35         trigger.touch = plat_center_touch;
36         trigger.movetype = MOVETYPE_NONE;
37         trigger.solid = SOLID_TRIGGER;
38         trigger.enemy = self;
39
40         tmin = self.absmin + '25 25 0';
41         tmax = self.absmax - '25 25 -8';
42         tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8);
43         if (self.spawnflags & PLAT_LOW_TRIGGER)
44                 tmax_z = tmin_z + 8;
45
46         if (self.size_x <= 50)
47         {
48                 tmin_x = (self.mins_x + self.maxs_x) / 2;
49                 tmax_x = tmin_x + 1;
50         }
51         if (self.size_y <= 50)
52         {
53                 tmin_y = (self.mins_y + self.maxs_y) / 2;
54                 tmax_y = tmin_y + 1;
55         }
56
57         if(tmin_x < tmax_x)
58                 if(tmin_y < tmax_y)
59                         if(tmin_z < tmax_z)
60                         {
61                                 setsize (trigger, tmin, tmax);
62                                 return;
63                         }
64
65         // otherwise, something is fishy...
66         remove(trigger);
67         objerror("plat_spawn_inside_trigger: platform has odd size or lip, can't spawn");
68 }
69
70 void plat_hit_top()
71 {
72         sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
73         self.state = 1;
74         self.think = plat_go_down;
75         self.nextthink = self.ltime + 3;
76 }
77
78 void plat_hit_bottom()
79 {
80         sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
81         self.state = 2;
82 }
83
84 void plat_go_down()
85 {
86         sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
87         self.state = 3;
88         SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, plat_hit_bottom);
89 }
90
91 void plat_go_up()
92 {
93         sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
94         self.state = 4;
95         SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, plat_hit_top);
96 }
97
98 void plat_center_touch()
99 {
100         if (!other.iscreature)
101                 return;
102
103         if (other.health <= 0)
104                 return;
105
106         self = self.enemy;
107         if (self.state == 2)
108                 plat_go_up ();
109         else if (self.state == 1)
110                 self.nextthink = self.ltime + 1;        // delay going down
111 }
112
113 void plat_outside_touch()
114 {
115         if (!other.iscreature)
116                 return;
117
118         if (other.health <= 0)
119                 return;
120
121         self = self.enemy;
122         if (self.state == 1)
123                 plat_go_down ();
124 }
125
126 void plat_trigger_use()
127 {
128         if (self.think)
129                 return;         // already activated
130         plat_go_down();
131 }
132
133
134 void plat_crush()
135 {
136     if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
137         Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
138     } else {
139         if((self.dmg) && (other.takedamage != DAMAGE_NO)) {   // Shall we bite?
140             Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
141             // Gib dead/dying stuff
142             if(other.deadflag != DEAD_NO)
143                 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
144         }
145
146         if (self.state == 4)
147             plat_go_down ();
148         else if (self.state == 3)
149             plat_go_up ();
150         // when in other states, then the plat_crush event came delayed after
151         // plat state already had changed
152         // this isn't a bug per se!
153     }
154 }
155
156 void plat_use()
157 {
158         self.use = func_null;
159         if (self.state != 4)
160                 objerror ("plat_use: not in up state");
161         plat_go_down();
162 }
163
164 .string sound1, sound2;
165
166 void plat_reset()
167 {
168         IFTARGETED
169         {
170                 setorigin (self, self.pos1);
171                 self.state = 4;
172                 self.use = plat_use;
173         }
174         else
175         {
176                 setorigin (self, self.pos2);
177                 self.state = 2;
178                 self.use = plat_trigger_use;
179         }
180 }
181
182 .float platmovetype_start_default, platmovetype_end_default;
183 float set_platmovetype(entity e, string s)
184 {
185         // sets platmovetype_start and platmovetype_end based on a string consisting of two values
186
187         float n;
188         n = tokenize_console(s);
189         if(n > 0)
190                 e.platmovetype_start = stof(argv(0));
191         else
192                 e.platmovetype_start = 0;
193
194         if(n > 1)
195                 e.platmovetype_end = stof(argv(1));
196         else
197                 e.platmovetype_end = e.platmovetype_start;
198
199         if(n > 2)
200                 if(argv(2) == "force")
201                         return TRUE; // no checking, return immediately
202
203         if(!cubic_speedfunc_is_sane(e.platmovetype_start, e.platmovetype_end))
204         {
205                 objerror("Invalid platform move type; platform would go in reverse, which is not allowed.");
206                 return FALSE;
207         }
208
209         return TRUE;
210 }
211
212 void spawnfunc_path_corner()
213 {
214         // setup values for overriding train movement
215         // if a second value does not exist, both start and end speeds are the single value specified
216         if(!set_platmovetype(self, self.platmovetype))
217                 return;
218 }
219 void spawnfunc_func_plat()
220 {
221         if (self.sounds == 0)
222                 self.sounds = 2;
223
224     if(self.spawnflags & 4)
225         self.dmg = 10000;
226
227     if(self.dmg && (self.message == ""))
228                 self.message = "was squished";
229     if(self.dmg && (self.message2 == ""))
230                 self.message2 = "was squished by";
231
232         if (self.sounds == 1)
233         {
234                 precache_sound ("plats/plat1.wav");
235                 precache_sound ("plats/plat2.wav");
236                 self.noise = "plats/plat1.wav";
237                 self.noise1 = "plats/plat2.wav";
238         }
239
240         if (self.sounds == 2)
241         {
242                 precache_sound ("plats/medplat1.wav");
243                 precache_sound ("plats/medplat2.wav");
244                 self.noise = "plats/medplat1.wav";
245                 self.noise1 = "plats/medplat2.wav";
246         }
247
248         if (self.sound1)
249         {
250                 precache_sound (self.sound1);
251                 self.noise = self.sound1;
252         }
253         if (self.sound2)
254         {
255                 precache_sound (self.sound2);
256                 self.noise1 = self.sound2;
257         }
258
259         self.mangle = self.angles;
260         self.angles = '0 0 0';
261
262         self.classname = "plat";
263         if (!InitMovingBrushTrigger())
264                 return;
265         self.effects |= EF_LOWPRECISION;
266         setsize (self, self.mins , self.maxs);
267
268         self.blocked = plat_crush;
269
270         if (!self.speed)
271                 self.speed = 150;
272         if (!self.lip)
273                 self.lip = 16;
274         if (!self.height)
275                 self.height = self.size_z - self.lip;
276
277         self.pos1 = self.origin;
278         self.pos2 = self.origin;
279         self.pos2_z = self.origin_z - self.height;
280
281         self.reset = plat_reset;
282         plat_reset();
283
284         plat_spawn_inside_trigger ();   // the "start moving" trigger
285 }
286
287 .float train_wait_turning;
288 void() train_next;
289 void train_wait()
290 {
291         entity oldself;
292         oldself = self;
293         self = self.enemy;
294         SUB_UseTargets();
295         self = oldself;
296         self.enemy = world;
297
298         // if turning is enabled, the train will turn toward the next point while waiting
299         if(self.platmovetype_turn && !self.train_wait_turning)
300         {
301                 entity targ, cp;
302                 vector ang;
303                 targ = find(world, targetname, self.target);
304                 if((self.spawnflags & 1) && targ.curvetarget)
305                         cp = find(world, targetname, targ.curvetarget);
306                 else
307                         cp = world;
308
309                 if(cp) // bezier curves movement
310                         ang = cp.origin - (self.origin - self.view_ofs); // use the origin of the control point of the next path_corner
311                 else // linear movement
312                         ang = targ.origin - (self.origin - self.view_ofs); // use the origin of the next path_corner
313                 ang = vectoangles(ang);
314                 ang_x = -ang_x; // flip up / down orientation
315
316                 if(self.wait > 0) // slow turning
317                         SUB_CalcAngleMove(ang, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
318                 else // instant turning
319                         SUB_CalcAngleMove(ang, TSPEED_TIME, 0.0000001, train_wait);
320                 self.train_wait_turning = TRUE;
321                 return;
322         }
323
324         if(self.noise != "")
325                 stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
326
327         if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
328         {
329                 self.train_wait_turning = FALSE;
330                 train_next();
331         }
332         else
333         {
334                 self.think = train_next;
335                 self.nextthink = self.ltime + self.wait;
336         }
337 }
338
339 void train_next()
340 {
341         entity targ, cp = world;
342         vector cp_org = '0 0 0';
343
344         targ = find(world, targetname, self.target);
345         self.target = targ.target;
346         if (self.spawnflags & 1)
347         {
348                 if(targ.curvetarget)
349                 {
350                         cp = find(world, targetname, targ.curvetarget); // get its second target (the control point)
351                         cp_org = cp.origin - self.view_ofs; // no control point found, assume a straight line to the destination
352                 }
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 (!InitMovingBrushTrigger())
420                 return;
421         self.effects |= EF_LOWPRECISION;
422         
423         if (self.spawnflags & 2)
424         {
425                 self.platmovetype_turn = TRUE;
426                 self.view_ofs = '0 0 0'; // don't offset a rotating train, origin works differently now
427         }
428         else
429                 self.view_ofs = self.mins;
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 (!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(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 (!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 (!(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 (!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(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 (!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 InstaGib 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 (!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 (!door.itemkeys)
1006                 return TRUE;
1007
1008         // this door require a key
1009         // only a player can have a key
1010         if (!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                         Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, 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                         Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_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                 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_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 (!(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 (!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 (!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 (!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 (!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 (!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(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 (!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(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 (!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 #endif
2175
2176 void conveyor_think()
2177 {
2178 #ifdef CSQC
2179         float dt = time - self.move_time;
2180         self.move_time = time;
2181         if(dt <= 0) { return; }
2182 #endif
2183         entity e;
2184
2185         // set myself as current conveyor where possible
2186         for(e = world; (e = findentity(e, conveyor, self)); )
2187                 e.conveyor = world;
2188
2189         if(self.state)
2190         {
2191                 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2192                         if(!e.conveyor.state)
2193 #ifdef SVQC
2194                                 if(isPushable(e))
2195 #elif defined(CSQC)
2196                                 if(e.isplayermodel)
2197 #endif
2198                                 {
2199                                         vector emin = e.absmin;
2200                                         vector emax = e.absmax;
2201                                         if(self.solid == SOLID_BSP)
2202                                         {
2203                                                 emin -= '1 1 1';
2204                                                 emax += '1 1 1';
2205                                         }
2206                                         if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2207                                                 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2208                                                         e.conveyor = self;
2209                                 }
2210
2211                 for(e = world; (e = findentity(e, conveyor, self)); )
2212                 {
2213 #ifdef SVQC
2214                         if(IS_CLIENT(e)) // doing it via velocity has quite some advantages
2215                                 continue; // done in SV_PlayerPhysics
2216 #elif defined(CSQC)
2217                         if(e.isplayermodel)
2218                                 continue;
2219 #endif
2220
2221                         setorigin(e, e.origin + self.movedir * PHYS_INPUT_FRAMETIME);
2222                         move_out_of_solid(e);
2223 #ifdef SVQC
2224                         UpdateCSQCProjectile(e);
2225 #endif
2226                         /*
2227                         // stupid conveyor code
2228                         tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2229                         if(trace_fraction > 0)
2230                                 setorigin(e, trace_endpos);
2231                         */
2232                 }
2233         }
2234
2235 #ifdef SVQC
2236         self.nextthink = time;
2237 #endif
2238 }
2239
2240 #ifdef SVQC
2241
2242 void conveyor_use()
2243 {
2244         self.state = !self.state;
2245
2246         self.SendFlags |= 2;
2247 }
2248
2249 void conveyor_reset()
2250 {
2251         self.state = (self.spawnflags & 1);
2252
2253         self.SendFlags |= 2;
2254 }
2255
2256 float conveyor_send(entity to, float sf)
2257 {
2258         WriteByte(MSG_ENTITY, ENT_CLIENT_CONVEYOR);
2259         WriteByte(MSG_ENTITY, sf);
2260
2261         if(sf & 1)
2262         {
2263                 WriteByte(MSG_ENTITY, self.warpzone_isboxy);
2264                 WriteCoord(MSG_ENTITY, self.origin_x);
2265                 WriteCoord(MSG_ENTITY, self.origin_y);
2266                 WriteCoord(MSG_ENTITY, self.origin_z);
2267
2268                 WriteCoord(MSG_ENTITY, self.mins_x);
2269                 WriteCoord(MSG_ENTITY, self.mins_y);
2270                 WriteCoord(MSG_ENTITY, self.mins_z);
2271                 WriteCoord(MSG_ENTITY, self.maxs_x);
2272                 WriteCoord(MSG_ENTITY, self.maxs_y);
2273                 WriteCoord(MSG_ENTITY, self.maxs_z);
2274
2275                 WriteCoord(MSG_ENTITY, self.movedir_x);
2276                 WriteCoord(MSG_ENTITY, self.movedir_y);
2277                 WriteCoord(MSG_ENTITY, self.movedir_z);
2278
2279                 WriteByte(MSG_ENTITY, self.speed);
2280                 WriteByte(MSG_ENTITY, self.state);
2281
2282                 WriteString(MSG_ENTITY, self.targetname);
2283                 WriteString(MSG_ENTITY, self.target);
2284         }
2285
2286         if(sf & 2)
2287                 WriteByte(MSG_ENTITY, self.state);
2288
2289         return TRUE;
2290 }
2291
2292 void conveyor_init()
2293 {
2294         if (!self.speed)
2295                 self.speed = 200;
2296         self.movedir = self.movedir * self.speed;
2297         self.think = conveyor_think;
2298         self.nextthink = time;
2299         IFTARGETED
2300         {
2301                 self.use = conveyor_use;
2302                 self.reset = conveyor_reset;
2303                 conveyor_reset();
2304         }
2305         else
2306                 self.state = 1;
2307
2308         Net_LinkEntity(self, 0, FALSE, conveyor_send);
2309
2310         self.SendFlags |= 1;
2311 }
2312
2313 void spawnfunc_trigger_conveyor()
2314 {
2315         SetMovedir();
2316         EXACTTRIGGER_INIT;
2317         conveyor_init();
2318 }
2319
2320 void spawnfunc_func_conveyor()
2321 {
2322         SetMovedir();
2323         InitMovingBrushTrigger();
2324         self.movetype = MOVETYPE_NONE;
2325         conveyor_init();
2326 }
2327
2328 #elif defined(CSQC)
2329
2330 void conveyor_init()
2331 {
2332         self.draw = conveyor_think;
2333         self.drawmask = MASK_NORMAL;
2334
2335         self.movetype = MOVETYPE_NONE;
2336         self.model = "";
2337         self.solid = SOLID_TRIGGER;
2338         self.move_origin = self.origin;
2339         self.move_time = time;
2340 }
2341
2342 void ent_conveyor()
2343 {
2344         float sf = ReadByte();
2345
2346         if(sf & 1)
2347         {
2348                 self.warpzone_isboxy = ReadByte();
2349                 self.origin_x = ReadCoord();
2350                 self.origin_y = ReadCoord();
2351                 self.origin_z = ReadCoord();
2352                 setorigin(self, self.origin);
2353
2354                 self.mins_x = ReadCoord();
2355                 self.mins_y = ReadCoord();
2356                 self.mins_z = ReadCoord();
2357                 self.maxs_x = ReadCoord();
2358                 self.maxs_y = ReadCoord();
2359                 self.maxs_z = ReadCoord();
2360                 setsize(self, self.mins, self.maxs);
2361
2362                 self.movedir_x = ReadCoord();
2363                 self.movedir_y = ReadCoord();
2364                 self.movedir_z = ReadCoord();
2365
2366                 self.speed = ReadByte();
2367                 self.state = ReadByte();
2368
2369                 self.targetname = strzone(ReadString());
2370                 self.target = strzone(ReadString());
2371
2372                 conveyor_init();
2373         }
2374
2375         if(sf & 2)
2376                 self.state = ReadByte();
2377 }
2378
2379 #endif