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');
18 float STATE_BOTTOM = 1;
22 .entity trigger_field;
24 void() plat_center_touch;
25 void() plat_outside_touch;
26 void() plat_trigger_use;
30 float PLAT_LOW_TRIGGER = 1;
32 void plat_spawn_inside_trigger()
38 trigger.touch = plat_center_touch;
39 trigger.movetype = MOVETYPE_NONE;
40 trigger.solid = SOLID_TRIGGER;
43 tmin = self.absmin + '25 25 0';
44 tmax = self.absmax - '25 25 -8';
45 tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8);
46 if (self.spawnflags & PLAT_LOW_TRIGGER)
49 if (self.size_x <= 50)
51 tmin_x = (self.mins_x + self.maxs_x) / 2;
54 if (self.size_y <= 50)
56 tmin_y = (self.mins_y + self.maxs_y) / 2;
64 setsize (trigger, tmin, tmax);
68 // otherwise, something is fishy...
70 objerror("plat_spawn_inside_trigger: platform has odd size or lip, can't spawn");
75 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
77 self.think = plat_go_down;
78 self.nextthink = self.ltime + 3;
81 void plat_hit_bottom()
83 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
89 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_NORM);
91 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, plat_hit_bottom);
96 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_NORM);
98 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, plat_hit_top);
101 void plat_center_touch()
103 if not(other.iscreature)
106 if (other.health <= 0)
112 else if (self.state == 1)
113 self.nextthink = self.ltime + 1; // delay going down
116 void plat_outside_touch()
118 if not(other.iscreature)
121 if (other.health <= 0)
129 void plat_trigger_use()
132 return; // already activated
139 if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
140 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
142 if((self.dmg) && (other.takedamage != DAMAGE_NO)) { // Shall we bite?
143 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
144 // Gib dead/dying stuff
145 if(other.deadflag != DEAD_NO)
146 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
151 else if (self.state == 3)
153 // when in other states, then the plat_crush event came delayed after
154 // plat state already had changed
155 // this isn't a bug per se!
163 objerror ("plat_use: not in up state");
167 .string sound1, sound2;
173 setorigin (self, self.pos1);
179 setorigin (self, self.pos2);
181 self.use = plat_trigger_use;
185 .float platmovetype_start_default, platmovetype_end_default;
186 float set_platmovetype(entity e, string s)
188 // sets platmovetype_start and platmovetype_end based on a string consisting of two values
191 n = tokenize_console(s);
193 e.platmovetype_start = stof(argv(0));
195 e.platmovetype_start = 0;
198 e.platmovetype_end = stof(argv(1));
200 e.platmovetype_end = e.platmovetype_start;
203 if(argv(2) == "force")
204 return TRUE; // no checking, return immediately
206 if(!cubic_speedfunc_is_sane(e.platmovetype_start, e.platmovetype_end))
208 objerror("Invalid platform move type; platform would go in reverse, which is not allowed.");
215 void spawnfunc_path_corner()
217 // setup values for overriding train movement
218 // if a second value does not exist, both start and end speeds are the single value specified
219 if(!set_platmovetype(self, self.platmovetype))
222 void spawnfunc_func_plat()
224 if (self.sounds == 0)
227 if(self.spawnflags & 4)
230 if(self.dmg && (!self.message))
231 self.message = "was squished";
232 if(self.dmg && (!self.message2))
233 self.message2 = "was squished by";
235 if (self.sounds == 1)
237 precache_sound ("plats/plat1.wav");
238 precache_sound ("plats/plat2.wav");
239 self.noise = "plats/plat1.wav";
240 self.noise1 = "plats/plat2.wav";
243 if (self.sounds == 2)
245 precache_sound ("plats/medplat1.wav");
246 precache_sound ("plats/medplat2.wav");
247 self.noise = "plats/medplat1.wav";
248 self.noise1 = "plats/medplat2.wav";
253 precache_sound (self.sound1);
254 self.noise = self.sound1;
258 precache_sound (self.sound2);
259 self.noise1 = self.sound2;
262 self.mangle = self.angles;
263 self.angles = '0 0 0';
265 self.classname = "plat";
266 if not(InitMovingBrushTrigger())
268 self.effects |= EF_LOWPRECISION;
269 setsize (self, self.mins , self.maxs);
271 self.blocked = plat_crush;
278 self.height = self.size_z - self.lip;
280 self.pos1 = self.origin;
281 self.pos2 = self.origin;
282 self.pos2_z = self.origin_z - self.height;
284 self.reset = plat_reset;
287 plat_spawn_inside_trigger (); // the "start moving" trigger
290 .float train_wait_turning;
301 // if turning is enabled, the train will turn toward the next point while waiting
302 if(self.platmovetype_turn && !self.train_wait_turning)
306 targ = find(world, targetname, self.target);
307 if((self.spawnflags & 1) && targ.curvetarget)
308 cp = find(world, targetname, targ.curvetarget);
312 if(cp) // bezier curves movement
313 ang = cp.origin - (self.origin - self.view_ofs); // use the origin of the control point of the next path_corner
314 else // linear movement
315 ang = targ.origin - (self.origin - self.view_ofs); // use the origin of the next path_corner
316 ang = vectoangles(ang);
317 ang_x = -ang_x; // flip up / down orientation
319 if(self.wait > 0) // slow turning
320 SUB_CalcAngleMove(ang, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
321 else // instant turning
322 SUB_CalcAngleMove(ang, TSPEED_TIME, 0.0000001, train_wait);
323 self.train_wait_turning = TRUE;
328 stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
330 if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
332 self.train_wait_turning = FALSE;
337 self.think = train_next;
338 self.nextthink = self.ltime + self.wait;
347 targ = find(world, targetname, self.target);
348 self.target = targ.target;
349 if (self.spawnflags & 1)
353 cp = find(world, targetname, targ.curvetarget); // get its second target (the control point)
354 cp_org = cp.origin - self.view_ofs; // no control point found, assume a straight line to the destination
360 objerror("train_next: no next target");
361 self.wait = targ.wait;
365 if(targ.platmovetype)
367 // this path_corner contains a movetype overrider, apply it
368 self.platmovetype_start = targ.platmovetype_start;
369 self.platmovetype_end = targ.platmovetype_end;
373 // this path_corner doesn't contain a movetype overrider, use the train's defaults
374 self.platmovetype_start = self.platmovetype_start_default;
375 self.platmovetype_end = self.platmovetype_end_default;
381 SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
383 SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
388 SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
390 SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
394 sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
397 void func_train_find()
400 targ = find(world, targetname, self.target);
401 self.target = targ.target;
403 objerror("func_train_find: no next target");
404 setorigin(self, targ.origin - self.view_ofs);
405 self.nextthink = self.ltime + 1;
406 self.think = train_next;
409 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
410 Ridable platform, targets spawnfunc_path_corner path to follow.
411 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
412 target : targetname of first spawnfunc_path_corner (starts here)
414 void spawnfunc_func_train()
416 if (self.noise != "")
417 precache_sound(self.noise);
420 objerror("func_train without a target");
424 if (self.spawnflags & 2)
426 self.platmovetype_turn = TRUE;
427 self.view_ofs = '0 0 0'; // don't offset a rotating train, origin works differently now
430 self.view_ofs = self.mins;
432 if not(InitMovingBrushTrigger())
434 self.effects |= EF_LOWPRECISION;
436 // wait for targets to spawn
437 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
439 self.blocked = generic_plat_blocked;
440 if(self.dmg & (!self.message))
441 self.message = " was squished";
442 if(self.dmg && (!self.message2))
443 self.message2 = "was squished by";
444 if(self.dmg && (!self.dmgtime))
446 self.dmgtime2 = time;
448 if(!set_platmovetype(self, self.platmovetype))
450 self.platmovetype_start_default = self.platmovetype_start;
451 self.platmovetype_end_default = self.platmovetype_end;
453 // TODO make a reset function for this one
456 void func_rotating_setactive(float astate)
459 if (astate == ACTIVE_TOGGLE)
461 if(self.active == ACTIVE_ACTIVE)
462 self.active = ACTIVE_NOT;
464 self.active = ACTIVE_ACTIVE;
467 self.active = astate;
469 if(self.active == ACTIVE_NOT)
470 self.avelocity = '0 0 0';
472 self.avelocity = self.pos1;
475 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
476 Brush model that spins in place on one axis (default Z).
477 speed : speed to rotate (in degrees per second)
478 noise : path/name of looping .wav file to play.
479 dmg : Do this mutch dmg every .dmgtime intervall when blocked
483 void spawnfunc_func_rotating()
485 if (self.noise != "")
487 precache_sound(self.noise);
488 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
491 self.active = ACTIVE_ACTIVE;
492 self.setactive = func_rotating_setactive;
496 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
497 if (self.spawnflags & 4) // X (untested)
498 self.avelocity = '0 0 1' * self.speed;
499 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
500 else if (self.spawnflags & 8) // Y (untested)
501 self.avelocity = '1 0 0' * self.speed;
502 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
504 self.avelocity = '0 1 0' * self.speed;
506 self.pos1 = self.avelocity;
508 if(self.dmg & (!self.message))
509 self.message = " was squished";
510 if(self.dmg && (!self.message2))
511 self.message2 = "was squished by";
514 if(self.dmg && (!self.dmgtime))
517 self.dmgtime2 = time;
519 if not(InitMovingBrushTrigger())
521 // no EF_LOWPRECISION here, as rounding angles is bad
523 self.blocked = generic_plat_blocked;
525 // wait for targets to spawn
526 self.nextthink = self.ltime + 999999999;
527 self.think = SUB_Null;
529 // TODO make a reset function for this one
533 void func_bobbing_controller_think()
536 self.nextthink = time + 0.1;
538 if not (self.owner.active == ACTIVE_ACTIVE)
540 self.owner.velocity = '0 0 0';
544 // calculate sinewave using makevectors
545 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
546 v = self.owner.destvec + self.owner.movedir * v_forward_y;
547 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
548 // * 10 so it will arrive in 0.1 sec
549 self.owner.velocity = (v - self.owner.origin) * 10;
552 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
553 Brush model that moves back and forth on one axis (default Z).
554 speed : how long one cycle takes in seconds (default 4)
555 height : how far the cycle moves (default 32)
556 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
557 noise : path/name of looping .wav file to play.
558 dmg : Do this mutch dmg every .dmgtime intervall when blocked
561 void spawnfunc_func_bobbing()
564 if (self.noise != "")
566 precache_sound(self.noise);
567 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
573 // center of bobbing motion
574 self.destvec = self.origin;
575 // time scale to get degrees
576 self.cnt = 360 / self.speed;
578 self.active = ACTIVE_ACTIVE;
580 // damage when blocked
581 self.blocked = generic_plat_blocked;
582 if(self.dmg & (!self.message))
583 self.message = " was squished";
584 if(self.dmg && (!self.message2))
585 self.message2 = "was squished by";
586 if(self.dmg && (!self.dmgtime))
588 self.dmgtime2 = time;
591 if (self.spawnflags & 1) // X
592 self.movedir = '1 0 0' * self.height;
593 else if (self.spawnflags & 2) // Y
594 self.movedir = '0 1 0' * self.height;
596 self.movedir = '0 0 1' * self.height;
598 if not(InitMovingBrushTrigger())
601 // wait for targets to spawn
602 controller = spawn();
603 controller.classname = "func_bobbing_controller";
604 controller.owner = self;
605 controller.nextthink = time + 1;
606 controller.think = func_bobbing_controller_think;
607 self.nextthink = self.ltime + 999999999;
608 self.think = SUB_Null;
610 // Savage: Reduce bandwith, critical on e.g. nexdm02
611 self.effects |= EF_LOWPRECISION;
613 // TODO make a reset function for this one
617 void func_pendulum_controller_think()
620 self.nextthink = time + 0.1;
622 if not (self.owner.active == ACTIVE_ACTIVE)
624 self.owner.avelocity_x = 0;
628 // calculate sinewave using makevectors
629 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
630 v = self.owner.speed * v_forward_y + self.cnt;
631 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
633 // * 10 so it will arrive in 0.1 sec
634 self.owner.avelocity_z = (remainder(v - self.owner.angles_z, 360)) * 10;
638 void spawnfunc_func_pendulum()
641 if (self.noise != "")
643 precache_sound(self.noise);
644 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
647 self.active = ACTIVE_ACTIVE;
649 // keys: angle, speed, phase, noise, freq
653 // not initializing self.dmg to 2, to allow damageless pendulum
655 if(self.dmg & (!self.message))
656 self.message = " was squished";
657 if(self.dmg && (!self.message2))
658 self.message2 = "was squished by";
659 if(self.dmg && (!self.dmgtime))
661 self.dmgtime2 = time;
663 self.blocked = generic_plat_blocked;
665 self.avelocity_z = 0.0000001;
666 if not(InitMovingBrushTrigger())
671 // find pendulum length (same formula as Q3A)
672 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins_z))));
675 // copy initial angle
676 self.cnt = self.angles_z;
678 // wait for targets to spawn
679 controller = spawn();
680 controller.classname = "func_pendulum_controller";
681 controller.owner = self;
682 controller.nextthink = time + 1;
683 controller.think = func_pendulum_controller_think;
684 self.nextthink = self.ltime + 999999999;
685 self.think = SUB_Null;
687 //self.effects |= EF_LOWPRECISION;
689 // TODO make a reset function for this one
692 // button and multiple button
695 void() button_return;
699 self.state = STATE_TOP;
700 self.nextthink = self.ltime + self.wait;
701 self.think = button_return;
702 activator = self.enemy;
704 self.frame = 1; // use alternate textures
709 self.state = STATE_BOTTOM;
714 self.state = STATE_DOWN;
715 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
716 self.frame = 0; // use normal textures
718 self.takedamage = DAMAGE_YES; // can be shot again
722 void button_blocked()
724 // do nothing, just don't come all the way back out
730 self.health = self.max_health;
731 self.takedamage = DAMAGE_NO; // will be reset upon return
733 if (self.state == STATE_UP || self.state == STATE_TOP)
736 if (self.noise != "")
737 sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
739 self.state = STATE_UP;
740 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
745 self.health = self.max_health;
746 setorigin(self, self.pos1);
747 self.frame = 0; // use normal textures
748 self.state = STATE_BOTTOM;
750 self.takedamage = DAMAGE_YES; // can be shot again
755 // if (activator.classname != "player")
757 // dprint(activator.classname);
758 // dprint(" triggered a button\n");
761 if not (self.active == ACTIVE_ACTIVE)
764 self.enemy = activator;
770 // if (activator.classname != "player")
772 // dprint(activator.classname);
773 // dprint(" touched a button\n");
777 if not(other.iscreature)
779 if(other.velocity * self.movedir < 0)
783 self.enemy = other.owner;
787 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
789 if(self.spawnflags & DOOR_NOSPLASH)
790 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
792 self.health = self.health - damage;
793 if (self.health <= 0)
795 // if (activator.classname != "player")
797 // dprint(activator.classname);
798 // dprint(" killed a button\n");
800 self.enemy = damage_attacker;
806 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
807 When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again.
809 "angle" determines the opening direction
810 "target" all entities with a matching targetname will be used
811 "speed" override the default 40 speed
812 "wait" override the default 1 second wait (-1 = never return)
813 "lip" override the default 4 pixel lip remaining at end of move
814 "health" if set, the button must be killed instead of touched. If set to -1, the button will fire on ANY attack, even damageless ones like the MinstaGib laser
821 void spawnfunc_func_button()
825 if not(InitMovingBrushTrigger())
827 self.effects |= EF_LOWPRECISION;
829 self.blocked = button_blocked;
830 self.use = button_use;
832 // if (self.health == 0) // all buttons are now shootable
836 self.max_health = self.health;
837 self.event_damage = button_damage;
838 self.takedamage = DAMAGE_YES;
841 self.touch = button_touch;
851 precache_sound(self.noise);
853 self.active = ACTIVE_ACTIVE;
855 self.pos1 = self.origin;
856 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
857 self.flags |= FL_NOTARGET;
863 float DOOR_START_OPEN = 1;
864 float DOOR_DONT_LINK = 4;
865 float DOOR_TOGGLE = 32;
869 Doors are similar to buttons, but can spawn a fat trigger field around them
870 to open without a touch, and they link together to form simultanious
873 Door.owner is the master door. If there is only one door, it points to itself.
874 If multiple doors, all will point to a single one.
876 Door.enemy chains from the master door through all doors linked in the chain.
881 =============================================================================
885 =============================================================================
890 void() door_rotating_go_down;
891 void() door_rotating_go_up;
896 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
897 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
900 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
901 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
903 //Dont chamge direction for dead or dying stuff
904 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
907 if (self.state == STATE_DOWN)
908 if (self.classname == "door")
913 door_rotating_go_up ();
916 if (self.classname == "door")
921 door_rotating_go_down ();
925 //gib dying stuff just to make sure
926 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
927 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
931 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
932 // if a door has a negative wait, it would never come back if blocked,
933 // so let it just squash the object to death real fast
934 /* if (self.wait >= 0)
936 if (self.state == STATE_DOWN)
947 if (self.noise1 != "")
948 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
949 self.state = STATE_TOP;
950 if (self.spawnflags & DOOR_TOGGLE)
951 return; // don't come down automatically
952 if (self.classname == "door")
954 self.think = door_go_down;
957 self.think = door_rotating_go_down;
959 self.nextthink = self.ltime + self.wait;
962 void door_hit_bottom()
964 if (self.noise1 != "")
965 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
966 self.state = STATE_BOTTOM;
971 if (self.noise2 != "")
972 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
975 self.takedamage = DAMAGE_YES;
976 self.health = self.max_health;
979 self.state = STATE_DOWN;
980 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
985 if (self.state == STATE_UP)
986 return; // already going up
988 if (self.state == STATE_TOP)
989 { // reset top wait time
990 self.nextthink = self.ltime + self.wait;
994 if (self.noise2 != "")
995 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
996 self.state = STATE_UP;
997 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
1000 oldmessage = self.message;
1003 self.message = oldmessage;
1009 =============================================================================
1011 ACTIVATION FUNCTIONS
1013 =============================================================================
1016 float door_check_keys(void) {
1026 if not(door.itemkeys)
1029 // this door require a key
1030 // only a player can have a key
1031 if (other.classname != "player")
1034 if (item_keys_usekey(door, other)) {
1035 // some keys were used
1036 if (other.key_door_messagetime <= time) {
1037 play2(other, "misc/talk.wav");
1038 centerprint(other, strcat("You also need ", item_keys_keylist(door.itemkeys), "!"));
1039 other.key_door_messagetime = time + 2;
1042 // no keys were used
1043 if (other.key_door_messagetime <= time) {
1044 play2(other, "misc/talk.wav");
1045 centerprint(other, strcat("You need ", item_keys_keylist(door.itemkeys), "!"));
1046 other.key_door_messagetime = time + 2;
1050 if (door.itemkeys) {
1051 // door is now unlocked
1052 play2(other, "misc/talk.wav");
1053 centerprint(other, "Door unlocked!");
1065 if (self.owner != self)
1066 objerror ("door_fire: self.owner != self");
1070 if (self.spawnflags & DOOR_TOGGLE)
1072 if (self.state == STATE_UP || self.state == STATE_TOP)
1077 if (self.classname == "door")
1083 door_rotating_go_down ();
1086 } while ( (self != starte) && (self != world) );
1092 // trigger all paired doors
1096 if (self.classname == "door")
1101 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
1102 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
1104 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
1105 self.pos2 = '0 0 0' - self.pos2;
1107 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
1108 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
1109 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
1111 door_rotating_go_up ();
1115 } while ( (self != starte) && (self != world) );
1124 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
1136 void door_trigger_touch()
1138 if (other.health < 1)
1139 if not(other.iscreature && other.deadflag == DEAD_NO)
1142 if (time < self.attack_finished_single)
1145 // check if door is locked
1146 if (!door_check_keys())
1149 self.attack_finished_single = time + 1;
1158 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1161 if(self.spawnflags & DOOR_NOSPLASH)
1162 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
1164 self.health = self.health - damage;
1166 if (self.itemkeys) {
1167 // don't allow opening doors through damage if keys are required
1171 if (self.health <= 0)
1175 self.health = self.max_health;
1176 self.takedamage = DAMAGE_NO; // wil be reset upon return
1192 if(other.classname != "player")
1194 if (self.owner.attack_finished_single > time)
1197 self.owner.attack_finished_single = time + 2;
1199 if (!(self.owner.dmg) && (self.owner.message != ""))
1201 if (other.flags & FL_CLIENT)
1202 centerprint (other, self.owner.message);
1203 play2(other, "misc/talk.wav");
1208 void door_generic_plat_blocked()
1211 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1212 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1215 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1216 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1218 //Dont chamge direction for dead or dying stuff
1219 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1222 if (self.state == STATE_DOWN)
1223 door_rotating_go_up ();
1225 door_rotating_go_down ();
1228 //gib dying stuff just to make sure
1229 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1230 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1234 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1235 // if a door has a negative wait, it would never come back if blocked,
1236 // so let it just squash the object to death real fast
1237 /* if (self.wait >= 0)
1239 if (self.state == STATE_DOWN)
1240 door_rotating_go_up ();
1242 door_rotating_go_down ();
1248 void door_rotating_hit_top()
1250 if (self.noise1 != "")
1251 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1252 self.state = STATE_TOP;
1253 if (self.spawnflags & DOOR_TOGGLE)
1254 return; // don't come down automatically
1255 self.think = door_rotating_go_down;
1256 self.nextthink = self.ltime + self.wait;
1259 void door_rotating_hit_bottom()
1261 if (self.noise1 != "")
1262 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1263 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1265 self.pos2 = '0 0 0' - self.pos2;
1268 self.state = STATE_BOTTOM;
1271 void door_rotating_go_down()
1273 if (self.noise2 != "")
1274 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1275 if (self.max_health)
1277 self.takedamage = DAMAGE_YES;
1278 self.health = self.max_health;
1281 self.state = STATE_DOWN;
1282 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
1285 void door_rotating_go_up()
1287 if (self.state == STATE_UP)
1288 return; // already going up
1290 if (self.state == STATE_TOP)
1291 { // reset top wait time
1292 self.nextthink = self.ltime + self.wait;
1295 if (self.noise2 != "")
1296 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1297 self.state = STATE_UP;
1298 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
1301 oldmessage = self.message;
1304 self.message = oldmessage;
1311 =============================================================================
1315 =============================================================================
1319 entity spawn_field(vector fmins, vector fmaxs)
1325 trigger.classname = "doortriggerfield";
1326 trigger.movetype = MOVETYPE_NONE;
1327 trigger.solid = SOLID_TRIGGER;
1328 trigger.owner = self;
1329 trigger.touch = door_trigger_touch;
1333 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1338 float EntitiesTouching(entity e1, entity e2)
1340 if (e1.absmin_x > e2.absmax_x)
1342 if (e1.absmin_y > e2.absmax_y)
1344 if (e1.absmin_z > e2.absmax_z)
1346 if (e1.absmax_x < e2.absmin_x)
1348 if (e1.absmax_y < e2.absmin_y)
1350 if (e1.absmax_z < e2.absmin_z)
1366 vector cmins, cmaxs;
1369 return; // already linked by another door
1370 if (self.spawnflags & 4)
1372 self.owner = self.enemy = self;
1380 self.trigger_field = spawn_field(self.absmin, self.absmax);
1382 return; // don't want to link this door
1385 cmins = self.absmin;
1386 cmaxs = self.absmax;
1393 self.owner = starte; // master door
1396 starte.health = self.health;
1398 starte.targetname = self.targetname;
1399 if (self.message != "")
1400 starte.message = self.message;
1402 t = find(t, classname, self.classname);
1405 self.enemy = starte; // make the chain a loop
1407 // shootable, or triggered doors just needed the owner/enemy links,
1408 // they don't spawn a field
1419 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1424 if (EntitiesTouching(self,t))
1427 objerror ("cross connected doors");
1432 if (t.absmin_x < cmins_x)
1433 cmins_x = t.absmin_x;
1434 if (t.absmin_y < cmins_y)
1435 cmins_y = t.absmin_y;
1436 if (t.absmin_z < cmins_z)
1437 cmins_z = t.absmin_z;
1438 if (t.absmax_x > cmaxs_x)
1439 cmaxs_x = t.absmax_x;
1440 if (t.absmax_y > cmaxs_y)
1441 cmaxs_y = t.absmax_y;
1442 if (t.absmax_z > cmaxs_z)
1443 cmaxs_z = t.absmax_z;
1450 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
1451 if two doors touch, they are assumed to be connected and operate as a unit.
1453 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1455 START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors).
1457 GOLD_KEY causes the door to open only if the activator holds a gold key.
1459 SILVER_KEY causes the door to open only if the activator holds a silver key.
1461 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1462 "angle" determines the opening direction
1463 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1464 "health" if set, door must be shot open
1465 "speed" movement speed (100 default)
1466 "wait" wait before returning (3 default, -1 = never return)
1467 "lip" lip remaining at end of move (8 default)
1468 "dmg" damage to inflict when blocked (2 default)
1475 FIXME: only one sound set available at the time being
1479 void door_init_startopen()
1481 setorigin (self, self.pos2);
1482 self.pos2 = self.pos1;
1483 self.pos1 = self.origin;
1488 setorigin(self, self.pos1);
1489 self.velocity = '0 0 0';
1490 self.state = STATE_BOTTOM;
1491 self.think = SUB_Null;
1494 // spawnflags require key (for now only func_door)
1495 #define SPAWNFLAGS_GOLD_KEY 8
1496 #define SPAWNFLAGS_SILVER_KEY 16
1497 void spawnfunc_func_door()
1499 // Quake 1 keys compatibility
1500 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
1501 self.itemkeys |= ITEM_KEY_BIT(0);
1502 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
1503 self.itemkeys |= ITEM_KEY_BIT(1);
1505 //if (!self.deathtype) // map makers can override this
1506 // self.deathtype = " got in the way";
1509 self.max_health = self.health;
1510 if not(InitMovingBrushTrigger())
1512 self.effects |= EF_LOWPRECISION;
1513 self.classname = "door";
1515 self.blocked = door_blocked;
1516 self.use = door_use;
1518 // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
1519 // if(self.spawnflags & 8)
1520 // self.dmg = 10000;
1522 if(self.dmg && (!self.message))
1523 self.message = "was squished";
1524 if(self.dmg && (!self.message2))
1525 self.message2 = "was squished by";
1527 if (self.sounds > 0)
1529 precache_sound ("plats/medplat1.wav");
1530 precache_sound ("plats/medplat2.wav");
1531 self.noise2 = "plats/medplat1.wav";
1532 self.noise1 = "plats/medplat2.wav";
1542 self.pos1 = self.origin;
1543 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1545 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1546 // but spawn in the open position
1547 if (self.spawnflags & DOOR_START_OPEN)
1548 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1550 self.state = STATE_BOTTOM;
1554 self.takedamage = DAMAGE_YES;
1555 self.event_damage = door_damage;
1561 self.touch = door_touch;
1563 // LinkDoors can't be done until all of the doors have been spawned, so
1564 // the sizes can be detected properly.
1565 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1567 self.reset = door_reset;
1570 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1571 if two doors touch, they are assumed to be connected and operate as a unit.
1573 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1575 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1576 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1577 must have set trigger_reverse to 1.
1578 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1580 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).
1582 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1583 "angle" determines the destination angle for opening. negative values reverse the direction.
1584 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1585 "health" if set, door must be shot open
1586 "speed" movement speed (100 default)
1587 "wait" wait before returning (3 default, -1 = never return)
1588 "dmg" damage to inflict when blocked (2 default)
1595 FIXME: only one sound set available at the time being
1598 void door_rotating_reset()
1600 self.angles = self.pos1;
1601 self.avelocity = '0 0 0';
1602 self.state = STATE_BOTTOM;
1603 self.think = SUB_Null;
1606 void door_rotating_init_startopen()
1608 self.angles = self.movedir;
1609 self.pos2 = '0 0 0';
1610 self.pos1 = self.movedir;
1614 void spawnfunc_func_door_rotating()
1617 //if (!self.deathtype) // map makers can override this
1618 // self.deathtype = " got in the way";
1620 // I abuse "movedir" for denoting the axis for now
1621 if (self.spawnflags & 64) // X (untested)
1622 self.movedir = '0 0 1';
1623 else if (self.spawnflags & 128) // Y (untested)
1624 self.movedir = '1 0 0';
1626 self.movedir = '0 1 0';
1628 if (self.angles_y==0) self.angles_y = 90;
1630 self.movedir = self.movedir * self.angles_y;
1631 self.angles = '0 0 0';
1633 self.max_health = self.health;
1634 self.avelocity = self.movedir;
1635 if not(InitMovingBrushTrigger())
1637 self.velocity = '0 0 0';
1638 //self.effects |= EF_LOWPRECISION;
1639 self.classname = "door_rotating";
1641 self.blocked = door_blocked;
1642 self.use = door_use;
1644 if(self.spawnflags & 8)
1647 if(self.dmg && (!self.message))
1648 self.message = "was squished";
1649 if(self.dmg && (!self.message2))
1650 self.message2 = "was squished by";
1652 if (self.sounds > 0)
1654 precache_sound ("plats/medplat1.wav");
1655 precache_sound ("plats/medplat2.wav");
1656 self.noise2 = "plats/medplat1.wav";
1657 self.noise1 = "plats/medplat2.wav";
1664 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1666 self.pos1 = '0 0 0';
1667 self.pos2 = self.movedir;
1669 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1670 // but spawn in the open position
1671 if (self.spawnflags & DOOR_START_OPEN)
1672 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1674 self.state = STATE_BOTTOM;
1678 self.takedamage = DAMAGE_YES;
1679 self.event_damage = door_damage;
1685 self.touch = door_touch;
1687 // LinkDoors can't be done until all of the doors have been spawned, so
1688 // the sizes can be detected properly.
1689 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1691 self.reset = door_rotating_reset;
1695 =============================================================================
1699 =============================================================================
1702 void() fd_secret_move1;
1703 void() fd_secret_move2;
1704 void() fd_secret_move3;
1705 void() fd_secret_move4;
1706 void() fd_secret_move5;
1707 void() fd_secret_move6;
1708 void() fd_secret_done;
1710 float SECRET_OPEN_ONCE = 1; // stays open
1711 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1712 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1713 float SECRET_NO_SHOOT = 8; // only opened by trigger
1714 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1717 void fd_secret_use()
1720 string message_save;
1722 self.health = 10000;
1723 self.bot_attack = TRUE;
1725 // exit if still moving around...
1726 if (self.origin != self.oldorigin)
1729 message_save = self.message;
1730 self.message = ""; // no more message
1731 SUB_UseTargets(); // fire all targets / killtargets
1732 self.message = message_save;
1734 self.velocity = '0 0 0';
1736 // Make a sound, wait a little...
1738 if (self.noise1 != "")
1739 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1740 self.nextthink = self.ltime + 0.1;
1742 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1743 makevectors(self.mangle);
1747 if (self.spawnflags & SECRET_1ST_DOWN)
1748 self.t_width = fabs(v_up * self.size);
1750 self.t_width = fabs(v_right * self.size);
1754 self.t_length = fabs(v_forward * self.size);
1756 if (self.spawnflags & SECRET_1ST_DOWN)
1757 self.dest1 = self.origin - v_up * self.t_width;
1759 self.dest1 = self.origin + v_right * (self.t_width * temp);
1761 self.dest2 = self.dest1 + v_forward * self.t_length;
1762 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
1763 if (self.noise2 != "")
1764 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1767 // Wait after first movement...
1768 void fd_secret_move1()
1770 self.nextthink = self.ltime + 1.0;
1771 self.think = fd_secret_move2;
1772 if (self.noise3 != "")
1773 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1776 // Start moving sideways w/sound...
1777 void fd_secret_move2()
1779 if (self.noise2 != "")
1780 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1781 SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
1784 // Wait here until time to go back...
1785 void fd_secret_move3()
1787 if (self.noise3 != "")
1788 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1789 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1791 self.nextthink = self.ltime + self.wait;
1792 self.think = fd_secret_move4;
1797 void fd_secret_move4()
1799 if (self.noise2 != "")
1800 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1801 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
1805 void fd_secret_move5()
1807 self.nextthink = self.ltime + 1.0;
1808 self.think = fd_secret_move6;
1809 if (self.noise3 != "")
1810 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1813 void fd_secret_move6()
1815 if (self.noise2 != "")
1816 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1817 SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
1820 void fd_secret_done()
1822 if (self.spawnflags&SECRET_YES_SHOOT)
1824 self.health = 10000;
1825 self.takedamage = DAMAGE_YES;
1826 //self.th_pain = fd_secret_use;
1828 if (self.noise3 != "")
1829 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1832 void secret_blocked()
1834 if (time < self.attack_finished_single)
1836 self.attack_finished_single = time + 0.5;
1837 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1849 if not(other.iscreature)
1851 if (self.attack_finished_single > time)
1854 self.attack_finished_single = time + 2;
1858 if (other.flags & FL_CLIENT)
1859 centerprint (other, self.message);
1860 play2(other, "misc/talk.wav");
1866 if (self.spawnflags&SECRET_YES_SHOOT)
1868 self.health = 10000;
1869 self.takedamage = DAMAGE_YES;
1871 setorigin(self, self.oldorigin);
1872 self.think = SUB_Null;
1875 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1876 Basic secret door. Slides back, then to the side. Angle determines direction.
1877 wait = # of seconds before coming back
1878 1st_left = 1st move is left of arrow
1879 1st_down = 1st move is down from arrow
1880 always_shoot = even if targeted, keep shootable
1881 t_width = override WIDTH to move back (or height if going down)
1882 t_length = override LENGTH to move sideways
1883 "dmg" damage to inflict when blocked (2 default)
1885 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1892 void spawnfunc_func_door_secret()
1894 /*if (!self.deathtype) // map makers can override this
1895 self.deathtype = " got in the way";*/
1901 self.mangle = self.angles;
1902 self.angles = '0 0 0';
1903 self.classname = "door";
1904 if not(InitMovingBrushTrigger())
1906 self.effects |= EF_LOWPRECISION;
1908 self.touch = secret_touch;
1909 self.blocked = secret_blocked;
1911 self.use = fd_secret_use;
1916 self.spawnflags |= SECRET_YES_SHOOT;
1918 if(self.spawnflags&SECRET_YES_SHOOT)
1920 self.health = 10000;
1921 self.takedamage = DAMAGE_YES;
1922 self.event_damage = fd_secret_use;
1924 self.oldorigin = self.origin;
1926 self.wait = 5; // 5 seconds before closing
1928 self.reset = secret_reset;
1932 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1933 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1934 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
1935 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1936 height: amplitude modifier (default 32)
1937 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1938 noise: path/name of looping .wav file to play.
1939 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1943 void func_fourier_controller_think()
1948 self.nextthink = time + 0.1;
1949 if not (self.owner.active == ACTIVE_ACTIVE)
1951 self.owner.velocity = '0 0 0';
1956 n = floor((tokenize_console(self.owner.netname)) / 5);
1957 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1959 v = self.owner.destvec;
1961 for(i = 0; i < n; ++i)
1963 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1964 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;
1967 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1968 // * 10 so it will arrive in 0.1 sec
1969 self.owner.velocity = (v - self.owner.origin) * 10;
1972 void spawnfunc_func_fourier()
1975 if (self.noise != "")
1977 precache_sound(self.noise);
1978 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
1985 self.destvec = self.origin;
1986 self.cnt = 360 / self.speed;
1988 self.blocked = generic_plat_blocked;
1989 if(self.dmg & (!self.message))
1990 self.message = " was squished";
1991 if(self.dmg && (!self.message2))
1992 self.message2 = "was squished by";
1993 if(self.dmg && (!self.dmgtime))
1994 self.dmgtime = 0.25;
1995 self.dmgtime2 = time;
1997 if(self.netname == "")
1998 self.netname = "1 0 0 0 1";
2000 if not(InitMovingBrushTrigger())
2003 self.active = ACTIVE_ACTIVE;
2005 // wait for targets to spawn
2006 controller = spawn();
2007 controller.classname = "func_fourier_controller";
2008 controller.owner = self;
2009 controller.nextthink = time + 1;
2010 controller.think = func_fourier_controller_think;
2011 self.nextthink = self.ltime + 999999999;
2012 self.think = SUB_Null;
2014 // Savage: Reduce bandwith, critical on e.g. nexdm02
2015 self.effects |= EF_LOWPRECISION;
2017 // TODO make a reset function for this one
2020 // reusing some fields havocbots declared
2021 .entity wp00, wp01, wp02, wp03;
2023 .float targetfactor, target2factor, target3factor, target4factor;
2024 .vector targetnormal, target2normal, target3normal, target4normal;
2026 vector func_vectormamamam_origin(entity o, float t)
2038 p = e.origin + t * e.velocity;
2040 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
2042 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
2048 p = e.origin + t * e.velocity;
2050 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
2052 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
2058 p = e.origin + t * e.velocity;
2060 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
2062 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
2068 p = e.origin + t * e.velocity;
2070 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
2072 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
2078 void func_vectormamamam_controller_think()
2080 self.nextthink = time + 0.1;
2082 if not (self.owner.active == ACTIVE_ACTIVE)
2084 self.owner.velocity = '0 0 0';
2088 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
2089 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
2092 void func_vectormamamam_findtarget()
2094 if(self.target != "")
2095 self.wp00 = find(world, targetname, self.target);
2097 if(self.target2 != "")
2098 self.wp01 = find(world, targetname, self.target2);
2100 if(self.target3 != "")
2101 self.wp02 = find(world, targetname, self.target3);
2103 if(self.target4 != "")
2104 self.wp03 = find(world, targetname, self.target4);
2106 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
2107 objerror("No reference entity found, so there is nothing to move. Aborting.");
2109 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
2112 controller = spawn();
2113 controller.classname = "func_vectormamamam_controller";
2114 controller.owner = self;
2115 controller.nextthink = time + 1;
2116 controller.think = func_vectormamamam_controller_think;
2119 void spawnfunc_func_vectormamamam()
2121 if (self.noise != "")
2123 precache_sound(self.noise);
2124 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
2127 if(!self.targetfactor)
2128 self.targetfactor = 1;
2130 if(!self.target2factor)
2131 self.target2factor = 1;
2133 if(!self.target3factor)
2134 self.target3factor = 1;
2136 if(!self.target4factor)
2137 self.target4factor = 1;
2139 if(vlen(self.targetnormal))
2140 self.targetnormal = normalize(self.targetnormal);
2142 if(vlen(self.target2normal))
2143 self.target2normal = normalize(self.target2normal);
2145 if(vlen(self.target3normal))
2146 self.target3normal = normalize(self.target3normal);
2148 if(vlen(self.target4normal))
2149 self.target4normal = normalize(self.target4normal);
2151 self.blocked = generic_plat_blocked;
2152 if(self.dmg & (!self.message))
2153 self.message = " was squished";
2154 if(self.dmg && (!self.message2))
2155 self.message2 = "was squished by";
2156 if(self.dmg && (!self.dmgtime))
2157 self.dmgtime = 0.25;
2158 self.dmgtime2 = time;
2160 if(self.netname == "")
2161 self.netname = "1 0 0 0 1";
2163 if not(InitMovingBrushTrigger())
2166 // wait for targets to spawn
2167 self.nextthink = self.ltime + 999999999;
2168 self.think = SUB_Null;
2170 // Savage: Reduce bandwith, critical on e.g. nexdm02
2171 self.effects |= EF_LOWPRECISION;
2173 self.active = ACTIVE_ACTIVE;
2175 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
2178 void conveyor_think()
2182 // set myself as current conveyor where possible
2183 for(e = world; (e = findentity(e, conveyor, self)); )
2188 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2189 if(!e.conveyor.state)
2192 vector emin = e.absmin;
2193 vector emax = e.absmax;
2194 if(self.solid == SOLID_BSP)
2199 if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2200 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2204 for(e = world; (e = findentity(e, conveyor, self)); )
2206 if(e.flags & FL_CLIENT) // doing it via velocity has quite some advantages
2207 continue; // done in SV_PlayerPhysics
2209 setorigin(e, e.origin + self.movedir * sys_frametime);
2210 move_out_of_solid(e);
2211 UpdateCSQCProjectile(e);
2213 // stupid conveyor code
2214 tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2215 if(trace_fraction > 0)
2216 setorigin(e, trace_endpos);
2221 self.nextthink = time;
2226 self.state = !self.state;
2229 void conveyor_reset()
2231 self.state = (self.spawnflags & 1);
2234 void conveyor_init()
2238 self.movedir = self.movedir * self.speed;
2239 self.think = conveyor_think;
2240 self.nextthink = time;
2243 self.use = conveyor_use;
2244 self.reset = conveyor_reset;
2251 void spawnfunc_trigger_conveyor()
2258 void spawnfunc_func_conveyor()
2261 InitMovingBrushTrigger();
2262 self.movetype = MOVETYPE_NONE;