diff --git a/src/p_things.cpp b/src/p_things.cpp index 4012a58723..9fac1cd674 100644 --- a/src/p_things.cpp +++ b/src/p_things.cpp @@ -163,6 +163,85 @@ bool P_Thing_Move (int tid, AActor *source, int mapspot, bool fog) return false; } +// [MC] Was part of P_Thing_Projectile, now its own function for use in ZScript. +// Aims mobj at targ based on speed and targ's velocity. +void VelIntercept(AActor *targ, AActor *mobj, double speed, bool aimpitch = false, bool oldvel = false) +{ + if (targ == nullptr || mobj == nullptr) return; + + if (speed > 0 && !targ->Vel.isZero()) + { + DVector3 aim = mobj->Vec3To(targ); + aim.Z += targ->Height / 2; + // Aiming at the target's position some time in the future + // is basically just an application of the law of sines: + // a/sin(A) = b/sin(B) + // Thanks to all those on the notgod phorum for helping me + // with the math. I don't think I would have thought of using + // trig alone had I been left to solve it by myself. + + bool nolead = false; + DVector3 tvel = targ->Vel; + if (!(targ->flags & MF_NOGRAVITY) && targ->waterlevel < 3) + { // If the target is subject to gravity and not underwater, + // assume that it isn't moving vertically. Thanks to gravity, + // even if we did consider the vertical component of the target's + // velocity, we would still miss more often than not. + tvel.Z = 0.0; + nolead = !!(targ->Vel.X == 0 && targ->Vel.Y == 0); + } + if (!nolead) + { + double dist = aim.Length(); + double targspeed = tvel.Length(); + double ydotx = -aim | tvel; + double a = g_acos(clamp(ydotx / targspeed / dist, -1.0, 1.0)); + double multiplier = double(pr_leadtarget.Random2())*0.1 / 255 + 1.1; + double sinb = -clamp(targspeed*multiplier * g_sin(a) / speed, -1.0, 1.0); + DVector3 prevel = mobj->Vel; + // Use the cross product of two of the triangle's sides to get a + // rotation vector. + DVector3 rv(tvel ^ aim); + // The vector must be normalized. + rv.MakeUnit(); + // Now combine the rotation vector with angle b to get a rotation matrix. + DMatrix3x3 rm(rv, g_cos(g_asin(sinb)), sinb); + // And multiply the original aim vector with the matrix to get a + // new aim vector that leads the target. + DVector3 aimvec = rm * aim; + // And make the projectile follow that vector at the desired speed. + mobj->Vel = aimvec * (speed / dist); + mobj->AngleFromVel(); + if (oldvel) + { + mobj->Vel = prevel; + } + if (aimpitch) // [MC] Ripped right out of A_FaceMovementDirection + { + const DVector2 velocity = mobj->Vel.XY(); + mobj->Angles.Pitch = -VecToAngle(velocity.Length(), mobj->Vel.Z); + } + } + else + { + mobj->Angles.Yaw = mobj->AngleTo(targ); + mobj->Vel = aim.Resized(speed); + } + } +} + +DEFINE_ACTION_FUNCTION(AActor, VelIntercept) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_OBJECT_NOT_NULL(targ, AActor); + PARAM_FLOAT_DEF(speed); + PARAM_BOOL_DEF(aimpitch); + PARAM_BOOL_DEF(oldvel); + if (speed < 0) speed = self->Speed; + VelIntercept(targ, self, speed, aimpitch, oldvel); + return 0; +} + bool P_Thing_Projectile (int tid, AActor *source, int type, const char *type_name, DAngle angle, double speed, double vspeed, int dest, AActor *forcedest, int gravity, int newtid, bool leadTarget) @@ -244,58 +323,11 @@ bool P_Thing_Projectile (int tid, AActor *source, int type, const char *type_nam } mobj->target = spot; - if (targ != NULL) + if (targ != nullptr) { - DVector3 aim = mobj->Vec3To(targ); - aim.Z += targ->Height / 2; - - if (leadTarget && speed > 0 && !targ->Vel.isZero()) + if (leadTarget) { - // Aiming at the target's position some time in the future - // is basically just an application of the law of sines: - // a/sin(A) = b/sin(B) - // Thanks to all those on the notgod phorum for helping me - // with the math. I don't think I would have thought of using - // trig alone had I been left to solve it by myself. - - DVector3 tvel = targ->Vel; - if (!(targ->flags & MF_NOGRAVITY) && targ->waterlevel < 3) - { // If the target is subject to gravity and not underwater, - // assume that it isn't moving vertically. Thanks to gravity, - // even if we did consider the vertical component of the target's - // velocity, we would still miss more often than not. - tvel.Z = 0.0; - if (targ->Vel.X == 0 && targ->Vel.Y == 0) - { - goto nolead; - } - } - double dist = aim.Length(); - double targspeed = tvel.Length(); - double ydotx = -aim | tvel; - double a = g_acos (clamp (ydotx / targspeed / dist, -1.0, 1.0)); - double multiplier = double(pr_leadtarget.Random2())*0.1/255+1.1; - double sinb = -clamp (targspeed*multiplier * g_sin(a) / speed, -1.0, 1.0); - - // Use the cross product of two of the triangle's sides to get a - // rotation vector. - DVector3 rv(tvel ^ aim); - // The vector must be normalized. - rv.MakeUnit(); - // Now combine the rotation vector with angle b to get a rotation matrix. - DMatrix3x3 rm(rv, g_cos(g_asin(sinb)), sinb); - // And multiply the original aim vector with the matrix to get a - // new aim vector that leads the target. - DVector3 aimvec = rm * aim; - // And make the projectile follow that vector at the desired speed. - mobj->Vel = aimvec * (speed / dist); - mobj->AngleFromVel(); - } - else - { -nolead: - mobj->Angles.Yaw = mobj->AngleTo(targ); - mobj->Vel = aim.Resized (speed); + VelIntercept(targ, mobj, speed); } if (mobj->flags2 & MF2_SEEKERMISSILE) { diff --git a/wadsrc/static/zscript/actor.txt b/wadsrc/static/zscript/actor.txt index df284fc8ca..767b87e0f8 100644 --- a/wadsrc/static/zscript/actor.txt +++ b/wadsrc/static/zscript/actor.txt @@ -685,6 +685,7 @@ class Actor : Thinker native native clearscope vector2 Vec2Angle(double length, double angle, bool absolute = false) const; native clearscope vector2 Vec2Offset(double x, double y, bool absolute = false) const; native clearscope vector3 Vec2OffsetZ(double x, double y, double atz, bool absolute = false) const; + native void VelIntercept(Actor targ, double speed = -1, bool aimpitch = true, bool oldvel = false); native void VelFromAngle(double speed = 0, double angle = 0); native void Vel3DFromAngle(double speed, double angle, double pitch); native void Thrust(double speed = 0, double angle = 0);