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