4 #include "../dpdefs/progsdefs.qh"
5 #include "../dpdefs/dpextensions.qh"
6 #include "../warpzonelib/mathlib.qh"
7 #include "../warpzonelib/common.qh"
8 #include "../warpzonelib/util_server.qh"
9 #include "../common/constants.qh"
10 #include "../common/util.qh"
11 #include "../common/weapons/weapons.qh"
12 #include "constants.qh"
14 #include "../common/notifications.qh"
15 #include "../common/deathtypes.qh"
16 #include "command/common.qh"
17 #include "../csqcmodellib/sv_model.qh"
23 void generic_plat_blocked()
25 if(self.dmg && other.takedamage != DAMAGE_NO) {
26 if(self.dmgtime2 < time) {
27 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
28 self.dmgtime2 = time + self.dmgtime;
31 // Gib dead/dying stuff
32 if(other.deadflag != DEAD_NO)
33 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
38 .entity trigger_field;
40 void() plat_center_touch;
41 void() plat_outside_touch;
42 void() plat_trigger_use;
46 const float PLAT_LOW_TRIGGER = 1;
48 void plat_spawn_inside_trigger()
54 trigger.touch = plat_center_touch;
55 trigger.movetype = MOVETYPE_NONE;
56 trigger.solid = SOLID_TRIGGER;
59 tmin = self.absmin + '25 25 0';
60 tmax = self.absmax - '25 25 -8';
61 tmin.z = tmax.z - (self.pos1_z - self.pos2_z + 8);
62 if (self.spawnflags & PLAT_LOW_TRIGGER)
65 if (self.size.x <= 50)
67 tmin.x = (self.mins.x + self.maxs.x) / 2;
70 if (self.size.y <= 50)
72 tmin.y = (self.mins.y + self.maxs.y) / 2;
80 setsize (trigger, tmin, tmax);
84 // otherwise, something is fishy...
86 objerror("plat_spawn_inside_trigger: platform has odd size or lip, can't spawn");
91 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
93 self.think = plat_go_down;
94 self.nextthink = self.ltime + 3;
97 void plat_hit_bottom()
99 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
105 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
107 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, plat_hit_bottom);
112 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
114 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, plat_hit_top);
117 void plat_center_touch()
119 if (!other.iscreature)
122 if (other.health <= 0)
128 else if (self.state == 1)
129 self.nextthink = self.ltime + 1; // delay going down
132 void plat_outside_touch()
134 if (!other.iscreature)
137 if (other.health <= 0)
145 void plat_trigger_use()
148 return; // already activated
155 if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
156 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
158 if((self.dmg) && (other.takedamage != DAMAGE_NO)) { // Shall we bite?
159 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
160 // Gib dead/dying stuff
161 if(other.deadflag != DEAD_NO)
162 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
167 else if (self.state == 3)
169 // when in other states, then the plat_crush event came delayed after
170 // plat state already had changed
171 // this isn't a bug per se!
177 self.use = func_null;
179 objerror ("plat_use: not in up state");
183 .string sound1, sound2;
189 setorigin (self, self.pos1);
195 setorigin (self, self.pos2);
197 self.use = plat_trigger_use;
201 .float platmovetype_start_default, platmovetype_end_default;
202 float set_platmovetype(entity e, string s)
204 // sets platmovetype_start and platmovetype_end based on a string consisting of two values
207 n = tokenize_console(s);
209 e.platmovetype_start = stof(argv(0));
211 e.platmovetype_start = 0;
214 e.platmovetype_end = stof(argv(1));
216 e.platmovetype_end = e.platmovetype_start;
219 if(argv(2) == "force")
220 return true; // no checking, return immediately
222 if(!cubic_speedfunc_is_sane(e.platmovetype_start, e.platmovetype_end))
224 objerror("Invalid platform move type; platform would go in reverse, which is not allowed.");
231 void spawnfunc_path_corner()
233 // setup values for overriding train movement
234 // if a second value does not exist, both start and end speeds are the single value specified
235 if(!set_platmovetype(self, self.platmovetype))
238 void spawnfunc_func_plat()
240 if (self.sounds == 0)
243 if(self.spawnflags & 4)
246 if(self.dmg && (self.message == ""))
247 self.message = "was squished";
248 if(self.dmg && (self.message2 == ""))
249 self.message2 = "was squished by";
251 if (self.sounds == 1)
253 precache_sound ("plats/plat1.wav");
254 precache_sound ("plats/plat2.wav");
255 self.noise = "plats/plat1.wav";
256 self.noise1 = "plats/plat2.wav";
259 if (self.sounds == 2)
261 precache_sound ("plats/medplat1.wav");
262 precache_sound ("plats/medplat2.wav");
263 self.noise = "plats/medplat1.wav";
264 self.noise1 = "plats/medplat2.wav";
269 precache_sound (self.sound1);
270 self.noise = self.sound1;
274 precache_sound (self.sound2);
275 self.noise1 = self.sound2;
278 self.mangle = self.angles;
279 self.angles = '0 0 0';
281 self.classname = "plat";
282 if (!InitMovingBrushTrigger())
284 self.effects |= EF_LOWPRECISION;
285 setsize (self, self.mins , self.maxs);
287 self.blocked = plat_crush;
294 self.height = self.size.z - self.lip;
296 self.pos1 = self.origin;
297 self.pos2 = self.origin;
298 self.pos2_z = self.origin.z - self.height;
300 self.reset = plat_reset;
303 plat_spawn_inside_trigger (); // the "start moving" trigger
306 .float train_wait_turning;
317 // if turning is enabled, the train will turn toward the next point while waiting
318 if(self.platmovetype_turn && !self.train_wait_turning)
322 targ = find(world, targetname, self.target);
323 if((self.spawnflags & 1) && targ.curvetarget)
324 cp = find(world, targetname, targ.curvetarget);
328 if(cp) // bezier curves movement
329 ang = cp.origin - (self.origin - self.view_ofs); // use the origin of the control point of the next path_corner
330 else // linear movement
331 ang = targ.origin - (self.origin - self.view_ofs); // use the origin of the next path_corner
332 ang = vectoangles(ang);
333 ang.x = -ang.x; // flip up / down orientation
335 if(self.wait > 0) // slow turning
336 SUB_CalcAngleMove(ang, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
337 else // instant turning
338 SUB_CalcAngleMove(ang, TSPEED_TIME, 0.0000001, train_wait);
339 self.train_wait_turning = true;
344 stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
346 if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
348 self.train_wait_turning = false;
353 self.think = train_next;
354 self.nextthink = self.ltime + self.wait;
360 entity targ, cp = world;
361 vector cp_org = '0 0 0';
363 targ = find(world, targetname, self.target);
364 self.target = targ.target;
365 if (self.spawnflags & 1)
369 cp = find(world, targetname, targ.curvetarget); // get its second target (the control point)
370 cp_org = cp.origin - self.view_ofs; // no control point found, assume a straight line to the destination
373 if (self.target == "")
374 objerror("train_next: no next target");
375 self.wait = targ.wait;
379 if(targ.platmovetype)
381 // this path_corner contains a movetype overrider, apply it
382 self.platmovetype_start = targ.platmovetype_start;
383 self.platmovetype_end = targ.platmovetype_end;
387 // this path_corner doesn't contain a movetype overrider, use the train's defaults
388 self.platmovetype_start = self.platmovetype_start_default;
389 self.platmovetype_end = self.platmovetype_end_default;
395 SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
397 SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
402 SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
404 SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
408 sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
411 void func_train_find()
414 targ = find(world, targetname, self.target);
415 self.target = targ.target;
416 if (self.target == "")
417 objerror("func_train_find: no next target");
418 setorigin(self, targ.origin - self.view_ofs);
419 self.nextthink = self.ltime + 1;
420 self.think = train_next;
423 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
424 Ridable platform, targets spawnfunc_path_corner path to follow.
425 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
426 target : targetname of first spawnfunc_path_corner (starts here)
428 void spawnfunc_func_train()
430 if (self.noise != "")
431 precache_sound(self.noise);
433 if (self.target == "")
434 objerror("func_train without a target");
438 if (!InitMovingBrushTrigger())
440 self.effects |= EF_LOWPRECISION;
442 if (self.spawnflags & 2)
444 self.platmovetype_turn = true;
445 self.view_ofs = '0 0 0'; // don't offset a rotating train, origin works differently now
448 self.view_ofs = self.mins;
450 // wait for targets to spawn
451 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
453 self.blocked = generic_plat_blocked;
454 if(self.dmg && (self.message == ""))
455 self.message = " was squished";
456 if(self.dmg && (self.message2 == ""))
457 self.message2 = "was squished by";
458 if(self.dmg && (!self.dmgtime))
460 self.dmgtime2 = time;
462 if(!set_platmovetype(self, self.platmovetype))
464 self.platmovetype_start_default = self.platmovetype_start;
465 self.platmovetype_end_default = self.platmovetype_end;
467 // TODO make a reset function for this one
470 void func_rotating_setactive(float astate)
473 if (astate == ACTIVE_TOGGLE)
475 if(self.active == ACTIVE_ACTIVE)
476 self.active = ACTIVE_NOT;
478 self.active = ACTIVE_ACTIVE;
481 self.active = astate;
483 if(self.active == ACTIVE_NOT)
484 self.avelocity = '0 0 0';
486 self.avelocity = self.pos1;
489 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
490 Brush model that spins in place on one axis (default Z).
491 speed : speed to rotate (in degrees per second)
492 noise : path/name of looping .wav file to play.
493 dmg : Do this mutch dmg every .dmgtime intervall when blocked
497 void spawnfunc_func_rotating()
499 if (self.noise != "")
501 precache_sound(self.noise);
502 ambientsound(self.origin, self.noise, VOL_BASE, ATTEN_IDLE);
505 self.active = ACTIVE_ACTIVE;
506 self.setactive = func_rotating_setactive;
510 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
511 if (self.spawnflags & 4) // X (untested)
512 self.avelocity = '0 0 1' * self.speed;
513 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
514 else if (self.spawnflags & 8) // Y (untested)
515 self.avelocity = '1 0 0' * self.speed;
516 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
518 self.avelocity = '0 1 0' * self.speed;
520 self.pos1 = self.avelocity;
522 if(self.dmg && (self.message == ""))
523 self.message = " was squished";
524 if(self.dmg && (self.message2 == ""))
525 self.message2 = "was squished by";
528 if(self.dmg && (!self.dmgtime))
531 self.dmgtime2 = time;
533 if (!InitMovingBrushTrigger())
535 // no EF_LOWPRECISION here, as rounding angles is bad
537 self.blocked = generic_plat_blocked;
539 // wait for targets to spawn
540 self.nextthink = self.ltime + 999999999;
541 self.think = SUB_NullThink; // for PushMove
543 // TODO make a reset function for this one
547 void func_bobbing_controller_think()
550 self.nextthink = time + 0.1;
552 if(self.owner.active != ACTIVE_ACTIVE)
554 self.owner.velocity = '0 0 0';
558 // calculate sinewave using makevectors
559 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
560 v = self.owner.destvec + self.owner.movedir * v_forward.y;
561 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
562 // * 10 so it will arrive in 0.1 sec
563 self.owner.velocity = (v - self.owner.origin) * 10;
566 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
567 Brush model that moves back and forth on one axis (default Z).
568 speed : how long one cycle takes in seconds (default 4)
569 height : how far the cycle moves (default 32)
570 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
571 noise : path/name of looping .wav file to play.
572 dmg : Do this mutch dmg every .dmgtime intervall when blocked
575 void spawnfunc_func_bobbing()
578 if (self.noise != "")
580 precache_sound(self.noise);
581 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
587 // center of bobbing motion
588 self.destvec = self.origin;
589 // time scale to get degrees
590 self.cnt = 360 / self.speed;
592 self.active = ACTIVE_ACTIVE;
594 // damage when blocked
595 self.blocked = generic_plat_blocked;
596 if(self.dmg && (self.message == ""))
597 self.message = " was squished";
598 if(self.dmg && (self.message2 == ""))
599 self.message2 = "was squished by";
600 if(self.dmg && (!self.dmgtime))
602 self.dmgtime2 = time;
605 if (self.spawnflags & 1) // X
606 self.movedir = '1 0 0' * self.height;
607 else if (self.spawnflags & 2) // Y
608 self.movedir = '0 1 0' * self.height;
610 self.movedir = '0 0 1' * self.height;
612 if (!InitMovingBrushTrigger())
615 // wait for targets to spawn
616 controller = spawn();
617 controller.classname = "func_bobbing_controller";
618 controller.owner = self;
619 controller.nextthink = time + 1;
620 controller.think = func_bobbing_controller_think;
621 self.nextthink = self.ltime + 999999999;
622 self.think = SUB_NullThink; // for PushMove
624 // Savage: Reduce bandwith, critical on e.g. nexdm02
625 self.effects |= EF_LOWPRECISION;
627 // TODO make a reset function for this one
631 void func_pendulum_controller_think()
634 self.nextthink = time + 0.1;
636 if (!(self.owner.active == ACTIVE_ACTIVE))
638 self.owner.avelocity_x = 0;
642 // calculate sinewave using makevectors
643 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
644 v = self.owner.speed * v_forward.y + self.cnt;
645 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
647 // * 10 so it will arrive in 0.1 sec
648 self.owner.avelocity_z = (remainder(v - self.owner.angles.z, 360)) * 10;
652 void spawnfunc_func_pendulum()
655 if (self.noise != "")
657 precache_sound(self.noise);
658 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
661 self.active = ACTIVE_ACTIVE;
663 // keys: angle, speed, phase, noise, freq
667 // not initializing self.dmg to 2, to allow damageless pendulum
669 if(self.dmg && (self.message == ""))
670 self.message = " was squished";
671 if(self.dmg && (self.message2 == ""))
672 self.message2 = "was squished by";
673 if(self.dmg && (!self.dmgtime))
675 self.dmgtime2 = time;
677 self.blocked = generic_plat_blocked;
679 self.avelocity_z = 0.0000001;
680 if (!InitMovingBrushTrigger())
685 // find pendulum length (same formula as Q3A)
686 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins.z))));
689 // copy initial angle
690 self.cnt = self.angles.z;
692 // wait for targets to spawn
693 controller = spawn();
694 controller.classname = "func_pendulum_controller";
695 controller.owner = self;
696 controller.nextthink = time + 1;
697 controller.think = func_pendulum_controller_think;
698 self.nextthink = self.ltime + 999999999;
699 self.think = SUB_NullThink; // for PushMove
701 //self.effects |= EF_LOWPRECISION;
703 // TODO make a reset function for this one
706 // button and multiple button
709 void() button_return;
713 self.state = STATE_TOP;
714 self.nextthink = self.ltime + self.wait;
715 self.think = button_return;
716 activator = self.enemy;
718 self.frame = 1; // use alternate textures
723 self.state = STATE_BOTTOM;
728 self.state = STATE_DOWN;
729 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
730 self.frame = 0; // use normal textures
732 self.takedamage = DAMAGE_YES; // can be shot again
736 void button_blocked()
738 // do nothing, just don't come all the way back out
744 self.health = self.max_health;
745 self.takedamage = DAMAGE_NO; // will be reset upon return
747 if (self.state == STATE_UP || self.state == STATE_TOP)
750 if (self.noise != "")
751 sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
753 self.state = STATE_UP;
754 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
759 self.health = self.max_health;
760 setorigin(self, self.pos1);
761 self.frame = 0; // use normal textures
762 self.state = STATE_BOTTOM;
764 self.takedamage = DAMAGE_YES; // can be shot again
769 if(self.active != ACTIVE_ACTIVE)
772 self.enemy = activator;
780 if (!other.iscreature)
782 if(other.velocity * self.movedir < 0)
786 self.enemy = other.owner;
790 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
792 if(self.spawnflags & DOOR_NOSPLASH)
793 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
795 self.health = self.health - damage;
796 if (self.health <= 0)
798 self.enemy = damage_attacker;
804 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
805 When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again.
807 "angle" determines the opening direction
808 "target" all entities with a matching targetname will be used
809 "speed" override the default 40 speed
810 "wait" override the default 1 second wait (-1 = never return)
811 "lip" override the default 4 pixel lip remaining at end of move
812 "health" if set, the button must be killed instead of touched. If set to -1, the button will fire on ANY attack, even damageless ones like the InstaGib laser
819 void spawnfunc_func_button()
823 if (!InitMovingBrushTrigger())
825 self.effects |= EF_LOWPRECISION;
827 self.blocked = button_blocked;
828 self.use = button_use;
830 // if (self.health == 0) // all buttons are now shootable
834 self.max_health = self.health;
835 self.event_damage = button_damage;
836 self.takedamage = DAMAGE_YES;
839 self.touch = button_touch;
849 precache_sound(self.noise);
851 self.active = ACTIVE_ACTIVE;
853 self.pos1 = self.origin;
854 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
855 self.flags |= FL_NOTARGET;
861 const float DOOR_START_OPEN = 1;
862 const float DOOR_DONT_LINK = 4;
863 const float DOOR_TOGGLE = 32;
867 Doors are similar to buttons, but can spawn a fat trigger field around them
868 to open without a touch, and they link together to form simultanious
871 Door.owner is the master door. If there is only one door, it points to itself.
872 If multiple doors, all will point to a single one.
874 Door.enemy chains from the master door through all doors linked in the chain.
879 =============================================================================
883 =============================================================================
888 void() door_rotating_go_down;
889 void() door_rotating_go_up;
894 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
895 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
898 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
899 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
901 //Dont chamge direction for dead or dying stuff
902 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
905 if (self.state == STATE_DOWN)
906 if (self.classname == "door")
911 door_rotating_go_up ();
914 if (self.classname == "door")
919 door_rotating_go_down ();
923 //gib dying stuff just to make sure
924 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
925 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
929 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
930 // if a door has a negative wait, it would never come back if blocked,
931 // so let it just squash the object to death real fast
932 /* if (self.wait >= 0)
934 if (self.state == STATE_DOWN)
945 if (self.noise1 != "")
946 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
947 self.state = STATE_TOP;
948 if (self.spawnflags & DOOR_TOGGLE)
949 return; // don't come down automatically
950 if (self.classname == "door")
952 self.think = door_go_down;
955 self.think = door_rotating_go_down;
957 self.nextthink = self.ltime + self.wait;
960 void door_hit_bottom()
962 if (self.noise1 != "")
963 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
964 self.state = STATE_BOTTOM;
969 if (self.noise2 != "")
970 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
973 self.takedamage = DAMAGE_YES;
974 self.health = self.max_health;
977 self.state = STATE_DOWN;
978 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
983 if (self.state == STATE_UP)
984 return; // already going up
986 if (self.state == STATE_TOP)
987 { // reset top wait time
988 self.nextthink = self.ltime + self.wait;
992 if (self.noise2 != "")
993 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
994 self.state = STATE_UP;
995 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
998 oldmessage = self.message;
1001 self.message = oldmessage;
1007 =============================================================================
1009 ACTIVATION FUNCTIONS
1011 =============================================================================
1014 float door_check_keys(void) {
1015 entity door = self.owner ? self.owner : self;
1021 // this door require a key
1022 // only a player can have a key
1023 if (!IS_PLAYER(other))
1026 if (item_keys_usekey(door, other)) {
1027 // some keys were used
1028 if (other.key_door_messagetime <= time) {
1029 play2(other, "misc/talk.wav");
1030 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
1031 other.key_door_messagetime = time + 2;
1034 // no keys were used
1035 if (other.key_door_messagetime <= time) {
1036 play2(other, "misc/talk.wav");
1037 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
1038 other.key_door_messagetime = time + 2;
1042 if (door.itemkeys) {
1043 // door is now unlocked
1044 play2(other, "misc/talk.wav");
1045 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_UNLOCKED);
1057 if (self.owner != self)
1058 objerror ("door_fire: self.owner != self");
1062 if (self.spawnflags & DOOR_TOGGLE)
1064 if (self.state == STATE_UP || self.state == STATE_TOP)
1069 if (self.classname == "door")
1075 door_rotating_go_down ();
1078 } while ( (self != starte) && (self != world) );
1084 // trigger all paired doors
1088 if (self.classname == "door")
1093 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
1094 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
1096 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
1097 self.pos2 = '0 0 0' - self.pos2;
1099 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
1100 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
1101 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
1103 door_rotating_go_up ();
1107 } while ( (self != starte) && (self != world) );
1116 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
1128 void door_trigger_touch()
1130 if (other.health < 1)
1131 if (!(other.iscreature && other.deadflag == DEAD_NO))
1134 if (time < self.attack_finished_single)
1137 // check if door is locked
1138 if (!door_check_keys())
1141 self.attack_finished_single = time + 1;
1150 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1153 if(self.spawnflags & DOOR_NOSPLASH)
1154 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
1156 self.health = self.health - damage;
1158 if (self.itemkeys) {
1159 // don't allow opening doors through damage if keys are required
1163 if (self.health <= 0)
1167 self.health = self.max_health;
1168 self.takedamage = DAMAGE_NO; // wil be reset upon return
1184 if (!IS_PLAYER(other))
1186 if (self.owner.attack_finished_single > time)
1189 self.owner.attack_finished_single = time + 2;
1191 if (!(self.owner.dmg) && (self.owner.message != ""))
1193 if (IS_CLIENT(other))
1194 centerprint(other, self.owner.message);
1195 play2(other, "misc/talk.wav");
1200 void door_generic_plat_blocked()
1203 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1204 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1207 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1208 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1210 //Dont chamge direction for dead or dying stuff
1211 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1214 if (self.state == STATE_DOWN)
1215 door_rotating_go_up ();
1217 door_rotating_go_down ();
1220 //gib dying stuff just to make sure
1221 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1222 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1226 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1227 // if a door has a negative wait, it would never come back if blocked,
1228 // so let it just squash the object to death real fast
1229 /* if (self.wait >= 0)
1231 if (self.state == STATE_DOWN)
1232 door_rotating_go_up ();
1234 door_rotating_go_down ();
1240 void door_rotating_hit_top()
1242 if (self.noise1 != "")
1243 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1244 self.state = STATE_TOP;
1245 if (self.spawnflags & DOOR_TOGGLE)
1246 return; // don't come down automatically
1247 self.think = door_rotating_go_down;
1248 self.nextthink = self.ltime + self.wait;
1251 void door_rotating_hit_bottom()
1253 if (self.noise1 != "")
1254 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1255 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1257 self.pos2 = '0 0 0' - self.pos2;
1260 self.state = STATE_BOTTOM;
1263 void door_rotating_go_down()
1265 if (self.noise2 != "")
1266 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1267 if (self.max_health)
1269 self.takedamage = DAMAGE_YES;
1270 self.health = self.max_health;
1273 self.state = STATE_DOWN;
1274 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
1277 void door_rotating_go_up()
1279 if (self.state == STATE_UP)
1280 return; // already going up
1282 if (self.state == STATE_TOP)
1283 { // reset top wait time
1284 self.nextthink = self.ltime + self.wait;
1287 if (self.noise2 != "")
1288 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1289 self.state = STATE_UP;
1290 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
1293 oldmessage = self.message;
1296 self.message = oldmessage;
1303 =============================================================================
1307 =============================================================================
1311 entity spawn_field(vector fmins, vector fmaxs)
1317 trigger.classname = "doortriggerfield";
1318 trigger.movetype = MOVETYPE_NONE;
1319 trigger.solid = SOLID_TRIGGER;
1320 trigger.owner = self;
1321 trigger.touch = door_trigger_touch;
1325 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1330 entity LinkDoors_nextent(entity cur, entity near, entity pass)
1332 while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
1338 float LinkDoors_isconnected(entity e1, entity e2, entity pass)
1341 if (e1.absmin.x > e2.absmax.x + DELTA)
1343 if (e1.absmin.y > e2.absmax.y + DELTA)
1345 if (e1.absmin.z > e2.absmax.z + DELTA)
1347 if (e2.absmin.x > e1.absmax.x + DELTA)
1349 if (e2.absmin.y > e1.absmax.y + DELTA)
1351 if (e2.absmin.z > e1.absmax.z + DELTA)
1366 vector cmins, cmaxs;
1369 return; // already linked by another door
1370 if (self.spawnflags & 4)
1372 self.owner = self.enemy = self;
1380 self.trigger_field = spawn_field(self.absmin, self.absmax);
1382 return; // don't want to link this door
1385 FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
1387 // set owner, and make a loop of the chain
1388 dprint("LinkDoors: linking doors:");
1389 for(t = self; ; t = t.enemy)
1391 dprint(" ", etos(t));
1393 if(t.enemy == world)
1401 // collect health, targetname, message, size
1402 cmins = self.absmin;
1403 cmaxs = self.absmax;
1404 for(t = self; ; t = t.enemy)
1406 if(t.health && !self.health)
1407 self.health = t.health;
1408 if((t.targetname != "") && (self.targetname == ""))
1409 self.targetname = t.targetname;
1410 if((t.message != "") && (self.message == ""))
1411 self.message = t.message;
1412 if (t.absmin.x < cmins.x)
1413 cmins.x = t.absmin.x;
1414 if (t.absmin.y < cmins.y)
1415 cmins.y = t.absmin.y;
1416 if (t.absmin.z < cmins.z)
1417 cmins.z = t.absmin.z;
1418 if (t.absmax.x > cmaxs.x)
1419 cmaxs.x = t.absmax.x;
1420 if (t.absmax.y > cmaxs.y)
1421 cmaxs.y = t.absmax.y;
1422 if (t.absmax.z > cmaxs.z)
1423 cmaxs.z = t.absmax.z;
1428 // distribute health, targetname, message
1429 for(t = self; t; t = t.enemy)
1431 t.health = self.health;
1432 t.targetname = self.targetname;
1433 t.message = self.message;
1438 // shootable, or triggered doors just needed the owner/enemy links,
1439 // they don't spawn a field
1448 self.trigger_field = spawn_field(cmins, cmaxs);
1452 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
1453 if two doors touch, they are assumed to be connected and operate as a unit.
1455 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1457 START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors).
1459 GOLD_KEY causes the door to open only if the activator holds a gold key.
1461 SILVER_KEY causes the door to open only if the activator holds a silver key.
1463 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1464 "angle" determines the opening direction
1465 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1466 "health" if set, door must be shot open
1467 "speed" movement speed (100 default)
1468 "wait" wait before returning (3 default, -1 = never return)
1469 "lip" lip remaining at end of move (8 default)
1470 "dmg" damage to inflict when blocked (2 default)
1477 FIXME: only one sound set available at the time being
1481 void door_init_startopen()
1483 setorigin (self, self.pos2);
1484 self.pos2 = self.pos1;
1485 self.pos1 = self.origin;
1490 setorigin(self, self.pos1);
1491 self.velocity = '0 0 0';
1492 self.state = STATE_BOTTOM;
1493 self.think = func_null;
1497 // spawnflags require key (for now only func_door)
1498 const float SPAWNFLAGS_GOLD_KEY = 8;
1499 const float SPAWNFLAGS_SILVER_KEY = 16;
1500 void spawnfunc_func_door()
1502 // Quake 1 keys compatibility
1503 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
1504 self.itemkeys |= ITEM_KEY_BIT(0);
1505 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
1506 self.itemkeys |= ITEM_KEY_BIT(1);
1508 //if (!self.deathtype) // map makers can override this
1509 // self.deathtype = " got in the way";
1512 self.max_health = self.health;
1513 if (!InitMovingBrushTrigger())
1515 self.effects |= EF_LOWPRECISION;
1516 self.classname = "door";
1518 self.blocked = door_blocked;
1519 self.use = door_use;
1521 // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
1522 // if(self.spawnflags & 8)
1523 // self.dmg = 10000;
1525 if(self.dmg && (self.message == ""))
1526 self.message = "was squished";
1527 if(self.dmg && (self.message2 == ""))
1528 self.message2 = "was squished by";
1530 if (self.sounds > 0)
1532 precache_sound ("plats/medplat1.wav");
1533 precache_sound ("plats/medplat2.wav");
1534 self.noise2 = "plats/medplat1.wav";
1535 self.noise1 = "plats/medplat2.wav";
1545 self.pos1 = self.origin;
1546 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1548 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1549 // but spawn in the open position
1550 if (self.spawnflags & DOOR_START_OPEN)
1551 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1553 self.state = STATE_BOTTOM;
1557 self.takedamage = DAMAGE_YES;
1558 self.event_damage = door_damage;
1564 self.touch = door_touch;
1566 // LinkDoors can't be done until all of the doors have been spawned, so
1567 // the sizes can be detected properly.
1568 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1570 self.reset = door_reset;
1573 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1574 if two doors touch, they are assumed to be connected and operate as a unit.
1576 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1578 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1579 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1580 must have set trigger_reverse to 1.
1581 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1583 START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not usefull for touch or takedamage doors).
1585 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1586 "angle" determines the destination angle for opening. negative values reverse the direction.
1587 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1588 "health" if set, door must be shot open
1589 "speed" movement speed (100 default)
1590 "wait" wait before returning (3 default, -1 = never return)
1591 "dmg" damage to inflict when blocked (2 default)
1598 FIXME: only one sound set available at the time being
1601 void door_rotating_reset()
1603 self.angles = self.pos1;
1604 self.avelocity = '0 0 0';
1605 self.state = STATE_BOTTOM;
1606 self.think = func_null;
1610 void door_rotating_init_startopen()
1612 self.angles = self.movedir;
1613 self.pos2 = '0 0 0';
1614 self.pos1 = self.movedir;
1618 void spawnfunc_func_door_rotating()
1621 //if (!self.deathtype) // map makers can override this
1622 // self.deathtype = " got in the way";
1624 // I abuse "movedir" for denoting the axis for now
1625 if (self.spawnflags & 64) // X (untested)
1626 self.movedir = '0 0 1';
1627 else if (self.spawnflags & 128) // Y (untested)
1628 self.movedir = '1 0 0';
1630 self.movedir = '0 1 0';
1632 if (self.angles.y ==0) self.angles.y = 90;
1634 self.movedir = self.movedir * self.angles.y;
1635 self.angles = '0 0 0';
1637 self.max_health = self.health;
1638 self.avelocity = self.movedir;
1639 if (!InitMovingBrushTrigger())
1641 self.velocity = '0 0 0';
1642 //self.effects |= EF_LOWPRECISION;
1643 self.classname = "door_rotating";
1645 self.blocked = door_blocked;
1646 self.use = door_use;
1648 if(self.spawnflags & 8)
1651 if(self.dmg && (self.message == ""))
1652 self.message = "was squished";
1653 if(self.dmg && (self.message2 == ""))
1654 self.message2 = "was squished by";
1656 if (self.sounds > 0)
1658 precache_sound ("plats/medplat1.wav");
1659 precache_sound ("plats/medplat2.wav");
1660 self.noise2 = "plats/medplat1.wav";
1661 self.noise1 = "plats/medplat2.wav";
1668 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1670 self.pos1 = '0 0 0';
1671 self.pos2 = self.movedir;
1673 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1674 // but spawn in the open position
1675 if (self.spawnflags & DOOR_START_OPEN)
1676 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1678 self.state = STATE_BOTTOM;
1682 self.takedamage = DAMAGE_YES;
1683 self.event_damage = door_damage;
1689 self.touch = door_touch;
1691 // LinkDoors can't be done until all of the doors have been spawned, so
1692 // the sizes can be detected properly.
1693 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1695 self.reset = door_rotating_reset;
1699 =============================================================================
1703 =============================================================================
1706 void() fd_secret_move1;
1707 void() fd_secret_move2;
1708 void() fd_secret_move3;
1709 void() fd_secret_move4;
1710 void() fd_secret_move5;
1711 void() fd_secret_move6;
1712 void() fd_secret_done;
1714 const float SECRET_OPEN_ONCE = 1; // stays open
1715 const float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1716 const float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1717 const float SECRET_NO_SHOOT = 8; // only opened by trigger
1718 const float SECRET_YES_SHOOT = 16; // shootable even if targeted
1720 void fd_secret_use()
1723 string message_save;
1725 self.health = 10000;
1726 self.bot_attack = true;
1728 // exit if still moving around...
1729 if (self.origin != self.oldorigin)
1732 message_save = self.message;
1733 self.message = ""; // no more message
1734 SUB_UseTargets(); // fire all targets / killtargets
1735 self.message = message_save;
1737 self.velocity = '0 0 0';
1739 // Make a sound, wait a little...
1741 if (self.noise1 != "")
1742 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1743 self.nextthink = self.ltime + 0.1;
1745 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1746 makevectors(self.mangle);
1750 if (self.spawnflags & SECRET_1ST_DOWN)
1751 self.t_width = fabs(v_up * self.size);
1753 self.t_width = fabs(v_right * self.size);
1757 self.t_length = fabs(v_forward * self.size);
1759 if (self.spawnflags & SECRET_1ST_DOWN)
1760 self.dest1 = self.origin - v_up * self.t_width;
1762 self.dest1 = self.origin + v_right * (self.t_width * temp);
1764 self.dest2 = self.dest1 + v_forward * self.t_length;
1765 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
1766 if (self.noise2 != "")
1767 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1770 void fd_secret_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1775 // Wait after first movement...
1776 void fd_secret_move1()
1778 self.nextthink = self.ltime + 1.0;
1779 self.think = fd_secret_move2;
1780 if (self.noise3 != "")
1781 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1784 // Start moving sideways w/sound...
1785 void fd_secret_move2()
1787 if (self.noise2 != "")
1788 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1789 SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
1792 // Wait here until time to go back...
1793 void fd_secret_move3()
1795 if (self.noise3 != "")
1796 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1797 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1799 self.nextthink = self.ltime + self.wait;
1800 self.think = fd_secret_move4;
1805 void fd_secret_move4()
1807 if (self.noise2 != "")
1808 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1809 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
1813 void fd_secret_move5()
1815 self.nextthink = self.ltime + 1.0;
1816 self.think = fd_secret_move6;
1817 if (self.noise3 != "")
1818 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1821 void fd_secret_move6()
1823 if (self.noise2 != "")
1824 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1825 SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
1828 void fd_secret_done()
1830 if (self.spawnflags&SECRET_YES_SHOOT)
1832 self.health = 10000;
1833 self.takedamage = DAMAGE_YES;
1834 //self.th_pain = fd_secret_use;
1836 if (self.noise3 != "")
1837 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1840 void secret_blocked()
1842 if (time < self.attack_finished_single)
1844 self.attack_finished_single = time + 0.5;
1845 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1857 if (!other.iscreature)
1859 if (self.attack_finished_single > time)
1862 self.attack_finished_single = time + 2;
1866 if (IS_CLIENT(other))
1867 centerprint(other, self.message);
1868 play2(other, "misc/talk.wav");
1874 if (self.spawnflags&SECRET_YES_SHOOT)
1876 self.health = 10000;
1877 self.takedamage = DAMAGE_YES;
1879 setorigin(self, self.oldorigin);
1880 self.think = func_null;
1884 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1885 Basic secret door. Slides back, then to the side. Angle determines direction.
1886 wait = # of seconds before coming back
1887 1st_left = 1st move is left of arrow
1888 1st_down = 1st move is down from arrow
1889 always_shoot = even if targeted, keep shootable
1890 t_width = override WIDTH to move back (or height if going down)
1891 t_length = override LENGTH to move sideways
1892 "dmg" damage to inflict when blocked (2 default)
1894 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1901 void spawnfunc_func_door_secret()
1903 /*if (!self.deathtype) // map makers can override this
1904 self.deathtype = " got in the way";*/
1910 self.mangle = self.angles;
1911 self.angles = '0 0 0';
1912 self.classname = "door";
1913 if (!InitMovingBrushTrigger())
1915 self.effects |= EF_LOWPRECISION;
1917 self.touch = secret_touch;
1918 self.blocked = secret_blocked;
1920 self.use = fd_secret_use;
1925 self.spawnflags |= SECRET_YES_SHOOT;
1927 if(self.spawnflags&SECRET_YES_SHOOT)
1929 self.health = 10000;
1930 self.takedamage = DAMAGE_YES;
1931 self.event_damage = fd_secret_damage;
1933 self.oldorigin = self.origin;
1935 self.wait = 5; // 5 seconds before closing
1937 self.reset = secret_reset;
1941 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1942 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1943 netname: list of <frequencymultiplier> <phase> <x> <y> <z> quadruples, separated by spaces; note that phase 0 represents a sine wave, and phase 0.25 a cosine wave (by default, it uses 1 0 0 0 1, to match func_bobbing's defaults
1944 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1945 height: amplitude modifier (default 32)
1946 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1947 noise: path/name of looping .wav file to play.
1948 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1952 void func_fourier_controller_think()
1957 self.nextthink = time + 0.1;
1958 if(self.owner.active != ACTIVE_ACTIVE)
1960 self.owner.velocity = '0 0 0';
1965 n = floor((tokenize_console(self.owner.netname)) / 5);
1966 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1968 v = self.owner.destvec;
1970 for(i = 0; i < n; ++i)
1972 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1973 v = v + ('1 0 0' * stof(argv(i*5+2)) + '0 1 0' * stof(argv(i*5+3)) + '0 0 1' * stof(argv(i*5+4))) * self.owner.height * v_forward.y;
1976 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1977 // * 10 so it will arrive in 0.1 sec
1978 self.owner.velocity = (v - self.owner.origin) * 10;
1981 void spawnfunc_func_fourier()
1984 if (self.noise != "")
1986 precache_sound(self.noise);
1987 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
1994 self.destvec = self.origin;
1995 self.cnt = 360 / self.speed;
1997 self.blocked = generic_plat_blocked;
1998 if(self.dmg && (self.message == ""))
1999 self.message = " was squished";
2000 if(self.dmg && (self.message2 == ""))
2001 self.message2 = "was squished by";
2002 if(self.dmg && (!self.dmgtime))
2003 self.dmgtime = 0.25;
2004 self.dmgtime2 = time;
2006 if(self.netname == "")
2007 self.netname = "1 0 0 0 1";
2009 if (!InitMovingBrushTrigger())
2012 self.active = ACTIVE_ACTIVE;
2014 // wait for targets to spawn
2015 controller = spawn();
2016 controller.classname = "func_fourier_controller";
2017 controller.owner = self;
2018 controller.nextthink = time + 1;
2019 controller.think = func_fourier_controller_think;
2020 self.nextthink = self.ltime + 999999999;
2021 self.think = SUB_NullThink; // for PushMove
2023 // Savage: Reduce bandwith, critical on e.g. nexdm02
2024 self.effects |= EF_LOWPRECISION;
2026 // TODO make a reset function for this one
2029 // reusing some fields havocbots declared
2030 .entity wp00, wp01, wp02, wp03;
2032 .float targetfactor, target2factor, target3factor, target4factor;
2033 .vector targetnormal, target2normal, target3normal, target4normal;
2035 vector func_vectormamamam_origin(entity o, float t)
2047 p = e.origin + t * e.velocity;
2049 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
2051 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
2057 p = e.origin + t * e.velocity;
2059 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
2061 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
2067 p = e.origin + t * e.velocity;
2069 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
2071 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
2077 p = e.origin + t * e.velocity;
2079 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
2081 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
2087 void func_vectormamamam_controller_think()
2089 self.nextthink = time + 0.1;
2091 if(self.owner.active != ACTIVE_ACTIVE)
2093 self.owner.velocity = '0 0 0';
2097 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
2098 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
2101 void func_vectormamamam_findtarget()
2103 if(self.target != "")
2104 self.wp00 = find(world, targetname, self.target);
2106 if(self.target2 != "")
2107 self.wp01 = find(world, targetname, self.target2);
2109 if(self.target3 != "")
2110 self.wp02 = find(world, targetname, self.target3);
2112 if(self.target4 != "")
2113 self.wp03 = find(world, targetname, self.target4);
2115 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
2116 objerror("No reference entity found, so there is nothing to move. Aborting.");
2118 self.destvec = self.origin - func_vectormamamam_origin(self, 0);
2121 controller = spawn();
2122 controller.classname = "func_vectormamamam_controller";
2123 controller.owner = self;
2124 controller.nextthink = time + 1;
2125 controller.think = func_vectormamamam_controller_think;
2128 void spawnfunc_func_vectormamamam()
2130 if (self.noise != "")
2132 precache_sound(self.noise);
2133 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
2136 if(!self.targetfactor)
2137 self.targetfactor = 1;
2139 if(!self.target2factor)
2140 self.target2factor = 1;
2142 if(!self.target3factor)
2143 self.target3factor = 1;
2145 if(!self.target4factor)
2146 self.target4factor = 1;
2148 if(vlen(self.targetnormal))
2149 self.targetnormal = normalize(self.targetnormal);
2151 if(vlen(self.target2normal))
2152 self.target2normal = normalize(self.target2normal);
2154 if(vlen(self.target3normal))
2155 self.target3normal = normalize(self.target3normal);
2157 if(vlen(self.target4normal))
2158 self.target4normal = normalize(self.target4normal);
2160 self.blocked = generic_plat_blocked;
2161 if(self.dmg && (self.message == ""))
2162 self.message = " was squished";
2163 if(self.dmg && (self.message == ""))
2164 self.message2 = "was squished by";
2165 if(self.dmg && (!self.dmgtime))
2166 self.dmgtime = 0.25;
2167 self.dmgtime2 = time;
2169 if(self.netname == "")
2170 self.netname = "1 0 0 0 1";
2172 if (!InitMovingBrushTrigger())
2175 // wait for targets to spawn
2176 self.nextthink = self.ltime + 999999999;
2177 self.think = SUB_NullThink; // for PushMove
2179 // Savage: Reduce bandwith, critical on e.g. nexdm02
2180 self.effects |= EF_LOWPRECISION;
2182 self.active = ACTIVE_ACTIVE;
2184 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
2189 void conveyor_think()
2192 float dt = time - self.move_time;
2193 self.move_time = time;
2194 if(dt <= 0) { return; }
2198 // set myself as current conveyor where possible
2199 for(e = world; (e = findentity(e, conveyor, self)); )
2204 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2205 if(!e.conveyor.state)
2212 vector emin = e.absmin;
2213 vector emax = e.absmax;
2214 if(self.solid == SOLID_BSP)
2219 if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2220 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2224 for(e = world; (e = findentity(e, conveyor, self)); )
2227 if(IS_CLIENT(e)) // doing it via velocity has quite some advantages
2228 continue; // done in SV_PlayerPhysics
2234 setorigin(e, e.origin + self.movedir * PHYS_INPUT_FRAMETIME);
2235 move_out_of_solid(e);
2237 UpdateCSQCProjectile(e);
2240 // stupid conveyor code
2241 tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2242 if(trace_fraction > 0)
2243 setorigin(e, trace_endpos);
2249 self.nextthink = time;
2257 self.state = !self.state;
2259 self.SendFlags |= 2;
2262 void conveyor_reset()
2264 self.state = (self.spawnflags & 1);
2266 self.SendFlags |= 2;
2269 float conveyor_send(entity to, float sf)
2271 WriteByte(MSG_ENTITY, ENT_CLIENT_CONVEYOR);
2272 WriteByte(MSG_ENTITY, sf);
2276 WriteByte(MSG_ENTITY, self.warpzone_isboxy);
2277 WriteCoord(MSG_ENTITY, self.origin_x);
2278 WriteCoord(MSG_ENTITY, self.origin_y);
2279 WriteCoord(MSG_ENTITY, self.origin_z);
2281 WriteCoord(MSG_ENTITY, self.mins_x);
2282 WriteCoord(MSG_ENTITY, self.mins_y);
2283 WriteCoord(MSG_ENTITY, self.mins_z);
2284 WriteCoord(MSG_ENTITY, self.maxs_x);
2285 WriteCoord(MSG_ENTITY, self.maxs_y);
2286 WriteCoord(MSG_ENTITY, self.maxs_z);
2288 WriteCoord(MSG_ENTITY, self.movedir_x);
2289 WriteCoord(MSG_ENTITY, self.movedir_y);
2290 WriteCoord(MSG_ENTITY, self.movedir_z);
2292 WriteByte(MSG_ENTITY, self.speed);
2293 WriteByte(MSG_ENTITY, self.state);
2295 WriteString(MSG_ENTITY, self.targetname);
2296 WriteString(MSG_ENTITY, self.target);
2300 WriteByte(MSG_ENTITY, self.state);
2305 void conveyor_init()
2309 self.movedir = self.movedir * self.speed;
2310 self.think = conveyor_think;
2311 self.nextthink = time;
2314 self.use = conveyor_use;
2315 self.reset = conveyor_reset;
2321 Net_LinkEntity(self, 0, false, conveyor_send);
2323 self.SendFlags |= 1;
2326 void spawnfunc_trigger_conveyor()
2333 void spawnfunc_func_conveyor()
2336 InitMovingBrushTrigger();
2337 self.movetype = MOVETYPE_NONE;
2343 void conveyor_init()
2345 self.draw = conveyor_think;
2346 self.drawmask = MASK_NORMAL;
2348 self.movetype = MOVETYPE_NONE;
2350 self.solid = SOLID_TRIGGER;
2351 self.move_origin = self.origin;
2352 self.move_time = time;
2357 float sf = ReadByte();
2361 self.warpzone_isboxy = ReadByte();
2362 self.origin_x = ReadCoord();
2363 self.origin_y = ReadCoord();
2364 self.origin_z = ReadCoord();
2365 setorigin(self, self.origin);
2367 self.mins_x = ReadCoord();
2368 self.mins_y = ReadCoord();
2369 self.mins_z = ReadCoord();
2370 self.maxs_x = ReadCoord();
2371 self.maxs_y = ReadCoord();
2372 self.maxs_z = ReadCoord();
2373 setsize(self, self.mins, self.maxs);
2375 self.movedir_x = ReadCoord();
2376 self.movedir_y = ReadCoord();
2377 self.movedir_z = ReadCoord();
2379 self.speed = ReadByte();
2380 self.state = ReadByte();
2382 self.targetname = strzone(ReadString());
2383 self.target = strzone(ReadString());
2389 self.state = ReadByte();