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