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