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 MinstaGib 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) {
1006 // this door require a key
1007 // only a player can have a key
1008 if (!IS_PLAYER(other))
1011 if (item_keys_usekey(door, other)) {
1012 // some keys were used
1013 if (other.key_door_messagetime <= time) {
1014 play2(other, "misc/talk.wav");
1015 centerprint(other, strcat("You also need ", item_keys_keylist(door.itemkeys), "!"));
1016 other.key_door_messagetime = time + 2;
1019 // no keys were used
1020 if (other.key_door_messagetime <= time) {
1021 play2(other, "misc/talk.wav");
1022 centerprint(other, strcat("You need ", item_keys_keylist(door.itemkeys), "!"));
1023 other.key_door_messagetime = time + 2;
1027 if (door.itemkeys) {
1028 // door is now unlocked
1029 play2(other, "misc/talk.wav");
1030 centerprint(other, "Door unlocked!");
1042 if (self.owner != self)
1043 objerror ("door_fire: self.owner != self");
1047 if (self.spawnflags & DOOR_TOGGLE)
1049 if (self.state == STATE_UP || self.state == STATE_TOP)
1054 if (self.classname == "door")
1060 door_rotating_go_down ();
1063 } while ( (self != starte) && (self != world) );
1069 // trigger all paired doors
1073 if (self.classname == "door")
1078 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
1079 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
1081 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
1082 self.pos2 = '0 0 0' - self.pos2;
1084 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
1085 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
1086 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
1088 door_rotating_go_up ();
1092 } while ( (self != starte) && (self != world) );
1101 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
1113 void door_trigger_touch()
1115 if (other.health < 1)
1116 if (!(other.iscreature && other.deadflag == DEAD_NO))
1119 if (time < self.attack_finished_single)
1122 // check if door is locked
1123 if (!door_check_keys())
1126 self.attack_finished_single = time + 1;
1135 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1138 if(self.spawnflags & DOOR_NOSPLASH)
1139 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
1141 self.health = self.health - damage;
1143 if (self.itemkeys) {
1144 // don't allow opening doors through damage if keys are required
1148 if (self.health <= 0)
1152 self.health = self.max_health;
1153 self.takedamage = DAMAGE_NO; // wil be reset upon return
1169 if (!IS_PLAYER(other))
1171 if (self.owner.attack_finished_single > time)
1174 self.owner.attack_finished_single = time + 2;
1176 if (!(self.owner.dmg) && (self.owner.message != ""))
1178 if (IS_CLIENT(other))
1179 centerprint (other, self.owner.message);
1180 play2(other, "misc/talk.wav");
1185 void door_generic_plat_blocked()
1188 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1189 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1192 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1193 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1195 //Dont chamge direction for dead or dying stuff
1196 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1199 if (self.state == STATE_DOWN)
1200 door_rotating_go_up ();
1202 door_rotating_go_down ();
1205 //gib dying stuff just to make sure
1206 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1207 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1211 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1212 // if a door has a negative wait, it would never come back if blocked,
1213 // so let it just squash the object to death real fast
1214 /* if (self.wait >= 0)
1216 if (self.state == STATE_DOWN)
1217 door_rotating_go_up ();
1219 door_rotating_go_down ();
1225 void door_rotating_hit_top()
1227 if (self.noise1 != "")
1228 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1229 self.state = STATE_TOP;
1230 if (self.spawnflags & DOOR_TOGGLE)
1231 return; // don't come down automatically
1232 self.think = door_rotating_go_down;
1233 self.nextthink = self.ltime + self.wait;
1236 void door_rotating_hit_bottom()
1238 if (self.noise1 != "")
1239 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1240 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1242 self.pos2 = '0 0 0' - self.pos2;
1245 self.state = STATE_BOTTOM;
1248 void door_rotating_go_down()
1250 if (self.noise2 != "")
1251 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1252 if (self.max_health)
1254 self.takedamage = DAMAGE_YES;
1255 self.health = self.max_health;
1258 self.state = STATE_DOWN;
1259 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
1262 void door_rotating_go_up()
1264 if (self.state == STATE_UP)
1265 return; // already going up
1267 if (self.state == STATE_TOP)
1268 { // reset top wait time
1269 self.nextthink = self.ltime + self.wait;
1272 if (self.noise2 != "")
1273 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1274 self.state = STATE_UP;
1275 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
1278 oldmessage = self.message;
1281 self.message = oldmessage;
1288 =============================================================================
1292 =============================================================================
1296 entity spawn_field(vector fmins, vector fmaxs)
1302 trigger.classname = "doortriggerfield";
1303 trigger.movetype = MOVETYPE_NONE;
1304 trigger.solid = SOLID_TRIGGER;
1305 trigger.owner = self;
1306 trigger.touch = door_trigger_touch;
1310 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1315 entity LinkDoors_nextent(entity cur, entity near, entity pass)
1317 while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
1323 float LinkDoors_isconnected(entity e1, entity e2, entity pass)
1326 if (e1.absmin_x > e2.absmax_x + DELTA)
1328 if (e1.absmin_y > e2.absmax_y + DELTA)
1330 if (e1.absmin_z > e2.absmax_z + DELTA)
1332 if (e2.absmin_x > e1.absmax_x + DELTA)
1334 if (e2.absmin_y > e1.absmax_y + DELTA)
1336 if (e2.absmin_z > e1.absmax_z + DELTA)
1351 vector cmins, cmaxs;
1354 return; // already linked by another door
1355 if (self.spawnflags & 4)
1357 self.owner = self.enemy = self;
1365 self.trigger_field = spawn_field(self.absmin, self.absmax);
1367 return; // don't want to link this door
1370 FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
1372 // set owner, and make a loop of the chain
1373 dprint("LinkDoors: linking doors:");
1374 for(t = self; ; t = t.enemy)
1376 dprint(" ", etos(t));
1378 if(t.enemy == world)
1386 // collect health, targetname, message, size
1387 cmins = self.absmin;
1388 cmaxs = self.absmax;
1389 for(t = self; ; t = t.enemy)
1391 if(t.health && !self.health)
1392 self.health = t.health;
1393 if((t.targetname != "") && (self.targetname == ""))
1394 self.targetname = t.targetname;
1395 if((t.message != "") && (self.message == ""))
1396 self.message = t.message;
1397 if (t.absmin_x < cmins_x)
1398 cmins_x = t.absmin_x;
1399 if (t.absmin_y < cmins_y)
1400 cmins_y = t.absmin_y;
1401 if (t.absmin_z < cmins_z)
1402 cmins_z = t.absmin_z;
1403 if (t.absmax_x > cmaxs_x)
1404 cmaxs_x = t.absmax_x;
1405 if (t.absmax_y > cmaxs_y)
1406 cmaxs_y = t.absmax_y;
1407 if (t.absmax_z > cmaxs_z)
1408 cmaxs_z = t.absmax_z;
1413 // distribute health, targetname, message
1414 for(t = self; t; t = t.enemy)
1416 t.health = self.health;
1417 t.targetname = self.targetname;
1418 t.message = self.message;
1423 // shootable, or triggered doors just needed the owner/enemy links,
1424 // they don't spawn a field
1433 self.trigger_field = spawn_field(cmins, cmaxs);
1437 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
1438 if two doors touch, they are assumed to be connected and operate as a unit.
1440 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1442 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).
1444 GOLD_KEY causes the door to open only if the activator holds a gold key.
1446 SILVER_KEY causes the door to open only if the activator holds a silver key.
1448 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1449 "angle" determines the opening direction
1450 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1451 "health" if set, door must be shot open
1452 "speed" movement speed (100 default)
1453 "wait" wait before returning (3 default, -1 = never return)
1454 "lip" lip remaining at end of move (8 default)
1455 "dmg" damage to inflict when blocked (2 default)
1462 FIXME: only one sound set available at the time being
1466 void door_init_startopen()
1468 setorigin (self, self.pos2);
1469 self.pos2 = self.pos1;
1470 self.pos1 = self.origin;
1475 setorigin(self, self.pos1);
1476 self.velocity = '0 0 0';
1477 self.state = STATE_BOTTOM;
1478 self.think = func_null;
1482 // spawnflags require key (for now only func_door)
1483 #define SPAWNFLAGS_GOLD_KEY 8
1484 #define SPAWNFLAGS_SILVER_KEY 16
1485 void spawnfunc_func_door()
1487 // Quake 1 keys compatibility
1488 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
1489 self.itemkeys |= ITEM_KEY_BIT(0);
1490 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
1491 self.itemkeys |= ITEM_KEY_BIT(1);
1493 //if (!self.deathtype) // map makers can override this
1494 // self.deathtype = " got in the way";
1497 self.max_health = self.health;
1498 if (!InitMovingBrushTrigger())
1500 self.effects |= EF_LOWPRECISION;
1501 self.classname = "door";
1503 self.blocked = door_blocked;
1504 self.use = door_use;
1506 // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
1507 // if(self.spawnflags & 8)
1508 // self.dmg = 10000;
1510 if(self.dmg && (self.message == ""))
1511 self.message = "was squished";
1512 if(self.dmg && (self.message2 == ""))
1513 self.message2 = "was squished by";
1515 if (self.sounds > 0)
1517 precache_sound ("plats/medplat1.wav");
1518 precache_sound ("plats/medplat2.wav");
1519 self.noise2 = "plats/medplat1.wav";
1520 self.noise1 = "plats/medplat2.wav";
1530 self.pos1 = self.origin;
1531 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1533 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1534 // but spawn in the open position
1535 if (self.spawnflags & DOOR_START_OPEN)
1536 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1538 self.state = STATE_BOTTOM;
1542 self.takedamage = DAMAGE_YES;
1543 self.event_damage = door_damage;
1549 self.touch = door_touch;
1551 // LinkDoors can't be done until all of the doors have been spawned, so
1552 // the sizes can be detected properly.
1553 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1555 self.reset = door_reset;
1558 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1559 if two doors touch, they are assumed to be connected and operate as a unit.
1561 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1563 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1564 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1565 must have set trigger_reverse to 1.
1566 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1568 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).
1570 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1571 "angle" determines the destination angle for opening. negative values reverse the direction.
1572 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1573 "health" if set, door must be shot open
1574 "speed" movement speed (100 default)
1575 "wait" wait before returning (3 default, -1 = never return)
1576 "dmg" damage to inflict when blocked (2 default)
1583 FIXME: only one sound set available at the time being
1586 void door_rotating_reset()
1588 self.angles = self.pos1;
1589 self.avelocity = '0 0 0';
1590 self.state = STATE_BOTTOM;
1591 self.think = func_null;
1595 void door_rotating_init_startopen()
1597 self.angles = self.movedir;
1598 self.pos2 = '0 0 0';
1599 self.pos1 = self.movedir;
1603 void spawnfunc_func_door_rotating()
1606 //if (!self.deathtype) // map makers can override this
1607 // self.deathtype = " got in the way";
1609 // I abuse "movedir" for denoting the axis for now
1610 if (self.spawnflags & 64) // X (untested)
1611 self.movedir = '0 0 1';
1612 else if (self.spawnflags & 128) // Y (untested)
1613 self.movedir = '1 0 0';
1615 self.movedir = '0 1 0';
1617 if (self.angles_y==0) self.angles_y = 90;
1619 self.movedir = self.movedir * self.angles_y;
1620 self.angles = '0 0 0';
1622 self.max_health = self.health;
1623 self.avelocity = self.movedir;
1624 if (!InitMovingBrushTrigger())
1626 self.velocity = '0 0 0';
1627 //self.effects |= EF_LOWPRECISION;
1628 self.classname = "door_rotating";
1630 self.blocked = door_blocked;
1631 self.use = door_use;
1633 if(self.spawnflags & 8)
1636 if(self.dmg && (self.message == ""))
1637 self.message = "was squished";
1638 if(self.dmg && (self.message2 == ""))
1639 self.message2 = "was squished by";
1641 if (self.sounds > 0)
1643 precache_sound ("plats/medplat1.wav");
1644 precache_sound ("plats/medplat2.wav");
1645 self.noise2 = "plats/medplat1.wav";
1646 self.noise1 = "plats/medplat2.wav";
1653 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1655 self.pos1 = '0 0 0';
1656 self.pos2 = self.movedir;
1658 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1659 // but spawn in the open position
1660 if (self.spawnflags & DOOR_START_OPEN)
1661 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1663 self.state = STATE_BOTTOM;
1667 self.takedamage = DAMAGE_YES;
1668 self.event_damage = door_damage;
1674 self.touch = door_touch;
1676 // LinkDoors can't be done until all of the doors have been spawned, so
1677 // the sizes can be detected properly.
1678 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1680 self.reset = door_rotating_reset;
1684 =============================================================================
1688 =============================================================================
1691 void() fd_secret_move1;
1692 void() fd_secret_move2;
1693 void() fd_secret_move3;
1694 void() fd_secret_move4;
1695 void() fd_secret_move5;
1696 void() fd_secret_move6;
1697 void() fd_secret_done;
1699 const float SECRET_OPEN_ONCE = 1; // stays open
1700 const float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1701 const float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1702 const float SECRET_NO_SHOOT = 8; // only opened by trigger
1703 const float SECRET_YES_SHOOT = 16; // shootable even if targeted
1705 void fd_secret_use()
1708 string message_save;
1710 self.health = 10000;
1711 self.bot_attack = TRUE;
1713 // exit if still moving around...
1714 if (self.origin != self.oldorigin)
1717 message_save = self.message;
1718 self.message = ""; // no more message
1719 SUB_UseTargets(); // fire all targets / killtargets
1720 self.message = message_save;
1722 self.velocity = '0 0 0';
1724 // Make a sound, wait a little...
1726 if (self.noise1 != "")
1727 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1728 self.nextthink = self.ltime + 0.1;
1730 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1731 makevectors(self.mangle);
1735 if (self.spawnflags & SECRET_1ST_DOWN)
1736 self.t_width = fabs(v_up * self.size);
1738 self.t_width = fabs(v_right * self.size);
1742 self.t_length = fabs(v_forward * self.size);
1744 if (self.spawnflags & SECRET_1ST_DOWN)
1745 self.dest1 = self.origin - v_up * self.t_width;
1747 self.dest1 = self.origin + v_right * (self.t_width * temp);
1749 self.dest2 = self.dest1 + v_forward * self.t_length;
1750 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
1751 if (self.noise2 != "")
1752 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1755 void fd_secret_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1760 // Wait after first movement...
1761 void fd_secret_move1()
1763 self.nextthink = self.ltime + 1.0;
1764 self.think = fd_secret_move2;
1765 if (self.noise3 != "")
1766 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1769 // Start moving sideways w/sound...
1770 void fd_secret_move2()
1772 if (self.noise2 != "")
1773 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1774 SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
1777 // Wait here until time to go back...
1778 void fd_secret_move3()
1780 if (self.noise3 != "")
1781 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1782 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1784 self.nextthink = self.ltime + self.wait;
1785 self.think = fd_secret_move4;
1790 void fd_secret_move4()
1792 if (self.noise2 != "")
1793 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1794 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
1798 void fd_secret_move5()
1800 self.nextthink = self.ltime + 1.0;
1801 self.think = fd_secret_move6;
1802 if (self.noise3 != "")
1803 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1806 void fd_secret_move6()
1808 if (self.noise2 != "")
1809 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1810 SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
1813 void fd_secret_done()
1815 if (self.spawnflags&SECRET_YES_SHOOT)
1817 self.health = 10000;
1818 self.takedamage = DAMAGE_YES;
1819 //self.th_pain = fd_secret_use;
1821 if (self.noise3 != "")
1822 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
1825 void secret_blocked()
1827 if (time < self.attack_finished_single)
1829 self.attack_finished_single = time + 0.5;
1830 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1842 if (!other.iscreature)
1844 if (self.attack_finished_single > time)
1847 self.attack_finished_single = time + 2;
1851 if (IS_CLIENT(other))
1852 centerprint (other, self.message);
1853 play2(other, "misc/talk.wav");
1859 if (self.spawnflags&SECRET_YES_SHOOT)
1861 self.health = 10000;
1862 self.takedamage = DAMAGE_YES;
1864 setorigin(self, self.oldorigin);
1865 self.think = func_null;
1869 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1870 Basic secret door. Slides back, then to the side. Angle determines direction.
1871 wait = # of seconds before coming back
1872 1st_left = 1st move is left of arrow
1873 1st_down = 1st move is down from arrow
1874 always_shoot = even if targeted, keep shootable
1875 t_width = override WIDTH to move back (or height if going down)
1876 t_length = override LENGTH to move sideways
1877 "dmg" damage to inflict when blocked (2 default)
1879 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1886 void spawnfunc_func_door_secret()
1888 /*if (!self.deathtype) // map makers can override this
1889 self.deathtype = " got in the way";*/
1895 self.mangle = self.angles;
1896 self.angles = '0 0 0';
1897 self.classname = "door";
1898 if (!InitMovingBrushTrigger())
1900 self.effects |= EF_LOWPRECISION;
1902 self.touch = secret_touch;
1903 self.blocked = secret_blocked;
1905 self.use = fd_secret_use;
1910 self.spawnflags |= SECRET_YES_SHOOT;
1912 if(self.spawnflags&SECRET_YES_SHOOT)
1914 self.health = 10000;
1915 self.takedamage = DAMAGE_YES;
1916 self.event_damage = fd_secret_damage;
1918 self.oldorigin = self.origin;
1920 self.wait = 5; // 5 seconds before closing
1922 self.reset = secret_reset;
1926 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1927 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1928 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
1929 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1930 height: amplitude modifier (default 32)
1931 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1932 noise: path/name of looping .wav file to play.
1933 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1937 void func_fourier_controller_think()
1942 self.nextthink = time + 0.1;
1943 if (!(self.owner.active == ACTIVE_ACTIVE))
1945 self.owner.velocity = '0 0 0';
1950 n = floor((tokenize_console(self.owner.netname)) / 5);
1951 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1953 v = self.owner.destvec;
1955 for(i = 0; i < n; ++i)
1957 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1958 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;
1961 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1962 // * 10 so it will arrive in 0.1 sec
1963 self.owner.velocity = (v - self.owner.origin) * 10;
1966 void spawnfunc_func_fourier()
1969 if (self.noise != "")
1971 precache_sound(self.noise);
1972 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
1979 self.destvec = self.origin;
1980 self.cnt = 360 / self.speed;
1982 self.blocked = generic_plat_blocked;
1983 if(self.dmg && (self.message == ""))
1984 self.message = " was squished";
1985 if(self.dmg && (self.message2 == ""))
1986 self.message2 = "was squished by";
1987 if(self.dmg && (!self.dmgtime))
1988 self.dmgtime = 0.25;
1989 self.dmgtime2 = time;
1991 if(self.netname == "")
1992 self.netname = "1 0 0 0 1";
1994 if (!InitMovingBrushTrigger())
1997 self.active = ACTIVE_ACTIVE;
1999 // wait for targets to spawn
2000 controller = spawn();
2001 controller.classname = "func_fourier_controller";
2002 controller.owner = self;
2003 controller.nextthink = time + 1;
2004 controller.think = func_fourier_controller_think;
2005 self.nextthink = self.ltime + 999999999;
2006 self.think = SUB_NullThink; // for PushMove
2008 // Savage: Reduce bandwith, critical on e.g. nexdm02
2009 self.effects |= EF_LOWPRECISION;
2011 // TODO make a reset function for this one
2014 // reusing some fields havocbots declared
2015 .entity wp00, wp01, wp02, wp03;
2017 .float targetfactor, target2factor, target3factor, target4factor;
2018 .vector targetnormal, target2normal, target3normal, target4normal;
2020 vector func_vectormamamam_origin(entity o, float t)
2032 p = e.origin + t * e.velocity;
2034 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
2036 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
2042 p = e.origin + t * e.velocity;
2044 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
2046 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
2052 p = e.origin + t * e.velocity;
2054 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
2056 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
2062 p = e.origin + t * e.velocity;
2064 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
2066 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
2072 void func_vectormamamam_controller_think()
2074 self.nextthink = time + 0.1;
2076 if (!(self.owner.active == ACTIVE_ACTIVE))
2078 self.owner.velocity = '0 0 0';
2082 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
2083 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
2086 void func_vectormamamam_findtarget()
2088 if(self.target != "")
2089 self.wp00 = find(world, targetname, self.target);
2091 if(self.target2 != "")
2092 self.wp01 = find(world, targetname, self.target2);
2094 if(self.target3 != "")
2095 self.wp02 = find(world, targetname, self.target3);
2097 if(self.target4 != "")
2098 self.wp03 = find(world, targetname, self.target4);
2100 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
2101 objerror("No reference entity found, so there is nothing to move. Aborting.");
2103 self.destvec = self.origin - func_vectormamamam_origin(self, 0);
2106 controller = spawn();
2107 controller.classname = "func_vectormamamam_controller";
2108 controller.owner = self;
2109 controller.nextthink = time + 1;
2110 controller.think = func_vectormamamam_controller_think;
2113 void spawnfunc_func_vectormamamam()
2115 if (self.noise != "")
2117 precache_sound(self.noise);
2118 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
2121 if(!self.targetfactor)
2122 self.targetfactor = 1;
2124 if(!self.target2factor)
2125 self.target2factor = 1;
2127 if(!self.target3factor)
2128 self.target3factor = 1;
2130 if(!self.target4factor)
2131 self.target4factor = 1;
2133 if(vlen(self.targetnormal))
2134 self.targetnormal = normalize(self.targetnormal);
2136 if(vlen(self.target2normal))
2137 self.target2normal = normalize(self.target2normal);
2139 if(vlen(self.target3normal))
2140 self.target3normal = normalize(self.target3normal);
2142 if(vlen(self.target4normal))
2143 self.target4normal = normalize(self.target4normal);
2145 self.blocked = generic_plat_blocked;
2146 if(self.dmg && (self.message == ""))
2147 self.message = " was squished";
2148 if(self.dmg && (self.message == ""))
2149 self.message2 = "was squished by";
2150 if(self.dmg && (!self.dmgtime))
2151 self.dmgtime = 0.25;
2152 self.dmgtime2 = time;
2154 if(self.netname == "")
2155 self.netname = "1 0 0 0 1";
2157 if (!InitMovingBrushTrigger())
2160 // wait for targets to spawn
2161 self.nextthink = self.ltime + 999999999;
2162 self.think = SUB_NullThink; // for PushMove
2164 // Savage: Reduce bandwith, critical on e.g. nexdm02
2165 self.effects |= EF_LOWPRECISION;
2167 self.active = ACTIVE_ACTIVE;
2169 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
2172 void conveyor_think()
2176 // set myself as current conveyor where possible
2177 for(e = world; (e = findentity(e, conveyor, self)); )
2182 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2183 if(!e.conveyor.state)
2186 vector emin = e.absmin;
2187 vector emax = e.absmax;
2188 if(self.solid == SOLID_BSP)
2193 if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2194 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2198 for(e = world; (e = findentity(e, conveyor, self)); )
2200 if(IS_CLIENT(e)) // doing it via velocity has quite some advantages
2201 continue; // done in SV_PlayerPhysics
2203 setorigin(e, e.origin + self.movedir * sys_frametime);
2204 move_out_of_solid(e);
2205 UpdateCSQCProjectile(e);
2207 // stupid conveyor code
2208 tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2209 if(trace_fraction > 0)
2210 setorigin(e, trace_endpos);
2215 self.nextthink = time;
2220 self.state = !self.state;
2223 void conveyor_reset()
2225 self.state = (self.spawnflags & 1);
2228 void conveyor_init()
2232 self.movedir = self.movedir * self.speed;
2233 self.think = conveyor_think;
2234 self.nextthink = time;
2237 self.use = conveyor_use;
2238 self.reset = conveyor_reset;
2245 void spawnfunc_trigger_conveyor()
2252 void spawnfunc_func_conveyor()
2255 InitMovingBrushTrigger();
2256 self.movetype = MOVETYPE_NONE;