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