4 #include "../dpdefs/progsdefs.qh"
5 #include "../dpdefs/dpextensions.qh"
7 #include "../warpzonelib/mathlib.qh"
8 #include "../warpzonelib/common.qh"
9 #include "../warpzonelib/util_server.qh"
10 #include "../common/constants.qh"
11 #include "../common/util.qh"
12 #include "../common/weapons/weapons.qh"
13 #include "constants.qh"
15 #include "../common/notifications.qh"
16 #include "../common/deathtypes.qh"
17 #include "command/common.qh"
18 #include "../csqcmodellib/sv_model.qh"
22 void generic_plat_blocked()
24 if(self.dmg && other.takedamage != DAMAGE_NO) {
25 if(self.dmgtime2 < time) {
26 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
27 self.dmgtime2 = time + self.dmgtime;
30 // Gib dead/dying stuff
31 if(other.deadflag != DEAD_NO)
32 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
37 .entity trigger_field;
39 void() plat_center_touch;
40 void() plat_outside_touch;
41 void() plat_trigger_use;
45 const float PLAT_LOW_TRIGGER = 1;
47 void plat_spawn_inside_trigger()
53 trigger.touch = plat_center_touch;
54 trigger.movetype = MOVETYPE_NONE;
55 trigger.solid = SOLID_TRIGGER;
58 tmin = self.absmin + '25 25 0';
59 tmax = self.absmax - '25 25 -8';
60 tmin_z = tmax.z - (self.pos1_z - self.pos2_z + 8);
61 if (self.spawnflags & PLAT_LOW_TRIGGER)
64 if (self.size.x <= 50)
66 tmin_x = (self.mins.x + self.maxs.x) / 2;
69 if (self.size.y <= 50)
71 tmin_y = (self.mins.y + self.maxs.y) / 2;
79 setsize (trigger, tmin, tmax);
83 // otherwise, something is fishy...
85 objerror("plat_spawn_inside_trigger: platform has odd size or lip, can't spawn");
90 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
92 self.think = plat_go_down;
93 self.nextthink = self.ltime + 3;
96 void plat_hit_bottom()
98 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
104 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
106 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, plat_hit_bottom);
111 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
113 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, plat_hit_top);
116 void plat_center_touch()
118 if (!other.iscreature)
121 if (other.health <= 0)
127 else if (self.state == 1)
128 self.nextthink = self.ltime + 1; // delay going down
131 void plat_outside_touch()
133 if (!other.iscreature)
136 if (other.health <= 0)
144 void plat_trigger_use()
147 return; // already activated
154 if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
155 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
157 if((self.dmg) && (other.takedamage != DAMAGE_NO)) { // Shall we bite?
158 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
159 // Gib dead/dying stuff
160 if(other.deadflag != DEAD_NO)
161 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
166 else if (self.state == 3)
168 // when in other states, then the plat_crush event came delayed after
169 // plat state already had changed
170 // this isn't a bug per se!
176 self.use = func_null;
178 objerror ("plat_use: not in up state");
182 .string sound1, sound2;
188 setorigin (self, self.pos1);
194 setorigin (self, self.pos2);
196 self.use = plat_trigger_use;
200 .float platmovetype_start_default, platmovetype_end_default;
201 float set_platmovetype(entity e, string s)
203 // sets platmovetype_start and platmovetype_end based on a string consisting of two values
206 n = tokenize_console(s);
208 e.platmovetype_start = stof(argv(0));
210 e.platmovetype_start = 0;
213 e.platmovetype_end = stof(argv(1));
215 e.platmovetype_end = e.platmovetype_start;
218 if(argv(2) == "force")
219 return true; // no checking, return immediately
221 if(!cubic_speedfunc_is_sane(e.platmovetype_start, e.platmovetype_end))
223 objerror("Invalid platform move type; platform would go in reverse, which is not allowed.");
230 void spawnfunc_path_corner()
232 // setup values for overriding train movement
233 // if a second value does not exist, both start and end speeds are the single value specified
234 if(!set_platmovetype(self, self.platmovetype))
237 void spawnfunc_func_plat()
239 if (self.sounds == 0)
242 if(self.spawnflags & 4)
245 if(self.dmg && (self.message == ""))
246 self.message = "was squished";
247 if(self.dmg && (self.message2 == ""))
248 self.message2 = "was squished by";
250 if (self.sounds == 1)
252 precache_sound ("plats/plat1.wav");
253 precache_sound ("plats/plat2.wav");
254 self.noise = "plats/plat1.wav";
255 self.noise1 = "plats/plat2.wav";
258 if (self.sounds == 2)
260 precache_sound ("plats/medplat1.wav");
261 precache_sound ("plats/medplat2.wav");
262 self.noise = "plats/medplat1.wav";
263 self.noise1 = "plats/medplat2.wav";
268 precache_sound (self.sound1);
269 self.noise = self.sound1;
273 precache_sound (self.sound2);
274 self.noise1 = self.sound2;
277 self.mangle = self.angles;
278 self.angles = '0 0 0';
280 self.classname = "plat";
281 if (!InitMovingBrushTrigger())
283 self.effects |= EF_LOWPRECISION;
284 setsize (self, self.mins , self.maxs);
286 self.blocked = plat_crush;
293 self.height = self.size.z - self.lip;
295 self.pos1 = self.origin;
296 self.pos2 = self.origin;
297 self.pos2_z = self.origin.z - self.height;
299 self.reset = plat_reset;
302 plat_spawn_inside_trigger (); // the "start moving" trigger
305 .float train_wait_turning;
316 // if turning is enabled, the train will turn toward the next point while waiting
317 if(self.platmovetype_turn && !self.train_wait_turning)
321 targ = find(world, targetname, self.target);
322 if((self.spawnflags & 1) && targ.curvetarget)
323 cp = find(world, targetname, targ.curvetarget);
327 if(cp) // bezier curves movement
328 ang = cp.origin - (self.origin - self.view_ofs); // use the origin of the control point of the next path_corner
329 else // linear movement
330 ang = targ.origin - (self.origin - self.view_ofs); // use the origin of the next path_corner
331 ang = vectoangles(ang);
332 ang_x = -ang.x; // flip up / down orientation
334 if(self.wait > 0) // slow turning
335 SUB_CalcAngleMove(ang, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
336 else // instant turning
337 SUB_CalcAngleMove(ang, TSPEED_TIME, 0.0000001, train_wait);
338 self.train_wait_turning = true;
343 stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
345 if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
347 self.train_wait_turning = false;
352 self.think = train_next;
353 self.nextthink = self.ltime + self.wait;
359 entity targ, cp = world;
360 vector cp_org = '0 0 0';
362 targ = find(world, targetname, self.target);
363 self.target = targ.target;
364 if (self.spawnflags & 1)
368 cp = find(world, targetname, targ.curvetarget); // get its second target (the control point)
369 cp_org = cp.origin - self.view_ofs; // no control point found, assume a straight line to the destination
372 if (self.target == "")
373 objerror("train_next: no next target");
374 self.wait = targ.wait;
378 if(targ.platmovetype)
380 // this path_corner contains a movetype overrider, apply it
381 self.platmovetype_start = targ.platmovetype_start;
382 self.platmovetype_end = targ.platmovetype_end;
386 // this path_corner doesn't contain a movetype overrider, use the train's defaults
387 self.platmovetype_start = self.platmovetype_start_default;
388 self.platmovetype_end = self.platmovetype_end_default;
394 SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
396 SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
401 SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
403 SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
407 sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
410 void func_train_find()
413 targ = find(world, targetname, self.target);
414 self.target = targ.target;
415 if (self.target == "")
416 objerror("func_train_find: no next target");
417 setorigin(self, targ.origin - self.view_ofs);
418 self.nextthink = self.ltime + 1;
419 self.think = train_next;
422 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
423 Ridable platform, targets spawnfunc_path_corner path to follow.
424 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
425 target : targetname of first spawnfunc_path_corner (starts here)
427 void spawnfunc_func_train()
429 if (self.noise != "")
430 precache_sound(self.noise);
432 if (self.target == "")
433 objerror("func_train without a target");
437 if (!InitMovingBrushTrigger())
439 self.effects |= EF_LOWPRECISION;
441 if (self.spawnflags & 2)
443 self.platmovetype_turn = true;
444 self.view_ofs = '0 0 0'; // don't offset a rotating train, origin works differently now
447 self.view_ofs = self.mins;
449 // wait for targets to spawn
450 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
452 self.blocked = generic_plat_blocked;
453 if(self.dmg && (self.message == ""))
454 self.message = " was squished";
455 if(self.dmg && (self.message2 == ""))
456 self.message2 = "was squished by";
457 if(self.dmg && (!self.dmgtime))
459 self.dmgtime2 = time;
461 if(!set_platmovetype(self, self.platmovetype))
463 self.platmovetype_start_default = self.platmovetype_start;
464 self.platmovetype_end_default = self.platmovetype_end;
466 // TODO make a reset function for this one
469 void func_rotating_setactive(float astate)
472 if (astate == ACTIVE_TOGGLE)
474 if(self.active == ACTIVE_ACTIVE)
475 self.active = ACTIVE_NOT;
477 self.active = ACTIVE_ACTIVE;
480 self.active = astate;
482 if(self.active == ACTIVE_NOT)
483 self.avelocity = '0 0 0';
485 self.avelocity = self.pos1;
488 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
489 Brush model that spins in place on one axis (default Z).
490 speed : speed to rotate (in degrees per second)
491 noise : path/name of looping .wav file to play.
492 dmg : Do this mutch dmg every .dmgtime intervall when blocked
496 void spawnfunc_func_rotating()
498 if (self.noise != "")
500 precache_sound(self.noise);
501 ambientsound(self.origin, self.noise, VOL_BASE, ATTEN_IDLE);
504 self.active = ACTIVE_ACTIVE;
505 self.setactive = func_rotating_setactive;
509 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
510 if (self.spawnflags & 4) // X (untested)
511 self.avelocity = '0 0 1' * self.speed;
512 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
513 else if (self.spawnflags & 8) // Y (untested)
514 self.avelocity = '1 0 0' * self.speed;
515 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
517 self.avelocity = '0 1 0' * self.speed;
519 self.pos1 = self.avelocity;
521 if(self.dmg && (self.message == ""))
522 self.message = " was squished";
523 if(self.dmg && (self.message2 == ""))
524 self.message2 = "was squished by";
527 if(self.dmg && (!self.dmgtime))
530 self.dmgtime2 = time;
532 if (!InitMovingBrushTrigger())
534 // no EF_LOWPRECISION here, as rounding angles is bad
536 self.blocked = generic_plat_blocked;
538 // wait for targets to spawn
539 self.nextthink = self.ltime + 999999999;
540 self.think = SUB_NullThink; // for PushMove
542 // TODO make a reset function for this one
546 void func_bobbing_controller_think()
549 self.nextthink = time + 0.1;
551 if(self.owner.active != ACTIVE_ACTIVE)
553 self.owner.velocity = '0 0 0';
557 // calculate sinewave using makevectors
558 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
559 v = self.owner.destvec + self.owner.movedir * v_forward.y;
560 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
561 // * 10 so it will arrive in 0.1 sec
562 self.owner.velocity = (v - self.owner.origin) * 10;
565 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
566 Brush model that moves back and forth on one axis (default Z).
567 speed : how long one cycle takes in seconds (default 4)
568 height : how far the cycle moves (default 32)
569 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
570 noise : path/name of looping .wav file to play.
571 dmg : Do this mutch dmg every .dmgtime intervall when blocked
574 void spawnfunc_func_bobbing()
577 if (self.noise != "")
579 precache_sound(self.noise);
580 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
586 // center of bobbing motion
587 self.destvec = self.origin;
588 // time scale to get degrees
589 self.cnt = 360 / self.speed;
591 self.active = ACTIVE_ACTIVE;
593 // damage when blocked
594 self.blocked = generic_plat_blocked;
595 if(self.dmg && (self.message == ""))
596 self.message = " was squished";
597 if(self.dmg && (self.message2 == ""))
598 self.message2 = "was squished by";
599 if(self.dmg && (!self.dmgtime))
601 self.dmgtime2 = time;
604 if (self.spawnflags & 1) // X
605 self.movedir = '1 0 0' * self.height;
606 else if (self.spawnflags & 2) // Y
607 self.movedir = '0 1 0' * self.height;
609 self.movedir = '0 0 1' * self.height;
611 if (!InitMovingBrushTrigger())
614 // wait for targets to spawn
615 controller = spawn();
616 controller.classname = "func_bobbing_controller";
617 controller.owner = self;
618 controller.nextthink = time + 1;
619 controller.think = func_bobbing_controller_think;
620 self.nextthink = self.ltime + 999999999;
621 self.think = SUB_NullThink; // for PushMove
623 // Savage: Reduce bandwith, critical on e.g. nexdm02
624 self.effects |= EF_LOWPRECISION;
626 // TODO make a reset function for this one
630 void func_pendulum_controller_think()
633 self.nextthink = time + 0.1;
635 if (!(self.owner.active == ACTIVE_ACTIVE))
637 self.owner.avelocity_x = 0;
641 // calculate sinewave using makevectors
642 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
643 v = self.owner.speed * v_forward.y + self.cnt;
644 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
646 // * 10 so it will arrive in 0.1 sec
647 self.owner.avelocity_z = (remainder(v - self.owner.angles.z, 360)) * 10;
651 void spawnfunc_func_pendulum()
654 if (self.noise != "")
656 precache_sound(self.noise);
657 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
660 self.active = ACTIVE_ACTIVE;
662 // keys: angle, speed, phase, noise, freq
666 // not initializing self.dmg to 2, to allow damageless pendulum
668 if(self.dmg && (self.message == ""))
669 self.message = " was squished";
670 if(self.dmg && (self.message2 == ""))
671 self.message2 = "was squished by";
672 if(self.dmg && (!self.dmgtime))
674 self.dmgtime2 = time;
676 self.blocked = generic_plat_blocked;
678 self.avelocity_z = 0.0000001;
679 if (!InitMovingBrushTrigger())
684 // find pendulum length (same formula as Q3A)
685 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins.z))));
688 // copy initial angle
689 self.cnt = self.angles.z;
691 // wait for targets to spawn
692 controller = spawn();
693 controller.classname = "func_pendulum_controller";
694 controller.owner = self;
695 controller.nextthink = time + 1;
696 controller.think = func_pendulum_controller_think;
697 self.nextthink = self.ltime + 999999999;
698 self.think = SUB_NullThink; // for PushMove
700 //self.effects |= EF_LOWPRECISION;
702 // TODO make a reset function for this one
705 // button and multiple button
708 void() button_return;
712 self.state = STATE_TOP;
713 self.nextthink = self.ltime + self.wait;
714 self.think = button_return;
715 activator = self.enemy;
717 self.frame = 1; // use alternate textures
722 self.state = STATE_BOTTOM;
727 self.state = STATE_DOWN;
728 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
729 self.frame = 0; // use normal textures
731 self.takedamage = DAMAGE_YES; // can be shot again
735 void button_blocked()
737 // do nothing, just don't come all the way back out
743 self.health = self.max_health;
744 self.takedamage = DAMAGE_NO; // will be reset upon return
746 if (self.state == STATE_UP || self.state == STATE_TOP)
749 if (self.noise != "")
750 sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
752 self.state = STATE_UP;
753 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
758 self.health = self.max_health;
759 setorigin(self, self.pos1);
760 self.frame = 0; // use normal textures
761 self.state = STATE_BOTTOM;
763 self.takedamage = DAMAGE_YES; // can be shot again
768 if(self.active != ACTIVE_ACTIVE)
771 self.enemy = activator;
779 if (!other.iscreature)
781 if(other.velocity * self.movedir < 0)
785 self.enemy = other.owner;
789 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
791 if(self.spawnflags & DOOR_NOSPLASH)
792 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
794 self.health = self.health - damage;
795 if (self.health <= 0)
797 self.enemy = damage_attacker;
803 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
804 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 "angle" determines the opening direction
807 "target" all entities with a matching targetname will be used
808 "speed" override the default 40 speed
809 "wait" override the default 1 second wait (-1 = never return)
810 "lip" override the default 4 pixel lip remaining at end of move
811 "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
818 void spawnfunc_func_button()
822 if (!InitMovingBrushTrigger())
824 self.effects |= EF_LOWPRECISION;
826 self.blocked = button_blocked;
827 self.use = button_use;
829 // if (self.health == 0) // all buttons are now shootable
833 self.max_health = self.health;
834 self.event_damage = button_damage;
835 self.takedamage = DAMAGE_YES;
838 self.touch = button_touch;
848 precache_sound(self.noise);
850 self.active = ACTIVE_ACTIVE;
852 self.pos1 = self.origin;
853 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
854 self.flags |= FL_NOTARGET;
860 const float DOOR_START_OPEN = 1;
861 const float DOOR_DONT_LINK = 4;
862 const float DOOR_TOGGLE = 32;
866 Doors are similar to buttons, but can spawn a fat trigger field around them
867 to open without a touch, and they link together to form simultanious
870 Door.owner is the master door. If there is only one door, it points to itself.
871 If multiple doors, all will point to a single one.
873 Door.enemy chains from the master door through all doors linked in the chain.
878 =============================================================================
882 =============================================================================
887 void() door_rotating_go_down;
888 void() door_rotating_go_up;
893 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
894 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
897 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
898 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
900 //Dont chamge direction for dead or dying stuff
901 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
904 if (self.state == STATE_DOWN)
905 if (self.classname == "door")
910 door_rotating_go_up ();
913 if (self.classname == "door")
918 door_rotating_go_down ();
922 //gib dying stuff just to make sure
923 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
924 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
928 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
929 // if a door has a negative wait, it would never come back if blocked,
930 // so let it just squash the object to death real fast
931 /* if (self.wait >= 0)
933 if (self.state == STATE_DOWN)
944 if (self.noise1 != "")
945 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
946 self.state = STATE_TOP;
947 if (self.spawnflags & DOOR_TOGGLE)
948 return; // don't come down automatically
949 if (self.classname == "door")
951 self.think = door_go_down;
954 self.think = door_rotating_go_down;
956 self.nextthink = self.ltime + self.wait;
959 void door_hit_bottom()
961 if (self.noise1 != "")
962 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
963 self.state = STATE_BOTTOM;
968 if (self.noise2 != "")
969 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
972 self.takedamage = DAMAGE_YES;
973 self.health = self.max_health;
976 self.state = STATE_DOWN;
977 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
982 if (self.state == STATE_UP)
983 return; // already going up
985 if (self.state == STATE_TOP)
986 { // reset top wait time
987 self.nextthink = self.ltime + self.wait;
991 if (self.noise2 != "")
992 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
993 self.state = STATE_UP;
994 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
997 oldmessage = self.message;
1000 self.message = oldmessage;
1006 =============================================================================
1008 ACTIVATION FUNCTIONS
1010 =============================================================================
1013 float door_check_keys(void) {
1014 entity door = self.owner ? self.owner : self;
1020 // this door require a key
1021 // only a player can have a key
1022 if (!IS_PLAYER(other))
1025 if (item_keys_usekey(door, other)) {
1026 // some keys were used
1027 if (other.key_door_messagetime <= time) {
1028 play2(other, "misc/talk.wav");
1029 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
1030 other.key_door_messagetime = time + 2;
1033 // no keys were used
1034 if (other.key_door_messagetime <= time) {
1035 play2(other, "misc/talk.wav");
1036 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
1037 other.key_door_messagetime = time + 2;
1041 if (door.itemkeys) {
1042 // door is now unlocked
1043 play2(other, "misc/talk.wav");
1044 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_UNLOCKED);
1056 if (self.owner != self)
1057 objerror ("door_fire: self.owner != self");
1061 if (self.spawnflags & DOOR_TOGGLE)
1063 if (self.state == STATE_UP || self.state == STATE_TOP)
1068 if (self.classname == "door")
1074 door_rotating_go_down ();
1077 } while ( (self != starte) && (self != world) );
1083 // trigger all paired doors
1087 if (self.classname == "door")
1092 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
1093 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
1095 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
1096 self.pos2 = '0 0 0' - self.pos2;
1098 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
1099 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
1100 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
1102 door_rotating_go_up ();
1106 } while ( (self != starte) && (self != world) );
1115 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
1127 void door_trigger_touch()
1129 if (other.health < 1)
1130 if (!(other.iscreature && other.deadflag == DEAD_NO))
1133 if (time < self.attack_finished_single)
1136 // check if door is locked
1137 if (!door_check_keys())
1140 self.attack_finished_single = time + 1;
1149 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1152 if(self.spawnflags & DOOR_NOSPLASH)
1153 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
1155 self.health = self.health - damage;
1157 if (self.itemkeys) {
1158 // don't allow opening doors through damage if keys are required
1162 if (self.health <= 0)
1166 self.health = self.max_health;
1167 self.takedamage = DAMAGE_NO; // wil be reset upon return
1183 if (!IS_PLAYER(other))
1185 if (self.owner.attack_finished_single > time)
1188 self.owner.attack_finished_single = time + 2;
1190 if (!(self.owner.dmg) && (self.owner.message != ""))
1192 if (IS_CLIENT(other))
1193 centerprint(other, self.owner.message);
1194 play2(other, "misc/talk.wav");
1199 void door_generic_plat_blocked()
1202 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1203 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1206 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1207 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1209 //Dont chamge direction for dead or dying stuff
1210 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1213 if (self.state == STATE_DOWN)
1214 door_rotating_go_up ();
1216 door_rotating_go_down ();
1219 //gib dying stuff just to make sure
1220 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1221 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1225 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1226 // if a door has a negative wait, it would never come back if blocked,
1227 // so let it just squash the object to death real fast
1228 /* if (self.wait >= 0)
1230 if (self.state == STATE_DOWN)
1231 door_rotating_go_up ();
1233 door_rotating_go_down ();
1239 void door_rotating_hit_top()
1241 if (self.noise1 != "")
1242 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1243 self.state = STATE_TOP;
1244 if (self.spawnflags & DOOR_TOGGLE)
1245 return; // don't come down automatically
1246 self.think = door_rotating_go_down;
1247 self.nextthink = self.ltime + self.wait;
1250 void door_rotating_hit_bottom()
1252 if (self.noise1 != "")
1253 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1254 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1256 self.pos2 = '0 0 0' - self.pos2;
1259 self.state = STATE_BOTTOM;
1262 void door_rotating_go_down()
1264 if (self.noise2 != "")
1265 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1266 if (self.max_health)
1268 self.takedamage = DAMAGE_YES;
1269 self.health = self.max_health;
1272 self.state = STATE_DOWN;
1273 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
1276 void door_rotating_go_up()
1278 if (self.state == STATE_UP)
1279 return; // already going up
1281 if (self.state == STATE_TOP)
1282 { // reset top wait time
1283 self.nextthink = self.ltime + self.wait;
1286 if (self.noise2 != "")
1287 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1288 self.state = STATE_UP;
1289 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
1292 oldmessage = self.message;
1295 self.message = oldmessage;
1302 =============================================================================
1306 =============================================================================
1310 entity spawn_field(vector fmins, vector fmaxs)
1316 trigger.classname = "doortriggerfield";
1317 trigger.movetype = MOVETYPE_NONE;
1318 trigger.solid = SOLID_TRIGGER;
1319 trigger.owner = self;
1320 trigger.touch = door_trigger_touch;
1324 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1329 entity LinkDoors_nextent(entity cur, entity near, entity pass)
1331 while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
1337 float LinkDoors_isconnected(entity e1, entity e2, entity pass)
1340 if (e1.absmin.x > e2.absmax.x + DELTA)
1342 if (e1.absmin.y > e2.absmax.y + DELTA)
1344 if (e1.absmin.z > e2.absmax.z + DELTA)
1346 if (e2.absmin.x > e1.absmax.x + DELTA)
1348 if (e2.absmin.y > e1.absmax.y + DELTA)
1350 if (e2.absmin.z > e1.absmax.z + DELTA)
1365 vector cmins, cmaxs;
1368 return; // already linked by another door
1369 if (self.spawnflags & 4)
1371 self.owner = self.enemy = self;
1379 self.trigger_field = spawn_field(self.absmin, self.absmax);
1381 return; // don't want to link this door
1384 FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
1386 // set owner, and make a loop of the chain
1387 dprint("LinkDoors: linking doors:");
1388 for(t = self; ; t = t.enemy)
1390 dprint(" ", etos(t));
1392 if(t.enemy == world)
1400 // collect health, targetname, message, size
1401 cmins = self.absmin;
1402 cmaxs = self.absmax;
1403 for(t = self; ; t = t.enemy)
1405 if(t.health && !self.health)
1406 self.health = t.health;
1407 if((t.targetname != "") && (self.targetname == ""))
1408 self.targetname = t.targetname;
1409 if((t.message != "") && (self.message == ""))
1410 self.message = t.message;
1411 if (t.absmin.x < cmins.x)
1412 cmins_x = t.absmin.x;
1413 if (t.absmin.y < cmins.y)
1414 cmins_y = t.absmin.y;
1415 if (t.absmin.z < cmins.z)
1416 cmins_z = t.absmin.z;
1417 if (t.absmax.x > cmaxs.x)
1418 cmaxs_x = t.absmax.x;
1419 if (t.absmax.y > cmaxs.y)
1420 cmaxs_y = t.absmax.y;
1421 if (t.absmax.z > cmaxs.z)
1422 cmaxs_z = t.absmax.z;
1427 // distribute health, targetname, message
1428 for(t = self; t; t = t.enemy)
1430 t.health = self.health;
1431 t.targetname = self.targetname;
1432 t.message = self.message;
1437 // shootable, or triggered doors just needed the owner/enemy links,
1438 // they don't spawn a field
1447 self.trigger_field = spawn_field(cmins, cmaxs);
1451 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
1452 if two doors touch, they are assumed to be connected and operate as a unit.
1454 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1456 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 GOLD_KEY causes the door to open only if the activator holds a gold key.
1460 SILVER_KEY causes the door to open only if the activator holds a silver key.
1462 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1463 "angle" determines the opening direction
1464 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1465 "health" if set, door must be shot open
1466 "speed" movement speed (100 default)
1467 "wait" wait before returning (3 default, -1 = never return)
1468 "lip" lip remaining at end of move (8 default)
1469 "dmg" damage to inflict when blocked (2 default)
1476 FIXME: only one sound set available at the time being
1480 void door_init_startopen()
1482 setorigin (self, self.pos2);
1483 self.pos2 = self.pos1;
1484 self.pos1 = self.origin;
1489 setorigin(self, self.pos1);
1490 self.velocity = '0 0 0';
1491 self.state = STATE_BOTTOM;
1492 self.think = func_null;
1496 // spawnflags require key (for now only func_door)
1497 const float SPAWNFLAGS_GOLD_KEY = 8;
1498 const float SPAWNFLAGS_SILVER_KEY = 16;
1499 void spawnfunc_func_door()
1501 // Quake 1 keys compatibility
1502 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
1503 self.itemkeys |= ITEM_KEY_BIT(0);
1504 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
1505 self.itemkeys |= ITEM_KEY_BIT(1);
1507 //if (!self.deathtype) // map makers can override this
1508 // self.deathtype = " got in the way";
1511 self.max_health = self.health;
1512 if (!InitMovingBrushTrigger())
1514 self.effects |= EF_LOWPRECISION;
1515 self.classname = "door";
1517 self.blocked = door_blocked;
1518 self.use = door_use;
1520 // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
1521 // if(self.spawnflags & 8)
1522 // self.dmg = 10000;
1524 if(self.dmg && (self.message == ""))
1525 self.message = "was squished";
1526 if(self.dmg && (self.message2 == ""))
1527 self.message2 = "was squished by";
1529 if (self.sounds > 0)
1531 precache_sound ("plats/medplat1.wav");
1532 precache_sound ("plats/medplat2.wav");
1533 self.noise2 = "plats/medplat1.wav";
1534 self.noise1 = "plats/medplat2.wav";
1544 self.pos1 = self.origin;
1545 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1547 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1548 // but spawn in the open position
1549 if (self.spawnflags & DOOR_START_OPEN)
1550 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1552 self.state = STATE_BOTTOM;
1556 self.takedamage = DAMAGE_YES;
1557 self.event_damage = door_damage;
1563 self.touch = door_touch;
1565 // LinkDoors can't be done until all of the doors have been spawned, so
1566 // the sizes can be detected properly.
1567 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1569 self.reset = door_reset;
1572 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1573 if two doors touch, they are assumed to be connected and operate as a unit.
1575 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1577 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1578 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1579 must have set trigger_reverse to 1.
1580 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1582 START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not usefull for touch or takedamage doors).
1584 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1585 "angle" determines the destination angle for opening. negative values reverse the direction.
1586 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1587 "health" if set, door must be shot open
1588 "speed" movement speed (100 default)
1589 "wait" wait before returning (3 default, -1 = never return)
1590 "dmg" damage to inflict when blocked (2 default)
1597 FIXME: only one sound set available at the time being
1600 void door_rotating_reset()
1602 self.angles = self.pos1;
1603 self.avelocity = '0 0 0';
1604 self.state = STATE_BOTTOM;
1605 self.think = func_null;
1609 void door_rotating_init_startopen()
1611 self.angles = self.movedir;
1612 self.pos2 = '0 0 0';
1613 self.pos1 = self.movedir;
1617 void spawnfunc_func_door_rotating()
1620 //if (!self.deathtype) // map makers can override this
1621 // self.deathtype = " got in the way";
1623 // I abuse "movedir" for denoting the axis for now
1624 if (self.spawnflags & 64) // X (untested)
1625 self.movedir = '0 0 1';
1626 else if (self.spawnflags & 128) // Y (untested)
1627 self.movedir = '1 0 0';
1629 self.movedir = '0 1 0';
1631 if (self.angles_y ==0) self.angles_y = 90;
1633 self.movedir = self.movedir * self.angles.y;
1634 self.angles = '0 0 0';
1636 self.max_health = self.health;
1637 self.avelocity = self.movedir;
1638 if (!InitMovingBrushTrigger())
1640 self.velocity = '0 0 0';
1641 //self.effects |= EF_LOWPRECISION;
1642 self.classname = "door_rotating";
1644 self.blocked = door_blocked;
1645 self.use = door_use;
1647 if(self.spawnflags & 8)
1650 if(self.dmg && (self.message == ""))
1651 self.message = "was squished";
1652 if(self.dmg && (self.message2 == ""))
1653 self.message2 = "was squished by";
1655 if (self.sounds > 0)
1657 precache_sound ("plats/medplat1.wav");
1658 precache_sound ("plats/medplat2.wav");
1659 self.noise2 = "plats/medplat1.wav";
1660 self.noise1 = "plats/medplat2.wav";
1667 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1669 self.pos1 = '0 0 0';
1670 self.pos2 = self.movedir;
1672 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1673 // but spawn in the open position
1674 if (self.spawnflags & DOOR_START_OPEN)
1675 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1677 self.state = STATE_BOTTOM;
1681 self.takedamage = DAMAGE_YES;
1682 self.event_damage = door_damage;
1688 self.touch = door_touch;
1690 // LinkDoors can't be done until all of the doors have been spawned, so
1691 // the sizes can be detected properly.
1692 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1694 self.reset = door_rotating_reset;
1698 =============================================================================
1702 =============================================================================
1705 void() fd_secret_move1;
1706 void() fd_secret_move2;
1707 void() fd_secret_move3;
1708 void() fd_secret_move4;
1709 void() fd_secret_move5;
1710 void() fd_secret_move6;
1711 void() fd_secret_done;
1713 const float SECRET_OPEN_ONCE = 1; // stays open
1714 const float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1715 const float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1716 const float SECRET_NO_SHOOT = 8; // only opened by trigger
1717 const float SECRET_YES_SHOOT = 16; // shootable even if targeted
1719 void fd_secret_use()
1722 string message_save;
1724 self.health = 10000;
1725 self.bot_attack = true;
1727 // exit if still moving around...
1728 if (self.origin != self.oldorigin)
1731 message_save = self.message;
1732 self.message = ""; // no more message
1733 SUB_UseTargets(); // fire all targets / killtargets
1734 self.message = message_save;
1736 self.velocity = '0 0 0';
1738 // Make a sound, wait a little...
1740 if (self.noise1 != "")
1741 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1742 self.nextthink = self.ltime + 0.1;
1744 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1745 makevectors(self.mangle);
1749 if (self.spawnflags & SECRET_1ST_DOWN)
1750 self.t_width = fabs(v_up * self.size);
1752 self.t_width = fabs(v_right * self.size);
1756 self.t_length = fabs(v_forward * self.size);
1758 if (self.spawnflags & SECRET_1ST_DOWN)
1759 self.dest1 = self.origin - v_up * self.t_width;
1761 self.dest1 = self.origin + v_right * (self.t_width * temp);
1763 self.dest2 = self.dest1 + v_forward * self.t_length;
1764 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
1765 if (self.noise2 != "")
1766 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1769 void fd_secret_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1774 // Wait after first movement...
1775 void fd_secret_move1()
1777 self.nextthink = self.ltime + 1.0;
1778 self.think = fd_secret_move2;
1779 if (self.noise3 != "")
1780 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1783 // Start moving sideways w/sound...
1784 void fd_secret_move2()
1786 if (self.noise2 != "")
1787 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1788 SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
1791 // Wait here until time to go back...
1792 void fd_secret_move3()
1794 if (self.noise3 != "")
1795 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1796 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1798 self.nextthink = self.ltime + self.wait;
1799 self.think = fd_secret_move4;
1804 void fd_secret_move4()
1806 if (self.noise2 != "")
1807 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1808 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
1812 void fd_secret_move5()
1814 self.nextthink = self.ltime + 1.0;
1815 self.think = fd_secret_move6;
1816 if (self.noise3 != "")
1817 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1820 void fd_secret_move6()
1822 if (self.noise2 != "")
1823 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1824 SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
1827 void fd_secret_done()
1829 if (self.spawnflags&SECRET_YES_SHOOT)
1831 self.health = 10000;
1832 self.takedamage = DAMAGE_YES;
1833 //self.th_pain = fd_secret_use;
1835 if (self.noise3 != "")
1836 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1839 void secret_blocked()
1841 if (time < self.attack_finished_single)
1843 self.attack_finished_single = time + 0.5;
1844 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1856 if (!other.iscreature)
1858 if (self.attack_finished_single > time)
1861 self.attack_finished_single = time + 2;
1865 if (IS_CLIENT(other))
1866 centerprint(other, self.message);
1867 play2(other, "misc/talk.wav");
1873 if (self.spawnflags&SECRET_YES_SHOOT)
1875 self.health = 10000;
1876 self.takedamage = DAMAGE_YES;
1878 setorigin(self, self.oldorigin);
1879 self.think = func_null;
1883 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1884 Basic secret door. Slides back, then to the side. Angle determines direction.
1885 wait = # of seconds before coming back
1886 1st_left = 1st move is left of arrow
1887 1st_down = 1st move is down from arrow
1888 always_shoot = even if targeted, keep shootable
1889 t_width = override WIDTH to move back (or height if going down)
1890 t_length = override LENGTH to move sideways
1891 "dmg" damage to inflict when blocked (2 default)
1893 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1900 void spawnfunc_func_door_secret()
1902 /*if (!self.deathtype) // map makers can override this
1903 self.deathtype = " got in the way";*/
1909 self.mangle = self.angles;
1910 self.angles = '0 0 0';
1911 self.classname = "door";
1912 if (!InitMovingBrushTrigger())
1914 self.effects |= EF_LOWPRECISION;
1916 self.touch = secret_touch;
1917 self.blocked = secret_blocked;
1919 self.use = fd_secret_use;
1924 self.spawnflags |= SECRET_YES_SHOOT;
1926 if(self.spawnflags&SECRET_YES_SHOOT)
1928 self.health = 10000;
1929 self.takedamage = DAMAGE_YES;
1930 self.event_damage = fd_secret_damage;
1932 self.oldorigin = self.origin;
1934 self.wait = 5; // 5 seconds before closing
1936 self.reset = secret_reset;
1940 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1941 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1942 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
1943 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1944 height: amplitude modifier (default 32)
1945 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1946 noise: path/name of looping .wav file to play.
1947 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1951 void func_fourier_controller_think()
1956 self.nextthink = time + 0.1;
1957 if(self.owner.active != ACTIVE_ACTIVE)
1959 self.owner.velocity = '0 0 0';
1964 n = floor((tokenize_console(self.owner.netname)) / 5);
1965 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1967 v = self.owner.destvec;
1969 for(i = 0; i < n; ++i)
1971 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1972 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;
1975 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1976 // * 10 so it will arrive in 0.1 sec
1977 self.owner.velocity = (v - self.owner.origin) * 10;
1980 void spawnfunc_func_fourier()
1983 if (self.noise != "")
1985 precache_sound(self.noise);
1986 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
1993 self.destvec = self.origin;
1994 self.cnt = 360 / self.speed;
1996 self.blocked = generic_plat_blocked;
1997 if(self.dmg && (self.message == ""))
1998 self.message = " was squished";
1999 if(self.dmg && (self.message2 == ""))
2000 self.message2 = "was squished by";
2001 if(self.dmg && (!self.dmgtime))
2002 self.dmgtime = 0.25;
2003 self.dmgtime2 = time;
2005 if(self.netname == "")
2006 self.netname = "1 0 0 0 1";
2008 if (!InitMovingBrushTrigger())
2011 self.active = ACTIVE_ACTIVE;
2013 // wait for targets to spawn
2014 controller = spawn();
2015 controller.classname = "func_fourier_controller";
2016 controller.owner = self;
2017 controller.nextthink = time + 1;
2018 controller.think = func_fourier_controller_think;
2019 self.nextthink = self.ltime + 999999999;
2020 self.think = SUB_NullThink; // for PushMove
2022 // Savage: Reduce bandwith, critical on e.g. nexdm02
2023 self.effects |= EF_LOWPRECISION;
2025 // TODO make a reset function for this one
2028 // reusing some fields havocbots declared
2029 .entity wp00, wp01, wp02, wp03;
2031 .float targetfactor, target2factor, target3factor, target4factor;
2032 .vector targetnormal, target2normal, target3normal, target4normal;
2034 vector func_vectormamamam_origin(entity o, float t)
2046 p = e.origin + t * e.velocity;
2048 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
2050 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
2056 p = e.origin + t * e.velocity;
2058 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
2060 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
2066 p = e.origin + t * e.velocity;
2068 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
2070 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
2076 p = e.origin + t * e.velocity;
2078 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
2080 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
2086 void func_vectormamamam_controller_think()
2088 self.nextthink = time + 0.1;
2090 if(self.owner.active != ACTIVE_ACTIVE)
2092 self.owner.velocity = '0 0 0';
2096 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
2097 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
2100 void func_vectormamamam_findtarget()
2102 if(self.target != "")
2103 self.wp00 = find(world, targetname, self.target);
2105 if(self.target2 != "")
2106 self.wp01 = find(world, targetname, self.target2);
2108 if(self.target3 != "")
2109 self.wp02 = find(world, targetname, self.target3);
2111 if(self.target4 != "")
2112 self.wp03 = find(world, targetname, self.target4);
2114 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
2115 objerror("No reference entity found, so there is nothing to move. Aborting.");
2117 self.destvec = self.origin - func_vectormamamam_origin(self, 0);
2120 controller = spawn();
2121 controller.classname = "func_vectormamamam_controller";
2122 controller.owner = self;
2123 controller.nextthink = time + 1;
2124 controller.think = func_vectormamamam_controller_think;
2127 void spawnfunc_func_vectormamamam()
2129 if (self.noise != "")
2131 precache_sound(self.noise);
2132 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
2135 if(!self.targetfactor)
2136 self.targetfactor = 1;
2138 if(!self.target2factor)
2139 self.target2factor = 1;
2141 if(!self.target3factor)
2142 self.target3factor = 1;
2144 if(!self.target4factor)
2145 self.target4factor = 1;
2147 if(vlen(self.targetnormal))
2148 self.targetnormal = normalize(self.targetnormal);
2150 if(vlen(self.target2normal))
2151 self.target2normal = normalize(self.target2normal);
2153 if(vlen(self.target3normal))
2154 self.target3normal = normalize(self.target3normal);
2156 if(vlen(self.target4normal))
2157 self.target4normal = normalize(self.target4normal);
2159 self.blocked = generic_plat_blocked;
2160 if(self.dmg && (self.message == ""))
2161 self.message = " was squished";
2162 if(self.dmg && (self.message == ""))
2163 self.message2 = "was squished by";
2164 if(self.dmg && (!self.dmgtime))
2165 self.dmgtime = 0.25;
2166 self.dmgtime2 = time;
2168 if(self.netname == "")
2169 self.netname = "1 0 0 0 1";
2171 if (!InitMovingBrushTrigger())
2174 // wait for targets to spawn
2175 self.nextthink = self.ltime + 999999999;
2176 self.think = SUB_NullThink; // for PushMove
2178 // Savage: Reduce bandwith, critical on e.g. nexdm02
2179 self.effects |= EF_LOWPRECISION;
2181 self.active = ACTIVE_ACTIVE;
2183 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
2186 void conveyor_think()
2190 // set myself as current conveyor where possible
2191 for(e = world; (e = findentity(e, conveyor, self)); )
2196 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2197 if(!e.conveyor.state)
2200 vector emin = e.absmin;
2201 vector emax = e.absmax;
2202 if(self.solid == SOLID_BSP)
2207 if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2208 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2212 for(e = world; (e = findentity(e, conveyor, self)); )
2214 if(IS_CLIENT(e)) // doing it via velocity has quite some advantages
2215 continue; // done in SV_PlayerPhysics
2217 setorigin(e, e.origin + self.movedir * sys_frametime);
2218 move_out_of_solid(e);
2219 UpdateCSQCProjectile(e);
2221 // stupid conveyor code
2222 tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2223 if(trace_fraction > 0)
2224 setorigin(e, trace_endpos);
2229 self.nextthink = time;
2234 self.state = !self.state;
2237 void conveyor_reset()
2239 self.state = (self.spawnflags & 1);
2242 void conveyor_init()
2246 self.movedir = self.movedir * self.speed;
2247 self.think = conveyor_think;
2248 self.nextthink = time;
2251 self.use = conveyor_use;
2252 self.reset = conveyor_reset;
2259 void spawnfunc_trigger_conveyor()
2266 void spawnfunc_func_conveyor()
2269 InitMovingBrushTrigger();
2270 self.movetype = MOVETYPE_NONE;