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