5 #include "command/common.qh"
11 #include "../common/constants.qh"
12 #include "../common/deathtypes.qh"
13 #include "../common/notifications.qh"
14 #include "../common/util.qh"
16 #include "../common/weapons/all.qh"
18 #include "../csqcmodellib/sv_model.qh"
20 #include "../warpzonelib/common.qh"
21 #include "../warpzonelib/mathlib.qh"
22 #include "../warpzonelib/util_server.qh"
27 void generic_plat_blocked()
29 if(self.dmg && other.takedamage != DAMAGE_NO) {
30 if(self.dmgtime2 < time) {
31 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
32 self.dmgtime2 = time + self.dmgtime;
35 // Gib dead/dying stuff
36 if(other.deadflag != DEAD_NO)
37 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
42 .entity trigger_field;
44 void() plat_center_touch;
45 void() plat_outside_touch;
46 void() plat_trigger_use;
50 const float PLAT_LOW_TRIGGER = 1;
52 void plat_spawn_inside_trigger()
58 trigger.touch = plat_center_touch;
59 trigger.movetype = MOVETYPE_NONE;
60 trigger.solid = SOLID_TRIGGER;
63 tmin = self.absmin + '25 25 0';
64 tmax = self.absmax - '25 25 -8';
65 tmin.z = tmax.z - (self.pos1_z - self.pos2_z + 8);
66 if (self.spawnflags & PLAT_LOW_TRIGGER)
69 if (self.size.x <= 50)
71 tmin.x = (self.mins.x + self.maxs.x) / 2;
74 if (self.size.y <= 50)
76 tmin.y = (self.mins.y + self.maxs.y) / 2;
84 setsize (trigger, tmin, tmax);
88 // otherwise, something is fishy...
90 objerror("plat_spawn_inside_trigger: platform has odd size or lip, can't spawn");
95 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
97 self.think = plat_go_down;
98 self.nextthink = self.ltime + 3;
101 void plat_hit_bottom()
103 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
109 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
111 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, plat_hit_bottom);
116 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
118 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, plat_hit_top);
121 void plat_center_touch()
123 if (!other.iscreature)
126 if (other.health <= 0)
132 else if (self.state == 1)
133 self.nextthink = self.ltime + 1; // delay going down
136 void plat_outside_touch()
138 if (!other.iscreature)
141 if (other.health <= 0)
149 void plat_trigger_use()
152 return; // already activated
159 if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
160 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
162 if((self.dmg) && (other.takedamage != DAMAGE_NO)) { // Shall we bite?
163 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
164 // Gib dead/dying stuff
165 if(other.deadflag != DEAD_NO)
166 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
171 else if (self.state == 3)
173 // when in other states, then the plat_crush event came delayed after
174 // plat state already had changed
175 // this isn't a bug per se!
181 self.use = func_null;
183 objerror ("plat_use: not in up state");
187 .string sound1, sound2;
193 setorigin (self, self.pos1);
199 setorigin (self, self.pos2);
201 self.use = plat_trigger_use;
205 .float platmovetype_start_default, platmovetype_end_default;
206 float set_platmovetype(entity e, string s)
208 // sets platmovetype_start and platmovetype_end based on a string consisting of two values
211 n = tokenize_console(s);
213 e.platmovetype_start = stof(argv(0));
215 e.platmovetype_start = 0;
218 e.platmovetype_end = stof(argv(1));
220 e.platmovetype_end = e.platmovetype_start;
223 if(argv(2) == "force")
224 return true; // no checking, return immediately
226 if(!cubic_speedfunc_is_sane(e.platmovetype_start, e.platmovetype_end))
228 objerror("Invalid platform move type; platform would go in reverse, which is not allowed.");
235 void spawnfunc_path_corner()
237 // setup values for overriding train movement
238 // if a second value does not exist, both start and end speeds are the single value specified
239 if(!set_platmovetype(self, self.platmovetype))
242 void spawnfunc_func_plat()
244 if (self.sounds == 0)
247 if(self.spawnflags & 4)
250 if(self.dmg && (self.message == ""))
251 self.message = "was squished";
252 if(self.dmg && (self.message2 == ""))
253 self.message2 = "was squished by";
255 if (self.sounds == 1)
257 precache_sound ("plats/plat1.wav");
258 precache_sound ("plats/plat2.wav");
259 self.noise = "plats/plat1.wav";
260 self.noise1 = "plats/plat2.wav";
263 if (self.sounds == 2)
265 precache_sound ("plats/medplat1.wav");
266 precache_sound ("plats/medplat2.wav");
267 self.noise = "plats/medplat1.wav";
268 self.noise1 = "plats/medplat2.wav";
273 precache_sound (self.sound1);
274 self.noise = self.sound1;
278 precache_sound (self.sound2);
279 self.noise1 = self.sound2;
282 self.mangle = self.angles;
283 self.angles = '0 0 0';
285 self.classname = "plat";
286 if (!InitMovingBrushTrigger())
288 self.effects |= EF_LOWPRECISION;
289 setsize (self, self.mins , self.maxs);
291 self.blocked = plat_crush;
298 self.height = self.size.z - self.lip;
300 self.pos1 = self.origin;
301 self.pos2 = self.origin;
302 self.pos2_z = self.origin.z - self.height;
304 self.reset = plat_reset;
307 plat_spawn_inside_trigger (); // the "start moving" trigger
310 .float train_wait_turning;
321 // if turning is enabled, the train will turn toward the next point while waiting
322 if(self.platmovetype_turn && !self.train_wait_turning)
326 targ = find(world, targetname, self.target);
327 if((self.spawnflags & 1) && targ.curvetarget)
328 cp = find(world, targetname, targ.curvetarget);
332 if(cp) // bezier curves movement
333 ang = cp.origin - (self.origin - self.view_ofs); // use the origin of the control point of the next path_corner
334 else // linear movement
335 ang = targ.origin - (self.origin - self.view_ofs); // use the origin of the next path_corner
336 ang = vectoangles(ang);
337 ang.x = -ang.x; // flip up / down orientation
339 if(self.wait > 0) // slow turning
340 SUB_CalcAngleMove(ang, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
341 else // instant turning
342 SUB_CalcAngleMove(ang, TSPEED_TIME, 0.0000001, train_wait);
343 self.train_wait_turning = true;
348 stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
350 if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
352 self.train_wait_turning = false;
357 self.think = train_next;
358 self.nextthink = self.ltime + self.wait;
364 entity targ, cp = world;
365 vector cp_org = '0 0 0';
367 targ = find(world, targetname, self.target);
368 self.target = targ.target;
369 if (self.spawnflags & 1)
373 cp = find(world, targetname, targ.curvetarget); // get its second target (the control point)
374 cp_org = cp.origin - self.view_ofs; // no control point found, assume a straight line to the destination
377 if (self.target == "")
378 objerror("train_next: no next target");
379 self.wait = targ.wait;
383 if(targ.platmovetype)
385 // this path_corner contains a movetype overrider, apply it
386 self.platmovetype_start = targ.platmovetype_start;
387 self.platmovetype_end = targ.platmovetype_end;
391 // this path_corner doesn't contain a movetype overrider, use the train's defaults
392 self.platmovetype_start = self.platmovetype_start_default;
393 self.platmovetype_end = self.platmovetype_end_default;
399 SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
401 SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
406 SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
408 SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
412 sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
415 void func_train_find()
418 targ = find(world, targetname, self.target);
419 self.target = targ.target;
420 if (self.target == "")
421 objerror("func_train_find: no next target");
422 setorigin(self, targ.origin - self.view_ofs);
423 self.nextthink = self.ltime + 1;
424 self.think = train_next;
427 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
428 Ridable platform, targets spawnfunc_path_corner path to follow.
429 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
430 target : targetname of first spawnfunc_path_corner (starts here)
432 void spawnfunc_func_train()
434 if (self.noise != "")
435 precache_sound(self.noise);
437 if (self.target == "")
438 objerror("func_train without a target");
442 if (!InitMovingBrushTrigger())
444 self.effects |= EF_LOWPRECISION;
446 if (self.spawnflags & 2)
448 self.platmovetype_turn = true;
449 self.view_ofs = '0 0 0'; // don't offset a rotating train, origin works differently now
452 self.view_ofs = self.mins;
454 // wait for targets to spawn
455 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
457 self.blocked = generic_plat_blocked;
458 if(self.dmg && (self.message == ""))
459 self.message = " was squished";
460 if(self.dmg && (self.message2 == ""))
461 self.message2 = "was squished by";
462 if(self.dmg && (!self.dmgtime))
464 self.dmgtime2 = time;
466 if(!set_platmovetype(self, self.platmovetype))
468 self.platmovetype_start_default = self.platmovetype_start;
469 self.platmovetype_end_default = self.platmovetype_end;
471 // TODO make a reset function for this one
474 void func_rotating_setactive(float astate)
477 if (astate == ACTIVE_TOGGLE)
479 if(self.active == ACTIVE_ACTIVE)
480 self.active = ACTIVE_NOT;
482 self.active = ACTIVE_ACTIVE;
485 self.active = astate;
487 if(self.active == ACTIVE_NOT)
488 self.avelocity = '0 0 0';
490 self.avelocity = self.pos1;
493 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
494 Brush model that spins in place on one axis (default Z).
495 speed : speed to rotate (in degrees per second)
496 noise : path/name of looping .wav file to play.
497 dmg : Do this mutch dmg every .dmgtime intervall when blocked
501 void spawnfunc_func_rotating()
503 if (self.noise != "")
505 precache_sound(self.noise);
506 ambientsound(self.origin, self.noise, VOL_BASE, ATTEN_IDLE);
509 self.active = ACTIVE_ACTIVE;
510 self.setactive = func_rotating_setactive;
514 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
515 if (self.spawnflags & 4) // X (untested)
516 self.avelocity = '0 0 1' * self.speed;
517 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
518 else if (self.spawnflags & 8) // Y (untested)
519 self.avelocity = '1 0 0' * self.speed;
520 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
522 self.avelocity = '0 1 0' * self.speed;
524 self.pos1 = self.avelocity;
526 if(self.dmg && (self.message == ""))
527 self.message = " was squished";
528 if(self.dmg && (self.message2 == ""))
529 self.message2 = "was squished by";
532 if(self.dmg && (!self.dmgtime))
535 self.dmgtime2 = time;
537 if (!InitMovingBrushTrigger())
539 // no EF_LOWPRECISION here, as rounding angles is bad
541 self.blocked = generic_plat_blocked;
543 // wait for targets to spawn
544 self.nextthink = self.ltime + 999999999;
545 self.think = SUB_NullThink; // for PushMove
547 // TODO make a reset function for this one
551 void func_bobbing_controller_think()
554 self.nextthink = time + 0.1;
556 if(self.owner.active != ACTIVE_ACTIVE)
558 self.owner.velocity = '0 0 0';
562 // calculate sinewave using makevectors
563 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
564 v = self.owner.destvec + self.owner.movedir * v_forward.y;
565 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
566 // * 10 so it will arrive in 0.1 sec
567 self.owner.velocity = (v - self.owner.origin) * 10;
570 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
571 Brush model that moves back and forth on one axis (default Z).
572 speed : how long one cycle takes in seconds (default 4)
573 height : how far the cycle moves (default 32)
574 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
575 noise : path/name of looping .wav file to play.
576 dmg : Do this mutch dmg every .dmgtime intervall when blocked
579 void spawnfunc_func_bobbing()
582 if (self.noise != "")
584 precache_sound(self.noise);
585 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
591 // center of bobbing motion
592 self.destvec = self.origin;
593 // time scale to get degrees
594 self.cnt = 360 / self.speed;
596 self.active = ACTIVE_ACTIVE;
598 // damage when blocked
599 self.blocked = generic_plat_blocked;
600 if(self.dmg && (self.message == ""))
601 self.message = " was squished";
602 if(self.dmg && (self.message2 == ""))
603 self.message2 = "was squished by";
604 if(self.dmg && (!self.dmgtime))
606 self.dmgtime2 = time;
609 if (self.spawnflags & 1) // X
610 self.movedir = '1 0 0' * self.height;
611 else if (self.spawnflags & 2) // Y
612 self.movedir = '0 1 0' * self.height;
614 self.movedir = '0 0 1' * self.height;
616 if (!InitMovingBrushTrigger())
619 // wait for targets to spawn
620 controller = spawn();
621 controller.classname = "func_bobbing_controller";
622 controller.owner = self;
623 controller.nextthink = time + 1;
624 controller.think = func_bobbing_controller_think;
625 self.nextthink = self.ltime + 999999999;
626 self.think = SUB_NullThink; // for PushMove
628 // Savage: Reduce bandwith, critical on e.g. nexdm02
629 self.effects |= EF_LOWPRECISION;
631 // TODO make a reset function for this one
635 void func_pendulum_controller_think()
638 self.nextthink = time + 0.1;
640 if (!(self.owner.active == ACTIVE_ACTIVE))
642 self.owner.avelocity_x = 0;
646 // calculate sinewave using makevectors
647 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
648 v = self.owner.speed * v_forward.y + self.cnt;
649 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
651 // * 10 so it will arrive in 0.1 sec
652 self.owner.avelocity_z = (remainder(v - self.owner.angles.z, 360)) * 10;
656 void spawnfunc_func_pendulum()
659 if (self.noise != "")
661 precache_sound(self.noise);
662 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
665 self.active = ACTIVE_ACTIVE;
667 // keys: angle, speed, phase, noise, freq
671 // not initializing self.dmg to 2, to allow damageless pendulum
673 if(self.dmg && (self.message == ""))
674 self.message = " was squished";
675 if(self.dmg && (self.message2 == ""))
676 self.message2 = "was squished by";
677 if(self.dmg && (!self.dmgtime))
679 self.dmgtime2 = time;
681 self.blocked = generic_plat_blocked;
683 self.avelocity_z = 0.0000001;
684 if (!InitMovingBrushTrigger())
689 // find pendulum length (same formula as Q3A)
690 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins.z))));
693 // copy initial angle
694 self.cnt = self.angles.z;
696 // wait for targets to spawn
697 controller = spawn();
698 controller.classname = "func_pendulum_controller";
699 controller.owner = self;
700 controller.nextthink = time + 1;
701 controller.think = func_pendulum_controller_think;
702 self.nextthink = self.ltime + 999999999;
703 self.think = SUB_NullThink; // for PushMove
705 //self.effects |= EF_LOWPRECISION;
707 // TODO make a reset function for this one
710 // button and multiple button
713 void() button_return;
717 self.state = STATE_TOP;
718 self.nextthink = self.ltime + self.wait;
719 self.think = button_return;
720 activator = self.enemy;
722 self.frame = 1; // use alternate textures
727 self.state = STATE_BOTTOM;
732 self.state = STATE_DOWN;
733 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
734 self.frame = 0; // use normal textures
736 self.takedamage = DAMAGE_YES; // can be shot again
740 void button_blocked()
742 // do nothing, just don't come all the way back out
748 self.health = self.max_health;
749 self.takedamage = DAMAGE_NO; // will be reset upon return
751 if (self.state == STATE_UP || self.state == STATE_TOP)
754 if (self.noise != "")
755 sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
757 self.state = STATE_UP;
758 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
763 self.health = self.max_health;
764 setorigin(self, self.pos1);
765 self.frame = 0; // use normal textures
766 self.state = STATE_BOTTOM;
768 self.takedamage = DAMAGE_YES; // can be shot again
773 if(self.active != ACTIVE_ACTIVE)
776 self.enemy = activator;
784 if (!other.iscreature)
786 if(other.velocity * self.movedir < 0)
790 self.enemy = other.owner;
794 void button_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
796 if(self.spawnflags & DOOR_NOSPLASH)
797 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
799 self.health = self.health - damage;
800 if (self.health <= 0)
802 self.enemy = damage_attacker;
808 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
809 When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again.
811 "angle" determines the opening direction
812 "target" all entities with a matching targetname will be used
813 "speed" override the default 40 speed
814 "wait" override the default 1 second wait (-1 = never return)
815 "lip" override the default 4 pixel lip remaining at end of move
816 "health" if set, the button must be killed instead of touched. If set to -1, the button will fire on ANY attack, even damageless ones like the InstaGib laser
823 void spawnfunc_func_button()
827 if (!InitMovingBrushTrigger())
829 self.effects |= EF_LOWPRECISION;
831 self.blocked = button_blocked;
832 self.use = button_use;
834 // if (self.health == 0) // all buttons are now shootable
838 self.max_health = self.health;
839 self.event_damage = button_damage;
840 self.takedamage = DAMAGE_YES;
843 self.touch = button_touch;
853 precache_sound(self.noise);
855 self.active = ACTIVE_ACTIVE;
857 self.pos1 = self.origin;
858 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
859 self.flags |= FL_NOTARGET;
865 const float DOOR_START_OPEN = 1;
866 const float DOOR_DONT_LINK = 4;
867 const float DOOR_TOGGLE = 32;
871 Doors are similar to buttons, but can spawn a fat trigger field around them
872 to open without a touch, and they link together to form simultanious
875 Door.owner is the master door. If there is only one door, it points to itself.
876 If multiple doors, all will point to a single one.
878 Door.enemy chains from the master door through all doors linked in the chain.
883 =============================================================================
887 =============================================================================
892 void() door_rotating_go_down;
893 void() door_rotating_go_up;
898 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
899 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
902 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
903 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
905 //Dont chamge direction for dead or dying stuff
906 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
909 if (self.state == STATE_DOWN)
910 if (self.classname == "door")
915 door_rotating_go_up ();
918 if (self.classname == "door")
923 door_rotating_go_down ();
927 //gib dying stuff just to make sure
928 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
929 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
933 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
934 // if a door has a negative wait, it would never come back if blocked,
935 // so let it just squash the object to death real fast
936 /* if (self.wait >= 0)
938 if (self.state == STATE_DOWN)
949 if (self.noise1 != "")
950 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
951 self.state = STATE_TOP;
952 if (self.spawnflags & DOOR_TOGGLE)
953 return; // don't come down automatically
954 if (self.classname == "door")
956 self.think = door_go_down;
959 self.think = door_rotating_go_down;
961 self.nextthink = self.ltime + self.wait;
964 void door_hit_bottom()
966 if (self.noise1 != "")
967 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
968 self.state = STATE_BOTTOM;
973 if (self.noise2 != "")
974 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
977 self.takedamage = DAMAGE_YES;
978 self.health = self.max_health;
981 self.state = STATE_DOWN;
982 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
987 if (self.state == STATE_UP)
988 return; // already going up
990 if (self.state == STATE_TOP)
991 { // reset top wait time
992 self.nextthink = self.ltime + self.wait;
996 if (self.noise2 != "")
997 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
998 self.state = STATE_UP;
999 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
1002 oldmessage = self.message;
1005 self.message = oldmessage;
1011 =============================================================================
1013 ACTIVATION FUNCTIONS
1015 =============================================================================
1018 float door_check_keys(void) {
1019 entity door = self.owner ? self.owner : self;
1025 // this door require a key
1026 // only a player can have a key
1027 if (!IS_PLAYER(other))
1030 if (item_keys_usekey(door, other)) {
1031 // some keys were used
1032 if (other.key_door_messagetime <= time) {
1033 play2(other, "misc/talk.wav");
1034 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
1035 other.key_door_messagetime = time + 2;
1038 // no keys were used
1039 if (other.key_door_messagetime <= time) {
1040 play2(other, "misc/talk.wav");
1041 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
1042 other.key_door_messagetime = time + 2;
1046 if (door.itemkeys) {
1047 // door is now unlocked
1048 play2(other, "misc/talk.wav");
1049 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_UNLOCKED);
1061 if (self.owner != self)
1062 objerror ("door_fire: self.owner != self");
1066 if (self.spawnflags & DOOR_TOGGLE)
1068 if (self.state == STATE_UP || self.state == STATE_TOP)
1073 if (self.classname == "door")
1079 door_rotating_go_down ();
1082 } while ( (self != starte) && (self != world) );
1088 // trigger all paired doors
1092 if (self.classname == "door")
1097 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
1098 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
1100 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
1101 self.pos2 = '0 0 0' - self.pos2;
1103 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
1104 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
1105 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
1107 door_rotating_go_up ();
1111 } while ( (self != starte) && (self != world) );
1120 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
1132 void door_trigger_touch()
1134 if (other.health < 1)
1135 if (!(other.iscreature && other.deadflag == DEAD_NO))
1138 if (time < self.attack_finished_single)
1141 // check if door is locked
1142 if (!door_check_keys())
1145 self.attack_finished_single = time + 1;
1154 void door_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
1157 if(self.spawnflags & DOOR_NOSPLASH)
1158 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
1160 self.health = self.health - damage;
1162 if (self.itemkeys) {
1163 // don't allow opening doors through damage if keys are required
1167 if (self.health <= 0)
1171 self.health = self.max_health;
1172 self.takedamage = DAMAGE_NO; // wil be reset upon return
1188 if (!IS_PLAYER(other))
1190 if (self.owner.attack_finished_single > time)
1193 self.owner.attack_finished_single = time + 2;
1195 if (!(self.owner.dmg) && (self.owner.message != ""))
1197 if (IS_CLIENT(other))
1198 centerprint(other, self.owner.message);
1199 play2(other, "misc/talk.wav");
1204 void door_generic_plat_blocked()
1207 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1208 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1211 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1212 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1214 //Dont chamge direction for dead or dying stuff
1215 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1218 if (self.state == STATE_DOWN)
1219 door_rotating_go_up ();
1221 door_rotating_go_down ();
1224 //gib dying stuff just to make sure
1225 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1226 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1230 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1231 // if a door has a negative wait, it would never come back if blocked,
1232 // so let it just squash the object to death real fast
1233 /* if (self.wait >= 0)
1235 if (self.state == STATE_DOWN)
1236 door_rotating_go_up ();
1238 door_rotating_go_down ();
1244 void door_rotating_hit_top()
1246 if (self.noise1 != "")
1247 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1248 self.state = STATE_TOP;
1249 if (self.spawnflags & DOOR_TOGGLE)
1250 return; // don't come down automatically
1251 self.think = door_rotating_go_down;
1252 self.nextthink = self.ltime + self.wait;
1255 void door_rotating_hit_bottom()
1257 if (self.noise1 != "")
1258 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1259 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1261 self.pos2 = '0 0 0' - self.pos2;
1264 self.state = STATE_BOTTOM;
1267 void door_rotating_go_down()
1269 if (self.noise2 != "")
1270 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1271 if (self.max_health)
1273 self.takedamage = DAMAGE_YES;
1274 self.health = self.max_health;
1277 self.state = STATE_DOWN;
1278 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
1281 void door_rotating_go_up()
1283 if (self.state == STATE_UP)
1284 return; // already going up
1286 if (self.state == STATE_TOP)
1287 { // reset top wait time
1288 self.nextthink = self.ltime + self.wait;
1291 if (self.noise2 != "")
1292 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1293 self.state = STATE_UP;
1294 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
1297 oldmessage = self.message;
1300 self.message = oldmessage;
1307 =============================================================================
1311 =============================================================================
1315 entity spawn_field(vector fmins, vector fmaxs)
1321 trigger.classname = "doortriggerfield";
1322 trigger.movetype = MOVETYPE_NONE;
1323 trigger.solid = SOLID_TRIGGER;
1324 trigger.owner = self;
1325 trigger.touch = door_trigger_touch;
1329 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1334 entity LinkDoors_nextent(entity cur, entity near, entity pass)
1336 while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
1342 float LinkDoors_isconnected(entity e1, entity e2, entity pass)
1345 if (e1.absmin.x > e2.absmax.x + DELTA)
1347 if (e1.absmin.y > e2.absmax.y + DELTA)
1349 if (e1.absmin.z > e2.absmax.z + DELTA)
1351 if (e2.absmin.x > e1.absmax.x + DELTA)
1353 if (e2.absmin.y > e1.absmax.y + DELTA)
1355 if (e2.absmin.z > e1.absmax.z + DELTA)
1370 vector cmins, cmaxs;
1373 return; // already linked by another door
1374 if (self.spawnflags & 4)
1376 self.owner = self.enemy = self;
1384 self.trigger_field = spawn_field(self.absmin, self.absmax);
1386 return; // don't want to link this door
1389 FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
1391 // set owner, and make a loop of the chain
1392 dprint("LinkDoors: linking doors:");
1393 for(t = self; ; t = t.enemy)
1395 dprint(" ", etos(t));
1397 if(t.enemy == world)
1405 // collect health, targetname, message, size
1406 cmins = self.absmin;
1407 cmaxs = self.absmax;
1408 for(t = self; ; t = t.enemy)
1410 if(t.health && !self.health)
1411 self.health = t.health;
1412 if((t.targetname != "") && (self.targetname == ""))
1413 self.targetname = t.targetname;
1414 if((t.message != "") && (self.message == ""))
1415 self.message = t.message;
1416 if (t.absmin.x < cmins.x)
1417 cmins.x = t.absmin.x;
1418 if (t.absmin.y < cmins.y)
1419 cmins.y = t.absmin.y;
1420 if (t.absmin.z < cmins.z)
1421 cmins.z = t.absmin.z;
1422 if (t.absmax.x > cmaxs.x)
1423 cmaxs.x = t.absmax.x;
1424 if (t.absmax.y > cmaxs.y)
1425 cmaxs.y = t.absmax.y;
1426 if (t.absmax.z > cmaxs.z)
1427 cmaxs.z = t.absmax.z;
1432 // distribute health, targetname, message
1433 for(t = self; t; t = t.enemy)
1435 t.health = self.health;
1436 t.targetname = self.targetname;
1437 t.message = self.message;
1442 // shootable, or triggered doors just needed the owner/enemy links,
1443 // they don't spawn a field
1452 self.trigger_field = spawn_field(cmins, cmaxs);
1456 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
1457 if two doors touch, they are assumed to be connected and operate as a unit.
1459 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1461 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).
1463 GOLD_KEY causes the door to open only if the activator holds a gold key.
1465 SILVER_KEY causes the door to open only if the activator holds a silver key.
1467 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1468 "angle" determines the opening direction
1469 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1470 "health" if set, door must be shot open
1471 "speed" movement speed (100 default)
1472 "wait" wait before returning (3 default, -1 = never return)
1473 "lip" lip remaining at end of move (8 default)
1474 "dmg" damage to inflict when blocked (2 default)
1481 FIXME: only one sound set available at the time being
1485 void door_init_startopen()
1487 setorigin (self, self.pos2);
1488 self.pos2 = self.pos1;
1489 self.pos1 = self.origin;
1494 setorigin(self, self.pos1);
1495 self.velocity = '0 0 0';
1496 self.state = STATE_BOTTOM;
1497 self.think = func_null;
1501 // spawnflags require key (for now only func_door)
1502 const float SPAWNFLAGS_GOLD_KEY = 8;
1503 const float SPAWNFLAGS_SILVER_KEY = 16;
1504 void spawnfunc_func_door()
1506 // Quake 1 keys compatibility
1507 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
1508 self.itemkeys |= ITEM_KEY_BIT(0);
1509 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
1510 self.itemkeys |= ITEM_KEY_BIT(1);
1512 //if (!self.deathtype) // map makers can override this
1513 // self.deathtype = " got in the way";
1516 self.max_health = self.health;
1517 if (!InitMovingBrushTrigger())
1519 self.effects |= EF_LOWPRECISION;
1520 self.classname = "door";
1522 self.blocked = door_blocked;
1523 self.use = door_use;
1525 // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
1526 // if(self.spawnflags & 8)
1527 // self.dmg = 10000;
1529 if(self.dmg && (self.message == ""))
1530 self.message = "was squished";
1531 if(self.dmg && (self.message2 == ""))
1532 self.message2 = "was squished by";
1534 if (self.sounds > 0)
1536 precache_sound ("plats/medplat1.wav");
1537 precache_sound ("plats/medplat2.wav");
1538 self.noise2 = "plats/medplat1.wav";
1539 self.noise1 = "plats/medplat2.wav";
1549 self.pos1 = self.origin;
1550 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1552 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1553 // but spawn in the open position
1554 if (self.spawnflags & DOOR_START_OPEN)
1555 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1557 self.state = STATE_BOTTOM;
1561 self.takedamage = DAMAGE_YES;
1562 self.event_damage = door_damage;
1568 self.touch = door_touch;
1570 // LinkDoors can't be done until all of the doors have been spawned, so
1571 // the sizes can be detected properly.
1572 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1574 self.reset = door_reset;
1577 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1578 if two doors touch, they are assumed to be connected and operate as a unit.
1580 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1582 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1583 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1584 must have set trigger_reverse to 1.
1585 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1587 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).
1589 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1590 "angle" determines the destination angle for opening. negative values reverse the direction.
1591 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1592 "health" if set, door must be shot open
1593 "speed" movement speed (100 default)
1594 "wait" wait before returning (3 default, -1 = never return)
1595 "dmg" damage to inflict when blocked (2 default)
1602 FIXME: only one sound set available at the time being
1605 void door_rotating_reset()
1607 self.angles = self.pos1;
1608 self.avelocity = '0 0 0';
1609 self.state = STATE_BOTTOM;
1610 self.think = func_null;
1614 void door_rotating_init_startopen()
1616 self.angles = self.movedir;
1617 self.pos2 = '0 0 0';
1618 self.pos1 = self.movedir;
1622 void spawnfunc_func_door_rotating()
1625 //if (!self.deathtype) // map makers can override this
1626 // self.deathtype = " got in the way";
1628 // I abuse "movedir" for denoting the axis for now
1629 if (self.spawnflags & 64) // X (untested)
1630 self.movedir = '0 0 1';
1631 else if (self.spawnflags & 128) // Y (untested)
1632 self.movedir = '1 0 0';
1634 self.movedir = '0 1 0';
1636 if (self.angles.y ==0) self.angles_y = 90;
1638 self.movedir = self.movedir * self.angles.y;
1639 self.angles = '0 0 0';
1641 self.max_health = self.health;
1642 self.avelocity = self.movedir;
1643 if (!InitMovingBrushTrigger())
1645 self.velocity = '0 0 0';
1646 //self.effects |= EF_LOWPRECISION;
1647 self.classname = "door_rotating";
1649 self.blocked = door_blocked;
1650 self.use = door_use;
1652 if(self.spawnflags & 8)
1655 if(self.dmg && (self.message == ""))
1656 self.message = "was squished";
1657 if(self.dmg && (self.message2 == ""))
1658 self.message2 = "was squished by";
1660 if (self.sounds > 0)
1662 precache_sound ("plats/medplat1.wav");
1663 precache_sound ("plats/medplat2.wav");
1664 self.noise2 = "plats/medplat1.wav";
1665 self.noise1 = "plats/medplat2.wav";
1672 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1674 self.pos1 = '0 0 0';
1675 self.pos2 = self.movedir;
1677 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1678 // but spawn in the open position
1679 if (self.spawnflags & DOOR_START_OPEN)
1680 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1682 self.state = STATE_BOTTOM;
1686 self.takedamage = DAMAGE_YES;
1687 self.event_damage = door_damage;
1693 self.touch = door_touch;
1695 // LinkDoors can't be done until all of the doors have been spawned, so
1696 // the sizes can be detected properly.
1697 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1699 self.reset = door_rotating_reset;
1703 =============================================================================
1707 =============================================================================
1710 void() fd_secret_move1;
1711 void() fd_secret_move2;
1712 void() fd_secret_move3;
1713 void() fd_secret_move4;
1714 void() fd_secret_move5;
1715 void() fd_secret_move6;
1716 void() fd_secret_done;
1718 const float SECRET_OPEN_ONCE = 1; // stays open
1719 const float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1720 const float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1721 const float SECRET_NO_SHOOT = 8; // only opened by trigger
1722 const float SECRET_YES_SHOOT = 16; // shootable even if targeted
1724 void fd_secret_use()
1727 string message_save;
1729 self.health = 10000;
1730 self.bot_attack = true;
1732 // exit if still moving around...
1733 if (self.origin != self.oldorigin)
1736 message_save = self.message;
1737 self.message = ""; // no more message
1738 SUB_UseTargets(); // fire all targets / killtargets
1739 self.message = message_save;
1741 self.velocity = '0 0 0';
1743 // Make a sound, wait a little...
1745 if (self.noise1 != "")
1746 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1747 self.nextthink = self.ltime + 0.1;
1749 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1750 makevectors(self.mangle);
1754 if (self.spawnflags & SECRET_1ST_DOWN)
1755 self.t_width = fabs(v_up * self.size);
1757 self.t_width = fabs(v_right * self.size);
1761 self.t_length = fabs(v_forward * self.size);
1763 if (self.spawnflags & SECRET_1ST_DOWN)
1764 self.dest1 = self.origin - v_up * self.t_width;
1766 self.dest1 = self.origin + v_right * (self.t_width * temp);
1768 self.dest2 = self.dest1 + v_forward * self.t_length;
1769 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
1770 if (self.noise2 != "")
1771 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1774 void fd_secret_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
1779 // Wait after first movement...
1780 void fd_secret_move1()
1782 self.nextthink = self.ltime + 1.0;
1783 self.think = fd_secret_move2;
1784 if (self.noise3 != "")
1785 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1788 // Start moving sideways w/sound...
1789 void fd_secret_move2()
1791 if (self.noise2 != "")
1792 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1793 SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
1796 // Wait here until time to go back...
1797 void fd_secret_move3()
1799 if (self.noise3 != "")
1800 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1801 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1803 self.nextthink = self.ltime + self.wait;
1804 self.think = fd_secret_move4;
1809 void fd_secret_move4()
1811 if (self.noise2 != "")
1812 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1813 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
1817 void fd_secret_move5()
1819 self.nextthink = self.ltime + 1.0;
1820 self.think = fd_secret_move6;
1821 if (self.noise3 != "")
1822 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1825 void fd_secret_move6()
1827 if (self.noise2 != "")
1828 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1829 SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
1832 void fd_secret_done()
1834 if (self.spawnflags&SECRET_YES_SHOOT)
1836 self.health = 10000;
1837 self.takedamage = DAMAGE_YES;
1838 //self.th_pain = fd_secret_use;
1840 if (self.noise3 != "")
1841 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1844 void secret_blocked()
1846 if (time < self.attack_finished_single)
1848 self.attack_finished_single = time + 0.5;
1849 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1861 if (!other.iscreature)
1863 if (self.attack_finished_single > time)
1866 self.attack_finished_single = time + 2;
1870 if (IS_CLIENT(other))
1871 centerprint(other, self.message);
1872 play2(other, "misc/talk.wav");
1878 if (self.spawnflags&SECRET_YES_SHOOT)
1880 self.health = 10000;
1881 self.takedamage = DAMAGE_YES;
1883 setorigin(self, self.oldorigin);
1884 self.think = func_null;
1888 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1889 Basic secret door. Slides back, then to the side. Angle determines direction.
1890 wait = # of seconds before coming back
1891 1st_left = 1st move is left of arrow
1892 1st_down = 1st move is down from arrow
1893 always_shoot = even if targeted, keep shootable
1894 t_width = override WIDTH to move back (or height if going down)
1895 t_length = override LENGTH to move sideways
1896 "dmg" damage to inflict when blocked (2 default)
1898 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1905 void spawnfunc_func_door_secret()
1907 /*if (!self.deathtype) // map makers can override this
1908 self.deathtype = " got in the way";*/
1914 self.mangle = self.angles;
1915 self.angles = '0 0 0';
1916 self.classname = "door";
1917 if (!InitMovingBrushTrigger())
1919 self.effects |= EF_LOWPRECISION;
1921 self.touch = secret_touch;
1922 self.blocked = secret_blocked;
1924 self.use = fd_secret_use;
1929 self.spawnflags |= SECRET_YES_SHOOT;
1931 if(self.spawnflags&SECRET_YES_SHOOT)
1933 self.health = 10000;
1934 self.takedamage = DAMAGE_YES;
1935 self.event_damage = fd_secret_damage;
1937 self.oldorigin = self.origin;
1939 self.wait = 5; // 5 seconds before closing
1941 self.reset = secret_reset;
1945 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1946 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1947 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
1948 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1949 height: amplitude modifier (default 32)
1950 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1951 noise: path/name of looping .wav file to play.
1952 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1956 void func_fourier_controller_think()
1961 self.nextthink = time + 0.1;
1962 if(self.owner.active != ACTIVE_ACTIVE)
1964 self.owner.velocity = '0 0 0';
1969 n = floor((tokenize_console(self.owner.netname)) / 5);
1970 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1972 v = self.owner.destvec;
1974 for(i = 0; i < n; ++i)
1976 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1977 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;
1980 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1981 // * 10 so it will arrive in 0.1 sec
1982 self.owner.velocity = (v - self.owner.origin) * 10;
1985 void spawnfunc_func_fourier()
1988 if (self.noise != "")
1990 precache_sound(self.noise);
1991 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
1998 self.destvec = self.origin;
1999 self.cnt = 360 / self.speed;
2001 self.blocked = generic_plat_blocked;
2002 if(self.dmg && (self.message == ""))
2003 self.message = " was squished";
2004 if(self.dmg && (self.message2 == ""))
2005 self.message2 = "was squished by";
2006 if(self.dmg && (!self.dmgtime))
2007 self.dmgtime = 0.25;
2008 self.dmgtime2 = time;
2010 if(self.netname == "")
2011 self.netname = "1 0 0 0 1";
2013 if (!InitMovingBrushTrigger())
2016 self.active = ACTIVE_ACTIVE;
2018 // wait for targets to spawn
2019 controller = spawn();
2020 controller.classname = "func_fourier_controller";
2021 controller.owner = self;
2022 controller.nextthink = time + 1;
2023 controller.think = func_fourier_controller_think;
2024 self.nextthink = self.ltime + 999999999;
2025 self.think = SUB_NullThink; // for PushMove
2027 // Savage: Reduce bandwith, critical on e.g. nexdm02
2028 self.effects |= EF_LOWPRECISION;
2030 // TODO make a reset function for this one
2033 // reusing some fields havocbots declared
2034 .entity wp00, wp01, wp02, wp03;
2036 .float targetfactor, target2factor, target3factor, target4factor;
2037 .vector targetnormal, target2normal, target3normal, target4normal;
2039 vector func_vectormamamam_origin(entity o, float t)
2051 p = e.origin + t * e.velocity;
2053 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
2055 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
2061 p = e.origin + t * e.velocity;
2063 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
2065 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
2071 p = e.origin + t * e.velocity;
2073 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
2075 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
2081 p = e.origin + t * e.velocity;
2083 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
2085 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
2091 void func_vectormamamam_controller_think()
2093 self.nextthink = time + 0.1;
2095 if(self.owner.active != ACTIVE_ACTIVE)
2097 self.owner.velocity = '0 0 0';
2101 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
2102 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
2105 void func_vectormamamam_findtarget()
2107 if(self.target != "")
2108 self.wp00 = find(world, targetname, self.target);
2110 if(self.target2 != "")
2111 self.wp01 = find(world, targetname, self.target2);
2113 if(self.target3 != "")
2114 self.wp02 = find(world, targetname, self.target3);
2116 if(self.target4 != "")
2117 self.wp03 = find(world, targetname, self.target4);
2119 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
2120 objerror("No reference entity found, so there is nothing to move. Aborting.");
2122 self.destvec = self.origin - func_vectormamamam_origin(self, 0);
2125 controller = spawn();
2126 controller.classname = "func_vectormamamam_controller";
2127 controller.owner = self;
2128 controller.nextthink = time + 1;
2129 controller.think = func_vectormamamam_controller_think;
2132 void spawnfunc_func_vectormamamam()
2134 if (self.noise != "")
2136 precache_sound(self.noise);
2137 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
2140 if(!self.targetfactor)
2141 self.targetfactor = 1;
2143 if(!self.target2factor)
2144 self.target2factor = 1;
2146 if(!self.target3factor)
2147 self.target3factor = 1;
2149 if(!self.target4factor)
2150 self.target4factor = 1;
2152 if(vlen(self.targetnormal))
2153 self.targetnormal = normalize(self.targetnormal);
2155 if(vlen(self.target2normal))
2156 self.target2normal = normalize(self.target2normal);
2158 if(vlen(self.target3normal))
2159 self.target3normal = normalize(self.target3normal);
2161 if(vlen(self.target4normal))
2162 self.target4normal = normalize(self.target4normal);
2164 self.blocked = generic_plat_blocked;
2165 if(self.dmg && (self.message == ""))
2166 self.message = " was squished";
2167 if(self.dmg && (self.message == ""))
2168 self.message2 = "was squished by";
2169 if(self.dmg && (!self.dmgtime))
2170 self.dmgtime = 0.25;
2171 self.dmgtime2 = time;
2173 if(self.netname == "")
2174 self.netname = "1 0 0 0 1";
2176 if (!InitMovingBrushTrigger())
2179 // wait for targets to spawn
2180 self.nextthink = self.ltime + 999999999;
2181 self.think = SUB_NullThink; // for PushMove
2183 // Savage: Reduce bandwith, critical on e.g. nexdm02
2184 self.effects |= EF_LOWPRECISION;
2186 self.active = ACTIVE_ACTIVE;
2188 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
2191 void conveyor_think()
2195 // set myself as current conveyor where possible
2196 for(e = world; (e = findentity(e, conveyor, self)); )
2201 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2202 if(!e.conveyor.state)
2205 vector emin = e.absmin;
2206 vector emax = e.absmax;
2207 if(self.solid == SOLID_BSP)
2212 if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2213 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2217 for(e = world; (e = findentity(e, conveyor, self)); )
2219 if(IS_CLIENT(e)) // doing it via velocity has quite some advantages
2220 continue; // done in SV_PlayerPhysics
2222 setorigin(e, e.origin + self.movedir * sys_frametime);
2223 move_out_of_solid(e);
2224 UpdateCSQCProjectile(e);
2226 // stupid conveyor code
2227 tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2228 if(trace_fraction > 0)
2229 setorigin(e, trace_endpos);
2234 self.nextthink = time;
2239 self.state = !self.state;
2242 void conveyor_reset()
2244 self.state = (self.spawnflags & 1);
2247 void conveyor_init()
2251 self.movedir = self.movedir * self.speed;
2252 self.think = conveyor_think;
2253 self.nextthink = time;
2256 self.use = conveyor_use;
2257 self.reset = conveyor_reset;
2264 void spawnfunc_trigger_conveyor()
2271 void spawnfunc_func_conveyor()
2274 InitMovingBrushTrigger();
2275 self.movetype = MOVETYPE_NONE;