diff --git a/spt.vcxproj b/spt.vcxproj index 1ee0305f2..62f80503b 100644 --- a/spt.vcxproj +++ b/spt.vcxproj @@ -1223,6 +1223,7 @@ del "$(OutDir)spt-version.obj" + diff --git a/spt.vcxproj.filters b/spt.vcxproj.filters index 595b293a9..a992be5d0 100644 --- a/spt.vcxproj.filters +++ b/spt.vcxproj.filters @@ -391,6 +391,9 @@ spt\features + + spt\features + diff --git a/spt/features/aim.cpp b/spt/features/aim.cpp index d84a61b48..12edbc6ef 100644 --- a/spt/features/aim.cpp +++ b/spt/features/aim.cpp @@ -5,6 +5,7 @@ #include "ent_utils.hpp" #include "math.hpp" #include "playerio.hpp" +#include "signals.hpp" #undef min #undef max @@ -330,7 +331,7 @@ CON_COMMAND(_y_spt_setangle, void AimFeature::LoadFeature() { - if (spt_generic.ORIG_ControllerMove && spt_playerio.ORIG_CreateMove) + if (spt_generic.ORIG_ControllerMove && CreateMoveSignal.Works) { InitCommand(tas_aim_reset); InitCommand(tas_aim); diff --git a/spt/features/ihud.cpp b/spt/features/ihud.cpp index f6f4f3847..7813703c7 100644 --- a/spt/features/ihud.cpp +++ b/spt/features/ihud.cpp @@ -70,13 +70,10 @@ class InputHud : public FeatureWrapper virtual void LoadFeature() override; - virtual void PreHook() override; - virtual void UnloadFeature() override; private: - DECL_HOOK_THISCALL(void, DecodeUserCmdFromBuffer, void*, bf_read& buf, int sequence_number); - void CreateMove(uintptr_t pCmd); + void SetData(uintptr_t pCmd); void DrawRectAndCenterTxt(Color buttonColor, int x0, int y0, @@ -101,8 +98,6 @@ class InputHud : public FeatureWrapper int buttonBits = 0; bool awaitingFrameDraw = false; - - bool loadingSuccessful = false; }; InputHud spt_ihud; @@ -564,22 +559,7 @@ CON_COMMAND(y_spt_ihud_add_key, "Add custom key to ihud.") } #endif -namespace patterns -{ - PATTERNS( - DecodeUserCmdFromBuffer, - "5135", - "83 EC 54 33 C0 D9 EE 89 44 24 ?? D9 54 24 ?? 89 44 24 ??", - "7197370", - "55 8B EC 83 EC 54 56 8B F1 C7 45 ?? ?? ?? ?? ?? 8D 4D ?? C7 45 ?? 00 00 00 00 C7 45 ?? 00 00 00 00 C7 45 ?? 00 00 00 00 C7 45 ?? 00 00 00 00 C7 45 ?? 00 00 00 00 E8 ?? ?? ?? ?? 8B 4D ??", - "4044", - "83 EC 54 53 57 8D 44 24 ?? 50 8B 44 24 ?? 99"); -} - -void InputHud::InitHooks() -{ - HOOK_FUNCTION(client, DecodeUserCmdFromBuffer); -} +void InputHud::InitHooks() {} bool InputHud::ShouldLoadFeature() { @@ -588,10 +568,11 @@ bool InputHud::ShouldLoadFeature() void InputHud::LoadFeature() { - if (!loadingSuccessful) + if (!(CreateMoveSignal.Works && DecodeUserCmdFromBufferSignal.Works)) return; - if (CreateMoveSignal.Works) - CreateMoveSignal.Connect(this, &InputHud::CreateMove); + + CreateMoveSignal.Connect(this, &InputHud::SetData); + DecodeUserCmdFromBufferSignal.Connect(this, &InputHud::SetData); bool result = spt_hud_feat.AddHudDefaultGroup(HudCallback( std::bind(&InputHud::DrawInputHud, this), []() { return y_spt_ihud.GetBool(); }, false)); @@ -664,27 +645,8 @@ void InputHud::LoadFeature() anglesSetting = {true, false, L"", font, 4, 1, 0, 0, background, highlight, textcolor, texthighlight, 0}; } -void InputHud::PreHook() -{ - loadingSuccessful = !!(ORIG_DecodeUserCmdFromBuffer); -} - void InputHud::UnloadFeature() {} -void InputHud::SetInputInfo(int button, Vector movement) -{ - if (awaitingFrameDraw) - { - buttonBits |= button; - } - else - { - buttonBits = button; - } - inputMovement = movement; - awaitingFrameDraw = true; -} - bool InputHud::ModifySetting(const char* element, const char* param, const char* value) { InputHud::Button* target; @@ -1057,21 +1019,20 @@ void InputHud::DrawButton(Button button) button.text.c_str()); } -IMPL_HOOK_THISCALL(InputHud, void, DecodeUserCmdFromBuffer, void*, bf_read& buf, int sequence_number) +void InputHud::SetData(uintptr_t pCmd) { - spt_ihud.ORIG_DecodeUserCmdFromBuffer(thisptr, buf, sequence_number); - - auto m_pCommands = - *reinterpret_cast(reinterpret_cast(thisptr) + spt_playerio.offM_pCommands); - auto pCmd = m_pCommands + spt_playerio.sizeofCUserCmd * (sequence_number % 90); auto cmd = reinterpret_cast(pCmd); - spt_ihud.SetInputInfo(cmd->buttons, Vector(cmd->sidemove, cmd->forwardmove, cmd->upmove)); -} -void InputHud::CreateMove(uintptr_t pCmd) -{ - auto cmd = reinterpret_cast(pCmd); - spt_ihud.SetInputInfo(cmd->buttons, Vector(cmd->sidemove, cmd->forwardmove, cmd->upmove)); + if (awaitingFrameDraw) + { + buttonBits |= cmd->buttons; + } + else + { + buttonBits = cmd->buttons; + } + inputMovement = Vector(cmd->sidemove, cmd->forwardmove, cmd->upmove); + awaitingFrameDraw = true; } std::string InputHud::Button::ToString() const diff --git a/spt/features/playerio.cpp b/spt/features/playerio.cpp index f5974a0d1..113e1cdf3 100644 --- a/spt/features/playerio.cpp +++ b/spt/features/playerio.cpp @@ -97,6 +97,14 @@ namespace patterns "8B 44 24 ?? 83 EC 1C 53 55 56 57", "dmomm", "83 EC 10 53 8B 5C 24 ?? 55 56 8B F1"); + PATTERNS( + DecodeUserCmdFromBuffer, + "5135", + "83 EC 54 33 C0 D9 EE 89 44 24 ?? D9 54 24 ?? 89 44 24 ??", + "7197370", + "55 8B EC 83 EC 54 56 8B F1 C7 45 ?? ?? ?? ?? ?? 8D 4D ?? C7 45 ?? 00 00 00 00 C7 45 ?? 00 00 00 00 C7 45 ?? 00 00 00 00 C7 45 ?? 00 00 00 00 C7 45 ?? 00 00 00 00 E8 ?? ?? ?? ?? 8B 4D ??", + "4044", + "83 EC 54 53 57 8D 44 24 ?? 50 8B 44 24 ?? 99"); PATTERNS( GetGroundEntity, "5135", @@ -114,6 +122,7 @@ namespace patterns void PlayerIOFeature::InitHooks() { HOOK_FUNCTION(client, CreateMove); + HOOK_FUNCTION(client, DecodeUserCmdFromBuffer); HOOK_FUNCTION(client, GetButtonBits); FIND_PATTERN(client, GetGroundEntity); } @@ -171,6 +180,11 @@ void PlayerIOFeature::PreHook() offSidemove = 28; } + if (ORIG_DecodeUserCmdFromBuffer) + { + DecodeUserCmdFromBufferSignal.Works = true; + } + GetPlayerFields(); } @@ -284,38 +298,48 @@ Strafe::MovementVars PlayerIOFeature::GetMovementVars() return vars; } -void __fastcall PlayerIOFeature::HOOKED_CreateMove_Func(void* thisptr, - int edx, - int sequence_number, - float input_sample_frametime, - bool active) +IMPL_HOOK_THISCALL(PlayerIOFeature, + void, + CreateMove, + void*, + int sequence_number, + float input_sample_frametime, + bool active) { - auto m_pCommands = *reinterpret_cast(reinterpret_cast(thisptr) + offM_pCommands); - pCmd = m_pCommands + sizeofCUserCmd * (sequence_number % 90); + auto m_pCommands = + *reinterpret_cast(reinterpret_cast(thisptr) + spt_playerio.offM_pCommands); + auto pCmd = m_pCommands + spt_playerio.sizeofCUserCmd * (sequence_number % 90); + spt_playerio.pCmd = pCmd; - ORIG_CreateMove(thisptr, edx, sequence_number, input_sample_frametime, active); + spt_playerio.ORIG_CreateMove(thisptr, sequence_number, input_sample_frametime, active); CreateMoveSignal(pCmd); - pCmd = 0; + spt_playerio.pCmd = 0; } -void __fastcall PlayerIOFeature::HOOKED_CreateMove(void* thisptr, - int edx, - int sequence_number, - float input_sample_frametime, - bool active) +IMPL_HOOK_THISCALL(PlayerIOFeature, void, DecodeUserCmdFromBuffer, void*, bf_read& buf, int sequence_number) { - spt_playerio.HOOKED_CreateMove_Func(thisptr, edx, sequence_number, input_sample_frametime, active); + spt_playerio.ORIG_DecodeUserCmdFromBuffer(thisptr, buf, sequence_number); + + auto m_pCommands = + *reinterpret_cast(reinterpret_cast(thisptr) + spt_playerio.offM_pCommands); + auto pCmd = m_pCommands + spt_playerio.sizeofCUserCmd * (sequence_number % 90); + + DecodeUserCmdFromBufferSignal(pCmd); } -int __fastcall PlayerIOFeature::HOOKED_GetButtonBits_Func(void* thisptr, int edx, int bResetState) +IMPL_HOOK_THISCALL(PlayerIOFeature, int, GetButtonBits, void*, int bResetState) { - int rv = ORIG_GetButtonBits(thisptr, edx, bResetState); + int rv = spt_playerio.ORIG_GetButtonBits(thisptr, bResetState); if (bResetState == 1) { static int keyPressed = 0; + int spamButtons = spt_playerio.spamButtons; + bool& forceJump = spt_playerio.forceJump; + bool& forceUnduck = spt_playerio.forceUnduck; + keyPressed = (keyPressed ^ spamButtons) & spamButtons; rv |= keyPressed; @@ -335,11 +359,6 @@ int __fastcall PlayerIOFeature::HOOKED_GetButtonBits_Func(void* thisptr, int edx return rv; } -int __fastcall PlayerIOFeature::HOOKED_GetButtonBits(void* thisptr, int edx, int bResetState) -{ - return spt_playerio.HOOKED_GetButtonBits_Func(thisptr, edx, bResetState); -} - bool PlayerIOFeature::GetFlagsDucking() { return m_fFlags.GetValue() & FL_DUCKING; @@ -354,7 +373,7 @@ Strafe::PlayerData PlayerIOFeature::GetPlayerData() const int IN_DUCK = 1 << 2; data.Ducking = GetFlagsDucking(); - data.DuckPressed = (ORIG_GetButtonBits(cinput_thisptr, 0, 0) & IN_DUCK); + data.DuckPressed = (ORIG_GetButtonBits(cinput_thisptr, 0) & IN_DUCK); data.UnduckedOrigin = m_vecAbsOrigin.GetValue(); data.Velocity = GetPlayerVelocity(); data.Basevelocity = Vector(); @@ -454,7 +473,7 @@ bool PlayerIOFeature::IsGroundEntitySet() bool PlayerIOFeature::TryJump() { const int IN_JUMP = (1 << 1); - return ORIG_GetButtonBits(cinput_thisptr, 0, 0) & IN_JUMP; + return ORIG_GetButtonBits(cinput_thisptr, 0) & IN_JUMP; } bool PlayerIOFeature::PlayerIOAddressesFound() diff --git a/spt/features/playerio.hpp b/spt/features/playerio.hpp index 11d48b80d..a7d387d7f 100644 --- a/spt/features/playerio.hpp +++ b/spt/features/playerio.hpp @@ -23,18 +23,9 @@ typedef void*(__cdecl* _GetLocalPlayer)(); class PlayerIOFeature : public FeatureWrapper { private: - void __fastcall HOOKED_CreateMove_Func(void* thisptr, - int edx, - int sequence_number, - float input_sample_frametime, - bool active); - static void __fastcall HOOKED_CreateMove(void* thisptr, - int edx, - int sequence_number, - float input_sample_frametime, - bool active); - int __fastcall HOOKED_GetButtonBits_Func(void* thisptr, int edx, int bResetState); - static int __fastcall HOOKED_GetButtonBits(void* thisptr, int edx, int bResetState); + DECL_HOOK_THISCALL(void, CreateMove, void*, int sequence_number, float input_sample_frametime, bool active); + DECL_HOOK_THISCALL(void, DecodeUserCmdFromBuffer, void*, bf_read& buf, int sequence_number); + DECL_HOOK_THISCALL(int, GetButtonBits, void*, int bResetState); public: virtual bool ShouldLoadFeature() override; @@ -60,10 +51,8 @@ class PlayerIOFeature : public FeatureWrapper int spamButtons = 0; ptrdiff_t offServerAbsOrigin = 0; uintptr_t pCmd = 0; - _CreateMove ORIG_CreateMove = nullptr; _GetGroundEntity ORIG_GetGroundEntity = nullptr; - _GetButtonBits ORIG_GetButtonBits = nullptr; Vector currentVelocity; Vector previousVelocity; diff --git a/spt/features/strafehud.cpp b/spt/features/strafehud.cpp new file mode 100644 index 000000000..c11a14d10 --- /dev/null +++ b/spt/features/strafehud.cpp @@ -0,0 +1,561 @@ +#include "stdafx.hpp" +#include "..\feature.hpp" +#include "hud.hpp" + +#ifdef SPT_HUD_ENABLED + +#include + +#include "interfaces.hpp" +#include "playerio.hpp" +#include "game_detection.hpp" +#include "math.hpp" +#include "signals.hpp" +#include "visualizations\imgui\imgui_interface.hpp" + +#ifdef OE +#include "..\game_shared\usercmd.h" +#else +#include "usercmd.h" +#endif + +#undef min +#undef max + +// Draw strafe graph +class StrafeHUD : public FeatureWrapper +{ +public: + void SetData(uintptr_t pCmd); + void DrawHUD(); + +protected: + virtual bool ShouldLoadFeature() override; + + virtual void InitHooks() override; + + virtual void LoadFeature() override; + + virtual void UnloadFeature() override; + +private: + Vector wishDir; + std::vector accels; + + struct Line + { + Color color; + int start; + int end; + }; + + struct Point + { + int x, y; + }; + + bool shouldUpdateLines = false; + std::vector lines; + std::vector points; +}; + +static StrafeHUD feat_strafehud; + +ConVar spt_strafehud("spt_strafehud", "0", FCVAR_CHEAT, "Draws the strafe HUD."); +ConVar spt_strafehud_x("spt_strafehud_x", "-10", FCVAR_CHEAT, "The X position for the strafe HUD."); +ConVar spt_strafehud_y("spt_strafehud_y", "-10", FCVAR_CHEAT, "The Y position for the strafe HUD."); +ConVar spt_strafehud_size("spt_strafehud_size", + "256", + FCVAR_CHEAT, + "The width and height of the strafe HUD.", + true, + 1, + false, + 0); +ConVar spt_strafehud_detail_scale("spt_strafehud_detail_scale", + "4", + FCVAR_CHEAT, + "The detail scale for the lines of the strafe HUD.", + true, + 0, + true, + 645); +ConVar spt_strafehud_lock_mode("spt_strafehud_lock_mode", + "1", + FCVAR_CHEAT, + "Lock mode used by the strafe HUD:\n" + "0 - view direction\n" + "1 - velocity direction\n" + "2 - absolute angles", + true, + 0, + true, + 2); + +// Strafe stuff + +static Vector GetGroundFrictionVelocity(const Strafe::PlayerData player, const Strafe::MovementVars& vars) +{ + const float friction = vars.Friction * vars.EntFriction; + Vector vel = player.Velocity; + + const float velLen = vel.Length2D(); + if (vars.OnGround) + { + if (velLen >= vars.Stopspeed) + { + vel *= (1.0f - vars.Frametime * friction); + } + else if (velLen >= std::max(0.1f, vars.Frametime * vars.Stopspeed * friction)) + { + vel -= (vel / velLen) * vars.Frametime * vars.Stopspeed * friction; + } + else + { + vel = Vector(0.0f, 0.0f, 0.0f); + } + + if (vel.Length2D() < 1.0f) + { + vel = Vector(0.0f, 0.0f, 0.0f); + } + } + + return vel; +} + +static void CreateWishDirs(const Strafe::PlayerData player, + const Strafe::MovementVars& vars, + float sinPlayerYaw, + float cosPlayerYaw, + __m128 forwardmoves, + __m128 sidemoves, + __m128& outWishDirX, + __m128& outWishDirY) +{ + __m128 wishDirX = sidemoves; + __m128 wishDirY = forwardmoves; + + const __m128 lengths = _mm_sqrt_ps(_mm_add_ps(_mm_mul_ps(wishDirX, wishDirX), _mm_mul_ps(wishDirY, wishDirY))); + const __m128 mask = _mm_cmpgt_ps(lengths, _mm_set1_ps(1.0f)); + wishDirX = _mm_blendv_ps(wishDirX, _mm_div_ps(wishDirX, lengths), mask); + wishDirY = _mm_blendv_ps(wishDirY, _mm_div_ps(wishDirY, lengths), mask); + + const __m128 sinYaw = _mm_set1_ps(sinPlayerYaw); + const __m128 cosYaw = _mm_set1_ps(cosPlayerYaw); + + outWishDirX = _mm_add_ps(_mm_mul_ps(sinYaw, wishDirX), _mm_mul_ps(cosYaw, wishDirY)); + outWishDirY = _mm_sub_ps(_mm_mul_ps(sinYaw, wishDirY), _mm_mul_ps(cosYaw, wishDirX)); + + if (utils::DoesGameLookLikePortal()) + { + if (!vars.OnGround && player.Velocity.Length2DSqr() > 300 * 300) + { + if (std::fabs(player.Velocity.x) > 150) + { + __m128 velX = _mm_set1_ps(player.Velocity.x); + outWishDirX = + _mm_blendv_ps(outWishDirX, + _mm_set1_ps(0.0f), + _mm_cmplt_ps(_mm_mul_ps(velX, outWishDirX), _mm_set1_ps(0.0f))); + } + if (std::fabs(player.Velocity.y) > 150) + { + __m128 velY = _mm_set1_ps(player.Velocity.y); + outWishDirY = + _mm_blendv_ps(outWishDirY, + _mm_set1_ps(0.0f), + _mm_cmplt_ps(_mm_mul_ps(velY, outWishDirY), _mm_set1_ps(0.0f))); + } + } + } +} + +static __m128 GetMaxSpeeds(const Strafe::PlayerData player, + const Strafe::MovementVars& vars, + __m128 wishDirX, + __m128 wishDirY, + bool forceOnGround) +{ + const __m128 maxSpeed = _mm_set1_ps(vars.Maxspeed); + const __m128 wishSpeedCap = _mm_set1_ps(vars.WishspeedCap); + + const __m128 duckMultiplier = _mm_set1_ps((vars.OnGround && player.Ducking) ? 0.33333333f : 1.0f); + + wishDirX = _mm_mul_ps(wishDirX, maxSpeed); + wishDirY = _mm_mul_ps(wishDirY, maxSpeed); + + const __m128 wishDirLen = + _mm_sqrt_ps(_mm_add_ps(_mm_mul_ps(wishDirX, wishDirX), _mm_mul_ps(wishDirY, wishDirY))); + + __m128 clampedMaxSpeed = _mm_min_ps(maxSpeed, wishDirLen); + clampedMaxSpeed = _mm_mul_ps(clampedMaxSpeed, duckMultiplier); + + if (forceOnGround || vars.OnGround) + return clampedMaxSpeed; + + return _mm_min_ps(wishSpeedCap, clampedMaxSpeed); +} + +static inline __m128 GetMaxAccels(const Strafe::PlayerData player, + const Strafe::MovementVars& vars, + __m128 wishDirX, + __m128 wishDirY) +{ + const float accel = vars.OnGround ? vars.Accelerate : vars.Airaccelerate; + return _mm_mul_ps(_mm_set1_ps(vars.EntFriction * vars.Frametime * accel), + GetMaxSpeeds(player, vars, wishDirX, wishDirY, true)); +} + +static void GetAccelAfterMove(const Strafe::PlayerData player, + const Strafe::MovementVars& vars, + float sinPlayerYaw, + float cosPlayerYaw, + const Vector& oldVel, + float oldVelLength2D, + const float* yaws, + float* outAccel) +{ + const __m128 vecYaws = _mm_loadu_ps(yaws); + const __m128 forwardmoves = _mm_cos_ps(vecYaws); + const __m128 sidemoves = _mm_sin_ps(vecYaws); + + __m128 wishDirX, wishDirY; + CreateWishDirs(player, vars, sinPlayerYaw, cosPlayerYaw, forwardmoves, sidemoves, wishDirX, wishDirY); + + const __m128 wishDirLengths = + _mm_sqrt_ps(_mm_add_ps(_mm_mul_ps(wishDirX, wishDirX), _mm_mul_ps(wishDirY, wishDirY))); + const __m128 zeroMask = _mm_cmpeq_ps(wishDirLengths, _mm_set1_ps(0.0f)); + + const __m128 maxSpeeds = GetMaxSpeeds(player, vars, wishDirX, wishDirY, false); + const __m128 maxAccels = GetMaxAccels(player, vars, wishDirX, wishDirY); + + const __m128 velX = _mm_set1_ps(oldVel.x); + const __m128 velY = _mm_set1_ps(oldVel.y); + + wishDirX = _mm_div_ps(wishDirX, wishDirLengths); + wishDirY = _mm_div_ps(wishDirY, wishDirLengths); + + const __m128 dotProducts = _mm_add_ps(_mm_mul_ps(velX, wishDirX), _mm_mul_ps(velY, wishDirY)); + const __m128 accelDiff = _mm_sub_ps(maxSpeeds, dotProducts); + const __m128 accelMask = _mm_cmple_ps(accelDiff, _mm_set1_ps(0.0f)); + + const __m128 accelForce = _mm_min_ps(maxAccels, accelDiff); + + const __m128 newVelX = _mm_add_ps(velX, _mm_mul_ps(wishDirX, accelForce)); + const __m128 newVelY = _mm_add_ps(velY, _mm_mul_ps(wishDirY, accelForce)); + const __m128 newVelLen = _mm_sqrt_ps(_mm_add_ps(_mm_mul_ps(newVelX, newVelX), _mm_mul_ps(newVelY, newVelY))); + + const __m128 accel = _mm_sub_ps(newVelLen, _mm_set1_ps(oldVelLength2D)); + + const __m128 result = _mm_blendv_ps(accel, _mm_set1_ps(0.0f), _mm_or_ps(zeroMask, accelMask)); + + _mm_storeu_ps(outAccel, result); +} + +void StrafeHUD::SetData(uintptr_t pCmd) +{ + if (!spt_strafehud.GetBool()) + return; + + const CUserCmd* cmd = reinterpret_cast(pCmd); + float forwardmove = cmd->forwardmove; + float sidemove = cmd->sidemove; + float upmove = cmd->upmove; + + accels.clear(); + lines.clear(); + + const auto player = spt_playerio.GetPlayerData(); + const auto vars = spt_playerio.GetMovementVars(); + const float playerYaw = utils::GetPlayerEyeAngles().y; + + float relAng = 0.0f; + const int lockMode = spt_strafehud_lock_mode.GetInt(); + if (lockMode > 0) + { + relAng = playerYaw; + if (lockMode == 1) + { + QAngle angles; + VectorAngles(player.Velocity, Vector(0, 0, 1), angles); + relAng -= angles.y; + } + relAng *= utils::M_DEG2RAD; + } + + const float speed = forwardmove * forwardmove + sidemove * sidemove + upmove * upmove; + if (speed > vars.Maxspeed * vars.Maxspeed) + { + const float ratio = vars.Maxspeed / std::sqrtf(speed); + forwardmove *= ratio; + sidemove *= ratio; + upmove *= ratio; + } + + wishDir = Vector(std::cosf(relAng) * sidemove - std::sinf(relAng) * forwardmove, + std::sinf(relAng) * sidemove + std::cosf(relAng) * forwardmove, + 0.0f); + const float wishDirLen = wishDir.Length2D(); + if (wishDirLen > 1.0f) + { + wishDir /= wishDirLen; + } + + int detail = spt_strafehud_size.GetInt() * spt_strafehud_detail_scale.GetFloat(); + detail = ((detail + 3) / 4) * 4; // round up to multiple of 4 + accels.reserve(detail); + + const Vector oldVel = GetGroundFrictionVelocity(player, vars); + + const float sinPlayerYaw = std::sinf(playerYaw * utils::M_DEG2RAD); + const float cosPlayerYaw = std::cosf(playerYaw * utils::M_DEG2RAD); + + for (int i = 0; i < detail; i += 4) + { + std::array ang; + for (int j = 0; j < 4; j++) + { + ang[j] = ((i + j) / (float)detail) * 2.0f * M_PI + relAng; + } + + std::array accel{}; + GetAccelAfterMove(player, + vars, + sinPlayerYaw, + cosPlayerYaw, + oldVel, + oldVel.Length2D(), + ang.data(), + accel.data()); + accels.insert(accels.end(), accel.begin(), accel.end()); + } + + auto [minIt, maxIt] = std::minmax_element(accels.begin(), accels.end()); + float biggestAccel = *maxIt; + float smallestAccel = *minIt; + for (float& accel : accels) + { + if (accel > 0.0f && biggestAccel > 0.0f) + { + accel /= biggestAccel; + } + else if (accel < 0.0f && smallestAccel < 0.0f) + { + accel /= -smallestAccel; + } + } + + shouldUpdateLines = true; +} + +void StrafeHUD::DrawHUD() +{ + const int pad = 5; + int size = spt_strafehud_size.GetInt(); + int x = spt_strafehud_x.GetInt(); + int y = spt_strafehud_y.GetInt(); + + if (x < 0) + x += spt_hud_feat.renderView->width - size; + if (y < 0) + y += spt_hud_feat.renderView->height - size; + + const Color bgColor(0, 0, 0, 192); + const Color lineColor(64, 64, 64, 255); + const Color wishDirColor(0, 0, 255, 255); + + auto surface = interfaces::surface; + + // Draw background + surface->DrawSetColor(bgColor); + surface->DrawFilledRect(x, y, x + size, y + size); + x += pad; + y += pad; + size -= pad * 2; + + const int mid_x = x + size / 2; + const int mid_y = y + size / 2; + + const float dx = size * 0.5f; + const float dy = size * 0.5f; + + // Containing rect + surface->DrawOutlinedRect(x, y, x + size, y + size); + + // Circles + surface->DrawOutlinedCircle(mid_x, mid_y, size / 2, 32); + surface->DrawOutlinedCircle(mid_x, mid_y, size / 4, 32); + + // Half-lines and diagonals + surface->DrawLine(mid_x, y, mid_x, y + size); + surface->DrawLine(x, mid_y, x + size, mid_y); + surface->DrawLine(x, y, x + size, y + size); + surface->DrawLine(x, y + size, x + size, y); + + // Acceleration line + if (shouldUpdateLines) + { + shouldUpdateLines = false; + + lines.clear(); + points.clear(); + + const Color accelColor(0, 255, 0, 255); + const Color decelColor(255, 0, 0, 255); + const Color nocelColor(255, 255, 0, 255); + + Color prevColor(0, 0, 0); + Line currentLine = Line{ + .color = nocelColor, + .start = 0, + .end = 0, + }; + + const int detail = accels.size(); + for (int i = 0; i < detail; i++) + { + const float ang1 = (i / (float)detail) * 2.0f * M_PI; + const int i2 = (i + 1) % detail; + const float ang2 = (i2 / (float)detail) * 2.0f * M_PI; + + const float a1 = std::min(std::max(accels[i], -1.0f), 1.0f); + const float a2 = std::min(std::max(accels[i2], -1.0f), 1.0f); + + const float ad1 = (a1 + 1.0f) * 0.5f; + const float ad2 = (a2 + 1.0f) * 0.5f; + + Color currentColor = nocelColor; + if ((ad1 != 0.0f && ad2 != 0.0f) && a1 * a2 > 0.0f) + { + currentColor = (a1 >= 0.0f) ? accelColor : decelColor; + } + + if (i == 0) + { + // Add first point + points.push_back(Point{mid_x + (int)(std::sinf(ang1) * dx * ad1), + mid_y - (int)(std::cosf(ang1) * dy * ad1)}); + } + + if (currentColor != prevColor) + { + if (currentLine.start != currentLine.end) + { + lines.push_back(currentLine); + } + currentLine = Line{currentColor, (int)points.size() - 1, 0}; + } + + points.push_back(Point{ + mid_x + (int)(std::sinf(ang2) * dx * ad2), + mid_y - (int)(std::cosf(ang2) * dy * ad2), + }); + + currentLine.end = (int)points.size() - 1; + prevColor = currentColor; + } + + points.push_back(points[0]); + currentLine.end = (int)points.size() - 1; + lines.push_back(currentLine); + } + + // Draw lines + for (const auto& line : lines) + { + assert(line.end > line.start); + + surface->DrawSetColor(line.color); + + for (int i = line.start; i < line.end; i++) + { + const auto& point1 = points[i]; + const auto& point2 = points[i + 1]; + surface->DrawLine(point1.x, point1.y, point2.x, point2.y); + } + } + + // Wish dir + surface->DrawSetColor(wishDirColor); + const int x0 = mid_x; + const int y0 = mid_y; + const int x1 = mid_x + wishDir.x * dx; + const int y1 = mid_y - wishDir.y * dy; + + const int halfThickness = 1; + + const float slope = (y1 - y0) / (float)(x1 - x0); + if (std::fabs(slope) <= 1) + { + for (int i = -halfThickness; i <= halfThickness; i++) + { + surface->DrawLine(x0, y0 + i, x1, y1 + i); + } + } + else + { + for (int i = -halfThickness; i <= halfThickness; i++) + { + surface->DrawLine(x0 + i, y0, x1 + i, y1); + } + } +} + +bool StrafeHUD::ShouldLoadFeature() +{ + return spt_hud_feat.ShouldLoadFeature(); +} + +void StrafeHUD::InitHooks() {} + +void StrafeHUD::LoadFeature() +{ + if (!(CreateMoveSignal.Works && DecodeUserCmdFromBufferSignal.Works)) + return; + + CreateMoveSignal.Connect(this, &StrafeHUD::SetData); + DecodeUserCmdFromBufferSignal.Connect(this, &StrafeHUD::SetData); + + bool result = spt_hud_feat.AddHudDefaultGroup(HudCallback( + std::bind(&StrafeHUD::DrawHUD, this), []() { return spt_strafehud.GetBool(); }, false)); + if (result) + { + InitConcommandBase(spt_strafehud); + InitConcommandBase(spt_strafehud_x); + InitConcommandBase(spt_strafehud_y); + InitConcommandBase(spt_strafehud_size); + InitConcommandBase(spt_strafehud_detail_scale); + InitConcommandBase(spt_strafehud_lock_mode); + } + + SptImGuiGroup::Hud_StrafeHud.RegisterUserCallback( + []() + { + ImGui::BeginDisabled(!SptImGui::CvarCheckbox(spt_strafehud, "##enabled")); + + const char* opts[] = {"View direction", "Velocity direction", "Absolute angles"}; + SptImGui::CvarCombo(spt_strafehud_lock_mode, "lock mode", opts, ARRAYSIZE(opts)); + + ConVar* var = &spt_strafehud_detail_scale; + long long varVal; + SptImGui::CvarsDragScalar(&var, &varVal, 1, false, "detail scale"); + ImGui::SameLine(); + SptImGui::CmdHelpMarkerWithName(*var); + var = &spt_strafehud_size; + SptImGui::CvarsDragScalar(&var, &varVal, 1, false, "HUD size"); + ImGui::SameLine(); + SptImGui::CmdHelpMarkerWithName(*var); + + ConVar* cvars[] = {&spt_strafehud_x, &spt_strafehud_y}; + float f[ARRAYSIZE(cvars)]; + SptImGui::CvarsDragScalar(cvars, f, ARRAYSIZE(cvars), true, "HUD pos"); + ImGui::SameLine(); + SptImGui::HelpMarker("Can be set with spt_strafehud_x/y"); + ImGui::EndDisabled(); + }); +} + +void StrafeHUD::UnloadFeature() {} + +#endif diff --git a/spt/features/visualizations/imgui/imgui_interface.hpp b/spt/features/visualizations/imgui/imgui_interface.hpp index ea534969c..4ff640a9f 100644 --- a/spt/features/visualizations/imgui/imgui_interface.hpp +++ b/spt/features/visualizations/imgui/imgui_interface.hpp @@ -214,6 +214,7 @@ namespace SptImGuiGroup inline Tab Hud_TextHud{"Text HUD", &Hud}; // use the RegisterHudCvarXXX functions below to add cvars here inline Tab Hud_IHud{"Input HUD", &Hud}; inline Tab Hud_JHud{"Jump HUD", &Hud}; + inline Tab Hud_StrafeHud{"Strafe HUD", &Hud}; // development/debugging features inline Tab Dev{"DEV", &Root}; diff --git a/spt/utils/signals.cpp b/spt/utils/signals.cpp index fa4b5988e..4fa7962c5 100644 --- a/spt/utils/signals.cpp +++ b/spt/utils/signals.cpp @@ -10,6 +10,7 @@ Gallant::Signal1 OngroundSignal; Gallant::Signal2 SetPausedSignal; Gallant::Signal1 SV_ActivateServerSignal; Gallant::Signal1 CreateMoveSignal; +Gallant::Signal1 DecodeUserCmdFromBufferSignal; Gallant::Signal0 VagCrashSignal; Gallant::Signal0 DemoStartPlaybackSignal; Gallant::Signal1 SV_FrameSignal; @@ -18,7 +19,6 @@ Gallant::Signal2 ProcessMovementPre_Signal; Gallant::Signal2 RenderViewPre_Signal; Gallant::Signal2 SetSignonStateSignal; - // Plugin callbacks Gallant::Signal0 TickSignal; Gallant::Signal1 LevelInitSignal; diff --git a/spt/utils/signals.hpp b/spt/utils/signals.hpp index 6a99f1794..744757990 100644 --- a/spt/utils/signals.hpp +++ b/spt/utils/signals.hpp @@ -14,6 +14,7 @@ extern Gallant::Signal1 OngroundSignal; extern Gallant::Signal2 SetPausedSignal; extern Gallant::Signal1 SV_ActivateServerSignal; extern Gallant::Signal1 CreateMoveSignal; +extern Gallant::Signal1 DecodeUserCmdFromBufferSignal; extern Gallant::Signal0 VagCrashSignal; extern Gallant::Signal0 DemoStartPlaybackSignal; extern Gallant::Signal1 SV_FrameSignal;