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