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