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 = vectoangles(cp.origin - (self.origin + self.mins)); // use the origin of the control point of the next path_corner
314 else // linear movement
315 org = vectoangles(targ.origin - (self.origin + self.mins)); // use the origin of the next path_corner
316 org_x = -org_x; // flip up / down orientation
318 if(self.wait >= 0) // slow turning
320 SUB_CalcAngleMove(org, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
321 self.train_wait_turning = TRUE;
324 else // instant turning
329 stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
331 if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
333 self.train_wait_turning = FALSE;
338 self.think = train_next;
339 self.nextthink = self.ltime + self.wait;
348 targ = find(world, targetname, self.target);
349 self.target = targ.target;
350 if (self.spawnflags & 1)
354 cp = find(world, targetname, targ.curvetarget); // get its second target (the control point)
355 cp_org = cp.origin - self.mins; // no control point found, assume a straight line to the destination
361 objerror("train_next: no next target");
362 self.wait = targ.wait;
366 if(targ.platmovetype)
368 // this path_corner contains a movetype overrider, apply it
369 self.platmovetype_start = targ.platmovetype_start;
370 self.platmovetype_end = targ.platmovetype_end;
374 // this path_corner doesn't contain a movetype overrider, use the train's defaults
375 self.platmovetype_start = self.platmovetype_start_default;
376 self.platmovetype_end = self.platmovetype_end_default;
382 SUB_CalcMove_Bezier(cp_org, targ.origin - self.mins, TSPEED_LINEAR, targ.speed, train_wait);
384 SUB_CalcMove(targ.origin - self.mins, TSPEED_LINEAR, targ.speed, train_wait);
389 SUB_CalcMove_Bezier(cp_org, targ.origin - self.mins, TSPEED_LINEAR, self.speed, train_wait);
391 SUB_CalcMove(targ.origin - self.mins, TSPEED_LINEAR, self.speed, train_wait);
395 sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
398 void func_train_find()
401 targ = find(world, targetname, self.target);
402 self.target = targ.target;
404 objerror("func_train_find: no next target");
405 setorigin(self, targ.origin - self.mins);
406 self.nextthink = self.ltime + 1;
407 self.think = train_next;
410 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
411 Ridable platform, targets spawnfunc_path_corner path to follow.
412 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
413 target : targetname of first spawnfunc_path_corner (starts here)
415 void spawnfunc_func_train()
417 if (self.noise != "")
418 precache_sound(self.noise);
421 objerror("func_train without a target");
424 if (self.spawnflags & 2)
425 self.platmovetype_turn = TRUE;
427 if not(InitMovingBrushTrigger())
429 self.effects |= EF_LOWPRECISION;
431 // wait for targets to spawn
432 InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
434 self.blocked = generic_plat_blocked;
435 if(self.dmg & (!self.message))
436 self.message = " was squished";
437 if(self.dmg && (!self.message2))
438 self.message2 = "was squished by";
439 if(self.dmg && (!self.dmgtime))
441 self.dmgtime2 = time;
443 if(!set_platmovetype(self, self.platmovetype))
445 self.platmovetype_start_default = self.platmovetype_start;
446 self.platmovetype_end_default = self.platmovetype_end;
448 // TODO make a reset function for this one
451 void func_rotating_setactive(float astate)
454 if (astate == ACTIVE_TOGGLE)
456 if(self.active == ACTIVE_ACTIVE)
457 self.active = ACTIVE_NOT;
459 self.active = ACTIVE_ACTIVE;
462 self.active = astate;
464 if(self.active == ACTIVE_NOT)
465 self.avelocity = '0 0 0';
467 self.avelocity = self.pos1;
470 /*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
471 Brush model that spins in place on one axis (default Z).
472 speed : speed to rotate (in degrees per second)
473 noise : path/name of looping .wav file to play.
474 dmg : Do this mutch dmg every .dmgtime intervall when blocked
478 void spawnfunc_func_rotating()
480 if (self.noise != "")
482 precache_sound(self.noise);
483 ambientsound(self.origin, self.noise, VOL_BASE, ATTN_IDLE);
486 self.active = ACTIVE_ACTIVE;
487 self.setactive = func_rotating_setactive;
491 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
492 if (self.spawnflags & 4) // X (untested)
493 self.avelocity = '0 0 1' * self.speed;
494 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
495 else if (self.spawnflags & 8) // Y (untested)
496 self.avelocity = '1 0 0' * self.speed;
497 // FIXME: test if this turns the right way, then remove this comment (negate as needed)
499 self.avelocity = '0 1 0' * self.speed;
501 self.pos1 = self.avelocity;
503 if(self.dmg & (!self.message))
504 self.message = " was squished";
505 if(self.dmg && (!self.message2))
506 self.message2 = "was squished by";
509 if(self.dmg && (!self.dmgtime))
512 self.dmgtime2 = time;
514 if not(InitMovingBrushTrigger())
516 // no EF_LOWPRECISION here, as rounding angles is bad
518 self.blocked = generic_plat_blocked;
520 // wait for targets to spawn
521 self.nextthink = self.ltime + 999999999;
522 self.think = SUB_Null;
524 // TODO make a reset function for this one
528 void func_bobbing_controller_think()
531 self.nextthink = time + 0.1;
533 if not (self.owner.active == ACTIVE_ACTIVE)
535 self.owner.velocity = '0 0 0';
539 // calculate sinewave using makevectors
540 makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
541 v = self.owner.destvec + self.owner.movedir * v_forward_y;
542 if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
543 // * 10 so it will arrive in 0.1 sec
544 self.owner.velocity = (v - self.owner.origin) * 10;
547 /*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
548 Brush model that moves back and forth on one axis (default Z).
549 speed : how long one cycle takes in seconds (default 4)
550 height : how far the cycle moves (default 32)
551 phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
552 noise : path/name of looping .wav file to play.
553 dmg : Do this mutch dmg every .dmgtime intervall when blocked
556 void spawnfunc_func_bobbing()
559 if (self.noise != "")
561 precache_sound(self.noise);
562 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
568 // center of bobbing motion
569 self.destvec = self.origin;
570 // time scale to get degrees
571 self.cnt = 360 / self.speed;
573 self.active = ACTIVE_ACTIVE;
575 // damage when blocked
576 self.blocked = generic_plat_blocked;
577 if(self.dmg & (!self.message))
578 self.message = " was squished";
579 if(self.dmg && (!self.message2))
580 self.message2 = "was squished by";
581 if(self.dmg && (!self.dmgtime))
583 self.dmgtime2 = time;
586 if (self.spawnflags & 1) // X
587 self.movedir = '1 0 0' * self.height;
588 else if (self.spawnflags & 2) // Y
589 self.movedir = '0 1 0' * self.height;
591 self.movedir = '0 0 1' * self.height;
593 if not(InitMovingBrushTrigger())
596 // wait for targets to spawn
597 controller = spawn();
598 controller.classname = "func_bobbing_controller";
599 controller.owner = self;
600 controller.nextthink = time + 1;
601 controller.think = func_bobbing_controller_think;
602 self.nextthink = self.ltime + 999999999;
603 self.think = SUB_Null;
605 // Savage: Reduce bandwith, critical on e.g. nexdm02
606 self.effects |= EF_LOWPRECISION;
608 // TODO make a reset function for this one
612 void func_pendulum_controller_think()
615 self.nextthink = time + 0.1;
617 if not (self.owner.active == ACTIVE_ACTIVE)
619 self.owner.avelocity_x = 0;
623 // calculate sinewave using makevectors
624 makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
625 v = self.owner.speed * v_forward_y + self.cnt;
626 if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
628 // * 10 so it will arrive in 0.1 sec
629 self.owner.avelocity_z = (remainder(v - self.owner.angles_z, 360)) * 10;
633 void spawnfunc_func_pendulum()
636 if (self.noise != "")
638 precache_sound(self.noise);
639 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
642 self.active = ACTIVE_ACTIVE;
644 // keys: angle, speed, phase, noise, freq
648 // not initializing self.dmg to 2, to allow damageless pendulum
650 if(self.dmg & (!self.message))
651 self.message = " was squished";
652 if(self.dmg && (!self.message2))
653 self.message2 = "was squished by";
654 if(self.dmg && (!self.dmgtime))
656 self.dmgtime2 = time;
658 self.blocked = generic_plat_blocked;
660 self.avelocity_z = 0.0000001;
661 if not(InitMovingBrushTrigger())
666 // find pendulum length (same formula as Q3A)
667 self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins_z))));
670 // copy initial angle
671 self.cnt = self.angles_z;
673 // wait for targets to spawn
674 controller = spawn();
675 controller.classname = "func_pendulum_controller";
676 controller.owner = self;
677 controller.nextthink = time + 1;
678 controller.think = func_pendulum_controller_think;
679 self.nextthink = self.ltime + 999999999;
680 self.think = SUB_Null;
682 //self.effects |= EF_LOWPRECISION;
684 // TODO make a reset function for this one
687 // button and multiple button
690 void() button_return;
694 self.state = STATE_TOP;
695 self.nextthink = self.ltime + self.wait;
696 self.think = button_return;
697 activator = self.enemy;
699 self.frame = 1; // use alternate textures
704 self.state = STATE_BOTTOM;
709 self.state = STATE_DOWN;
710 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
711 self.frame = 0; // use normal textures
713 self.takedamage = DAMAGE_YES; // can be shot again
717 void button_blocked()
719 // do nothing, just don't come all the way back out
725 self.health = self.max_health;
726 self.takedamage = DAMAGE_NO; // will be reset upon return
728 if (self.state == STATE_UP || self.state == STATE_TOP)
731 if (self.noise != "")
732 sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NORM);
734 self.state = STATE_UP;
735 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
740 self.health = self.max_health;
741 setorigin(self, self.pos1);
742 self.frame = 0; // use normal textures
743 self.state = STATE_BOTTOM;
745 self.takedamage = DAMAGE_YES; // can be shot again
750 // if (activator.classname != "player")
752 // dprint(activator.classname);
753 // dprint(" triggered a button\n");
756 if not (self.active == ACTIVE_ACTIVE)
759 self.enemy = activator;
765 // if (activator.classname != "player")
767 // dprint(activator.classname);
768 // dprint(" touched a button\n");
772 if not(other.iscreature)
774 if(other.velocity * self.movedir < 0)
778 self.enemy = other.owner;
782 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
784 if(self.spawnflags & DOOR_NOSPLASH)
785 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
787 self.health = self.health - damage;
788 if (self.health <= 0)
790 // if (activator.classname != "player")
792 // dprint(activator.classname);
793 // dprint(" killed a button\n");
795 self.enemy = damage_attacker;
801 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
802 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.
804 "angle" determines the opening direction
805 "target" all entities with a matching targetname will be used
806 "speed" override the default 40 speed
807 "wait" override the default 1 second wait (-1 = never return)
808 "lip" override the default 4 pixel lip remaining at end of move
809 "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
816 void spawnfunc_func_button()
820 if not(InitMovingBrushTrigger())
822 self.effects |= EF_LOWPRECISION;
824 self.blocked = button_blocked;
825 self.use = button_use;
827 // if (self.health == 0) // all buttons are now shootable
831 self.max_health = self.health;
832 self.event_damage = button_damage;
833 self.takedamage = DAMAGE_YES;
836 self.touch = button_touch;
846 precache_sound(self.noise);
848 self.active = ACTIVE_ACTIVE;
850 self.pos1 = self.origin;
851 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
852 self.flags |= FL_NOTARGET;
858 float DOOR_START_OPEN = 1;
859 float DOOR_DONT_LINK = 4;
860 float DOOR_TOGGLE = 32;
864 Doors are similar to buttons, but can spawn a fat trigger field around them
865 to open without a touch, and they link together to form simultanious
868 Door.owner is the master door. If there is only one door, it points to itself.
869 If multiple doors, all will point to a single one.
871 Door.enemy chains from the master door through all doors linked in the chain.
876 =============================================================================
880 =============================================================================
885 void() door_rotating_go_down;
886 void() door_rotating_go_up;
891 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
892 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
895 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
896 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
898 //Dont chamge direction for dead or dying stuff
899 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
902 if (self.state == STATE_DOWN)
903 if (self.classname == "door")
908 door_rotating_go_up ();
911 if (self.classname == "door")
916 door_rotating_go_down ();
920 //gib dying stuff just to make sure
921 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
922 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
926 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
927 // if a door has a negative wait, it would never come back if blocked,
928 // so let it just squash the object to death real fast
929 /* if (self.wait >= 0)
931 if (self.state == STATE_DOWN)
942 if (self.noise1 != "")
943 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
944 self.state = STATE_TOP;
945 if (self.spawnflags & DOOR_TOGGLE)
946 return; // don't come down automatically
947 if (self.classname == "door")
949 self.think = door_go_down;
952 self.think = door_rotating_go_down;
954 self.nextthink = self.ltime + self.wait;
957 void door_hit_bottom()
959 if (self.noise1 != "")
960 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
961 self.state = STATE_BOTTOM;
966 if (self.noise2 != "")
967 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
970 self.takedamage = DAMAGE_YES;
971 self.health = self.max_health;
974 self.state = STATE_DOWN;
975 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
980 if (self.state == STATE_UP)
981 return; // already going up
983 if (self.state == STATE_TOP)
984 { // reset top wait time
985 self.nextthink = self.ltime + self.wait;
989 if (self.noise2 != "")
990 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
991 self.state = STATE_UP;
992 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
995 oldmessage = self.message;
998 self.message = oldmessage;
1004 =============================================================================
1006 ACTIVATION FUNCTIONS
1008 =============================================================================
1011 float door_check_keys(void) {
1021 if not(door.itemkeys)
1024 // this door require a key
1025 // only a player can have a key
1026 if (other.classname != "player")
1029 if (item_keys_usekey(door, other)) {
1030 // some keys were used
1031 if (other.key_door_messagetime <= time) {
1032 play2(other, "misc/talk.wav");
1033 centerprint(other, strcat("You also need ", item_keys_keylist(door.itemkeys), "!"));
1034 other.key_door_messagetime = time + 2;
1037 // no keys were used
1038 if (other.key_door_messagetime <= time) {
1039 play2(other, "misc/talk.wav");
1040 centerprint(other, strcat("You need ", item_keys_keylist(door.itemkeys), "!"));
1041 other.key_door_messagetime = time + 2;
1045 if (door.itemkeys) {
1046 // door is now unlocked
1047 play2(other, "misc/talk.wav");
1048 centerprint(other, "Door unlocked!");
1060 if (self.owner != self)
1061 objerror ("door_fire: self.owner != self");
1065 if (self.spawnflags & DOOR_TOGGLE)
1067 if (self.state == STATE_UP || self.state == STATE_TOP)
1072 if (self.classname == "door")
1078 door_rotating_go_down ();
1081 } while ( (self != starte) && (self != world) );
1087 // trigger all paired doors
1091 if (self.classname == "door")
1096 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
1097 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
1099 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
1100 self.pos2 = '0 0 0' - self.pos2;
1102 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
1103 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
1104 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
1106 door_rotating_go_up ();
1110 } while ( (self != starte) && (self != world) );
1119 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
1131 void door_trigger_touch()
1133 if (other.health < 1)
1134 if not(other.iscreature && other.deadflag == DEAD_NO)
1137 if (time < self.attack_finished_single)
1140 // check if door is locked
1141 if (!door_check_keys())
1144 self.attack_finished_single = time + 1;
1153 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1156 if(self.spawnflags & DOOR_NOSPLASH)
1157 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
1159 self.health = self.health - damage;
1161 if (self.itemkeys) {
1162 // don't allow opening doors through damage if keys are required
1166 if (self.health <= 0)
1170 self.health = self.max_health;
1171 self.takedamage = DAMAGE_NO; // wil be reset upon return
1187 if(other.classname != "player")
1189 if (self.owner.attack_finished_single > time)
1192 self.owner.attack_finished_single = time + 2;
1194 if (!(self.owner.dmg) && (self.owner.message != ""))
1196 if (other.flags & FL_CLIENT)
1197 centerprint (other, self.owner.message);
1198 play2(other, "misc/talk.wav");
1203 void door_generic_plat_blocked()
1206 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1207 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1210 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1211 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1213 //Dont chamge direction for dead or dying stuff
1214 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1217 if (self.state == STATE_DOWN)
1218 door_rotating_go_up ();
1220 door_rotating_go_down ();
1223 //gib dying stuff just to make sure
1224 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1225 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1229 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1230 // if a door has a negative wait, it would never come back if blocked,
1231 // so let it just squash the object to death real fast
1232 /* if (self.wait >= 0)
1234 if (self.state == STATE_DOWN)
1235 door_rotating_go_up ();
1237 door_rotating_go_down ();
1243 void door_rotating_hit_top()
1245 if (self.noise1 != "")
1246 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1247 self.state = STATE_TOP;
1248 if (self.spawnflags & DOOR_TOGGLE)
1249 return; // don't come down automatically
1250 self.think = door_rotating_go_down;
1251 self.nextthink = self.ltime + self.wait;
1254 void door_rotating_hit_bottom()
1256 if (self.noise1 != "")
1257 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1258 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1260 self.pos2 = '0 0 0' - self.pos2;
1263 self.state = STATE_BOTTOM;
1266 void door_rotating_go_down()
1268 if (self.noise2 != "")
1269 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1270 if (self.max_health)
1272 self.takedamage = DAMAGE_YES;
1273 self.health = self.max_health;
1276 self.state = STATE_DOWN;
1277 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
1280 void door_rotating_go_up()
1282 if (self.state == STATE_UP)
1283 return; // already going up
1285 if (self.state == STATE_TOP)
1286 { // reset top wait time
1287 self.nextthink = self.ltime + self.wait;
1290 if (self.noise2 != "")
1291 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1292 self.state = STATE_UP;
1293 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
1296 oldmessage = self.message;
1299 self.message = oldmessage;
1306 =============================================================================
1310 =============================================================================
1314 entity spawn_field(vector fmins, vector fmaxs)
1320 trigger.classname = "doortriggerfield";
1321 trigger.movetype = MOVETYPE_NONE;
1322 trigger.solid = SOLID_TRIGGER;
1323 trigger.owner = self;
1324 trigger.touch = door_trigger_touch;
1328 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1333 float EntitiesTouching(entity e1, entity e2)
1335 if (e1.absmin_x > e2.absmax_x)
1337 if (e1.absmin_y > e2.absmax_y)
1339 if (e1.absmin_z > e2.absmax_z)
1341 if (e1.absmax_x < e2.absmin_x)
1343 if (e1.absmax_y < e2.absmin_y)
1345 if (e1.absmax_z < e2.absmin_z)
1361 vector cmins, cmaxs;
1364 return; // already linked by another door
1365 if (self.spawnflags & 4)
1367 self.owner = self.enemy = self;
1375 self.trigger_field = spawn_field(self.absmin, self.absmax);
1377 return; // don't want to link this door
1380 cmins = self.absmin;
1381 cmaxs = self.absmax;
1388 self.owner = starte; // master door
1391 starte.health = self.health;
1393 starte.targetname = self.targetname;
1394 if (self.message != "")
1395 starte.message = self.message;
1397 t = find(t, classname, self.classname);
1400 self.enemy = starte; // make the chain a loop
1402 // shootable, or triggered doors just needed the owner/enemy links,
1403 // they don't spawn a field
1414 self.owner.trigger_field = spawn_field(cmins, cmaxs);
1419 if (EntitiesTouching(self,t))
1422 objerror ("cross connected doors");
1427 if (t.absmin_x < cmins_x)
1428 cmins_x = t.absmin_x;
1429 if (t.absmin_y < cmins_y)
1430 cmins_y = t.absmin_y;
1431 if (t.absmin_z < cmins_z)
1432 cmins_z = t.absmin_z;
1433 if (t.absmax_x > cmaxs_x)
1434 cmaxs_x = t.absmax_x;
1435 if (t.absmax_y > cmaxs_y)
1436 cmaxs_y = t.absmax_y;
1437 if (t.absmax_z > cmaxs_z)
1438 cmaxs_z = t.absmax_z;
1445 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
1446 if two doors touch, they are assumed to be connected and operate as a unit.
1448 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1450 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).
1452 GOLD_KEY causes the door to open only if the activator holds a gold key.
1454 SILVER_KEY causes the door to open only if the activator holds a silver key.
1456 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1457 "angle" determines the opening direction
1458 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1459 "health" if set, door must be shot open
1460 "speed" movement speed (100 default)
1461 "wait" wait before returning (3 default, -1 = never return)
1462 "lip" lip remaining at end of move (8 default)
1463 "dmg" damage to inflict when blocked (2 default)
1470 FIXME: only one sound set available at the time being
1474 void door_init_startopen()
1476 setorigin (self, self.pos2);
1477 self.pos2 = self.pos1;
1478 self.pos1 = self.origin;
1483 setorigin(self, self.pos1);
1484 self.velocity = '0 0 0';
1485 self.state = STATE_BOTTOM;
1486 self.think = SUB_Null;
1489 // spawnflags require key (for now only func_door)
1490 #define SPAWNFLAGS_GOLD_KEY 8
1491 #define SPAWNFLAGS_SILVER_KEY 16
1492 void spawnfunc_func_door()
1494 // Quake 1 keys compatibility
1495 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
1496 self.itemkeys |= ITEM_KEY_BIT(0);
1497 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
1498 self.itemkeys |= ITEM_KEY_BIT(1);
1500 //if (!self.deathtype) // map makers can override this
1501 // self.deathtype = " got in the way";
1504 self.max_health = self.health;
1505 if not(InitMovingBrushTrigger())
1507 self.effects |= EF_LOWPRECISION;
1508 self.classname = "door";
1510 self.blocked = door_blocked;
1511 self.use = door_use;
1513 // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
1514 // if(self.spawnflags & 8)
1515 // self.dmg = 10000;
1517 if(self.dmg && (!self.message))
1518 self.message = "was squished";
1519 if(self.dmg && (!self.message2))
1520 self.message2 = "was squished by";
1522 if (self.sounds > 0)
1524 precache_sound ("plats/medplat1.wav");
1525 precache_sound ("plats/medplat2.wav");
1526 self.noise2 = "plats/medplat1.wav";
1527 self.noise1 = "plats/medplat2.wav";
1537 self.pos1 = self.origin;
1538 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1540 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1541 // but spawn in the open position
1542 if (self.spawnflags & DOOR_START_OPEN)
1543 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1545 self.state = STATE_BOTTOM;
1549 self.takedamage = DAMAGE_YES;
1550 self.event_damage = door_damage;
1556 self.touch = door_touch;
1558 // LinkDoors can't be done until all of the doors have been spawned, so
1559 // the sizes can be detected properly.
1560 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1562 self.reset = door_reset;
1565 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1566 if two doors touch, they are assumed to be connected and operate as a unit.
1568 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1570 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1571 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1572 must have set trigger_reverse to 1.
1573 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1575 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).
1577 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1578 "angle" determines the destination angle for opening. negative values reverse the direction.
1579 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1580 "health" if set, door must be shot open
1581 "speed" movement speed (100 default)
1582 "wait" wait before returning (3 default, -1 = never return)
1583 "dmg" damage to inflict when blocked (2 default)
1590 FIXME: only one sound set available at the time being
1593 void door_rotating_reset()
1595 self.angles = self.pos1;
1596 self.avelocity = '0 0 0';
1597 self.state = STATE_BOTTOM;
1598 self.think = SUB_Null;
1601 void door_rotating_init_startopen()
1603 self.angles = self.movedir;
1604 self.pos2 = '0 0 0';
1605 self.pos1 = self.movedir;
1609 void spawnfunc_func_door_rotating()
1612 //if (!self.deathtype) // map makers can override this
1613 // self.deathtype = " got in the way";
1615 // I abuse "movedir" for denoting the axis for now
1616 if (self.spawnflags & 64) // X (untested)
1617 self.movedir = '0 0 1';
1618 else if (self.spawnflags & 128) // Y (untested)
1619 self.movedir = '1 0 0';
1621 self.movedir = '0 1 0';
1623 if (self.angles_y==0) self.angles_y = 90;
1625 self.movedir = self.movedir * self.angles_y;
1626 self.angles = '0 0 0';
1628 self.max_health = self.health;
1629 self.avelocity = self.movedir;
1630 if not(InitMovingBrushTrigger())
1632 self.velocity = '0 0 0';
1633 //self.effects |= EF_LOWPRECISION;
1634 self.classname = "door_rotating";
1636 self.blocked = door_blocked;
1637 self.use = door_use;
1639 if(self.spawnflags & 8)
1642 if(self.dmg && (!self.message))
1643 self.message = "was squished";
1644 if(self.dmg && (!self.message2))
1645 self.message2 = "was squished by";
1647 if (self.sounds > 0)
1649 precache_sound ("plats/medplat1.wav");
1650 precache_sound ("plats/medplat2.wav");
1651 self.noise2 = "plats/medplat1.wav";
1652 self.noise1 = "plats/medplat2.wav";
1659 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1661 self.pos1 = '0 0 0';
1662 self.pos2 = self.movedir;
1664 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1665 // but spawn in the open position
1666 if (self.spawnflags & DOOR_START_OPEN)
1667 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1669 self.state = STATE_BOTTOM;
1673 self.takedamage = DAMAGE_YES;
1674 self.event_damage = door_damage;
1680 self.touch = door_touch;
1682 // LinkDoors can't be done until all of the doors have been spawned, so
1683 // the sizes can be detected properly.
1684 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1686 self.reset = door_rotating_reset;
1690 =============================================================================
1694 =============================================================================
1697 void() fd_secret_move1;
1698 void() fd_secret_move2;
1699 void() fd_secret_move3;
1700 void() fd_secret_move4;
1701 void() fd_secret_move5;
1702 void() fd_secret_move6;
1703 void() fd_secret_done;
1705 float SECRET_OPEN_ONCE = 1; // stays open
1706 float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1707 float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1708 float SECRET_NO_SHOOT = 8; // only opened by trigger
1709 float SECRET_YES_SHOOT = 16; // shootable even if targeted
1712 void fd_secret_use()
1715 string message_save;
1717 self.health = 10000;
1718 self.bot_attack = TRUE;
1720 // exit if still moving around...
1721 if (self.origin != self.oldorigin)
1724 message_save = self.message;
1725 self.message = ""; // no more message
1726 SUB_UseTargets(); // fire all targets / killtargets
1727 self.message = message_save;
1729 self.velocity = '0 0 0';
1731 // Make a sound, wait a little...
1733 if (self.noise1 != "")
1734 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTN_NORM);
1735 self.nextthink = self.ltime + 0.1;
1737 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1738 makevectors(self.mangle);
1742 if (self.spawnflags & SECRET_1ST_DOWN)
1743 self.t_width = fabs(v_up * self.size);
1745 self.t_width = fabs(v_right * self.size);
1749 self.t_length = fabs(v_forward * self.size);
1751 if (self.spawnflags & SECRET_1ST_DOWN)
1752 self.dest1 = self.origin - v_up * self.t_width;
1754 self.dest1 = self.origin + v_right * (self.t_width * temp);
1756 self.dest2 = self.dest1 + v_forward * self.t_length;
1757 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
1758 if (self.noise2 != "")
1759 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1762 // Wait after first movement...
1763 void fd_secret_move1()
1765 self.nextthink = self.ltime + 1.0;
1766 self.think = fd_secret_move2;
1767 if (self.noise3 != "")
1768 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1771 // Start moving sideways w/sound...
1772 void fd_secret_move2()
1774 if (self.noise2 != "")
1775 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1776 SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
1779 // Wait here until time to go back...
1780 void fd_secret_move3()
1782 if (self.noise3 != "")
1783 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1784 if (!(self.spawnflags & SECRET_OPEN_ONCE))
1786 self.nextthink = self.ltime + self.wait;
1787 self.think = fd_secret_move4;
1792 void fd_secret_move4()
1794 if (self.noise2 != "")
1795 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1796 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
1800 void fd_secret_move5()
1802 self.nextthink = self.ltime + 1.0;
1803 self.think = fd_secret_move6;
1804 if (self.noise3 != "")
1805 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1808 void fd_secret_move6()
1810 if (self.noise2 != "")
1811 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTN_NORM);
1812 SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
1815 void fd_secret_done()
1817 if (self.spawnflags&SECRET_YES_SHOOT)
1819 self.health = 10000;
1820 self.takedamage = DAMAGE_YES;
1821 //self.th_pain = fd_secret_use;
1823 if (self.noise3 != "")
1824 sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTN_NORM);
1827 void secret_blocked()
1829 if (time < self.attack_finished_single)
1831 self.attack_finished_single = time + 0.5;
1832 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1844 if not(other.iscreature)
1846 if (self.attack_finished_single > time)
1849 self.attack_finished_single = time + 2;
1853 if (other.flags & FL_CLIENT)
1854 centerprint (other, self.message);
1855 play2(other, "misc/talk.wav");
1861 if (self.spawnflags&SECRET_YES_SHOOT)
1863 self.health = 10000;
1864 self.takedamage = DAMAGE_YES;
1866 setorigin(self, self.oldorigin);
1867 self.think = SUB_Null;
1870 /*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
1871 Basic secret door. Slides back, then to the side. Angle determines direction.
1872 wait = # of seconds before coming back
1873 1st_left = 1st move is left of arrow
1874 1st_down = 1st move is down from arrow
1875 always_shoot = even if targeted, keep shootable
1876 t_width = override WIDTH to move back (or height if going down)
1877 t_length = override LENGTH to move sideways
1878 "dmg" damage to inflict when blocked (2 default)
1880 If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
1887 void spawnfunc_func_door_secret()
1889 /*if (!self.deathtype) // map makers can override this
1890 self.deathtype = " got in the way";*/
1896 self.mangle = self.angles;
1897 self.angles = '0 0 0';
1898 self.classname = "door";
1899 if not(InitMovingBrushTrigger())
1901 self.effects |= EF_LOWPRECISION;
1903 self.touch = secret_touch;
1904 self.blocked = secret_blocked;
1906 self.use = fd_secret_use;
1911 self.spawnflags |= SECRET_YES_SHOOT;
1913 if(self.spawnflags&SECRET_YES_SHOOT)
1915 self.health = 10000;
1916 self.takedamage = DAMAGE_YES;
1917 self.event_damage = fd_secret_use;
1919 self.oldorigin = self.origin;
1921 self.wait = 5; // 5 seconds before closing
1923 self.reset = secret_reset;
1927 /*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
1928 Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
1929 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
1930 speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
1931 height: amplitude modifier (default 32)
1932 phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
1933 noise: path/name of looping .wav file to play.
1934 dmg: Do this mutch dmg every .dmgtime intervall when blocked
1938 void func_fourier_controller_think()
1943 self.nextthink = time + 0.1;
1944 if not (self.owner.active == ACTIVE_ACTIVE)
1946 self.owner.velocity = '0 0 0';
1951 n = floor((tokenize_console(self.owner.netname)) / 5);
1952 t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
1954 v = self.owner.destvec;
1956 for(i = 0; i < n; ++i)
1958 makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
1959 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;
1962 if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
1963 // * 10 so it will arrive in 0.1 sec
1964 self.owner.velocity = (v - self.owner.origin) * 10;
1967 void spawnfunc_func_fourier()
1970 if (self.noise != "")
1972 precache_sound(self.noise);
1973 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
1980 self.destvec = self.origin;
1981 self.cnt = 360 / self.speed;
1983 self.blocked = generic_plat_blocked;
1984 if(self.dmg & (!self.message))
1985 self.message = " was squished";
1986 if(self.dmg && (!self.message2))
1987 self.message2 = "was squished by";
1988 if(self.dmg && (!self.dmgtime))
1989 self.dmgtime = 0.25;
1990 self.dmgtime2 = time;
1992 if(self.netname == "")
1993 self.netname = "1 0 0 0 1";
1995 if not(InitMovingBrushTrigger())
1998 self.active = ACTIVE_ACTIVE;
2000 // wait for targets to spawn
2001 controller = spawn();
2002 controller.classname = "func_fourier_controller";
2003 controller.owner = self;
2004 controller.nextthink = time + 1;
2005 controller.think = func_fourier_controller_think;
2006 self.nextthink = self.ltime + 999999999;
2007 self.think = SUB_Null;
2009 // Savage: Reduce bandwith, critical on e.g. nexdm02
2010 self.effects |= EF_LOWPRECISION;
2012 // TODO make a reset function for this one
2015 // reusing some fields havocbots declared
2016 .entity wp00, wp01, wp02, wp03;
2018 .float targetfactor, target2factor, target3factor, target4factor;
2019 .vector targetnormal, target2normal, target3normal, target4normal;
2021 vector func_vectormamamam_origin(entity o, float t)
2033 p = e.origin + t * e.velocity;
2035 v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
2037 v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
2043 p = e.origin + t * e.velocity;
2045 v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
2047 v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
2053 p = e.origin + t * e.velocity;
2055 v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
2057 v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
2063 p = e.origin + t * e.velocity;
2065 v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
2067 v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
2073 void func_vectormamamam_controller_think()
2075 self.nextthink = time + 0.1;
2077 if not (self.owner.active == ACTIVE_ACTIVE)
2079 self.owner.velocity = '0 0 0';
2083 if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
2084 self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
2087 void func_vectormamamam_findtarget()
2089 if(self.target != "")
2090 self.wp00 = find(world, targetname, self.target);
2092 if(self.target2 != "")
2093 self.wp01 = find(world, targetname, self.target2);
2095 if(self.target3 != "")
2096 self.wp02 = find(world, targetname, self.target3);
2098 if(self.target4 != "")
2099 self.wp03 = find(world, targetname, self.target4);
2101 if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
2102 objerror("No reference entity found, so there is nothing to move. Aborting.");
2104 self.destvec = self.origin - func_vectormamamam_origin(self.owner, 0);
2107 controller = spawn();
2108 controller.classname = "func_vectormamamam_controller";
2109 controller.owner = self;
2110 controller.nextthink = time + 1;
2111 controller.think = func_vectormamamam_controller_think;
2114 void spawnfunc_func_vectormamamam()
2116 if (self.noise != "")
2118 precache_sound(self.noise);
2119 soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTN_IDLE);
2122 if(!self.targetfactor)
2123 self.targetfactor = 1;
2125 if(!self.target2factor)
2126 self.target2factor = 1;
2128 if(!self.target3factor)
2129 self.target3factor = 1;
2131 if(!self.target4factor)
2132 self.target4factor = 1;
2134 if(vlen(self.targetnormal))
2135 self.targetnormal = normalize(self.targetnormal);
2137 if(vlen(self.target2normal))
2138 self.target2normal = normalize(self.target2normal);
2140 if(vlen(self.target3normal))
2141 self.target3normal = normalize(self.target3normal);
2143 if(vlen(self.target4normal))
2144 self.target4normal = normalize(self.target4normal);
2146 self.blocked = generic_plat_blocked;
2147 if(self.dmg & (!self.message))
2148 self.message = " was squished";
2149 if(self.dmg && (!self.message2))
2150 self.message2 = "was squished by";
2151 if(self.dmg && (!self.dmgtime))
2152 self.dmgtime = 0.25;
2153 self.dmgtime2 = time;
2155 if(self.netname == "")
2156 self.netname = "1 0 0 0 1";
2158 if not(InitMovingBrushTrigger())
2161 // wait for targets to spawn
2162 self.nextthink = self.ltime + 999999999;
2163 self.think = SUB_Null;
2165 // Savage: Reduce bandwith, critical on e.g. nexdm02
2166 self.effects |= EF_LOWPRECISION;
2168 self.active = ACTIVE_ACTIVE;
2170 InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
2173 void conveyor_think()
2177 // set myself as current conveyor where possible
2178 for(e = world; (e = findentity(e, conveyor, self)); )
2183 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2184 if(!e.conveyor.state)
2187 vector emin = e.absmin;
2188 vector emax = e.absmax;
2189 if(self.solid == SOLID_BSP)
2194 if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2195 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2199 for(e = world; (e = findentity(e, conveyor, self)); )
2201 if(e.flags & FL_CLIENT) // doing it via velocity has quite some advantages
2202 continue; // done in SV_PlayerPhysics
2204 setorigin(e, e.origin + self.movedir * sys_frametime);
2205 move_out_of_solid(e);
2206 UpdateCSQCProjectile(e);
2208 // stupid conveyor code
2209 tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2210 if(trace_fraction > 0)
2211 setorigin(e, trace_endpos);
2216 self.nextthink = time;
2221 self.state = !self.state;
2224 void conveyor_reset()
2226 self.state = (self.spawnflags & 1);
2229 void conveyor_init()
2233 self.movedir = self.movedir * self.speed;
2234 self.think = conveyor_think;
2235 self.nextthink = time;
2238 self.use = conveyor_use;
2239 self.reset = conveyor_reset;
2246 void spawnfunc_trigger_conveyor()
2253 void spawnfunc_func_conveyor()
2256 InitMovingBrushTrigger();
2257 self.movetype = MOVETYPE_NONE;