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 using bezier curves and turning is enabled, the train will turn toward the next point while waiting
302 if(!self.train_wait_turning)
303 if(self.spawnflags & 1 && self.bezier_turn && self.wait >= 0)
307 targ = find(world, targetname, self.target);
308 org = normalize(targ.origin);
309 SUB_CalcAngleMove(org, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
310 self.train_wait_turning = TRUE;
315 stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
317 if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
319 self.train_wait_turning = FALSE;
324 self.think = train_next;
325 self.nextthink = self.ltime + self.wait;
334 targ = find(world, targetname, self.target);
335 self.target = targ.target;
336 if (self.spawnflags & 1)
340 cp = find(world, targetname, targ.curve); // get its second target (the control point)
341 cp_org = cp.origin - self.mins; // no control point found, assume a straight line to the destination
347 objerror("train_next: no next target");
348 self.wait = targ.wait;
352 if(targ.platmovetype)
354 // this path_corner contains a movetype overrider, apply it
355 self.platmovetype_start = targ.platmovetype_start;
356 self.platmovetype_end = targ.platmovetype_end;
360 // this path_corner doesn't contain a movetype overrider, use the train's defaults
361 self.platmovetype_start = self.platmovetype_start_default;
362 self.platmovetype_end = self.platmovetype_end_default;
368 SUB_CalcMove_Bezier(cp_org, targ.origin - self.mins, TSPEED_LINEAR, targ.speed, train_wait);
370 SUB_CalcMove(targ.origin - self.mins, TSPEED_LINEAR, targ.speed, train_wait);
375 SUB_CalcMove_Bezier(cp_org, targ.origin - self.mins, TSPEED_LINEAR, self.speed, train_wait);
377 SUB_CalcMove(targ.origin - self.mins, TSPEED_LINEAR, self.speed, train_wait);
381 sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
384 void func_train_find()
387 targ = find(world, targetname, self.target);
388 self.target = targ.target;
390 objerror("func_train_find: no next target");
391 setorigin(self, targ.origin - self.mins);
392 self.nextthink = self.ltime + 1;
393 self.think = train_next;
396 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
397 Ridable platform, targets spawnfunc_path_corner path to follow.
398 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
399 target : targetname of first spawnfunc_path_corner (starts here)
401 void spawnfunc_func_train()
403 if (self.noise != "")
404 precache_sound(self.noise);
407 objerror("func_train without a target");
410 if (self.spawnflags & 2)
411 self.bezier_turn = TRUE;
413 if not(InitMovingBrushTrigger())
415 self.effects |= EF_LOWPRECISION;
417 // wait for targets to spawn
418 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
420 self.blocked = generic_plat_blocked;
421 if(self.dmg & (!self.message))
422 self.message = " was squished";
423 if(self.dmg && (!self.message2))
424 self.message2 = "was squished by";
425 if(self.dmg && (!self.dmgtime))
427 self.dmgtime2 = time;
429 if(!set_platmovetype(self, self.platmovetype))
431 self.platmovetype_start_default = self.platmovetype_start;
432 self.platmovetype_end_default = self.platmovetype_end;
434 // TODO make a reset function for this one
437 void func_rotating_setactive(float astate)
440 if (astate == ACTIVE_TOGGLE)
442 if(self.active == ACTIVE_ACTIVE)
443 self.active = ACTIVE_NOT;
445 self.active = ACTIVE_ACTIVE;
448 self.active = astate;
450 if(self.active == ACTIVE_NOT)
451 self.avelocity = '0 0 0';
453 self.avelocity = self.pos1;
456 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
457 Brush model that spins in place on one axis (default Z).
458 speed : speed to rotate (in degrees per second)
459 noise : path/name of looping .wav file to play.
460 dmg : Do this mutch dmg every .dmgtime intervall when blocked
464 void spawnfunc_func_rotating()
466 if (self.noise != "")
468 precache_sound(self.noise);
469 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
472 self.active = ACTIVE_ACTIVE;
473 self.setactive = func_rotating_setactive;
477 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
478 if (self.spawnflags & 4) // X (untested)
479 self.avelocity = '0 0 1' * self.speed;
480 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
481 else if (self.spawnflags & 8) // Y (untested)
482 self.avelocity = '1 0 0' * self.speed;
483 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
485 self.avelocity = '0 1 0' * self.speed;
487 self.pos1 = self.avelocity;
489 if(self.dmg & (!self.message))
490 self.message = " was squished";
491 if(self.dmg && (!self.message2))
492 self.message2 = "was squished by";
495 if(self.dmg && (!self.dmgtime))
498 self.dmgtime2 = time;
500 if not(InitMovingBrushTrigger())
502 // no EF_LOWPRECISION here, as rounding angles is bad
504 self.blocked = generic_plat_blocked;
506 // wait for targets to spawn
507 self.nextthink = self.ltime + 999999999;
508 self.think = SUB_Null;
510 // TODO make a reset function for this one
514 void func_bobbing_controller_think()
517 self.nextthink = time + 0.1;
519 if not (self.owner.active == ACTIVE_ACTIVE)
521 self.owner.velocity = '0 0 0';
525 // calculate sinewave using makevectors
526 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
527 v = self.owner.destvec + self.owner.movedir * v_forward_y;
528 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
529 // * 10 so it will arrive in 0.1 sec
530 self.owner.velocity = (v - self.owner.origin) * 10;
533 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
534 Brush model that moves back and forth on one axis (default Z).
535 speed : how long one cycle takes in seconds (default 4)
536 height : how far the cycle moves (default 32)
537 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
538 noise : path/name of looping .wav file to play.
539 dmg : Do this mutch dmg every .dmgtime intervall when blocked
542 void spawnfunc_func_bobbing()
545 if (self.noise != "")
547 precache_sound(self.noise);
548 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
554 // center of bobbing motion
555 self.destvec = self.origin;
556 // time scale to get degrees
557 self.cnt = 360 / self.speed;
559 self.active = ACTIVE_ACTIVE;
561 // damage when blocked
562 self.blocked = generic_plat_blocked;
563 if(self.dmg & (!self.message))
564 self.message = " was squished";
565 if(self.dmg && (!self.message2))
566 self.message2 = "was squished by";
567 if(self.dmg && (!self.dmgtime))
569 self.dmgtime2 = time;
572 if (self.spawnflags & 1) // X
573 self.movedir = '1 0 0' * self.height;
574 else if (self.spawnflags & 2) // Y
575 self.movedir = '0 1 0' * self.height;
577 self.movedir = '0 0 1' * self.height;
579 if not(InitMovingBrushTrigger())
582 // wait for targets to spawn
583 controller = spawn();
584 controller.classname = "func_bobbing_controller";
585 controller.owner = self;
586 controller.nextthink = time + 1;
587 controller.think = func_bobbing_controller_think;
588 self.nextthink = self.ltime + 999999999;
589 self.think = SUB_Null;
591 // Savage: Reduce bandwith, critical on e.g. nexdm02
592 self.effects |= EF_LOWPRECISION;
594 // TODO make a reset function for this one
598 void func_pendulum_controller_think()
601 self.nextthink = time + 0.1;
603 if not (self.owner.active == ACTIVE_ACTIVE)
605 self.owner.avelocity_x = 0;
609 // calculate sinewave using makevectors
610 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
611 v = self.owner.speed * v_forward_y + self.cnt;
612 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
614 // * 10 so it will arrive in 0.1 sec
615 self.owner.avelocity_z = (remainder(v - self.owner.angles_z, 360)) * 10;
619 void spawnfunc_func_pendulum()
622 if (self.noise != "")
624 precache_sound(self.noise);
625 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
628 self.active = ACTIVE_ACTIVE;
630 // keys: angle, speed, phase, noise, freq
634 // not initializing self.dmg to 2, to allow damageless pendulum
636 if(self.dmg & (!self.message))
637 self.message = " was squished";
638 if(self.dmg && (!self.message2))
639 self.message2 = "was squished by";
640 if(self.dmg && (!self.dmgtime))
642 self.dmgtime2 = time;
644 self.blocked = generic_plat_blocked;
646 self.avelocity_z = 0.0000001;
647 if not(InitMovingBrushTrigger())
652 // find pendulum length (same formula as Q3A)
653 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins_z))));
656 // copy initial angle
657 self.cnt = self.angles_z;
659 // wait for targets to spawn
660 controller = spawn();
661 controller.classname = "func_pendulum_controller";
662 controller.owner = self;
663 controller.nextthink = time + 1;
664 controller.think = func_pendulum_controller_think;
665 self.nextthink = self.ltime + 999999999;
666 self.think = SUB_Null;
668 //self.effects |= EF_LOWPRECISION;
670 // TODO make a reset function for this one
673 // button and multiple button
676 void() button_return;
680 self.state = STATE_TOP;
681 self.nextthink = self.ltime + self.wait;
682 self.think = button_return;
683 activator = self.enemy;
685 self.frame = 1; // use alternate textures
690 self.state = STATE_BOTTOM;
695 self.state = STATE_DOWN;
696 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
697 self.frame = 0; // use normal textures
699 self.takedamage = DAMAGE_YES; // can be shot again
703 void button_blocked()
705 // do nothing, just don't come all the way back out
711 self.health = self.max_health;
712 self.takedamage = DAMAGE_NO; // will be reset upon return
714 if (self.state == STATE_UP || self.state == STATE_TOP)
717 if (self.noise != "")
718 sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
720 self.state = STATE_UP;
721 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
726 self.health = self.max_health;
727 setorigin(self, self.pos1);
728 self.frame = 0; // use normal textures
729 self.state = STATE_BOTTOM;
731 self.takedamage = DAMAGE_YES; // can be shot again
736 // if (activator.classname != "player")
738 // dprint(activator.classname);
739 // dprint(" triggered a button\n");
742 if not (self.active == ACTIVE_ACTIVE)
745 self.enemy = activator;
751 // if (activator.classname != "player")
753 // dprint(activator.classname);
754 // dprint(" touched a button\n");
758 if not(other.iscreature)
760 if(other.velocity * self.movedir < 0)
764 self.enemy = other.owner;
768 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
770 if(self.spawnflags & DOOR_NOSPLASH)
771 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
773 self.health = self.health - damage;
774 if (self.health <= 0)
776 // if (activator.classname != "player")
778 // dprint(activator.classname);
779 // dprint(" killed a button\n");
781 self.enemy = damage_attacker;
787 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
788 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.
790 "angle" determines the opening direction
791 "target" all entities with a matching targetname will be used
792 "speed" override the default 40 speed
793 "wait" override the default 1 second wait (-1 = never return)
794 "lip" override the default 4 pixel lip remaining at end of move
795 "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
802 void spawnfunc_func_button()
806 if not(InitMovingBrushTrigger())
808 self.effects |= EF_LOWPRECISION;
810 self.blocked = button_blocked;
811 self.use = button_use;
813 // if (self.health == 0) // all buttons are now shootable
817 self.max_health = self.health;
818 self.event_damage = button_damage;
819 self.takedamage = DAMAGE_YES;
822 self.touch = button_touch;
832 precache_sound(self.noise);
834 self.active = ACTIVE_ACTIVE;
836 self.pos1 = self.origin;
837 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
838 self.flags |= FL_NOTARGET;
844 float DOOR_START_OPEN = 1;
845 float DOOR_DONT_LINK = 4;
846 float DOOR_TOGGLE = 32;
850 Doors are similar to buttons, but can spawn a fat trigger field around them
851 to open without a touch, and they link together to form simultanious
854 Door.owner is the master door. If there is only one door, it points to itself.
855 If multiple doors, all will point to a single one.
857 Door.enemy chains from the master door through all doors linked in the chain.
862 =============================================================================
866 =============================================================================
871 void() door_rotating_go_down;
872 void() door_rotating_go_up;
877 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
878 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
881 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
882 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
884 //Dont chamge direction for dead or dying stuff
885 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
888 if (self.state == STATE_DOWN)
889 if (self.classname == "door")
894 door_rotating_go_up ();
897 if (self.classname == "door")
902 door_rotating_go_down ();
906 //gib dying stuff just to make sure
907 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
908 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
912 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
913 // if a door has a negative wait, it would never come back if blocked,
914 // so let it just squash the object to death real fast
915 /* if (self.wait >= 0)
917 if (self.state == STATE_DOWN)
928 if (self.noise1 != "")
929 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
930 self.state = STATE_TOP;
931 if (self.spawnflags & DOOR_TOGGLE)
932 return; // don't come down automatically
933 if (self.classname == "door")
935 self.think = door_go_down;
938 self.think = door_rotating_go_down;
940 self.nextthink = self.ltime + self.wait;
943 void door_hit_bottom()
945 if (self.noise1 != "")
946 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
947 self.state = STATE_BOTTOM;
952 if (self.noise2 != "")
953 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
956 self.takedamage = DAMAGE_YES;
957 self.health = self.max_health;
960 self.state = STATE_DOWN;
961 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
966 if (self.state == STATE_UP)
967 return; // already going up
969 if (self.state == STATE_TOP)
970 { // reset top wait time
971 self.nextthink = self.ltime + self.wait;
975 if (self.noise2 != "")
976 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
977 self.state = STATE_UP;
978 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
981 oldmessage = self.message;
984 self.message = oldmessage;
990 =============================================================================
994 =============================================================================
997 float door_check_keys(void) {
1007 if not(door.itemkeys)
1010 // this door require a key
1011 // only a player can have a key
1012 if (other.classname != "player")
1015 if (item_keys_usekey(door, other)) {
1016 // some keys were used
1017 if (other.key_door_messagetime <= time) {
1018 play2(other, "misc/talk.wav");
1019 centerprint(other, strcat("You also need ", item_keys_keylist(door.itemkeys), "!"));
1020 other.key_door_messagetime = time + 2;
1023 // no keys were used
1024 if (other.key_door_messagetime <= time) {
1025 play2(other, "misc/talk.wav");
1026 centerprint(other, strcat("You need ", item_keys_keylist(door.itemkeys), "!"));
1027 other.key_door_messagetime = time + 2;
1031 if (door.itemkeys) {
1032 // door is now unlocked
1033 play2(other, "misc/talk.wav");
1034 centerprint(other, "Door unlocked!");
1046 if (self.owner != self)
1047 objerror ("door_fire: self.owner != self");
1051 if (self.spawnflags & DOOR_TOGGLE)
1053 if (self.state == STATE_UP || self.state == STATE_TOP)
1058 if (self.classname == "door")
1064 door_rotating_go_down ();
1067 } while ( (self != starte) && (self != world) );
1073 // trigger all paired doors
1077 if (self.classname == "door")
1082 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
1083 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
1085 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
1086 self.pos2 = '0 0 0' - self.pos2;
1088 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
1089 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
1090 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
1092 door_rotating_go_up ();
1096 } while ( (self != starte) && (self != world) );
1105 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
1117 void door_trigger_touch()
1119 if (other.health < 1)
1120 if not(other.iscreature && other.deadflag == DEAD_NO)
1123 if (time < self.attack_finished_single)
1126 // check if door is locked
1127 if (!door_check_keys())
1130 self.attack_finished_single = time + 1;
1139 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1142 if(self.spawnflags & DOOR_NOSPLASH)
1143 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
1145 self.health = self.health - damage;
1147 if (self.itemkeys) {
1148 // don't allow opening doors through damage if keys are required
1152 if (self.health <= 0)
1156 self.health = self.max_health;
1157 self.takedamage = DAMAGE_NO; // wil be reset upon return
1173 if(other.classname != "player")
1175 if (self.owner.attack_finished_single > time)
1178 self.owner.attack_finished_single = time + 2;
1180 if (!(self.owner.dmg) && (self.owner.message != ""))
1182 if (other.flags & FL_CLIENT)
1183 centerprint (other, self.owner.message);
1184 play2(other, "misc/talk.wav");
1189 void door_generic_plat_blocked()
1192 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1193 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1196 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1197 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1199 //Dont chamge direction for dead or dying stuff
1200 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1203 if (self.state == STATE_DOWN)
1204 door_rotating_go_up ();
1206 door_rotating_go_down ();
1209 //gib dying stuff just to make sure
1210 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1211 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1215 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1216 // if a door has a negative wait, it would never come back if blocked,
1217 // so let it just squash the object to death real fast
1218 /* if (self.wait >= 0)
1220 if (self.state == STATE_DOWN)
1221 door_rotating_go_up ();
1223 door_rotating_go_down ();
1229 void door_rotating_hit_top()
1231 if (self.noise1 != "")
1232 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1233 self.state = STATE_TOP;
1234 if (self.spawnflags & DOOR_TOGGLE)
1235 return; // don't come down automatically
1236 self.think = door_rotating_go_down;
1237 self.nextthink = self.ltime + self.wait;
1240 void door_rotating_hit_bottom()
1242 if (self.noise1 != "")
1243 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1244 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1246 self.pos2 = '0 0 0' - self.pos2;
1249 self.state = STATE_BOTTOM;
1252 void door_rotating_go_down()
1254 if (self.noise2 != "")
1255 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1256 if (self.max_health)
1258 self.takedamage = DAMAGE_YES;
1259 self.health = self.max_health;
1262 self.state = STATE_DOWN;
1263 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
1266 void door_rotating_go_up()
1268 if (self.state == STATE_UP)
1269 return; // already going up
1271 if (self.state == STATE_TOP)
1272 { // reset top wait time
1273 self.nextthink = self.ltime + self.wait;
1276 if (self.noise2 != "")
1277 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1278 self.state = STATE_UP;
1279 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
1282 oldmessage = self.message;
1285 self.message = oldmessage;
1292 =============================================================================
1296 =============================================================================
1300 entity spawn_field(vector fmins, vector fmaxs)
1306 trigger.classname = "doortriggerfield";
1307 trigger.movetype = MOVETYPE_NONE;
1308 trigger.solid = SOLID_TRIGGER;
1309 trigger.owner = self;
1310 trigger.touch = door_trigger_touch;
1314 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1319 float EntitiesTouching(entity e1, entity e2)
1321 if (e1.absmin_x > e2.absmax_x)
1323 if (e1.absmin_y > e2.absmax_y)
1325 if (e1.absmin_z > e2.absmax_z)
1327 if (e1.absmax_x < e2.absmin_x)
1329 if (e1.absmax_y < e2.absmin_y)
1331 if (e1.absmax_z < e2.absmin_z)
1347 vector cmins, cmaxs;
1350 return; // already linked by another door
1351 if (self.spawnflags & 4)
1353 self.owner = self.enemy = self;
1361 self.trigger_field = spawn_field(self.absmin, self.absmax);
1363 return; // don't want to link this door
1366 cmins = self.absmin;
1367 cmaxs = self.absmax;
1374 self.owner = starte; // master door
1377 starte.health = self.health;
1379 starte.targetname = self.targetname;
1380 if (self.message != "")
1381 starte.message = self.message;
1383 t = find(t, classname, self.classname);
1386 self.enemy = starte; // make the chain a loop
1388 // shootable, or triggered doors just needed the owner/enemy links,
1389 // they don't spawn a field
1400 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1405 if (EntitiesTouching(self,t))
1408 objerror ("cross connected doors");
1413 if (t.absmin_x < cmins_x)
1414 cmins_x = t.absmin_x;
1415 if (t.absmin_y < cmins_y)
1416 cmins_y = t.absmin_y;
1417 if (t.absmin_z < cmins_z)
1418 cmins_z = t.absmin_z;
1419 if (t.absmax_x > cmaxs_x)
1420 cmaxs_x = t.absmax_x;
1421 if (t.absmax_y > cmaxs_y)
1422 cmaxs_y = t.absmax_y;
1423 if (t.absmax_z > cmaxs_z)
1424 cmaxs_z = t.absmax_z;
1431 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
1432 if two doors touch, they are assumed to be connected and operate as a unit.
1434 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1436 START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors).
1438 GOLD_KEY causes the door to open only if the activator holds a gold key.
1440 SILVER_KEY causes the door to open only if the activator holds a silver key.
1442 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1443 "angle" determines the opening direction
1444 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1445 "health" if set, door must be shot open
1446 "speed" movement speed (100 default)
1447 "wait" wait before returning (3 default, -1 = never return)
1448 "lip" lip remaining at end of move (8 default)
1449 "dmg" damage to inflict when blocked (2 default)
1456 FIXME: only one sound set available at the time being
1460 void door_init_startopen()
1462 setorigin (self, self.pos2);
1463 self.pos2 = self.pos1;
1464 self.pos1 = self.origin;
1469 setorigin(self, self.pos1);
1470 self.velocity = '0 0 0';
1471 self.state = STATE_BOTTOM;
1472 self.think = SUB_Null;
1475 // spawnflags require key (for now only func_door)
1476 #define SPAWNFLAGS_GOLD_KEY 8
1477 #define SPAWNFLAGS_SILVER_KEY 16
1478 void spawnfunc_func_door()
1480 // Quake 1 keys compatibility
1481 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
1482 self.itemkeys |= ITEM_KEY_BIT(0);
1483 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
1484 self.itemkeys |= ITEM_KEY_BIT(1);
1486 //if (!self.deathtype) // map makers can override this
1487 // self.deathtype = " got in the way";
1490 self.max_health = self.health;
1491 if not(InitMovingBrushTrigger())
1493 self.effects |= EF_LOWPRECISION;
1494 self.classname = "door";
1496 self.blocked = door_blocked;
1497 self.use = door_use;
1499 // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
1500 // if(self.spawnflags & 8)
1501 // self.dmg = 10000;
1503 if(self.dmg && (!self.message))
1504 self.message = "was squished";
1505 if(self.dmg && (!self.message2))
1506 self.message2 = "was squished by";
1508 if (self.sounds > 0)
1510 precache_sound ("plats/medplat1.wav");
1511 precache_sound ("plats/medplat2.wav");
1512 self.noise2 = "plats/medplat1.wav";
1513 self.noise1 = "plats/medplat2.wav";
1523 self.pos1 = self.origin;
1524 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1526 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1527 // but spawn in the open position
1528 if (self.spawnflags & DOOR_START_OPEN)
1529 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1531 self.state = STATE_BOTTOM;
1535 self.takedamage = DAMAGE_YES;
1536 self.event_damage = door_damage;
1542 self.touch = door_touch;
1544 // LinkDoors can't be done until all of the doors have been spawned, so
1545 // the sizes can be detected properly.
1546 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1548 self.reset = door_reset;
1551 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1552 if two doors touch, they are assumed to be connected and operate as a unit.
1554 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1556 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1557 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1558 must have set trigger_reverse to 1.
1559 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1561 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).
1563 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1564 "angle" determines the destination angle for opening. negative values reverse the direction.
1565 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1566 "health" if set, door must be shot open
1567 "speed" movement speed (100 default)
1568 "wait" wait before returning (3 default, -1 = never return)
1569 "dmg" damage to inflict when blocked (2 default)
1576 FIXME: only one sound set available at the time being
1579 void door_rotating_reset()
1581 self.angles = self.pos1;
1582 self.avelocity = '0 0 0';
1583 self.state = STATE_BOTTOM;
1584 self.think = SUB_Null;
1587 void door_rotating_init_startopen()
1589 self.angles = self.movedir;
1590 self.pos2 = '0 0 0';
1591 self.pos1 = self.movedir;
1595 void spawnfunc_func_door_rotating()
1598 //if (!self.deathtype) // map makers can override this
1599 // self.deathtype = " got in the way";
1601 // I abuse "movedir" for denoting the axis for now
1602 if (self.spawnflags & 64) // X (untested)
1603 self.movedir = '0 0 1';
1604 else if (self.spawnflags & 128) // Y (untested)
1605 self.movedir = '1 0 0';
1607 self.movedir = '0 1 0';
1609 if (self.angles_y==0) self.angles_y = 90;
1611 self.movedir = self.movedir * self.angles_y;
1612 self.angles = '0 0 0';
1614 self.max_health = self.health;
1615 self.avelocity = self.movedir;
1616 if not(InitMovingBrushTrigger())
1618 self.velocity = '0 0 0';
1619 //self.effects |= EF_LOWPRECISION;
1620 self.classname = "door_rotating";
1622 self.blocked = door_blocked;
1623 self.use = door_use;
1625 if(self.spawnflags & 8)
1628 if(self.dmg && (!self.message))
1629 self.message = "was squished";
1630 if(self.dmg && (!self.message2))
1631 self.message2 = "was squished by";
1633 if (self.sounds > 0)
1635 precache_sound ("plats/medplat1.wav");
1636 precache_sound ("plats/medplat2.wav");
1637 self.noise2 = "plats/medplat1.wav";
1638 self.noise1 = "plats/medplat2.wav";
1645 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1647 self.pos1 = '0 0 0';
1648 self.pos2 = self.movedir;
1650 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1651 // but spawn in the open position
1652 if (self.spawnflags & DOOR_START_OPEN)
1653 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1655 self.state = STATE_BOTTOM;
1659 self.takedamage = DAMAGE_YES;
1660 self.event_damage = door_damage;
1666 self.touch = door_touch;
1668 // LinkDoors can't be done until all of the doors have been spawned, so
1669 // the sizes can be detected properly.
1670 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1672 self.reset = door_rotating_reset;
1676 =============================================================================
1680 =============================================================================
1683 void() fd_secret_move1;
1684 void() fd_secret_move2;
1685 void() fd_secret_move3;
1686 void() fd_secret_move4;
1687 void() fd_secret_move5;
1688 void() fd_secret_move6;
1689 void() fd_secret_done;
1691 float SECRET_OPEN_ONCE = 1; // stays open
1692 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1693 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1694 float SECRET_NO_SHOOT = 8; // only opened by trigger
1695 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1698 void fd_secret_use()
1701 string message_save;
1703 self.health = 10000;
1704 self.bot_attack = TRUE;
1706 // exit if still moving around...
1707 if (self.origin != self.oldorigin)
1710 message_save = self.message;
1711 self.message = ""; // no more message
1712 SUB_UseTargets(); // fire all targets / killtargets
1713 self.message = message_save;
1715 self.velocity = '0 0 0';
1717 // Make a sound, wait a little...
1719 if (self.noise1 != "")
1720 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1721 self.nextthink = self.ltime + 0.1;
1723 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1724 makevectors(self.mangle);
1728 if (self.spawnflags & SECRET_1ST_DOWN)
1729 self.t_width = fabs(v_up * self.size);
1731 self.t_width = fabs(v_right * self.size);
1735 self.t_length = fabs(v_forward * self.size);
1737 if (self.spawnflags & SECRET_1ST_DOWN)
1738 self.dest1 = self.origin - v_up * self.t_width;
1740 self.dest1 = self.origin + v_right * (self.t_width * temp);
1742 self.dest2 = self.dest1 + v_forward * self.t_length;
1743 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
1744 if (self.noise2 != "")
1745 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1748 // Wait after first movement...
1749 void fd_secret_move1()
1751 self.nextthink = self.ltime + 1.0;
1752 self.think = fd_secret_move2;
1753 if (self.noise3 != "")
1754 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1757 // Start moving sideways w/sound...
1758 void fd_secret_move2()
1760 if (self.noise2 != "")
1761 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1762 SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
1765 // Wait here until time to go back...
1766 void fd_secret_move3()
1768 if (self.noise3 != "")
1769 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1770 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1772 self.nextthink = self.ltime + self.wait;
1773 self.think = fd_secret_move4;
1778 void fd_secret_move4()
1780 if (self.noise2 != "")
1781 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1782 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
1786 void fd_secret_move5()
1788 self.nextthink = self.ltime + 1.0;
1789 self.think = fd_secret_move6;
1790 if (self.noise3 != "")
1791 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1794 void fd_secret_move6()
1796 if (self.noise2 != "")
1797 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1798 SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
1801 void fd_secret_done()
1803 if (self.spawnflags&SECRET_YES_SHOOT)
1805 self.health = 10000;
1806 self.takedamage = DAMAGE_YES;
1807 //self.th_pain = fd_secret_use;
1809 if (self.noise3 != "")
1810 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1813 void secret_blocked()
1815 if (time < self.attack_finished_single)
1817 self.attack_finished_single = time + 0.5;
1818 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1830 if not(other.iscreature)
1832 if (self.attack_finished_single > time)
1835 self.attack_finished_single = time + 2;
1839 if (other.flags & FL_CLIENT)
1840 centerprint (other, self.message);
1841 play2(other, "misc/talk.wav");
1847 if (self.spawnflags&SECRET_YES_SHOOT)
1849 self.health = 10000;
1850 self.takedamage = DAMAGE_YES;
1852 setorigin(self, self.oldorigin);
1853 self.think = SUB_Null;
1856 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1857 Basic secret door. Slides back, then to the side. Angle determines direction.
1858 wait = # of seconds before coming back
1859 1st_left = 1st move is left of arrow
1860 1st_down = 1st move is down from arrow
1861 always_shoot = even if targeted, keep shootable
1862 t_width = override WIDTH to move back (or height if going down)
1863 t_length = override LENGTH to move sideways
1864 "dmg" damage to inflict when blocked (2 default)
1866 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1873 void spawnfunc_func_door_secret()
1875 /*if (!self.deathtype) // map makers can override this
1876 self.deathtype = " got in the way";*/
1882 self.mangle = self.angles;
1883 self.angles = '0 0 0';
1884 self.classname = "door";
1885 if not(InitMovingBrushTrigger())
1887 self.effects |= EF_LOWPRECISION;
1889 self.touch = secret_touch;
1890 self.blocked = secret_blocked;
1892 self.use = fd_secret_use;
1897 self.spawnflags |= SECRET_YES_SHOOT;
1899 if(self.spawnflags&SECRET_YES_SHOOT)
1901 self.health = 10000;
1902 self.takedamage = DAMAGE_YES;
1903 self.event_damage = fd_secret_use;
1905 self.oldorigin = self.origin;
1907 self.wait = 5; // 5 seconds before closing
1909 self.reset = secret_reset;
1913 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1914 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1915 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
1916 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1917 height: amplitude modifier (default 32)
1918 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1919 noise: path/name of looping .wav file to play.
1920 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1924 void func_fourier_controller_think()
1929 self.nextthink = time + 0.1;
1930 if not (self.owner.active == ACTIVE_ACTIVE)
1932 self.owner.velocity = '0 0 0';
1937 n = floor((tokenize_console(self.owner.netname)) / 5);
1938 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1940 v = self.owner.destvec;
1942 for(i = 0; i < n; ++i)
1944 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1945 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;
1948 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1949 // * 10 so it will arrive in 0.1 sec
1950 self.owner.velocity = (v - self.owner.origin) * 10;
1953 void spawnfunc_func_fourier()
1956 if (self.noise != "")
1958 precache_sound(self.noise);
1959 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
1966 self.destvec = self.origin;
1967 self.cnt = 360 / self.speed;
1969 self.blocked = generic_plat_blocked;
1970 if(self.dmg & (!self.message))
1971 self.message = " was squished";
1972 if(self.dmg && (!self.message2))
1973 self.message2 = "was squished by";
1974 if(self.dmg && (!self.dmgtime))
1975 self.dmgtime = 0.25;
1976 self.dmgtime2 = time;
1978 if(self.netname == "")
1979 self.netname = "1 0 0 0 1";
1981 if not(InitMovingBrushTrigger())
1984 self.active = ACTIVE_ACTIVE;
1986 // wait for targets to spawn
1987 controller = spawn();
1988 controller.classname = "func_fourier_controller";
1989 controller.owner = self;
1990 controller.nextthink = time + 1;
1991 controller.think = func_fourier_controller_think;
1992 self.nextthink = self.ltime + 999999999;
1993 self.think = SUB_Null;
1995 // Savage: Reduce bandwith, critical on e.g. nexdm02
1996 self.effects |= EF_LOWPRECISION;
1998 // TODO make a reset function for this one
2001 // reusing some fields havocbots declared
2002 .entity wp00, wp01, wp02, wp03;
2004 .float targetfactor, target2factor, target3factor, target4factor;
2005 .vector targetnormal, target2normal, target3normal, target4normal;
2007 vector func_vectormamamam_origin(entity o, float t)
2019 p = e.origin + t * e.velocity;
2021 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
2023 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
2029 p = e.origin + t * e.velocity;
2031 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
2033 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
2039 p = e.origin + t * e.velocity;
2041 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
2043 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
2049 p = e.origin + t * e.velocity;
2051 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
2053 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
2059 void func_vectormamamam_controller_think()
2061 self.nextthink = time + 0.1;
2063 if not (self.owner.active == ACTIVE_ACTIVE)
2065 self.owner.velocity = '0 0 0';
2069 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
2070 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
2073 void func_vectormamamam_findtarget()
2075 if(self.target != "")
2076 self.wp00 = find(world, targetname, self.target);
2078 if(self.target2 != "")
2079 self.wp01 = find(world, targetname, self.target2);
2081 if(self.target3 != "")
2082 self.wp02 = find(world, targetname, self.target3);
2084 if(self.target4 != "")
2085 self.wp03 = find(world, targetname, self.target4);
2087 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
2088 objerror("No reference entity found, so there is nothing to move. Aborting.");
2090 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
2093 controller = spawn();
2094 controller.classname = "func_vectormamamam_controller";
2095 controller.owner = self;
2096 controller.nextthink = time + 1;
2097 controller.think = func_vectormamamam_controller_think;
2100 void spawnfunc_func_vectormamamam()
2102 if (self.noise != "")
2104 precache_sound(self.noise);
2105 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
2108 if(!self.targetfactor)
2109 self.targetfactor = 1;
2111 if(!self.target2factor)
2112 self.target2factor = 1;
2114 if(!self.target3factor)
2115 self.target3factor = 1;
2117 if(!self.target4factor)
2118 self.target4factor = 1;
2120 if(vlen(self.targetnormal))
2121 self.targetnormal = normalize(self.targetnormal);
2123 if(vlen(self.target2normal))
2124 self.target2normal = normalize(self.target2normal);
2126 if(vlen(self.target3normal))
2127 self.target3normal = normalize(self.target3normal);
2129 if(vlen(self.target4normal))
2130 self.target4normal = normalize(self.target4normal);
2132 self.blocked = generic_plat_blocked;
2133 if(self.dmg & (!self.message))
2134 self.message = " was squished";
2135 if(self.dmg && (!self.message2))
2136 self.message2 = "was squished by";
2137 if(self.dmg && (!self.dmgtime))
2138 self.dmgtime = 0.25;
2139 self.dmgtime2 = time;
2141 if(self.netname == "")
2142 self.netname = "1 0 0 0 1";
2144 if not(InitMovingBrushTrigger())
2147 // wait for targets to spawn
2148 self.nextthink = self.ltime + 999999999;
2149 self.think = SUB_Null;
2151 // Savage: Reduce bandwith, critical on e.g. nexdm02
2152 self.effects |= EF_LOWPRECISION;
2154 self.active = ACTIVE_ACTIVE;
2156 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
2159 void conveyor_think()
2163 // set myself as current conveyor where possible
2164 for(e = world; (e = findentity(e, conveyor, self)); )
2169 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2170 if(!e.conveyor.state)
2173 vector emin = e.absmin;
2174 vector emax = e.absmax;
2175 if(self.solid == SOLID_BSP)
2180 if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2181 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2185 for(e = world; (e = findentity(e, conveyor, self)); )
2187 if(e.flags & FL_CLIENT) // doing it via velocity has quite some advantages
2188 continue; // done in SV_PlayerPhysics
2190 setorigin(e, e.origin + self.movedir * sys_frametime);
2191 move_out_of_solid(e);
2192 UpdateCSQCProjectile(e);
2194 // stupid conveyor code
2195 tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2196 if(trace_fraction > 0)
2197 setorigin(e, trace_endpos);
2202 self.nextthink = time;
2207 self.state = !self.state;
2210 void conveyor_reset()
2212 self.state = (self.spawnflags & 1);
2215 void conveyor_init()
2219 self.movedir = self.movedir * self.speed;
2220 self.think = conveyor_think;
2221 self.nextthink = time;
2224 self.use = conveyor_use;
2225 self.reset = conveyor_reset;
2232 void spawnfunc_trigger_conveyor()
2239 void spawnfunc_func_conveyor()
2242 InitMovingBrushTrigger();
2243 self.movetype = MOVETYPE_NONE;