2 void generic_plat_blocked()
4 if(self.dmg && other.takedamage != DAMAGE_NO) {
5 if(self.dmgtime2 < time) {
6 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
7 self.dmgtime2 = time + self.dmgtime;
10 // Gib dead/dying stuff
11 if(other.deadflag != DEAD_NO)
12 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
18 float STATE_BOTTOM = 1;
22 .entity trigger_field;
24 void() plat_center_touch;
25 void() plat_outside_touch;
26 void() plat_trigger_use;
30 float PLAT_LOW_TRIGGER = 1;
32 void plat_spawn_inside_trigger()
38 trigger.touch = plat_center_touch;
39 trigger.movetype = MOVETYPE_NONE;
40 trigger.solid = SOLID_TRIGGER;
43 tmin = self.absmin + '25 25 0';
44 tmax = self.absmax - '25 25 -8';
45 tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8);
46 if (self.spawnflags & PLAT_LOW_TRIGGER)
49 if (self.size_x <= 50)
51 tmin_x = (self.mins_x + self.maxs_x) / 2;
54 if (self.size_y <= 50)
56 tmin_y = (self.mins_y + self.maxs_y) / 2;
64 setsize (trigger, tmin, tmax);
68 // otherwise, something is fishy...
70 objerror("plat_spawn_inside_trigger: platform has odd size or lip, can't spawn");
75 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
77 self.think = plat_go_down;
78 self.nextthink = self.ltime + 3;
81 void plat_hit_bottom()
83 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
89 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_NORM);
91 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, plat_hit_bottom);
96 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_NORM);
98 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, plat_hit_top);
101 void plat_center_touch()
103 if not(other.iscreature)
106 if (other.health <= 0)
112 else if (self.state == 1)
113 self.nextthink = self.ltime + 1; // delay going down
116 void plat_outside_touch()
118 if not(other.iscreature)
121 if (other.health <= 0)
129 void plat_trigger_use()
132 return; // already activated
139 if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
140 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
142 if((self.dmg) && (other.takedamage != DAMAGE_NO)) { // Shall we bite?
143 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
144 // Gib dead/dying stuff
145 if(other.deadflag != DEAD_NO)
146 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
151 else if (self.state == 3)
153 // when in other states, then the plat_crush event came delayed after
154 // plat state already had changed
155 // this isn't a bug per se!
163 objerror ("plat_use: not in up state");
167 .string sound1, sound2;
173 setorigin (self, self.pos1);
179 setorigin (self, self.pos2);
181 self.use = plat_trigger_use;
185 .float platmovetype_start_default, platmovetype_end_default;
186 float set_platmovetype(entity e, string s)
188 // sets platmovetype_start and platmovetype_end based on a string consisting of two values
191 n = tokenize_console(s);
193 e.platmovetype_start = stof(argv(0));
195 e.platmovetype_start = 0;
198 e.platmovetype_end = stof(argv(1));
200 e.platmovetype_end = e.platmovetype_start;
203 if(argv(2) == "force")
204 return TRUE; // no checking, return immediately
206 if(!cubic_speedfunc_is_sane(e.platmovetype_start, e.platmovetype_end))
208 objerror("Invalid platform move type; platform would go in reverse, which is not allowed.");
215 void spawnfunc_path_corner()
217 // setup values for overriding train movement
218 // if a second value does not exist, both start and end speeds are the single value specified
219 if(!set_platmovetype(self, self.platmovetype))
222 void spawnfunc_func_plat()
224 if (self.sounds == 0)
227 if(self.spawnflags & 4)
230 if(self.dmg && (!self.message))
231 self.message = "was squished";
232 if(self.dmg && (!self.message2))
233 self.message2 = "was squished by";
235 if (self.sounds == 1)
237 precache_sound ("plats/plat1.wav");
238 precache_sound ("plats/plat2.wav");
239 self.noise = "plats/plat1.wav";
240 self.noise1 = "plats/plat2.wav";
243 if (self.sounds == 2)
245 precache_sound ("plats/medplat1.wav");
246 precache_sound ("plats/medplat2.wav");
247 self.noise = "plats/medplat1.wav";
248 self.noise1 = "plats/medplat2.wav";
253 precache_sound (self.sound1);
254 self.noise = self.sound1;
258 precache_sound (self.sound2);
259 self.noise1 = self.sound2;
262 self.mangle = self.angles;
263 self.angles = '0 0 0';
265 self.classname = "plat";
266 if not(InitMovingBrushTrigger())
268 self.effects |= EF_LOWPRECISION;
269 setsize (self, self.mins , self.maxs);
271 self.blocked = plat_crush;
278 self.height = self.size_z - self.lip;
280 self.pos1 = self.origin;
281 self.pos2 = self.origin;
282 self.pos2_z = self.origin_z - self.height;
284 self.reset = plat_reset;
287 plat_spawn_inside_trigger (); // the "start moving" trigger
290 .float train_wait_turning;
301 // if turning is enabled, the train will turn toward the next point while waiting
302 if(self.platmovetype_turn && !self.train_wait_turning)
306 targ = find(world, targetname, self.target);
307 if((self.spawnflags & 1) && targ.curvetarget)
308 cp = find(world, targetname, targ.curvetarget);
312 if(cp) // bezier curves movement
313 org = normalize(cp.origin - (self.origin - self.mins)); // use the origin of the control point of the next path_corner
314 else // linear movement
315 org = normalize(targ.origin - (self.origin - self.mins)); // use the origin of the next path_corner
317 if(self.wait >= 0) // slow turning
319 SUB_CalcAngleMove(org, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
320 self.train_wait_turning = TRUE;
323 else // instant turning
324 self.angles = vectoangles(org);
328 stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
330 if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
332 self.train_wait_turning = FALSE;
337 self.think = train_next;
338 self.nextthink = self.ltime + self.wait;
347 targ = find(world, targetname, self.target);
348 self.target = targ.target;
349 if (self.spawnflags & 1)
353 cp = find(world, targetname, targ.curvetarget); // get its second target (the control point)
354 cp_org = cp.origin - self.mins; // no control point found, assume a straight line to the destination
360 objerror("train_next: no next target");
361 self.wait = targ.wait;
365 if(targ.platmovetype)
367 // this path_corner contains a movetype overrider, apply it
368 self.platmovetype_start = targ.platmovetype_start;
369 self.platmovetype_end = targ.platmovetype_end;
373 // this path_corner doesn't contain a movetype overrider, use the train's defaults
374 self.platmovetype_start = self.platmovetype_start_default;
375 self.platmovetype_end = self.platmovetype_end_default;
381 SUB_CalcMove_Bezier(cp_org, targ.origin - self.mins, TSPEED_LINEAR, targ.speed, train_wait);
383 SUB_CalcMove(targ.origin - self.mins, TSPEED_LINEAR, targ.speed, train_wait);
388 SUB_CalcMove_Bezier(cp_org, targ.origin - self.mins, TSPEED_LINEAR, self.speed, train_wait);
390 SUB_CalcMove(targ.origin - self.mins, TSPEED_LINEAR, self.speed, train_wait);
394 sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
397 void func_train_find()
400 targ = find(world, targetname, self.target);
401 self.target = targ.target;
403 objerror("func_train_find: no next target");
404 setorigin(self, targ.origin - self.mins);
405 self.nextthink = self.ltime + 1;
406 self.think = train_next;
409 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
410 Ridable platform, targets spawnfunc_path_corner path to follow.
411 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
412 target : targetname of first spawnfunc_path_corner (starts here)
414 void spawnfunc_func_train()
416 if (self.noise != "")
417 precache_sound(self.noise);
420 objerror("func_train without a target");
423 if (self.spawnflags & 2)
424 self.platmovetype_turn = TRUE;
426 if not(InitMovingBrushTrigger())
428 self.effects |= EF_LOWPRECISION;
430 // wait for targets to spawn
431 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
433 self.blocked = generic_plat_blocked;
434 if(self.dmg & (!self.message))
435 self.message = " was squished";
436 if(self.dmg && (!self.message2))
437 self.message2 = "was squished by";
438 if(self.dmg && (!self.dmgtime))
440 self.dmgtime2 = time;
442 if(!set_platmovetype(self, self.platmovetype))
444 self.platmovetype_start_default = self.platmovetype_start;
445 self.platmovetype_end_default = self.platmovetype_end;
447 // TODO make a reset function for this one
450 void func_rotating_setactive(float astate)
453 if (astate == ACTIVE_TOGGLE)
455 if(self.active == ACTIVE_ACTIVE)
456 self.active = ACTIVE_NOT;
458 self.active = ACTIVE_ACTIVE;
461 self.active = astate;
463 if(self.active == ACTIVE_NOT)
464 self.avelocity = '0 0 0';
466 self.avelocity = self.pos1;
469 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
470 Brush model that spins in place on one axis (default Z).
471 speed : speed to rotate (in degrees per second)
472 noise : path/name of looping .wav file to play.
473 dmg : Do this mutch dmg every .dmgtime intervall when blocked
477 void spawnfunc_func_rotating()
479 if (self.noise != "")
481 precache_sound(self.noise);
482 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
485 self.active = ACTIVE_ACTIVE;
486 self.setactive = func_rotating_setactive;
490 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
491 if (self.spawnflags & 4) // X (untested)
492 self.avelocity = '0 0 1' * self.speed;
493 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
494 else if (self.spawnflags & 8) // Y (untested)
495 self.avelocity = '1 0 0' * self.speed;
496 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
498 self.avelocity = '0 1 0' * self.speed;
500 self.pos1 = self.avelocity;
502 if(self.dmg & (!self.message))
503 self.message = " was squished";
504 if(self.dmg && (!self.message2))
505 self.message2 = "was squished by";
508 if(self.dmg && (!self.dmgtime))
511 self.dmgtime2 = time;
513 if not(InitMovingBrushTrigger())
515 // no EF_LOWPRECISION here, as rounding angles is bad
517 self.blocked = generic_plat_blocked;
519 // wait for targets to spawn
520 self.nextthink = self.ltime + 999999999;
521 self.think = SUB_Null;
523 // TODO make a reset function for this one
527 void func_bobbing_controller_think()
530 self.nextthink = time + 0.1;
532 if not (self.owner.active == ACTIVE_ACTIVE)
534 self.owner.velocity = '0 0 0';
538 // calculate sinewave using makevectors
539 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
540 v = self.owner.destvec + self.owner.movedir * v_forward_y;
541 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
542 // * 10 so it will arrive in 0.1 sec
543 self.owner.velocity = (v - self.owner.origin) * 10;
546 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
547 Brush model that moves back and forth on one axis (default Z).
548 speed : how long one cycle takes in seconds (default 4)
549 height : how far the cycle moves (default 32)
550 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
551 noise : path/name of looping .wav file to play.
552 dmg : Do this mutch dmg every .dmgtime intervall when blocked
555 void spawnfunc_func_bobbing()
558 if (self.noise != "")
560 precache_sound(self.noise);
561 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
567 // center of bobbing motion
568 self.destvec = self.origin;
569 // time scale to get degrees
570 self.cnt = 360 / self.speed;
572 self.active = ACTIVE_ACTIVE;
574 // damage when blocked
575 self.blocked = generic_plat_blocked;
576 if(self.dmg & (!self.message))
577 self.message = " was squished";
578 if(self.dmg && (!self.message2))
579 self.message2 = "was squished by";
580 if(self.dmg && (!self.dmgtime))
582 self.dmgtime2 = time;
585 if (self.spawnflags & 1) // X
586 self.movedir = '1 0 0' * self.height;
587 else if (self.spawnflags & 2) // Y
588 self.movedir = '0 1 0' * self.height;
590 self.movedir = '0 0 1' * self.height;
592 if not(InitMovingBrushTrigger())
595 // wait for targets to spawn
596 controller = spawn();
597 controller.classname = "func_bobbing_controller";
598 controller.owner = self;
599 controller.nextthink = time + 1;
600 controller.think = func_bobbing_controller_think;
601 self.nextthink = self.ltime + 999999999;
602 self.think = SUB_Null;
604 // Savage: Reduce bandwith, critical on e.g. nexdm02
605 self.effects |= EF_LOWPRECISION;
607 // TODO make a reset function for this one
611 void func_pendulum_controller_think()
614 self.nextthink = time + 0.1;
616 if not (self.owner.active == ACTIVE_ACTIVE)
618 self.owner.avelocity_x = 0;
622 // calculate sinewave using makevectors
623 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
624 v = self.owner.speed * v_forward_y + self.cnt;
625 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
627 // * 10 so it will arrive in 0.1 sec
628 self.owner.avelocity_z = (remainder(v - self.owner.angles_z, 360)) * 10;
632 void spawnfunc_func_pendulum()
635 if (self.noise != "")
637 precache_sound(self.noise);
638 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
641 self.active = ACTIVE_ACTIVE;
643 // keys: angle, speed, phase, noise, freq
647 // not initializing self.dmg to 2, to allow damageless pendulum
649 if(self.dmg & (!self.message))
650 self.message = " was squished";
651 if(self.dmg && (!self.message2))
652 self.message2 = "was squished by";
653 if(self.dmg && (!self.dmgtime))
655 self.dmgtime2 = time;
657 self.blocked = generic_plat_blocked;
659 self.avelocity_z = 0.0000001;
660 if not(InitMovingBrushTrigger())
665 // find pendulum length (same formula as Q3A)
666 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins_z))));
669 // copy initial angle
670 self.cnt = self.angles_z;
672 // wait for targets to spawn
673 controller = spawn();
674 controller.classname = "func_pendulum_controller";
675 controller.owner = self;
676 controller.nextthink = time + 1;
677 controller.think = func_pendulum_controller_think;
678 self.nextthink = self.ltime + 999999999;
679 self.think = SUB_Null;
681 //self.effects |= EF_LOWPRECISION;
683 // TODO make a reset function for this one
686 // button and multiple button
689 void() button_return;
693 self.state = STATE_TOP;
694 self.nextthink = self.ltime + self.wait;
695 self.think = button_return;
696 activator = self.enemy;
698 self.frame = 1; // use alternate textures
703 self.state = STATE_BOTTOM;
708 self.state = STATE_DOWN;
709 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
710 self.frame = 0; // use normal textures
712 self.takedamage = DAMAGE_YES; // can be shot again
716 void button_blocked()
718 // do nothing, just don't come all the way back out
724 self.health = self.max_health;
725 self.takedamage = DAMAGE_NO; // will be reset upon return
727 if (self.state == STATE_UP || self.state == STATE_TOP)
730 if (self.noise != "")
731 sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
733 self.state = STATE_UP;
734 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
739 self.health = self.max_health;
740 setorigin(self, self.pos1);
741 self.frame = 0; // use normal textures
742 self.state = STATE_BOTTOM;
744 self.takedamage = DAMAGE_YES; // can be shot again
749 // if (activator.classname != "player")
751 // dprint(activator.classname);
752 // dprint(" triggered a button\n");
755 if not (self.active == ACTIVE_ACTIVE)
758 self.enemy = activator;
764 // if (activator.classname != "player")
766 // dprint(activator.classname);
767 // dprint(" touched a button\n");
771 if not(other.iscreature)
773 if(other.velocity * self.movedir < 0)
777 self.enemy = other.owner;
781 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
783 if(self.spawnflags & DOOR_NOSPLASH)
784 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
786 self.health = self.health - damage;
787 if (self.health <= 0)
789 // if (activator.classname != "player")
791 // dprint(activator.classname);
792 // dprint(" killed a button\n");
794 self.enemy = damage_attacker;
800 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
801 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.
803 "angle" determines the opening direction
804 "target" all entities with a matching targetname will be used
805 "speed" override the default 40 speed
806 "wait" override the default 1 second wait (-1 = never return)
807 "lip" override the default 4 pixel lip remaining at end of move
808 "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
815 void spawnfunc_func_button()
819 if not(InitMovingBrushTrigger())
821 self.effects |= EF_LOWPRECISION;
823 self.blocked = button_blocked;
824 self.use = button_use;
826 // if (self.health == 0) // all buttons are now shootable
830 self.max_health = self.health;
831 self.event_damage = button_damage;
832 self.takedamage = DAMAGE_YES;
835 self.touch = button_touch;
845 precache_sound(self.noise);
847 self.active = ACTIVE_ACTIVE;
849 self.pos1 = self.origin;
850 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
851 self.flags |= FL_NOTARGET;
857 float DOOR_START_OPEN = 1;
858 float DOOR_DONT_LINK = 4;
859 float DOOR_TOGGLE = 32;
863 Doors are similar to buttons, but can spawn a fat trigger field around them
864 to open without a touch, and they link together to form simultanious
867 Door.owner is the master door. If there is only one door, it points to itself.
868 If multiple doors, all will point to a single one.
870 Door.enemy chains from the master door through all doors linked in the chain.
875 =============================================================================
879 =============================================================================
884 void() door_rotating_go_down;
885 void() door_rotating_go_up;
890 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
891 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
894 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
895 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
897 //Dont chamge direction for dead or dying stuff
898 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
901 if (self.state == STATE_DOWN)
902 if (self.classname == "door")
907 door_rotating_go_up ();
910 if (self.classname == "door")
915 door_rotating_go_down ();
919 //gib dying stuff just to make sure
920 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
921 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
925 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
926 // if a door has a negative wait, it would never come back if blocked,
927 // so let it just squash the object to death real fast
928 /* if (self.wait >= 0)
930 if (self.state == STATE_DOWN)
941 if (self.noise1 != "")
942 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
943 self.state = STATE_TOP;
944 if (self.spawnflags & DOOR_TOGGLE)
945 return; // don't come down automatically
946 if (self.classname == "door")
948 self.think = door_go_down;
951 self.think = door_rotating_go_down;
953 self.nextthink = self.ltime + self.wait;
956 void door_hit_bottom()
958 if (self.noise1 != "")
959 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
960 self.state = STATE_BOTTOM;
965 if (self.noise2 != "")
966 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
969 self.takedamage = DAMAGE_YES;
970 self.health = self.max_health;
973 self.state = STATE_DOWN;
974 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
979 if (self.state == STATE_UP)
980 return; // already going up
982 if (self.state == STATE_TOP)
983 { // reset top wait time
984 self.nextthink = self.ltime + self.wait;
988 if (self.noise2 != "")
989 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
990 self.state = STATE_UP;
991 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
994 oldmessage = self.message;
997 self.message = oldmessage;
1003 =============================================================================
1005 ACTIVATION FUNCTIONS
1007 =============================================================================
1010 float door_check_keys(void) {
1020 if not(door.itemkeys)
1023 // this door require a key
1024 // only a player can have a key
1025 if (other.classname != "player")
1028 if (item_keys_usekey(door, other)) {
1029 // some keys were used
1030 if (other.key_door_messagetime <= time) {
1031 play2(other, "misc/talk.wav");
1032 centerprint(other, strcat("You also need ", item_keys_keylist(door.itemkeys), "!"));
1033 other.key_door_messagetime = time + 2;
1036 // no keys were used
1037 if (other.key_door_messagetime <= time) {
1038 play2(other, "misc/talk.wav");
1039 centerprint(other, strcat("You need ", item_keys_keylist(door.itemkeys), "!"));
1040 other.key_door_messagetime = time + 2;
1044 if (door.itemkeys) {
1045 // door is now unlocked
1046 play2(other, "misc/talk.wav");
1047 centerprint(other, "Door unlocked!");
1059 if (self.owner != self)
1060 objerror ("door_fire: self.owner != self");
1064 if (self.spawnflags & DOOR_TOGGLE)
1066 if (self.state == STATE_UP || self.state == STATE_TOP)
1071 if (self.classname == "door")
1077 door_rotating_go_down ();
1080 } while ( (self != starte) && (self != world) );
1086 // trigger all paired doors
1090 if (self.classname == "door")
1095 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
1096 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
1098 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
1099 self.pos2 = '0 0 0' - self.pos2;
1101 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
1102 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
1103 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
1105 door_rotating_go_up ();
1109 } while ( (self != starte) && (self != world) );
1118 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
1130 void door_trigger_touch()
1132 if (other.health < 1)
1133 if not(other.iscreature && other.deadflag == DEAD_NO)
1136 if (time < self.attack_finished_single)
1139 // check if door is locked
1140 if (!door_check_keys())
1143 self.attack_finished_single = time + 1;
1152 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1155 if(self.spawnflags & DOOR_NOSPLASH)
1156 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
1158 self.health = self.health - damage;
1160 if (self.itemkeys) {
1161 // don't allow opening doors through damage if keys are required
1165 if (self.health <= 0)
1169 self.health = self.max_health;
1170 self.takedamage = DAMAGE_NO; // wil be reset upon return
1186 if(other.classname != "player")
1188 if (self.owner.attack_finished_single > time)
1191 self.owner.attack_finished_single = time + 2;
1193 if (!(self.owner.dmg) && (self.owner.message != ""))
1195 if (other.flags & FL_CLIENT)
1196 centerprint (other, self.owner.message);
1197 play2(other, "misc/talk.wav");
1202 void door_generic_plat_blocked()
1205 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1206 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1209 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1210 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1212 //Dont chamge direction for dead or dying stuff
1213 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1216 if (self.state == STATE_DOWN)
1217 door_rotating_go_up ();
1219 door_rotating_go_down ();
1222 //gib dying stuff just to make sure
1223 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1224 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1228 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1229 // if a door has a negative wait, it would never come back if blocked,
1230 // so let it just squash the object to death real fast
1231 /* if (self.wait >= 0)
1233 if (self.state == STATE_DOWN)
1234 door_rotating_go_up ();
1236 door_rotating_go_down ();
1242 void door_rotating_hit_top()
1244 if (self.noise1 != "")
1245 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1246 self.state = STATE_TOP;
1247 if (self.spawnflags & DOOR_TOGGLE)
1248 return; // don't come down automatically
1249 self.think = door_rotating_go_down;
1250 self.nextthink = self.ltime + self.wait;
1253 void door_rotating_hit_bottom()
1255 if (self.noise1 != "")
1256 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1257 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1259 self.pos2 = '0 0 0' - self.pos2;
1262 self.state = STATE_BOTTOM;
1265 void door_rotating_go_down()
1267 if (self.noise2 != "")
1268 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1269 if (self.max_health)
1271 self.takedamage = DAMAGE_YES;
1272 self.health = self.max_health;
1275 self.state = STATE_DOWN;
1276 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
1279 void door_rotating_go_up()
1281 if (self.state == STATE_UP)
1282 return; // already going up
1284 if (self.state == STATE_TOP)
1285 { // reset top wait time
1286 self.nextthink = self.ltime + self.wait;
1289 if (self.noise2 != "")
1290 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1291 self.state = STATE_UP;
1292 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
1295 oldmessage = self.message;
1298 self.message = oldmessage;
1305 =============================================================================
1309 =============================================================================
1313 entity spawn_field(vector fmins, vector fmaxs)
1319 trigger.classname = "doortriggerfield";
1320 trigger.movetype = MOVETYPE_NONE;
1321 trigger.solid = SOLID_TRIGGER;
1322 trigger.owner = self;
1323 trigger.touch = door_trigger_touch;
1327 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1332 float EntitiesTouching(entity e1, entity e2)
1334 if (e1.absmin_x > e2.absmax_x)
1336 if (e1.absmin_y > e2.absmax_y)
1338 if (e1.absmin_z > e2.absmax_z)
1340 if (e1.absmax_x < e2.absmin_x)
1342 if (e1.absmax_y < e2.absmin_y)
1344 if (e1.absmax_z < e2.absmin_z)
1360 vector cmins, cmaxs;
1363 return; // already linked by another door
1364 if (self.spawnflags & 4)
1366 self.owner = self.enemy = self;
1374 self.trigger_field = spawn_field(self.absmin, self.absmax);
1376 return; // don't want to link this door
1379 cmins = self.absmin;
1380 cmaxs = self.absmax;
1387 self.owner = starte; // master door
1390 starte.health = self.health;
1392 starte.targetname = self.targetname;
1393 if (self.message != "")
1394 starte.message = self.message;
1396 t = find(t, classname, self.classname);
1399 self.enemy = starte; // make the chain a loop
1401 // shootable, or triggered doors just needed the owner/enemy links,
1402 // they don't spawn a field
1413 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1418 if (EntitiesTouching(self,t))
1421 objerror ("cross connected doors");
1426 if (t.absmin_x < cmins_x)
1427 cmins_x = t.absmin_x;
1428 if (t.absmin_y < cmins_y)
1429 cmins_y = t.absmin_y;
1430 if (t.absmin_z < cmins_z)
1431 cmins_z = t.absmin_z;
1432 if (t.absmax_x > cmaxs_x)
1433 cmaxs_x = t.absmax_x;
1434 if (t.absmax_y > cmaxs_y)
1435 cmaxs_y = t.absmax_y;
1436 if (t.absmax_z > cmaxs_z)
1437 cmaxs_z = t.absmax_z;
1444 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
1445 if two doors touch, they are assumed to be connected and operate as a unit.
1447 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1449 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).
1451 GOLD_KEY causes the door to open only if the activator holds a gold key.
1453 SILVER_KEY causes the door to open only if the activator holds a silver key.
1455 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1456 "angle" determines the opening direction
1457 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1458 "health" if set, door must be shot open
1459 "speed" movement speed (100 default)
1460 "wait" wait before returning (3 default, -1 = never return)
1461 "lip" lip remaining at end of move (8 default)
1462 "dmg" damage to inflict when blocked (2 default)
1469 FIXME: only one sound set available at the time being
1473 void door_init_startopen()
1475 setorigin (self, self.pos2);
1476 self.pos2 = self.pos1;
1477 self.pos1 = self.origin;
1482 setorigin(self, self.pos1);
1483 self.velocity = '0 0 0';
1484 self.state = STATE_BOTTOM;
1485 self.think = SUB_Null;
1488 // spawnflags require key (for now only func_door)
1489 #define SPAWNFLAGS_GOLD_KEY 8
1490 #define SPAWNFLAGS_SILVER_KEY 16
1491 void spawnfunc_func_door()
1493 // Quake 1 keys compatibility
1494 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
1495 self.itemkeys |= ITEM_KEY_BIT(0);
1496 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
1497 self.itemkeys |= ITEM_KEY_BIT(1);
1499 //if (!self.deathtype) // map makers can override this
1500 // self.deathtype = " got in the way";
1503 self.max_health = self.health;
1504 if not(InitMovingBrushTrigger())
1506 self.effects |= EF_LOWPRECISION;
1507 self.classname = "door";
1509 self.blocked = door_blocked;
1510 self.use = door_use;
1512 // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
1513 // if(self.spawnflags & 8)
1514 // self.dmg = 10000;
1516 if(self.dmg && (!self.message))
1517 self.message = "was squished";
1518 if(self.dmg && (!self.message2))
1519 self.message2 = "was squished by";
1521 if (self.sounds > 0)
1523 precache_sound ("plats/medplat1.wav");
1524 precache_sound ("plats/medplat2.wav");
1525 self.noise2 = "plats/medplat1.wav";
1526 self.noise1 = "plats/medplat2.wav";
1536 self.pos1 = self.origin;
1537 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1539 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1540 // but spawn in the open position
1541 if (self.spawnflags & DOOR_START_OPEN)
1542 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1544 self.state = STATE_BOTTOM;
1548 self.takedamage = DAMAGE_YES;
1549 self.event_damage = door_damage;
1555 self.touch = door_touch;
1557 // LinkDoors can't be done until all of the doors have been spawned, so
1558 // the sizes can be detected properly.
1559 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1561 self.reset = door_reset;
1564 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1565 if two doors touch, they are assumed to be connected and operate as a unit.
1567 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1569 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1570 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1571 must have set trigger_reverse to 1.
1572 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1574 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).
1576 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1577 "angle" determines the destination angle for opening. negative values reverse the direction.
1578 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1579 "health" if set, door must be shot open
1580 "speed" movement speed (100 default)
1581 "wait" wait before returning (3 default, -1 = never return)
1582 "dmg" damage to inflict when blocked (2 default)
1589 FIXME: only one sound set available at the time being
1592 void door_rotating_reset()
1594 self.angles = self.pos1;
1595 self.avelocity = '0 0 0';
1596 self.state = STATE_BOTTOM;
1597 self.think = SUB_Null;
1600 void door_rotating_init_startopen()
1602 self.angles = self.movedir;
1603 self.pos2 = '0 0 0';
1604 self.pos1 = self.movedir;
1608 void spawnfunc_func_door_rotating()
1611 //if (!self.deathtype) // map makers can override this
1612 // self.deathtype = " got in the way";
1614 // I abuse "movedir" for denoting the axis for now
1615 if (self.spawnflags & 64) // X (untested)
1616 self.movedir = '0 0 1';
1617 else if (self.spawnflags & 128) // Y (untested)
1618 self.movedir = '1 0 0';
1620 self.movedir = '0 1 0';
1622 if (self.angles_y==0) self.angles_y = 90;
1624 self.movedir = self.movedir * self.angles_y;
1625 self.angles = '0 0 0';
1627 self.max_health = self.health;
1628 self.avelocity = self.movedir;
1629 if not(InitMovingBrushTrigger())
1631 self.velocity = '0 0 0';
1632 //self.effects |= EF_LOWPRECISION;
1633 self.classname = "door_rotating";
1635 self.blocked = door_blocked;
1636 self.use = door_use;
1638 if(self.spawnflags & 8)
1641 if(self.dmg && (!self.message))
1642 self.message = "was squished";
1643 if(self.dmg && (!self.message2))
1644 self.message2 = "was squished by";
1646 if (self.sounds > 0)
1648 precache_sound ("plats/medplat1.wav");
1649 precache_sound ("plats/medplat2.wav");
1650 self.noise2 = "plats/medplat1.wav";
1651 self.noise1 = "plats/medplat2.wav";
1658 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1660 self.pos1 = '0 0 0';
1661 self.pos2 = self.movedir;
1663 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1664 // but spawn in the open position
1665 if (self.spawnflags & DOOR_START_OPEN)
1666 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1668 self.state = STATE_BOTTOM;
1672 self.takedamage = DAMAGE_YES;
1673 self.event_damage = door_damage;
1679 self.touch = door_touch;
1681 // LinkDoors can't be done until all of the doors have been spawned, so
1682 // the sizes can be detected properly.
1683 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1685 self.reset = door_rotating_reset;
1689 =============================================================================
1693 =============================================================================
1696 void() fd_secret_move1;
1697 void() fd_secret_move2;
1698 void() fd_secret_move3;
1699 void() fd_secret_move4;
1700 void() fd_secret_move5;
1701 void() fd_secret_move6;
1702 void() fd_secret_done;
1704 float SECRET_OPEN_ONCE = 1; // stays open
1705 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1706 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1707 float SECRET_NO_SHOOT = 8; // only opened by trigger
1708 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1711 void fd_secret_use()
1714 string message_save;
1716 self.health = 10000;
1717 self.bot_attack = TRUE;
1719 // exit if still moving around...
1720 if (self.origin != self.oldorigin)
1723 message_save = self.message;
1724 self.message = ""; // no more message
1725 SUB_UseTargets(); // fire all targets / killtargets
1726 self.message = message_save;
1728 self.velocity = '0 0 0';
1730 // Make a sound, wait a little...
1732 if (self.noise1 != "")
1733 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1734 self.nextthink = self.ltime + 0.1;
1736 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1737 makevectors(self.mangle);
1741 if (self.spawnflags & SECRET_1ST_DOWN)
1742 self.t_width = fabs(v_up * self.size);
1744 self.t_width = fabs(v_right * self.size);
1748 self.t_length = fabs(v_forward * self.size);
1750 if (self.spawnflags & SECRET_1ST_DOWN)
1751 self.dest1 = self.origin - v_up * self.t_width;
1753 self.dest1 = self.origin + v_right * (self.t_width * temp);
1755 self.dest2 = self.dest1 + v_forward * self.t_length;
1756 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
1757 if (self.noise2 != "")
1758 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1761 // Wait after first movement...
1762 void fd_secret_move1()
1764 self.nextthink = self.ltime + 1.0;
1765 self.think = fd_secret_move2;
1766 if (self.noise3 != "")
1767 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1770 // Start moving sideways w/sound...
1771 void fd_secret_move2()
1773 if (self.noise2 != "")
1774 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1775 SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
1778 // Wait here until time to go back...
1779 void fd_secret_move3()
1781 if (self.noise3 != "")
1782 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1783 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1785 self.nextthink = self.ltime + self.wait;
1786 self.think = fd_secret_move4;
1791 void fd_secret_move4()
1793 if (self.noise2 != "")
1794 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1795 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
1799 void fd_secret_move5()
1801 self.nextthink = self.ltime + 1.0;
1802 self.think = fd_secret_move6;
1803 if (self.noise3 != "")
1804 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1807 void fd_secret_move6()
1809 if (self.noise2 != "")
1810 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1811 SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
1814 void fd_secret_done()
1816 if (self.spawnflags&SECRET_YES_SHOOT)
1818 self.health = 10000;
1819 self.takedamage = DAMAGE_YES;
1820 //self.th_pain = fd_secret_use;
1822 if (self.noise3 != "")
1823 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1826 void secret_blocked()
1828 if (time < self.attack_finished_single)
1830 self.attack_finished_single = time + 0.5;
1831 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1843 if not(other.iscreature)
1845 if (self.attack_finished_single > time)
1848 self.attack_finished_single = time + 2;
1852 if (other.flags & FL_CLIENT)
1853 centerprint (other, self.message);
1854 play2(other, "misc/talk.wav");
1860 if (self.spawnflags&SECRET_YES_SHOOT)
1862 self.health = 10000;
1863 self.takedamage = DAMAGE_YES;
1865 setorigin(self, self.oldorigin);
1866 self.think = SUB_Null;
1869 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1870 Basic secret door. Slides back, then to the side. Angle determines direction.
1871 wait = # of seconds before coming back
1872 1st_left = 1st move is left of arrow
1873 1st_down = 1st move is down from arrow
1874 always_shoot = even if targeted, keep shootable
1875 t_width = override WIDTH to move back (or height if going down)
1876 t_length = override LENGTH to move sideways
1877 "dmg" damage to inflict when blocked (2 default)
1879 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1886 void spawnfunc_func_door_secret()
1888 /*if (!self.deathtype) // map makers can override this
1889 self.deathtype = " got in the way";*/
1895 self.mangle = self.angles;
1896 self.angles = '0 0 0';
1897 self.classname = "door";
1898 if not(InitMovingBrushTrigger())
1900 self.effects |= EF_LOWPRECISION;
1902 self.touch = secret_touch;
1903 self.blocked = secret_blocked;
1905 self.use = fd_secret_use;
1910 self.spawnflags |= SECRET_YES_SHOOT;
1912 if(self.spawnflags&SECRET_YES_SHOOT)
1914 self.health = 10000;
1915 self.takedamage = DAMAGE_YES;
1916 self.event_damage = fd_secret_use;
1918 self.oldorigin = self.origin;
1920 self.wait = 5; // 5 seconds before closing
1922 self.reset = secret_reset;
1926 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1927 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1928 netname: list of <frequencymultiplier> <phase> <x> <y> <z> quadruples, separated by spaces; note that phase 0 represents a sine wave, and phase 0.25 a cosine wave (by default, it uses 1 0 0 0 1, to match func_bobbing's defaults
1929 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1930 height: amplitude modifier (default 32)
1931 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1932 noise: path/name of looping .wav file to play.
1933 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1937 void func_fourier_controller_think()
1942 self.nextthink = time + 0.1;
1943 if not (self.owner.active == ACTIVE_ACTIVE)
1945 self.owner.velocity = '0 0 0';
1950 n = floor((tokenize_console(self.owner.netname)) / 5);
1951 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1953 v = self.owner.destvec;
1955 for(i = 0; i < n; ++i)
1957 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1958 v = v + ('1 0 0' * stof(argv(i*5+2)) + '0 1 0' * stof(argv(i*5+3)) + '0 0 1' * stof(argv(i*5+4))) * self.owner.height * v_forward_y;
1961 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1962 // * 10 so it will arrive in 0.1 sec
1963 self.owner.velocity = (v - self.owner.origin) * 10;
1966 void spawnfunc_func_fourier()
1969 if (self.noise != "")
1971 precache_sound(self.noise);
1972 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
1979 self.destvec = self.origin;
1980 self.cnt = 360 / self.speed;
1982 self.blocked = generic_plat_blocked;
1983 if(self.dmg & (!self.message))
1984 self.message = " was squished";
1985 if(self.dmg && (!self.message2))
1986 self.message2 = "was squished by";
1987 if(self.dmg && (!self.dmgtime))
1988 self.dmgtime = 0.25;
1989 self.dmgtime2 = time;
1991 if(self.netname == "")
1992 self.netname = "1 0 0 0 1";
1994 if not(InitMovingBrushTrigger())
1997 self.active = ACTIVE_ACTIVE;
1999 // wait for targets to spawn
2000 controller = spawn();
2001 controller.classname = "func_fourier_controller";
2002 controller.owner = self;
2003 controller.nextthink = time + 1;
2004 controller.think = func_fourier_controller_think;
2005 self.nextthink = self.ltime + 999999999;
2006 self.think = SUB_Null;
2008 // Savage: Reduce bandwith, critical on e.g. nexdm02
2009 self.effects |= EF_LOWPRECISION;
2011 // TODO make a reset function for this one
2014 // reusing some fields havocbots declared
2015 .entity wp00, wp01, wp02, wp03;
2017 .float targetfactor, target2factor, target3factor, target4factor;
2018 .vector targetnormal, target2normal, target3normal, target4normal;
2020 vector func_vectormamamam_origin(entity o, float t)
2032 p = e.origin + t * e.velocity;
2034 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
2036 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
2042 p = e.origin + t * e.velocity;
2044 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
2046 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
2052 p = e.origin + t * e.velocity;
2054 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
2056 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
2062 p = e.origin + t * e.velocity;
2064 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
2066 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
2072 void func_vectormamamam_controller_think()
2074 self.nextthink = time + 0.1;
2076 if not (self.owner.active == ACTIVE_ACTIVE)
2078 self.owner.velocity = '0 0 0';
2082 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
2083 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
2086 void func_vectormamamam_findtarget()
2088 if(self.target != "")
2089 self.wp00 = find(world, targetname, self.target);
2091 if(self.target2 != "")
2092 self.wp01 = find(world, targetname, self.target2);
2094 if(self.target3 != "")
2095 self.wp02 = find(world, targetname, self.target3);
2097 if(self.target4 != "")
2098 self.wp03 = find(world, targetname, self.target4);
2100 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
2101 objerror("No reference entity found, so there is nothing to move. Aborting.");
2103 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
2106 controller = spawn();
2107 controller.classname = "func_vectormamamam_controller";
2108 controller.owner = self;
2109 controller.nextthink = time + 1;
2110 controller.think = func_vectormamamam_controller_think;
2113 void spawnfunc_func_vectormamamam()
2115 if (self.noise != "")
2117 precache_sound(self.noise);
2118 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
2121 if(!self.targetfactor)
2122 self.targetfactor = 1;
2124 if(!self.target2factor)
2125 self.target2factor = 1;
2127 if(!self.target3factor)
2128 self.target3factor = 1;
2130 if(!self.target4factor)
2131 self.target4factor = 1;
2133 if(vlen(self.targetnormal))
2134 self.targetnormal = normalize(self.targetnormal);
2136 if(vlen(self.target2normal))
2137 self.target2normal = normalize(self.target2normal);
2139 if(vlen(self.target3normal))
2140 self.target3normal = normalize(self.target3normal);
2142 if(vlen(self.target4normal))
2143 self.target4normal = normalize(self.target4normal);
2145 self.blocked = generic_plat_blocked;
2146 if(self.dmg & (!self.message))
2147 self.message = " was squished";
2148 if(self.dmg && (!self.message2))
2149 self.message2 = "was squished by";
2150 if(self.dmg && (!self.dmgtime))
2151 self.dmgtime = 0.25;
2152 self.dmgtime2 = time;
2154 if(self.netname == "")
2155 self.netname = "1 0 0 0 1";
2157 if not(InitMovingBrushTrigger())
2160 // wait for targets to spawn
2161 self.nextthink = self.ltime + 999999999;
2162 self.think = SUB_Null;
2164 // Savage: Reduce bandwith, critical on e.g. nexdm02
2165 self.effects |= EF_LOWPRECISION;
2167 self.active = ACTIVE_ACTIVE;
2169 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
2172 void conveyor_think()
2176 // set myself as current conveyor where possible
2177 for(e = world; (e = findentity(e, conveyor, self)); )
2182 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2183 if(!e.conveyor.state)
2186 vector emin = e.absmin;
2187 vector emax = e.absmax;
2188 if(self.solid == SOLID_BSP)
2193 if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2194 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2198 for(e = world; (e = findentity(e, conveyor, self)); )
2200 if(e.flags & FL_CLIENT) // doing it via velocity has quite some advantages
2201 continue; // done in SV_PlayerPhysics
2203 setorigin(e, e.origin + self.movedir * sys_frametime);
2204 move_out_of_solid(e);
2205 UpdateCSQCProjectile(e);
2207 // stupid conveyor code
2208 tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2209 if(trace_fraction > 0)
2210 setorigin(e, trace_endpos);
2215 self.nextthink = time;
2220 self.state = !self.state;
2223 void conveyor_reset()
2225 self.state = (self.spawnflags & 1);
2228 void conveyor_init()
2232 self.movedir = self.movedir * self.speed;
2233 self.think = conveyor_think;
2234 self.nextthink = time;
2237 self.use = conveyor_use;
2238 self.reset = conveyor_reset;
2245 void spawnfunc_trigger_conveyor()
2252 void spawnfunc_func_conveyor()
2255 InitMovingBrushTrigger();
2256 self.movetype = MOVETYPE_NONE;