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