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