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