5 #include "command/common.qh"
10 #include "../common/constants.qh"
11 #include "../common/deathtypes.qh"
12 #include "../common/notifications.qh"
13 #include "../common/util.qh"
15 #include "../common/weapons/all.qh"
17 #include "../csqcmodellib/sv_model.qh"
19 #include "../warpzonelib/common.qh"
20 #include "../warpzonelib/mathlib.qh"
21 #include "../warpzonelib/util_server.qh"
26 void generic_plat_blocked()
28 if(self.dmg && other.takedamage != DAMAGE_NO) {
29 if(self.dmgtime2 < time) {
30 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
31 self.dmgtime2 = time + self.dmgtime;
34 // Gib dead/dying stuff
35 if(other.deadflag != DEAD_NO)
36 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
41 .entity trigger_field;
43 void() plat_center_touch;
44 void() plat_outside_touch;
45 void() plat_trigger_use;
49 const float PLAT_LOW_TRIGGER = 1;
51 void plat_spawn_inside_trigger()
57 trigger.touch = plat_center_touch;
58 trigger.movetype = MOVETYPE_NONE;
59 trigger.solid = SOLID_TRIGGER;
62 tmin = self.absmin + '25 25 0';
63 tmax = self.absmax - '25 25 -8';
64 tmin.z = tmax.z - (self.pos1_z - self.pos2_z + 8);
65 if (self.spawnflags & PLAT_LOW_TRIGGER)
68 if (self.size.x <= 50)
70 tmin.x = (self.mins.x + self.maxs.x) / 2;
73 if (self.size.y <= 50)
75 tmin.y = (self.mins.y + self.maxs.y) / 2;
83 setsize (trigger, tmin, tmax);
87 // otherwise, something is fishy...
89 objerror("plat_spawn_inside_trigger: platform has odd size or lip, can't spawn");
94 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
96 self.think = plat_go_down;
97 self.nextthink = self.ltime + 3;
100 void plat_hit_bottom()
102 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
108 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
110 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, plat_hit_bottom);
115 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
117 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, plat_hit_top);
120 void plat_center_touch()
122 if (!other.iscreature)
125 if (other.health <= 0)
131 else if (self.state == 1)
132 self.nextthink = self.ltime + 1; // delay going down
135 void plat_outside_touch()
137 if (!other.iscreature)
140 if (other.health <= 0)
148 void plat_trigger_use()
151 return; // already activated
158 if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
159 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
161 if((self.dmg) && (other.takedamage != DAMAGE_NO)) { // Shall we bite?
162 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
163 // Gib dead/dying stuff
164 if(other.deadflag != DEAD_NO)
165 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
170 else if (self.state == 3)
172 // when in other states, then the plat_crush event came delayed after
173 // plat state already had changed
174 // this isn't a bug per se!
180 self.use = func_null;
182 objerror ("plat_use: not in up state");
186 .string sound1, sound2;
192 setorigin (self, self.pos1);
198 setorigin (self, self.pos2);
200 self.use = plat_trigger_use;
204 .float platmovetype_start_default, platmovetype_end_default;
205 float set_platmovetype(entity e, string s)
207 // sets platmovetype_start and platmovetype_end based on a string consisting of two values
210 n = tokenize_console(s);
212 e.platmovetype_start = stof(argv(0));
214 e.platmovetype_start = 0;
217 e.platmovetype_end = stof(argv(1));
219 e.platmovetype_end = e.platmovetype_start;
222 if(argv(2) == "force")
223 return true; // no checking, return immediately
225 if(!cubic_speedfunc_is_sane(e.platmovetype_start, e.platmovetype_end))
227 objerror("Invalid platform move type; platform would go in reverse, which is not allowed.");
234 void spawnfunc_path_corner()
236 // setup values for overriding train movement
237 // if a second value does not exist, both start and end speeds are the single value specified
238 if(!set_platmovetype(self, self.platmovetype))
241 void spawnfunc_func_plat()
243 if (self.sounds == 0)
246 if(self.spawnflags & 4)
249 if(self.dmg && (self.message == ""))
250 self.message = "was squished";
251 if(self.dmg && (self.message2 == ""))
252 self.message2 = "was squished by";
254 if (self.sounds == 1)
256 precache_sound ("plats/plat1.wav");
257 precache_sound ("plats/plat2.wav");
258 self.noise = "plats/plat1.wav";
259 self.noise1 = "plats/plat2.wav";
262 if (self.sounds == 2)
264 precache_sound ("plats/medplat1.wav");
265 precache_sound ("plats/medplat2.wav");
266 self.noise = "plats/medplat1.wav";
267 self.noise1 = "plats/medplat2.wav";
272 precache_sound (self.sound1);
273 self.noise = self.sound1;
277 precache_sound (self.sound2);
278 self.noise1 = self.sound2;
281 self.mangle = self.angles;
282 self.angles = '0 0 0';
284 self.classname = "plat";
285 if (!InitMovingBrushTrigger())
287 self.effects |= EF_LOWPRECISION;
288 setsize (self, self.mins , self.maxs);
290 self.blocked = plat_crush;
297 self.height = self.size.z - self.lip;
299 self.pos1 = self.origin;
300 self.pos2 = self.origin;
301 self.pos2_z = self.origin.z - self.height;
303 self.reset = plat_reset;
306 plat_spawn_inside_trigger (); // the "start moving" trigger
309 .float train_wait_turning;
320 // if turning is enabled, the train will turn toward the next point while waiting
321 if(self.platmovetype_turn && !self.train_wait_turning)
325 targ = find(world, targetname, self.target);
326 if((self.spawnflags & 1) && targ.curvetarget)
327 cp = find(world, targetname, targ.curvetarget);
331 if(cp) // bezier curves movement
332 ang = cp.origin - (self.origin - self.view_ofs); // use the origin of the control point of the next path_corner
333 else // linear movement
334 ang = targ.origin - (self.origin - self.view_ofs); // use the origin of the next path_corner
335 ang = vectoangles(ang);
336 ang.x = -ang.x; // flip up / down orientation
338 if(self.wait > 0) // slow turning
339 SUB_CalcAngleMove(ang, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
340 else // instant turning
341 SUB_CalcAngleMove(ang, TSPEED_TIME, 0.0000001, train_wait);
342 self.train_wait_turning = true;
347 stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
349 if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
351 self.train_wait_turning = false;
356 self.think = train_next;
357 self.nextthink = self.ltime + self.wait;
363 entity targ, cp = world;
364 vector cp_org = '0 0 0';
366 targ = find(world, targetname, self.target);
367 self.target = targ.target;
368 if (self.spawnflags & 1)
372 cp = find(world, targetname, targ.curvetarget); // get its second target (the control point)
373 cp_org = cp.origin - self.view_ofs; // no control point found, assume a straight line to the destination
376 if (self.target == "")
377 objerror("train_next: no next target");
378 self.wait = targ.wait;
382 if(targ.platmovetype)
384 // this path_corner contains a movetype overrider, apply it
385 self.platmovetype_start = targ.platmovetype_start;
386 self.platmovetype_end = targ.platmovetype_end;
390 // this path_corner doesn't contain a movetype overrider, use the train's defaults
391 self.platmovetype_start = self.platmovetype_start_default;
392 self.platmovetype_end = self.platmovetype_end_default;
398 SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
400 SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
405 SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
407 SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
411 sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
414 void func_train_find()
417 targ = find(world, targetname, self.target);
418 self.target = targ.target;
419 if (self.target == "")
420 objerror("func_train_find: no next target");
421 setorigin(self, targ.origin - self.view_ofs);
422 self.nextthink = self.ltime + 1;
423 self.think = train_next;
426 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
427 Ridable platform, targets spawnfunc_path_corner path to follow.
428 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
429 target : targetname of first spawnfunc_path_corner (starts here)
431 void spawnfunc_func_train()
433 if (self.noise != "")
434 precache_sound(self.noise);
436 if (self.target == "")
437 objerror("func_train without a target");
441 if (!InitMovingBrushTrigger())
443 self.effects |= EF_LOWPRECISION;
445 if (self.spawnflags & 2)
447 self.platmovetype_turn = true;
448 self.view_ofs = '0 0 0'; // don't offset a rotating train, origin works differently now
451 self.view_ofs = self.mins;
453 // wait for targets to spawn
454 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
456 self.blocked = generic_plat_blocked;
457 if(self.dmg && (self.message == ""))
458 self.message = " was squished";
459 if(self.dmg && (self.message2 == ""))
460 self.message2 = "was squished by";
461 if(self.dmg && (!self.dmgtime))
463 self.dmgtime2 = time;
465 if(!set_platmovetype(self, self.platmovetype))
467 self.platmovetype_start_default = self.platmovetype_start;
468 self.platmovetype_end_default = self.platmovetype_end;
470 // TODO make a reset function for this one
473 void func_rotating_setactive(float astate)
476 if (astate == ACTIVE_TOGGLE)
478 if(self.active == ACTIVE_ACTIVE)
479 self.active = ACTIVE_NOT;
481 self.active = ACTIVE_ACTIVE;
484 self.active = astate;
486 if(self.active == ACTIVE_NOT)
487 self.avelocity = '0 0 0';
489 self.avelocity = self.pos1;
492 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
493 Brush model that spins in place on one axis (default Z).
494 speed : speed to rotate (in degrees per second)
495 noise : path/name of looping .wav file to play.
496 dmg : Do this mutch dmg every .dmgtime intervall when blocked
500 void spawnfunc_func_rotating()
502 if (self.noise != "")
504 precache_sound(self.noise);
505 ambientsound(self.origin, self.noise, VOL_BASE, ATTEN_IDLE);
508 self.active = ACTIVE_ACTIVE;
509 self.setactive = func_rotating_setactive;
513 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
514 if (self.spawnflags & 4) // X (untested)
515 self.avelocity = '0 0 1' * self.speed;
516 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
517 else if (self.spawnflags & 8) // Y (untested)
518 self.avelocity = '1 0 0' * self.speed;
519 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
521 self.avelocity = '0 1 0' * self.speed;
523 self.pos1 = self.avelocity;
525 if(self.dmg && (self.message == ""))
526 self.message = " was squished";
527 if(self.dmg && (self.message2 == ""))
528 self.message2 = "was squished by";
531 if(self.dmg && (!self.dmgtime))
534 self.dmgtime2 = time;
536 if (!InitMovingBrushTrigger())
538 // no EF_LOWPRECISION here, as rounding angles is bad
540 self.blocked = generic_plat_blocked;
542 // wait for targets to spawn
543 self.nextthink = self.ltime + 999999999;
544 self.think = SUB_NullThink; // for PushMove
546 // TODO make a reset function for this one
550 void func_bobbing_controller_think()
553 self.nextthink = time + 0.1;
555 if(self.owner.active != ACTIVE_ACTIVE)
557 self.owner.velocity = '0 0 0';
561 // calculate sinewave using makevectors
562 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
563 v = self.owner.destvec + self.owner.movedir * v_forward.y;
564 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
565 // * 10 so it will arrive in 0.1 sec
566 self.owner.velocity = (v - self.owner.origin) * 10;
569 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
570 Brush model that moves back and forth on one axis (default Z).
571 speed : how long one cycle takes in seconds (default 4)
572 height : how far the cycle moves (default 32)
573 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
574 noise : path/name of looping .wav file to play.
575 dmg : Do this mutch dmg every .dmgtime intervall when blocked
578 void spawnfunc_func_bobbing()
581 if (self.noise != "")
583 precache_sound(self.noise);
584 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
590 // center of bobbing motion
591 self.destvec = self.origin;
592 // time scale to get degrees
593 self.cnt = 360 / self.speed;
595 self.active = ACTIVE_ACTIVE;
597 // damage when blocked
598 self.blocked = generic_plat_blocked;
599 if(self.dmg && (self.message == ""))
600 self.message = " was squished";
601 if(self.dmg && (self.message2 == ""))
602 self.message2 = "was squished by";
603 if(self.dmg && (!self.dmgtime))
605 self.dmgtime2 = time;
608 if (self.spawnflags & 1) // X
609 self.movedir = '1 0 0' * self.height;
610 else if (self.spawnflags & 2) // Y
611 self.movedir = '0 1 0' * self.height;
613 self.movedir = '0 0 1' * self.height;
615 if (!InitMovingBrushTrigger())
618 // wait for targets to spawn
619 controller = spawn();
620 controller.classname = "func_bobbing_controller";
621 controller.owner = self;
622 controller.nextthink = time + 1;
623 controller.think = func_bobbing_controller_think;
624 self.nextthink = self.ltime + 999999999;
625 self.think = SUB_NullThink; // for PushMove
627 // Savage: Reduce bandwith, critical on e.g. nexdm02
628 self.effects |= EF_LOWPRECISION;
630 // TODO make a reset function for this one
634 void func_pendulum_controller_think()
637 self.nextthink = time + 0.1;
639 if (!(self.owner.active == ACTIVE_ACTIVE))
641 self.owner.avelocity_x = 0;
645 // calculate sinewave using makevectors
646 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
647 v = self.owner.speed * v_forward.y + self.cnt;
648 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
650 // * 10 so it will arrive in 0.1 sec
651 self.owner.avelocity_z = (remainder(v - self.owner.angles.z, 360)) * 10;
655 void spawnfunc_func_pendulum()
658 if (self.noise != "")
660 precache_sound(self.noise);
661 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
664 self.active = ACTIVE_ACTIVE;
666 // keys: angle, speed, phase, noise, freq
670 // not initializing self.dmg to 2, to allow damageless pendulum
672 if(self.dmg && (self.message == ""))
673 self.message = " was squished";
674 if(self.dmg && (self.message2 == ""))
675 self.message2 = "was squished by";
676 if(self.dmg && (!self.dmgtime))
678 self.dmgtime2 = time;
680 self.blocked = generic_plat_blocked;
682 self.avelocity_z = 0.0000001;
683 if (!InitMovingBrushTrigger())
688 // find pendulum length (same formula as Q3A)
689 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins.z))));
692 // copy initial angle
693 self.cnt = self.angles.z;
695 // wait for targets to spawn
696 controller = spawn();
697 controller.classname = "func_pendulum_controller";
698 controller.owner = self;
699 controller.nextthink = time + 1;
700 controller.think = func_pendulum_controller_think;
701 self.nextthink = self.ltime + 999999999;
702 self.think = SUB_NullThink; // for PushMove
704 //self.effects |= EF_LOWPRECISION;
706 // TODO make a reset function for this one
709 // button and multiple button
712 void() button_return;
716 self.state = STATE_TOP;
717 self.nextthink = self.ltime + self.wait;
718 self.think = button_return;
719 activator = self.enemy;
721 self.frame = 1; // use alternate textures
726 self.state = STATE_BOTTOM;
731 self.state = STATE_DOWN;
732 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
733 self.frame = 0; // use normal textures
735 self.takedamage = DAMAGE_YES; // can be shot again
739 void button_blocked()
741 // do nothing, just don't come all the way back out
747 self.health = self.max_health;
748 self.takedamage = DAMAGE_NO; // will be reset upon return
750 if (self.state == STATE_UP || self.state == STATE_TOP)
753 if (self.noise != "")
754 sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
756 self.state = STATE_UP;
757 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
762 self.health = self.max_health;
763 setorigin(self, self.pos1);
764 self.frame = 0; // use normal textures
765 self.state = STATE_BOTTOM;
767 self.takedamage = DAMAGE_YES; // can be shot again
772 if(self.active != ACTIVE_ACTIVE)
775 self.enemy = activator;
783 if (!other.iscreature)
785 if(other.velocity * self.movedir < 0)
789 self.enemy = other.owner;
793 void button_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
795 if(self.spawnflags & DOOR_NOSPLASH)
796 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
798 self.health = self.health - damage;
799 if (self.health <= 0)
801 self.enemy = damage_attacker;
807 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
808 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.
810 "angle" determines the opening direction
811 "target" all entities with a matching targetname will be used
812 "speed" override the default 40 speed
813 "wait" override the default 1 second wait (-1 = never return)
814 "lip" override the default 4 pixel lip remaining at end of move
815 "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
822 void spawnfunc_func_button()
826 if (!InitMovingBrushTrigger())
828 self.effects |= EF_LOWPRECISION;
830 self.blocked = button_blocked;
831 self.use = button_use;
833 // if (self.health == 0) // all buttons are now shootable
837 self.max_health = self.health;
838 self.event_damage = button_damage;
839 self.takedamage = DAMAGE_YES;
842 self.touch = button_touch;
852 precache_sound(self.noise);
854 self.active = ACTIVE_ACTIVE;
856 self.pos1 = self.origin;
857 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
858 self.flags |= FL_NOTARGET;
864 const float DOOR_START_OPEN = 1;
865 const float DOOR_DONT_LINK = 4;
866 const float DOOR_TOGGLE = 32;
870 Doors are similar to buttons, but can spawn a fat trigger field around them
871 to open without a touch, and they link together to form simultanious
874 Door.owner is the master door. If there is only one door, it points to itself.
875 If multiple doors, all will point to a single one.
877 Door.enemy chains from the master door through all doors linked in the chain.
882 =============================================================================
886 =============================================================================
891 void() door_rotating_go_down;
892 void() door_rotating_go_up;
897 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
898 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
901 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
902 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
904 //Dont chamge direction for dead or dying stuff
905 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
908 if (self.state == STATE_DOWN)
909 if (self.classname == "door")
914 door_rotating_go_up ();
917 if (self.classname == "door")
922 door_rotating_go_down ();
926 //gib dying stuff just to make sure
927 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
928 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
932 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
933 // if a door has a negative wait, it would never come back if blocked,
934 // so let it just squash the object to death real fast
935 /* if (self.wait >= 0)
937 if (self.state == STATE_DOWN)
948 if (self.noise1 != "")
949 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
950 self.state = STATE_TOP;
951 if (self.spawnflags & DOOR_TOGGLE)
952 return; // don't come down automatically
953 if (self.classname == "door")
955 self.think = door_go_down;
958 self.think = door_rotating_go_down;
960 self.nextthink = self.ltime + self.wait;
963 void door_hit_bottom()
965 if (self.noise1 != "")
966 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
967 self.state = STATE_BOTTOM;
972 if (self.noise2 != "")
973 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
976 self.takedamage = DAMAGE_YES;
977 self.health = self.max_health;
980 self.state = STATE_DOWN;
981 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
986 if (self.state == STATE_UP)
987 return; // already going up
989 if (self.state == STATE_TOP)
990 { // reset top wait time
991 self.nextthink = self.ltime + self.wait;
995 if (self.noise2 != "")
996 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
997 self.state = STATE_UP;
998 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
1001 oldmessage = self.message;
1004 self.message = oldmessage;
1010 =============================================================================
1012 ACTIVATION FUNCTIONS
1014 =============================================================================
1017 float door_check_keys(void) {
1018 entity door = self.owner ? self.owner : self;
1024 // this door require a key
1025 // only a player can have a key
1026 if (!IS_PLAYER(other))
1029 if (item_keys_usekey(door, other)) {
1030 // some keys were used
1031 if (other.key_door_messagetime <= time) {
1032 play2(other, "misc/talk.wav");
1033 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
1034 other.key_door_messagetime = time + 2;
1037 // no keys were used
1038 if (other.key_door_messagetime <= time) {
1039 play2(other, "misc/talk.wav");
1040 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
1041 other.key_door_messagetime = time + 2;
1045 if (door.itemkeys) {
1046 // door is now unlocked
1047 play2(other, "misc/talk.wav");
1048 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_UNLOCKED);
1060 if (self.owner != self)
1061 objerror ("door_fire: self.owner != self");
1065 if (self.spawnflags & DOOR_TOGGLE)
1067 if (self.state == STATE_UP || self.state == STATE_TOP)
1072 if (self.classname == "door")
1078 door_rotating_go_down ();
1081 } while ( (self != starte) && (self != world) );
1087 // trigger all paired doors
1091 if (self.classname == "door")
1096 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
1097 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
1099 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
1100 self.pos2 = '0 0 0' - self.pos2;
1102 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
1103 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
1104 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
1106 door_rotating_go_up ();
1110 } while ( (self != starte) && (self != world) );
1119 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
1131 void door_trigger_touch()
1133 if (other.health < 1)
1134 if (!(other.iscreature && other.deadflag == DEAD_NO))
1137 if (time < self.attack_finished_single)
1140 // check if door is locked
1141 if (!door_check_keys())
1144 self.attack_finished_single = time + 1;
1153 void door_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
1156 if(self.spawnflags & DOOR_NOSPLASH)
1157 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
1159 self.health = self.health - damage;
1161 if (self.itemkeys) {
1162 // don't allow opening doors through damage if keys are required
1166 if (self.health <= 0)
1170 self.health = self.max_health;
1171 self.takedamage = DAMAGE_NO; // wil be reset upon return
1187 if (!IS_PLAYER(other))
1189 if (self.owner.attack_finished_single > time)
1192 self.owner.attack_finished_single = time + 2;
1194 if (!(self.owner.dmg) && (self.owner.message != ""))
1196 if (IS_CLIENT(other))
1197 centerprint(other, self.owner.message);
1198 play2(other, "misc/talk.wav");
1203 void door_generic_plat_blocked()
1206 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1207 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1210 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1211 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1213 //Dont chamge direction for dead or dying stuff
1214 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1217 if (self.state == STATE_DOWN)
1218 door_rotating_go_up ();
1220 door_rotating_go_down ();
1223 //gib dying stuff just to make sure
1224 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1225 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1229 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1230 // if a door has a negative wait, it would never come back if blocked,
1231 // so let it just squash the object to death real fast
1232 /* if (self.wait >= 0)
1234 if (self.state == STATE_DOWN)
1235 door_rotating_go_up ();
1237 door_rotating_go_down ();
1243 void door_rotating_hit_top()
1245 if (self.noise1 != "")
1246 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1247 self.state = STATE_TOP;
1248 if (self.spawnflags & DOOR_TOGGLE)
1249 return; // don't come down automatically
1250 self.think = door_rotating_go_down;
1251 self.nextthink = self.ltime + self.wait;
1254 void door_rotating_hit_bottom()
1256 if (self.noise1 != "")
1257 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1258 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1260 self.pos2 = '0 0 0' - self.pos2;
1263 self.state = STATE_BOTTOM;
1266 void door_rotating_go_down()
1268 if (self.noise2 != "")
1269 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1270 if (self.max_health)
1272 self.takedamage = DAMAGE_YES;
1273 self.health = self.max_health;
1276 self.state = STATE_DOWN;
1277 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
1280 void door_rotating_go_up()
1282 if (self.state == STATE_UP)
1283 return; // already going up
1285 if (self.state == STATE_TOP)
1286 { // reset top wait time
1287 self.nextthink = self.ltime + self.wait;
1290 if (self.noise2 != "")
1291 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1292 self.state = STATE_UP;
1293 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
1296 oldmessage = self.message;
1299 self.message = oldmessage;
1306 =============================================================================
1310 =============================================================================
1314 entity spawn_field(vector fmins, vector fmaxs)
1320 trigger.classname = "doortriggerfield";
1321 trigger.movetype = MOVETYPE_NONE;
1322 trigger.solid = SOLID_TRIGGER;
1323 trigger.owner = self;
1324 trigger.touch = door_trigger_touch;
1328 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1333 entity LinkDoors_nextent(entity cur, entity near, entity pass)
1335 while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
1341 float LinkDoors_isconnected(entity e1, entity e2, entity pass)
1344 if (e1.absmin.x > e2.absmax.x + DELTA)
1346 if (e1.absmin.y > e2.absmax.y + DELTA)
1348 if (e1.absmin.z > e2.absmax.z + DELTA)
1350 if (e2.absmin.x > e1.absmax.x + DELTA)
1352 if (e2.absmin.y > e1.absmax.y + DELTA)
1354 if (e2.absmin.z > e1.absmax.z + DELTA)
1369 vector cmins, cmaxs;
1372 return; // already linked by another door
1373 if (self.spawnflags & 4)
1375 self.owner = self.enemy = self;
1383 self.trigger_field = spawn_field(self.absmin, self.absmax);
1385 return; // don't want to link this door
1388 FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
1390 // set owner, and make a loop of the chain
1391 dprint("LinkDoors: linking doors:");
1392 for(t = self; ; t = t.enemy)
1394 dprint(" ", etos(t));
1396 if(t.enemy == world)
1404 // collect health, targetname, message, size
1405 cmins = self.absmin;
1406 cmaxs = self.absmax;
1407 for(t = self; ; t = t.enemy)
1409 if(t.health && !self.health)
1410 self.health = t.health;
1411 if((t.targetname != "") && (self.targetname == ""))
1412 self.targetname = t.targetname;
1413 if((t.message != "") && (self.message == ""))
1414 self.message = t.message;
1415 if (t.absmin.x < cmins.x)
1416 cmins.x = t.absmin.x;
1417 if (t.absmin.y < cmins.y)
1418 cmins.y = t.absmin.y;
1419 if (t.absmin.z < cmins.z)
1420 cmins.z = t.absmin.z;
1421 if (t.absmax.x > cmaxs.x)
1422 cmaxs.x = t.absmax.x;
1423 if (t.absmax.y > cmaxs.y)
1424 cmaxs.y = t.absmax.y;
1425 if (t.absmax.z > cmaxs.z)
1426 cmaxs.z = t.absmax.z;
1431 // distribute health, targetname, message
1432 for(t = self; t; t = t.enemy)
1434 t.health = self.health;
1435 t.targetname = self.targetname;
1436 t.message = self.message;
1441 // shootable, or triggered doors just needed the owner/enemy links,
1442 // they don't spawn a field
1451 self.trigger_field = spawn_field(cmins, cmaxs);
1455 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
1456 if two doors touch, they are assumed to be connected and operate as a unit.
1458 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1460 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).
1462 GOLD_KEY causes the door to open only if the activator holds a gold key.
1464 SILVER_KEY causes the door to open only if the activator holds a silver key.
1466 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1467 "angle" determines the opening direction
1468 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1469 "health" if set, door must be shot open
1470 "speed" movement speed (100 default)
1471 "wait" wait before returning (3 default, -1 = never return)
1472 "lip" lip remaining at end of move (8 default)
1473 "dmg" damage to inflict when blocked (2 default)
1480 FIXME: only one sound set available at the time being
1484 void door_init_startopen()
1486 setorigin (self, self.pos2);
1487 self.pos2 = self.pos1;
1488 self.pos1 = self.origin;
1493 setorigin(self, self.pos1);
1494 self.velocity = '0 0 0';
1495 self.state = STATE_BOTTOM;
1496 self.think = func_null;
1500 // spawnflags require key (for now only func_door)
1501 const float SPAWNFLAGS_GOLD_KEY = 8;
1502 const float SPAWNFLAGS_SILVER_KEY = 16;
1503 void spawnfunc_func_door()
1505 // Quake 1 keys compatibility
1506 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
1507 self.itemkeys |= ITEM_KEY_BIT(0);
1508 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
1509 self.itemkeys |= ITEM_KEY_BIT(1);
1511 //if (!self.deathtype) // map makers can override this
1512 // self.deathtype = " got in the way";
1515 self.max_health = self.health;
1516 if (!InitMovingBrushTrigger())
1518 self.effects |= EF_LOWPRECISION;
1519 self.classname = "door";
1521 self.blocked = door_blocked;
1522 self.use = door_use;
1524 // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
1525 // if(self.spawnflags & 8)
1526 // self.dmg = 10000;
1528 if(self.dmg && (self.message == ""))
1529 self.message = "was squished";
1530 if(self.dmg && (self.message2 == ""))
1531 self.message2 = "was squished by";
1533 if (self.sounds > 0)
1535 precache_sound ("plats/medplat1.wav");
1536 precache_sound ("plats/medplat2.wav");
1537 self.noise2 = "plats/medplat1.wav";
1538 self.noise1 = "plats/medplat2.wav";
1548 self.pos1 = self.origin;
1549 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1551 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1552 // but spawn in the open position
1553 if (self.spawnflags & DOOR_START_OPEN)
1554 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1556 self.state = STATE_BOTTOM;
1560 self.takedamage = DAMAGE_YES;
1561 self.event_damage = door_damage;
1567 self.touch = door_touch;
1569 // LinkDoors can't be done until all of the doors have been spawned, so
1570 // the sizes can be detected properly.
1571 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1573 self.reset = door_reset;
1576 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1577 if two doors touch, they are assumed to be connected and operate as a unit.
1579 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1581 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1582 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1583 must have set trigger_reverse to 1.
1584 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1586 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).
1588 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1589 "angle" determines the destination angle for opening. negative values reverse the direction.
1590 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1591 "health" if set, door must be shot open
1592 "speed" movement speed (100 default)
1593 "wait" wait before returning (3 default, -1 = never return)
1594 "dmg" damage to inflict when blocked (2 default)
1601 FIXME: only one sound set available at the time being
1604 void door_rotating_reset()
1606 self.angles = self.pos1;
1607 self.avelocity = '0 0 0';
1608 self.state = STATE_BOTTOM;
1609 self.think = func_null;
1613 void door_rotating_init_startopen()
1615 self.angles = self.movedir;
1616 self.pos2 = '0 0 0';
1617 self.pos1 = self.movedir;
1621 void spawnfunc_func_door_rotating()
1624 //if (!self.deathtype) // map makers can override this
1625 // self.deathtype = " got in the way";
1627 // I abuse "movedir" for denoting the axis for now
1628 if (self.spawnflags & 64) // X (untested)
1629 self.movedir = '0 0 1';
1630 else if (self.spawnflags & 128) // Y (untested)
1631 self.movedir = '1 0 0';
1633 self.movedir = '0 1 0';
1635 if (self.angles.y ==0) self.angles_y = 90;
1637 self.movedir = self.movedir * self.angles.y;
1638 self.angles = '0 0 0';
1640 self.max_health = self.health;
1641 self.avelocity = self.movedir;
1642 if (!InitMovingBrushTrigger())
1644 self.velocity = '0 0 0';
1645 //self.effects |= EF_LOWPRECISION;
1646 self.classname = "door_rotating";
1648 self.blocked = door_blocked;
1649 self.use = door_use;
1651 if(self.spawnflags & 8)
1654 if(self.dmg && (self.message == ""))
1655 self.message = "was squished";
1656 if(self.dmg && (self.message2 == ""))
1657 self.message2 = "was squished by";
1659 if (self.sounds > 0)
1661 precache_sound ("plats/medplat1.wav");
1662 precache_sound ("plats/medplat2.wav");
1663 self.noise2 = "plats/medplat1.wav";
1664 self.noise1 = "plats/medplat2.wav";
1671 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1673 self.pos1 = '0 0 0';
1674 self.pos2 = self.movedir;
1676 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1677 // but spawn in the open position
1678 if (self.spawnflags & DOOR_START_OPEN)
1679 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1681 self.state = STATE_BOTTOM;
1685 self.takedamage = DAMAGE_YES;
1686 self.event_damage = door_damage;
1692 self.touch = door_touch;
1694 // LinkDoors can't be done until all of the doors have been spawned, so
1695 // the sizes can be detected properly.
1696 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1698 self.reset = door_rotating_reset;
1702 =============================================================================
1706 =============================================================================
1709 void() fd_secret_move1;
1710 void() fd_secret_move2;
1711 void() fd_secret_move3;
1712 void() fd_secret_move4;
1713 void() fd_secret_move5;
1714 void() fd_secret_move6;
1715 void() fd_secret_done;
1717 const float SECRET_OPEN_ONCE = 1; // stays open
1718 const float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1719 const float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1720 const float SECRET_NO_SHOOT = 8; // only opened by trigger
1721 const float SECRET_YES_SHOOT = 16; // shootable even if targeted
1723 void fd_secret_use()
1726 string message_save;
1728 self.health = 10000;
1729 self.bot_attack = true;
1731 // exit if still moving around...
1732 if (self.origin != self.oldorigin)
1735 message_save = self.message;
1736 self.message = ""; // no more message
1737 SUB_UseTargets(); // fire all targets / killtargets
1738 self.message = message_save;
1740 self.velocity = '0 0 0';
1742 // Make a sound, wait a little...
1744 if (self.noise1 != "")
1745 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1746 self.nextthink = self.ltime + 0.1;
1748 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1749 makevectors(self.mangle);
1753 if (self.spawnflags & SECRET_1ST_DOWN)
1754 self.t_width = fabs(v_up * self.size);
1756 self.t_width = fabs(v_right * self.size);
1760 self.t_length = fabs(v_forward * self.size);
1762 if (self.spawnflags & SECRET_1ST_DOWN)
1763 self.dest1 = self.origin - v_up * self.t_width;
1765 self.dest1 = self.origin + v_right * (self.t_width * temp);
1767 self.dest2 = self.dest1 + v_forward * self.t_length;
1768 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
1769 if (self.noise2 != "")
1770 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1773 void fd_secret_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
1778 // Wait after first movement...
1779 void fd_secret_move1()
1781 self.nextthink = self.ltime + 1.0;
1782 self.think = fd_secret_move2;
1783 if (self.noise3 != "")
1784 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1787 // Start moving sideways w/sound...
1788 void fd_secret_move2()
1790 if (self.noise2 != "")
1791 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1792 SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
1795 // Wait here until time to go back...
1796 void fd_secret_move3()
1798 if (self.noise3 != "")
1799 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1800 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1802 self.nextthink = self.ltime + self.wait;
1803 self.think = fd_secret_move4;
1808 void fd_secret_move4()
1810 if (self.noise2 != "")
1811 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1812 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
1816 void fd_secret_move5()
1818 self.nextthink = self.ltime + 1.0;
1819 self.think = fd_secret_move6;
1820 if (self.noise3 != "")
1821 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1824 void fd_secret_move6()
1826 if (self.noise2 != "")
1827 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1828 SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
1831 void fd_secret_done()
1833 if (self.spawnflags&SECRET_YES_SHOOT)
1835 self.health = 10000;
1836 self.takedamage = DAMAGE_YES;
1837 //self.th_pain = fd_secret_use;
1839 if (self.noise3 != "")
1840 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1843 void secret_blocked()
1845 if (time < self.attack_finished_single)
1847 self.attack_finished_single = time + 0.5;
1848 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1860 if (!other.iscreature)
1862 if (self.attack_finished_single > time)
1865 self.attack_finished_single = time + 2;
1869 if (IS_CLIENT(other))
1870 centerprint(other, self.message);
1871 play2(other, "misc/talk.wav");
1877 if (self.spawnflags&SECRET_YES_SHOOT)
1879 self.health = 10000;
1880 self.takedamage = DAMAGE_YES;
1882 setorigin(self, self.oldorigin);
1883 self.think = func_null;
1887 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1888 Basic secret door. Slides back, then to the side. Angle determines direction.
1889 wait = # of seconds before coming back
1890 1st_left = 1st move is left of arrow
1891 1st_down = 1st move is down from arrow
1892 always_shoot = even if targeted, keep shootable
1893 t_width = override WIDTH to move back (or height if going down)
1894 t_length = override LENGTH to move sideways
1895 "dmg" damage to inflict when blocked (2 default)
1897 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1904 void spawnfunc_func_door_secret()
1906 /*if (!self.deathtype) // map makers can override this
1907 self.deathtype = " got in the way";*/
1913 self.mangle = self.angles;
1914 self.angles = '0 0 0';
1915 self.classname = "door";
1916 if (!InitMovingBrushTrigger())
1918 self.effects |= EF_LOWPRECISION;
1920 self.touch = secret_touch;
1921 self.blocked = secret_blocked;
1923 self.use = fd_secret_use;
1928 self.spawnflags |= SECRET_YES_SHOOT;
1930 if(self.spawnflags&SECRET_YES_SHOOT)
1932 self.health = 10000;
1933 self.takedamage = DAMAGE_YES;
1934 self.event_damage = fd_secret_damage;
1936 self.oldorigin = self.origin;
1938 self.wait = 5; // 5 seconds before closing
1940 self.reset = secret_reset;
1944 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1945 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1946 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
1947 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1948 height: amplitude modifier (default 32)
1949 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1950 noise: path/name of looping .wav file to play.
1951 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1955 void func_fourier_controller_think()
1960 self.nextthink = time + 0.1;
1961 if(self.owner.active != ACTIVE_ACTIVE)
1963 self.owner.velocity = '0 0 0';
1968 n = floor((tokenize_console(self.owner.netname)) / 5);
1969 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1971 v = self.owner.destvec;
1973 for(i = 0; i < n; ++i)
1975 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1976 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;
1979 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1980 // * 10 so it will arrive in 0.1 sec
1981 self.owner.velocity = (v - self.owner.origin) * 10;
1984 void spawnfunc_func_fourier()
1987 if (self.noise != "")
1989 precache_sound(self.noise);
1990 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
1997 self.destvec = self.origin;
1998 self.cnt = 360 / self.speed;
2000 self.blocked = generic_plat_blocked;
2001 if(self.dmg && (self.message == ""))
2002 self.message = " was squished";
2003 if(self.dmg && (self.message2 == ""))
2004 self.message2 = "was squished by";
2005 if(self.dmg && (!self.dmgtime))
2006 self.dmgtime = 0.25;
2007 self.dmgtime2 = time;
2009 if(self.netname == "")
2010 self.netname = "1 0 0 0 1";
2012 if (!InitMovingBrushTrigger())
2015 self.active = ACTIVE_ACTIVE;
2017 // wait for targets to spawn
2018 controller = spawn();
2019 controller.classname = "func_fourier_controller";
2020 controller.owner = self;
2021 controller.nextthink = time + 1;
2022 controller.think = func_fourier_controller_think;
2023 self.nextthink = self.ltime + 999999999;
2024 self.think = SUB_NullThink; // for PushMove
2026 // Savage: Reduce bandwith, critical on e.g. nexdm02
2027 self.effects |= EF_LOWPRECISION;
2029 // TODO make a reset function for this one
2032 // reusing some fields havocbots declared
2033 .entity wp00, wp01, wp02, wp03;
2035 .float targetfactor, target2factor, target3factor, target4factor;
2036 .vector targetnormal, target2normal, target3normal, target4normal;
2038 vector func_vectormamamam_origin(entity o, float t)
2050 p = e.origin + t * e.velocity;
2052 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
2054 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
2060 p = e.origin + t * e.velocity;
2062 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
2064 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
2070 p = e.origin + t * e.velocity;
2072 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
2074 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
2080 p = e.origin + t * e.velocity;
2082 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
2084 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
2090 void func_vectormamamam_controller_think()
2092 self.nextthink = time + 0.1;
2094 if(self.owner.active != ACTIVE_ACTIVE)
2096 self.owner.velocity = '0 0 0';
2100 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
2101 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
2104 void func_vectormamamam_findtarget()
2106 if(self.target != "")
2107 self.wp00 = find(world, targetname, self.target);
2109 if(self.target2 != "")
2110 self.wp01 = find(world, targetname, self.target2);
2112 if(self.target3 != "")
2113 self.wp02 = find(world, targetname, self.target3);
2115 if(self.target4 != "")
2116 self.wp03 = find(world, targetname, self.target4);
2118 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
2119 objerror("No reference entity found, so there is nothing to move. Aborting.");
2121 self.destvec = self.origin - func_vectormamamam_origin(self, 0);
2124 controller = spawn();
2125 controller.classname = "func_vectormamamam_controller";
2126 controller.owner = self;
2127 controller.nextthink = time + 1;
2128 controller.think = func_vectormamamam_controller_think;
2131 void spawnfunc_func_vectormamamam()
2133 if (self.noise != "")
2135 precache_sound(self.noise);
2136 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
2139 if(!self.targetfactor)
2140 self.targetfactor = 1;
2142 if(!self.target2factor)
2143 self.target2factor = 1;
2145 if(!self.target3factor)
2146 self.target3factor = 1;
2148 if(!self.target4factor)
2149 self.target4factor = 1;
2151 if(vlen(self.targetnormal))
2152 self.targetnormal = normalize(self.targetnormal);
2154 if(vlen(self.target2normal))
2155 self.target2normal = normalize(self.target2normal);
2157 if(vlen(self.target3normal))
2158 self.target3normal = normalize(self.target3normal);
2160 if(vlen(self.target4normal))
2161 self.target4normal = normalize(self.target4normal);
2163 self.blocked = generic_plat_blocked;
2164 if(self.dmg && (self.message == ""))
2165 self.message = " was squished";
2166 if(self.dmg && (self.message == ""))
2167 self.message2 = "was squished by";
2168 if(self.dmg && (!self.dmgtime))
2169 self.dmgtime = 0.25;
2170 self.dmgtime2 = time;
2172 if(self.netname == "")
2173 self.netname = "1 0 0 0 1";
2175 if (!InitMovingBrushTrigger())
2178 // wait for targets to spawn
2179 self.nextthink = self.ltime + 999999999;
2180 self.think = SUB_NullThink; // for PushMove
2182 // Savage: Reduce bandwith, critical on e.g. nexdm02
2183 self.effects |= EF_LOWPRECISION;
2185 self.active = ACTIVE_ACTIVE;
2187 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
2190 void conveyor_think()
2194 // set myself as current conveyor where possible
2195 for(e = world; (e = findentity(e, conveyor, self)); )
2200 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2201 if(!e.conveyor.state)
2204 vector emin = e.absmin;
2205 vector emax = e.absmax;
2206 if(self.solid == SOLID_BSP)
2211 if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2212 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2216 for(e = world; (e = findentity(e, conveyor, self)); )
2218 if(IS_CLIENT(e)) // doing it via velocity has quite some advantages
2219 continue; // done in SV_PlayerPhysics
2221 setorigin(e, e.origin + self.movedir * sys_frametime);
2222 move_out_of_solid(e);
2223 UpdateCSQCProjectile(e);
2225 // stupid conveyor code
2226 tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2227 if(trace_fraction > 0)
2228 setorigin(e, trace_endpos);
2233 self.nextthink = time;
2238 self.state = !self.state;
2241 void conveyor_reset()
2243 self.state = (self.spawnflags & 1);
2246 void conveyor_init()
2250 self.movedir = self.movedir * self.speed;
2251 self.think = conveyor_think;
2252 self.nextthink = time;
2255 self.use = conveyor_use;
2256 self.reset = conveyor_reset;
2263 void spawnfunc_trigger_conveyor()
2270 void spawnfunc_func_conveyor()
2273 InitMovingBrushTrigger();
2274 self.movetype = MOVETYPE_NONE;