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