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.bezier_turn && !self.train_wait_turning)
306 targ = find(world, targetname, self.target);
307 if(self.spawnflags & 1) // bezier curves movement
309 org = normalize(targ.origin);
312 SUB_CalcAngleMove(org, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
313 self.train_wait_turning = TRUE;
318 self.angles = vectoangles(targ.origin - self.origin);
323 org = normalize(targ.origin);
326 SUB_CalcAngleMove(org, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
327 self.train_wait_turning = TRUE;
332 self.angles = vectoangles(targ.origin - self.origin);
338 stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
340 if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
342 self.train_wait_turning = FALSE;
347 self.think = train_next;
348 self.nextthink = self.ltime + self.wait;
357 targ = find(world, targetname, self.target);
358 self.target = targ.target;
359 if (self.spawnflags & 1)
363 cp = find(world, targetname, targ.curve); // get its second target (the control point)
364 cp_org = cp.origin - self.mins; // no control point found, assume a straight line to the destination
370 objerror("train_next: no next target");
371 self.wait = targ.wait;
375 if(targ.platmovetype)
377 // this path_corner contains a movetype overrider, apply it
378 self.platmovetype_start = targ.platmovetype_start;
379 self.platmovetype_end = targ.platmovetype_end;
383 // this path_corner doesn't contain a movetype overrider, use the train's defaults
384 self.platmovetype_start = self.platmovetype_start_default;
385 self.platmovetype_end = self.platmovetype_end_default;
391 SUB_CalcMove_Bezier(cp_org, targ.origin - self.mins, TSPEED_LINEAR, targ.speed, train_wait);
393 SUB_CalcMove(targ.origin - self.mins, TSPEED_LINEAR, targ.speed, train_wait);
398 SUB_CalcMove_Bezier(cp_org, targ.origin - self.mins, TSPEED_LINEAR, self.speed, train_wait);
400 SUB_CalcMove(targ.origin - self.mins, TSPEED_LINEAR, self.speed, train_wait);
404 sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
407 void func_train_find()
410 targ = find(world, targetname, self.target);
411 self.target = targ.target;
413 objerror("func_train_find: no next target");
414 setorigin(self, targ.origin - self.mins);
415 self.nextthink = self.ltime + 1;
416 self.think = train_next;
419 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
420 Ridable platform, targets spawnfunc_path_corner path to follow.
421 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
422 target : targetname of first spawnfunc_path_corner (starts here)
424 void spawnfunc_func_train()
426 if (self.noise != "")
427 precache_sound(self.noise);
430 objerror("func_train without a target");
433 if (self.spawnflags & 2)
434 self.bezier_turn = TRUE;
436 if not(InitMovingBrushTrigger())
438 self.effects |= EF_LOWPRECISION;
440 // wait for targets to spawn
441 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
443 self.blocked = generic_plat_blocked;
444 if(self.dmg & (!self.message))
445 self.message = " was squished";
446 if(self.dmg && (!self.message2))
447 self.message2 = "was squished by";
448 if(self.dmg && (!self.dmgtime))
450 self.dmgtime2 = time;
452 if(!set_platmovetype(self, self.platmovetype))
454 self.platmovetype_start_default = self.platmovetype_start;
455 self.platmovetype_end_default = self.platmovetype_end;
457 // TODO make a reset function for this one
460 void func_rotating_setactive(float astate)
463 if (astate == ACTIVE_TOGGLE)
465 if(self.active == ACTIVE_ACTIVE)
466 self.active = ACTIVE_NOT;
468 self.active = ACTIVE_ACTIVE;
471 self.active = astate;
473 if(self.active == ACTIVE_NOT)
474 self.avelocity = '0 0 0';
476 self.avelocity = self.pos1;
479 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
480 Brush model that spins in place on one axis (default Z).
481 speed : speed to rotate (in degrees per second)
482 noise : path/name of looping .wav file to play.
483 dmg : Do this mutch dmg every .dmgtime intervall when blocked
487 void spawnfunc_func_rotating()
489 if (self.noise != "")
491 precache_sound(self.noise);
492 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
495 self.active = ACTIVE_ACTIVE;
496 self.setactive = func_rotating_setactive;
500 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
501 if (self.spawnflags & 4) // X (untested)
502 self.avelocity = '0 0 1' * self.speed;
503 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
504 else if (self.spawnflags & 8) // Y (untested)
505 self.avelocity = '1 0 0' * self.speed;
506 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
508 self.avelocity = '0 1 0' * self.speed;
510 self.pos1 = self.avelocity;
512 if(self.dmg & (!self.message))
513 self.message = " was squished";
514 if(self.dmg && (!self.message2))
515 self.message2 = "was squished by";
518 if(self.dmg && (!self.dmgtime))
521 self.dmgtime2 = time;
523 if not(InitMovingBrushTrigger())
525 // no EF_LOWPRECISION here, as rounding angles is bad
527 self.blocked = generic_plat_blocked;
529 // wait for targets to spawn
530 self.nextthink = self.ltime + 999999999;
531 self.think = SUB_Null;
533 // TODO make a reset function for this one
537 void func_bobbing_controller_think()
540 self.nextthink = time + 0.1;
542 if not (self.owner.active == ACTIVE_ACTIVE)
544 self.owner.velocity = '0 0 0';
548 // calculate sinewave using makevectors
549 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
550 v = self.owner.destvec + self.owner.movedir * v_forward_y;
551 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
552 // * 10 so it will arrive in 0.1 sec
553 self.owner.velocity = (v - self.owner.origin) * 10;
556 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
557 Brush model that moves back and forth on one axis (default Z).
558 speed : how long one cycle takes in seconds (default 4)
559 height : how far the cycle moves (default 32)
560 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
561 noise : path/name of looping .wav file to play.
562 dmg : Do this mutch dmg every .dmgtime intervall when blocked
565 void spawnfunc_func_bobbing()
568 if (self.noise != "")
570 precache_sound(self.noise);
571 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
577 // center of bobbing motion
578 self.destvec = self.origin;
579 // time scale to get degrees
580 self.cnt = 360 / self.speed;
582 self.active = ACTIVE_ACTIVE;
584 // damage when blocked
585 self.blocked = generic_plat_blocked;
586 if(self.dmg & (!self.message))
587 self.message = " was squished";
588 if(self.dmg && (!self.message2))
589 self.message2 = "was squished by";
590 if(self.dmg && (!self.dmgtime))
592 self.dmgtime2 = time;
595 if (self.spawnflags & 1) // X
596 self.movedir = '1 0 0' * self.height;
597 else if (self.spawnflags & 2) // Y
598 self.movedir = '0 1 0' * self.height;
600 self.movedir = '0 0 1' * self.height;
602 if not(InitMovingBrushTrigger())
605 // wait for targets to spawn
606 controller = spawn();
607 controller.classname = "func_bobbing_controller";
608 controller.owner = self;
609 controller.nextthink = time + 1;
610 controller.think = func_bobbing_controller_think;
611 self.nextthink = self.ltime + 999999999;
612 self.think = SUB_Null;
614 // Savage: Reduce bandwith, critical on e.g. nexdm02
615 self.effects |= EF_LOWPRECISION;
617 // TODO make a reset function for this one
621 void func_pendulum_controller_think()
624 self.nextthink = time + 0.1;
626 if not (self.owner.active == ACTIVE_ACTIVE)
628 self.owner.avelocity_x = 0;
632 // calculate sinewave using makevectors
633 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
634 v = self.owner.speed * v_forward_y + self.cnt;
635 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
637 // * 10 so it will arrive in 0.1 sec
638 self.owner.avelocity_z = (remainder(v - self.owner.angles_z, 360)) * 10;
642 void spawnfunc_func_pendulum()
645 if (self.noise != "")
647 precache_sound(self.noise);
648 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
651 self.active = ACTIVE_ACTIVE;
653 // keys: angle, speed, phase, noise, freq
657 // not initializing self.dmg to 2, to allow damageless pendulum
659 if(self.dmg & (!self.message))
660 self.message = " was squished";
661 if(self.dmg && (!self.message2))
662 self.message2 = "was squished by";
663 if(self.dmg && (!self.dmgtime))
665 self.dmgtime2 = time;
667 self.blocked = generic_plat_blocked;
669 self.avelocity_z = 0.0000001;
670 if not(InitMovingBrushTrigger())
675 // find pendulum length (same formula as Q3A)
676 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins_z))));
679 // copy initial angle
680 self.cnt = self.angles_z;
682 // wait for targets to spawn
683 controller = spawn();
684 controller.classname = "func_pendulum_controller";
685 controller.owner = self;
686 controller.nextthink = time + 1;
687 controller.think = func_pendulum_controller_think;
688 self.nextthink = self.ltime + 999999999;
689 self.think = SUB_Null;
691 //self.effects |= EF_LOWPRECISION;
693 // TODO make a reset function for this one
696 // button and multiple button
699 void() button_return;
703 self.state = STATE_TOP;
704 self.nextthink = self.ltime + self.wait;
705 self.think = button_return;
706 activator = self.enemy;
708 self.frame = 1; // use alternate textures
713 self.state = STATE_BOTTOM;
718 self.state = STATE_DOWN;
719 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
720 self.frame = 0; // use normal textures
722 self.takedamage = DAMAGE_YES; // can be shot again
726 void button_blocked()
728 // do nothing, just don't come all the way back out
734 self.health = self.max_health;
735 self.takedamage = DAMAGE_NO; // will be reset upon return
737 if (self.state == STATE_UP || self.state == STATE_TOP)
740 if (self.noise != "")
741 sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
743 self.state = STATE_UP;
744 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
749 self.health = self.max_health;
750 setorigin(self, self.pos1);
751 self.frame = 0; // use normal textures
752 self.state = STATE_BOTTOM;
754 self.takedamage = DAMAGE_YES; // can be shot again
759 // if (activator.classname != "player")
761 // dprint(activator.classname);
762 // dprint(" triggered a button\n");
765 if not (self.active == ACTIVE_ACTIVE)
768 self.enemy = activator;
774 // if (activator.classname != "player")
776 // dprint(activator.classname);
777 // dprint(" touched a button\n");
781 if not(other.iscreature)
783 if(other.velocity * self.movedir < 0)
787 self.enemy = other.owner;
791 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
793 if(self.spawnflags & DOOR_NOSPLASH)
794 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
796 self.health = self.health - damage;
797 if (self.health <= 0)
799 // if (activator.classname != "player")
801 // dprint(activator.classname);
802 // dprint(" killed a button\n");
804 self.enemy = damage_attacker;
810 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
811 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.
813 "angle" determines the opening direction
814 "target" all entities with a matching targetname will be used
815 "speed" override the default 40 speed
816 "wait" override the default 1 second wait (-1 = never return)
817 "lip" override the default 4 pixel lip remaining at end of move
818 "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
825 void spawnfunc_func_button()
829 if not(InitMovingBrushTrigger())
831 self.effects |= EF_LOWPRECISION;
833 self.blocked = button_blocked;
834 self.use = button_use;
836 // if (self.health == 0) // all buttons are now shootable
840 self.max_health = self.health;
841 self.event_damage = button_damage;
842 self.takedamage = DAMAGE_YES;
845 self.touch = button_touch;
855 precache_sound(self.noise);
857 self.active = ACTIVE_ACTIVE;
859 self.pos1 = self.origin;
860 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
861 self.flags |= FL_NOTARGET;
867 float DOOR_START_OPEN = 1;
868 float DOOR_DONT_LINK = 4;
869 float DOOR_TOGGLE = 32;
873 Doors are similar to buttons, but can spawn a fat trigger field around them
874 to open without a touch, and they link together to form simultanious
877 Door.owner is the master door. If there is only one door, it points to itself.
878 If multiple doors, all will point to a single one.
880 Door.enemy chains from the master door through all doors linked in the chain.
885 =============================================================================
889 =============================================================================
894 void() door_rotating_go_down;
895 void() door_rotating_go_up;
900 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
901 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
904 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
905 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
907 //Dont chamge direction for dead or dying stuff
908 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
911 if (self.state == STATE_DOWN)
912 if (self.classname == "door")
917 door_rotating_go_up ();
920 if (self.classname == "door")
925 door_rotating_go_down ();
929 //gib dying stuff just to make sure
930 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
931 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
935 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
936 // if a door has a negative wait, it would never come back if blocked,
937 // so let it just squash the object to death real fast
938 /* if (self.wait >= 0)
940 if (self.state == STATE_DOWN)
951 if (self.noise1 != "")
952 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
953 self.state = STATE_TOP;
954 if (self.spawnflags & DOOR_TOGGLE)
955 return; // don't come down automatically
956 if (self.classname == "door")
958 self.think = door_go_down;
961 self.think = door_rotating_go_down;
963 self.nextthink = self.ltime + self.wait;
966 void door_hit_bottom()
968 if (self.noise1 != "")
969 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
970 self.state = STATE_BOTTOM;
975 if (self.noise2 != "")
976 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
979 self.takedamage = DAMAGE_YES;
980 self.health = self.max_health;
983 self.state = STATE_DOWN;
984 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
989 if (self.state == STATE_UP)
990 return; // already going up
992 if (self.state == STATE_TOP)
993 { // reset top wait time
994 self.nextthink = self.ltime + self.wait;
998 if (self.noise2 != "")
999 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1000 self.state = STATE_UP;
1001 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
1004 oldmessage = self.message;
1007 self.message = oldmessage;
1013 =============================================================================
1015 ACTIVATION FUNCTIONS
1017 =============================================================================
1020 float door_check_keys(void) {
1030 if not(door.itemkeys)
1033 // this door require a key
1034 // only a player can have a key
1035 if (other.classname != "player")
1038 if (item_keys_usekey(door, other)) {
1039 // some keys were used
1040 if (other.key_door_messagetime <= time) {
1041 play2(other, "misc/talk.wav");
1042 centerprint(other, strcat("You also need ", item_keys_keylist(door.itemkeys), "!"));
1043 other.key_door_messagetime = time + 2;
1046 // no keys were used
1047 if (other.key_door_messagetime <= time) {
1048 play2(other, "misc/talk.wav");
1049 centerprint(other, strcat("You need ", item_keys_keylist(door.itemkeys), "!"));
1050 other.key_door_messagetime = time + 2;
1054 if (door.itemkeys) {
1055 // door is now unlocked
1056 play2(other, "misc/talk.wav");
1057 centerprint(other, "Door unlocked!");
1069 if (self.owner != self)
1070 objerror ("door_fire: self.owner != self");
1074 if (self.spawnflags & DOOR_TOGGLE)
1076 if (self.state == STATE_UP || self.state == STATE_TOP)
1081 if (self.classname == "door")
1087 door_rotating_go_down ();
1090 } while ( (self != starte) && (self != world) );
1096 // trigger all paired doors
1100 if (self.classname == "door")
1105 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
1106 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
1108 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
1109 self.pos2 = '0 0 0' - self.pos2;
1111 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
1112 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
1113 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
1115 door_rotating_go_up ();
1119 } while ( (self != starte) && (self != world) );
1128 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
1140 void door_trigger_touch()
1142 if (other.health < 1)
1143 if not(other.iscreature && other.deadflag == DEAD_NO)
1146 if (time < self.attack_finished_single)
1149 // check if door is locked
1150 if (!door_check_keys())
1153 self.attack_finished_single = time + 1;
1162 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1165 if(self.spawnflags & DOOR_NOSPLASH)
1166 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
1168 self.health = self.health - damage;
1170 if (self.itemkeys) {
1171 // don't allow opening doors through damage if keys are required
1175 if (self.health <= 0)
1179 self.health = self.max_health;
1180 self.takedamage = DAMAGE_NO; // wil be reset upon return
1196 if(other.classname != "player")
1198 if (self.owner.attack_finished_single > time)
1201 self.owner.attack_finished_single = time + 2;
1203 if (!(self.owner.dmg) && (self.owner.message != ""))
1205 if (other.flags & FL_CLIENT)
1206 centerprint (other, self.owner.message);
1207 play2(other, "misc/talk.wav");
1212 void door_generic_plat_blocked()
1215 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1216 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1219 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1220 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1222 //Dont chamge direction for dead or dying stuff
1223 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1226 if (self.state == STATE_DOWN)
1227 door_rotating_go_up ();
1229 door_rotating_go_down ();
1232 //gib dying stuff just to make sure
1233 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1234 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1238 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1239 // if a door has a negative wait, it would never come back if blocked,
1240 // so let it just squash the object to death real fast
1241 /* if (self.wait >= 0)
1243 if (self.state == STATE_DOWN)
1244 door_rotating_go_up ();
1246 door_rotating_go_down ();
1252 void door_rotating_hit_top()
1254 if (self.noise1 != "")
1255 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1256 self.state = STATE_TOP;
1257 if (self.spawnflags & DOOR_TOGGLE)
1258 return; // don't come down automatically
1259 self.think = door_rotating_go_down;
1260 self.nextthink = self.ltime + self.wait;
1263 void door_rotating_hit_bottom()
1265 if (self.noise1 != "")
1266 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1267 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1269 self.pos2 = '0 0 0' - self.pos2;
1272 self.state = STATE_BOTTOM;
1275 void door_rotating_go_down()
1277 if (self.noise2 != "")
1278 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1279 if (self.max_health)
1281 self.takedamage = DAMAGE_YES;
1282 self.health = self.max_health;
1285 self.state = STATE_DOWN;
1286 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
1289 void door_rotating_go_up()
1291 if (self.state == STATE_UP)
1292 return; // already going up
1294 if (self.state == STATE_TOP)
1295 { // reset top wait time
1296 self.nextthink = self.ltime + self.wait;
1299 if (self.noise2 != "")
1300 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1301 self.state = STATE_UP;
1302 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
1305 oldmessage = self.message;
1308 self.message = oldmessage;
1315 =============================================================================
1319 =============================================================================
1323 entity spawn_field(vector fmins, vector fmaxs)
1329 trigger.classname = "doortriggerfield";
1330 trigger.movetype = MOVETYPE_NONE;
1331 trigger.solid = SOLID_TRIGGER;
1332 trigger.owner = self;
1333 trigger.touch = door_trigger_touch;
1337 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1342 float EntitiesTouching(entity e1, entity e2)
1344 if (e1.absmin_x > e2.absmax_x)
1346 if (e1.absmin_y > e2.absmax_y)
1348 if (e1.absmin_z > e2.absmax_z)
1350 if (e1.absmax_x < e2.absmin_x)
1352 if (e1.absmax_y < e2.absmin_y)
1354 if (e1.absmax_z < e2.absmin_z)
1370 vector cmins, cmaxs;
1373 return; // already linked by another door
1374 if (self.spawnflags & 4)
1376 self.owner = self.enemy = self;
1384 self.trigger_field = spawn_field(self.absmin, self.absmax);
1386 return; // don't want to link this door
1389 cmins = self.absmin;
1390 cmaxs = self.absmax;
1397 self.owner = starte; // master door
1400 starte.health = self.health;
1402 starte.targetname = self.targetname;
1403 if (self.message != "")
1404 starte.message = self.message;
1406 t = find(t, classname, self.classname);
1409 self.enemy = starte; // make the chain a loop
1411 // shootable, or triggered doors just needed the owner/enemy links,
1412 // they don't spawn a field
1423 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1428 if (EntitiesTouching(self,t))
1431 objerror ("cross connected doors");
1436 if (t.absmin_x < cmins_x)
1437 cmins_x = t.absmin_x;
1438 if (t.absmin_y < cmins_y)
1439 cmins_y = t.absmin_y;
1440 if (t.absmin_z < cmins_z)
1441 cmins_z = t.absmin_z;
1442 if (t.absmax_x > cmaxs_x)
1443 cmaxs_x = t.absmax_x;
1444 if (t.absmax_y > cmaxs_y)
1445 cmaxs_y = t.absmax_y;
1446 if (t.absmax_z > cmaxs_z)
1447 cmaxs_z = t.absmax_z;
1454 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
1455 if two doors touch, they are assumed to be connected and operate as a unit.
1457 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1459 START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors).
1461 GOLD_KEY causes the door to open only if the activator holds a gold key.
1463 SILVER_KEY causes the door to open only if the activator holds a silver key.
1465 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1466 "angle" determines the opening direction
1467 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1468 "health" if set, door must be shot open
1469 "speed" movement speed (100 default)
1470 "wait" wait before returning (3 default, -1 = never return)
1471 "lip" lip remaining at end of move (8 default)
1472 "dmg" damage to inflict when blocked (2 default)
1479 FIXME: only one sound set available at the time being
1483 void door_init_startopen()
1485 setorigin (self, self.pos2);
1486 self.pos2 = self.pos1;
1487 self.pos1 = self.origin;
1492 setorigin(self, self.pos1);
1493 self.velocity = '0 0 0';
1494 self.state = STATE_BOTTOM;
1495 self.think = SUB_Null;
1498 // spawnflags require key (for now only func_door)
1499 #define SPAWNFLAGS_GOLD_KEY 8
1500 #define SPAWNFLAGS_SILVER_KEY 16
1501 void spawnfunc_func_door()
1503 // Quake 1 keys compatibility
1504 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
1505 self.itemkeys |= ITEM_KEY_BIT(0);
1506 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
1507 self.itemkeys |= ITEM_KEY_BIT(1);
1509 //if (!self.deathtype) // map makers can override this
1510 // self.deathtype = " got in the way";
1513 self.max_health = self.health;
1514 if not(InitMovingBrushTrigger())
1516 self.effects |= EF_LOWPRECISION;
1517 self.classname = "door";
1519 self.blocked = door_blocked;
1520 self.use = door_use;
1522 // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
1523 // if(self.spawnflags & 8)
1524 // self.dmg = 10000;
1526 if(self.dmg && (!self.message))
1527 self.message = "was squished";
1528 if(self.dmg && (!self.message2))
1529 self.message2 = "was squished by";
1531 if (self.sounds > 0)
1533 precache_sound ("plats/medplat1.wav");
1534 precache_sound ("plats/medplat2.wav");
1535 self.noise2 = "plats/medplat1.wav";
1536 self.noise1 = "plats/medplat2.wav";
1546 self.pos1 = self.origin;
1547 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1549 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1550 // but spawn in the open position
1551 if (self.spawnflags & DOOR_START_OPEN)
1552 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1554 self.state = STATE_BOTTOM;
1558 self.takedamage = DAMAGE_YES;
1559 self.event_damage = door_damage;
1565 self.touch = door_touch;
1567 // LinkDoors can't be done until all of the doors have been spawned, so
1568 // the sizes can be detected properly.
1569 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1571 self.reset = door_reset;
1574 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1575 if two doors touch, they are assumed to be connected and operate as a unit.
1577 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1579 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1580 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1581 must have set trigger_reverse to 1.
1582 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1584 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).
1586 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1587 "angle" determines the destination angle for opening. negative values reverse the direction.
1588 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1589 "health" if set, door must be shot open
1590 "speed" movement speed (100 default)
1591 "wait" wait before returning (3 default, -1 = never return)
1592 "dmg" damage to inflict when blocked (2 default)
1599 FIXME: only one sound set available at the time being
1602 void door_rotating_reset()
1604 self.angles = self.pos1;
1605 self.avelocity = '0 0 0';
1606 self.state = STATE_BOTTOM;
1607 self.think = SUB_Null;
1610 void door_rotating_init_startopen()
1612 self.angles = self.movedir;
1613 self.pos2 = '0 0 0';
1614 self.pos1 = self.movedir;
1618 void spawnfunc_func_door_rotating()
1621 //if (!self.deathtype) // map makers can override this
1622 // self.deathtype = " got in the way";
1624 // I abuse "movedir" for denoting the axis for now
1625 if (self.spawnflags & 64) // X (untested)
1626 self.movedir = '0 0 1';
1627 else if (self.spawnflags & 128) // Y (untested)
1628 self.movedir = '1 0 0';
1630 self.movedir = '0 1 0';
1632 if (self.angles_y==0) self.angles_y = 90;
1634 self.movedir = self.movedir * self.angles_y;
1635 self.angles = '0 0 0';
1637 self.max_health = self.health;
1638 self.avelocity = self.movedir;
1639 if not(InitMovingBrushTrigger())
1641 self.velocity = '0 0 0';
1642 //self.effects |= EF_LOWPRECISION;
1643 self.classname = "door_rotating";
1645 self.blocked = door_blocked;
1646 self.use = door_use;
1648 if(self.spawnflags & 8)
1651 if(self.dmg && (!self.message))
1652 self.message = "was squished";
1653 if(self.dmg && (!self.message2))
1654 self.message2 = "was squished by";
1656 if (self.sounds > 0)
1658 precache_sound ("plats/medplat1.wav");
1659 precache_sound ("plats/medplat2.wav");
1660 self.noise2 = "plats/medplat1.wav";
1661 self.noise1 = "plats/medplat2.wav";
1668 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1670 self.pos1 = '0 0 0';
1671 self.pos2 = self.movedir;
1673 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1674 // but spawn in the open position
1675 if (self.spawnflags & DOOR_START_OPEN)
1676 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1678 self.state = STATE_BOTTOM;
1682 self.takedamage = DAMAGE_YES;
1683 self.event_damage = door_damage;
1689 self.touch = door_touch;
1691 // LinkDoors can't be done until all of the doors have been spawned, so
1692 // the sizes can be detected properly.
1693 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1695 self.reset = door_rotating_reset;
1699 =============================================================================
1703 =============================================================================
1706 void() fd_secret_move1;
1707 void() fd_secret_move2;
1708 void() fd_secret_move3;
1709 void() fd_secret_move4;
1710 void() fd_secret_move5;
1711 void() fd_secret_move6;
1712 void() fd_secret_done;
1714 float SECRET_OPEN_ONCE = 1; // stays open
1715 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1716 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1717 float SECRET_NO_SHOOT = 8; // only opened by trigger
1718 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1721 void fd_secret_use()
1724 string message_save;
1726 self.health = 10000;
1727 self.bot_attack = TRUE;
1729 // exit if still moving around...
1730 if (self.origin != self.oldorigin)
1733 message_save = self.message;
1734 self.message = ""; // no more message
1735 SUB_UseTargets(); // fire all targets / killtargets
1736 self.message = message_save;
1738 self.velocity = '0 0 0';
1740 // Make a sound, wait a little...
1742 if (self.noise1 != "")
1743 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1744 self.nextthink = self.ltime + 0.1;
1746 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1747 makevectors(self.mangle);
1751 if (self.spawnflags & SECRET_1ST_DOWN)
1752 self.t_width = fabs(v_up * self.size);
1754 self.t_width = fabs(v_right * self.size);
1758 self.t_length = fabs(v_forward * self.size);
1760 if (self.spawnflags & SECRET_1ST_DOWN)
1761 self.dest1 = self.origin - v_up * self.t_width;
1763 self.dest1 = self.origin + v_right * (self.t_width * temp);
1765 self.dest2 = self.dest1 + v_forward * self.t_length;
1766 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
1767 if (self.noise2 != "")
1768 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1771 // Wait after first movement...
1772 void fd_secret_move1()
1774 self.nextthink = self.ltime + 1.0;
1775 self.think = fd_secret_move2;
1776 if (self.noise3 != "")
1777 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1780 // Start moving sideways w/sound...
1781 void fd_secret_move2()
1783 if (self.noise2 != "")
1784 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1785 SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
1788 // Wait here until time to go back...
1789 void fd_secret_move3()
1791 if (self.noise3 != "")
1792 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1793 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1795 self.nextthink = self.ltime + self.wait;
1796 self.think = fd_secret_move4;
1801 void fd_secret_move4()
1803 if (self.noise2 != "")
1804 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1805 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
1809 void fd_secret_move5()
1811 self.nextthink = self.ltime + 1.0;
1812 self.think = fd_secret_move6;
1813 if (self.noise3 != "")
1814 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1817 void fd_secret_move6()
1819 if (self.noise2 != "")
1820 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1821 SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
1824 void fd_secret_done()
1826 if (self.spawnflags&SECRET_YES_SHOOT)
1828 self.health = 10000;
1829 self.takedamage = DAMAGE_YES;
1830 //self.th_pain = fd_secret_use;
1832 if (self.noise3 != "")
1833 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1836 void secret_blocked()
1838 if (time < self.attack_finished_single)
1840 self.attack_finished_single = time + 0.5;
1841 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1853 if not(other.iscreature)
1855 if (self.attack_finished_single > time)
1858 self.attack_finished_single = time + 2;
1862 if (other.flags & FL_CLIENT)
1863 centerprint (other, self.message);
1864 play2(other, "misc/talk.wav");
1870 if (self.spawnflags&SECRET_YES_SHOOT)
1872 self.health = 10000;
1873 self.takedamage = DAMAGE_YES;
1875 setorigin(self, self.oldorigin);
1876 self.think = SUB_Null;
1879 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1880 Basic secret door. Slides back, then to the side. Angle determines direction.
1881 wait = # of seconds before coming back
1882 1st_left = 1st move is left of arrow
1883 1st_down = 1st move is down from arrow
1884 always_shoot = even if targeted, keep shootable
1885 t_width = override WIDTH to move back (or height if going down)
1886 t_length = override LENGTH to move sideways
1887 "dmg" damage to inflict when blocked (2 default)
1889 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1896 void spawnfunc_func_door_secret()
1898 /*if (!self.deathtype) // map makers can override this
1899 self.deathtype = " got in the way";*/
1905 self.mangle = self.angles;
1906 self.angles = '0 0 0';
1907 self.classname = "door";
1908 if not(InitMovingBrushTrigger())
1910 self.effects |= EF_LOWPRECISION;
1912 self.touch = secret_touch;
1913 self.blocked = secret_blocked;
1915 self.use = fd_secret_use;
1920 self.spawnflags |= SECRET_YES_SHOOT;
1922 if(self.spawnflags&SECRET_YES_SHOOT)
1924 self.health = 10000;
1925 self.takedamage = DAMAGE_YES;
1926 self.event_damage = fd_secret_use;
1928 self.oldorigin = self.origin;
1930 self.wait = 5; // 5 seconds before closing
1932 self.reset = secret_reset;
1936 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1937 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1938 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
1939 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1940 height: amplitude modifier (default 32)
1941 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1942 noise: path/name of looping .wav file to play.
1943 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1947 void func_fourier_controller_think()
1952 self.nextthink = time + 0.1;
1953 if not (self.owner.active == ACTIVE_ACTIVE)
1955 self.owner.velocity = '0 0 0';
1960 n = floor((tokenize_console(self.owner.netname)) / 5);
1961 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1963 v = self.owner.destvec;
1965 for(i = 0; i < n; ++i)
1967 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1968 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;
1971 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1972 // * 10 so it will arrive in 0.1 sec
1973 self.owner.velocity = (v - self.owner.origin) * 10;
1976 void spawnfunc_func_fourier()
1979 if (self.noise != "")
1981 precache_sound(self.noise);
1982 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
1989 self.destvec = self.origin;
1990 self.cnt = 360 / self.speed;
1992 self.blocked = generic_plat_blocked;
1993 if(self.dmg & (!self.message))
1994 self.message = " was squished";
1995 if(self.dmg && (!self.message2))
1996 self.message2 = "was squished by";
1997 if(self.dmg && (!self.dmgtime))
1998 self.dmgtime = 0.25;
1999 self.dmgtime2 = time;
2001 if(self.netname == "")
2002 self.netname = "1 0 0 0 1";
2004 if not(InitMovingBrushTrigger())
2007 self.active = ACTIVE_ACTIVE;
2009 // wait for targets to spawn
2010 controller = spawn();
2011 controller.classname = "func_fourier_controller";
2012 controller.owner = self;
2013 controller.nextthink = time + 1;
2014 controller.think = func_fourier_controller_think;
2015 self.nextthink = self.ltime + 999999999;
2016 self.think = SUB_Null;
2018 // Savage: Reduce bandwith, critical on e.g. nexdm02
2019 self.effects |= EF_LOWPRECISION;
2021 // TODO make a reset function for this one
2024 // reusing some fields havocbots declared
2025 .entity wp00, wp01, wp02, wp03;
2027 .float targetfactor, target2factor, target3factor, target4factor;
2028 .vector targetnormal, target2normal, target3normal, target4normal;
2030 vector func_vectormamamam_origin(entity o, float t)
2042 p = e.origin + t * e.velocity;
2044 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
2046 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
2052 p = e.origin + t * e.velocity;
2054 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
2056 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
2062 p = e.origin + t * e.velocity;
2064 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
2066 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
2072 p = e.origin + t * e.velocity;
2074 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
2076 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
2082 void func_vectormamamam_controller_think()
2084 self.nextthink = time + 0.1;
2086 if not (self.owner.active == ACTIVE_ACTIVE)
2088 self.owner.velocity = '0 0 0';
2092 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
2093 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
2096 void func_vectormamamam_findtarget()
2098 if(self.target != "")
2099 self.wp00 = find(world, targetname, self.target);
2101 if(self.target2 != "")
2102 self.wp01 = find(world, targetname, self.target2);
2104 if(self.target3 != "")
2105 self.wp02 = find(world, targetname, self.target3);
2107 if(self.target4 != "")
2108 self.wp03 = find(world, targetname, self.target4);
2110 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
2111 objerror("No reference entity found, so there is nothing to move. Aborting.");
2113 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
2116 controller = spawn();
2117 controller.classname = "func_vectormamamam_controller";
2118 controller.owner = self;
2119 controller.nextthink = time + 1;
2120 controller.think = func_vectormamamam_controller_think;
2123 void spawnfunc_func_vectormamamam()
2125 if (self.noise != "")
2127 precache_sound(self.noise);
2128 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
2131 if(!self.targetfactor)
2132 self.targetfactor = 1;
2134 if(!self.target2factor)
2135 self.target2factor = 1;
2137 if(!self.target3factor)
2138 self.target3factor = 1;
2140 if(!self.target4factor)
2141 self.target4factor = 1;
2143 if(vlen(self.targetnormal))
2144 self.targetnormal = normalize(self.targetnormal);
2146 if(vlen(self.target2normal))
2147 self.target2normal = normalize(self.target2normal);
2149 if(vlen(self.target3normal))
2150 self.target3normal = normalize(self.target3normal);
2152 if(vlen(self.target4normal))
2153 self.target4normal = normalize(self.target4normal);
2155 self.blocked = generic_plat_blocked;
2156 if(self.dmg & (!self.message))
2157 self.message = " was squished";
2158 if(self.dmg && (!self.message2))
2159 self.message2 = "was squished by";
2160 if(self.dmg && (!self.dmgtime))
2161 self.dmgtime = 0.25;
2162 self.dmgtime2 = time;
2164 if(self.netname == "")
2165 self.netname = "1 0 0 0 1";
2167 if not(InitMovingBrushTrigger())
2170 // wait for targets to spawn
2171 self.nextthink = self.ltime + 999999999;
2172 self.think = SUB_Null;
2174 // Savage: Reduce bandwith, critical on e.g. nexdm02
2175 self.effects |= EF_LOWPRECISION;
2177 self.active = ACTIVE_ACTIVE;
2179 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
2182 void conveyor_think()
2186 // set myself as current conveyor where possible
2187 for(e = world; (e = findentity(e, conveyor, self)); )
2192 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2193 if(!e.conveyor.state)
2196 vector emin = e.absmin;
2197 vector emax = e.absmax;
2198 if(self.solid == SOLID_BSP)
2203 if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2204 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2208 for(e = world; (e = findentity(e, conveyor, self)); )
2210 if(e.flags & FL_CLIENT) // doing it via velocity has quite some advantages
2211 continue; // done in SV_PlayerPhysics
2213 setorigin(e, e.origin + self.movedir * sys_frametime);
2214 move_out_of_solid(e);
2215 UpdateCSQCProjectile(e);
2217 // stupid conveyor code
2218 tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2219 if(trace_fraction > 0)
2220 setorigin(e, trace_endpos);
2225 self.nextthink = time;
2230 self.state = !self.state;
2233 void conveyor_reset()
2235 self.state = (self.spawnflags & 1);
2238 void conveyor_init()
2242 self.movedir = self.movedir * self.speed;
2243 self.think = conveyor_think;
2244 self.nextthink = time;
2247 self.use = conveyor_use;
2248 self.reset = conveyor_reset;
2255 void spawnfunc_trigger_conveyor()
2262 void spawnfunc_func_conveyor()
2265 InitMovingBrushTrigger();
2266 self.movetype = MOVETYPE_NONE;