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