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