2 void generic_plat_blocked()
4 if(self.dmg && other.takedamage != DAMAGE_NO) {
5 if(self.dmgtime2 < time) {
6 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
7 self.dmgtime2 = time + self.dmgtime;
10 // Gib dead/dying stuff
11 if(other.deadflag != DEAD_NO)
12 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
17 .entity trigger_field;
19 void() plat_center_touch;
20 void() plat_outside_touch;
21 void() plat_trigger_use;
25 const float PLAT_LOW_TRIGGER = 1;
27 void plat_spawn_inside_trigger()
33 trigger.touch = plat_center_touch;
34 trigger.movetype = MOVETYPE_NONE;
35 trigger.solid = SOLID_TRIGGER;
38 tmin = self.absmin + '25 25 0';
39 tmax = self.absmax - '25 25 -8';
40 tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8);
41 if (self.spawnflags & PLAT_LOW_TRIGGER)
44 if (self.size_x <= 50)
46 tmin_x = (self.mins_x + self.maxs_x) / 2;
49 if (self.size_y <= 50)
51 tmin_y = (self.mins_y + self.maxs_y) / 2;
59 setsize (trigger, tmin, tmax);
63 // otherwise, something is fishy...
65 objerror("plat_spawn_inside_trigger: platform has odd size or lip, can't spawn");
70 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
72 self.think = plat_go_down;
73 self.nextthink = self.ltime + 3;
76 void plat_hit_bottom()
78 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
84 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
86 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, plat_hit_bottom);
91 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
93 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, plat_hit_top);
96 void plat_center_touch()
98 if (!other.iscreature)
101 if (other.health <= 0)
107 else if (self.state == 1)
108 self.nextthink = self.ltime + 1; // delay going down
111 void plat_outside_touch()
113 if (!other.iscreature)
116 if (other.health <= 0)
124 void plat_trigger_use()
127 return; // already activated
134 if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
135 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
137 if((self.dmg) && (other.takedamage != DAMAGE_NO)) { // Shall we bite?
138 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
139 // Gib dead/dying stuff
140 if(other.deadflag != DEAD_NO)
141 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
146 else if (self.state == 3)
148 // when in other states, then the plat_crush event came delayed after
149 // plat state already had changed
150 // this isn't a bug per se!
156 self.use = func_null;
158 objerror ("plat_use: not in up state");
162 .string sound1, sound2;
168 setorigin (self, self.pos1);
174 setorigin (self, self.pos2);
176 self.use = plat_trigger_use;
180 .float platmovetype_start_default, platmovetype_end_default;
181 float set_platmovetype(entity e, string s)
183 // sets platmovetype_start and platmovetype_end based on a string consisting of two values
186 n = tokenize_console(s);
188 e.platmovetype_start = stof(argv(0));
190 e.platmovetype_start = 0;
193 e.platmovetype_end = stof(argv(1));
195 e.platmovetype_end = e.platmovetype_start;
198 if(argv(2) == "force")
199 return TRUE; // no checking, return immediately
201 if(!cubic_speedfunc_is_sane(e.platmovetype_start, e.platmovetype_end))
203 objerror("Invalid platform move type; platform would go in reverse, which is not allowed.");
210 void spawnfunc_path_corner()
212 // setup values for overriding train movement
213 // if a second value does not exist, both start and end speeds are the single value specified
214 if(!set_platmovetype(self, self.platmovetype))
217 void spawnfunc_func_plat()
219 if (self.sounds == 0)
222 if(self.spawnflags & 4)
225 if(self.dmg && (self.message == ""))
226 self.message = "was squished";
227 if(self.dmg && (self.message2 == ""))
228 self.message2 = "was squished by";
230 if (self.sounds == 1)
232 precache_sound ("plats/plat1.wav");
233 precache_sound ("plats/plat2.wav");
234 self.noise = "plats/plat1.wav";
235 self.noise1 = "plats/plat2.wav";
238 if (self.sounds == 2)
240 precache_sound ("plats/medplat1.wav");
241 precache_sound ("plats/medplat2.wav");
242 self.noise = "plats/medplat1.wav";
243 self.noise1 = "plats/medplat2.wav";
248 precache_sound (self.sound1);
249 self.noise = self.sound1;
253 precache_sound (self.sound2);
254 self.noise1 = self.sound2;
257 self.mangle = self.angles;
258 self.angles = '0 0 0';
260 self.classname = "plat";
261 if (!InitMovingBrushTrigger())
263 self.effects |= EF_LOWPRECISION;
264 setsize (self, self.mins , self.maxs);
266 self.blocked = plat_crush;
273 self.height = self.size_z - self.lip;
275 self.pos1 = self.origin;
276 self.pos2 = self.origin;
277 self.pos2_z = self.origin_z - self.height;
279 self.reset = plat_reset;
282 plat_spawn_inside_trigger (); // the "start moving" trigger
285 .float train_wait_turning;
296 // if turning is enabled, the train will turn toward the next point while waiting
297 if(self.platmovetype_turn && !self.train_wait_turning)
301 targ = find(world, targetname, self.target);
302 if((self.spawnflags & 1) && targ.curvetarget)
303 cp = find(world, targetname, targ.curvetarget);
307 if(cp) // bezier curves movement
308 ang = cp.origin - (self.origin - self.view_ofs); // use the origin of the control point of the next path_corner
309 else // linear movement
310 ang = targ.origin - (self.origin - self.view_ofs); // use the origin of the next path_corner
311 ang = vectoangles(ang);
312 ang_x = -ang_x; // flip up / down orientation
314 if(self.wait > 0) // slow turning
315 SUB_CalcAngleMove(ang, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
316 else // instant turning
317 SUB_CalcAngleMove(ang, TSPEED_TIME, 0.0000001, train_wait);
318 self.train_wait_turning = TRUE;
323 stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
325 if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
327 self.train_wait_turning = FALSE;
332 self.think = train_next;
333 self.nextthink = self.ltime + self.wait;
339 entity targ, cp = world;
340 vector cp_org = '0 0 0';
342 targ = find(world, targetname, self.target);
343 self.target = targ.target;
344 if (self.spawnflags & 1)
348 cp = find(world, targetname, targ.curvetarget); // get its second target (the control point)
349 cp_org = cp.origin - self.view_ofs; // no control point found, assume a straight line to the destination
352 if (self.target == "")
353 objerror("train_next: no next target");
354 self.wait = targ.wait;
358 if(targ.platmovetype)
360 // this path_corner contains a movetype overrider, apply it
361 self.platmovetype_start = targ.platmovetype_start;
362 self.platmovetype_end = targ.platmovetype_end;
366 // this path_corner doesn't contain a movetype overrider, use the train's defaults
367 self.platmovetype_start = self.platmovetype_start_default;
368 self.platmovetype_end = self.platmovetype_end_default;
374 SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
376 SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
381 SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
383 SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
387 sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
390 void func_train_find()
393 targ = find(world, targetname, self.target);
394 self.target = targ.target;
395 if (self.target == "")
396 objerror("func_train_find: no next target");
397 setorigin(self, targ.origin - self.view_ofs);
398 self.nextthink = self.ltime + 1;
399 self.think = train_next;
402 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
403 Ridable platform, targets spawnfunc_path_corner path to follow.
404 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
405 target : targetname of first spawnfunc_path_corner (starts here)
407 void spawnfunc_func_train()
409 if (self.noise != "")
410 precache_sound(self.noise);
412 if (self.target == "")
413 objerror("func_train without a target");
417 if (!InitMovingBrushTrigger())
419 self.effects |= EF_LOWPRECISION;
421 if (self.spawnflags & 2)
423 self.platmovetype_turn = TRUE;
424 self.view_ofs = '0 0 0'; // don't offset a rotating train, origin works differently now
427 self.view_ofs = self.mins;
429 // wait for targets to spawn
430 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
432 self.blocked = generic_plat_blocked;
433 if(self.dmg && (self.message == ""))
434 self.message = " was squished";
435 if(self.dmg && (self.message2 == ""))
436 self.message2 = "was squished by";
437 if(self.dmg && (!self.dmgtime))
439 self.dmgtime2 = time;
441 if(!set_platmovetype(self, self.platmovetype))
443 self.platmovetype_start_default = self.platmovetype_start;
444 self.platmovetype_end_default = self.platmovetype_end;
446 // TODO make a reset function for this one
449 void func_rotating_setactive(float astate)
452 if (astate == ACTIVE_TOGGLE)
454 if(self.active == ACTIVE_ACTIVE)
455 self.active = ACTIVE_NOT;
457 self.active = ACTIVE_ACTIVE;
460 self.active = astate;
462 if(self.active == ACTIVE_NOT)
463 self.avelocity = '0 0 0';
465 self.avelocity = self.pos1;
468 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
469 Brush model that spins in place on one axis (default Z).
470 speed : speed to rotate (in degrees per second)
471 noise : path/name of looping .wav file to play.
472 dmg : Do this mutch dmg every .dmgtime intervall when blocked
476 void spawnfunc_func_rotating()
478 if (self.noise != "")
480 precache_sound(self.noise);
481 ambientsound(self.origin, self.noise, VOL_BASE, ATTEN_IDLE);
484 self.active = ACTIVE_ACTIVE;
485 self.setactive = func_rotating_setactive;
489 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
490 if (self.spawnflags & 4) // X (untested)
491 self.avelocity = '0 0 1' * self.speed;
492 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
493 else if (self.spawnflags & 8) // Y (untested)
494 self.avelocity = '1 0 0' * self.speed;
495 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
497 self.avelocity = '0 1 0' * self.speed;
499 self.pos1 = self.avelocity;
501 if(self.dmg && (self.message == ""))
502 self.message = " was squished";
503 if(self.dmg && (self.message2 == ""))
504 self.message2 = "was squished by";
507 if(self.dmg && (!self.dmgtime))
510 self.dmgtime2 = time;
512 if (!InitMovingBrushTrigger())
514 // no EF_LOWPRECISION here, as rounding angles is bad
516 self.blocked = generic_plat_blocked;
518 // wait for targets to spawn
519 self.nextthink = self.ltime + 999999999;
520 self.think = SUB_NullThink; // for PushMove
522 // TODO make a reset function for this one
526 void func_bobbing_controller_think()
529 self.nextthink = time + 0.1;
531 if(self.owner.active != ACTIVE_ACTIVE)
533 self.owner.velocity = '0 0 0';
537 // calculate sinewave using makevectors
538 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
539 v = self.owner.destvec + self.owner.movedir * v_forward_y;
540 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
541 // * 10 so it will arrive in 0.1 sec
542 self.owner.velocity = (v - self.owner.origin) * 10;
545 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
546 Brush model that moves back and forth on one axis (default Z).
547 speed : how long one cycle takes in seconds (default 4)
548 height : how far the cycle moves (default 32)
549 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
550 noise : path/name of looping .wav file to play.
551 dmg : Do this mutch dmg every .dmgtime intervall when blocked
554 void spawnfunc_func_bobbing()
557 if (self.noise != "")
559 precache_sound(self.noise);
560 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
566 // center of bobbing motion
567 self.destvec = self.origin;
568 // time scale to get degrees
569 self.cnt = 360 / self.speed;
571 self.active = ACTIVE_ACTIVE;
573 // damage when blocked
574 self.blocked = generic_plat_blocked;
575 if(self.dmg && (self.message == ""))
576 self.message = " was squished";
577 if(self.dmg && (self.message2 == ""))
578 self.message2 = "was squished by";
579 if(self.dmg && (!self.dmgtime))
581 self.dmgtime2 = time;
584 if (self.spawnflags & 1) // X
585 self.movedir = '1 0 0' * self.height;
586 else if (self.spawnflags & 2) // Y
587 self.movedir = '0 1 0' * self.height;
589 self.movedir = '0 0 1' * self.height;
591 if (!InitMovingBrushTrigger())
594 // wait for targets to spawn
595 controller = spawn();
596 controller.classname = "func_bobbing_controller";
597 controller.owner = self;
598 controller.nextthink = time + 1;
599 controller.think = func_bobbing_controller_think;
600 self.nextthink = self.ltime + 999999999;
601 self.think = SUB_NullThink; // for PushMove
603 // Savage: Reduce bandwith, critical on e.g. nexdm02
604 self.effects |= EF_LOWPRECISION;
606 // TODO make a reset function for this one
610 void func_pendulum_controller_think()
613 self.nextthink = time + 0.1;
615 if (!(self.owner.active == ACTIVE_ACTIVE))
617 self.owner.avelocity_x = 0;
621 // calculate sinewave using makevectors
622 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
623 v = self.owner.speed * v_forward_y + self.cnt;
624 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
626 // * 10 so it will arrive in 0.1 sec
627 self.owner.avelocity_z = (remainder(v - self.owner.angles_z, 360)) * 10;
631 void spawnfunc_func_pendulum()
634 if (self.noise != "")
636 precache_sound(self.noise);
637 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
640 self.active = ACTIVE_ACTIVE;
642 // keys: angle, speed, phase, noise, freq
646 // not initializing self.dmg to 2, to allow damageless pendulum
648 if(self.dmg && (self.message == ""))
649 self.message = " was squished";
650 if(self.dmg && (self.message2 == ""))
651 self.message2 = "was squished by";
652 if(self.dmg && (!self.dmgtime))
654 self.dmgtime2 = time;
656 self.blocked = generic_plat_blocked;
658 self.avelocity_z = 0.0000001;
659 if (!InitMovingBrushTrigger())
664 // find pendulum length (same formula as Q3A)
665 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins_z))));
668 // copy initial angle
669 self.cnt = self.angles_z;
671 // wait for targets to spawn
672 controller = spawn();
673 controller.classname = "func_pendulum_controller";
674 controller.owner = self;
675 controller.nextthink = time + 1;
676 controller.think = func_pendulum_controller_think;
677 self.nextthink = self.ltime + 999999999;
678 self.think = SUB_NullThink; // for PushMove
680 //self.effects |= EF_LOWPRECISION;
682 // TODO make a reset function for this one
685 // button and multiple button
688 void() button_return;
692 self.state = STATE_TOP;
693 self.nextthink = self.ltime + self.wait;
694 self.think = button_return;
695 activator = self.enemy;
697 self.frame = 1; // use alternate textures
702 self.state = STATE_BOTTOM;
707 self.state = STATE_DOWN;
708 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
709 self.frame = 0; // use normal textures
711 self.takedamage = DAMAGE_YES; // can be shot again
715 void button_blocked()
717 // do nothing, just don't come all the way back out
723 self.health = self.max_health;
724 self.takedamage = DAMAGE_NO; // will be reset upon return
726 if (self.state == STATE_UP || self.state == STATE_TOP)
729 if (self.noise != "")
730 sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
732 self.state = STATE_UP;
733 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
738 self.health = self.max_health;
739 setorigin(self, self.pos1);
740 self.frame = 0; // use normal textures
741 self.state = STATE_BOTTOM;
743 self.takedamage = DAMAGE_YES; // can be shot again
748 if(self.active != ACTIVE_ACTIVE)
751 self.enemy = activator;
759 if (!other.iscreature)
761 if(other.velocity * self.movedir < 0)
765 self.enemy = other.owner;
769 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
771 if(self.spawnflags & DOOR_NOSPLASH)
772 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
774 self.health = self.health - damage;
775 if (self.health <= 0)
777 self.enemy = damage_attacker;
783 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
784 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.
786 "angle" determines the opening direction
787 "target" all entities with a matching targetname will be used
788 "speed" override the default 40 speed
789 "wait" override the default 1 second wait (-1 = never return)
790 "lip" override the default 4 pixel lip remaining at end of move
791 "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
798 void spawnfunc_func_button()
802 if (!InitMovingBrushTrigger())
804 self.effects |= EF_LOWPRECISION;
806 self.blocked = button_blocked;
807 self.use = button_use;
809 // if (self.health == 0) // all buttons are now shootable
813 self.max_health = self.health;
814 self.event_damage = button_damage;
815 self.takedamage = DAMAGE_YES;
818 self.touch = button_touch;
828 precache_sound(self.noise);
830 self.active = ACTIVE_ACTIVE;
832 self.pos1 = self.origin;
833 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
834 self.flags |= FL_NOTARGET;
840 const float DOOR_START_OPEN = 1;
841 const float DOOR_DONT_LINK = 4;
842 const float DOOR_TOGGLE = 32;
846 Doors are similar to buttons, but can spawn a fat trigger field around them
847 to open without a touch, and they link together to form simultanious
850 Door.owner is the master door. If there is only one door, it points to itself.
851 If multiple doors, all will point to a single one.
853 Door.enemy chains from the master door through all doors linked in the chain.
858 =============================================================================
862 =============================================================================
867 void() door_rotating_go_down;
868 void() door_rotating_go_up;
873 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
874 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
877 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
878 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
880 //Dont chamge direction for dead or dying stuff
881 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
884 if (self.state == STATE_DOWN)
885 if (self.classname == "door")
890 door_rotating_go_up ();
893 if (self.classname == "door")
898 door_rotating_go_down ();
902 //gib dying stuff just to make sure
903 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
904 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
908 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
909 // if a door has a negative wait, it would never come back if blocked,
910 // so let it just squash the object to death real fast
911 /* if (self.wait >= 0)
913 if (self.state == STATE_DOWN)
924 if (self.noise1 != "")
925 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
926 self.state = STATE_TOP;
927 if (self.spawnflags & DOOR_TOGGLE)
928 return; // don't come down automatically
929 if (self.classname == "door")
931 self.think = door_go_down;
934 self.think = door_rotating_go_down;
936 self.nextthink = self.ltime + self.wait;
939 void door_hit_bottom()
941 if (self.noise1 != "")
942 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
943 self.state = STATE_BOTTOM;
948 if (self.noise2 != "")
949 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
952 self.takedamage = DAMAGE_YES;
953 self.health = self.max_health;
956 self.state = STATE_DOWN;
957 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
962 if (self.state == STATE_UP)
963 return; // already going up
965 if (self.state == STATE_TOP)
966 { // reset top wait time
967 self.nextthink = self.ltime + self.wait;
971 if (self.noise2 != "")
972 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
973 self.state = STATE_UP;
974 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
977 oldmessage = self.message;
980 self.message = oldmessage;
986 =============================================================================
990 =============================================================================
993 float door_check_keys(void) {
994 entity door = self.owner ? self.owner : self;
1000 // this door require a key
1001 // only a player can have a key
1002 if (!IS_PLAYER(other))
1005 if (item_keys_usekey(door, other)) {
1006 // some keys were used
1007 if (other.key_door_messagetime <= time) {
1008 play2(other, "misc/talk.wav");
1009 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
1010 other.key_door_messagetime = time + 2;
1013 // no keys were used
1014 if (other.key_door_messagetime <= time) {
1015 play2(other, "misc/talk.wav");
1016 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
1017 other.key_door_messagetime = time + 2;
1021 if (door.itemkeys) {
1022 // door is now unlocked
1023 play2(other, "misc/talk.wav");
1024 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_UNLOCKED);
1036 if (self.owner != self)
1037 objerror ("door_fire: self.owner != self");
1041 if (self.spawnflags & DOOR_TOGGLE)
1043 if (self.state == STATE_UP || self.state == STATE_TOP)
1048 if (self.classname == "door")
1054 door_rotating_go_down ();
1057 } while ( (self != starte) && (self != world) );
1063 // trigger all paired doors
1067 if (self.classname == "door")
1072 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
1073 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
1075 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
1076 self.pos2 = '0 0 0' - self.pos2;
1078 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
1079 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
1080 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
1082 door_rotating_go_up ();
1086 } while ( (self != starte) && (self != world) );
1095 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
1107 void door_trigger_touch()
1109 if (other.health < 1)
1110 if (!(other.iscreature && other.deadflag == DEAD_NO))
1113 if (time < self.attack_finished_single)
1116 // check if door is locked
1117 if (!door_check_keys())
1120 self.attack_finished_single = time + 1;
1129 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1132 if(self.spawnflags & DOOR_NOSPLASH)
1133 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
1135 self.health = self.health - damage;
1137 if (self.itemkeys) {
1138 // don't allow opening doors through damage if keys are required
1142 if (self.health <= 0)
1146 self.health = self.max_health;
1147 self.takedamage = DAMAGE_NO; // wil be reset upon return
1163 if (!IS_PLAYER(other))
1165 if (self.owner.attack_finished_single > time)
1168 self.owner.attack_finished_single = time + 2;
1170 if (!(self.owner.dmg) && (self.owner.message != ""))
1172 if (IS_CLIENT(other))
1173 centerprint(other, self.owner.message);
1174 play2(other, "misc/talk.wav");
1179 void door_generic_plat_blocked()
1182 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1183 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1186 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1187 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1189 //Dont chamge direction for dead or dying stuff
1190 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1193 if (self.state == STATE_DOWN)
1194 door_rotating_go_up ();
1196 door_rotating_go_down ();
1199 //gib dying stuff just to make sure
1200 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1201 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1205 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1206 // if a door has a negative wait, it would never come back if blocked,
1207 // so let it just squash the object to death real fast
1208 /* if (self.wait >= 0)
1210 if (self.state == STATE_DOWN)
1211 door_rotating_go_up ();
1213 door_rotating_go_down ();
1219 void door_rotating_hit_top()
1221 if (self.noise1 != "")
1222 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1223 self.state = STATE_TOP;
1224 if (self.spawnflags & DOOR_TOGGLE)
1225 return; // don't come down automatically
1226 self.think = door_rotating_go_down;
1227 self.nextthink = self.ltime + self.wait;
1230 void door_rotating_hit_bottom()
1232 if (self.noise1 != "")
1233 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1234 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1236 self.pos2 = '0 0 0' - self.pos2;
1239 self.state = STATE_BOTTOM;
1242 void door_rotating_go_down()
1244 if (self.noise2 != "")
1245 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1246 if (self.max_health)
1248 self.takedamage = DAMAGE_YES;
1249 self.health = self.max_health;
1252 self.state = STATE_DOWN;
1253 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
1256 void door_rotating_go_up()
1258 if (self.state == STATE_UP)
1259 return; // already going up
1261 if (self.state == STATE_TOP)
1262 { // reset top wait time
1263 self.nextthink = self.ltime + self.wait;
1266 if (self.noise2 != "")
1267 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1268 self.state = STATE_UP;
1269 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
1272 oldmessage = self.message;
1275 self.message = oldmessage;
1282 =============================================================================
1286 =============================================================================
1290 entity spawn_field(vector fmins, vector fmaxs)
1296 trigger.classname = "doortriggerfield";
1297 trigger.movetype = MOVETYPE_NONE;
1298 trigger.solid = SOLID_TRIGGER;
1299 trigger.owner = self;
1300 trigger.touch = door_trigger_touch;
1304 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1309 entity LinkDoors_nextent(entity cur, entity near, entity pass)
1311 while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
1317 float LinkDoors_isconnected(entity e1, entity e2, entity pass)
1320 if (e1.absmin_x > e2.absmax_x + DELTA)
1322 if (e1.absmin_y > e2.absmax_y + DELTA)
1324 if (e1.absmin_z > e2.absmax_z + DELTA)
1326 if (e2.absmin_x > e1.absmax_x + DELTA)
1328 if (e2.absmin_y > e1.absmax_y + DELTA)
1330 if (e2.absmin_z > e1.absmax_z + DELTA)
1345 vector cmins, cmaxs;
1348 return; // already linked by another door
1349 if (self.spawnflags & 4)
1351 self.owner = self.enemy = self;
1359 self.trigger_field = spawn_field(self.absmin, self.absmax);
1361 return; // don't want to link this door
1364 FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
1366 // set owner, and make a loop of the chain
1367 dprint("LinkDoors: linking doors:");
1368 for(t = self; ; t = t.enemy)
1370 dprint(" ", etos(t));
1372 if(t.enemy == world)
1380 // collect health, targetname, message, size
1381 cmins = self.absmin;
1382 cmaxs = self.absmax;
1383 for(t = self; ; t = t.enemy)
1385 if(t.health && !self.health)
1386 self.health = t.health;
1387 if((t.targetname != "") && (self.targetname == ""))
1388 self.targetname = t.targetname;
1389 if((t.message != "") && (self.message == ""))
1390 self.message = t.message;
1391 if (t.absmin_x < cmins_x)
1392 cmins_x = t.absmin_x;
1393 if (t.absmin_y < cmins_y)
1394 cmins_y = t.absmin_y;
1395 if (t.absmin_z < cmins_z)
1396 cmins_z = t.absmin_z;
1397 if (t.absmax_x > cmaxs_x)
1398 cmaxs_x = t.absmax_x;
1399 if (t.absmax_y > cmaxs_y)
1400 cmaxs_y = t.absmax_y;
1401 if (t.absmax_z > cmaxs_z)
1402 cmaxs_z = t.absmax_z;
1407 // distribute health, targetname, message
1408 for(t = self; t; t = t.enemy)
1410 t.health = self.health;
1411 t.targetname = self.targetname;
1412 t.message = self.message;
1417 // shootable, or triggered doors just needed the owner/enemy links,
1418 // they don't spawn a field
1427 self.trigger_field = spawn_field(cmins, cmaxs);
1431 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
1432 if two doors touch, they are assumed to be connected and operate as a unit.
1434 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1436 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).
1438 GOLD_KEY causes the door to open only if the activator holds a gold key.
1440 SILVER_KEY causes the door to open only if the activator holds a silver key.
1442 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1443 "angle" determines the opening direction
1444 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1445 "health" if set, door must be shot open
1446 "speed" movement speed (100 default)
1447 "wait" wait before returning (3 default, -1 = never return)
1448 "lip" lip remaining at end of move (8 default)
1449 "dmg" damage to inflict when blocked (2 default)
1456 FIXME: only one sound set available at the time being
1460 void door_init_startopen()
1462 setorigin (self, self.pos2);
1463 self.pos2 = self.pos1;
1464 self.pos1 = self.origin;
1469 setorigin(self, self.pos1);
1470 self.velocity = '0 0 0';
1471 self.state = STATE_BOTTOM;
1472 self.think = func_null;
1476 // spawnflags require key (for now only func_door)
1477 const float SPAWNFLAGS_GOLD_KEY = 8;
1478 const float SPAWNFLAGS_SILVER_KEY = 16;
1479 void spawnfunc_func_door()
1481 // Quake 1 keys compatibility
1482 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
1483 self.itemkeys |= ITEM_KEY_BIT(0);
1484 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
1485 self.itemkeys |= ITEM_KEY_BIT(1);
1487 //if (!self.deathtype) // map makers can override this
1488 // self.deathtype = " got in the way";
1491 self.max_health = self.health;
1492 if (!InitMovingBrushTrigger())
1494 self.effects |= EF_LOWPRECISION;
1495 self.classname = "door";
1497 self.blocked = door_blocked;
1498 self.use = door_use;
1500 // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
1501 // if(self.spawnflags & 8)
1502 // self.dmg = 10000;
1504 if(self.dmg && (self.message == ""))
1505 self.message = "was squished";
1506 if(self.dmg && (self.message2 == ""))
1507 self.message2 = "was squished by";
1509 if (self.sounds > 0)
1511 precache_sound ("plats/medplat1.wav");
1512 precache_sound ("plats/medplat2.wav");
1513 self.noise2 = "plats/medplat1.wav";
1514 self.noise1 = "plats/medplat2.wav";
1524 self.pos1 = self.origin;
1525 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1527 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1528 // but spawn in the open position
1529 if (self.spawnflags & DOOR_START_OPEN)
1530 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1532 self.state = STATE_BOTTOM;
1536 self.takedamage = DAMAGE_YES;
1537 self.event_damage = door_damage;
1543 self.touch = door_touch;
1545 // LinkDoors can't be done until all of the doors have been spawned, so
1546 // the sizes can be detected properly.
1547 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1549 self.reset = door_reset;
1552 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1553 if two doors touch, they are assumed to be connected and operate as a unit.
1555 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1557 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1558 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1559 must have set trigger_reverse to 1.
1560 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1562 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).
1564 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1565 "angle" determines the destination angle for opening. negative values reverse the direction.
1566 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1567 "health" if set, door must be shot open
1568 "speed" movement speed (100 default)
1569 "wait" wait before returning (3 default, -1 = never return)
1570 "dmg" damage to inflict when blocked (2 default)
1577 FIXME: only one sound set available at the time being
1580 void door_rotating_reset()
1582 self.angles = self.pos1;
1583 self.avelocity = '0 0 0';
1584 self.state = STATE_BOTTOM;
1585 self.think = func_null;
1589 void door_rotating_init_startopen()
1591 self.angles = self.movedir;
1592 self.pos2 = '0 0 0';
1593 self.pos1 = self.movedir;
1597 void spawnfunc_func_door_rotating()
1600 //if (!self.deathtype) // map makers can override this
1601 // self.deathtype = " got in the way";
1603 // I abuse "movedir" for denoting the axis for now
1604 if (self.spawnflags & 64) // X (untested)
1605 self.movedir = '0 0 1';
1606 else if (self.spawnflags & 128) // Y (untested)
1607 self.movedir = '1 0 0';
1609 self.movedir = '0 1 0';
1611 if (self.angles_y==0) self.angles_y = 90;
1613 self.movedir = self.movedir * self.angles_y;
1614 self.angles = '0 0 0';
1616 self.max_health = self.health;
1617 self.avelocity = self.movedir;
1618 if (!InitMovingBrushTrigger())
1620 self.velocity = '0 0 0';
1621 //self.effects |= EF_LOWPRECISION;
1622 self.classname = "door_rotating";
1624 self.blocked = door_blocked;
1625 self.use = door_use;
1627 if(self.spawnflags & 8)
1630 if(self.dmg && (self.message == ""))
1631 self.message = "was squished";
1632 if(self.dmg && (self.message2 == ""))
1633 self.message2 = "was squished by";
1635 if (self.sounds > 0)
1637 precache_sound ("plats/medplat1.wav");
1638 precache_sound ("plats/medplat2.wav");
1639 self.noise2 = "plats/medplat1.wav";
1640 self.noise1 = "plats/medplat2.wav";
1647 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1649 self.pos1 = '0 0 0';
1650 self.pos2 = self.movedir;
1652 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1653 // but spawn in the open position
1654 if (self.spawnflags & DOOR_START_OPEN)
1655 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1657 self.state = STATE_BOTTOM;
1661 self.takedamage = DAMAGE_YES;
1662 self.event_damage = door_damage;
1668 self.touch = door_touch;
1670 // LinkDoors can't be done until all of the doors have been spawned, so
1671 // the sizes can be detected properly.
1672 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1674 self.reset = door_rotating_reset;
1678 =============================================================================
1682 =============================================================================
1685 void() fd_secret_move1;
1686 void() fd_secret_move2;
1687 void() fd_secret_move3;
1688 void() fd_secret_move4;
1689 void() fd_secret_move5;
1690 void() fd_secret_move6;
1691 void() fd_secret_done;
1693 const float SECRET_OPEN_ONCE = 1; // stays open
1694 const float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1695 const float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1696 const float SECRET_NO_SHOOT = 8; // only opened by trigger
1697 const float SECRET_YES_SHOOT = 16; // shootable even if targeted
1699 void fd_secret_use()
1702 string message_save;
1704 self.health = 10000;
1705 self.bot_attack = TRUE;
1707 // exit if still moving around...
1708 if (self.origin != self.oldorigin)
1711 message_save = self.message;
1712 self.message = ""; // no more message
1713 SUB_UseTargets(); // fire all targets / killtargets
1714 self.message = message_save;
1716 self.velocity = '0 0 0';
1718 // Make a sound, wait a little...
1720 if (self.noise1 != "")
1721 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1722 self.nextthink = self.ltime + 0.1;
1724 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1725 makevectors(self.mangle);
1729 if (self.spawnflags & SECRET_1ST_DOWN)
1730 self.t_width = fabs(v_up * self.size);
1732 self.t_width = fabs(v_right * self.size);
1736 self.t_length = fabs(v_forward * self.size);
1738 if (self.spawnflags & SECRET_1ST_DOWN)
1739 self.dest1 = self.origin - v_up * self.t_width;
1741 self.dest1 = self.origin + v_right * (self.t_width * temp);
1743 self.dest2 = self.dest1 + v_forward * self.t_length;
1744 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
1745 if (self.noise2 != "")
1746 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1749 void fd_secret_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1754 // Wait after first movement...
1755 void fd_secret_move1()
1757 self.nextthink = self.ltime + 1.0;
1758 self.think = fd_secret_move2;
1759 if (self.noise3 != "")
1760 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1763 // Start moving sideways w/sound...
1764 void fd_secret_move2()
1766 if (self.noise2 != "")
1767 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1768 SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
1771 // Wait here until time to go back...
1772 void fd_secret_move3()
1774 if (self.noise3 != "")
1775 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1776 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1778 self.nextthink = self.ltime + self.wait;
1779 self.think = fd_secret_move4;
1784 void fd_secret_move4()
1786 if (self.noise2 != "")
1787 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1788 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
1792 void fd_secret_move5()
1794 self.nextthink = self.ltime + 1.0;
1795 self.think = fd_secret_move6;
1796 if (self.noise3 != "")
1797 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1800 void fd_secret_move6()
1802 if (self.noise2 != "")
1803 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1804 SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
1807 void fd_secret_done()
1809 if (self.spawnflags&SECRET_YES_SHOOT)
1811 self.health = 10000;
1812 self.takedamage = DAMAGE_YES;
1813 //self.th_pain = fd_secret_use;
1815 if (self.noise3 != "")
1816 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1819 void secret_blocked()
1821 if (time < self.attack_finished_single)
1823 self.attack_finished_single = time + 0.5;
1824 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1836 if (!other.iscreature)
1838 if (self.attack_finished_single > time)
1841 self.attack_finished_single = time + 2;
1845 if (IS_CLIENT(other))
1846 centerprint(other, self.message);
1847 play2(other, "misc/talk.wav");
1853 if (self.spawnflags&SECRET_YES_SHOOT)
1855 self.health = 10000;
1856 self.takedamage = DAMAGE_YES;
1858 setorigin(self, self.oldorigin);
1859 self.think = func_null;
1863 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1864 Basic secret door. Slides back, then to the side. Angle determines direction.
1865 wait = # of seconds before coming back
1866 1st_left = 1st move is left of arrow
1867 1st_down = 1st move is down from arrow
1868 always_shoot = even if targeted, keep shootable
1869 t_width = override WIDTH to move back (or height if going down)
1870 t_length = override LENGTH to move sideways
1871 "dmg" damage to inflict when blocked (2 default)
1873 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1880 void spawnfunc_func_door_secret()
1882 /*if (!self.deathtype) // map makers can override this
1883 self.deathtype = " got in the way";*/
1889 self.mangle = self.angles;
1890 self.angles = '0 0 0';
1891 self.classname = "door";
1892 if (!InitMovingBrushTrigger())
1894 self.effects |= EF_LOWPRECISION;
1896 self.touch = secret_touch;
1897 self.blocked = secret_blocked;
1899 self.use = fd_secret_use;
1904 self.spawnflags |= SECRET_YES_SHOOT;
1906 if(self.spawnflags&SECRET_YES_SHOOT)
1908 self.health = 10000;
1909 self.takedamage = DAMAGE_YES;
1910 self.event_damage = fd_secret_damage;
1912 self.oldorigin = self.origin;
1914 self.wait = 5; // 5 seconds before closing
1916 self.reset = secret_reset;
1920 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1921 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1922 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
1923 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1924 height: amplitude modifier (default 32)
1925 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1926 noise: path/name of looping .wav file to play.
1927 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1931 void func_fourier_controller_think()
1936 self.nextthink = time + 0.1;
1937 if(self.owner.active != ACTIVE_ACTIVE)
1939 self.owner.velocity = '0 0 0';
1944 n = floor((tokenize_console(self.owner.netname)) / 5);
1945 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1947 v = self.owner.destvec;
1949 for(i = 0; i < n; ++i)
1951 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1952 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;
1955 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1956 // * 10 so it will arrive in 0.1 sec
1957 self.owner.velocity = (v - self.owner.origin) * 10;
1960 void spawnfunc_func_fourier()
1963 if (self.noise != "")
1965 precache_sound(self.noise);
1966 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
1973 self.destvec = self.origin;
1974 self.cnt = 360 / self.speed;
1976 self.blocked = generic_plat_blocked;
1977 if(self.dmg && (self.message == ""))
1978 self.message = " was squished";
1979 if(self.dmg && (self.message2 == ""))
1980 self.message2 = "was squished by";
1981 if(self.dmg && (!self.dmgtime))
1982 self.dmgtime = 0.25;
1983 self.dmgtime2 = time;
1985 if(self.netname == "")
1986 self.netname = "1 0 0 0 1";
1988 if (!InitMovingBrushTrigger())
1991 self.active = ACTIVE_ACTIVE;
1993 // wait for targets to spawn
1994 controller = spawn();
1995 controller.classname = "func_fourier_controller";
1996 controller.owner = self;
1997 controller.nextthink = time + 1;
1998 controller.think = func_fourier_controller_think;
1999 self.nextthink = self.ltime + 999999999;
2000 self.think = SUB_NullThink; // for PushMove
2002 // Savage: Reduce bandwith, critical on e.g. nexdm02
2003 self.effects |= EF_LOWPRECISION;
2005 // TODO make a reset function for this one
2008 // reusing some fields havocbots declared
2009 .entity wp00, wp01, wp02, wp03;
2011 .float targetfactor, target2factor, target3factor, target4factor;
2012 .vector targetnormal, target2normal, target3normal, target4normal;
2014 vector func_vectormamamam_origin(entity o, float t)
2026 p = e.origin + t * e.velocity;
2028 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
2030 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
2036 p = e.origin + t * e.velocity;
2038 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
2040 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
2046 p = e.origin + t * e.velocity;
2048 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
2050 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
2056 p = e.origin + t * e.velocity;
2058 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
2060 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
2066 void func_vectormamamam_controller_think()
2068 self.nextthink = time + 0.1;
2070 if(self.owner.active != ACTIVE_ACTIVE)
2072 self.owner.velocity = '0 0 0';
2076 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
2077 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
2080 void func_vectormamamam_findtarget()
2082 if(self.target != "")
2083 self.wp00 = find(world, targetname, self.target);
2085 if(self.target2 != "")
2086 self.wp01 = find(world, targetname, self.target2);
2088 if(self.target3 != "")
2089 self.wp02 = find(world, targetname, self.target3);
2091 if(self.target4 != "")
2092 self.wp03 = find(world, targetname, self.target4);
2094 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
2095 objerror("No reference entity found, so there is nothing to move. Aborting.");
2097 self.destvec = self.origin - func_vectormamamam_origin(self, 0);
2100 controller = spawn();
2101 controller.classname = "func_vectormamamam_controller";
2102 controller.owner = self;
2103 controller.nextthink = time + 1;
2104 controller.think = func_vectormamamam_controller_think;
2107 void spawnfunc_func_vectormamamam()
2109 if (self.noise != "")
2111 precache_sound(self.noise);
2112 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
2115 if(!self.targetfactor)
2116 self.targetfactor = 1;
2118 if(!self.target2factor)
2119 self.target2factor = 1;
2121 if(!self.target3factor)
2122 self.target3factor = 1;
2124 if(!self.target4factor)
2125 self.target4factor = 1;
2127 if(vlen(self.targetnormal))
2128 self.targetnormal = normalize(self.targetnormal);
2130 if(vlen(self.target2normal))
2131 self.target2normal = normalize(self.target2normal);
2133 if(vlen(self.target3normal))
2134 self.target3normal = normalize(self.target3normal);
2136 if(vlen(self.target4normal))
2137 self.target4normal = normalize(self.target4normal);
2139 self.blocked = generic_plat_blocked;
2140 if(self.dmg && (self.message == ""))
2141 self.message = " was squished";
2142 if(self.dmg && (self.message == ""))
2143 self.message2 = "was squished by";
2144 if(self.dmg && (!self.dmgtime))
2145 self.dmgtime = 0.25;
2146 self.dmgtime2 = time;
2148 if(self.netname == "")
2149 self.netname = "1 0 0 0 1";
2151 if (!InitMovingBrushTrigger())
2154 // wait for targets to spawn
2155 self.nextthink = self.ltime + 999999999;
2156 self.think = SUB_NullThink; // for PushMove
2158 // Savage: Reduce bandwith, critical on e.g. nexdm02
2159 self.effects |= EF_LOWPRECISION;
2161 self.active = ACTIVE_ACTIVE;
2163 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
2166 void conveyor_think()
2170 // set myself as current conveyor where possible
2171 for(e = world; (e = findentity(e, conveyor, self)); )
2176 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2177 if(!e.conveyor.state)
2180 vector emin = e.absmin;
2181 vector emax = e.absmax;
2182 if(self.solid == SOLID_BSP)
2187 if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2188 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2192 for(e = world; (e = findentity(e, conveyor, self)); )
2194 if(IS_CLIENT(e)) // doing it via velocity has quite some advantages
2195 continue; // done in SV_PlayerPhysics
2197 setorigin(e, e.origin + self.movedir * sys_frametime);
2198 move_out_of_solid(e);
2199 UpdateCSQCProjectile(e);
2201 // stupid conveyor code
2202 tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2203 if(trace_fraction > 0)
2204 setorigin(e, trace_endpos);
2209 self.nextthink = time;
2214 self.state = !self.state;
2217 void conveyor_reset()
2219 self.state = (self.spawnflags & 1);
2222 void conveyor_init()
2226 self.movedir = self.movedir * self.speed;
2227 self.think = conveyor_think;
2228 self.nextthink = time;
2231 self.use = conveyor_use;
2232 self.reset = conveyor_reset;
2239 void spawnfunc_trigger_conveyor()
2246 void spawnfunc_func_conveyor()
2249 InitMovingBrushTrigger();
2250 self.movetype = MOVETYPE_NONE;