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 // FIXME don't do this, instead save the cp for next time to an entity field... we don't do find() for target, kthx?
339 cp = find(world, target, targ.targetname); // get the previous corner first
342 cp = find(world, targetname, cp.curve); // now get its second target (the control point)
343 cp_org = cp.origin - self.mins; // no control point found, assume a straight line to the destination
349 objerror("train_next: no next target");
350 self.wait = targ.wait;
354 if(targ.platmovetype)
356 // this path_corner contains a movetype overrider, apply it
357 self.platmovetype_start = targ.platmovetype_start;
358 self.platmovetype_end = targ.platmovetype_end;
362 // this path_corner doesn't contain a movetype overrider, use the train's defaults
363 self.platmovetype_start = self.platmovetype_start_default;
364 self.platmovetype_end = self.platmovetype_end_default;
370 SUB_CalcMove_Bezier(cp_org, targ.origin - self.mins, TSPEED_LINEAR, targ.speed, train_wait);
372 SUB_CalcMove(targ.origin - self.mins, TSPEED_LINEAR, targ.speed, train_wait);
377 SUB_CalcMove_Bezier(cp_org, targ.origin - self.mins, TSPEED_LINEAR, self.speed, train_wait);
379 SUB_CalcMove(targ.origin - self.mins, TSPEED_LINEAR, self.speed, train_wait);
383 sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
386 void func_train_find()
389 targ = find(world, targetname, self.target);
390 self.target = targ.target;
392 objerror("func_train_find: no next target");
393 setorigin(self, targ.origin - self.mins);
394 self.nextthink = self.ltime + 1;
395 self.think = train_next;
398 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
399 Ridable platform, targets spawnfunc_path_corner path to follow.
400 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
401 target : targetname of first spawnfunc_path_corner (starts here)
403 void spawnfunc_func_train()
405 if (self.noise != "")
406 precache_sound(self.noise);
409 objerror("func_train without a target");
412 if (self.spawnflags & 2)
413 self.bezier_turn = TRUE;
415 if not(InitMovingBrushTrigger())
417 self.effects |= EF_LOWPRECISION;
419 // wait for targets to spawn
420 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
422 self.blocked = generic_plat_blocked;
423 if(self.dmg & (!self.message))
424 self.message = " was squished";
425 if(self.dmg && (!self.message2))
426 self.message2 = "was squished by";
427 if(self.dmg && (!self.dmgtime))
429 self.dmgtime2 = time;
431 if(!set_platmovetype(self, self.platmovetype))
433 self.platmovetype_start_default = self.platmovetype_start;
434 self.platmovetype_end_default = self.platmovetype_end;
436 // TODO make a reset function for this one
439 void func_rotating_setactive(float astate)
442 if (astate == ACTIVE_TOGGLE)
444 if(self.active == ACTIVE_ACTIVE)
445 self.active = ACTIVE_NOT;
447 self.active = ACTIVE_ACTIVE;
450 self.active = astate;
452 if(self.active == ACTIVE_NOT)
453 self.avelocity = '0 0 0';
455 self.avelocity = self.pos1;
458 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
459 Brush model that spins in place on one axis (default Z).
460 speed : speed to rotate (in degrees per second)
461 noise : path/name of looping .wav file to play.
462 dmg : Do this mutch dmg every .dmgtime intervall when blocked
466 void spawnfunc_func_rotating()
468 if (self.noise != "")
470 precache_sound(self.noise);
471 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
474 self.active = ACTIVE_ACTIVE;
475 self.setactive = func_rotating_setactive;
479 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
480 if (self.spawnflags & 4) // X (untested)
481 self.avelocity = '0 0 1' * self.speed;
482 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
483 else if (self.spawnflags & 8) // Y (untested)
484 self.avelocity = '1 0 0' * self.speed;
485 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
487 self.avelocity = '0 1 0' * self.speed;
489 self.pos1 = self.avelocity;
491 if(self.dmg & (!self.message))
492 self.message = " was squished";
493 if(self.dmg && (!self.message2))
494 self.message2 = "was squished by";
497 if(self.dmg && (!self.dmgtime))
500 self.dmgtime2 = time;
502 if not(InitMovingBrushTrigger())
504 // no EF_LOWPRECISION here, as rounding angles is bad
506 self.blocked = generic_plat_blocked;
508 // wait for targets to spawn
509 self.nextthink = self.ltime + 999999999;
510 self.think = SUB_Null;
512 // TODO make a reset function for this one
516 void func_bobbing_controller_think()
519 self.nextthink = time + 0.1;
521 if not (self.owner.active == ACTIVE_ACTIVE)
523 self.owner.velocity = '0 0 0';
527 // calculate sinewave using makevectors
528 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
529 v = self.owner.destvec + self.owner.movedir * v_forward_y;
530 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
531 // * 10 so it will arrive in 0.1 sec
532 self.owner.velocity = (v - self.owner.origin) * 10;
535 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
536 Brush model that moves back and forth on one axis (default Z).
537 speed : how long one cycle takes in seconds (default 4)
538 height : how far the cycle moves (default 32)
539 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
540 noise : path/name of looping .wav file to play.
541 dmg : Do this mutch dmg every .dmgtime intervall when blocked
544 void spawnfunc_func_bobbing()
547 if (self.noise != "")
549 precache_sound(self.noise);
550 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
556 // center of bobbing motion
557 self.destvec = self.origin;
558 // time scale to get degrees
559 self.cnt = 360 / self.speed;
561 self.active = ACTIVE_ACTIVE;
563 // damage when blocked
564 self.blocked = generic_plat_blocked;
565 if(self.dmg & (!self.message))
566 self.message = " was squished";
567 if(self.dmg && (!self.message2))
568 self.message2 = "was squished by";
569 if(self.dmg && (!self.dmgtime))
571 self.dmgtime2 = time;
574 if (self.spawnflags & 1) // X
575 self.movedir = '1 0 0' * self.height;
576 else if (self.spawnflags & 2) // Y
577 self.movedir = '0 1 0' * self.height;
579 self.movedir = '0 0 1' * self.height;
581 if not(InitMovingBrushTrigger())
584 // wait for targets to spawn
585 controller = spawn();
586 controller.classname = "func_bobbing_controller";
587 controller.owner = self;
588 controller.nextthink = time + 1;
589 controller.think = func_bobbing_controller_think;
590 self.nextthink = self.ltime + 999999999;
591 self.think = SUB_Null;
593 // Savage: Reduce bandwith, critical on e.g. nexdm02
594 self.effects |= EF_LOWPRECISION;
596 // TODO make a reset function for this one
600 void func_pendulum_controller_think()
603 self.nextthink = time + 0.1;
605 if not (self.owner.active == ACTIVE_ACTIVE)
607 self.owner.avelocity_x = 0;
611 // calculate sinewave using makevectors
612 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
613 v = self.owner.speed * v_forward_y + self.cnt;
614 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
616 // * 10 so it will arrive in 0.1 sec
617 self.owner.avelocity_z = (remainder(v - self.owner.angles_z, 360)) * 10;
621 void spawnfunc_func_pendulum()
624 if (self.noise != "")
626 precache_sound(self.noise);
627 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
630 self.active = ACTIVE_ACTIVE;
632 // keys: angle, speed, phase, noise, freq
636 // not initializing self.dmg to 2, to allow damageless pendulum
638 if(self.dmg & (!self.message))
639 self.message = " was squished";
640 if(self.dmg && (!self.message2))
641 self.message2 = "was squished by";
642 if(self.dmg && (!self.dmgtime))
644 self.dmgtime2 = time;
646 self.blocked = generic_plat_blocked;
648 self.avelocity_z = 0.0000001;
649 if not(InitMovingBrushTrigger())
654 // find pendulum length (same formula as Q3A)
655 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins_z))));
658 // copy initial angle
659 self.cnt = self.angles_z;
661 // wait for targets to spawn
662 controller = spawn();
663 controller.classname = "func_pendulum_controller";
664 controller.owner = self;
665 controller.nextthink = time + 1;
666 controller.think = func_pendulum_controller_think;
667 self.nextthink = self.ltime + 999999999;
668 self.think = SUB_Null;
670 //self.effects |= EF_LOWPRECISION;
672 // TODO make a reset function for this one
675 // button and multiple button
678 void() button_return;
682 self.state = STATE_TOP;
683 self.nextthink = self.ltime + self.wait;
684 self.think = button_return;
685 activator = self.enemy;
687 self.frame = 1; // use alternate textures
692 self.state = STATE_BOTTOM;
697 self.state = STATE_DOWN;
698 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
699 self.frame = 0; // use normal textures
701 self.takedamage = DAMAGE_YES; // can be shot again
705 void button_blocked()
707 // do nothing, just don't come all the way back out
713 self.health = self.max_health;
714 self.takedamage = DAMAGE_NO; // will be reset upon return
716 if (self.state == STATE_UP || self.state == STATE_TOP)
719 if (self.noise != "")
720 sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
722 self.state = STATE_UP;
723 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
728 self.health = self.max_health;
729 setorigin(self, self.pos1);
730 self.frame = 0; // use normal textures
731 self.state = STATE_BOTTOM;
733 self.takedamage = DAMAGE_YES; // can be shot again
738 // if (activator.classname != "player")
740 // dprint(activator.classname);
741 // dprint(" triggered a button\n");
744 if not (self.active == ACTIVE_ACTIVE)
747 self.enemy = activator;
753 // if (activator.classname != "player")
755 // dprint(activator.classname);
756 // dprint(" touched a button\n");
760 if not(other.iscreature)
762 if(other.velocity * self.movedir < 0)
766 self.enemy = other.owner;
770 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
772 if(self.spawnflags & DOOR_NOSPLASH)
773 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
775 self.health = self.health - damage;
776 if (self.health <= 0)
778 // if (activator.classname != "player")
780 // dprint(activator.classname);
781 // dprint(" killed a button\n");
783 self.enemy = damage_attacker;
789 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
790 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.
792 "angle" determines the opening direction
793 "target" all entities with a matching targetname will be used
794 "speed" override the default 40 speed
795 "wait" override the default 1 second wait (-1 = never return)
796 "lip" override the default 4 pixel lip remaining at end of move
797 "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
804 void spawnfunc_func_button()
808 if not(InitMovingBrushTrigger())
810 self.effects |= EF_LOWPRECISION;
812 self.blocked = button_blocked;
813 self.use = button_use;
815 // if (self.health == 0) // all buttons are now shootable
819 self.max_health = self.health;
820 self.event_damage = button_damage;
821 self.takedamage = DAMAGE_YES;
824 self.touch = button_touch;
834 precache_sound(self.noise);
836 self.active = ACTIVE_ACTIVE;
838 self.pos1 = self.origin;
839 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
840 self.flags |= FL_NOTARGET;
846 float DOOR_START_OPEN = 1;
847 float DOOR_DONT_LINK = 4;
848 float DOOR_TOGGLE = 32;
852 Doors are similar to buttons, but can spawn a fat trigger field around them
853 to open without a touch, and they link together to form simultanious
856 Door.owner is the master door. If there is only one door, it points to itself.
857 If multiple doors, all will point to a single one.
859 Door.enemy chains from the master door through all doors linked in the chain.
864 =============================================================================
868 =============================================================================
873 void() door_rotating_go_down;
874 void() door_rotating_go_up;
879 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
880 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
883 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
884 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
886 //Dont chamge direction for dead or dying stuff
887 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
890 if (self.state == STATE_DOWN)
891 if (self.classname == "door")
896 door_rotating_go_up ();
899 if (self.classname == "door")
904 door_rotating_go_down ();
908 //gib dying stuff just to make sure
909 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
910 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
914 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
915 // if a door has a negative wait, it would never come back if blocked,
916 // so let it just squash the object to death real fast
917 /* if (self.wait >= 0)
919 if (self.state == STATE_DOWN)
930 if (self.noise1 != "")
931 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
932 self.state = STATE_TOP;
933 if (self.spawnflags & DOOR_TOGGLE)
934 return; // don't come down automatically
935 if (self.classname == "door")
937 self.think = door_go_down;
940 self.think = door_rotating_go_down;
942 self.nextthink = self.ltime + self.wait;
945 void door_hit_bottom()
947 if (self.noise1 != "")
948 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
949 self.state = STATE_BOTTOM;
954 if (self.noise2 != "")
955 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
958 self.takedamage = DAMAGE_YES;
959 self.health = self.max_health;
962 self.state = STATE_DOWN;
963 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
968 if (self.state == STATE_UP)
969 return; // already going up
971 if (self.state == STATE_TOP)
972 { // reset top wait time
973 self.nextthink = self.ltime + self.wait;
977 if (self.noise2 != "")
978 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
979 self.state = STATE_UP;
980 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
983 oldmessage = self.message;
986 self.message = oldmessage;
992 =============================================================================
996 =============================================================================
999 float door_check_keys(void) {
1009 if not(door.itemkeys)
1012 // this door require a key
1013 // only a player can have a key
1014 if (other.classname != "player")
1017 if (item_keys_usekey(door, other)) {
1018 // some keys were used
1019 if (other.key_door_messagetime <= time) {
1020 play2(other, "misc/talk.wav");
1021 centerprint(other, strcat("You also need ", item_keys_keylist(door.itemkeys), "!"));
1022 other.key_door_messagetime = time + 2;
1025 // no keys were used
1026 if (other.key_door_messagetime <= time) {
1027 play2(other, "misc/talk.wav");
1028 centerprint(other, strcat("You need ", item_keys_keylist(door.itemkeys), "!"));
1029 other.key_door_messagetime = time + 2;
1033 if (door.itemkeys) {
1034 // door is now unlocked
1035 play2(other, "misc/talk.wav");
1036 centerprint(other, "Door unlocked!");
1048 if (self.owner != self)
1049 objerror ("door_fire: self.owner != self");
1053 if (self.spawnflags & DOOR_TOGGLE)
1055 if (self.state == STATE_UP || self.state == STATE_TOP)
1060 if (self.classname == "door")
1066 door_rotating_go_down ();
1069 } while ( (self != starte) && (self != world) );
1075 // trigger all paired doors
1079 if (self.classname == "door")
1084 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
1085 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
1087 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
1088 self.pos2 = '0 0 0' - self.pos2;
1090 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
1091 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
1092 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
1094 door_rotating_go_up ();
1098 } while ( (self != starte) && (self != world) );
1107 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
1119 void door_trigger_touch()
1121 if (other.health < 1)
1122 if not(other.iscreature && other.deadflag == DEAD_NO)
1125 if (time < self.attack_finished_single)
1128 // check if door is locked
1129 if (!door_check_keys())
1132 self.attack_finished_single = time + 1;
1141 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1144 if(self.spawnflags & DOOR_NOSPLASH)
1145 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
1147 self.health = self.health - damage;
1149 if (self.itemkeys) {
1150 // don't allow opening doors through damage if keys are required
1154 if (self.health <= 0)
1158 self.health = self.max_health;
1159 self.takedamage = DAMAGE_NO; // wil be reset upon return
1175 if(other.classname != "player")
1177 if (self.owner.attack_finished_single > time)
1180 self.owner.attack_finished_single = time + 2;
1182 if (!(self.owner.dmg) && (self.owner.message != ""))
1184 if (other.flags & FL_CLIENT)
1185 centerprint (other, self.owner.message);
1186 play2(other, "misc/talk.wav");
1191 void door_generic_plat_blocked()
1194 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1195 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1198 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1199 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1201 //Dont chamge direction for dead or dying stuff
1202 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1205 if (self.state == STATE_DOWN)
1206 door_rotating_go_up ();
1208 door_rotating_go_down ();
1211 //gib dying stuff just to make sure
1212 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1213 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1217 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1218 // if a door has a negative wait, it would never come back if blocked,
1219 // so let it just squash the object to death real fast
1220 /* if (self.wait >= 0)
1222 if (self.state == STATE_DOWN)
1223 door_rotating_go_up ();
1225 door_rotating_go_down ();
1231 void door_rotating_hit_top()
1233 if (self.noise1 != "")
1234 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1235 self.state = STATE_TOP;
1236 if (self.spawnflags & DOOR_TOGGLE)
1237 return; // don't come down automatically
1238 self.think = door_rotating_go_down;
1239 self.nextthink = self.ltime + self.wait;
1242 void door_rotating_hit_bottom()
1244 if (self.noise1 != "")
1245 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1246 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1248 self.pos2 = '0 0 0' - self.pos2;
1251 self.state = STATE_BOTTOM;
1254 void door_rotating_go_down()
1256 if (self.noise2 != "")
1257 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1258 if (self.max_health)
1260 self.takedamage = DAMAGE_YES;
1261 self.health = self.max_health;
1264 self.state = STATE_DOWN;
1265 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
1268 void door_rotating_go_up()
1270 if (self.state == STATE_UP)
1271 return; // already going up
1273 if (self.state == STATE_TOP)
1274 { // reset top wait time
1275 self.nextthink = self.ltime + self.wait;
1278 if (self.noise2 != "")
1279 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1280 self.state = STATE_UP;
1281 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
1284 oldmessage = self.message;
1287 self.message = oldmessage;
1294 =============================================================================
1298 =============================================================================
1302 entity spawn_field(vector fmins, vector fmaxs)
1308 trigger.classname = "doortriggerfield";
1309 trigger.movetype = MOVETYPE_NONE;
1310 trigger.solid = SOLID_TRIGGER;
1311 trigger.owner = self;
1312 trigger.touch = door_trigger_touch;
1316 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1321 float EntitiesTouching(entity e1, entity e2)
1323 if (e1.absmin_x > e2.absmax_x)
1325 if (e1.absmin_y > e2.absmax_y)
1327 if (e1.absmin_z > e2.absmax_z)
1329 if (e1.absmax_x < e2.absmin_x)
1331 if (e1.absmax_y < e2.absmin_y)
1333 if (e1.absmax_z < e2.absmin_z)
1349 vector cmins, cmaxs;
1352 return; // already linked by another door
1353 if (self.spawnflags & 4)
1355 self.owner = self.enemy = self;
1363 self.trigger_field = spawn_field(self.absmin, self.absmax);
1365 return; // don't want to link this door
1368 cmins = self.absmin;
1369 cmaxs = self.absmax;
1376 self.owner = starte; // master door
1379 starte.health = self.health;
1381 starte.targetname = self.targetname;
1382 if (self.message != "")
1383 starte.message = self.message;
1385 t = find(t, classname, self.classname);
1388 self.enemy = starte; // make the chain a loop
1390 // shootable, or triggered doors just needed the owner/enemy links,
1391 // they don't spawn a field
1402 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1407 if (EntitiesTouching(self,t))
1410 objerror ("cross connected doors");
1415 if (t.absmin_x < cmins_x)
1416 cmins_x = t.absmin_x;
1417 if (t.absmin_y < cmins_y)
1418 cmins_y = t.absmin_y;
1419 if (t.absmin_z < cmins_z)
1420 cmins_z = t.absmin_z;
1421 if (t.absmax_x > cmaxs_x)
1422 cmaxs_x = t.absmax_x;
1423 if (t.absmax_y > cmaxs_y)
1424 cmaxs_y = t.absmax_y;
1425 if (t.absmax_z > cmaxs_z)
1426 cmaxs_z = t.absmax_z;
1433 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
1434 if two doors touch, they are assumed to be connected and operate as a unit.
1436 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1438 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).
1440 GOLD_KEY causes the door to open only if the activator holds a gold key.
1442 SILVER_KEY causes the door to open only if the activator holds a silver key.
1444 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1445 "angle" determines the opening direction
1446 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1447 "health" if set, door must be shot open
1448 "speed" movement speed (100 default)
1449 "wait" wait before returning (3 default, -1 = never return)
1450 "lip" lip remaining at end of move (8 default)
1451 "dmg" damage to inflict when blocked (2 default)
1458 FIXME: only one sound set available at the time being
1462 void door_init_startopen()
1464 setorigin (self, self.pos2);
1465 self.pos2 = self.pos1;
1466 self.pos1 = self.origin;
1471 setorigin(self, self.pos1);
1472 self.velocity = '0 0 0';
1473 self.state = STATE_BOTTOM;
1474 self.think = SUB_Null;
1477 // spawnflags require key (for now only func_door)
1478 #define SPAWNFLAGS_GOLD_KEY 8
1479 #define SPAWNFLAGS_SILVER_KEY 16
1480 void spawnfunc_func_door()
1482 // Quake 1 keys compatibility
1483 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
1484 self.itemkeys |= ITEM_KEY_BIT(0);
1485 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
1486 self.itemkeys |= ITEM_KEY_BIT(1);
1488 //if (!self.deathtype) // map makers can override this
1489 // self.deathtype = " got in the way";
1492 self.max_health = self.health;
1493 if not(InitMovingBrushTrigger())
1495 self.effects |= EF_LOWPRECISION;
1496 self.classname = "door";
1498 self.blocked = door_blocked;
1499 self.use = door_use;
1501 // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
1502 // if(self.spawnflags & 8)
1503 // self.dmg = 10000;
1505 if(self.dmg && (!self.message))
1506 self.message = "was squished";
1507 if(self.dmg && (!self.message2))
1508 self.message2 = "was squished by";
1510 if (self.sounds > 0)
1512 precache_sound ("plats/medplat1.wav");
1513 precache_sound ("plats/medplat2.wav");
1514 self.noise2 = "plats/medplat1.wav";
1515 self.noise1 = "plats/medplat2.wav";
1525 self.pos1 = self.origin;
1526 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1528 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1529 // but spawn in the open position
1530 if (self.spawnflags & DOOR_START_OPEN)
1531 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1533 self.state = STATE_BOTTOM;
1537 self.takedamage = DAMAGE_YES;
1538 self.event_damage = door_damage;
1544 self.touch = door_touch;
1546 // LinkDoors can't be done until all of the doors have been spawned, so
1547 // the sizes can be detected properly.
1548 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1550 self.reset = door_reset;
1553 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1554 if two doors touch, they are assumed to be connected and operate as a unit.
1556 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1558 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1559 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1560 must have set trigger_reverse to 1.
1561 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1563 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).
1565 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1566 "angle" determines the destination angle for opening. negative values reverse the direction.
1567 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1568 "health" if set, door must be shot open
1569 "speed" movement speed (100 default)
1570 "wait" wait before returning (3 default, -1 = never return)
1571 "dmg" damage to inflict when blocked (2 default)
1578 FIXME: only one sound set available at the time being
1581 void door_rotating_reset()
1583 self.angles = self.pos1;
1584 self.avelocity = '0 0 0';
1585 self.state = STATE_BOTTOM;
1586 self.think = SUB_Null;
1589 void door_rotating_init_startopen()
1591 self.angles = self.movedir;
1592 self.pos2 = '0 0 0';
1593 self.pos1 = self.movedir;
1597 void spawnfunc_func_door_rotating()
1600 //if (!self.deathtype) // map makers can override this
1601 // self.deathtype = " got in the way";
1603 // I abuse "movedir" for denoting the axis for now
1604 if (self.spawnflags & 64) // X (untested)
1605 self.movedir = '0 0 1';
1606 else if (self.spawnflags & 128) // Y (untested)
1607 self.movedir = '1 0 0';
1609 self.movedir = '0 1 0';
1611 if (self.angles_y==0) self.angles_y = 90;
1613 self.movedir = self.movedir * self.angles_y;
1614 self.angles = '0 0 0';
1616 self.max_health = self.health;
1617 self.avelocity = self.movedir;
1618 if not(InitMovingBrushTrigger())
1620 self.velocity = '0 0 0';
1621 //self.effects |= EF_LOWPRECISION;
1622 self.classname = "door_rotating";
1624 self.blocked = door_blocked;
1625 self.use = door_use;
1627 if(self.spawnflags & 8)
1630 if(self.dmg && (!self.message))
1631 self.message = "was squished";
1632 if(self.dmg && (!self.message2))
1633 self.message2 = "was squished by";
1635 if (self.sounds > 0)
1637 precache_sound ("plats/medplat1.wav");
1638 precache_sound ("plats/medplat2.wav");
1639 self.noise2 = "plats/medplat1.wav";
1640 self.noise1 = "plats/medplat2.wav";
1647 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1649 self.pos1 = '0 0 0';
1650 self.pos2 = self.movedir;
1652 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1653 // but spawn in the open position
1654 if (self.spawnflags & DOOR_START_OPEN)
1655 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1657 self.state = STATE_BOTTOM;
1661 self.takedamage = DAMAGE_YES;
1662 self.event_damage = door_damage;
1668 self.touch = door_touch;
1670 // LinkDoors can't be done until all of the doors have been spawned, so
1671 // the sizes can be detected properly.
1672 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1674 self.reset = door_rotating_reset;
1678 =============================================================================
1682 =============================================================================
1685 void() fd_secret_move1;
1686 void() fd_secret_move2;
1687 void() fd_secret_move3;
1688 void() fd_secret_move4;
1689 void() fd_secret_move5;
1690 void() fd_secret_move6;
1691 void() fd_secret_done;
1693 float SECRET_OPEN_ONCE = 1; // stays open
1694 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1695 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1696 float SECRET_NO_SHOOT = 8; // only opened by trigger
1697 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1700 void fd_secret_use()
1703 string message_save;
1705 self.health = 10000;
1706 self.bot_attack = TRUE;
1708 // exit if still moving around...
1709 if (self.origin != self.oldorigin)
1712 message_save = self.message;
1713 self.message = ""; // no more message
1714 SUB_UseTargets(); // fire all targets / killtargets
1715 self.message = message_save;
1717 self.velocity = '0 0 0';
1719 // Make a sound, wait a little...
1721 if (self.noise1 != "")
1722 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1723 self.nextthink = self.ltime + 0.1;
1725 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1726 makevectors(self.mangle);
1730 if (self.spawnflags & SECRET_1ST_DOWN)
1731 self.t_width = fabs(v_up * self.size);
1733 self.t_width = fabs(v_right * self.size);
1737 self.t_length = fabs(v_forward * self.size);
1739 if (self.spawnflags & SECRET_1ST_DOWN)
1740 self.dest1 = self.origin - v_up * self.t_width;
1742 self.dest1 = self.origin + v_right * (self.t_width * temp);
1744 self.dest2 = self.dest1 + v_forward * self.t_length;
1745 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
1746 if (self.noise2 != "")
1747 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1750 // Wait after first movement...
1751 void fd_secret_move1()
1753 self.nextthink = self.ltime + 1.0;
1754 self.think = fd_secret_move2;
1755 if (self.noise3 != "")
1756 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1759 // Start moving sideways w/sound...
1760 void fd_secret_move2()
1762 if (self.noise2 != "")
1763 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1764 SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
1767 // Wait here until time to go back...
1768 void fd_secret_move3()
1770 if (self.noise3 != "")
1771 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1772 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1774 self.nextthink = self.ltime + self.wait;
1775 self.think = fd_secret_move4;
1780 void fd_secret_move4()
1782 if (self.noise2 != "")
1783 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1784 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
1788 void fd_secret_move5()
1790 self.nextthink = self.ltime + 1.0;
1791 self.think = fd_secret_move6;
1792 if (self.noise3 != "")
1793 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1796 void fd_secret_move6()
1798 if (self.noise2 != "")
1799 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1800 SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
1803 void fd_secret_done()
1805 if (self.spawnflags&SECRET_YES_SHOOT)
1807 self.health = 10000;
1808 self.takedamage = DAMAGE_YES;
1809 //self.th_pain = fd_secret_use;
1811 if (self.noise3 != "")
1812 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1815 void secret_blocked()
1817 if (time < self.attack_finished_single)
1819 self.attack_finished_single = time + 0.5;
1820 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1832 if not(other.iscreature)
1834 if (self.attack_finished_single > time)
1837 self.attack_finished_single = time + 2;
1841 if (other.flags & FL_CLIENT)
1842 centerprint (other, self.message);
1843 play2(other, "misc/talk.wav");
1849 if (self.spawnflags&SECRET_YES_SHOOT)
1851 self.health = 10000;
1852 self.takedamage = DAMAGE_YES;
1854 setorigin(self, self.oldorigin);
1855 self.think = SUB_Null;
1858 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1859 Basic secret door. Slides back, then to the side. Angle determines direction.
1860 wait = # of seconds before coming back
1861 1st_left = 1st move is left of arrow
1862 1st_down = 1st move is down from arrow
1863 always_shoot = even if targeted, keep shootable
1864 t_width = override WIDTH to move back (or height if going down)
1865 t_length = override LENGTH to move sideways
1866 "dmg" damage to inflict when blocked (2 default)
1868 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1875 void spawnfunc_func_door_secret()
1877 /*if (!self.deathtype) // map makers can override this
1878 self.deathtype = " got in the way";*/
1884 self.mangle = self.angles;
1885 self.angles = '0 0 0';
1886 self.classname = "door";
1887 if not(InitMovingBrushTrigger())
1889 self.effects |= EF_LOWPRECISION;
1891 self.touch = secret_touch;
1892 self.blocked = secret_blocked;
1894 self.use = fd_secret_use;
1899 self.spawnflags |= SECRET_YES_SHOOT;
1901 if(self.spawnflags&SECRET_YES_SHOOT)
1903 self.health = 10000;
1904 self.takedamage = DAMAGE_YES;
1905 self.event_damage = fd_secret_use;
1907 self.oldorigin = self.origin;
1909 self.wait = 5; // 5 seconds before closing
1911 self.reset = secret_reset;
1915 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1916 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1917 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
1918 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1919 height: amplitude modifier (default 32)
1920 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1921 noise: path/name of looping .wav file to play.
1922 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1926 void func_fourier_controller_think()
1931 self.nextthink = time + 0.1;
1932 if not (self.owner.active == ACTIVE_ACTIVE)
1934 self.owner.velocity = '0 0 0';
1939 n = floor((tokenize_console(self.owner.netname)) / 5);
1940 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1942 v = self.owner.destvec;
1944 for(i = 0; i < n; ++i)
1946 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1947 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;
1950 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1951 // * 10 so it will arrive in 0.1 sec
1952 self.owner.velocity = (v - self.owner.origin) * 10;
1955 void spawnfunc_func_fourier()
1958 if (self.noise != "")
1960 precache_sound(self.noise);
1961 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
1968 self.destvec = self.origin;
1969 self.cnt = 360 / self.speed;
1971 self.blocked = generic_plat_blocked;
1972 if(self.dmg & (!self.message))
1973 self.message = " was squished";
1974 if(self.dmg && (!self.message2))
1975 self.message2 = "was squished by";
1976 if(self.dmg && (!self.dmgtime))
1977 self.dmgtime = 0.25;
1978 self.dmgtime2 = time;
1980 if(self.netname == "")
1981 self.netname = "1 0 0 0 1";
1983 if not(InitMovingBrushTrigger())
1986 self.active = ACTIVE_ACTIVE;
1988 // wait for targets to spawn
1989 controller = spawn();
1990 controller.classname = "func_fourier_controller";
1991 controller.owner = self;
1992 controller.nextthink = time + 1;
1993 controller.think = func_fourier_controller_think;
1994 self.nextthink = self.ltime + 999999999;
1995 self.think = SUB_Null;
1997 // Savage: Reduce bandwith, critical on e.g. nexdm02
1998 self.effects |= EF_LOWPRECISION;
2000 // TODO make a reset function for this one
2003 // reusing some fields havocbots declared
2004 .entity wp00, wp01, wp02, wp03;
2006 .float targetfactor, target2factor, target3factor, target4factor;
2007 .vector targetnormal, target2normal, target3normal, target4normal;
2009 vector func_vectormamamam_origin(entity o, float t)
2021 p = e.origin + t * e.velocity;
2023 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
2025 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
2031 p = e.origin + t * e.velocity;
2033 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
2035 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
2041 p = e.origin + t * e.velocity;
2043 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
2045 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
2051 p = e.origin + t * e.velocity;
2053 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
2055 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
2061 void func_vectormamamam_controller_think()
2063 self.nextthink = time + 0.1;
2065 if not (self.owner.active == ACTIVE_ACTIVE)
2067 self.owner.velocity = '0 0 0';
2071 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
2072 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
2075 void func_vectormamamam_findtarget()
2077 if(self.target != "")
2078 self.wp00 = find(world, targetname, self.target);
2080 if(self.target2 != "")
2081 self.wp01 = find(world, targetname, self.target2);
2083 if(self.target3 != "")
2084 self.wp02 = find(world, targetname, self.target3);
2086 if(self.target4 != "")
2087 self.wp03 = find(world, targetname, self.target4);
2089 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
2090 objerror("No reference entity found, so there is nothing to move. Aborting.");
2092 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
2095 controller = spawn();
2096 controller.classname = "func_vectormamamam_controller";
2097 controller.owner = self;
2098 controller.nextthink = time + 1;
2099 controller.think = func_vectormamamam_controller_think;
2102 void spawnfunc_func_vectormamamam()
2104 if (self.noise != "")
2106 precache_sound(self.noise);
2107 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
2110 if(!self.targetfactor)
2111 self.targetfactor = 1;
2113 if(!self.target2factor)
2114 self.target2factor = 1;
2116 if(!self.target3factor)
2117 self.target3factor = 1;
2119 if(!self.target4factor)
2120 self.target4factor = 1;
2122 if(vlen(self.targetnormal))
2123 self.targetnormal = normalize(self.targetnormal);
2125 if(vlen(self.target2normal))
2126 self.target2normal = normalize(self.target2normal);
2128 if(vlen(self.target3normal))
2129 self.target3normal = normalize(self.target3normal);
2131 if(vlen(self.target4normal))
2132 self.target4normal = normalize(self.target4normal);
2134 self.blocked = generic_plat_blocked;
2135 if(self.dmg & (!self.message))
2136 self.message = " was squished";
2137 if(self.dmg && (!self.message2))
2138 self.message2 = "was squished by";
2139 if(self.dmg && (!self.dmgtime))
2140 self.dmgtime = 0.25;
2141 self.dmgtime2 = time;
2143 if(self.netname == "")
2144 self.netname = "1 0 0 0 1";
2146 if not(InitMovingBrushTrigger())
2149 // wait for targets to spawn
2150 self.nextthink = self.ltime + 999999999;
2151 self.think = SUB_Null;
2153 // Savage: Reduce bandwith, critical on e.g. nexdm02
2154 self.effects |= EF_LOWPRECISION;
2156 self.active = ACTIVE_ACTIVE;
2158 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
2161 void conveyor_think()
2165 // set myself as current conveyor where possible
2166 for(e = world; (e = findentity(e, conveyor, self)); )
2171 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2172 if(!e.conveyor.state)
2175 vector emin = e.absmin;
2176 vector emax = e.absmax;
2177 if(self.solid == SOLID_BSP)
2182 if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2183 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2187 for(e = world; (e = findentity(e, conveyor, self)); )
2189 if(e.flags & FL_CLIENT) // doing it via velocity has quite some advantages
2190 continue; // done in SV_PlayerPhysics
2192 setorigin(e, e.origin + self.movedir * sys_frametime);
2193 move_out_of_solid(e);
2194 UpdateCSQCProjectile(e);
2196 // stupid conveyor code
2197 tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2198 if(trace_fraction > 0)
2199 setorigin(e, trace_endpos);
2204 self.nextthink = time;
2209 self.state = !self.state;
2212 void conveyor_reset()
2214 self.state = (self.spawnflags & 1);
2217 void conveyor_init()
2221 self.movedir = self.movedir * self.speed;
2222 self.think = conveyor_think;
2223 self.nextthink = time;
2226 self.use = conveyor_use;
2227 self.reset = conveyor_reset;
2234 void spawnfunc_trigger_conveyor()
2241 void spawnfunc_func_conveyor()
2244 InitMovingBrushTrigger();
2245 self.movetype = MOVETYPE_NONE;