2 void generic_plat_blocked()
4 if(self.dmg && other.takedamage != DAMAGE_NO) {
5 if(self.dmgtime2 < time) {
6 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
7 self.dmgtime2 = time + self.dmgtime;
10 // Gib dead/dying stuff
11 if(other.deadflag != DEAD_NO)
12 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
18 float STATE_BOTTOM = 1;
22 .entity trigger_field;
24 void() plat_center_touch;
25 void() plat_outside_touch;
26 void() plat_trigger_use;
30 float PLAT_LOW_TRIGGER = 1;
32 void plat_spawn_inside_trigger()
38 trigger.touch = plat_center_touch;
39 trigger.movetype = MOVETYPE_NONE;
40 trigger.solid = SOLID_TRIGGER;
43 tmin = self.absmin + '25 25 0';
44 tmax = self.absmax - '25 25 -8';
45 tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8);
46 if (self.spawnflags & PLAT_LOW_TRIGGER)
49 if (self.size_x <= 50)
51 tmin_x = (self.mins_x + self.maxs_x) / 2;
54 if (self.size_y <= 50)
56 tmin_y = (self.mins_y + self.maxs_y) / 2;
64 setsize (trigger, tmin, tmax);
68 // otherwise, something is fishy...
70 objerror("plat_spawn_inside_trigger: platform has odd size or lip, can't spawn");
75 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
77 self.think = plat_go_down;
78 self.nextthink = self.ltime + 3;
81 void plat_hit_bottom()
83 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
89 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_NORM);
91 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, plat_hit_bottom);
96 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_NORM);
98 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, plat_hit_top);
101 void plat_center_touch()
103 if not(other.iscreature)
106 if (other.health <= 0)
112 else if (self.state == 1)
113 self.nextthink = self.ltime + 1; // delay going down
116 void plat_outside_touch()
118 if not(other.iscreature)
121 if (other.health <= 0)
129 void plat_trigger_use()
132 return; // already activated
139 if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
140 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
142 if((self.dmg) && (other.takedamage != DAMAGE_NO)) { // Shall we bite?
143 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
144 // Gib dead/dying stuff
145 if(other.deadflag != DEAD_NO)
146 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
151 else if (self.state == 3)
153 // when in other states, then the plat_crush event came delayed after
154 // plat state already had changed
155 // this isn't a bug per se!
163 objerror ("plat_use: not in up state");
167 .string sound1, sound2;
173 setorigin (self, self.pos1);
179 setorigin (self, self.pos2);
181 self.use = plat_trigger_use;
185 .float platmovetype_start_default, platmovetype_end_default;
186 float set_platmovetype(entity e, string s)
188 // sets platmovetype_start and platmovetype_end based on a string consisting of two values
191 n = tokenize_console(s);
193 e.platmovetype_start = stof(argv(0));
195 e.platmovetype_start = 0;
198 e.platmovetype_end = stof(argv(1));
200 e.platmovetype_end = e.platmovetype_start;
203 if(argv(2) == "force")
204 return TRUE; // no checking, return immediately
206 if(!cubic_speedfunc_is_sane(e.platmovetype_start, e.platmovetype_end))
208 objerror("Invalid platform move type; platform would go in reverse, which is not allowed.");
215 void spawnfunc_path_corner()
217 // setup values for overriding train movement
218 // if a second value does not exist, both start and end speeds are the single value specified
219 if(!set_platmovetype(self, self.platmovetype))
222 void spawnfunc_func_plat()
224 if (self.sounds == 0)
227 if(self.spawnflags & 4)
230 if(self.dmg && (!self.message))
231 self.message = "was squished";
232 if(self.dmg && (!self.message2))
233 self.message2 = "was squished by";
235 if (self.sounds == 1)
237 precache_sound ("plats/plat1.wav");
238 precache_sound ("plats/plat2.wav");
239 self.noise = "plats/plat1.wav";
240 self.noise1 = "plats/plat2.wav";
243 if (self.sounds == 2)
245 precache_sound ("plats/medplat1.wav");
246 precache_sound ("plats/medplat2.wav");
247 self.noise = "plats/medplat1.wav";
248 self.noise1 = "plats/medplat2.wav";
253 precache_sound (self.sound1);
254 self.noise = self.sound1;
258 precache_sound (self.sound2);
259 self.noise1 = self.sound2;
262 self.mangle = self.angles;
263 self.angles = '0 0 0';
265 self.classname = "plat";
266 if not(InitMovingBrushTrigger())
268 self.effects |= EF_LOWPRECISION;
269 setsize (self, self.mins , self.maxs);
271 self.blocked = plat_crush;
278 self.height = self.size_z - self.lip;
280 self.pos1 = self.origin;
281 self.pos2 = self.origin;
282 self.pos2_z = self.origin_z - self.height;
284 self.reset = plat_reset;
287 plat_spawn_inside_trigger (); // the "start moving" trigger
290 .float train_wait_turning;
301 // if turning is enabled, the train will turn toward the next point while waiting
302 if(self.platmovetype_turn && !self.train_wait_turning)
306 targ = find(world, targetname, self.target);
307 if((self.spawnflags & 1) && targ.curvetarget)
308 cp = find(world, targetname, targ.curvetarget);
312 if(cp) // bezier curves movement
313 org = cp.origin - (self.origin + self.mins); // use the origin of the control point of the next path_corner
314 else // linear movement
315 org = targ.origin - (self.origin + self.mins); // use the origin of the next path_corner
316 org = vectoangles(org);
317 org_x = -org_x; // flip up / down orientation
319 if(self.wait >= 0) // slow turning
321 SUB_CalcAngleMove(org, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
322 self.train_wait_turning = TRUE;
325 else // instant turning
330 stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
332 if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
334 self.train_wait_turning = FALSE;
339 self.think = train_next;
340 self.nextthink = self.ltime + self.wait;
349 targ = find(world, targetname, self.target);
350 self.target = targ.target;
351 if (self.spawnflags & 1)
355 cp = find(world, targetname, targ.curvetarget); // get its second target (the control point)
356 cp_org = cp.origin - self.mins; // no control point found, assume a straight line to the destination
362 objerror("train_next: no next target");
363 self.wait = targ.wait;
367 if(targ.platmovetype)
369 // this path_corner contains a movetype overrider, apply it
370 self.platmovetype_start = targ.platmovetype_start;
371 self.platmovetype_end = targ.platmovetype_end;
375 // this path_corner doesn't contain a movetype overrider, use the train's defaults
376 self.platmovetype_start = self.platmovetype_start_default;
377 self.platmovetype_end = self.platmovetype_end_default;
383 SUB_CalcMove_Bezier(cp_org, targ.origin - self.mins, TSPEED_LINEAR, targ.speed, train_wait);
385 SUB_CalcMove(targ.origin - self.mins, TSPEED_LINEAR, targ.speed, train_wait);
390 SUB_CalcMove_Bezier(cp_org, targ.origin - self.mins, TSPEED_LINEAR, self.speed, train_wait);
392 SUB_CalcMove(targ.origin - self.mins, TSPEED_LINEAR, self.speed, train_wait);
396 sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
399 void func_train_find()
402 targ = find(world, targetname, self.target);
403 self.target = targ.target;
405 objerror("func_train_find: no next target");
406 setorigin(self, targ.origin - self.mins);
407 self.nextthink = self.ltime + 1;
408 self.think = train_next;
411 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
412 Ridable platform, targets spawnfunc_path_corner path to follow.
413 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
414 target : targetname of first spawnfunc_path_corner (starts here)
416 void spawnfunc_func_train()
418 if (self.noise != "")
419 precache_sound(self.noise);
422 objerror("func_train without a target");
425 if (self.spawnflags & 2)
426 self.platmovetype_turn = TRUE;
428 if not(InitMovingBrushTrigger())
430 self.effects |= EF_LOWPRECISION;
432 // wait for targets to spawn
433 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
435 self.blocked = generic_plat_blocked;
436 if(self.dmg & (!self.message))
437 self.message = " was squished";
438 if(self.dmg && (!self.message2))
439 self.message2 = "was squished by";
440 if(self.dmg && (!self.dmgtime))
442 self.dmgtime2 = time;
444 if(!set_platmovetype(self, self.platmovetype))
446 self.platmovetype_start_default = self.platmovetype_start;
447 self.platmovetype_end_default = self.platmovetype_end;
449 // TODO make a reset function for this one
452 void func_rotating_setactive(float astate)
455 if (astate == ACTIVE_TOGGLE)
457 if(self.active == ACTIVE_ACTIVE)
458 self.active = ACTIVE_NOT;
460 self.active = ACTIVE_ACTIVE;
463 self.active = astate;
465 if(self.active == ACTIVE_NOT)
466 self.avelocity = '0 0 0';
468 self.avelocity = self.pos1;
471 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
472 Brush model that spins in place on one axis (default Z).
473 speed : speed to rotate (in degrees per second)
474 noise : path/name of looping .wav file to play.
475 dmg : Do this mutch dmg every .dmgtime intervall when blocked
479 void spawnfunc_func_rotating()
481 if (self.noise != "")
483 precache_sound(self.noise);
484 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
487 self.active = ACTIVE_ACTIVE;
488 self.setactive = func_rotating_setactive;
492 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
493 if (self.spawnflags & 4) // X (untested)
494 self.avelocity = '0 0 1' * self.speed;
495 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
496 else if (self.spawnflags & 8) // Y (untested)
497 self.avelocity = '1 0 0' * self.speed;
498 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
500 self.avelocity = '0 1 0' * self.speed;
502 self.pos1 = self.avelocity;
504 if(self.dmg & (!self.message))
505 self.message = " was squished";
506 if(self.dmg && (!self.message2))
507 self.message2 = "was squished by";
510 if(self.dmg && (!self.dmgtime))
513 self.dmgtime2 = time;
515 if not(InitMovingBrushTrigger())
517 // no EF_LOWPRECISION here, as rounding angles is bad
519 self.blocked = generic_plat_blocked;
521 // wait for targets to spawn
522 self.nextthink = self.ltime + 999999999;
523 self.think = SUB_Null;
525 // TODO make a reset function for this one
529 void func_bobbing_controller_think()
532 self.nextthink = time + 0.1;
534 if not (self.owner.active == ACTIVE_ACTIVE)
536 self.owner.velocity = '0 0 0';
540 // calculate sinewave using makevectors
541 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
542 v = self.owner.destvec + self.owner.movedir * v_forward_y;
543 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
544 // * 10 so it will arrive in 0.1 sec
545 self.owner.velocity = (v - self.owner.origin) * 10;
548 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
549 Brush model that moves back and forth on one axis (default Z).
550 speed : how long one cycle takes in seconds (default 4)
551 height : how far the cycle moves (default 32)
552 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
553 noise : path/name of looping .wav file to play.
554 dmg : Do this mutch dmg every .dmgtime intervall when blocked
557 void spawnfunc_func_bobbing()
560 if (self.noise != "")
562 precache_sound(self.noise);
563 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
569 // center of bobbing motion
570 self.destvec = self.origin;
571 // time scale to get degrees
572 self.cnt = 360 / self.speed;
574 self.active = ACTIVE_ACTIVE;
576 // damage when blocked
577 self.blocked = generic_plat_blocked;
578 if(self.dmg & (!self.message))
579 self.message = " was squished";
580 if(self.dmg && (!self.message2))
581 self.message2 = "was squished by";
582 if(self.dmg && (!self.dmgtime))
584 self.dmgtime2 = time;
587 if (self.spawnflags & 1) // X
588 self.movedir = '1 0 0' * self.height;
589 else if (self.spawnflags & 2) // Y
590 self.movedir = '0 1 0' * self.height;
592 self.movedir = '0 0 1' * self.height;
594 if not(InitMovingBrushTrigger())
597 // wait for targets to spawn
598 controller = spawn();
599 controller.classname = "func_bobbing_controller";
600 controller.owner = self;
601 controller.nextthink = time + 1;
602 controller.think = func_bobbing_controller_think;
603 self.nextthink = self.ltime + 999999999;
604 self.think = SUB_Null;
606 // Savage: Reduce bandwith, critical on e.g. nexdm02
607 self.effects |= EF_LOWPRECISION;
609 // TODO make a reset function for this one
613 void func_pendulum_controller_think()
616 self.nextthink = time + 0.1;
618 if not (self.owner.active == ACTIVE_ACTIVE)
620 self.owner.avelocity_x = 0;
624 // calculate sinewave using makevectors
625 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
626 v = self.owner.speed * v_forward_y + self.cnt;
627 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
629 // * 10 so it will arrive in 0.1 sec
630 self.owner.avelocity_z = (remainder(v - self.owner.angles_z, 360)) * 10;
634 void spawnfunc_func_pendulum()
637 if (self.noise != "")
639 precache_sound(self.noise);
640 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
643 self.active = ACTIVE_ACTIVE;
645 // keys: angle, speed, phase, noise, freq
649 // not initializing self.dmg to 2, to allow damageless pendulum
651 if(self.dmg & (!self.message))
652 self.message = " was squished";
653 if(self.dmg && (!self.message2))
654 self.message2 = "was squished by";
655 if(self.dmg && (!self.dmgtime))
657 self.dmgtime2 = time;
659 self.blocked = generic_plat_blocked;
661 self.avelocity_z = 0.0000001;
662 if not(InitMovingBrushTrigger())
667 // find pendulum length (same formula as Q3A)
668 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins_z))));
671 // copy initial angle
672 self.cnt = self.angles_z;
674 // wait for targets to spawn
675 controller = spawn();
676 controller.classname = "func_pendulum_controller";
677 controller.owner = self;
678 controller.nextthink = time + 1;
679 controller.think = func_pendulum_controller_think;
680 self.nextthink = self.ltime + 999999999;
681 self.think = SUB_Null;
683 //self.effects |= EF_LOWPRECISION;
685 // TODO make a reset function for this one
688 // button and multiple button
691 void() button_return;
695 self.state = STATE_TOP;
696 self.nextthink = self.ltime + self.wait;
697 self.think = button_return;
698 activator = self.enemy;
700 self.frame = 1; // use alternate textures
705 self.state = STATE_BOTTOM;
710 self.state = STATE_DOWN;
711 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
712 self.frame = 0; // use normal textures
714 self.takedamage = DAMAGE_YES; // can be shot again
718 void button_blocked()
720 // do nothing, just don't come all the way back out
726 self.health = self.max_health;
727 self.takedamage = DAMAGE_NO; // will be reset upon return
729 if (self.state == STATE_UP || self.state == STATE_TOP)
732 if (self.noise != "")
733 sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
735 self.state = STATE_UP;
736 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
741 self.health = self.max_health;
742 setorigin(self, self.pos1);
743 self.frame = 0; // use normal textures
744 self.state = STATE_BOTTOM;
746 self.takedamage = DAMAGE_YES; // can be shot again
751 // if (activator.classname != "player")
753 // dprint(activator.classname);
754 // dprint(" triggered a button\n");
757 if not (self.active == ACTIVE_ACTIVE)
760 self.enemy = activator;
766 // if (activator.classname != "player")
768 // dprint(activator.classname);
769 // dprint(" touched a button\n");
773 if not(other.iscreature)
775 if(other.velocity * self.movedir < 0)
779 self.enemy = other.owner;
783 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
785 if(self.spawnflags & DOOR_NOSPLASH)
786 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
788 self.health = self.health - damage;
789 if (self.health <= 0)
791 // if (activator.classname != "player")
793 // dprint(activator.classname);
794 // dprint(" killed a button\n");
796 self.enemy = damage_attacker;
802 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
803 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.
805 "angle" determines the opening direction
806 "target" all entities with a matching targetname will be used
807 "speed" override the default 40 speed
808 "wait" override the default 1 second wait (-1 = never return)
809 "lip" override the default 4 pixel lip remaining at end of move
810 "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
817 void spawnfunc_func_button()
821 if not(InitMovingBrushTrigger())
823 self.effects |= EF_LOWPRECISION;
825 self.blocked = button_blocked;
826 self.use = button_use;
828 // if (self.health == 0) // all buttons are now shootable
832 self.max_health = self.health;
833 self.event_damage = button_damage;
834 self.takedamage = DAMAGE_YES;
837 self.touch = button_touch;
847 precache_sound(self.noise);
849 self.active = ACTIVE_ACTIVE;
851 self.pos1 = self.origin;
852 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
853 self.flags |= FL_NOTARGET;
859 float DOOR_START_OPEN = 1;
860 float DOOR_DONT_LINK = 4;
861 float DOOR_TOGGLE = 32;
865 Doors are similar to buttons, but can spawn a fat trigger field around them
866 to open without a touch, and they link together to form simultanious
869 Door.owner is the master door. If there is only one door, it points to itself.
870 If multiple doors, all will point to a single one.
872 Door.enemy chains from the master door through all doors linked in the chain.
877 =============================================================================
881 =============================================================================
886 void() door_rotating_go_down;
887 void() door_rotating_go_up;
892 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
893 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
896 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
897 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
899 //Dont chamge direction for dead or dying stuff
900 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
903 if (self.state == STATE_DOWN)
904 if (self.classname == "door")
909 door_rotating_go_up ();
912 if (self.classname == "door")
917 door_rotating_go_down ();
921 //gib dying stuff just to make sure
922 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
923 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
927 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
928 // if a door has a negative wait, it would never come back if blocked,
929 // so let it just squash the object to death real fast
930 /* if (self.wait >= 0)
932 if (self.state == STATE_DOWN)
943 if (self.noise1 != "")
944 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
945 self.state = STATE_TOP;
946 if (self.spawnflags & DOOR_TOGGLE)
947 return; // don't come down automatically
948 if (self.classname == "door")
950 self.think = door_go_down;
953 self.think = door_rotating_go_down;
955 self.nextthink = self.ltime + self.wait;
958 void door_hit_bottom()
960 if (self.noise1 != "")
961 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
962 self.state = STATE_BOTTOM;
967 if (self.noise2 != "")
968 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
971 self.takedamage = DAMAGE_YES;
972 self.health = self.max_health;
975 self.state = STATE_DOWN;
976 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
981 if (self.state == STATE_UP)
982 return; // already going up
984 if (self.state == STATE_TOP)
985 { // reset top wait time
986 self.nextthink = self.ltime + self.wait;
990 if (self.noise2 != "")
991 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
992 self.state = STATE_UP;
993 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
996 oldmessage = self.message;
999 self.message = oldmessage;
1005 =============================================================================
1007 ACTIVATION FUNCTIONS
1009 =============================================================================
1012 float door_check_keys(void) {
1022 if not(door.itemkeys)
1025 // this door require a key
1026 // only a player can have a key
1027 if (other.classname != "player")
1030 if (item_keys_usekey(door, other)) {
1031 // some keys were used
1032 if (other.key_door_messagetime <= time) {
1033 play2(other, "misc/talk.wav");
1034 centerprint(other, strcat("You also need ", item_keys_keylist(door.itemkeys), "!"));
1035 other.key_door_messagetime = time + 2;
1038 // no keys were used
1039 if (other.key_door_messagetime <= time) {
1040 play2(other, "misc/talk.wav");
1041 centerprint(other, strcat("You need ", item_keys_keylist(door.itemkeys), "!"));
1042 other.key_door_messagetime = time + 2;
1046 if (door.itemkeys) {
1047 // door is now unlocked
1048 play2(other, "misc/talk.wav");
1049 centerprint(other, "Door unlocked!");
1061 if (self.owner != self)
1062 objerror ("door_fire: self.owner != self");
1066 if (self.spawnflags & DOOR_TOGGLE)
1068 if (self.state == STATE_UP || self.state == STATE_TOP)
1073 if (self.classname == "door")
1079 door_rotating_go_down ();
1082 } while ( (self != starte) && (self != world) );
1088 // trigger all paired doors
1092 if (self.classname == "door")
1097 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
1098 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
1100 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
1101 self.pos2 = '0 0 0' - self.pos2;
1103 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
1104 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
1105 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
1107 door_rotating_go_up ();
1111 } while ( (self != starte) && (self != world) );
1120 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
1132 void door_trigger_touch()
1134 if (other.health < 1)
1135 if not(other.iscreature && other.deadflag == DEAD_NO)
1138 if (time < self.attack_finished_single)
1141 // check if door is locked
1142 if (!door_check_keys())
1145 self.attack_finished_single = time + 1;
1154 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1157 if(self.spawnflags & DOOR_NOSPLASH)
1158 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
1160 self.health = self.health - damage;
1162 if (self.itemkeys) {
1163 // don't allow opening doors through damage if keys are required
1167 if (self.health <= 0)
1171 self.health = self.max_health;
1172 self.takedamage = DAMAGE_NO; // wil be reset upon return
1188 if(other.classname != "player")
1190 if (self.owner.attack_finished_single > time)
1193 self.owner.attack_finished_single = time + 2;
1195 if (!(self.owner.dmg) && (self.owner.message != ""))
1197 if (other.flags & FL_CLIENT)
1198 centerprint (other, self.owner.message);
1199 play2(other, "misc/talk.wav");
1204 void door_generic_plat_blocked()
1207 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1208 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1211 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1212 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1214 //Dont chamge direction for dead or dying stuff
1215 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1218 if (self.state == STATE_DOWN)
1219 door_rotating_go_up ();
1221 door_rotating_go_down ();
1224 //gib dying stuff just to make sure
1225 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1226 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1230 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1231 // if a door has a negative wait, it would never come back if blocked,
1232 // so let it just squash the object to death real fast
1233 /* if (self.wait >= 0)
1235 if (self.state == STATE_DOWN)
1236 door_rotating_go_up ();
1238 door_rotating_go_down ();
1244 void door_rotating_hit_top()
1246 if (self.noise1 != "")
1247 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1248 self.state = STATE_TOP;
1249 if (self.spawnflags & DOOR_TOGGLE)
1250 return; // don't come down automatically
1251 self.think = door_rotating_go_down;
1252 self.nextthink = self.ltime + self.wait;
1255 void door_rotating_hit_bottom()
1257 if (self.noise1 != "")
1258 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1259 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1261 self.pos2 = '0 0 0' - self.pos2;
1264 self.state = STATE_BOTTOM;
1267 void door_rotating_go_down()
1269 if (self.noise2 != "")
1270 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1271 if (self.max_health)
1273 self.takedamage = DAMAGE_YES;
1274 self.health = self.max_health;
1277 self.state = STATE_DOWN;
1278 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
1281 void door_rotating_go_up()
1283 if (self.state == STATE_UP)
1284 return; // already going up
1286 if (self.state == STATE_TOP)
1287 { // reset top wait time
1288 self.nextthink = self.ltime + self.wait;
1291 if (self.noise2 != "")
1292 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1293 self.state = STATE_UP;
1294 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
1297 oldmessage = self.message;
1300 self.message = oldmessage;
1307 =============================================================================
1311 =============================================================================
1315 entity spawn_field(vector fmins, vector fmaxs)
1321 trigger.classname = "doortriggerfield";
1322 trigger.movetype = MOVETYPE_NONE;
1323 trigger.solid = SOLID_TRIGGER;
1324 trigger.owner = self;
1325 trigger.touch = door_trigger_touch;
1329 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1334 float EntitiesTouching(entity e1, entity e2)
1336 if (e1.absmin_x > e2.absmax_x)
1338 if (e1.absmin_y > e2.absmax_y)
1340 if (e1.absmin_z > e2.absmax_z)
1342 if (e1.absmax_x < e2.absmin_x)
1344 if (e1.absmax_y < e2.absmin_y)
1346 if (e1.absmax_z < e2.absmin_z)
1362 vector cmins, cmaxs;
1365 return; // already linked by another door
1366 if (self.spawnflags & 4)
1368 self.owner = self.enemy = self;
1376 self.trigger_field = spawn_field(self.absmin, self.absmax);
1378 return; // don't want to link this door
1381 cmins = self.absmin;
1382 cmaxs = self.absmax;
1389 self.owner = starte; // master door
1392 starte.health = self.health;
1394 starte.targetname = self.targetname;
1395 if (self.message != "")
1396 starte.message = self.message;
1398 t = find(t, classname, self.classname);
1401 self.enemy = starte; // make the chain a loop
1403 // shootable, or triggered doors just needed the owner/enemy links,
1404 // they don't spawn a field
1415 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1420 if (EntitiesTouching(self,t))
1423 objerror ("cross connected doors");
1428 if (t.absmin_x < cmins_x)
1429 cmins_x = t.absmin_x;
1430 if (t.absmin_y < cmins_y)
1431 cmins_y = t.absmin_y;
1432 if (t.absmin_z < cmins_z)
1433 cmins_z = t.absmin_z;
1434 if (t.absmax_x > cmaxs_x)
1435 cmaxs_x = t.absmax_x;
1436 if (t.absmax_y > cmaxs_y)
1437 cmaxs_y = t.absmax_y;
1438 if (t.absmax_z > cmaxs_z)
1439 cmaxs_z = t.absmax_z;
1446 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
1447 if two doors touch, they are assumed to be connected and operate as a unit.
1449 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1451 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).
1453 GOLD_KEY causes the door to open only if the activator holds a gold key.
1455 SILVER_KEY causes the door to open only if the activator holds a silver key.
1457 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1458 "angle" determines the opening direction
1459 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1460 "health" if set, door must be shot open
1461 "speed" movement speed (100 default)
1462 "wait" wait before returning (3 default, -1 = never return)
1463 "lip" lip remaining at end of move (8 default)
1464 "dmg" damage to inflict when blocked (2 default)
1471 FIXME: only one sound set available at the time being
1475 void door_init_startopen()
1477 setorigin (self, self.pos2);
1478 self.pos2 = self.pos1;
1479 self.pos1 = self.origin;
1484 setorigin(self, self.pos1);
1485 self.velocity = '0 0 0';
1486 self.state = STATE_BOTTOM;
1487 self.think = SUB_Null;
1490 // spawnflags require key (for now only func_door)
1491 #define SPAWNFLAGS_GOLD_KEY 8
1492 #define SPAWNFLAGS_SILVER_KEY 16
1493 void spawnfunc_func_door()
1495 // Quake 1 keys compatibility
1496 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
1497 self.itemkeys |= ITEM_KEY_BIT(0);
1498 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
1499 self.itemkeys |= ITEM_KEY_BIT(1);
1501 //if (!self.deathtype) // map makers can override this
1502 // self.deathtype = " got in the way";
1505 self.max_health = self.health;
1506 if not(InitMovingBrushTrigger())
1508 self.effects |= EF_LOWPRECISION;
1509 self.classname = "door";
1511 self.blocked = door_blocked;
1512 self.use = door_use;
1514 // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
1515 // if(self.spawnflags & 8)
1516 // self.dmg = 10000;
1518 if(self.dmg && (!self.message))
1519 self.message = "was squished";
1520 if(self.dmg && (!self.message2))
1521 self.message2 = "was squished by";
1523 if (self.sounds > 0)
1525 precache_sound ("plats/medplat1.wav");
1526 precache_sound ("plats/medplat2.wav");
1527 self.noise2 = "plats/medplat1.wav";
1528 self.noise1 = "plats/medplat2.wav";
1538 self.pos1 = self.origin;
1539 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1541 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1542 // but spawn in the open position
1543 if (self.spawnflags & DOOR_START_OPEN)
1544 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1546 self.state = STATE_BOTTOM;
1550 self.takedamage = DAMAGE_YES;
1551 self.event_damage = door_damage;
1557 self.touch = door_touch;
1559 // LinkDoors can't be done until all of the doors have been spawned, so
1560 // the sizes can be detected properly.
1561 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1563 self.reset = door_reset;
1566 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1567 if two doors touch, they are assumed to be connected and operate as a unit.
1569 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1571 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1572 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1573 must have set trigger_reverse to 1.
1574 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1576 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).
1578 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1579 "angle" determines the destination angle for opening. negative values reverse the direction.
1580 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1581 "health" if set, door must be shot open
1582 "speed" movement speed (100 default)
1583 "wait" wait before returning (3 default, -1 = never return)
1584 "dmg" damage to inflict when blocked (2 default)
1591 FIXME: only one sound set available at the time being
1594 void door_rotating_reset()
1596 self.angles = self.pos1;
1597 self.avelocity = '0 0 0';
1598 self.state = STATE_BOTTOM;
1599 self.think = SUB_Null;
1602 void door_rotating_init_startopen()
1604 self.angles = self.movedir;
1605 self.pos2 = '0 0 0';
1606 self.pos1 = self.movedir;
1610 void spawnfunc_func_door_rotating()
1613 //if (!self.deathtype) // map makers can override this
1614 // self.deathtype = " got in the way";
1616 // I abuse "movedir" for denoting the axis for now
1617 if (self.spawnflags & 64) // X (untested)
1618 self.movedir = '0 0 1';
1619 else if (self.spawnflags & 128) // Y (untested)
1620 self.movedir = '1 0 0';
1622 self.movedir = '0 1 0';
1624 if (self.angles_y==0) self.angles_y = 90;
1626 self.movedir = self.movedir * self.angles_y;
1627 self.angles = '0 0 0';
1629 self.max_health = self.health;
1630 self.avelocity = self.movedir;
1631 if not(InitMovingBrushTrigger())
1633 self.velocity = '0 0 0';
1634 //self.effects |= EF_LOWPRECISION;
1635 self.classname = "door_rotating";
1637 self.blocked = door_blocked;
1638 self.use = door_use;
1640 if(self.spawnflags & 8)
1643 if(self.dmg && (!self.message))
1644 self.message = "was squished";
1645 if(self.dmg && (!self.message2))
1646 self.message2 = "was squished by";
1648 if (self.sounds > 0)
1650 precache_sound ("plats/medplat1.wav");
1651 precache_sound ("plats/medplat2.wav");
1652 self.noise2 = "plats/medplat1.wav";
1653 self.noise1 = "plats/medplat2.wav";
1660 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1662 self.pos1 = '0 0 0';
1663 self.pos2 = self.movedir;
1665 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1666 // but spawn in the open position
1667 if (self.spawnflags & DOOR_START_OPEN)
1668 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1670 self.state = STATE_BOTTOM;
1674 self.takedamage = DAMAGE_YES;
1675 self.event_damage = door_damage;
1681 self.touch = door_touch;
1683 // LinkDoors can't be done until all of the doors have been spawned, so
1684 // the sizes can be detected properly.
1685 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1687 self.reset = door_rotating_reset;
1691 =============================================================================
1695 =============================================================================
1698 void() fd_secret_move1;
1699 void() fd_secret_move2;
1700 void() fd_secret_move3;
1701 void() fd_secret_move4;
1702 void() fd_secret_move5;
1703 void() fd_secret_move6;
1704 void() fd_secret_done;
1706 float SECRET_OPEN_ONCE = 1; // stays open
1707 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1708 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1709 float SECRET_NO_SHOOT = 8; // only opened by trigger
1710 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1713 void fd_secret_use()
1716 string message_save;
1718 self.health = 10000;
1719 self.bot_attack = TRUE;
1721 // exit if still moving around...
1722 if (self.origin != self.oldorigin)
1725 message_save = self.message;
1726 self.message = ""; // no more message
1727 SUB_UseTargets(); // fire all targets / killtargets
1728 self.message = message_save;
1730 self.velocity = '0 0 0';
1732 // Make a sound, wait a little...
1734 if (self.noise1 != "")
1735 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1736 self.nextthink = self.ltime + 0.1;
1738 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1739 makevectors(self.mangle);
1743 if (self.spawnflags & SECRET_1ST_DOWN)
1744 self.t_width = fabs(v_up * self.size);
1746 self.t_width = fabs(v_right * self.size);
1750 self.t_length = fabs(v_forward * self.size);
1752 if (self.spawnflags & SECRET_1ST_DOWN)
1753 self.dest1 = self.origin - v_up * self.t_width;
1755 self.dest1 = self.origin + v_right * (self.t_width * temp);
1757 self.dest2 = self.dest1 + v_forward * self.t_length;
1758 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
1759 if (self.noise2 != "")
1760 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1763 // Wait after first movement...
1764 void fd_secret_move1()
1766 self.nextthink = self.ltime + 1.0;
1767 self.think = fd_secret_move2;
1768 if (self.noise3 != "")
1769 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1772 // Start moving sideways w/sound...
1773 void fd_secret_move2()
1775 if (self.noise2 != "")
1776 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1777 SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
1780 // Wait here until time to go back...
1781 void fd_secret_move3()
1783 if (self.noise3 != "")
1784 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1785 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1787 self.nextthink = self.ltime + self.wait;
1788 self.think = fd_secret_move4;
1793 void fd_secret_move4()
1795 if (self.noise2 != "")
1796 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1797 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
1801 void fd_secret_move5()
1803 self.nextthink = self.ltime + 1.0;
1804 self.think = fd_secret_move6;
1805 if (self.noise3 != "")
1806 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1809 void fd_secret_move6()
1811 if (self.noise2 != "")
1812 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1813 SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
1816 void fd_secret_done()
1818 if (self.spawnflags&SECRET_YES_SHOOT)
1820 self.health = 10000;
1821 self.takedamage = DAMAGE_YES;
1822 //self.th_pain = fd_secret_use;
1824 if (self.noise3 != "")
1825 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1828 void secret_blocked()
1830 if (time < self.attack_finished_single)
1832 self.attack_finished_single = time + 0.5;
1833 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1845 if not(other.iscreature)
1847 if (self.attack_finished_single > time)
1850 self.attack_finished_single = time + 2;
1854 if (other.flags & FL_CLIENT)
1855 centerprint (other, self.message);
1856 play2(other, "misc/talk.wav");
1862 if (self.spawnflags&SECRET_YES_SHOOT)
1864 self.health = 10000;
1865 self.takedamage = DAMAGE_YES;
1867 setorigin(self, self.oldorigin);
1868 self.think = SUB_Null;
1871 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1872 Basic secret door. Slides back, then to the side. Angle determines direction.
1873 wait = # of seconds before coming back
1874 1st_left = 1st move is left of arrow
1875 1st_down = 1st move is down from arrow
1876 always_shoot = even if targeted, keep shootable
1877 t_width = override WIDTH to move back (or height if going down)
1878 t_length = override LENGTH to move sideways
1879 "dmg" damage to inflict when blocked (2 default)
1881 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1888 void spawnfunc_func_door_secret()
1890 /*if (!self.deathtype) // map makers can override this
1891 self.deathtype = " got in the way";*/
1897 self.mangle = self.angles;
1898 self.angles = '0 0 0';
1899 self.classname = "door";
1900 if not(InitMovingBrushTrigger())
1902 self.effects |= EF_LOWPRECISION;
1904 self.touch = secret_touch;
1905 self.blocked = secret_blocked;
1907 self.use = fd_secret_use;
1912 self.spawnflags |= SECRET_YES_SHOOT;
1914 if(self.spawnflags&SECRET_YES_SHOOT)
1916 self.health = 10000;
1917 self.takedamage = DAMAGE_YES;
1918 self.event_damage = fd_secret_use;
1920 self.oldorigin = self.origin;
1922 self.wait = 5; // 5 seconds before closing
1924 self.reset = secret_reset;
1928 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1929 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1930 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
1931 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1932 height: amplitude modifier (default 32)
1933 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1934 noise: path/name of looping .wav file to play.
1935 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1939 void func_fourier_controller_think()
1944 self.nextthink = time + 0.1;
1945 if not (self.owner.active == ACTIVE_ACTIVE)
1947 self.owner.velocity = '0 0 0';
1952 n = floor((tokenize_console(self.owner.netname)) / 5);
1953 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1955 v = self.owner.destvec;
1957 for(i = 0; i < n; ++i)
1959 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1960 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;
1963 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1964 // * 10 so it will arrive in 0.1 sec
1965 self.owner.velocity = (v - self.owner.origin) * 10;
1968 void spawnfunc_func_fourier()
1971 if (self.noise != "")
1973 precache_sound(self.noise);
1974 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
1981 self.destvec = self.origin;
1982 self.cnt = 360 / self.speed;
1984 self.blocked = generic_plat_blocked;
1985 if(self.dmg & (!self.message))
1986 self.message = " was squished";
1987 if(self.dmg && (!self.message2))
1988 self.message2 = "was squished by";
1989 if(self.dmg && (!self.dmgtime))
1990 self.dmgtime = 0.25;
1991 self.dmgtime2 = time;
1993 if(self.netname == "")
1994 self.netname = "1 0 0 0 1";
1996 if not(InitMovingBrushTrigger())
1999 self.active = ACTIVE_ACTIVE;
2001 // wait for targets to spawn
2002 controller = spawn();
2003 controller.classname = "func_fourier_controller";
2004 controller.owner = self;
2005 controller.nextthink = time + 1;
2006 controller.think = func_fourier_controller_think;
2007 self.nextthink = self.ltime + 999999999;
2008 self.think = SUB_Null;
2010 // Savage: Reduce bandwith, critical on e.g. nexdm02
2011 self.effects |= EF_LOWPRECISION;
2013 // TODO make a reset function for this one
2016 // reusing some fields havocbots declared
2017 .entity wp00, wp01, wp02, wp03;
2019 .float targetfactor, target2factor, target3factor, target4factor;
2020 .vector targetnormal, target2normal, target3normal, target4normal;
2022 vector func_vectormamamam_origin(entity o, float t)
2034 p = e.origin + t * e.velocity;
2036 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
2038 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
2044 p = e.origin + t * e.velocity;
2046 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
2048 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
2054 p = e.origin + t * e.velocity;
2056 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
2058 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
2064 p = e.origin + t * e.velocity;
2066 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
2068 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
2074 void func_vectormamamam_controller_think()
2076 self.nextthink = time + 0.1;
2078 if not (self.owner.active == ACTIVE_ACTIVE)
2080 self.owner.velocity = '0 0 0';
2084 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
2085 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
2088 void func_vectormamamam_findtarget()
2090 if(self.target != "")
2091 self.wp00 = find(world, targetname, self.target);
2093 if(self.target2 != "")
2094 self.wp01 = find(world, targetname, self.target2);
2096 if(self.target3 != "")
2097 self.wp02 = find(world, targetname, self.target3);
2099 if(self.target4 != "")
2100 self.wp03 = find(world, targetname, self.target4);
2102 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
2103 objerror("No reference entity found, so there is nothing to move. Aborting.");
2105 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
2108 controller = spawn();
2109 controller.classname = "func_vectormamamam_controller";
2110 controller.owner = self;
2111 controller.nextthink = time + 1;
2112 controller.think = func_vectormamamam_controller_think;
2115 void spawnfunc_func_vectormamamam()
2117 if (self.noise != "")
2119 precache_sound(self.noise);
2120 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
2123 if(!self.targetfactor)
2124 self.targetfactor = 1;
2126 if(!self.target2factor)
2127 self.target2factor = 1;
2129 if(!self.target3factor)
2130 self.target3factor = 1;
2132 if(!self.target4factor)
2133 self.target4factor = 1;
2135 if(vlen(self.targetnormal))
2136 self.targetnormal = normalize(self.targetnormal);
2138 if(vlen(self.target2normal))
2139 self.target2normal = normalize(self.target2normal);
2141 if(vlen(self.target3normal))
2142 self.target3normal = normalize(self.target3normal);
2144 if(vlen(self.target4normal))
2145 self.target4normal = normalize(self.target4normal);
2147 self.blocked = generic_plat_blocked;
2148 if(self.dmg & (!self.message))
2149 self.message = " was squished";
2150 if(self.dmg && (!self.message2))
2151 self.message2 = "was squished by";
2152 if(self.dmg && (!self.dmgtime))
2153 self.dmgtime = 0.25;
2154 self.dmgtime2 = time;
2156 if(self.netname == "")
2157 self.netname = "1 0 0 0 1";
2159 if not(InitMovingBrushTrigger())
2162 // wait for targets to spawn
2163 self.nextthink = self.ltime + 999999999;
2164 self.think = SUB_Null;
2166 // Savage: Reduce bandwith, critical on e.g. nexdm02
2167 self.effects |= EF_LOWPRECISION;
2169 self.active = ACTIVE_ACTIVE;
2171 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
2174 void conveyor_think()
2178 // set myself as current conveyor where possible
2179 for(e = world; (e = findentity(e, conveyor, self)); )
2184 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2185 if(!e.conveyor.state)
2188 vector emin = e.absmin;
2189 vector emax = e.absmax;
2190 if(self.solid == SOLID_BSP)
2195 if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2196 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2200 for(e = world; (e = findentity(e, conveyor, self)); )
2202 if(e.flags & FL_CLIENT) // doing it via velocity has quite some advantages
2203 continue; // done in SV_PlayerPhysics
2205 setorigin(e, e.origin + self.movedir * sys_frametime);
2206 move_out_of_solid(e);
2207 UpdateCSQCProjectile(e);
2209 // stupid conveyor code
2210 tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2211 if(trace_fraction > 0)
2212 setorigin(e, trace_endpos);
2217 self.nextthink = time;
2222 self.state = !self.state;
2225 void conveyor_reset()
2227 self.state = (self.spawnflags & 1);
2230 void conveyor_init()
2234 self.movedir = self.movedir * self.speed;
2235 self.think = conveyor_think;
2236 self.nextthink = time;
2239 self.use = conveyor_use;
2240 self.reset = conveyor_reset;
2247 void spawnfunc_trigger_conveyor()
2254 void spawnfunc_func_conveyor()
2257 InitMovingBrushTrigger();
2258 self.movetype = MOVETYPE_NONE;