2 void generic_plat_blocked()
4 if(self.dmg && other.takedamage != DAMAGE_NO) {
5 if(self.dmgtime2 < time) {
6 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
7 self.dmgtime2 = time + self.dmgtime;
10 // Gib dead/dying stuff
11 if(other.deadflag != DEAD_NO)
12 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
18 float STATE_BOTTOM = 1;
22 .entity trigger_field;
24 void() plat_center_touch;
25 void() plat_outside_touch;
26 void() plat_trigger_use;
30 float PLAT_LOW_TRIGGER = 1;
32 void plat_spawn_inside_trigger()
38 trigger.touch = plat_center_touch;
39 trigger.movetype = MOVETYPE_NONE;
40 trigger.solid = SOLID_TRIGGER;
43 tmin = self.absmin + '25 25 0';
44 tmax = self.absmax - '25 25 -8';
45 tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8);
46 if (self.spawnflags & PLAT_LOW_TRIGGER)
49 if (self.size_x <= 50)
51 tmin_x = (self.mins_x + self.maxs_x) / 2;
54 if (self.size_y <= 50)
56 tmin_y = (self.mins_y + self.maxs_y) / 2;
64 setsize (trigger, tmin, tmax);
68 // otherwise, something is fishy...
70 objerror("plat_spawn_inside_trigger: platform has odd size or lip, can't spawn");
75 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
77 self.think = plat_go_down;
78 self.nextthink = self.ltime + 3;
81 void plat_hit_bottom()
83 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
89 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_NORM);
91 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, plat_hit_bottom);
96 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_NORM);
98 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, plat_hit_top);
101 void plat_center_touch()
103 if not(other.iscreature)
106 if (other.health <= 0)
112 else if (self.state == 1)
113 self.nextthink = self.ltime + 1; // delay going down
116 void plat_outside_touch()
118 if not(other.iscreature)
121 if (other.health <= 0)
129 void plat_trigger_use()
132 return; // already activated
139 if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
140 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
142 if((self.dmg) && (other.takedamage != DAMAGE_NO)) { // Shall we bite?
143 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
144 // Gib dead/dying stuff
145 if(other.deadflag != DEAD_NO)
146 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
151 else if (self.state == 3)
153 // when in other states, then the plat_crush event came delayed after
154 // plat state already had changed
155 // this isn't a bug per se!
163 objerror ("plat_use: not in up state");
167 .string sound1, sound2;
173 setorigin (self, self.pos1);
179 setorigin (self, self.pos2);
181 self.use = plat_trigger_use;
185 .float platmovetype_start_default, platmovetype_end_default;
186 float set_platmovetype(entity e, string s)
188 // sets platmovetype_start and platmovetype_end based on a string consisting of two values
191 n = tokenize_console(s);
193 e.platmovetype_start = stof(argv(0));
195 e.platmovetype_start = 0;
198 e.platmovetype_end = stof(argv(1));
200 e.platmovetype_end = e.platmovetype_start;
203 if(argv(2) == "force")
204 return TRUE; // no checking, return immediately
206 if(!cubic_speedfunc_is_sane(e.platmovetype_start, e.platmovetype_end))
208 objerror("Invalid platform move type; platform would go in reverse, which is not allowed.");
215 void spawnfunc_path_corner()
217 // setup values for overriding train movement
218 // if a second value does not exist, both start and end speeds are the single value specified
219 if(!set_platmovetype(self, self.platmovetype))
222 void spawnfunc_func_plat()
224 if (self.sounds == 0)
227 if(self.spawnflags & 4)
230 if(self.dmg && (!self.message))
231 self.message = "was squished";
232 if(self.dmg && (!self.message2))
233 self.message2 = "was squished by";
235 if (self.sounds == 1)
237 precache_sound ("plats/plat1.wav");
238 precache_sound ("plats/plat2.wav");
239 self.noise = "plats/plat1.wav";
240 self.noise1 = "plats/plat2.wav";
243 if (self.sounds == 2)
245 precache_sound ("plats/medplat1.wav");
246 precache_sound ("plats/medplat2.wav");
247 self.noise = "plats/medplat1.wav";
248 self.noise1 = "plats/medplat2.wav";
253 precache_sound (self.sound1);
254 self.noise = self.sound1;
258 precache_sound (self.sound2);
259 self.noise1 = self.sound2;
262 self.mangle = self.angles;
263 self.angles = '0 0 0';
265 self.classname = "plat";
266 if not(InitMovingBrushTrigger())
268 self.effects |= EF_LOWPRECISION;
269 setsize (self, self.mins , self.maxs);
271 self.blocked = plat_crush;
278 self.height = self.size_z - self.lip;
280 self.pos1 = self.origin;
281 self.pos2 = self.origin;
282 self.pos2_z = self.origin_z - self.height;
284 self.reset = plat_reset;
287 plat_spawn_inside_trigger (); // the "start moving" trigger
290 .float train_wait_turning;
301 // if using bezier curves and turning is enabled, the train will turn toward the next point while waiting
302 if(!self.train_wait_turning)
303 if(self.spawnflags & 1 && self.bezier_turn && self.wait >= 0)
307 targ = find(world, targetname, self.target);
308 org = normalize(targ.origin);
309 SUB_CalcAngleMove(org, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
310 self.train_wait_turning = TRUE;
315 stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
317 if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
319 self.train_wait_turning = FALSE;
324 self.think = train_next;
325 self.nextthink = self.ltime + self.wait;
334 targ = find(world, targetname, self.target);
335 self.target = targ.target;
336 if (self.spawnflags & 1)
338 cp = find(world, target, targ.targetname); // get the previous corner first
339 cp = find(world, targetname, cp.curve); // now get its second target (the control point)
340 if(cp.targetname == "")
341 cp_org = targ.origin - self.mins; // no control point found, assume a straight line to the destination
343 cp_org = cp.origin - self.mins;
346 objerror("train_next: no next target");
347 self.wait = targ.wait;
351 if(targ.platmovetype)
353 // this path_corner contains a movetype overrider, apply it
354 self.platmovetype_start = targ.platmovetype_start;
355 self.platmovetype_end = targ.platmovetype_end;
359 // this path_corner doesn't contain a movetype overrider, use the train's defaults
360 self.platmovetype_start = self.platmovetype_start_default;
361 self.platmovetype_end = self.platmovetype_end_default;
366 if (self.spawnflags & 1)
367 SUB_CalcMove_Bezier(cp_org, targ.origin - self.mins, TSPEED_LINEAR, targ.speed, train_wait);
369 SUB_CalcMove(targ.origin - self.mins, TSPEED_LINEAR, targ.speed, train_wait);
373 if (self.spawnflags & 1)
374 SUB_CalcMove_Bezier(cp_org, targ.origin - self.mins, TSPEED_LINEAR, self.speed, train_wait);
376 SUB_CalcMove(targ.origin - self.mins, TSPEED_LINEAR, self.speed, train_wait);
380 sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
383 void func_train_find()
386 targ = find(world, targetname, self.target);
387 self.target = targ.target;
389 objerror("func_train_find: no next target");
390 setorigin(self, targ.origin - self.mins);
391 self.nextthink = self.ltime + 1;
392 self.think = train_next;
395 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
396 Ridable platform, targets spawnfunc_path_corner path to follow.
397 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
398 target : targetname of first spawnfunc_path_corner (starts here)
400 void spawnfunc_func_train()
402 if (self.noise != "")
403 precache_sound(self.noise);
406 objerror("func_train without a target");
409 if (self.spawnflags & 2)
410 self.bezier_turn = TRUE;
412 if not(InitMovingBrushTrigger())
414 self.effects |= EF_LOWPRECISION;
416 // wait for targets to spawn
417 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
419 self.blocked = generic_plat_blocked;
420 if(self.dmg & (!self.message))
421 self.message = " was squished";
422 if(self.dmg && (!self.message2))
423 self.message2 = "was squished by";
424 if(self.dmg && (!self.dmgtime))
426 self.dmgtime2 = time;
428 if(!set_platmovetype(self, self.platmovetype))
430 self.platmovetype_start_default = self.platmovetype_start;
431 self.platmovetype_end_default = self.platmovetype_end;
433 // TODO make a reset function for this one
436 void func_rotating_setactive(float astate)
439 if (astate == ACTIVE_TOGGLE)
441 if(self.active == ACTIVE_ACTIVE)
442 self.active = ACTIVE_NOT;
444 self.active = ACTIVE_ACTIVE;
447 self.active = astate;
449 if(self.active == ACTIVE_NOT)
450 self.avelocity = '0 0 0';
452 self.avelocity = self.pos1;
455 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
456 Brush model that spins in place on one axis (default Z).
457 speed : speed to rotate (in degrees per second)
458 noise : path/name of looping .wav file to play.
459 dmg : Do this mutch dmg every .dmgtime intervall when blocked
463 void spawnfunc_func_rotating()
465 if (self.noise != "")
467 precache_sound(self.noise);
468 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
471 self.active = ACTIVE_ACTIVE;
472 self.setactive = func_rotating_setactive;
476 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
477 if (self.spawnflags & 4) // X (untested)
478 self.avelocity = '0 0 1' * self.speed;
479 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
480 else if (self.spawnflags & 8) // Y (untested)
481 self.avelocity = '1 0 0' * self.speed;
482 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
484 self.avelocity = '0 1 0' * self.speed;
486 self.pos1 = self.avelocity;
488 if(self.dmg & (!self.message))
489 self.message = " was squished";
490 if(self.dmg && (!self.message2))
491 self.message2 = "was squished by";
494 if(self.dmg && (!self.dmgtime))
497 self.dmgtime2 = time;
499 if not(InitMovingBrushTrigger())
501 // no EF_LOWPRECISION here, as rounding angles is bad
503 self.blocked = generic_plat_blocked;
505 // wait for targets to spawn
506 self.nextthink = self.ltime + 999999999;
507 self.think = SUB_Null;
509 // TODO make a reset function for this one
513 void func_bobbing_controller_think()
516 self.nextthink = time + 0.1;
518 if not (self.owner.active == ACTIVE_ACTIVE)
520 self.owner.velocity = '0 0 0';
524 // calculate sinewave using makevectors
525 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
526 v = self.owner.destvec + self.owner.movedir * v_forward_y;
527 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
528 // * 10 so it will arrive in 0.1 sec
529 self.owner.velocity = (v - self.owner.origin) * 10;
532 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
533 Brush model that moves back and forth on one axis (default Z).
534 speed : how long one cycle takes in seconds (default 4)
535 height : how far the cycle moves (default 32)
536 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
537 noise : path/name of looping .wav file to play.
538 dmg : Do this mutch dmg every .dmgtime intervall when blocked
541 void spawnfunc_func_bobbing()
544 if (self.noise != "")
546 precache_sound(self.noise);
547 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
553 // center of bobbing motion
554 self.destvec = self.origin;
555 // time scale to get degrees
556 self.cnt = 360 / self.speed;
558 self.active = ACTIVE_ACTIVE;
560 // damage when blocked
561 self.blocked = generic_plat_blocked;
562 if(self.dmg & (!self.message))
563 self.message = " was squished";
564 if(self.dmg && (!self.message2))
565 self.message2 = "was squished by";
566 if(self.dmg && (!self.dmgtime))
568 self.dmgtime2 = time;
571 if (self.spawnflags & 1) // X
572 self.movedir = '1 0 0' * self.height;
573 else if (self.spawnflags & 2) // Y
574 self.movedir = '0 1 0' * self.height;
576 self.movedir = '0 0 1' * self.height;
578 if not(InitMovingBrushTrigger())
581 // wait for targets to spawn
582 controller = spawn();
583 controller.classname = "func_bobbing_controller";
584 controller.owner = self;
585 controller.nextthink = time + 1;
586 controller.think = func_bobbing_controller_think;
587 self.nextthink = self.ltime + 999999999;
588 self.think = SUB_Null;
590 // Savage: Reduce bandwith, critical on e.g. nexdm02
591 self.effects |= EF_LOWPRECISION;
593 // TODO make a reset function for this one
597 void func_pendulum_controller_think()
600 self.nextthink = time + 0.1;
602 if not (self.owner.active == ACTIVE_ACTIVE)
604 self.owner.avelocity_x = 0;
608 // calculate sinewave using makevectors
609 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
610 v = self.owner.speed * v_forward_y + self.cnt;
611 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
613 // * 10 so it will arrive in 0.1 sec
614 self.owner.avelocity_z = (remainder(v - self.owner.angles_z, 360)) * 10;
618 void spawnfunc_func_pendulum()
621 if (self.noise != "")
623 precache_sound(self.noise);
624 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
627 self.active = ACTIVE_ACTIVE;
629 // keys: angle, speed, phase, noise, freq
633 // not initializing self.dmg to 2, to allow damageless pendulum
635 if(self.dmg & (!self.message))
636 self.message = " was squished";
637 if(self.dmg && (!self.message2))
638 self.message2 = "was squished by";
639 if(self.dmg && (!self.dmgtime))
641 self.dmgtime2 = time;
643 self.blocked = generic_plat_blocked;
645 self.avelocity_z = 0.0000001;
646 if not(InitMovingBrushTrigger())
651 // find pendulum length (same formula as Q3A)
652 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins_z))));
655 // copy initial angle
656 self.cnt = self.angles_z;
658 // wait for targets to spawn
659 controller = spawn();
660 controller.classname = "func_pendulum_controller";
661 controller.owner = self;
662 controller.nextthink = time + 1;
663 controller.think = func_pendulum_controller_think;
664 self.nextthink = self.ltime + 999999999;
665 self.think = SUB_Null;
667 //self.effects |= EF_LOWPRECISION;
669 // TODO make a reset function for this one
672 // button and multiple button
675 void() button_return;
679 self.state = STATE_TOP;
680 self.nextthink = self.ltime + self.wait;
681 self.think = button_return;
682 activator = self.enemy;
684 self.frame = 1; // use alternate textures
689 self.state = STATE_BOTTOM;
694 self.state = STATE_DOWN;
695 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
696 self.frame = 0; // use normal textures
698 self.takedamage = DAMAGE_YES; // can be shot again
702 void button_blocked()
704 // do nothing, just don't come all the way back out
710 self.health = self.max_health;
711 self.takedamage = DAMAGE_NO; // will be reset upon return
713 if (self.state == STATE_UP || self.state == STATE_TOP)
716 if (self.noise != "")
717 sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
719 self.state = STATE_UP;
720 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
725 self.health = self.max_health;
726 setorigin(self, self.pos1);
727 self.frame = 0; // use normal textures
728 self.state = STATE_BOTTOM;
730 self.takedamage = DAMAGE_YES; // can be shot again
735 // if (activator.classname != "player")
737 // dprint(activator.classname);
738 // dprint(" triggered a button\n");
741 if not (self.active == ACTIVE_ACTIVE)
744 self.enemy = activator;
750 // if (activator.classname != "player")
752 // dprint(activator.classname);
753 // dprint(" touched a button\n");
757 if not(other.iscreature)
759 if(other.velocity * self.movedir < 0)
763 self.enemy = other.owner;
767 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
769 if(self.spawnflags & DOOR_NOSPLASH)
770 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
772 self.health = self.health - damage;
773 if (self.health <= 0)
775 // if (activator.classname != "player")
777 // dprint(activator.classname);
778 // dprint(" killed a button\n");
780 self.enemy = damage_attacker;
786 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
787 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.
789 "angle" determines the opening direction
790 "target" all entities with a matching targetname will be used
791 "speed" override the default 40 speed
792 "wait" override the default 1 second wait (-1 = never return)
793 "lip" override the default 4 pixel lip remaining at end of move
794 "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
801 void spawnfunc_func_button()
805 if not(InitMovingBrushTrigger())
807 self.effects |= EF_LOWPRECISION;
809 self.blocked = button_blocked;
810 self.use = button_use;
812 // if (self.health == 0) // all buttons are now shootable
816 self.max_health = self.health;
817 self.event_damage = button_damage;
818 self.takedamage = DAMAGE_YES;
821 self.touch = button_touch;
831 precache_sound(self.noise);
833 self.active = ACTIVE_ACTIVE;
835 self.pos1 = self.origin;
836 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
837 self.flags |= FL_NOTARGET;
843 float DOOR_START_OPEN = 1;
844 float DOOR_DONT_LINK = 4;
845 float DOOR_TOGGLE = 32;
849 Doors are similar to buttons, but can spawn a fat trigger field around them
850 to open without a touch, and they link together to form simultanious
853 Door.owner is the master door. If there is only one door, it points to itself.
854 If multiple doors, all will point to a single one.
856 Door.enemy chains from the master door through all doors linked in the chain.
861 =============================================================================
865 =============================================================================
870 void() door_rotating_go_down;
871 void() door_rotating_go_up;
876 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
877 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
880 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
881 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
883 //Dont chamge direction for dead or dying stuff
884 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
887 if (self.state == STATE_DOWN)
888 if (self.classname == "door")
893 door_rotating_go_up ();
896 if (self.classname == "door")
901 door_rotating_go_down ();
905 //gib dying stuff just to make sure
906 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
907 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
911 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
912 // if a door has a negative wait, it would never come back if blocked,
913 // so let it just squash the object to death real fast
914 /* if (self.wait >= 0)
916 if (self.state == STATE_DOWN)
927 if (self.noise1 != "")
928 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
929 self.state = STATE_TOP;
930 if (self.spawnflags & DOOR_TOGGLE)
931 return; // don't come down automatically
932 if (self.classname == "door")
934 self.think = door_go_down;
937 self.think = door_rotating_go_down;
939 self.nextthink = self.ltime + self.wait;
942 void door_hit_bottom()
944 if (self.noise1 != "")
945 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
946 self.state = STATE_BOTTOM;
951 if (self.noise2 != "")
952 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
955 self.takedamage = DAMAGE_YES;
956 self.health = self.max_health;
959 self.state = STATE_DOWN;
960 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
965 if (self.state == STATE_UP)
966 return; // already going up
968 if (self.state == STATE_TOP)
969 { // reset top wait time
970 self.nextthink = self.ltime + self.wait;
974 if (self.noise2 != "")
975 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
976 self.state = STATE_UP;
977 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
980 oldmessage = self.message;
983 self.message = oldmessage;
989 =============================================================================
993 =============================================================================
996 float door_check_keys(void) {
1006 if not(door.itemkeys)
1009 // this door require a key
1010 // only a player can have a key
1011 if (other.classname != "player")
1014 if (item_keys_usekey(door, other)) {
1015 // some keys were used
1016 if (other.key_door_messagetime <= time) {
1017 play2(other, "misc/talk.wav");
1018 centerprint(other, strcat("You also need ", item_keys_keylist(door.itemkeys), "!"));
1019 other.key_door_messagetime = time + 2;
1022 // no keys were used
1023 if (other.key_door_messagetime <= time) {
1024 play2(other, "misc/talk.wav");
1025 centerprint(other, strcat("You need ", item_keys_keylist(door.itemkeys), "!"));
1026 other.key_door_messagetime = time + 2;
1030 if (door.itemkeys) {
1031 // door is now unlocked
1032 play2(other, "misc/talk.wav");
1033 centerprint(other, "Door unlocked!");
1045 if (self.owner != self)
1046 objerror ("door_fire: self.owner != self");
1050 if (self.spawnflags & DOOR_TOGGLE)
1052 if (self.state == STATE_UP || self.state == STATE_TOP)
1057 if (self.classname == "door")
1063 door_rotating_go_down ();
1066 } while ( (self != starte) && (self != world) );
1072 // trigger all paired doors
1076 if (self.classname == "door")
1081 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
1082 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
1084 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
1085 self.pos2 = '0 0 0' - self.pos2;
1087 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
1088 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
1089 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
1091 door_rotating_go_up ();
1095 } while ( (self != starte) && (self != world) );
1104 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
1116 void door_trigger_touch()
1118 if (other.health < 1)
1119 if not(other.iscreature && other.deadflag == DEAD_NO)
1122 if (time < self.attack_finished_single)
1125 // check if door is locked
1126 if (!door_check_keys())
1129 self.attack_finished_single = time + 1;
1138 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1141 if(self.spawnflags & DOOR_NOSPLASH)
1142 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
1144 self.health = self.health - damage;
1146 if (self.itemkeys) {
1147 // don't allow opening doors through damage if keys are required
1151 if (self.health <= 0)
1155 self.health = self.max_health;
1156 self.takedamage = DAMAGE_NO; // wil be reset upon return
1172 if(other.classname != "player")
1174 if (self.owner.attack_finished_single > time)
1177 self.owner.attack_finished_single = time + 2;
1179 if (!(self.owner.dmg) && (self.owner.message != ""))
1181 if (other.flags & FL_CLIENT)
1182 centerprint (other, self.owner.message);
1183 play2(other, "misc/talk.wav");
1188 void door_generic_plat_blocked()
1191 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1192 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1195 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1196 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1198 //Dont chamge direction for dead or dying stuff
1199 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1202 if (self.state == STATE_DOWN)
1203 door_rotating_go_up ();
1205 door_rotating_go_down ();
1208 //gib dying stuff just to make sure
1209 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1210 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1214 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1215 // if a door has a negative wait, it would never come back if blocked,
1216 // so let it just squash the object to death real fast
1217 /* if (self.wait >= 0)
1219 if (self.state == STATE_DOWN)
1220 door_rotating_go_up ();
1222 door_rotating_go_down ();
1228 void door_rotating_hit_top()
1230 if (self.noise1 != "")
1231 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1232 self.state = STATE_TOP;
1233 if (self.spawnflags & DOOR_TOGGLE)
1234 return; // don't come down automatically
1235 self.think = door_rotating_go_down;
1236 self.nextthink = self.ltime + self.wait;
1239 void door_rotating_hit_bottom()
1241 if (self.noise1 != "")
1242 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1243 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1245 self.pos2 = '0 0 0' - self.pos2;
1248 self.state = STATE_BOTTOM;
1251 void door_rotating_go_down()
1253 if (self.noise2 != "")
1254 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1255 if (self.max_health)
1257 self.takedamage = DAMAGE_YES;
1258 self.health = self.max_health;
1261 self.state = STATE_DOWN;
1262 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
1265 void door_rotating_go_up()
1267 if (self.state == STATE_UP)
1268 return; // already going up
1270 if (self.state == STATE_TOP)
1271 { // reset top wait time
1272 self.nextthink = self.ltime + self.wait;
1275 if (self.noise2 != "")
1276 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1277 self.state = STATE_UP;
1278 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
1281 oldmessage = self.message;
1284 self.message = oldmessage;
1291 =============================================================================
1295 =============================================================================
1299 entity spawn_field(vector fmins, vector fmaxs)
1305 trigger.classname = "doortriggerfield";
1306 trigger.movetype = MOVETYPE_NONE;
1307 trigger.solid = SOLID_TRIGGER;
1308 trigger.owner = self;
1309 trigger.touch = door_trigger_touch;
1313 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1318 float EntitiesTouching(entity e1, entity e2)
1320 if (e1.absmin_x > e2.absmax_x)
1322 if (e1.absmin_y > e2.absmax_y)
1324 if (e1.absmin_z > e2.absmax_z)
1326 if (e1.absmax_x < e2.absmin_x)
1328 if (e1.absmax_y < e2.absmin_y)
1330 if (e1.absmax_z < e2.absmin_z)
1346 vector cmins, cmaxs;
1349 return; // already linked by another door
1350 if (self.spawnflags & 4)
1352 self.owner = self.enemy = self;
1360 self.trigger_field = spawn_field(self.absmin, self.absmax);
1362 return; // don't want to link this door
1365 cmins = self.absmin;
1366 cmaxs = self.absmax;
1373 self.owner = starte; // master door
1376 starte.health = self.health;
1378 starte.targetname = self.targetname;
1379 if (self.message != "")
1380 starte.message = self.message;
1382 t = find(t, classname, self.classname);
1385 self.enemy = starte; // make the chain a loop
1387 // shootable, or triggered doors just needed the owner/enemy links,
1388 // they don't spawn a field
1399 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1404 if (EntitiesTouching(self,t))
1407 objerror ("cross connected doors");
1412 if (t.absmin_x < cmins_x)
1413 cmins_x = t.absmin_x;
1414 if (t.absmin_y < cmins_y)
1415 cmins_y = t.absmin_y;
1416 if (t.absmin_z < cmins_z)
1417 cmins_z = t.absmin_z;
1418 if (t.absmax_x > cmaxs_x)
1419 cmaxs_x = t.absmax_x;
1420 if (t.absmax_y > cmaxs_y)
1421 cmaxs_y = t.absmax_y;
1422 if (t.absmax_z > cmaxs_z)
1423 cmaxs_z = t.absmax_z;
1430 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
1431 if two doors touch, they are assumed to be connected and operate as a unit.
1433 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1435 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).
1437 GOLD_KEY causes the door to open only if the activator holds a gold key.
1439 SILVER_KEY causes the door to open only if the activator holds a silver key.
1441 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1442 "angle" determines the opening direction
1443 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1444 "health" if set, door must be shot open
1445 "speed" movement speed (100 default)
1446 "wait" wait before returning (3 default, -1 = never return)
1447 "lip" lip remaining at end of move (8 default)
1448 "dmg" damage to inflict when blocked (2 default)
1455 FIXME: only one sound set available at the time being
1459 void door_init_startopen()
1461 setorigin (self, self.pos2);
1462 self.pos2 = self.pos1;
1463 self.pos1 = self.origin;
1468 setorigin(self, self.pos1);
1469 self.velocity = '0 0 0';
1470 self.state = STATE_BOTTOM;
1471 self.think = SUB_Null;
1474 // spawnflags require key (for now only func_door)
1475 #define SPAWNFLAGS_GOLD_KEY 8
1476 #define SPAWNFLAGS_SILVER_KEY 16
1477 void spawnfunc_func_door()
1479 // Quake 1 keys compatibility
1480 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
1481 self.itemkeys |= ITEM_KEY_BIT(0);
1482 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
1483 self.itemkeys |= ITEM_KEY_BIT(1);
1485 //if (!self.deathtype) // map makers can override this
1486 // self.deathtype = " got in the way";
1489 self.max_health = self.health;
1490 if not(InitMovingBrushTrigger())
1492 self.effects |= EF_LOWPRECISION;
1493 self.classname = "door";
1495 self.blocked = door_blocked;
1496 self.use = door_use;
1498 // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
1499 // if(self.spawnflags & 8)
1500 // self.dmg = 10000;
1502 if(self.dmg && (!self.message))
1503 self.message = "was squished";
1504 if(self.dmg && (!self.message2))
1505 self.message2 = "was squished by";
1507 if (self.sounds > 0)
1509 precache_sound ("plats/medplat1.wav");
1510 precache_sound ("plats/medplat2.wav");
1511 self.noise2 = "plats/medplat1.wav";
1512 self.noise1 = "plats/medplat2.wav";
1522 self.pos1 = self.origin;
1523 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1525 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1526 // but spawn in the open position
1527 if (self.spawnflags & DOOR_START_OPEN)
1528 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1530 self.state = STATE_BOTTOM;
1534 self.takedamage = DAMAGE_YES;
1535 self.event_damage = door_damage;
1541 self.touch = door_touch;
1543 // LinkDoors can't be done until all of the doors have been spawned, so
1544 // the sizes can be detected properly.
1545 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1547 self.reset = door_reset;
1550 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1551 if two doors touch, they are assumed to be connected and operate as a unit.
1553 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1555 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1556 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1557 must have set trigger_reverse to 1.
1558 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1560 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).
1562 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1563 "angle" determines the destination angle for opening. negative values reverse the direction.
1564 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1565 "health" if set, door must be shot open
1566 "speed" movement speed (100 default)
1567 "wait" wait before returning (3 default, -1 = never return)
1568 "dmg" damage to inflict when blocked (2 default)
1575 FIXME: only one sound set available at the time being
1578 void door_rotating_reset()
1580 self.angles = self.pos1;
1581 self.avelocity = '0 0 0';
1582 self.state = STATE_BOTTOM;
1583 self.think = SUB_Null;
1586 void door_rotating_init_startopen()
1588 self.angles = self.movedir;
1589 self.pos2 = '0 0 0';
1590 self.pos1 = self.movedir;
1594 void spawnfunc_func_door_rotating()
1597 //if (!self.deathtype) // map makers can override this
1598 // self.deathtype = " got in the way";
1600 // I abuse "movedir" for denoting the axis for now
1601 if (self.spawnflags & 64) // X (untested)
1602 self.movedir = '0 0 1';
1603 else if (self.spawnflags & 128) // Y (untested)
1604 self.movedir = '1 0 0';
1606 self.movedir = '0 1 0';
1608 if (self.angles_y==0) self.angles_y = 90;
1610 self.movedir = self.movedir * self.angles_y;
1611 self.angles = '0 0 0';
1613 self.max_health = self.health;
1614 self.avelocity = self.movedir;
1615 if not(InitMovingBrushTrigger())
1617 self.velocity = '0 0 0';
1618 //self.effects |= EF_LOWPRECISION;
1619 self.classname = "door_rotating";
1621 self.blocked = door_blocked;
1622 self.use = door_use;
1624 if(self.spawnflags & 8)
1627 if(self.dmg && (!self.message))
1628 self.message = "was squished";
1629 if(self.dmg && (!self.message2))
1630 self.message2 = "was squished by";
1632 if (self.sounds > 0)
1634 precache_sound ("plats/medplat1.wav");
1635 precache_sound ("plats/medplat2.wav");
1636 self.noise2 = "plats/medplat1.wav";
1637 self.noise1 = "plats/medplat2.wav";
1644 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1646 self.pos1 = '0 0 0';
1647 self.pos2 = self.movedir;
1649 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1650 // but spawn in the open position
1651 if (self.spawnflags & DOOR_START_OPEN)
1652 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1654 self.state = STATE_BOTTOM;
1658 self.takedamage = DAMAGE_YES;
1659 self.event_damage = door_damage;
1665 self.touch = door_touch;
1667 // LinkDoors can't be done until all of the doors have been spawned, so
1668 // the sizes can be detected properly.
1669 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1671 self.reset = door_rotating_reset;
1675 =============================================================================
1679 =============================================================================
1682 void() fd_secret_move1;
1683 void() fd_secret_move2;
1684 void() fd_secret_move3;
1685 void() fd_secret_move4;
1686 void() fd_secret_move5;
1687 void() fd_secret_move6;
1688 void() fd_secret_done;
1690 float SECRET_OPEN_ONCE = 1; // stays open
1691 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1692 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1693 float SECRET_NO_SHOOT = 8; // only opened by trigger
1694 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1697 void fd_secret_use()
1700 string message_save;
1702 self.health = 10000;
1703 self.bot_attack = TRUE;
1705 // exit if still moving around...
1706 if (self.origin != self.oldorigin)
1709 message_save = self.message;
1710 self.message = ""; // no more message
1711 SUB_UseTargets(); // fire all targets / killtargets
1712 self.message = message_save;
1714 self.velocity = '0 0 0';
1716 // Make a sound, wait a little...
1718 if (self.noise1 != "")
1719 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1720 self.nextthink = self.ltime + 0.1;
1722 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1723 makevectors(self.mangle);
1727 if (self.spawnflags & SECRET_1ST_DOWN)
1728 self.t_width = fabs(v_up * self.size);
1730 self.t_width = fabs(v_right * self.size);
1734 self.t_length = fabs(v_forward * self.size);
1736 if (self.spawnflags & SECRET_1ST_DOWN)
1737 self.dest1 = self.origin - v_up * self.t_width;
1739 self.dest1 = self.origin + v_right * (self.t_width * temp);
1741 self.dest2 = self.dest1 + v_forward * self.t_length;
1742 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
1743 if (self.noise2 != "")
1744 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1747 // Wait after first movement...
1748 void fd_secret_move1()
1750 self.nextthink = self.ltime + 1.0;
1751 self.think = fd_secret_move2;
1752 if (self.noise3 != "")
1753 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1756 // Start moving sideways w/sound...
1757 void fd_secret_move2()
1759 if (self.noise2 != "")
1760 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1761 SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
1764 // Wait here until time to go back...
1765 void fd_secret_move3()
1767 if (self.noise3 != "")
1768 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1769 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1771 self.nextthink = self.ltime + self.wait;
1772 self.think = fd_secret_move4;
1777 void fd_secret_move4()
1779 if (self.noise2 != "")
1780 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1781 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
1785 void fd_secret_move5()
1787 self.nextthink = self.ltime + 1.0;
1788 self.think = fd_secret_move6;
1789 if (self.noise3 != "")
1790 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1793 void fd_secret_move6()
1795 if (self.noise2 != "")
1796 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1797 SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
1800 void fd_secret_done()
1802 if (self.spawnflags&SECRET_YES_SHOOT)
1804 self.health = 10000;
1805 self.takedamage = DAMAGE_YES;
1806 //self.th_pain = fd_secret_use;
1808 if (self.noise3 != "")
1809 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1812 void secret_blocked()
1814 if (time < self.attack_finished_single)
1816 self.attack_finished_single = time + 0.5;
1817 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1829 if not(other.iscreature)
1831 if (self.attack_finished_single > time)
1834 self.attack_finished_single = time + 2;
1838 if (other.flags & FL_CLIENT)
1839 centerprint (other, self.message);
1840 play2(other, "misc/talk.wav");
1846 if (self.spawnflags&SECRET_YES_SHOOT)
1848 self.health = 10000;
1849 self.takedamage = DAMAGE_YES;
1851 setorigin(self, self.oldorigin);
1852 self.think = SUB_Null;
1855 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1856 Basic secret door. Slides back, then to the side. Angle determines direction.
1857 wait = # of seconds before coming back
1858 1st_left = 1st move is left of arrow
1859 1st_down = 1st move is down from arrow
1860 always_shoot = even if targeted, keep shootable
1861 t_width = override WIDTH to move back (or height if going down)
1862 t_length = override LENGTH to move sideways
1863 "dmg" damage to inflict when blocked (2 default)
1865 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1872 void spawnfunc_func_door_secret()
1874 /*if (!self.deathtype) // map makers can override this
1875 self.deathtype = " got in the way";*/
1881 self.mangle = self.angles;
1882 self.angles = '0 0 0';
1883 self.classname = "door";
1884 if not(InitMovingBrushTrigger())
1886 self.effects |= EF_LOWPRECISION;
1888 self.touch = secret_touch;
1889 self.blocked = secret_blocked;
1891 self.use = fd_secret_use;
1896 self.spawnflags |= SECRET_YES_SHOOT;
1898 if(self.spawnflags&SECRET_YES_SHOOT)
1900 self.health = 10000;
1901 self.takedamage = DAMAGE_YES;
1902 self.event_damage = fd_secret_use;
1904 self.oldorigin = self.origin;
1906 self.wait = 5; // 5 seconds before closing
1908 self.reset = secret_reset;
1912 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1913 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1914 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
1915 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1916 height: amplitude modifier (default 32)
1917 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1918 noise: path/name of looping .wav file to play.
1919 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1923 void func_fourier_controller_think()
1928 self.nextthink = time + 0.1;
1929 if not (self.owner.active == ACTIVE_ACTIVE)
1931 self.owner.velocity = '0 0 0';
1936 n = floor((tokenize_console(self.owner.netname)) / 5);
1937 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1939 v = self.owner.destvec;
1941 for(i = 0; i < n; ++i)
1943 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1944 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;
1947 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1948 // * 10 so it will arrive in 0.1 sec
1949 self.owner.velocity = (v - self.owner.origin) * 10;
1952 void spawnfunc_func_fourier()
1955 if (self.noise != "")
1957 precache_sound(self.noise);
1958 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
1965 self.destvec = self.origin;
1966 self.cnt = 360 / self.speed;
1968 self.blocked = generic_plat_blocked;
1969 if(self.dmg & (!self.message))
1970 self.message = " was squished";
1971 if(self.dmg && (!self.message2))
1972 self.message2 = "was squished by";
1973 if(self.dmg && (!self.dmgtime))
1974 self.dmgtime = 0.25;
1975 self.dmgtime2 = time;
1977 if(self.netname == "")
1978 self.netname = "1 0 0 0 1";
1980 if not(InitMovingBrushTrigger())
1983 self.active = ACTIVE_ACTIVE;
1985 // wait for targets to spawn
1986 controller = spawn();
1987 controller.classname = "func_fourier_controller";
1988 controller.owner = self;
1989 controller.nextthink = time + 1;
1990 controller.think = func_fourier_controller_think;
1991 self.nextthink = self.ltime + 999999999;
1992 self.think = SUB_Null;
1994 // Savage: Reduce bandwith, critical on e.g. nexdm02
1995 self.effects |= EF_LOWPRECISION;
1997 // TODO make a reset function for this one
2000 // reusing some fields havocbots declared
2001 .entity wp00, wp01, wp02, wp03;
2003 .float targetfactor, target2factor, target3factor, target4factor;
2004 .vector targetnormal, target2normal, target3normal, target4normal;
2006 vector func_vectormamamam_origin(entity o, float t)
2018 p = e.origin + t * e.velocity;
2020 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
2022 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
2028 p = e.origin + t * e.velocity;
2030 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
2032 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
2038 p = e.origin + t * e.velocity;
2040 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
2042 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
2048 p = e.origin + t * e.velocity;
2050 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
2052 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
2058 void func_vectormamamam_controller_think()
2060 self.nextthink = time + 0.1;
2062 if not (self.owner.active == ACTIVE_ACTIVE)
2064 self.owner.velocity = '0 0 0';
2068 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
2069 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
2072 void func_vectormamamam_findtarget()
2074 if(self.target != "")
2075 self.wp00 = find(world, targetname, self.target);
2077 if(self.target2 != "")
2078 self.wp01 = find(world, targetname, self.target2);
2080 if(self.target3 != "")
2081 self.wp02 = find(world, targetname, self.target3);
2083 if(self.target4 != "")
2084 self.wp03 = find(world, targetname, self.target4);
2086 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
2087 objerror("No reference entity found, so there is nothing to move. Aborting.");
2089 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
2092 controller = spawn();
2093 controller.classname = "func_vectormamamam_controller";
2094 controller.owner = self;
2095 controller.nextthink = time + 1;
2096 controller.think = func_vectormamamam_controller_think;
2099 void spawnfunc_func_vectormamamam()
2101 if (self.noise != "")
2103 precache_sound(self.noise);
2104 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
2107 if(!self.targetfactor)
2108 self.targetfactor = 1;
2110 if(!self.target2factor)
2111 self.target2factor = 1;
2113 if(!self.target3factor)
2114 self.target3factor = 1;
2116 if(!self.target4factor)
2117 self.target4factor = 1;
2119 if(vlen(self.targetnormal))
2120 self.targetnormal = normalize(self.targetnormal);
2122 if(vlen(self.target2normal))
2123 self.target2normal = normalize(self.target2normal);
2125 if(vlen(self.target3normal))
2126 self.target3normal = normalize(self.target3normal);
2128 if(vlen(self.target4normal))
2129 self.target4normal = normalize(self.target4normal);
2131 self.blocked = generic_plat_blocked;
2132 if(self.dmg & (!self.message))
2133 self.message = " was squished";
2134 if(self.dmg && (!self.message2))
2135 self.message2 = "was squished by";
2136 if(self.dmg && (!self.dmgtime))
2137 self.dmgtime = 0.25;
2138 self.dmgtime2 = time;
2140 if(self.netname == "")
2141 self.netname = "1 0 0 0 1";
2143 if not(InitMovingBrushTrigger())
2146 // wait for targets to spawn
2147 self.nextthink = self.ltime + 999999999;
2148 self.think = SUB_Null;
2150 // Savage: Reduce bandwith, critical on e.g. nexdm02
2151 self.effects |= EF_LOWPRECISION;
2153 self.active = ACTIVE_ACTIVE;
2155 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
2158 void conveyor_think()
2162 // set myself as current conveyor where possible
2163 for(e = world; (e = findentity(e, conveyor, self)); )
2168 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2169 if(!e.conveyor.state)
2172 vector emin = e.absmin;
2173 vector emax = e.absmax;
2174 if(self.solid == SOLID_BSP)
2179 if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2180 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2184 for(e = world; (e = findentity(e, conveyor, self)); )
2186 if(e.flags & FL_CLIENT) // doing it via velocity has quite some advantages
2187 continue; // done in SV_PlayerPhysics
2189 setorigin(e, e.origin + self.movedir * sys_frametime);
2190 move_out_of_solid(e);
2191 UpdateCSQCProjectile(e);
2193 // stupid conveyor code
2194 tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2195 if(trace_fraction > 0)
2196 setorigin(e, trace_endpos);
2201 self.nextthink = time;
2206 self.state = !self.state;
2209 void conveyor_reset()
2211 self.state = (self.spawnflags & 1);
2214 void conveyor_init()
2218 self.movedir = self.movedir * self.speed;
2219 self.think = conveyor_think;
2220 self.nextthink = time;
2223 self.use = conveyor_use;
2224 self.reset = conveyor_reset;
2231 void spawnfunc_trigger_conveyor()
2238 void spawnfunc_func_conveyor()
2241 InitMovingBrushTrigger();
2242 self.movetype = MOVETYPE_NONE;