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