4 void generic_plat_blocked()
6 if(self.dmg && other.takedamage != DAMAGE_NO) {
7 if(self.dmgtime2 < time) {
8 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
9 self.dmgtime2 = time + self.dmgtime;
12 // Gib dead/dying stuff
13 if(other.deadflag != DEAD_NO)
14 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
19 .entity trigger_field;
21 void() plat_center_touch;
22 void() plat_outside_touch;
23 void() plat_trigger_use;
27 const float PLAT_LOW_TRIGGER = 1;
29 void plat_spawn_inside_trigger()
35 trigger.touch = plat_center_touch;
36 trigger.movetype = MOVETYPE_NONE;
37 trigger.solid = SOLID_TRIGGER;
40 tmin = self.absmin + '25 25 0';
41 tmax = self.absmax - '25 25 -8';
42 tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8);
43 if (self.spawnflags & PLAT_LOW_TRIGGER)
46 if (self.size_x <= 50)
48 tmin_x = (self.mins_x + self.maxs_x) / 2;
51 if (self.size_y <= 50)
53 tmin_y = (self.mins_y + self.maxs_y) / 2;
61 setsize (trigger, tmin, tmax);
65 // otherwise, something is fishy...
67 objerror("plat_spawn_inside_trigger: platform has odd size or lip, can't spawn");
72 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
74 self.think = plat_go_down;
75 self.nextthink = self.ltime + 3;
78 void plat_hit_bottom()
80 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
86 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
88 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, plat_hit_bottom);
93 sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
95 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, plat_hit_top);
98 void plat_center_touch()
100 if (!other.iscreature)
103 if (other.health <= 0)
109 else if (self.state == 1)
110 self.nextthink = self.ltime + 1; // delay going down
113 void plat_outside_touch()
115 if (!other.iscreature)
118 if (other.health <= 0)
126 void plat_trigger_use()
129 return; // already activated
136 if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
137 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
139 if((self.dmg) && (other.takedamage != DAMAGE_NO)) { // Shall we bite?
140 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
141 // Gib dead/dying stuff
142 if(other.deadflag != DEAD_NO)
143 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
148 else if (self.state == 3)
150 // when in other states, then the plat_crush event came delayed after
151 // plat state already had changed
152 // this isn't a bug per se!
158 self.use = func_null;
160 objerror ("plat_use: not in up state");
164 .string sound1, sound2;
170 setorigin (self, self.pos1);
176 setorigin (self, self.pos2);
178 self.use = plat_trigger_use;
182 .float platmovetype_start_default, platmovetype_end_default;
183 float set_platmovetype(entity e, string s)
185 // sets platmovetype_start and platmovetype_end based on a string consisting of two values
188 n = tokenize_console(s);
190 e.platmovetype_start = stof(argv(0));
192 e.platmovetype_start = 0;
195 e.platmovetype_end = stof(argv(1));
197 e.platmovetype_end = e.platmovetype_start;
200 if(argv(2) == "force")
201 return TRUE; // no checking, return immediately
203 if(!cubic_speedfunc_is_sane(e.platmovetype_start, e.platmovetype_end))
205 objerror("Invalid platform move type; platform would go in reverse, which is not allowed.");
212 void spawnfunc_path_corner()
214 // setup values for overriding train movement
215 // if a second value does not exist, both start and end speeds are the single value specified
216 if(!set_platmovetype(self, self.platmovetype))
219 void spawnfunc_func_plat()
221 if (self.sounds == 0)
224 if(self.spawnflags & 4)
227 if(self.dmg && (self.message == ""))
228 self.message = "was squished";
229 if(self.dmg && (self.message2 == ""))
230 self.message2 = "was squished by";
232 if (self.sounds == 1)
234 precache_sound ("plats/plat1.wav");
235 precache_sound ("plats/plat2.wav");
236 self.noise = "plats/plat1.wav";
237 self.noise1 = "plats/plat2.wav";
240 if (self.sounds == 2)
242 precache_sound ("plats/medplat1.wav");
243 precache_sound ("plats/medplat2.wav");
244 self.noise = "plats/medplat1.wav";
245 self.noise1 = "plats/medplat2.wav";
250 precache_sound (self.sound1);
251 self.noise = self.sound1;
255 precache_sound (self.sound2);
256 self.noise1 = self.sound2;
259 self.mangle = self.angles;
260 self.angles = '0 0 0';
262 self.classname = "plat";
263 if (!InitMovingBrushTrigger())
265 self.effects |= EF_LOWPRECISION;
266 setsize (self, self.mins , self.maxs);
268 self.blocked = plat_crush;
275 self.height = self.size_z - self.lip;
277 self.pos1 = self.origin;
278 self.pos2 = self.origin;
279 self.pos2_z = self.origin_z - self.height;
281 self.reset = plat_reset;
284 plat_spawn_inside_trigger (); // the "start moving" trigger
287 .float train_wait_turning;
298 // if turning is enabled, the train will turn toward the next point while waiting
299 if(self.platmovetype_turn && !self.train_wait_turning)
303 targ = find(world, targetname, self.target);
304 if((self.spawnflags & 1) && targ.curvetarget)
305 cp = find(world, targetname, targ.curvetarget);
309 if(cp) // bezier curves movement
310 ang = cp.origin - (self.origin - self.view_ofs); // use the origin of the control point of the next path_corner
311 else // linear movement
312 ang = targ.origin - (self.origin - self.view_ofs); // use the origin of the next path_corner
313 ang = vectoangles(ang);
314 ang_x = -ang_x; // flip up / down orientation
316 if(self.wait > 0) // slow turning
317 SUB_CalcAngleMove(ang, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
318 else // instant turning
319 SUB_CalcAngleMove(ang, TSPEED_TIME, 0.0000001, train_wait);
320 self.train_wait_turning = TRUE;
325 stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
327 if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
329 self.train_wait_turning = FALSE;
334 self.think = train_next;
335 self.nextthink = self.ltime + self.wait;
341 entity targ, cp = world;
342 vector cp_org = '0 0 0';
344 targ = find(world, targetname, self.target);
345 self.target = targ.target;
346 if (self.spawnflags & 1)
350 cp = find(world, targetname, targ.curvetarget); // get its second target (the control point)
351 cp_org = cp.origin - self.view_ofs; // no control point found, assume a straight line to the destination
354 if (self.target == "")
355 objerror("train_next: no next target");
356 self.wait = targ.wait;
360 if(targ.platmovetype)
362 // this path_corner contains a movetype overrider, apply it
363 self.platmovetype_start = targ.platmovetype_start;
364 self.platmovetype_end = targ.platmovetype_end;
368 // this path_corner doesn't contain a movetype overrider, use the train's defaults
369 self.platmovetype_start = self.platmovetype_start_default;
370 self.platmovetype_end = self.platmovetype_end_default;
376 SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
378 SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
383 SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
385 SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
389 sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
392 void func_train_find()
395 targ = find(world, targetname, self.target);
396 self.target = targ.target;
397 if (self.target == "")
398 objerror("func_train_find: no next target");
399 setorigin(self, targ.origin - self.view_ofs);
400 self.nextthink = self.ltime + 1;
401 self.think = train_next;
404 /*QUAKED spawnfunc_func_train (0 .5 .8) ?
405 Ridable platform, targets spawnfunc_path_corner path to follow.
406 speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
407 target : targetname of first spawnfunc_path_corner (starts here)
409 void spawnfunc_func_train()
411 if (self.noise != "")
412 precache_sound(self.noise);
414 if (self.target == "")
415 objerror("func_train without a target");
419 if (!InitMovingBrushTrigger())
421 self.effects |= EF_LOWPRECISION;
423 if (self.spawnflags & 2)
425 self.platmovetype_turn = TRUE;
426 self.view_ofs = '0 0 0'; // don't offset a rotating train, origin works differently now
429 self.view_ofs = self.mins;
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, ATTEN_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 (!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_NullThink; // for PushMove
524 // TODO make a reset function for this one
528 void func_bobbing_controller_think()
531 self.nextthink = time + 0.1;
533 if(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, ATTEN_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 (!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_NullThink; // for PushMove
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 (!(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, ATTEN_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 (!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_NullThink; // for PushMove
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, ATTEN_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(self.active != ACTIVE_ACTIVE)
753 self.enemy = activator;
761 if (!other.iscreature)
763 if(other.velocity * self.movedir < 0)
767 self.enemy = other.owner;
771 void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
773 if(self.spawnflags & DOOR_NOSPLASH)
774 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
776 self.health = self.health - damage;
777 if (self.health <= 0)
779 self.enemy = damage_attacker;
785 /*QUAKED spawnfunc_func_button (0 .5 .8) ?
786 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.
788 "angle" determines the opening direction
789 "target" all entities with a matching targetname will be used
790 "speed" override the default 40 speed
791 "wait" override the default 1 second wait (-1 = never return)
792 "lip" override the default 4 pixel lip remaining at end of move
793 "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 InstaGib laser
800 void spawnfunc_func_button()
804 if (!InitMovingBrushTrigger())
806 self.effects |= EF_LOWPRECISION;
808 self.blocked = button_blocked;
809 self.use = button_use;
811 // if (self.health == 0) // all buttons are now shootable
815 self.max_health = self.health;
816 self.event_damage = button_damage;
817 self.takedamage = DAMAGE_YES;
820 self.touch = button_touch;
830 precache_sound(self.noise);
832 self.active = ACTIVE_ACTIVE;
834 self.pos1 = self.origin;
835 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
836 self.flags |= FL_NOTARGET;
842 const float DOOR_START_OPEN = 1;
843 const float DOOR_DONT_LINK = 4;
844 const float DOOR_TOGGLE = 32;
848 Doors are similar to buttons, but can spawn a fat trigger field around them
849 to open without a touch, and they link together to form simultanious
852 Door.owner is the master door. If there is only one door, it points to itself.
853 If multiple doors, all will point to a single one.
855 Door.enemy chains from the master door through all doors linked in the chain.
860 =============================================================================
864 =============================================================================
869 void() door_rotating_go_down;
870 void() door_rotating_go_up;
875 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
876 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
879 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
880 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
882 //Dont chamge direction for dead or dying stuff
883 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
886 if (self.state == STATE_DOWN)
887 if (self.classname == "door")
892 door_rotating_go_up ();
895 if (self.classname == "door")
900 door_rotating_go_down ();
904 //gib dying stuff just to make sure
905 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
906 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
910 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
911 // if a door has a negative wait, it would never come back if blocked,
912 // so let it just squash the object to death real fast
913 /* if (self.wait >= 0)
915 if (self.state == STATE_DOWN)
926 if (self.noise1 != "")
927 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
928 self.state = STATE_TOP;
929 if (self.spawnflags & DOOR_TOGGLE)
930 return; // don't come down automatically
931 if (self.classname == "door")
933 self.think = door_go_down;
936 self.think = door_rotating_go_down;
938 self.nextthink = self.ltime + self.wait;
941 void door_hit_bottom()
943 if (self.noise1 != "")
944 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
945 self.state = STATE_BOTTOM;
950 if (self.noise2 != "")
951 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
954 self.takedamage = DAMAGE_YES;
955 self.health = self.max_health;
958 self.state = STATE_DOWN;
959 SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
964 if (self.state == STATE_UP)
965 return; // already going up
967 if (self.state == STATE_TOP)
968 { // reset top wait time
969 self.nextthink = self.ltime + self.wait;
973 if (self.noise2 != "")
974 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
975 self.state = STATE_UP;
976 SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
979 oldmessage = self.message;
982 self.message = oldmessage;
988 =============================================================================
992 =============================================================================
995 float door_check_keys(void) {
1008 // this door require a key
1009 // only a player can have a key
1010 if (!IS_PLAYER(other))
1013 if (item_keys_usekey(door, other)) {
1014 // some keys were used
1015 if (other.key_door_messagetime <= time) {
1016 play2(other, "misc/talk.wav");
1017 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
1018 other.key_door_messagetime = time + 2;
1021 // no keys were used
1022 if (other.key_door_messagetime <= time) {
1023 play2(other, "misc/talk.wav");
1024 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
1025 other.key_door_messagetime = time + 2;
1029 if (door.itemkeys) {
1030 // door is now unlocked
1031 play2(other, "misc/talk.wav");
1032 Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_UNLOCKED);
1044 if (self.owner != self)
1045 objerror ("door_fire: self.owner != self");
1049 if (self.spawnflags & DOOR_TOGGLE)
1051 if (self.state == STATE_UP || self.state == STATE_TOP)
1056 if (self.classname == "door")
1062 door_rotating_go_down ();
1065 } while ( (self != starte) && (self != world) );
1071 // trigger all paired doors
1075 if (self.classname == "door")
1080 // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
1081 if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
1083 self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
1084 self.pos2 = '0 0 0' - self.pos2;
1086 // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
1087 if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
1088 && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
1090 door_rotating_go_up ();
1094 } while ( (self != starte) && (self != world) );
1103 //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
1115 void door_trigger_touch()
1117 if (other.health < 1)
1118 if (!(other.iscreature && other.deadflag == DEAD_NO))
1121 if (time < self.attack_finished_single)
1124 // check if door is locked
1125 if (!door_check_keys())
1128 self.attack_finished_single = time + 1;
1137 void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
1140 if(self.spawnflags & DOOR_NOSPLASH)
1141 if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
1143 self.health = self.health - damage;
1145 if (self.itemkeys) {
1146 // don't allow opening doors through damage if keys are required
1150 if (self.health <= 0)
1154 self.health = self.max_health;
1155 self.takedamage = DAMAGE_NO; // wil be reset upon return
1171 if (!IS_PLAYER(other))
1173 if (self.owner.attack_finished_single > time)
1176 self.owner.attack_finished_single = time + 2;
1178 if (!(self.owner.dmg) && (self.owner.message != ""))
1180 if (IS_CLIENT(other))
1181 centerprint(other, self.owner.message);
1182 play2(other, "misc/talk.wav");
1187 void door_generic_plat_blocked()
1190 if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
1191 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1194 if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
1195 Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1197 //Dont chamge direction for dead or dying stuff
1198 if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
1201 if (self.state == STATE_DOWN)
1202 door_rotating_go_up ();
1204 door_rotating_go_down ();
1207 //gib dying stuff just to make sure
1208 if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
1209 Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
1213 //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
1214 // if a door has a negative wait, it would never come back if blocked,
1215 // so let it just squash the object to death real fast
1216 /* if (self.wait >= 0)
1218 if (self.state == STATE_DOWN)
1219 door_rotating_go_up ();
1221 door_rotating_go_down ();
1227 void door_rotating_hit_top()
1229 if (self.noise1 != "")
1230 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1231 self.state = STATE_TOP;
1232 if (self.spawnflags & DOOR_TOGGLE)
1233 return; // don't come down automatically
1234 self.think = door_rotating_go_down;
1235 self.nextthink = self.ltime + self.wait;
1238 void door_rotating_hit_bottom()
1240 if (self.noise1 != "")
1241 sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1242 if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
1244 self.pos2 = '0 0 0' - self.pos2;
1247 self.state = STATE_BOTTOM;
1250 void door_rotating_go_down()
1252 if (self.noise2 != "")
1253 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1254 if (self.max_health)
1256 self.takedamage = DAMAGE_YES;
1257 self.health = self.max_health;
1260 self.state = STATE_DOWN;
1261 SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
1264 void door_rotating_go_up()
1266 if (self.state == STATE_UP)
1267 return; // already going up
1269 if (self.state == STATE_TOP)
1270 { // reset top wait time
1271 self.nextthink = self.ltime + self.wait;
1274 if (self.noise2 != "")
1275 sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1276 self.state = STATE_UP;
1277 SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
1280 oldmessage = self.message;
1283 self.message = oldmessage;
1290 =============================================================================
1294 =============================================================================
1298 entity spawn_field(vector fmins, vector fmaxs)
1304 trigger.classname = "doortriggerfield";
1305 trigger.movetype = MOVETYPE_NONE;
1306 trigger.solid = SOLID_TRIGGER;
1307 trigger.owner = self;
1308 trigger.touch = door_trigger_touch;
1312 setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
1317 entity LinkDoors_nextent(entity cur, entity near, entity pass)
1319 while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
1325 float LinkDoors_isconnected(entity e1, entity e2, entity pass)
1328 if (e1.absmin_x > e2.absmax_x + DELTA)
1330 if (e1.absmin_y > e2.absmax_y + DELTA)
1332 if (e1.absmin_z > e2.absmax_z + DELTA)
1334 if (e2.absmin_x > e1.absmax_x + DELTA)
1336 if (e2.absmin_y > e1.absmax_y + DELTA)
1338 if (e2.absmin_z > e1.absmax_z + DELTA)
1353 vector cmins, cmaxs;
1356 return; // already linked by another door
1357 if (self.spawnflags & 4)
1359 self.owner = self.enemy = self;
1367 self.trigger_field = spawn_field(self.absmin, self.absmax);
1369 return; // don't want to link this door
1372 FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
1374 // set owner, and make a loop of the chain
1375 dprint("LinkDoors: linking doors:");
1376 for(t = self; ; t = t.enemy)
1378 dprint(" ", etos(t));
1380 if(t.enemy == world)
1388 // collect health, targetname, message, size
1389 cmins = self.absmin;
1390 cmaxs = self.absmax;
1391 for(t = self; ; t = t.enemy)
1393 if(t.health && !self.health)
1394 self.health = t.health;
1395 if((t.targetname != "") && (self.targetname == ""))
1396 self.targetname = t.targetname;
1397 if((t.message != "") && (self.message == ""))
1398 self.message = t.message;
1399 if (t.absmin_x < cmins_x)
1400 cmins_x = t.absmin_x;
1401 if (t.absmin_y < cmins_y)
1402 cmins_y = t.absmin_y;
1403 if (t.absmin_z < cmins_z)
1404 cmins_z = t.absmin_z;
1405 if (t.absmax_x > cmaxs_x)
1406 cmaxs_x = t.absmax_x;
1407 if (t.absmax_y > cmaxs_y)
1408 cmaxs_y = t.absmax_y;
1409 if (t.absmax_z > cmaxs_z)
1410 cmaxs_z = t.absmax_z;
1415 // distribute health, targetname, message
1416 for(t = self; t; t = t.enemy)
1418 t.health = self.health;
1419 t.targetname = self.targetname;
1420 t.message = self.message;
1425 // shootable, or triggered doors just needed the owner/enemy links,
1426 // they don't spawn a field
1435 self.trigger_field = spawn_field(cmins, cmaxs);
1439 /*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
1440 if two doors touch, they are assumed to be connected and operate as a unit.
1442 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1444 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).
1446 GOLD_KEY causes the door to open only if the activator holds a gold key.
1448 SILVER_KEY causes the door to open only if the activator holds a silver key.
1450 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1451 "angle" determines the opening direction
1452 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1453 "health" if set, door must be shot open
1454 "speed" movement speed (100 default)
1455 "wait" wait before returning (3 default, -1 = never return)
1456 "lip" lip remaining at end of move (8 default)
1457 "dmg" damage to inflict when blocked (2 default)
1464 FIXME: only one sound set available at the time being
1468 void door_init_startopen()
1470 setorigin (self, self.pos2);
1471 self.pos2 = self.pos1;
1472 self.pos1 = self.origin;
1477 setorigin(self, self.pos1);
1478 self.velocity = '0 0 0';
1479 self.state = STATE_BOTTOM;
1480 self.think = func_null;
1484 // spawnflags require key (for now only func_door)
1485 #define SPAWNFLAGS_GOLD_KEY 8
1486 #define SPAWNFLAGS_SILVER_KEY 16
1487 void spawnfunc_func_door()
1489 // Quake 1 keys compatibility
1490 if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
1491 self.itemkeys |= ITEM_KEY_BIT(0);
1492 if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
1493 self.itemkeys |= ITEM_KEY_BIT(1);
1495 //if (!self.deathtype) // map makers can override this
1496 // self.deathtype = " got in the way";
1499 self.max_health = self.health;
1500 if (!InitMovingBrushTrigger())
1502 self.effects |= EF_LOWPRECISION;
1503 self.classname = "door";
1505 self.blocked = door_blocked;
1506 self.use = door_use;
1508 // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
1509 // if(self.spawnflags & 8)
1510 // self.dmg = 10000;
1512 if(self.dmg && (self.message == ""))
1513 self.message = "was squished";
1514 if(self.dmg && (self.message2 == ""))
1515 self.message2 = "was squished by";
1517 if (self.sounds > 0)
1519 precache_sound ("plats/medplat1.wav");
1520 precache_sound ("plats/medplat2.wav");
1521 self.noise2 = "plats/medplat1.wav";
1522 self.noise1 = "plats/medplat2.wav";
1532 self.pos1 = self.origin;
1533 self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
1535 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1536 // but spawn in the open position
1537 if (self.spawnflags & DOOR_START_OPEN)
1538 InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
1540 self.state = STATE_BOTTOM;
1544 self.takedamage = DAMAGE_YES;
1545 self.event_damage = door_damage;
1551 self.touch = door_touch;
1553 // LinkDoors can't be done until all of the doors have been spawned, so
1554 // the sizes can be detected properly.
1555 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1557 self.reset = door_reset;
1560 /*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
1561 if two doors touch, they are assumed to be connected and operate as a unit.
1563 TOGGLE causes the door to wait in both the start and end states for a trigger event.
1565 BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
1566 The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
1567 must have set trigger_reverse to 1.
1568 BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
1570 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).
1572 "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
1573 "angle" determines the destination angle for opening. negative values reverse the direction.
1574 "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
1575 "health" if set, door must be shot open
1576 "speed" movement speed (100 default)
1577 "wait" wait before returning (3 default, -1 = never return)
1578 "dmg" damage to inflict when blocked (2 default)
1585 FIXME: only one sound set available at the time being
1588 void door_rotating_reset()
1590 self.angles = self.pos1;
1591 self.avelocity = '0 0 0';
1592 self.state = STATE_BOTTOM;
1593 self.think = func_null;
1597 void door_rotating_init_startopen()
1599 self.angles = self.movedir;
1600 self.pos2 = '0 0 0';
1601 self.pos1 = self.movedir;
1605 void spawnfunc_func_door_rotating()
1608 //if (!self.deathtype) // map makers can override this
1609 // self.deathtype = " got in the way";
1611 // I abuse "movedir" for denoting the axis for now
1612 if (self.spawnflags & 64) // X (untested)
1613 self.movedir = '0 0 1';
1614 else if (self.spawnflags & 128) // Y (untested)
1615 self.movedir = '1 0 0';
1617 self.movedir = '0 1 0';
1619 if (self.angles_y==0) self.angles_y = 90;
1621 self.movedir = self.movedir * self.angles_y;
1622 self.angles = '0 0 0';
1624 self.max_health = self.health;
1625 self.avelocity = self.movedir;
1626 if (!InitMovingBrushTrigger())
1628 self.velocity = '0 0 0';
1629 //self.effects |= EF_LOWPRECISION;
1630 self.classname = "door_rotating";
1632 self.blocked = door_blocked;
1633 self.use = door_use;
1635 if(self.spawnflags & 8)
1638 if(self.dmg && (self.message == ""))
1639 self.message = "was squished";
1640 if(self.dmg && (self.message2 == ""))
1641 self.message2 = "was squished by";
1643 if (self.sounds > 0)
1645 precache_sound ("plats/medplat1.wav");
1646 precache_sound ("plats/medplat2.wav");
1647 self.noise2 = "plats/medplat1.wav";
1648 self.noise1 = "plats/medplat2.wav";
1655 self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
1657 self.pos1 = '0 0 0';
1658 self.pos2 = self.movedir;
1660 // DOOR_START_OPEN is to allow an entity to be lighted in the closed position
1661 // but spawn in the open position
1662 if (self.spawnflags & DOOR_START_OPEN)
1663 InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
1665 self.state = STATE_BOTTOM;
1669 self.takedamage = DAMAGE_YES;
1670 self.event_damage = door_damage;
1676 self.touch = door_touch;
1678 // LinkDoors can't be done until all of the doors have been spawned, so
1679 // the sizes can be detected properly.
1680 InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
1682 self.reset = door_rotating_reset;
1686 =============================================================================
1690 =============================================================================
1693 void() fd_secret_move1;
1694 void() fd_secret_move2;
1695 void() fd_secret_move3;
1696 void() fd_secret_move4;
1697 void() fd_secret_move5;
1698 void() fd_secret_move6;
1699 void() fd_secret_done;
1701 const float SECRET_OPEN_ONCE = 1; // stays open
1702 const float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
1703 const float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
1704 const float SECRET_NO_SHOOT = 8; // only opened by trigger
1705 const float SECRET_YES_SHOOT = 16; // shootable even if targeted
1707 void fd_secret_use()
1710 string message_save;
1712 self.health = 10000;
1713 self.bot_attack = TRUE;
1715 // exit if still moving around...
1716 if (self.origin != self.oldorigin)
1719 message_save = self.message;
1720 self.message = ""; // no more message
1721 SUB_UseTargets(); // fire all targets / killtargets
1722 self.message = message_save;
1724 self.velocity = '0 0 0';
1726 // Make a sound, wait a little...
1728 if (self.noise1 != "")
1729 sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
1730 self.nextthink = self.ltime + 0.1;
1732 temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
1733 makevectors(self.mangle);
1737 if (self.spawnflags & SECRET_1ST_DOWN)
1738 self.t_width = fabs(v_up * self.size);
1740 self.t_width = fabs(v_right * self.size);
1744 self.t_length = fabs(v_forward * self.size);
1746 if (self.spawnflags & SECRET_1ST_DOWN)
1747 self.dest1 = self.origin - v_up * self.t_width;
1749 self.dest1 = self.origin + v_right * (self.t_width * temp);
1751 self.dest2 = self.dest1 + v_forward * self.t_length;
1752 SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
1753 if (self.noise2 != "")
1754 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
1757 void fd_secret_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
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, ATTEN_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, ATTEN_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, ATTEN_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, ATTEN_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, ATTEN_NORM);
1808 void fd_secret_move6()
1810 if (self.noise2 != "")
1811 sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_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, ATTEN_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 (!other.iscreature)
1846 if (self.attack_finished_single > time)
1849 self.attack_finished_single = time + 2;
1853 if (IS_CLIENT(other))
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 = func_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 (!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_damage;
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(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, ATTEN_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 (!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_NullThink; // for PushMove
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(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, 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, ATTEN_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.message == ""))
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 (!InitMovingBrushTrigger())
2162 // wait for targets to spawn
2163 self.nextthink = self.ltime + 999999999;
2164 self.think = SUB_NullThink; // for PushMove
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);
2176 void conveyor_think()
2179 float dt = time - self.move_time;
2180 self.move_time = time;
2181 if(dt <= 0) { return; }
2185 // set myself as current conveyor where possible
2186 for(e = world; (e = findentity(e, conveyor, self)); )
2191 for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
2192 if(!e.conveyor.state)
2199 vector emin = e.absmin;
2200 vector emax = e.absmax;
2201 if(self.solid == SOLID_BSP)
2206 if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
2207 if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
2211 for(e = world; (e = findentity(e, conveyor, self)); )
2214 if(IS_CLIENT(e)) // doing it via velocity has quite some advantages
2215 continue; // done in SV_PlayerPhysics
2221 setorigin(e, e.origin + self.movedir * PHYS_INPUT_FRAMETIME);
2222 move_out_of_solid(e);
2224 UpdateCSQCProjectile(e);
2227 // stupid conveyor code
2228 tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
2229 if(trace_fraction > 0)
2230 setorigin(e, trace_endpos);
2236 self.nextthink = time;
2244 self.state = !self.state;
2246 self.SendFlags |= 2;
2249 void conveyor_reset()
2251 self.state = (self.spawnflags & 1);
2253 self.SendFlags |= 2;
2256 float conveyor_send(entity to, float sf)
2258 WriteByte(MSG_ENTITY, ENT_CLIENT_CONVEYOR);
2259 WriteByte(MSG_ENTITY, sf);
2263 WriteByte(MSG_ENTITY, self.warpzone_isboxy);
2264 WriteCoord(MSG_ENTITY, self.origin_x);
2265 WriteCoord(MSG_ENTITY, self.origin_y);
2266 WriteCoord(MSG_ENTITY, self.origin_z);
2268 WriteCoord(MSG_ENTITY, self.mins_x);
2269 WriteCoord(MSG_ENTITY, self.mins_y);
2270 WriteCoord(MSG_ENTITY, self.mins_z);
2271 WriteCoord(MSG_ENTITY, self.maxs_x);
2272 WriteCoord(MSG_ENTITY, self.maxs_y);
2273 WriteCoord(MSG_ENTITY, self.maxs_z);
2275 WriteCoord(MSG_ENTITY, self.movedir_x);
2276 WriteCoord(MSG_ENTITY, self.movedir_y);
2277 WriteCoord(MSG_ENTITY, self.movedir_z);
2279 WriteByte(MSG_ENTITY, self.speed);
2280 WriteByte(MSG_ENTITY, self.state);
2282 WriteString(MSG_ENTITY, self.targetname);
2283 WriteString(MSG_ENTITY, self.target);
2287 WriteByte(MSG_ENTITY, self.state);
2292 void conveyor_init()
2296 self.movedir = self.movedir * self.speed;
2297 self.think = conveyor_think;
2298 self.nextthink = time;
2301 self.use = conveyor_use;
2302 self.reset = conveyor_reset;
2308 Net_LinkEntity(self, 0, FALSE, conveyor_send);
2310 self.SendFlags |= 1;
2313 void spawnfunc_trigger_conveyor()
2320 void spawnfunc_func_conveyor()
2323 InitMovingBrushTrigger();
2324 self.movetype = MOVETYPE_NONE;
2330 void conveyor_init()
2332 self.draw = conveyor_think;
2333 self.drawmask = MASK_NORMAL;
2335 self.movetype = MOVETYPE_NONE;
2337 self.solid = SOLID_TRIGGER;
2338 self.move_origin = self.origin;
2339 self.move_time = time;
2344 float sf = ReadByte();
2348 self.warpzone_isboxy = ReadByte();
2349 self.origin_x = ReadCoord();
2350 self.origin_y = ReadCoord();
2351 self.origin_z = ReadCoord();
2352 setorigin(self, self.origin);
2354 self.mins_x = ReadCoord();
2355 self.mins_y = ReadCoord();
2356 self.mins_z = ReadCoord();
2357 self.maxs_x = ReadCoord();
2358 self.maxs_y = ReadCoord();
2359 self.maxs_z = ReadCoord();
2360 setsize(self, self.mins, self.maxs);
2362 self.movedir_x = ReadCoord();
2363 self.movedir_y = ReadCoord();
2364 self.movedir_z = ReadCoord();
2366 self.speed = ReadByte();
2367 self.state = ReadByte();
2369 self.targetname = strzone(ReadString());
2370 self.target = strzone(ReadString());
2376 self.state = ReadByte();