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"
21 void generic_plat_blocked()
23 if(self.dmg && other.takedamage != DAMAGE_NO) {
24 if(self.dmgtime2 < time) {
25 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
26 self.dmgtime2 = time + self.dmgtime;
29 // Gib dead/dying stuff
30 if(other.deadflag != DEAD_NO)
31 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
36 .entity trigger_field;
38 void() plat_center_touch;
39 void() plat_outside_touch;
40 void() plat_trigger_use;
44 const float PLAT_LOW_TRIGGER = 1;
46 void plat_spawn_inside_trigger()
52 trigger.touch = plat_center_touch;
53 trigger.movetype = MOVETYPE_NONE;
54 trigger.solid = SOLID_TRIGGER;
57 tmin = self.absmin + '25 25 0';
58 tmax = self.absmax - '25 25 -8';
59 tmin.z = tmax.z - (self.pos1_z - self.pos2_z + 8);
60 if (self.spawnflags & PLAT_LOW_TRIGGER)
63 if (self.size.x <= 50)
65 tmin.x = (self.mins.x + self.maxs.x) / 2;
68 if (self.size.y <= 50)
70 tmin.y = (self.mins.y + self.maxs.y) / 2;
78 setsize (trigger, tmin, tmax);
82 // otherwise, something is fishy...
84 objerror("plat_spawn_inside_trigger: platform has odd size or lip, can't spawn");
89 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
91 self.think = plat_go_down;
92 self.nextthink = self.ltime + 3;
95 void plat_hit_bottom()
97 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
103 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
105 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, plat_hit_bottom);
110 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
112 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, plat_hit_top);
115 void plat_center_touch()
117 if (!other.iscreature)
120 if (other.health <= 0)
126 else if (self.state == 1)
127 self.nextthink = self.ltime + 1; // delay going down
130 void plat_outside_touch()
132 if (!other.iscreature)
135 if (other.health <= 0)
143 void plat_trigger_use()
146 return; // already activated
153 if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
154 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
156 if((self.dmg) && (other.takedamage != DAMAGE_NO)) { // Shall we bite?
157 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
158 // Gib dead/dying stuff
159 if(other.deadflag != DEAD_NO)
160 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
165 else if (self.state == 3)
167 // when in other states, then the plat_crush event came delayed after
168 // plat state already had changed
169 // this isn't a bug per se!
175 self.use = func_null;
177 objerror ("plat_use: not in up state");
181 .string sound1, sound2;
187 setorigin (self, self.pos1);
193 setorigin (self, self.pos2);
195 self.use = plat_trigger_use;
199 .float platmovetype_start_default, platmovetype_end_default;
200 float set_platmovetype(entity e, string s)
202 // sets platmovetype_start and platmovetype_end based on a string consisting of two values
205 n = tokenize_console(s);
207 e.platmovetype_start = stof(argv(0));
209 e.platmovetype_start = 0;
212 e.platmovetype_end = stof(argv(1));
214 e.platmovetype_end = e.platmovetype_start;
217 if(argv(2) == "force")
218 return true; // no checking, return immediately
220 if(!cubic_speedfunc_is_sane(e.platmovetype_start, e.platmovetype_end))
222 objerror("Invalid platform move type; platform would go in reverse, which is not allowed.");
229 void spawnfunc_path_corner()
231 // setup values for overriding train movement
232 // if a second value does not exist, both start and end speeds are the single value specified
233 if(!set_platmovetype(self, self.platmovetype))
236 void spawnfunc_func_plat()
238 if (self.sounds == 0)
241 if(self.spawnflags & 4)
244 if(self.dmg && (self.message == ""))
245 self.message = "was squished";
246 if(self.dmg && (self.message2 == ""))
247 self.message2 = "was squished by";
249 if (self.sounds == 1)
251 precache_sound ("plats/plat1.wav");
252 precache_sound ("plats/plat2.wav");
253 self.noise = "plats/plat1.wav";
254 self.noise1 = "plats/plat2.wav";
257 if (self.sounds == 2)
259 precache_sound ("plats/medplat1.wav");
260 precache_sound ("plats/medplat2.wav");
261 self.noise = "plats/medplat1.wav";
262 self.noise1 = "plats/medplat2.wav";
267 precache_sound (self.sound1);
268 self.noise = self.sound1;
272 precache_sound (self.sound2);
273 self.noise1 = self.sound2;
276 self.mangle = self.angles;
277 self.angles = '0 0 0';
279 self.classname = "plat";
280 if (!InitMovingBrushTrigger())
282 self.effects |= EF_LOWPRECISION;
283 setsize (self, self.mins , self.maxs);
285 self.blocked = plat_crush;
292 self.height = self.size.z - self.lip;
294 self.pos1 = self.origin;
295 self.pos2 = self.origin;
296 self.pos2_z = self.origin.z - self.height;
298 self.reset = plat_reset;
301 plat_spawn_inside_trigger (); // the "start moving" trigger
304 .float train_wait_turning;
315 // if turning is enabled, the train will turn toward the next point while waiting
316 if(self.platmovetype_turn && !self.train_wait_turning)
320 targ = find(world, targetname, self.target);
321 if((self.spawnflags & 1) && targ.curvetarget)
322 cp = find(world, targetname, targ.curvetarget);
326 if(cp) // bezier curves movement
327 ang = cp.origin - (self.origin - self.view_ofs); // use the origin of the control point of the next path_corner
328 else // linear movement
329 ang = targ.origin - (self.origin - self.view_ofs); // use the origin of the next path_corner
330 ang = vectoangles(ang);
331 ang.x = -ang.x; // flip up / down orientation
333 if(self.wait > 0) // slow turning
334 SUB_CalcAngleMove(ang, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
335 else // instant turning
336 SUB_CalcAngleMove(ang, TSPEED_TIME, 0.0000001, train_wait);
337 self.train_wait_turning = true;
342 stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
344 if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
346 self.train_wait_turning = false;
351 self.think = train_next;
352 self.nextthink = self.ltime + self.wait;
358 entity targ, cp = world;
359 vector cp_org = '0 0 0';
361 targ = find(world, targetname, self.target);
362 self.target = targ.target;
363 if (self.spawnflags & 1)
367 cp = find(world, targetname, targ.curvetarget); // get its second target (the control point)
368 cp_org = cp.origin - self.view_ofs; // no control point found, assume a straight line to the destination
371 if (self.target == "")
372 objerror("train_next: no next target");
373 self.wait = targ.wait;
377 if(targ.platmovetype)
379 // this path_corner contains a movetype overrider, apply it
380 self.platmovetype_start = targ.platmovetype_start;
381 self.platmovetype_end = targ.platmovetype_end;
385 // this path_corner doesn't contain a movetype overrider, use the train's defaults
386 self.platmovetype_start = self.platmovetype_start_default;
387 self.platmovetype_end = self.platmovetype_end_default;
393 SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
395 SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
400 SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
402 SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
406 sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
409 void func_train_find()
412 targ = find(world, targetname, self.target);
413 self.target = targ.target;
414 if (self.target == "")
415 objerror("func_train_find: no next target");
416 setorigin(self, targ.origin - self.view_ofs);
417 self.nextthink = self.ltime + 1;
418 self.think = train_next;
421 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
422 Ridable platform, targets spawnfunc_path_corner path to follow.
423 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
424 target : targetname of first spawnfunc_path_corner (starts here)
426 void spawnfunc_func_train()
428 if (self.noise != "")
429 precache_sound(self.noise);
431 if (self.target == "")
432 objerror("func_train without a target");
436 if (!InitMovingBrushTrigger())
438 self.effects |= EF_LOWPRECISION;
440 if (self.spawnflags & 2)
442 self.platmovetype_turn = true;
443 self.view_ofs = '0 0 0'; // don't offset a rotating train, origin works differently now
446 self.view_ofs = self.mins;
448 // wait for targets to spawn
449 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
451 self.blocked = generic_plat_blocked;
452 if(self.dmg && (self.message == ""))
453 self.message = " was squished";
454 if(self.dmg && (self.message2 == ""))
455 self.message2 = "was squished by";
456 if(self.dmg && (!self.dmgtime))
458 self.dmgtime2 = time;
460 if(!set_platmovetype(self, self.platmovetype))
462 self.platmovetype_start_default = self.platmovetype_start;
463 self.platmovetype_end_default = self.platmovetype_end;
465 // TODO make a reset function for this one
468 void func_rotating_setactive(float astate)
471 if (astate == ACTIVE_TOGGLE)
473 if(self.active == ACTIVE_ACTIVE)
474 self.active = ACTIVE_NOT;
476 self.active = ACTIVE_ACTIVE;
479 self.active = astate;
481 if(self.active == ACTIVE_NOT)
482 self.avelocity = '0 0 0';
484 self.avelocity = self.pos1;
487 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
488 Brush model that spins in place on one axis (default Z).
489 speed : speed to rotate (in degrees per second)
490 noise : path/name of looping .wav file to play.
491 dmg : Do this mutch dmg every .dmgtime intervall when blocked
495 void spawnfunc_func_rotating()
497 if (self.noise != "")
499 precache_sound(self.noise);
500 ambientsound(self.origin, self.noise, VOL_BASE, ATTEN_IDLE);
503 self.active = ACTIVE_ACTIVE;
504 self.setactive = func_rotating_setactive;
508 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
509 if (self.spawnflags & 4) // X (untested)
510 self.avelocity = '0 0 1' * self.speed;
511 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
512 else if (self.spawnflags & 8) // Y (untested)
513 self.avelocity = '1 0 0' * self.speed;
514 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
516 self.avelocity = '0 1 0' * self.speed;
518 self.pos1 = self.avelocity;
520 if(self.dmg && (self.message == ""))
521 self.message = " was squished";
522 if(self.dmg && (self.message2 == ""))
523 self.message2 = "was squished by";
526 if(self.dmg && (!self.dmgtime))
529 self.dmgtime2 = time;
531 if (!InitMovingBrushTrigger())
533 // no EF_LOWPRECISION here, as rounding angles is bad
535 self.blocked = generic_plat_blocked;
537 // wait for targets to spawn
538 self.nextthink = self.ltime + 999999999;
539 self.think = SUB_NullThink; // for PushMove
541 // TODO make a reset function for this one
545 void func_bobbing_controller_think()
548 self.nextthink = time + 0.1;
550 if(self.owner.active != ACTIVE_ACTIVE)
552 self.owner.velocity = '0 0 0';
556 // calculate sinewave using makevectors
557 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
558 v = self.owner.destvec + self.owner.movedir * v_forward.y;
559 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
560 // * 10 so it will arrive in 0.1 sec
561 self.owner.velocity = (v - self.owner.origin) * 10;
564 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
565 Brush model that moves back and forth on one axis (default Z).
566 speed : how long one cycle takes in seconds (default 4)
567 height : how far the cycle moves (default 32)
568 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
569 noise : path/name of looping .wav file to play.
570 dmg : Do this mutch dmg every .dmgtime intervall when blocked
573 void spawnfunc_func_bobbing()
576 if (self.noise != "")
578 precache_sound(self.noise);
579 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
585 // center of bobbing motion
586 self.destvec = self.origin;
587 // time scale to get degrees
588 self.cnt = 360 / self.speed;
590 self.active = ACTIVE_ACTIVE;
592 // damage when blocked
593 self.blocked = generic_plat_blocked;
594 if(self.dmg && (self.message == ""))
595 self.message = " was squished";
596 if(self.dmg && (self.message2 == ""))
597 self.message2 = "was squished by";
598 if(self.dmg && (!self.dmgtime))
600 self.dmgtime2 = time;
603 if (self.spawnflags & 1) // X
604 self.movedir = '1 0 0' * self.height;
605 else if (self.spawnflags & 2) // Y
606 self.movedir = '0 1 0' * self.height;
608 self.movedir = '0 0 1' * self.height;
610 if (!InitMovingBrushTrigger())
613 // wait for targets to spawn
614 controller = spawn();
615 controller.classname = "func_bobbing_controller";
616 controller.owner = self;
617 controller.nextthink = time + 1;
618 controller.think = func_bobbing_controller_think;
619 self.nextthink = self.ltime + 999999999;
620 self.think = SUB_NullThink; // for PushMove
622 // Savage: Reduce bandwith, critical on e.g. nexdm02
623 self.effects |= EF_LOWPRECISION;
625 // TODO make a reset function for this one
629 void func_pendulum_controller_think()
632 self.nextthink = time + 0.1;
634 if (!(self.owner.active == ACTIVE_ACTIVE))
636 self.owner.avelocity_x = 0;
640 // calculate sinewave using makevectors
641 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
642 v = self.owner.speed * v_forward.y + self.cnt;
643 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
645 // * 10 so it will arrive in 0.1 sec
646 self.owner.avelocity_z = (remainder(v - self.owner.angles.z, 360)) * 10;
650 void spawnfunc_func_pendulum()
653 if (self.noise != "")
655 precache_sound(self.noise);
656 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
659 self.active = ACTIVE_ACTIVE;
661 // keys: angle, speed, phase, noise, freq
665 // not initializing self.dmg to 2, to allow damageless pendulum
667 if(self.dmg && (self.message == ""))
668 self.message = " was squished";
669 if(self.dmg && (self.message2 == ""))
670 self.message2 = "was squished by";
671 if(self.dmg && (!self.dmgtime))
673 self.dmgtime2 = time;
675 self.blocked = generic_plat_blocked;
677 self.avelocity_z = 0.0000001;
678 if (!InitMovingBrushTrigger())
683 // find pendulum length (same formula as Q3A)
684 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins.z))));
687 // copy initial angle
688 self.cnt = self.angles.z;
690 // wait for targets to spawn
691 controller = spawn();
692 controller.classname = "func_pendulum_controller";
693 controller.owner = self;
694 controller.nextthink = time + 1;
695 controller.think = func_pendulum_controller_think;
696 self.nextthink = self.ltime + 999999999;
697 self.think = SUB_NullThink; // for PushMove
699 //self.effects |= EF_LOWPRECISION;
701 // TODO make a reset function for this one
704 // button and multiple button
707 void() button_return;
711 self.state = STATE_TOP;
712 self.nextthink = self.ltime + self.wait;
713 self.think = button_return;
714 activator = self.enemy;
716 self.frame = 1; // use alternate textures
721 self.state = STATE_BOTTOM;
726 self.state = STATE_DOWN;
727 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
728 self.frame = 0; // use normal textures
730 self.takedamage = DAMAGE_YES; // can be shot again
734 void button_blocked()
736 // do nothing, just don't come all the way back out
742 self.health = self.max_health;
743 self.takedamage = DAMAGE_NO; // will be reset upon return
745 if (self.state == STATE_UP || self.state == STATE_TOP)
748 if (self.noise != "")
749 sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
751 self.state = STATE_UP;
752 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
757 self.health = self.max_health;
758 setorigin(self, self.pos1);
759 self.frame = 0; // use normal textures
760 self.state = STATE_BOTTOM;
762 self.takedamage = DAMAGE_YES; // can be shot again
767 if(self.active != ACTIVE_ACTIVE)
770 self.enemy = activator;
778 if (!other.iscreature)
780 if(other.velocity * self.movedir < 0)
784 self.enemy = other.owner;
788 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
790 if(self.spawnflags & DOOR_NOSPLASH)
791 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
793 self.health = self.health - damage;
794 if (self.health <= 0)
796 self.enemy = damage_attacker;
802 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
803 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.
805 "angle" determines the opening direction
806 "target" all entities with a matching targetname will be used
807 "speed" override the default 40 speed
808 "wait" override the default 1 second wait (-1 = never return)
809 "lip" override the default 4 pixel lip remaining at end of move
810 "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
817 void spawnfunc_func_button()
821 if (!InitMovingBrushTrigger())
823 self.effects |= EF_LOWPRECISION;
825 self.blocked = button_blocked;
826 self.use = button_use;
828 // if (self.health == 0) // all buttons are now shootable
832 self.max_health = self.health;
833 self.event_damage = button_damage;
834 self.takedamage = DAMAGE_YES;
837 self.touch = button_touch;
847 precache_sound(self.noise);
849 self.active = ACTIVE_ACTIVE;
851 self.pos1 = self.origin;
852 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
853 self.flags |= FL_NOTARGET;
859 const float DOOR_START_OPEN = 1;
860 const float DOOR_DONT_LINK = 4;
861 const float DOOR_TOGGLE = 32;
865 Doors are similar to buttons, but can spawn a fat trigger field around them
866 to open without a touch, and they link together to form simultanious
869 Door.owner is the master door. If there is only one door, it points to itself.
870 If multiple doors, all will point to a single one.
872 Door.enemy chains from the master door through all doors linked in the chain.
877 =============================================================================
881 =============================================================================
886 void() door_rotating_go_down;
887 void() door_rotating_go_up;
892 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
893 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
896 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
897 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
899 //Dont chamge direction for dead or dying stuff
900 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
903 if (self.state == STATE_DOWN)
904 if (self.classname == "door")
909 door_rotating_go_up ();
912 if (self.classname == "door")
917 door_rotating_go_down ();
921 //gib dying stuff just to make sure
922 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
923 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
927 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
928 // if a door has a negative wait, it would never come back if blocked,
929 // so let it just squash the object to death real fast
930 /* if (self.wait >= 0)
932 if (self.state == STATE_DOWN)
943 if (self.noise1 != "")
944 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
945 self.state = STATE_TOP;
946 if (self.spawnflags & DOOR_TOGGLE)
947 return; // don't come down automatically
948 if (self.classname == "door")
950 self.think = door_go_down;
953 self.think = door_rotating_go_down;
955 self.nextthink = self.ltime + self.wait;
958 void door_hit_bottom()
960 if (self.noise1 != "")
961 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
962 self.state = STATE_BOTTOM;
967 if (self.noise2 != "")
968 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
971 self.takedamage = DAMAGE_YES;
972 self.health = self.max_health;
975 self.state = STATE_DOWN;
976 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
981 if (self.state == STATE_UP)
982 return; // already going up
984 if (self.state == STATE_TOP)
985 { // reset top wait time
986 self.nextthink = self.ltime + self.wait;
990 if (self.noise2 != "")
991 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
992 self.state = STATE_UP;
993 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
996 oldmessage = self.message;
999 self.message = oldmessage;
1005 =============================================================================
1007 ACTIVATION FUNCTIONS
1009 =============================================================================
1012 float door_check_keys(void) {
1013 entity door = self.owner ? self.owner : self;
1019 // this door require a key
1020 // only a player can have a key
1021 if (!IS_PLAYER(other))
1024 if (item_keys_usekey(door, other)) {
1025 // some keys were used
1026 if (other.key_door_messagetime <= time) {
1027 play2(other, "misc/talk.wav");
1028 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
1029 other.key_door_messagetime = time + 2;
1032 // no keys were used
1033 if (other.key_door_messagetime <= time) {
1034 play2(other, "misc/talk.wav");
1035 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
1036 other.key_door_messagetime = time + 2;
1040 if (door.itemkeys) {
1041 // door is now unlocked
1042 play2(other, "misc/talk.wav");
1043 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_UNLOCKED);
1055 if (self.owner != self)
1056 objerror ("door_fire: self.owner != self");
1060 if (self.spawnflags & DOOR_TOGGLE)
1062 if (self.state == STATE_UP || self.state == STATE_TOP)
1067 if (self.classname == "door")
1073 door_rotating_go_down ();
1076 } while ( (self != starte) && (self != world) );
1082 // trigger all paired doors
1086 if (self.classname == "door")
1091 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
1092 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
1094 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
1095 self.pos2 = '0 0 0' - self.pos2;
1097 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
1098 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
1099 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
1101 door_rotating_go_up ();
1105 } while ( (self != starte) && (self != world) );
1114 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
1126 void door_trigger_touch()
1128 if (other.health < 1)
1129 if (!(other.iscreature && other.deadflag == DEAD_NO))
1132 if (time < self.attack_finished_single)
1135 // check if door is locked
1136 if (!door_check_keys())
1139 self.attack_finished_single = time + 1;
1148 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1151 if(self.spawnflags & DOOR_NOSPLASH)
1152 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
1154 self.health = self.health - damage;
1156 if (self.itemkeys) {
1157 // don't allow opening doors through damage if keys are required
1161 if (self.health <= 0)
1165 self.health = self.max_health;
1166 self.takedamage = DAMAGE_NO; // wil be reset upon return
1182 if (!IS_PLAYER(other))
1184 if (self.owner.attack_finished_single > time)
1187 self.owner.attack_finished_single = time + 2;
1189 if (!(self.owner.dmg) && (self.owner.message != ""))
1191 if (IS_CLIENT(other))
1192 centerprint(other, self.owner.message);
1193 play2(other, "misc/talk.wav");
1198 void door_generic_plat_blocked()
1201 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1202 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1205 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1206 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1208 //Dont chamge direction for dead or dying stuff
1209 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1212 if (self.state == STATE_DOWN)
1213 door_rotating_go_up ();
1215 door_rotating_go_down ();
1218 //gib dying stuff just to make sure
1219 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1220 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1224 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1225 // if a door has a negative wait, it would never come back if blocked,
1226 // so let it just squash the object to death real fast
1227 /* if (self.wait >= 0)
1229 if (self.state == STATE_DOWN)
1230 door_rotating_go_up ();
1232 door_rotating_go_down ();
1238 void door_rotating_hit_top()
1240 if (self.noise1 != "")
1241 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1242 self.state = STATE_TOP;
1243 if (self.spawnflags & DOOR_TOGGLE)
1244 return; // don't come down automatically
1245 self.think = door_rotating_go_down;
1246 self.nextthink = self.ltime + self.wait;
1249 void door_rotating_hit_bottom()
1251 if (self.noise1 != "")
1252 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1253 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1255 self.pos2 = '0 0 0' - self.pos2;
1258 self.state = STATE_BOTTOM;
1261 void door_rotating_go_down()
1263 if (self.noise2 != "")
1264 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1265 if (self.max_health)
1267 self.takedamage = DAMAGE_YES;
1268 self.health = self.max_health;
1271 self.state = STATE_DOWN;
1272 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
1275 void door_rotating_go_up()
1277 if (self.state == STATE_UP)
1278 return; // already going up
1280 if (self.state == STATE_TOP)
1281 { // reset top wait time
1282 self.nextthink = self.ltime + self.wait;
1285 if (self.noise2 != "")
1286 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1287 self.state = STATE_UP;
1288 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
1291 oldmessage = self.message;
1294 self.message = oldmessage;
1301 =============================================================================
1305 =============================================================================
1309 entity spawn_field(vector fmins, vector fmaxs)
1315 trigger.classname = "doortriggerfield";
1316 trigger.movetype = MOVETYPE_NONE;
1317 trigger.solid = SOLID_TRIGGER;
1318 trigger.owner = self;
1319 trigger.touch = door_trigger_touch;
1323 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1328 entity LinkDoors_nextent(entity cur, entity near, entity pass)
1330 while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
1336 float LinkDoors_isconnected(entity e1, entity e2, entity pass)
1339 if (e1.absmin.x > e2.absmax.x + DELTA)
1341 if (e1.absmin.y > e2.absmax.y + DELTA)
1343 if (e1.absmin.z > e2.absmax.z + DELTA)
1345 if (e2.absmin.x > e1.absmax.x + DELTA)
1347 if (e2.absmin.y > e1.absmax.y + DELTA)
1349 if (e2.absmin.z > e1.absmax.z + DELTA)
1364 vector cmins, cmaxs;
1367 return; // already linked by another door
1368 if (self.spawnflags & 4)
1370 self.owner = self.enemy = self;
1378 self.trigger_field = spawn_field(self.absmin, self.absmax);
1380 return; // don't want to link this door
1383 FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
1385 // set owner, and make a loop of the chain
1386 dprint("LinkDoors: linking doors:");
1387 for(t = self; ; t = t.enemy)
1389 dprint(" ", etos(t));
1391 if(t.enemy == world)
1399 // collect health, targetname, message, size
1400 cmins = self.absmin;
1401 cmaxs = self.absmax;
1402 for(t = self; ; t = t.enemy)
1404 if(t.health && !self.health)
1405 self.health = t.health;
1406 if((t.targetname != "") && (self.targetname == ""))
1407 self.targetname = t.targetname;
1408 if((t.message != "") && (self.message == ""))
1409 self.message = t.message;
1410 if (t.absmin.x < cmins.x)
1411 cmins.x = t.absmin.x;
1412 if (t.absmin.y < cmins.y)
1413 cmins.y = t.absmin.y;
1414 if (t.absmin.z < cmins.z)
1415 cmins.z = t.absmin.z;
1416 if (t.absmax.x > cmaxs.x)
1417 cmaxs.x = t.absmax.x;
1418 if (t.absmax.y > cmaxs.y)
1419 cmaxs.y = t.absmax.y;
1420 if (t.absmax.z > cmaxs.z)
1421 cmaxs.z = t.absmax.z;
1426 // distribute health, targetname, message
1427 for(t = self; t; t = t.enemy)
1429 t.health = self.health;
1430 t.targetname = self.targetname;
1431 t.message = self.message;
1436 // shootable, or triggered doors just needed the owner/enemy links,
1437 // they don't spawn a field
1446 self.trigger_field = spawn_field(cmins, cmaxs);
1450 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
1451 if two doors touch, they are assumed to be connected and operate as a unit.
1453 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1455 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).
1457 GOLD_KEY causes the door to open only if the activator holds a gold key.
1459 SILVER_KEY causes the door to open only if the activator holds a silver key.
1461 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1462 "angle" determines the opening direction
1463 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1464 "health" if set, door must be shot open
1465 "speed" movement speed (100 default)
1466 "wait" wait before returning (3 default, -1 = never return)
1467 "lip" lip remaining at end of move (8 default)
1468 "dmg" damage to inflict when blocked (2 default)
1475 FIXME: only one sound set available at the time being
1479 void door_init_startopen()
1481 setorigin (self, self.pos2);
1482 self.pos2 = self.pos1;
1483 self.pos1 = self.origin;
1488 setorigin(self, self.pos1);
1489 self.velocity = '0 0 0';
1490 self.state = STATE_BOTTOM;
1491 self.think = func_null;
1495 // spawnflags require key (for now only func_door)
1496 const float SPAWNFLAGS_GOLD_KEY = 8;
1497 const float SPAWNFLAGS_SILVER_KEY = 16;
1498 void spawnfunc_func_door()
1500 // Quake 1 keys compatibility
1501 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
1502 self.itemkeys |= ITEM_KEY_BIT(0);
1503 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
1504 self.itemkeys |= ITEM_KEY_BIT(1);
1506 //if (!self.deathtype) // map makers can override this
1507 // self.deathtype = " got in the way";
1510 self.max_health = self.health;
1511 if (!InitMovingBrushTrigger())
1513 self.effects |= EF_LOWPRECISION;
1514 self.classname = "door";
1516 self.blocked = door_blocked;
1517 self.use = door_use;
1519 // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
1520 // if(self.spawnflags & 8)
1521 // self.dmg = 10000;
1523 if(self.dmg && (self.message == ""))
1524 self.message = "was squished";
1525 if(self.dmg && (self.message2 == ""))
1526 self.message2 = "was squished by";
1528 if (self.sounds > 0)
1530 precache_sound ("plats/medplat1.wav");
1531 precache_sound ("plats/medplat2.wav");
1532 self.noise2 = "plats/medplat1.wav";
1533 self.noise1 = "plats/medplat2.wav";
1543 self.pos1 = self.origin;
1544 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1546 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1547 // but spawn in the open position
1548 if (self.spawnflags & DOOR_START_OPEN)
1549 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1551 self.state = STATE_BOTTOM;
1555 self.takedamage = DAMAGE_YES;
1556 self.event_damage = door_damage;
1562 self.touch = door_touch;
1564 // LinkDoors can't be done until all of the doors have been spawned, so
1565 // the sizes can be detected properly.
1566 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1568 self.reset = door_reset;
1571 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1572 if two doors touch, they are assumed to be connected and operate as a unit.
1574 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1576 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1577 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1578 must have set trigger_reverse to 1.
1579 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1581 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).
1583 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1584 "angle" determines the destination angle for opening. negative values reverse the direction.
1585 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1586 "health" if set, door must be shot open
1587 "speed" movement speed (100 default)
1588 "wait" wait before returning (3 default, -1 = never return)
1589 "dmg" damage to inflict when blocked (2 default)
1596 FIXME: only one sound set available at the time being
1599 void door_rotating_reset()
1601 self.angles = self.pos1;
1602 self.avelocity = '0 0 0';
1603 self.state = STATE_BOTTOM;
1604 self.think = func_null;
1608 void door_rotating_init_startopen()
1610 self.angles = self.movedir;
1611 self.pos2 = '0 0 0';
1612 self.pos1 = self.movedir;
1616 void spawnfunc_func_door_rotating()
1619 //if (!self.deathtype) // map makers can override this
1620 // self.deathtype = " got in the way";
1622 // I abuse "movedir" for denoting the axis for now
1623 if (self.spawnflags & 64) // X (untested)
1624 self.movedir = '0 0 1';
1625 else if (self.spawnflags & 128) // Y (untested)
1626 self.movedir = '1 0 0';
1628 self.movedir = '0 1 0';
1630 if (self.angles.y ==0) self.angles.y = 90;
1632 self.movedir = self.movedir * self.angles.y;
1633 self.angles = '0 0 0';
1635 self.max_health = self.health;
1636 self.avelocity = self.movedir;
1637 if (!InitMovingBrushTrigger())
1639 self.velocity = '0 0 0';
1640 //self.effects |= EF_LOWPRECISION;
1641 self.classname = "door_rotating";
1643 self.blocked = door_blocked;
1644 self.use = door_use;
1646 if(self.spawnflags & 8)
1649 if(self.dmg && (self.message == ""))
1650 self.message = "was squished";
1651 if(self.dmg && (self.message2 == ""))
1652 self.message2 = "was squished by";
1654 if (self.sounds > 0)
1656 precache_sound ("plats/medplat1.wav");
1657 precache_sound ("plats/medplat2.wav");
1658 self.noise2 = "plats/medplat1.wav";
1659 self.noise1 = "plats/medplat2.wav";
1666 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1668 self.pos1 = '0 0 0';
1669 self.pos2 = self.movedir;
1671 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1672 // but spawn in the open position
1673 if (self.spawnflags & DOOR_START_OPEN)
1674 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1676 self.state = STATE_BOTTOM;
1680 self.takedamage = DAMAGE_YES;
1681 self.event_damage = door_damage;
1687 self.touch = door_touch;
1689 // LinkDoors can't be done until all of the doors have been spawned, so
1690 // the sizes can be detected properly.
1691 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1693 self.reset = door_rotating_reset;
1697 =============================================================================
1701 =============================================================================
1704 void() fd_secret_move1;
1705 void() fd_secret_move2;
1706 void() fd_secret_move3;
1707 void() fd_secret_move4;
1708 void() fd_secret_move5;
1709 void() fd_secret_move6;
1710 void() fd_secret_done;
1712 const float SECRET_OPEN_ONCE = 1; // stays open
1713 const float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1714 const float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1715 const float SECRET_NO_SHOOT = 8; // only opened by trigger
1716 const float SECRET_YES_SHOOT = 16; // shootable even if targeted
1718 void fd_secret_use()
1721 string message_save;
1723 self.health = 10000;
1724 self.bot_attack = true;
1726 // exit if still moving around...
1727 if (self.origin != self.oldorigin)
1730 message_save = self.message;
1731 self.message = ""; // no more message
1732 SUB_UseTargets(); // fire all targets / killtargets
1733 self.message = message_save;
1735 self.velocity = '0 0 0';
1737 // Make a sound, wait a little...
1739 if (self.noise1 != "")
1740 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1741 self.nextthink = self.ltime + 0.1;
1743 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1744 makevectors(self.mangle);
1748 if (self.spawnflags & SECRET_1ST_DOWN)
1749 self.t_width = fabs(v_up * self.size);
1751 self.t_width = fabs(v_right * self.size);
1755 self.t_length = fabs(v_forward * self.size);
1757 if (self.spawnflags & SECRET_1ST_DOWN)
1758 self.dest1 = self.origin - v_up * self.t_width;
1760 self.dest1 = self.origin + v_right * (self.t_width * temp);
1762 self.dest2 = self.dest1 + v_forward * self.t_length;
1763 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
1764 if (self.noise2 != "")
1765 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1768 void fd_secret_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1773 // Wait after first movement...
1774 void fd_secret_move1()
1776 self.nextthink = self.ltime + 1.0;
1777 self.think = fd_secret_move2;
1778 if (self.noise3 != "")
1779 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1782 // Start moving sideways w/sound...
1783 void fd_secret_move2()
1785 if (self.noise2 != "")
1786 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1787 SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
1790 // Wait here until time to go back...
1791 void fd_secret_move3()
1793 if (self.noise3 != "")
1794 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1795 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1797 self.nextthink = self.ltime + self.wait;
1798 self.think = fd_secret_move4;
1803 void fd_secret_move4()
1805 if (self.noise2 != "")
1806 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1807 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
1811 void fd_secret_move5()
1813 self.nextthink = self.ltime + 1.0;
1814 self.think = fd_secret_move6;
1815 if (self.noise3 != "")
1816 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1819 void fd_secret_move6()
1821 if (self.noise2 != "")
1822 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1823 SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
1826 void fd_secret_done()
1828 if (self.spawnflags&SECRET_YES_SHOOT)
1830 self.health = 10000;
1831 self.takedamage = DAMAGE_YES;
1832 //self.th_pain = fd_secret_use;
1834 if (self.noise3 != "")
1835 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1838 void secret_blocked()
1840 if (time < self.attack_finished_single)
1842 self.attack_finished_single = time + 0.5;
1843 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1855 if (!other.iscreature)
1857 if (self.attack_finished_single > time)
1860 self.attack_finished_single = time + 2;
1864 if (IS_CLIENT(other))
1865 centerprint(other, self.message);
1866 play2(other, "misc/talk.wav");
1872 if (self.spawnflags&SECRET_YES_SHOOT)
1874 self.health = 10000;
1875 self.takedamage = DAMAGE_YES;
1877 setorigin(self, self.oldorigin);
1878 self.think = func_null;
1882 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1883 Basic secret door. Slides back, then to the side. Angle determines direction.
1884 wait = # of seconds before coming back
1885 1st_left = 1st move is left of arrow
1886 1st_down = 1st move is down from arrow
1887 always_shoot = even if targeted, keep shootable
1888 t_width = override WIDTH to move back (or height if going down)
1889 t_length = override LENGTH to move sideways
1890 "dmg" damage to inflict when blocked (2 default)
1892 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1899 void spawnfunc_func_door_secret()
1901 /*if (!self.deathtype) // map makers can override this
1902 self.deathtype = " got in the way";*/
1908 self.mangle = self.angles;
1909 self.angles = '0 0 0';
1910 self.classname = "door";
1911 if (!InitMovingBrushTrigger())
1913 self.effects |= EF_LOWPRECISION;
1915 self.touch = secret_touch;
1916 self.blocked = secret_blocked;
1918 self.use = fd_secret_use;
1923 self.spawnflags |= SECRET_YES_SHOOT;
1925 if(self.spawnflags&SECRET_YES_SHOOT)
1927 self.health = 10000;
1928 self.takedamage = DAMAGE_YES;
1929 self.event_damage = fd_secret_damage;
1931 self.oldorigin = self.origin;
1933 self.wait = 5; // 5 seconds before closing
1935 self.reset = secret_reset;
1939 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1940 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1941 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
1942 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1943 height: amplitude modifier (default 32)
1944 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1945 noise: path/name of looping .wav file to play.
1946 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1950 void func_fourier_controller_think()
1955 self.nextthink = time + 0.1;
1956 if(self.owner.active != ACTIVE_ACTIVE)
1958 self.owner.velocity = '0 0 0';
1963 n = floor((tokenize_console(self.owner.netname)) / 5);
1964 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1966 v = self.owner.destvec;
1968 for(i = 0; i < n; ++i)
1970 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1971 v = v + ('1 0 0' * stof(argv(i*5+2)) + '0 1 0' * stof(argv(i*5+3)) + '0 0 1' * stof(argv(i*5+4))) * self.owner.height * v_forward.y;
1974 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1975 // * 10 so it will arrive in 0.1 sec
1976 self.owner.velocity = (v - self.owner.origin) * 10;
1979 void spawnfunc_func_fourier()
1982 if (self.noise != "")
1984 precache_sound(self.noise);
1985 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
1992 self.destvec = self.origin;
1993 self.cnt = 360 / self.speed;
1995 self.blocked = generic_plat_blocked;
1996 if(self.dmg && (self.message == ""))
1997 self.message = " was squished";
1998 if(self.dmg && (self.message2 == ""))
1999 self.message2 = "was squished by";
2000 if(self.dmg && (!self.dmgtime))
2001 self.dmgtime = 0.25;
2002 self.dmgtime2 = time;
2004 if(self.netname == "")
2005 self.netname = "1 0 0 0 1";
2007 if (!InitMovingBrushTrigger())
2010 self.active = ACTIVE_ACTIVE;
2012 // wait for targets to spawn
2013 controller = spawn();
2014 controller.classname = "func_fourier_controller";
2015 controller.owner = self;
2016 controller.nextthink = time + 1;
2017 controller.think = func_fourier_controller_think;
2018 self.nextthink = self.ltime + 999999999;
2019 self.think = SUB_NullThink; // for PushMove
2021 // Savage: Reduce bandwith, critical on e.g. nexdm02
2022 self.effects |= EF_LOWPRECISION;
2024 // TODO make a reset function for this one
2027 // reusing some fields havocbots declared
2028 .entity wp00, wp01, wp02, wp03;
2030 .float targetfactor, target2factor, target3factor, target4factor;
2031 .vector targetnormal, target2normal, target3normal, target4normal;
2033 vector func_vectormamamam_origin(entity o, float t)
2045 p = e.origin + t * e.velocity;
2047 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
2049 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
2055 p = e.origin + t * e.velocity;
2057 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
2059 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
2065 p = e.origin + t * e.velocity;
2067 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
2069 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
2075 p = e.origin + t * e.velocity;
2077 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
2079 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
2085 void func_vectormamamam_controller_think()
2087 self.nextthink = time + 0.1;
2089 if(self.owner.active != ACTIVE_ACTIVE)
2091 self.owner.velocity = '0 0 0';
2095 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
2096 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
2099 void func_vectormamamam_findtarget()
2101 if(self.target != "")
2102 self.wp00 = find(world, targetname, self.target);
2104 if(self.target2 != "")
2105 self.wp01 = find(world, targetname, self.target2);
2107 if(self.target3 != "")
2108 self.wp02 = find(world, targetname, self.target3);
2110 if(self.target4 != "")
2111 self.wp03 = find(world, targetname, self.target4);
2113 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
2114 objerror("No reference entity found, so there is nothing to move. Aborting.");
2116 self.destvec = self.origin - func_vectormamamam_origin(self, 0);
2119 controller = spawn();
2120 controller.classname = "func_vectormamamam_controller";
2121 controller.owner = self;
2122 controller.nextthink = time + 1;
2123 controller.think = func_vectormamamam_controller_think;
2126 void spawnfunc_func_vectormamamam()
2128 if (self.noise != "")
2130 precache_sound(self.noise);
2131 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
2134 if(!self.targetfactor)
2135 self.targetfactor = 1;
2137 if(!self.target2factor)
2138 self.target2factor = 1;
2140 if(!self.target3factor)
2141 self.target3factor = 1;
2143 if(!self.target4factor)
2144 self.target4factor = 1;
2146 if(vlen(self.targetnormal))
2147 self.targetnormal = normalize(self.targetnormal);
2149 if(vlen(self.target2normal))
2150 self.target2normal = normalize(self.target2normal);
2152 if(vlen(self.target3normal))
2153 self.target3normal = normalize(self.target3normal);
2155 if(vlen(self.target4normal))
2156 self.target4normal = normalize(self.target4normal);
2158 self.blocked = generic_plat_blocked;
2159 if(self.dmg && (self.message == ""))
2160 self.message = " was squished";
2161 if(self.dmg && (self.message == ""))
2162 self.message2 = "was squished by";
2163 if(self.dmg && (!self.dmgtime))
2164 self.dmgtime = 0.25;
2165 self.dmgtime2 = time;
2167 if(self.netname == "")
2168 self.netname = "1 0 0 0 1";
2170 if (!InitMovingBrushTrigger())
2173 // wait for targets to spawn
2174 self.nextthink = self.ltime + 999999999;
2175 self.think = SUB_NullThink; // for PushMove
2177 // Savage: Reduce bandwith, critical on e.g. nexdm02
2178 self.effects |= EF_LOWPRECISION;
2180 self.active = ACTIVE_ACTIVE;
2182 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
2185 void conveyor_think()
2189 // set myself as current conveyor where possible
2190 for(e = world; (e = findentity(e, conveyor, self)); )
2195 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2196 if(!e.conveyor.state)
2199 vector emin = e.absmin;
2200 vector emax = e.absmax;
2201 if(self.solid == SOLID_BSP)
2206 if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2207 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2211 for(e = world; (e = findentity(e, conveyor, self)); )
2213 if(IS_CLIENT(e)) // doing it via velocity has quite some advantages
2214 continue; // done in SV_PlayerPhysics
2216 setorigin(e, e.origin + self.movedir * sys_frametime);
2217 move_out_of_solid(e);
2218 UpdateCSQCProjectile(e);
2220 // stupid conveyor code
2221 tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2222 if(trace_fraction > 0)
2223 setorigin(e, trace_endpos);
2228 self.nextthink = time;
2233 self.state = !self.state;
2236 void conveyor_reset()
2238 self.state = (self.spawnflags & 1);
2241 void conveyor_init()
2245 self.movedir = self.movedir * self.speed;
2246 self.think = conveyor_think;
2247 self.nextthink = time;
2250 self.use = conveyor_use;
2251 self.reset = conveyor_reset;
2258 void spawnfunc_trigger_conveyor()
2265 void spawnfunc_func_conveyor()
2268 InitMovingBrushTrigger();
2269 self.movetype = MOVETYPE_NONE;