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 set_platmovetype(entity e, string s)
187 // sets platmovetype_start and platmovetype_end based on a string consisting of two values
190 n = tokenize_console(s);
192 e.platmovetype_start = stof(argv(0));
194 e.platmovetype_start = 0;
197 e.platmovetype_end = stof(argv(1));
199 e.platmovetype_end = e.platmovetype_start;
202 if(argv(2) == "force")
203 return TRUE; // no checking, return immediately
205 if(!cubic_speedfunc_is_sane(e.platmovetype_start, e.platmovetype_end))
207 objerror("Invalid platform move type; platform would go in reverse, which is not allowed.");
214 void spawnfunc_path_corner()
216 // setup values for overriding train movement
217 // if a second value does not exist, both start and end speeds are the single value specified
218 if(!set_platmovetype(self, self.platmovetype))
221 void spawnfunc_func_plat()
223 if (self.sounds == 0)
226 if(self.spawnflags & 4)
229 if(self.dmg && (!self.message))
230 self.message = "was squished";
231 if(self.dmg && (!self.message2))
232 self.message2 = "was squished by";
234 if (self.sounds == 1)
236 precache_sound ("plats/plat1.wav");
237 precache_sound ("plats/plat2.wav");
238 self.noise = "plats/plat1.wav";
239 self.noise1 = "plats/plat2.wav";
242 if (self.sounds == 2)
244 precache_sound ("plats/medplat1.wav");
245 precache_sound ("plats/medplat2.wav");
246 self.noise = "plats/medplat1.wav";
247 self.noise1 = "plats/medplat2.wav";
252 precache_sound (self.sound1);
253 self.noise = self.sound1;
257 precache_sound (self.sound2);
258 self.noise1 = self.sound2;
261 self.mangle = self.angles;
262 self.angles = '0 0 0';
264 self.classname = "plat";
265 if not(InitMovingBrushTrigger())
267 self.effects |= EF_LOWPRECISION;
268 setsize (self, self.mins , self.maxs);
270 self.blocked = plat_crush;
277 self.height = self.size_z - self.lip;
279 self.pos1 = self.origin;
280 self.pos2 = self.origin;
281 self.pos2_z = self.origin_z - self.height;
283 self.reset = plat_reset;
286 plat_spawn_inside_trigger (); // the "start moving" trigger
289 .float train_wait_turning;
300 // if using bezier curves and turning is enabled, the train will turn toward the next point while waiting
301 if(!self.train_wait_turning)
302 if(self.spawnflags & 1 && self.bezier_turn && self.wait >= 0)
306 targ = find(world, targetname, self.target);
307 org = normalize(targ.origin);
308 SUB_CalcAngleMove(org, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
309 self.train_wait_turning = TRUE;
314 stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
316 if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
318 self.train_wait_turning = FALSE;
323 self.think = train_next;
324 self.nextthink = self.ltime + self.wait;
333 targ = find(world, targetname, self.target);
334 self.target = targ.target;
335 if (self.spawnflags & 1)
337 cp = find(world, target, targ.targetname); // get the previous corner first
338 cp = find(world, targetname, cp.curve); // now get its second target (the control point)
339 if(cp.targetname == "")
340 cp_org = targ.origin - self.mins; // no control point found, assume a straight line to the destination
342 cp_org = cp.origin - self.mins;
345 objerror("train_next: no next target");
346 self.wait = targ.wait;
350 // override train movement type if necessary
351 if(targ.platmovetype_start || targ.platmovetype_end)
352 set_platmovetype(self, targ.platmovetype);
354 set_platmovetype(self, self.platmovetype);
358 if (self.spawnflags & 1)
359 SUB_CalcMove_Bezier(cp_org, targ.origin - self.mins, TSPEED_LINEAR, targ.speed, train_wait);
361 SUB_CalcMove(targ.origin - self.mins, TSPEED_LINEAR, targ.speed, train_wait);
365 if (self.spawnflags & 1)
366 SUB_CalcMove_Bezier(cp_org, targ.origin - self.mins, TSPEED_LINEAR, self.speed, train_wait);
368 SUB_CalcMove(targ.origin - self.mins, TSPEED_LINEAR, self.speed, train_wait);
372 sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
375 void func_train_find()
378 targ = find(world, targetname, self.target);
379 self.target = targ.target;
381 objerror("func_train_find: no next target");
382 setorigin(self, targ.origin - self.mins);
383 self.nextthink = self.ltime + 1;
384 self.think = train_next;
387 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
388 Ridable platform, targets spawnfunc_path_corner path to follow.
389 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
390 target : targetname of first spawnfunc_path_corner (starts here)
392 void spawnfunc_func_train()
394 if (self.noise != "")
395 precache_sound(self.noise);
398 objerror("func_train without a target");
401 if (self.spawnflags & 2)
402 self.bezier_turn = TRUE;
404 if not(InitMovingBrushTrigger())
406 self.effects |= EF_LOWPRECISION;
408 // wait for targets to spawn
409 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
411 self.blocked = generic_plat_blocked;
412 if(self.dmg & (!self.message))
413 self.message = " was squished";
414 if(self.dmg && (!self.message2))
415 self.message2 = "was squished by";
416 if(self.dmg && (!self.dmgtime))
418 self.dmgtime2 = time;
420 set_platmovetype(self, self.platmovetype);
422 // TODO make a reset function for this one
425 void func_rotating_setactive(float astate)
428 if (astate == ACTIVE_TOGGLE)
430 if(self.active == ACTIVE_ACTIVE)
431 self.active = ACTIVE_NOT;
433 self.active = ACTIVE_ACTIVE;
436 self.active = astate;
438 if(self.active == ACTIVE_NOT)
439 self.avelocity = '0 0 0';
441 self.avelocity = self.pos1;
444 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
445 Brush model that spins in place on one axis (default Z).
446 speed : speed to rotate (in degrees per second)
447 noise : path/name of looping .wav file to play.
448 dmg : Do this mutch dmg every .dmgtime intervall when blocked
452 void spawnfunc_func_rotating()
454 if (self.noise != "")
456 precache_sound(self.noise);
457 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
460 self.active = ACTIVE_ACTIVE;
461 self.setactive = func_rotating_setactive;
465 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
466 if (self.spawnflags & 4) // X (untested)
467 self.avelocity = '0 0 1' * self.speed;
468 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
469 else if (self.spawnflags & 8) // Y (untested)
470 self.avelocity = '1 0 0' * self.speed;
471 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
473 self.avelocity = '0 1 0' * self.speed;
475 self.pos1 = self.avelocity;
477 if(self.dmg & (!self.message))
478 self.message = " was squished";
479 if(self.dmg && (!self.message2))
480 self.message2 = "was squished by";
483 if(self.dmg && (!self.dmgtime))
486 self.dmgtime2 = time;
488 if not(InitMovingBrushTrigger())
490 // no EF_LOWPRECISION here, as rounding angles is bad
492 self.blocked = generic_plat_blocked;
494 // wait for targets to spawn
495 self.nextthink = self.ltime + 999999999;
496 self.think = SUB_Null;
498 // TODO make a reset function for this one
502 void func_bobbing_controller_think()
505 self.nextthink = time + 0.1;
507 if not (self.owner.active == ACTIVE_ACTIVE)
509 self.owner.velocity = '0 0 0';
513 // calculate sinewave using makevectors
514 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
515 v = self.owner.destvec + self.owner.movedir * v_forward_y;
516 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
517 // * 10 so it will arrive in 0.1 sec
518 self.owner.velocity = (v - self.owner.origin) * 10;
521 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
522 Brush model that moves back and forth on one axis (default Z).
523 speed : how long one cycle takes in seconds (default 4)
524 height : how far the cycle moves (default 32)
525 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
526 noise : path/name of looping .wav file to play.
527 dmg : Do this mutch dmg every .dmgtime intervall when blocked
530 void spawnfunc_func_bobbing()
533 if (self.noise != "")
535 precache_sound(self.noise);
536 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
542 // center of bobbing motion
543 self.destvec = self.origin;
544 // time scale to get degrees
545 self.cnt = 360 / self.speed;
547 self.active = ACTIVE_ACTIVE;
549 // damage when blocked
550 self.blocked = generic_plat_blocked;
551 if(self.dmg & (!self.message))
552 self.message = " was squished";
553 if(self.dmg && (!self.message2))
554 self.message2 = "was squished by";
555 if(self.dmg && (!self.dmgtime))
557 self.dmgtime2 = time;
560 if (self.spawnflags & 1) // X
561 self.movedir = '1 0 0' * self.height;
562 else if (self.spawnflags & 2) // Y
563 self.movedir = '0 1 0' * self.height;
565 self.movedir = '0 0 1' * self.height;
567 if not(InitMovingBrushTrigger())
570 // wait for targets to spawn
571 controller = spawn();
572 controller.classname = "func_bobbing_controller";
573 controller.owner = self;
574 controller.nextthink = time + 1;
575 controller.think = func_bobbing_controller_think;
576 self.nextthink = self.ltime + 999999999;
577 self.think = SUB_Null;
579 // Savage: Reduce bandwith, critical on e.g. nexdm02
580 self.effects |= EF_LOWPRECISION;
582 // TODO make a reset function for this one
586 void func_pendulum_controller_think()
589 self.nextthink = time + 0.1;
591 if not (self.owner.active == ACTIVE_ACTIVE)
593 self.owner.avelocity_x = 0;
597 // calculate sinewave using makevectors
598 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
599 v = self.owner.speed * v_forward_y + self.cnt;
600 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
602 // * 10 so it will arrive in 0.1 sec
603 self.owner.avelocity_z = (remainder(v - self.owner.angles_z, 360)) * 10;
607 void spawnfunc_func_pendulum()
610 if (self.noise != "")
612 precache_sound(self.noise);
613 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
616 self.active = ACTIVE_ACTIVE;
618 // keys: angle, speed, phase, noise, freq
622 // not initializing self.dmg to 2, to allow damageless pendulum
624 if(self.dmg & (!self.message))
625 self.message = " was squished";
626 if(self.dmg && (!self.message2))
627 self.message2 = "was squished by";
628 if(self.dmg && (!self.dmgtime))
630 self.dmgtime2 = time;
632 self.blocked = generic_plat_blocked;
634 self.avelocity_z = 0.0000001;
635 if not(InitMovingBrushTrigger())
640 // find pendulum length (same formula as Q3A)
641 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins_z))));
644 // copy initial angle
645 self.cnt = self.angles_z;
647 // wait for targets to spawn
648 controller = spawn();
649 controller.classname = "func_pendulum_controller";
650 controller.owner = self;
651 controller.nextthink = time + 1;
652 controller.think = func_pendulum_controller_think;
653 self.nextthink = self.ltime + 999999999;
654 self.think = SUB_Null;
656 //self.effects |= EF_LOWPRECISION;
658 // TODO make a reset function for this one
661 // button and multiple button
664 void() button_return;
668 self.state = STATE_TOP;
669 self.nextthink = self.ltime + self.wait;
670 self.think = button_return;
671 activator = self.enemy;
673 self.frame = 1; // use alternate textures
678 self.state = STATE_BOTTOM;
683 self.state = STATE_DOWN;
684 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
685 self.frame = 0; // use normal textures
687 self.takedamage = DAMAGE_YES; // can be shot again
691 void button_blocked()
693 // do nothing, just don't come all the way back out
699 self.health = self.max_health;
700 self.takedamage = DAMAGE_NO; // will be reset upon return
702 if (self.state == STATE_UP || self.state == STATE_TOP)
705 if (self.noise != "")
706 sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
708 self.state = STATE_UP;
709 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
714 self.health = self.max_health;
715 setorigin(self, self.pos1);
716 self.frame = 0; // use normal textures
717 self.state = STATE_BOTTOM;
719 self.takedamage = DAMAGE_YES; // can be shot again
724 // if (activator.classname != "player")
726 // dprint(activator.classname);
727 // dprint(" triggered a button\n");
730 if not (self.active == ACTIVE_ACTIVE)
733 self.enemy = activator;
739 // if (activator.classname != "player")
741 // dprint(activator.classname);
742 // dprint(" touched a button\n");
746 if not(other.iscreature)
748 if(other.velocity * self.movedir < 0)
752 self.enemy = other.owner;
756 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
758 if(self.spawnflags & DOOR_NOSPLASH)
759 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
761 self.health = self.health - damage;
762 if (self.health <= 0)
764 // if (activator.classname != "player")
766 // dprint(activator.classname);
767 // dprint(" killed a button\n");
769 self.enemy = damage_attacker;
775 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
776 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.
778 "angle" determines the opening direction
779 "target" all entities with a matching targetname will be used
780 "speed" override the default 40 speed
781 "wait" override the default 1 second wait (-1 = never return)
782 "lip" override the default 4 pixel lip remaining at end of move
783 "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
790 void spawnfunc_func_button()
794 if not(InitMovingBrushTrigger())
796 self.effects |= EF_LOWPRECISION;
798 self.blocked = button_blocked;
799 self.use = button_use;
801 // if (self.health == 0) // all buttons are now shootable
805 self.max_health = self.health;
806 self.event_damage = button_damage;
807 self.takedamage = DAMAGE_YES;
810 self.touch = button_touch;
820 precache_sound(self.noise);
822 self.active = ACTIVE_ACTIVE;
824 self.pos1 = self.origin;
825 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
826 self.flags |= FL_NOTARGET;
832 float DOOR_START_OPEN = 1;
833 float DOOR_DONT_LINK = 4;
834 float DOOR_TOGGLE = 32;
838 Doors are similar to buttons, but can spawn a fat trigger field around them
839 to open without a touch, and they link together to form simultanious
842 Door.owner is the master door. If there is only one door, it points to itself.
843 If multiple doors, all will point to a single one.
845 Door.enemy chains from the master door through all doors linked in the chain.
850 =============================================================================
854 =============================================================================
859 void() door_rotating_go_down;
860 void() door_rotating_go_up;
865 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
866 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
869 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
870 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
872 //Dont chamge direction for dead or dying stuff
873 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
876 if (self.state == STATE_DOWN)
877 if (self.classname == "door")
882 door_rotating_go_up ();
885 if (self.classname == "door")
890 door_rotating_go_down ();
894 //gib dying stuff just to make sure
895 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
896 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
900 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
901 // if a door has a negative wait, it would never come back if blocked,
902 // so let it just squash the object to death real fast
903 /* if (self.wait >= 0)
905 if (self.state == STATE_DOWN)
916 if (self.noise1 != "")
917 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
918 self.state = STATE_TOP;
919 if (self.spawnflags & DOOR_TOGGLE)
920 return; // don't come down automatically
921 if (self.classname == "door")
923 self.think = door_go_down;
926 self.think = door_rotating_go_down;
928 self.nextthink = self.ltime + self.wait;
931 void door_hit_bottom()
933 if (self.noise1 != "")
934 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
935 self.state = STATE_BOTTOM;
940 if (self.noise2 != "")
941 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
944 self.takedamage = DAMAGE_YES;
945 self.health = self.max_health;
948 self.state = STATE_DOWN;
949 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
954 if (self.state == STATE_UP)
955 return; // already going up
957 if (self.state == STATE_TOP)
958 { // reset top wait time
959 self.nextthink = self.ltime + self.wait;
963 if (self.noise2 != "")
964 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
965 self.state = STATE_UP;
966 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
969 oldmessage = self.message;
972 self.message = oldmessage;
978 =============================================================================
982 =============================================================================
985 float door_check_keys(void) {
995 if not(door.itemkeys)
998 // this door require a key
999 // only a player can have a key
1000 if (other.classname != "player")
1003 if (item_keys_usekey(door, other)) {
1004 // some keys were used
1005 if (other.key_door_messagetime <= time) {
1006 play2(other, "misc/talk.wav");
1007 centerprint(other, strcat("You also need ", item_keys_keylist(door.itemkeys), "!"));
1008 other.key_door_messagetime = time + 2;
1011 // no keys were used
1012 if (other.key_door_messagetime <= time) {
1013 play2(other, "misc/talk.wav");
1014 centerprint(other, strcat("You need ", item_keys_keylist(door.itemkeys), "!"));
1015 other.key_door_messagetime = time + 2;
1019 if (door.itemkeys) {
1020 // door is now unlocked
1021 play2(other, "misc/talk.wav");
1022 centerprint(other, "Door unlocked!");
1034 if (self.owner != self)
1035 objerror ("door_fire: self.owner != self");
1039 if (self.spawnflags & DOOR_TOGGLE)
1041 if (self.state == STATE_UP || self.state == STATE_TOP)
1046 if (self.classname == "door")
1052 door_rotating_go_down ();
1055 } while ( (self != starte) && (self != world) );
1061 // trigger all paired doors
1065 if (self.classname == "door")
1070 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
1071 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
1073 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
1074 self.pos2 = '0 0 0' - self.pos2;
1076 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
1077 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
1078 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
1080 door_rotating_go_up ();
1084 } while ( (self != starte) && (self != world) );
1093 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
1105 void door_trigger_touch()
1107 if (other.health < 1)
1108 if not(other.iscreature && other.deadflag == DEAD_NO)
1111 if (time < self.attack_finished_single)
1114 // check if door is locked
1115 if (!door_check_keys())
1118 self.attack_finished_single = time + 1;
1127 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1130 if(self.spawnflags & DOOR_NOSPLASH)
1131 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
1133 self.health = self.health - damage;
1135 if (self.itemkeys) {
1136 // don't allow opening doors through damage if keys are required
1140 if (self.health <= 0)
1144 self.health = self.max_health;
1145 self.takedamage = DAMAGE_NO; // wil be reset upon return
1161 if(other.classname != "player")
1163 if (self.owner.attack_finished_single > time)
1166 self.owner.attack_finished_single = time + 2;
1168 if (!(self.owner.dmg) && (self.owner.message != ""))
1170 if (other.flags & FL_CLIENT)
1171 centerprint (other, self.owner.message);
1172 play2(other, "misc/talk.wav");
1177 void door_generic_plat_blocked()
1180 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1181 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1184 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1185 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1187 //Dont chamge direction for dead or dying stuff
1188 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1191 if (self.state == STATE_DOWN)
1192 door_rotating_go_up ();
1194 door_rotating_go_down ();
1197 //gib dying stuff just to make sure
1198 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1199 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1203 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1204 // if a door has a negative wait, it would never come back if blocked,
1205 // so let it just squash the object to death real fast
1206 /* if (self.wait >= 0)
1208 if (self.state == STATE_DOWN)
1209 door_rotating_go_up ();
1211 door_rotating_go_down ();
1217 void door_rotating_hit_top()
1219 if (self.noise1 != "")
1220 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1221 self.state = STATE_TOP;
1222 if (self.spawnflags & DOOR_TOGGLE)
1223 return; // don't come down automatically
1224 self.think = door_rotating_go_down;
1225 self.nextthink = self.ltime + self.wait;
1228 void door_rotating_hit_bottom()
1230 if (self.noise1 != "")
1231 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1232 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1234 self.pos2 = '0 0 0' - self.pos2;
1237 self.state = STATE_BOTTOM;
1240 void door_rotating_go_down()
1242 if (self.noise2 != "")
1243 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1244 if (self.max_health)
1246 self.takedamage = DAMAGE_YES;
1247 self.health = self.max_health;
1250 self.state = STATE_DOWN;
1251 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
1254 void door_rotating_go_up()
1256 if (self.state == STATE_UP)
1257 return; // already going up
1259 if (self.state == STATE_TOP)
1260 { // reset top wait time
1261 self.nextthink = self.ltime + self.wait;
1264 if (self.noise2 != "")
1265 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1266 self.state = STATE_UP;
1267 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
1270 oldmessage = self.message;
1273 self.message = oldmessage;
1280 =============================================================================
1284 =============================================================================
1288 entity spawn_field(vector fmins, vector fmaxs)
1294 trigger.classname = "doortriggerfield";
1295 trigger.movetype = MOVETYPE_NONE;
1296 trigger.solid = SOLID_TRIGGER;
1297 trigger.owner = self;
1298 trigger.touch = door_trigger_touch;
1302 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1307 float EntitiesTouching(entity e1, entity e2)
1309 if (e1.absmin_x > e2.absmax_x)
1311 if (e1.absmin_y > e2.absmax_y)
1313 if (e1.absmin_z > e2.absmax_z)
1315 if (e1.absmax_x < e2.absmin_x)
1317 if (e1.absmax_y < e2.absmin_y)
1319 if (e1.absmax_z < e2.absmin_z)
1335 vector cmins, cmaxs;
1338 return; // already linked by another door
1339 if (self.spawnflags & 4)
1341 self.owner = self.enemy = self;
1349 self.trigger_field = spawn_field(self.absmin, self.absmax);
1351 return; // don't want to link this door
1354 cmins = self.absmin;
1355 cmaxs = self.absmax;
1362 self.owner = starte; // master door
1365 starte.health = self.health;
1367 starte.targetname = self.targetname;
1368 if (self.message != "")
1369 starte.message = self.message;
1371 t = find(t, classname, self.classname);
1374 self.enemy = starte; // make the chain a loop
1376 // shootable, or triggered doors just needed the owner/enemy links,
1377 // they don't spawn a field
1388 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1393 if (EntitiesTouching(self,t))
1396 objerror ("cross connected doors");
1401 if (t.absmin_x < cmins_x)
1402 cmins_x = t.absmin_x;
1403 if (t.absmin_y < cmins_y)
1404 cmins_y = t.absmin_y;
1405 if (t.absmin_z < cmins_z)
1406 cmins_z = t.absmin_z;
1407 if (t.absmax_x > cmaxs_x)
1408 cmaxs_x = t.absmax_x;
1409 if (t.absmax_y > cmaxs_y)
1410 cmaxs_y = t.absmax_y;
1411 if (t.absmax_z > cmaxs_z)
1412 cmaxs_z = t.absmax_z;
1419 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
1420 if two doors touch, they are assumed to be connected and operate as a unit.
1422 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1424 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).
1426 GOLD_KEY causes the door to open only if the activator holds a gold key.
1428 SILVER_KEY causes the door to open only if the activator holds a silver key.
1430 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1431 "angle" determines the opening direction
1432 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1433 "health" if set, door must be shot open
1434 "speed" movement speed (100 default)
1435 "wait" wait before returning (3 default, -1 = never return)
1436 "lip" lip remaining at end of move (8 default)
1437 "dmg" damage to inflict when blocked (2 default)
1444 FIXME: only one sound set available at the time being
1448 void door_init_startopen()
1450 setorigin (self, self.pos2);
1451 self.pos2 = self.pos1;
1452 self.pos1 = self.origin;
1457 setorigin(self, self.pos1);
1458 self.velocity = '0 0 0';
1459 self.state = STATE_BOTTOM;
1460 self.think = SUB_Null;
1463 // spawnflags require key (for now only func_door)
1464 #define SPAWNFLAGS_GOLD_KEY 8
1465 #define SPAWNFLAGS_SILVER_KEY 16
1466 void spawnfunc_func_door()
1468 // Quake 1 keys compatibility
1469 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
1470 self.itemkeys |= ITEM_KEY_BIT(0);
1471 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
1472 self.itemkeys |= ITEM_KEY_BIT(1);
1474 //if (!self.deathtype) // map makers can override this
1475 // self.deathtype = " got in the way";
1478 self.max_health = self.health;
1479 if not(InitMovingBrushTrigger())
1481 self.effects |= EF_LOWPRECISION;
1482 self.classname = "door";
1484 self.blocked = door_blocked;
1485 self.use = door_use;
1487 // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
1488 // if(self.spawnflags & 8)
1489 // self.dmg = 10000;
1491 if(self.dmg && (!self.message))
1492 self.message = "was squished";
1493 if(self.dmg && (!self.message2))
1494 self.message2 = "was squished by";
1496 if (self.sounds > 0)
1498 precache_sound ("plats/medplat1.wav");
1499 precache_sound ("plats/medplat2.wav");
1500 self.noise2 = "plats/medplat1.wav";
1501 self.noise1 = "plats/medplat2.wav";
1511 self.pos1 = self.origin;
1512 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1514 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1515 // but spawn in the open position
1516 if (self.spawnflags & DOOR_START_OPEN)
1517 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1519 self.state = STATE_BOTTOM;
1523 self.takedamage = DAMAGE_YES;
1524 self.event_damage = door_damage;
1530 self.touch = door_touch;
1532 // LinkDoors can't be done until all of the doors have been spawned, so
1533 // the sizes can be detected properly.
1534 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1536 self.reset = door_reset;
1539 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1540 if two doors touch, they are assumed to be connected and operate as a unit.
1542 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1544 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1545 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1546 must have set trigger_reverse to 1.
1547 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1549 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).
1551 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1552 "angle" determines the destination angle for opening. negative values reverse the direction.
1553 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1554 "health" if set, door must be shot open
1555 "speed" movement speed (100 default)
1556 "wait" wait before returning (3 default, -1 = never return)
1557 "dmg" damage to inflict when blocked (2 default)
1564 FIXME: only one sound set available at the time being
1567 void door_rotating_reset()
1569 self.angles = self.pos1;
1570 self.avelocity = '0 0 0';
1571 self.state = STATE_BOTTOM;
1572 self.think = SUB_Null;
1575 void door_rotating_init_startopen()
1577 self.angles = self.movedir;
1578 self.pos2 = '0 0 0';
1579 self.pos1 = self.movedir;
1583 void spawnfunc_func_door_rotating()
1586 //if (!self.deathtype) // map makers can override this
1587 // self.deathtype = " got in the way";
1589 // I abuse "movedir" for denoting the axis for now
1590 if (self.spawnflags & 64) // X (untested)
1591 self.movedir = '0 0 1';
1592 else if (self.spawnflags & 128) // Y (untested)
1593 self.movedir = '1 0 0';
1595 self.movedir = '0 1 0';
1597 if (self.angles_y==0) self.angles_y = 90;
1599 self.movedir = self.movedir * self.angles_y;
1600 self.angles = '0 0 0';
1602 self.max_health = self.health;
1603 self.avelocity = self.movedir;
1604 if not(InitMovingBrushTrigger())
1606 self.velocity = '0 0 0';
1607 //self.effects |= EF_LOWPRECISION;
1608 self.classname = "door_rotating";
1610 self.blocked = door_blocked;
1611 self.use = door_use;
1613 if(self.spawnflags & 8)
1616 if(self.dmg && (!self.message))
1617 self.message = "was squished";
1618 if(self.dmg && (!self.message2))
1619 self.message2 = "was squished by";
1621 if (self.sounds > 0)
1623 precache_sound ("plats/medplat1.wav");
1624 precache_sound ("plats/medplat2.wav");
1625 self.noise2 = "plats/medplat1.wav";
1626 self.noise1 = "plats/medplat2.wav";
1633 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1635 self.pos1 = '0 0 0';
1636 self.pos2 = self.movedir;
1638 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1639 // but spawn in the open position
1640 if (self.spawnflags & DOOR_START_OPEN)
1641 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1643 self.state = STATE_BOTTOM;
1647 self.takedamage = DAMAGE_YES;
1648 self.event_damage = door_damage;
1654 self.touch = door_touch;
1656 // LinkDoors can't be done until all of the doors have been spawned, so
1657 // the sizes can be detected properly.
1658 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1660 self.reset = door_rotating_reset;
1664 =============================================================================
1668 =============================================================================
1671 void() fd_secret_move1;
1672 void() fd_secret_move2;
1673 void() fd_secret_move3;
1674 void() fd_secret_move4;
1675 void() fd_secret_move5;
1676 void() fd_secret_move6;
1677 void() fd_secret_done;
1679 float SECRET_OPEN_ONCE = 1; // stays open
1680 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1681 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1682 float SECRET_NO_SHOOT = 8; // only opened by trigger
1683 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1686 void fd_secret_use()
1689 string message_save;
1691 self.health = 10000;
1692 self.bot_attack = TRUE;
1694 // exit if still moving around...
1695 if (self.origin != self.oldorigin)
1698 message_save = self.message;
1699 self.message = ""; // no more message
1700 SUB_UseTargets(); // fire all targets / killtargets
1701 self.message = message_save;
1703 self.velocity = '0 0 0';
1705 // Make a sound, wait a little...
1707 if (self.noise1 != "")
1708 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1709 self.nextthink = self.ltime + 0.1;
1711 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1712 makevectors(self.mangle);
1716 if (self.spawnflags & SECRET_1ST_DOWN)
1717 self.t_width = fabs(v_up * self.size);
1719 self.t_width = fabs(v_right * self.size);
1723 self.t_length = fabs(v_forward * self.size);
1725 if (self.spawnflags & SECRET_1ST_DOWN)
1726 self.dest1 = self.origin - v_up * self.t_width;
1728 self.dest1 = self.origin + v_right * (self.t_width * temp);
1730 self.dest2 = self.dest1 + v_forward * self.t_length;
1731 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
1732 if (self.noise2 != "")
1733 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1736 // Wait after first movement...
1737 void fd_secret_move1()
1739 self.nextthink = self.ltime + 1.0;
1740 self.think = fd_secret_move2;
1741 if (self.noise3 != "")
1742 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1745 // Start moving sideways w/sound...
1746 void fd_secret_move2()
1748 if (self.noise2 != "")
1749 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1750 SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
1753 // Wait here until time to go back...
1754 void fd_secret_move3()
1756 if (self.noise3 != "")
1757 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1758 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1760 self.nextthink = self.ltime + self.wait;
1761 self.think = fd_secret_move4;
1766 void fd_secret_move4()
1768 if (self.noise2 != "")
1769 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1770 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
1774 void fd_secret_move5()
1776 self.nextthink = self.ltime + 1.0;
1777 self.think = fd_secret_move6;
1778 if (self.noise3 != "")
1779 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1782 void fd_secret_move6()
1784 if (self.noise2 != "")
1785 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1786 SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
1789 void fd_secret_done()
1791 if (self.spawnflags&SECRET_YES_SHOOT)
1793 self.health = 10000;
1794 self.takedamage = DAMAGE_YES;
1795 //self.th_pain = fd_secret_use;
1797 if (self.noise3 != "")
1798 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1801 void secret_blocked()
1803 if (time < self.attack_finished_single)
1805 self.attack_finished_single = time + 0.5;
1806 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1818 if not(other.iscreature)
1820 if (self.attack_finished_single > time)
1823 self.attack_finished_single = time + 2;
1827 if (other.flags & FL_CLIENT)
1828 centerprint (other, self.message);
1829 play2(other, "misc/talk.wav");
1835 if (self.spawnflags&SECRET_YES_SHOOT)
1837 self.health = 10000;
1838 self.takedamage = DAMAGE_YES;
1840 setorigin(self, self.oldorigin);
1841 self.think = SUB_Null;
1844 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1845 Basic secret door. Slides back, then to the side. Angle determines direction.
1846 wait = # of seconds before coming back
1847 1st_left = 1st move is left of arrow
1848 1st_down = 1st move is down from arrow
1849 always_shoot = even if targeted, keep shootable
1850 t_width = override WIDTH to move back (or height if going down)
1851 t_length = override LENGTH to move sideways
1852 "dmg" damage to inflict when blocked (2 default)
1854 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1861 void spawnfunc_func_door_secret()
1863 /*if (!self.deathtype) // map makers can override this
1864 self.deathtype = " got in the way";*/
1870 self.mangle = self.angles;
1871 self.angles = '0 0 0';
1872 self.classname = "door";
1873 if not(InitMovingBrushTrigger())
1875 self.effects |= EF_LOWPRECISION;
1877 self.touch = secret_touch;
1878 self.blocked = secret_blocked;
1880 self.use = fd_secret_use;
1885 self.spawnflags |= SECRET_YES_SHOOT;
1887 if(self.spawnflags&SECRET_YES_SHOOT)
1889 self.health = 10000;
1890 self.takedamage = DAMAGE_YES;
1891 self.event_damage = fd_secret_use;
1893 self.oldorigin = self.origin;
1895 self.wait = 5; // 5 seconds before closing
1897 self.reset = secret_reset;
1901 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1902 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1903 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
1904 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1905 height: amplitude modifier (default 32)
1906 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1907 noise: path/name of looping .wav file to play.
1908 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1912 void func_fourier_controller_think()
1917 self.nextthink = time + 0.1;
1918 if not (self.owner.active == ACTIVE_ACTIVE)
1920 self.owner.velocity = '0 0 0';
1925 n = floor((tokenize_console(self.owner.netname)) / 5);
1926 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1928 v = self.owner.destvec;
1930 for(i = 0; i < n; ++i)
1932 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1933 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;
1936 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1937 // * 10 so it will arrive in 0.1 sec
1938 self.owner.velocity = (v - self.owner.origin) * 10;
1941 void spawnfunc_func_fourier()
1944 if (self.noise != "")
1946 precache_sound(self.noise);
1947 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
1954 self.destvec = self.origin;
1955 self.cnt = 360 / self.speed;
1957 self.blocked = generic_plat_blocked;
1958 if(self.dmg & (!self.message))
1959 self.message = " was squished";
1960 if(self.dmg && (!self.message2))
1961 self.message2 = "was squished by";
1962 if(self.dmg && (!self.dmgtime))
1963 self.dmgtime = 0.25;
1964 self.dmgtime2 = time;
1966 if(self.netname == "")
1967 self.netname = "1 0 0 0 1";
1969 if not(InitMovingBrushTrigger())
1972 self.active = ACTIVE_ACTIVE;
1974 // wait for targets to spawn
1975 controller = spawn();
1976 controller.classname = "func_fourier_controller";
1977 controller.owner = self;
1978 controller.nextthink = time + 1;
1979 controller.think = func_fourier_controller_think;
1980 self.nextthink = self.ltime + 999999999;
1981 self.think = SUB_Null;
1983 // Savage: Reduce bandwith, critical on e.g. nexdm02
1984 self.effects |= EF_LOWPRECISION;
1986 // TODO make a reset function for this one
1989 // reusing some fields havocbots declared
1990 .entity wp00, wp01, wp02, wp03;
1992 .float targetfactor, target2factor, target3factor, target4factor;
1993 .vector targetnormal, target2normal, target3normal, target4normal;
1995 vector func_vectormamamam_origin(entity o, float t)
2007 p = e.origin + t * e.velocity;
2009 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
2011 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
2017 p = e.origin + t * e.velocity;
2019 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
2021 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
2027 p = e.origin + t * e.velocity;
2029 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
2031 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
2037 p = e.origin + t * e.velocity;
2039 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
2041 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
2047 void func_vectormamamam_controller_think()
2049 self.nextthink = time + 0.1;
2051 if not (self.owner.active == ACTIVE_ACTIVE)
2053 self.owner.velocity = '0 0 0';
2057 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
2058 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
2061 void func_vectormamamam_findtarget()
2063 if(self.target != "")
2064 self.wp00 = find(world, targetname, self.target);
2066 if(self.target2 != "")
2067 self.wp01 = find(world, targetname, self.target2);
2069 if(self.target3 != "")
2070 self.wp02 = find(world, targetname, self.target3);
2072 if(self.target4 != "")
2073 self.wp03 = find(world, targetname, self.target4);
2075 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
2076 objerror("No reference entity found, so there is nothing to move. Aborting.");
2078 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
2081 controller = spawn();
2082 controller.classname = "func_vectormamamam_controller";
2083 controller.owner = self;
2084 controller.nextthink = time + 1;
2085 controller.think = func_vectormamamam_controller_think;
2088 void spawnfunc_func_vectormamamam()
2090 if (self.noise != "")
2092 precache_sound(self.noise);
2093 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
2096 if(!self.targetfactor)
2097 self.targetfactor = 1;
2099 if(!self.target2factor)
2100 self.target2factor = 1;
2102 if(!self.target3factor)
2103 self.target3factor = 1;
2105 if(!self.target4factor)
2106 self.target4factor = 1;
2108 if(vlen(self.targetnormal))
2109 self.targetnormal = normalize(self.targetnormal);
2111 if(vlen(self.target2normal))
2112 self.target2normal = normalize(self.target2normal);
2114 if(vlen(self.target3normal))
2115 self.target3normal = normalize(self.target3normal);
2117 if(vlen(self.target4normal))
2118 self.target4normal = normalize(self.target4normal);
2120 self.blocked = generic_plat_blocked;
2121 if(self.dmg & (!self.message))
2122 self.message = " was squished";
2123 if(self.dmg && (!self.message2))
2124 self.message2 = "was squished by";
2125 if(self.dmg && (!self.dmgtime))
2126 self.dmgtime = 0.25;
2127 self.dmgtime2 = time;
2129 if(self.netname == "")
2130 self.netname = "1 0 0 0 1";
2132 if not(InitMovingBrushTrigger())
2135 // wait for targets to spawn
2136 self.nextthink = self.ltime + 999999999;
2137 self.think = SUB_Null;
2139 // Savage: Reduce bandwith, critical on e.g. nexdm02
2140 self.effects |= EF_LOWPRECISION;
2142 self.active = ACTIVE_ACTIVE;
2144 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
2147 void conveyor_think()
2151 // set myself as current conveyor where possible
2152 for(e = world; (e = findentity(e, conveyor, self)); )
2157 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2158 if(!e.conveyor.state)
2161 vector emin = e.absmin;
2162 vector emax = e.absmax;
2163 if(self.solid == SOLID_BSP)
2168 if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2169 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2173 for(e = world; (e = findentity(e, conveyor, self)); )
2175 if(e.flags & FL_CLIENT) // doing it via velocity has quite some advantages
2176 continue; // done in SV_PlayerPhysics
2178 setorigin(e, e.origin + self.movedir * sys_frametime);
2179 move_out_of_solid(e);
2180 UpdateCSQCProjectile(e);
2182 // stupid conveyor code
2183 tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2184 if(trace_fraction > 0)
2185 setorigin(e, trace_endpos);
2190 self.nextthink = time;
2195 self.state = !self.state;
2198 void conveyor_reset()
2200 self.state = (self.spawnflags & 1);
2203 void conveyor_init()
2207 self.movedir = self.movedir * self.speed;
2208 self.think = conveyor_think;
2209 self.nextthink = time;
2212 self.use = conveyor_use;
2213 self.reset = conveyor_reset;
2220 void spawnfunc_trigger_conveyor()
2227 void spawnfunc_func_conveyor()
2230 InitMovingBrushTrigger();
2231 self.movetype = MOVETYPE_NONE;