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