5 #include "command/common.qh"
10 #include "../common/constants.qh"
11 #include "../common/deathtypes.qh"
12 #include "../common/notifications.qh"
13 #include "../common/util.qh"
15 #include "../common/weapons/all.qh"
17 #include "../csqcmodellib/sv_model.qh"
19 #include "../warpzonelib/common.qh"
20 #include "../warpzonelib/util_server.qh"
25 void generic_plat_blocked()
27 if(self.dmg && other.takedamage != DAMAGE_NO) {
28 if(self.dmgtime2 < time) {
29 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
30 self.dmgtime2 = time + self.dmgtime;
33 // Gib dead/dying stuff
34 if(other.deadflag != DEAD_NO)
35 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
40 .entity trigger_field;
42 void() plat_center_touch;
43 void() plat_outside_touch;
44 void() plat_trigger_use;
48 const float PLAT_LOW_TRIGGER = 1;
50 void plat_spawn_inside_trigger()
56 trigger.touch = plat_center_touch;
57 trigger.movetype = MOVETYPE_NONE;
58 trigger.solid = SOLID_TRIGGER;
61 tmin = self.absmin + '25 25 0';
62 tmax = self.absmax - '25 25 -8';
63 tmin.z = tmax.z - (self.pos1_z - self.pos2_z + 8);
64 if (self.spawnflags & PLAT_LOW_TRIGGER)
67 if (self.size.x <= 50)
69 tmin.x = (self.mins.x + self.maxs.x) / 2;
72 if (self.size.y <= 50)
74 tmin.y = (self.mins.y + self.maxs.y) / 2;
82 setsize (trigger, tmin, tmax);
86 // otherwise, something is fishy...
88 objerror("plat_spawn_inside_trigger: platform has odd size or lip, can't spawn");
93 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
95 self.think = plat_go_down;
96 self.nextthink = self.ltime + 3;
99 void plat_hit_bottom()
101 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
107 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
109 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, plat_hit_bottom);
114 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
116 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, plat_hit_top);
119 void plat_center_touch()
121 if (!other.iscreature)
124 if (other.health <= 0)
130 else if (self.state == 1)
131 self.nextthink = self.ltime + 1; // delay going down
134 void plat_outside_touch()
136 if (!other.iscreature)
139 if (other.health <= 0)
147 void plat_trigger_use()
150 return; // already activated
157 if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
158 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
160 if((self.dmg) && (other.takedamage != DAMAGE_NO)) { // Shall we bite?
161 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
162 // Gib dead/dying stuff
163 if(other.deadflag != DEAD_NO)
164 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
169 else if (self.state == 3)
171 // when in other states, then the plat_crush event came delayed after
172 // plat state already had changed
173 // this isn't a bug per se!
179 self.use = func_null;
181 objerror ("plat_use: not in up state");
185 .string sound1, sound2;
191 setorigin (self, self.pos1);
197 setorigin (self, self.pos2);
199 self.use = plat_trigger_use;
203 .float platmovetype_start_default, platmovetype_end_default;
204 float set_platmovetype(entity e, string s)
206 // sets platmovetype_start and platmovetype_end based on a string consisting of two values
209 n = tokenize_console(s);
211 e.platmovetype_start = stof(argv(0));
213 e.platmovetype_start = 0;
216 e.platmovetype_end = stof(argv(1));
218 e.platmovetype_end = e.platmovetype_start;
221 if(argv(2) == "force")
222 return true; // no checking, return immediately
224 if(!cubic_speedfunc_is_sane(e.platmovetype_start, e.platmovetype_end))
226 objerror("Invalid platform move type; platform would go in reverse, which is not allowed.");
233 void spawnfunc_path_corner()
235 // setup values for overriding train movement
236 // if a second value does not exist, both start and end speeds are the single value specified
237 if(!set_platmovetype(self, self.platmovetype))
240 void spawnfunc_func_plat()
242 if (self.sounds == 0)
245 if(self.spawnflags & 4)
248 if(self.dmg && (self.message == ""))
249 self.message = "was squished";
250 if(self.dmg && (self.message2 == ""))
251 self.message2 = "was squished by";
253 if (self.sounds == 1)
255 precache_sound ("plats/plat1.wav");
256 precache_sound ("plats/plat2.wav");
257 self.noise = "plats/plat1.wav";
258 self.noise1 = "plats/plat2.wav";
261 if (self.sounds == 2)
263 precache_sound ("plats/medplat1.wav");
264 precache_sound ("plats/medplat2.wav");
265 self.noise = "plats/medplat1.wav";
266 self.noise1 = "plats/medplat2.wav";
271 precache_sound (self.sound1);
272 self.noise = self.sound1;
276 precache_sound (self.sound2);
277 self.noise1 = self.sound2;
280 self.mangle = self.angles;
281 self.angles = '0 0 0';
283 self.classname = "plat";
284 if (!InitMovingBrushTrigger())
286 self.effects |= EF_LOWPRECISION;
287 setsize (self, self.mins , self.maxs);
289 self.blocked = plat_crush;
296 self.height = self.size.z - self.lip;
298 self.pos1 = self.origin;
299 self.pos2 = self.origin;
300 self.pos2_z = self.origin.z - self.height;
302 self.reset = plat_reset;
305 plat_spawn_inside_trigger (); // the "start moving" trigger
308 .float train_wait_turning;
319 // if turning is enabled, the train will turn toward the next point while waiting
320 if(self.platmovetype_turn && !self.train_wait_turning)
324 targ = find(world, targetname, self.target);
325 if((self.spawnflags & 1) && targ.curvetarget)
326 cp = find(world, targetname, targ.curvetarget);
330 if(cp) // bezier curves movement
331 ang = cp.origin - (self.origin - self.view_ofs); // use the origin of the control point of the next path_corner
332 else // linear movement
333 ang = targ.origin - (self.origin - self.view_ofs); // use the origin of the next path_corner
334 ang = vectoangles(ang);
335 ang.x = -ang.x; // flip up / down orientation
337 if(self.wait > 0) // slow turning
338 SUB_CalcAngleMove(ang, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
339 else // instant turning
340 SUB_CalcAngleMove(ang, TSPEED_TIME, 0.0000001, train_wait);
341 self.train_wait_turning = true;
346 stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
348 if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
350 self.train_wait_turning = false;
355 self.think = train_next;
356 self.nextthink = self.ltime + self.wait;
362 entity targ, cp = world;
363 vector cp_org = '0 0 0';
365 targ = find(world, targetname, self.target);
366 self.target = targ.target;
367 if (self.spawnflags & 1)
371 cp = find(world, targetname, targ.curvetarget); // get its second target (the control point)
372 cp_org = cp.origin - self.view_ofs; // no control point found, assume a straight line to the destination
375 if (self.target == "")
376 objerror("train_next: no next target");
377 self.wait = targ.wait;
381 if(targ.platmovetype)
383 // this path_corner contains a movetype overrider, apply it
384 self.platmovetype_start = targ.platmovetype_start;
385 self.platmovetype_end = targ.platmovetype_end;
389 // this path_corner doesn't contain a movetype overrider, use the train's defaults
390 self.platmovetype_start = self.platmovetype_start_default;
391 self.platmovetype_end = self.platmovetype_end_default;
397 SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
399 SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
404 SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
406 SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
410 sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
413 void func_train_find()
416 targ = find(world, targetname, self.target);
417 self.target = targ.target;
418 if (self.target == "")
419 objerror("func_train_find: no next target");
420 setorigin(self, targ.origin - self.view_ofs);
421 self.nextthink = self.ltime + 1;
422 self.think = train_next;
425 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
426 Ridable platform, targets spawnfunc_path_corner path to follow.
427 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
428 target : targetname of first spawnfunc_path_corner (starts here)
430 void spawnfunc_func_train()
432 if (self.noise != "")
433 precache_sound(self.noise);
435 if (self.target == "")
436 objerror("func_train without a target");
440 if (!InitMovingBrushTrigger())
442 self.effects |= EF_LOWPRECISION;
444 if (self.spawnflags & 2)
446 self.platmovetype_turn = true;
447 self.view_ofs = '0 0 0'; // don't offset a rotating train, origin works differently now
450 self.view_ofs = self.mins;
452 // wait for targets to spawn
453 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
455 self.blocked = generic_plat_blocked;
456 if(self.dmg && (self.message == ""))
457 self.message = " was squished";
458 if(self.dmg && (self.message2 == ""))
459 self.message2 = "was squished by";
460 if(self.dmg && (!self.dmgtime))
462 self.dmgtime2 = time;
464 if(!set_platmovetype(self, self.platmovetype))
466 self.platmovetype_start_default = self.platmovetype_start;
467 self.platmovetype_end_default = self.platmovetype_end;
469 // TODO make a reset function for this one
472 void func_rotating_setactive(float astate)
475 if (astate == ACTIVE_TOGGLE)
477 if(self.active == ACTIVE_ACTIVE)
478 self.active = ACTIVE_NOT;
480 self.active = ACTIVE_ACTIVE;
483 self.active = astate;
485 if(self.active == ACTIVE_NOT)
486 self.avelocity = '0 0 0';
488 self.avelocity = self.pos1;
491 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
492 Brush model that spins in place on one axis (default Z).
493 speed : speed to rotate (in degrees per second)
494 noise : path/name of looping .wav file to play.
495 dmg : Do this mutch dmg every .dmgtime intervall when blocked
499 void spawnfunc_func_rotating()
501 if (self.noise != "")
503 precache_sound(self.noise);
504 ambientsound(self.origin, self.noise, VOL_BASE, ATTEN_IDLE);
507 self.active = ACTIVE_ACTIVE;
508 self.setactive = func_rotating_setactive;
512 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
513 if (self.spawnflags & 4) // X (untested)
514 self.avelocity = '0 0 1' * self.speed;
515 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
516 else if (self.spawnflags & 8) // Y (untested)
517 self.avelocity = '1 0 0' * self.speed;
518 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
520 self.avelocity = '0 1 0' * self.speed;
522 self.pos1 = self.avelocity;
524 if(self.dmg && (self.message == ""))
525 self.message = " was squished";
526 if(self.dmg && (self.message2 == ""))
527 self.message2 = "was squished by";
530 if(self.dmg && (!self.dmgtime))
533 self.dmgtime2 = time;
535 if (!InitMovingBrushTrigger())
537 // no EF_LOWPRECISION here, as rounding angles is bad
539 self.blocked = generic_plat_blocked;
541 // wait for targets to spawn
542 self.nextthink = self.ltime + 999999999;
543 self.think = SUB_NullThink; // for PushMove
545 // TODO make a reset function for this one
549 void func_bobbing_controller_think()
552 self.nextthink = time + 0.1;
554 if(self.owner.active != ACTIVE_ACTIVE)
556 self.owner.velocity = '0 0 0';
560 // calculate sinewave using makevectors
561 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
562 v = self.owner.destvec + self.owner.movedir * v_forward.y;
563 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
564 // * 10 so it will arrive in 0.1 sec
565 self.owner.velocity = (v - self.owner.origin) * 10;
568 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
569 Brush model that moves back and forth on one axis (default Z).
570 speed : how long one cycle takes in seconds (default 4)
571 height : how far the cycle moves (default 32)
572 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
573 noise : path/name of looping .wav file to play.
574 dmg : Do this mutch dmg every .dmgtime intervall when blocked
577 void spawnfunc_func_bobbing()
580 if (self.noise != "")
582 precache_sound(self.noise);
583 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
589 // center of bobbing motion
590 self.destvec = self.origin;
591 // time scale to get degrees
592 self.cnt = 360 / self.speed;
594 self.active = ACTIVE_ACTIVE;
596 // damage when blocked
597 self.blocked = generic_plat_blocked;
598 if(self.dmg && (self.message == ""))
599 self.message = " was squished";
600 if(self.dmg && (self.message2 == ""))
601 self.message2 = "was squished by";
602 if(self.dmg && (!self.dmgtime))
604 self.dmgtime2 = time;
607 if (self.spawnflags & 1) // X
608 self.movedir = '1 0 0' * self.height;
609 else if (self.spawnflags & 2) // Y
610 self.movedir = '0 1 0' * self.height;
612 self.movedir = '0 0 1' * self.height;
614 if (!InitMovingBrushTrigger())
617 // wait for targets to spawn
618 controller = spawn();
619 controller.classname = "func_bobbing_controller";
620 controller.owner = self;
621 controller.nextthink = time + 1;
622 controller.think = func_bobbing_controller_think;
623 self.nextthink = self.ltime + 999999999;
624 self.think = SUB_NullThink; // for PushMove
626 // Savage: Reduce bandwith, critical on e.g. nexdm02
627 self.effects |= EF_LOWPRECISION;
629 // TODO make a reset function for this one
633 void func_pendulum_controller_think()
636 self.nextthink = time + 0.1;
638 if (!(self.owner.active == ACTIVE_ACTIVE))
640 self.owner.avelocity_x = 0;
644 // calculate sinewave using makevectors
645 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
646 v = self.owner.speed * v_forward.y + self.cnt;
647 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
649 // * 10 so it will arrive in 0.1 sec
650 self.owner.avelocity_z = (remainder(v - self.owner.angles.z, 360)) * 10;
654 void spawnfunc_func_pendulum()
657 if (self.noise != "")
659 precache_sound(self.noise);
660 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
663 self.active = ACTIVE_ACTIVE;
665 // keys: angle, speed, phase, noise, freq
669 // not initializing self.dmg to 2, to allow damageless pendulum
671 if(self.dmg && (self.message == ""))
672 self.message = " was squished";
673 if(self.dmg && (self.message2 == ""))
674 self.message2 = "was squished by";
675 if(self.dmg && (!self.dmgtime))
677 self.dmgtime2 = time;
679 self.blocked = generic_plat_blocked;
681 self.avelocity_z = 0.0000001;
682 if (!InitMovingBrushTrigger())
687 // find pendulum length (same formula as Q3A)
688 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins.z))));
691 // copy initial angle
692 self.cnt = self.angles.z;
694 // wait for targets to spawn
695 controller = spawn();
696 controller.classname = "func_pendulum_controller";
697 controller.owner = self;
698 controller.nextthink = time + 1;
699 controller.think = func_pendulum_controller_think;
700 self.nextthink = self.ltime + 999999999;
701 self.think = SUB_NullThink; // for PushMove
703 //self.effects |= EF_LOWPRECISION;
705 // TODO make a reset function for this one
708 // button and multiple button
711 void() button_return;
715 self.state = STATE_TOP;
716 self.nextthink = self.ltime + self.wait;
717 self.think = button_return;
718 activator = self.enemy;
720 self.frame = 1; // use alternate textures
725 self.state = STATE_BOTTOM;
730 self.state = STATE_DOWN;
731 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
732 self.frame = 0; // use normal textures
734 self.takedamage = DAMAGE_YES; // can be shot again
738 void button_blocked()
740 // do nothing, just don't come all the way back out
746 self.health = self.max_health;
747 self.takedamage = DAMAGE_NO; // will be reset upon return
749 if (self.state == STATE_UP || self.state == STATE_TOP)
752 if (self.noise != "")
753 sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
755 self.state = STATE_UP;
756 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
761 self.health = self.max_health;
762 setorigin(self, self.pos1);
763 self.frame = 0; // use normal textures
764 self.state = STATE_BOTTOM;
766 self.takedamage = DAMAGE_YES; // can be shot again
771 if(self.active != ACTIVE_ACTIVE)
774 self.enemy = activator;
782 if (!other.iscreature)
784 if(other.velocity * self.movedir < 0)
788 self.enemy = other.owner;
792 void button_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
794 if(self.spawnflags & DOOR_NOSPLASH)
795 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
797 self.health = self.health - damage;
798 if (self.health <= 0)
800 self.enemy = damage_attacker;
806 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
807 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.
809 "angle" determines the opening direction
810 "target" all entities with a matching targetname will be used
811 "speed" override the default 40 speed
812 "wait" override the default 1 second wait (-1 = never return)
813 "lip" override the default 4 pixel lip remaining at end of move
814 "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
821 void spawnfunc_func_button()
825 if (!InitMovingBrushTrigger())
827 self.effects |= EF_LOWPRECISION;
829 self.blocked = button_blocked;
830 self.use = button_use;
832 // if (self.health == 0) // all buttons are now shootable
836 self.max_health = self.health;
837 self.event_damage = button_damage;
838 self.takedamage = DAMAGE_YES;
841 self.touch = button_touch;
851 precache_sound(self.noise);
853 self.active = ACTIVE_ACTIVE;
855 self.pos1 = self.origin;
856 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
857 self.flags |= FL_NOTARGET;
863 const float DOOR_START_OPEN = 1;
864 const float DOOR_DONT_LINK = 4;
865 const float DOOR_TOGGLE = 32;
869 Doors are similar to buttons, but can spawn a fat trigger field around them
870 to open without a touch, and they link together to form simultanious
873 Door.owner is the master door. If there is only one door, it points to itself.
874 If multiple doors, all will point to a single one.
876 Door.enemy chains from the master door through all doors linked in the chain.
881 =============================================================================
885 =============================================================================
890 void() door_rotating_go_down;
891 void() door_rotating_go_up;
896 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
897 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
900 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
901 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
903 //Dont chamge direction for dead or dying stuff
904 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
907 if (self.state == STATE_DOWN)
908 if (self.classname == "door")
913 door_rotating_go_up ();
916 if (self.classname == "door")
921 door_rotating_go_down ();
925 //gib dying stuff just to make sure
926 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
927 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
931 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
932 // if a door has a negative wait, it would never come back if blocked,
933 // so let it just squash the object to death real fast
934 /* if (self.wait >= 0)
936 if (self.state == STATE_DOWN)
947 if (self.noise1 != "")
948 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
949 self.state = STATE_TOP;
950 if (self.spawnflags & DOOR_TOGGLE)
951 return; // don't come down automatically
952 if (self.classname == "door")
954 self.think = door_go_down;
957 self.think = door_rotating_go_down;
959 self.nextthink = self.ltime + self.wait;
962 void door_hit_bottom()
964 if (self.noise1 != "")
965 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
966 self.state = STATE_BOTTOM;
971 if (self.noise2 != "")
972 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
975 self.takedamage = DAMAGE_YES;
976 self.health = self.max_health;
979 self.state = STATE_DOWN;
980 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
985 if (self.state == STATE_UP)
986 return; // already going up
988 if (self.state == STATE_TOP)
989 { // reset top wait time
990 self.nextthink = self.ltime + self.wait;
994 if (self.noise2 != "")
995 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
996 self.state = STATE_UP;
997 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
1000 oldmessage = self.message;
1003 self.message = oldmessage;
1009 =============================================================================
1011 ACTIVATION FUNCTIONS
1013 =============================================================================
1016 float door_check_keys(void) {
1017 entity door = self.owner ? self.owner : self;
1023 // this door require a key
1024 // only a player can have a key
1025 if (!IS_PLAYER(other))
1028 if (item_keys_usekey(door, other)) {
1029 // some keys were used
1030 if (other.key_door_messagetime <= time) {
1031 play2(other, "misc/talk.wav");
1032 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
1033 other.key_door_messagetime = time + 2;
1036 // no keys were used
1037 if (other.key_door_messagetime <= time) {
1038 play2(other, "misc/talk.wav");
1039 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
1040 other.key_door_messagetime = time + 2;
1044 if (door.itemkeys) {
1045 // door is now unlocked
1046 play2(other, "misc/talk.wav");
1047 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_UNLOCKED);
1059 if (self.owner != self)
1060 objerror ("door_fire: self.owner != self");
1064 if (self.spawnflags & DOOR_TOGGLE)
1066 if (self.state == STATE_UP || self.state == STATE_TOP)
1071 if (self.classname == "door")
1077 door_rotating_go_down ();
1080 } while ( (self != starte) && (self != world) );
1086 // trigger all paired doors
1090 if (self.classname == "door")
1095 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
1096 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
1098 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
1099 self.pos2 = '0 0 0' - self.pos2;
1101 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
1102 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
1103 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
1105 door_rotating_go_up ();
1109 } while ( (self != starte) && (self != world) );
1118 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
1130 void door_trigger_touch()
1132 if (other.health < 1)
1133 if (!(other.iscreature && other.deadflag == DEAD_NO))
1136 if (time < self.attack_finished_single)
1139 // check if door is locked
1140 if (!door_check_keys())
1143 self.attack_finished_single = time + 1;
1152 void door_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
1155 if(self.spawnflags & DOOR_NOSPLASH)
1156 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
1158 self.health = self.health - damage;
1160 if (self.itemkeys) {
1161 // don't allow opening doors through damage if keys are required
1165 if (self.health <= 0)
1169 self.health = self.max_health;
1170 self.takedamage = DAMAGE_NO; // wil be reset upon return
1186 if (!IS_PLAYER(other))
1188 if (self.owner.attack_finished_single > time)
1191 self.owner.attack_finished_single = time + 2;
1193 if (!(self.owner.dmg) && (self.owner.message != ""))
1195 if (IS_CLIENT(other))
1196 centerprint(other, self.owner.message);
1197 play2(other, "misc/talk.wav");
1202 void door_generic_plat_blocked()
1205 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1206 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1209 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1210 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1212 //Dont chamge direction for dead or dying stuff
1213 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1216 if (self.state == STATE_DOWN)
1217 door_rotating_go_up ();
1219 door_rotating_go_down ();
1222 //gib dying stuff just to make sure
1223 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1224 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1228 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1229 // if a door has a negative wait, it would never come back if blocked,
1230 // so let it just squash the object to death real fast
1231 /* if (self.wait >= 0)
1233 if (self.state == STATE_DOWN)
1234 door_rotating_go_up ();
1236 door_rotating_go_down ();
1242 void door_rotating_hit_top()
1244 if (self.noise1 != "")
1245 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1246 self.state = STATE_TOP;
1247 if (self.spawnflags & DOOR_TOGGLE)
1248 return; // don't come down automatically
1249 self.think = door_rotating_go_down;
1250 self.nextthink = self.ltime + self.wait;
1253 void door_rotating_hit_bottom()
1255 if (self.noise1 != "")
1256 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1257 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1259 self.pos2 = '0 0 0' - self.pos2;
1262 self.state = STATE_BOTTOM;
1265 void door_rotating_go_down()
1267 if (self.noise2 != "")
1268 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1269 if (self.max_health)
1271 self.takedamage = DAMAGE_YES;
1272 self.health = self.max_health;
1275 self.state = STATE_DOWN;
1276 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
1279 void door_rotating_go_up()
1281 if (self.state == STATE_UP)
1282 return; // already going up
1284 if (self.state == STATE_TOP)
1285 { // reset top wait time
1286 self.nextthink = self.ltime + self.wait;
1289 if (self.noise2 != "")
1290 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1291 self.state = STATE_UP;
1292 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
1295 oldmessage = self.message;
1298 self.message = oldmessage;
1305 =============================================================================
1309 =============================================================================
1313 entity spawn_field(vector fmins, vector fmaxs)
1319 trigger.classname = "doortriggerfield";
1320 trigger.movetype = MOVETYPE_NONE;
1321 trigger.solid = SOLID_TRIGGER;
1322 trigger.owner = self;
1323 trigger.touch = door_trigger_touch;
1327 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1332 entity LinkDoors_nextent(entity cur, entity near, entity pass)
1334 while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
1340 float LinkDoors_isconnected(entity e1, entity e2, entity pass)
1343 if (e1.absmin.x > e2.absmax.x + DELTA)
1345 if (e1.absmin.y > e2.absmax.y + DELTA)
1347 if (e1.absmin.z > e2.absmax.z + DELTA)
1349 if (e2.absmin.x > e1.absmax.x + DELTA)
1351 if (e2.absmin.y > e1.absmax.y + DELTA)
1353 if (e2.absmin.z > e1.absmax.z + DELTA)
1368 vector cmins, cmaxs;
1371 return; // already linked by another door
1372 if (self.spawnflags & 4)
1374 self.owner = self.enemy = self;
1382 self.trigger_field = spawn_field(self.absmin, self.absmax);
1384 return; // don't want to link this door
1387 FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
1389 // set owner, and make a loop of the chain
1390 LOG_TRACE("LinkDoors: linking doors:");
1391 for(t = self; ; t = t.enemy)
1393 LOG_TRACE(" ", etos(t));
1395 if(t.enemy == world)
1403 // collect health, targetname, message, size
1404 cmins = self.absmin;
1405 cmaxs = self.absmax;
1406 for(t = self; ; t = t.enemy)
1408 if(t.health && !self.health)
1409 self.health = t.health;
1410 if((t.targetname != "") && (self.targetname == ""))
1411 self.targetname = t.targetname;
1412 if((t.message != "") && (self.message == ""))
1413 self.message = t.message;
1414 if (t.absmin.x < cmins.x)
1415 cmins.x = t.absmin.x;
1416 if (t.absmin.y < cmins.y)
1417 cmins.y = t.absmin.y;
1418 if (t.absmin.z < cmins.z)
1419 cmins.z = t.absmin.z;
1420 if (t.absmax.x > cmaxs.x)
1421 cmaxs.x = t.absmax.x;
1422 if (t.absmax.y > cmaxs.y)
1423 cmaxs.y = t.absmax.y;
1424 if (t.absmax.z > cmaxs.z)
1425 cmaxs.z = t.absmax.z;
1430 // distribute health, targetname, message
1431 for(t = self; t; t = t.enemy)
1433 t.health = self.health;
1434 t.targetname = self.targetname;
1435 t.message = self.message;
1440 // shootable, or triggered doors just needed the owner/enemy links,
1441 // they don't spawn a field
1450 self.trigger_field = spawn_field(cmins, cmaxs);
1454 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
1455 if two doors touch, they are assumed to be connected and operate as a unit.
1457 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1459 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).
1461 GOLD_KEY causes the door to open only if the activator holds a gold key.
1463 SILVER_KEY causes the door to open only if the activator holds a silver key.
1465 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1466 "angle" determines the opening direction
1467 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1468 "health" if set, door must be shot open
1469 "speed" movement speed (100 default)
1470 "wait" wait before returning (3 default, -1 = never return)
1471 "lip" lip remaining at end of move (8 default)
1472 "dmg" damage to inflict when blocked (2 default)
1479 FIXME: only one sound set available at the time being
1483 void door_init_startopen()
1485 setorigin (self, self.pos2);
1486 self.pos2 = self.pos1;
1487 self.pos1 = self.origin;
1492 setorigin(self, self.pos1);
1493 self.velocity = '0 0 0';
1494 self.state = STATE_BOTTOM;
1495 self.think = func_null;
1499 // spawnflags require key (for now only func_door)
1500 const float SPAWNFLAGS_GOLD_KEY = 8;
1501 const float SPAWNFLAGS_SILVER_KEY = 16;
1502 void spawnfunc_func_door()
1504 // Quake 1 keys compatibility
1505 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
1506 self.itemkeys |= ITEM_KEY_BIT(0);
1507 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
1508 self.itemkeys |= ITEM_KEY_BIT(1);
1510 //if (!self.deathtype) // map makers can override this
1511 // self.deathtype = " got in the way";
1514 self.max_health = self.health;
1515 if (!InitMovingBrushTrigger())
1517 self.effects |= EF_LOWPRECISION;
1518 self.classname = "door";
1520 self.blocked = door_blocked;
1521 self.use = door_use;
1523 // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
1524 // if(self.spawnflags & 8)
1525 // self.dmg = 10000;
1527 if(self.dmg && (self.message == ""))
1528 self.message = "was squished";
1529 if(self.dmg && (self.message2 == ""))
1530 self.message2 = "was squished by";
1532 if (self.sounds > 0)
1534 precache_sound ("plats/medplat1.wav");
1535 precache_sound ("plats/medplat2.wav");
1536 self.noise2 = "plats/medplat1.wav";
1537 self.noise1 = "plats/medplat2.wav";
1547 self.pos1 = self.origin;
1548 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1550 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1551 // but spawn in the open position
1552 if (self.spawnflags & DOOR_START_OPEN)
1553 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1555 self.state = STATE_BOTTOM;
1559 self.takedamage = DAMAGE_YES;
1560 self.event_damage = door_damage;
1566 self.touch = door_touch;
1568 // LinkDoors can't be done until all of the doors have been spawned, so
1569 // the sizes can be detected properly.
1570 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1572 self.reset = door_reset;
1575 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1576 if two doors touch, they are assumed to be connected and operate as a unit.
1578 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1580 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1581 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1582 must have set trigger_reverse to 1.
1583 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1585 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).
1587 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1588 "angle" determines the destination angle for opening. negative values reverse the direction.
1589 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1590 "health" if set, door must be shot open
1591 "speed" movement speed (100 default)
1592 "wait" wait before returning (3 default, -1 = never return)
1593 "dmg" damage to inflict when blocked (2 default)
1600 FIXME: only one sound set available at the time being
1603 void door_rotating_reset()
1605 self.angles = self.pos1;
1606 self.avelocity = '0 0 0';
1607 self.state = STATE_BOTTOM;
1608 self.think = func_null;
1612 void door_rotating_init_startopen()
1614 self.angles = self.movedir;
1615 self.pos2 = '0 0 0';
1616 self.pos1 = self.movedir;
1620 void spawnfunc_func_door_rotating()
1623 //if (!self.deathtype) // map makers can override this
1624 // self.deathtype = " got in the way";
1626 // I abuse "movedir" for denoting the axis for now
1627 if (self.spawnflags & 64) // X (untested)
1628 self.movedir = '0 0 1';
1629 else if (self.spawnflags & 128) // Y (untested)
1630 self.movedir = '1 0 0';
1632 self.movedir = '0 1 0';
1634 if (self.angles.y ==0) self.angles_y = 90;
1636 self.movedir = self.movedir * self.angles.y;
1637 self.angles = '0 0 0';
1639 self.max_health = self.health;
1640 self.avelocity = self.movedir;
1641 if (!InitMovingBrushTrigger())
1643 self.velocity = '0 0 0';
1644 //self.effects |= EF_LOWPRECISION;
1645 self.classname = "door_rotating";
1647 self.blocked = door_blocked;
1648 self.use = door_use;
1650 if(self.spawnflags & 8)
1653 if(self.dmg && (self.message == ""))
1654 self.message = "was squished";
1655 if(self.dmg && (self.message2 == ""))
1656 self.message2 = "was squished by";
1658 if (self.sounds > 0)
1660 precache_sound ("plats/medplat1.wav");
1661 precache_sound ("plats/medplat2.wav");
1662 self.noise2 = "plats/medplat1.wav";
1663 self.noise1 = "plats/medplat2.wav";
1670 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1672 self.pos1 = '0 0 0';
1673 self.pos2 = self.movedir;
1675 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1676 // but spawn in the open position
1677 if (self.spawnflags & DOOR_START_OPEN)
1678 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1680 self.state = STATE_BOTTOM;
1684 self.takedamage = DAMAGE_YES;
1685 self.event_damage = door_damage;
1691 self.touch = door_touch;
1693 // LinkDoors can't be done until all of the doors have been spawned, so
1694 // the sizes can be detected properly.
1695 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1697 self.reset = door_rotating_reset;
1701 =============================================================================
1705 =============================================================================
1708 void() fd_secret_move1;
1709 void() fd_secret_move2;
1710 void() fd_secret_move3;
1711 void() fd_secret_move4;
1712 void() fd_secret_move5;
1713 void() fd_secret_move6;
1714 void() fd_secret_done;
1716 const float SECRET_OPEN_ONCE = 1; // stays open
1717 const float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1718 const float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1719 const float SECRET_NO_SHOOT = 8; // only opened by trigger
1720 const float SECRET_YES_SHOOT = 16; // shootable even if targeted
1722 void fd_secret_use()
1725 string message_save;
1727 self.health = 10000;
1728 self.bot_attack = true;
1730 // exit if still moving around...
1731 if (self.origin != self.oldorigin)
1734 message_save = self.message;
1735 self.message = ""; // no more message
1736 SUB_UseTargets(); // fire all targets / killtargets
1737 self.message = message_save;
1739 self.velocity = '0 0 0';
1741 // Make a sound, wait a little...
1743 if (self.noise1 != "")
1744 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1745 self.nextthink = self.ltime + 0.1;
1747 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1748 makevectors(self.mangle);
1752 if (self.spawnflags & SECRET_1ST_DOWN)
1753 self.t_width = fabs(v_up * self.size);
1755 self.t_width = fabs(v_right * self.size);
1759 self.t_length = fabs(v_forward * self.size);
1761 if (self.spawnflags & SECRET_1ST_DOWN)
1762 self.dest1 = self.origin - v_up * self.t_width;
1764 self.dest1 = self.origin + v_right * (self.t_width * temp);
1766 self.dest2 = self.dest1 + v_forward * self.t_length;
1767 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
1768 if (self.noise2 != "")
1769 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1772 void fd_secret_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
1777 // Wait after first movement...
1778 void fd_secret_move1()
1780 self.nextthink = self.ltime + 1.0;
1781 self.think = fd_secret_move2;
1782 if (self.noise3 != "")
1783 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1786 // Start moving sideways w/sound...
1787 void fd_secret_move2()
1789 if (self.noise2 != "")
1790 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1791 SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
1794 // Wait here until time to go back...
1795 void fd_secret_move3()
1797 if (self.noise3 != "")
1798 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1799 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1801 self.nextthink = self.ltime + self.wait;
1802 self.think = fd_secret_move4;
1807 void fd_secret_move4()
1809 if (self.noise2 != "")
1810 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1811 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
1815 void fd_secret_move5()
1817 self.nextthink = self.ltime + 1.0;
1818 self.think = fd_secret_move6;
1819 if (self.noise3 != "")
1820 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1823 void fd_secret_move6()
1825 if (self.noise2 != "")
1826 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1827 SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
1830 void fd_secret_done()
1832 if (self.spawnflags&SECRET_YES_SHOOT)
1834 self.health = 10000;
1835 self.takedamage = DAMAGE_YES;
1836 //self.th_pain = fd_secret_use;
1838 if (self.noise3 != "")
1839 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1842 void secret_blocked()
1844 if (time < self.attack_finished_single)
1846 self.attack_finished_single = time + 0.5;
1847 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1859 if (!other.iscreature)
1861 if (self.attack_finished_single > time)
1864 self.attack_finished_single = time + 2;
1868 if (IS_CLIENT(other))
1869 centerprint(other, self.message);
1870 play2(other, "misc/talk.wav");
1876 if (self.spawnflags&SECRET_YES_SHOOT)
1878 self.health = 10000;
1879 self.takedamage = DAMAGE_YES;
1881 setorigin(self, self.oldorigin);
1882 self.think = func_null;
1886 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1887 Basic secret door. Slides back, then to the side. Angle determines direction.
1888 wait = # of seconds before coming back
1889 1st_left = 1st move is left of arrow
1890 1st_down = 1st move is down from arrow
1891 always_shoot = even if targeted, keep shootable
1892 t_width = override WIDTH to move back (or height if going down)
1893 t_length = override LENGTH to move sideways
1894 "dmg" damage to inflict when blocked (2 default)
1896 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1903 void spawnfunc_func_door_secret()
1905 /*if (!self.deathtype) // map makers can override this
1906 self.deathtype = " got in the way";*/
1912 self.mangle = self.angles;
1913 self.angles = '0 0 0';
1914 self.classname = "door";
1915 if (!InitMovingBrushTrigger())
1917 self.effects |= EF_LOWPRECISION;
1919 self.touch = secret_touch;
1920 self.blocked = secret_blocked;
1922 self.use = fd_secret_use;
1927 self.spawnflags |= SECRET_YES_SHOOT;
1929 if(self.spawnflags&SECRET_YES_SHOOT)
1931 self.health = 10000;
1932 self.takedamage = DAMAGE_YES;
1933 self.event_damage = fd_secret_damage;
1935 self.oldorigin = self.origin;
1937 self.wait = 5; // 5 seconds before closing
1939 self.reset = secret_reset;
1943 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1944 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1945 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
1946 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1947 height: amplitude modifier (default 32)
1948 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1949 noise: path/name of looping .wav file to play.
1950 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1954 void func_fourier_controller_think()
1959 self.nextthink = time + 0.1;
1960 if(self.owner.active != ACTIVE_ACTIVE)
1962 self.owner.velocity = '0 0 0';
1967 n = floor((tokenize_console(self.owner.netname)) / 5);
1968 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1970 v = self.owner.destvec;
1972 for(i = 0; i < n; ++i)
1974 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1975 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;
1978 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1979 // * 10 so it will arrive in 0.1 sec
1980 self.owner.velocity = (v - self.owner.origin) * 10;
1983 void spawnfunc_func_fourier()
1986 if (self.noise != "")
1988 precache_sound(self.noise);
1989 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
1996 self.destvec = self.origin;
1997 self.cnt = 360 / self.speed;
1999 self.blocked = generic_plat_blocked;
2000 if(self.dmg && (self.message == ""))
2001 self.message = " was squished";
2002 if(self.dmg && (self.message2 == ""))
2003 self.message2 = "was squished by";
2004 if(self.dmg && (!self.dmgtime))
2005 self.dmgtime = 0.25;
2006 self.dmgtime2 = time;
2008 if(self.netname == "")
2009 self.netname = "1 0 0 0 1";
2011 if (!InitMovingBrushTrigger())
2014 self.active = ACTIVE_ACTIVE;
2016 // wait for targets to spawn
2017 controller = spawn();
2018 controller.classname = "func_fourier_controller";
2019 controller.owner = self;
2020 controller.nextthink = time + 1;
2021 controller.think = func_fourier_controller_think;
2022 self.nextthink = self.ltime + 999999999;
2023 self.think = SUB_NullThink; // for PushMove
2025 // Savage: Reduce bandwith, critical on e.g. nexdm02
2026 self.effects |= EF_LOWPRECISION;
2028 // TODO make a reset function for this one
2031 // reusing some fields havocbots declared
2032 .entity wp00, wp01, wp02, wp03;
2034 .float targetfactor, target2factor, target3factor, target4factor;
2035 .vector targetnormal, target2normal, target3normal, target4normal;
2037 vector func_vectormamamam_origin(entity o, float t)
2049 p = e.origin + t * e.velocity;
2051 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
2053 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
2059 p = e.origin + t * e.velocity;
2061 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
2063 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
2069 p = e.origin + t * e.velocity;
2071 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
2073 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
2079 p = e.origin + t * e.velocity;
2081 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
2083 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
2089 void func_vectormamamam_controller_think()
2091 self.nextthink = time + 0.1;
2093 if(self.owner.active != ACTIVE_ACTIVE)
2095 self.owner.velocity = '0 0 0';
2099 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
2100 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
2103 void func_vectormamamam_findtarget()
2105 if(self.target != "")
2106 self.wp00 = find(world, targetname, self.target);
2108 if(self.target2 != "")
2109 self.wp01 = find(world, targetname, self.target2);
2111 if(self.target3 != "")
2112 self.wp02 = find(world, targetname, self.target3);
2114 if(self.target4 != "")
2115 self.wp03 = find(world, targetname, self.target4);
2117 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
2118 objerror("No reference entity found, so there is nothing to move. Aborting.");
2120 self.destvec = self.origin - func_vectormamamam_origin(self, 0);
2123 controller = spawn();
2124 controller.classname = "func_vectormamamam_controller";
2125 controller.owner = self;
2126 controller.nextthink = time + 1;
2127 controller.think = func_vectormamamam_controller_think;
2130 void spawnfunc_func_vectormamamam()
2132 if (self.noise != "")
2134 precache_sound(self.noise);
2135 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
2138 if(!self.targetfactor)
2139 self.targetfactor = 1;
2141 if(!self.target2factor)
2142 self.target2factor = 1;
2144 if(!self.target3factor)
2145 self.target3factor = 1;
2147 if(!self.target4factor)
2148 self.target4factor = 1;
2150 if(vlen(self.targetnormal))
2151 self.targetnormal = normalize(self.targetnormal);
2153 if(vlen(self.target2normal))
2154 self.target2normal = normalize(self.target2normal);
2156 if(vlen(self.target3normal))
2157 self.target3normal = normalize(self.target3normal);
2159 if(vlen(self.target4normal))
2160 self.target4normal = normalize(self.target4normal);
2162 self.blocked = generic_plat_blocked;
2163 if(self.dmg && (self.message == ""))
2164 self.message = " was squished";
2165 if(self.dmg && (self.message == ""))
2166 self.message2 = "was squished by";
2167 if(self.dmg && (!self.dmgtime))
2168 self.dmgtime = 0.25;
2169 self.dmgtime2 = time;
2171 if(self.netname == "")
2172 self.netname = "1 0 0 0 1";
2174 if (!InitMovingBrushTrigger())
2177 // wait for targets to spawn
2178 self.nextthink = self.ltime + 999999999;
2179 self.think = SUB_NullThink; // for PushMove
2181 // Savage: Reduce bandwith, critical on e.g. nexdm02
2182 self.effects |= EF_LOWPRECISION;
2184 self.active = ACTIVE_ACTIVE;
2186 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
2189 void conveyor_think()
2193 // set myself as current conveyor where possible
2194 for(e = world; (e = findentity(e, conveyor, self)); )
2199 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2200 if(!e.conveyor.state)
2203 vector emin = e.absmin;
2204 vector emax = e.absmax;
2205 if(self.solid == SOLID_BSP)
2210 if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2211 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2215 for(e = world; (e = findentity(e, conveyor, self)); )
2217 if(IS_CLIENT(e)) // doing it via velocity has quite some advantages
2218 continue; // done in SV_PlayerPhysics
2220 setorigin(e, e.origin + self.movedir * sys_frametime);
2221 move_out_of_solid(e);
2222 UpdateCSQCProjectile(e);
2224 // stupid conveyor code
2225 tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2226 if(trace_fraction > 0)
2227 setorigin(e, trace_endpos);
2232 self.nextthink = time;
2237 self.state = !self.state;
2240 void conveyor_reset()
2242 self.state = (self.spawnflags & 1);
2245 void conveyor_init()
2249 self.movedir = self.movedir * self.speed;
2250 self.think = conveyor_think;
2251 self.nextthink = time;
2254 self.use = conveyor_use;
2255 self.reset = conveyor_reset;
2262 void spawnfunc_trigger_conveyor()
2269 void spawnfunc_func_conveyor()
2272 InitMovingBrushTrigger();
2273 self.movetype = MOVETYPE_NONE;