From 6fc8d8c16bee442e185654a3316a6847dd77cd72 Mon Sep 17 00:00:00 2001 From: kedou Date: Fri, 7 Aug 2026 21:50:05 +0800 Subject: [PATCH] feat: expose aim smooth as 0-100 --- CFramework/Cheat/Aim.cpp | 119 ++++++++++++++++++++++----- CFramework/Cheat/Framework.h | 5 ++ CFramework/Cheat/Menu.cpp | 2 +- CFramework/Framework/Config/Config.h | 2 +- 4 files changed, 107 insertions(+), 21 deletions(-) diff --git a/CFramework/Cheat/Aim.cpp b/CFramework/Cheat/Aim.cpp index 119f724..16ea25a 100644 --- a/CFramework/Cheat/Aim.cpp +++ b/CFramework/Cheat/Aim.cpp @@ -36,6 +36,38 @@ namespace return a; } + float Clamp01(float value) + { + if (value < 0.f) return 0.f; + if (value > 1.f) return 1.f; + return value; + } + + float FrameScale() + { + float deltaTime = ImGui::GetIO().DeltaTime; + if (!(deltaTime > 0.f) || deltaTime > 0.2f) + deltaTime = 1.f / 60.f; + + float scale = deltaTime * 60.f; + if (scale < 0.5f) scale = 0.5f; + if (scale > 2.f) scale = 2.f; + return scale; + } + + // Distance-adaptive response: + // far target -> catch up; near target -> settle without a large final snap. + float AimResponse(float error, float smooth, float referenceError) + { + smooth = Clamp01(smooth * 0.01f); + const float nearResponse = 0.06f + (1.f - smooth) * 0.10f; + const float farResponse = 0.28f + (1.f - smooth) * 0.26f; + const float weight = Clamp01(error / (referenceError > 0.001f ? referenceError : 1.f)); + const float perFrame = nearResponse + (farResponse - nearResponse) * weight; + + return 1.f - powf(1.f - perFrame, FrameScale()); + } + // IsKeyDown は Framework/Input/Input.h のグローバル関数を使用 // (GetAsyncKeyState はマウスボタンを検知できない環境があるため、WH_MOUSE_LL フックで捕捉) @@ -80,7 +112,12 @@ void CFramework::AimAll() // ---- 自瞄 ---- if (!g.g_Aim) + { + m_aimLastTarget = 0; + m_aimResidualX = 0.f; + m_aimResidualY = 0.f; return; + } // FOV 円はキーを押していなくても表示 Vector2 Center = Vector2(GameSize.x * 0.5f, GameSize.y * 0.5f); @@ -91,7 +128,12 @@ void CFramework::AimAll() // ※ GetAsyncKeyState / 低级钩子 在本环境(管理员+RawInput独占)都检测不到按键, // g_AimForce 是绕过按键检测的替代方案 if (!g.g_AimForce && !IsKeyDown(g.g_AimKey0) && !IsKeyDown(g.g_AimKey1) && !IsKeyDown(g.g_AimKey2)) + { + m_aimLastTarget = 0; + m_aimResidualX = 0.f; + m_aimResidualY = 0.f; return; + } // カメラ位置 (ローカル頭部を優先、無ければ origin) local.UpdateBone(); @@ -179,13 +221,31 @@ void CFramework::AimAll() m_diagBestDist = bestDist; if (bestAddr == 0) + { + m_aimLastTarget = 0; + m_aimResidualX = 0.f; + m_aimResidualY = 0.f; return; + } + + if (bestAddr != m_aimLastTarget) + { + m_aimLastTarget = bestAddr; + m_aimResidualX = 0.f; + m_aimResidualY = 0.f; + } // 2. 照準適用 if (g.g_AimMethod == 0) + { + m_aimResidualX = 0.f; + m_aimResidualY = 0.f; AimMemory(bestPoint, localPos); + } else + { AimMouse(bestScreen, Center); + } // 3. ターゲット線 if (g.g_AimDrawTarget) @@ -268,32 +328,54 @@ Vector3 CFramework::GetAimBone(CPlayer* pEntity) // Mouse モード: 画面中心とターゲットの差分をマウス移動量に変換 void CFramework::AimMouse(const Vector2& target, const Vector2& center) { - float dx = target.x - center.x; - float dy = target.y - center.y; + const float dx = target.x - center.x; + const float dy = target.y - center.y; + const float dist = sqrtf(dx * dx + dy * dy); - float dist = sqrtf(dx * dx + dy * dy); - if (dist < 1.f) // 既に当たっている + if (dist < 0.5f) + { + m_aimResidualX = 0.f; + m_aimResidualY = 0.f; return; + } - // smooth: 0=即時追従, 1=ゆっくり追従 - float scale = g.g_AimSens * (1.f - g.g_AimSmooth); - float mx = dx * scale; - float my = dy * scale; + const float referenceError = g.g_AimFov > 32.f ? g.g_AimFov * 0.5f : 32.f; + const float response = AimResponse(dist, g.g_AimSmooth, referenceError); - // 1フレームの最大移動量でクランプ (なめらか + 暴走防止) - float maxSpeed = g.g_AimMaxSpeed; - float len = sqrtf(mx * mx + my * my); + float sensitivity = g.g_AimSens; + if (sensitivity < 0.1f) sensitivity = 0.1f; + if (sensitivity > 5.f) sensitivity = 5.f; + + // Never allow the smoothing slider to turn a small error into a one-frame snap. + float gain = response * sensitivity; + if (gain > 0.85f) gain = 0.85f; + + float mx = dx * gain; + float my = dy * gain; + + // MaxSpeed is expressed at 60 FPS; keep the behavior stable across frame rates. + float maxSpeed = g.g_AimMaxSpeed * FrameScale(); + if (maxSpeed < 1.f) maxSpeed = 1.f; + + const float len = sqrtf(mx * mx + my * my); if (len > maxSpeed) { mx = mx / len * maxSpeed; my = my / len * maxSpeed; } - // デッドゾーン (細かいジッター防止) - if (fabsf(mx) < 0.5f && fabsf(my) < 0.5f) + // Preserve fractional movement instead of truncating every frame to LONG. + const float moveX = mx + m_aimResidualX; + const float moveY = my + m_aimResidualY; + const LONG sendX = static_cast(lroundf(moveX)); + const LONG sendY = static_cast(lroundf(moveY)); + m_aimResidualX = moveX - static_cast(sendX); + m_aimResidualY = moveY - static_cast(sendY); + + if (sendX == 0 && sendY == 0) return; - SendMouseMove((LONG)mx, (LONG)my); + SendMouseMove(sendX, sendY); } // Memory モード: MovementContext._rotation (Vector2 x=yaw y=pitch) へ書く @@ -319,13 +401,12 @@ void CFramework::AimMemory(const Vector3& target, const Vector3& localPos) yaw = ClampAngle(yaw); pitch = ClampAngle(pitch); - // 平滑化: 現在値 → 目標値へ補間 (yaw は短い方へ回す) - float t = 1.f - g.g_AimSmooth; - if (t <= 0.f) - return; - + // Distance-adaptive exponential smoothing. It is never 0 or 1, so the + // slider cannot produce either a one-frame snap or a permanently lagging aim. float dyaw = ClampAngle(yaw - rot.x); float dpitch = ClampAngle(pitch - rot.y); + float angleError = sqrtf(dyaw * dyaw + dpitch * dpitch); + float t = AimResponse(angleError, g.g_AimSmooth, 0.75f); Vector2 newRot{ ClampAngle(rot.x + dyaw * t), diff --git a/CFramework/Cheat/Framework.h b/CFramework/Cheat/Framework.h index 69b903b..020b1d7 100644 --- a/CFramework/Cheat/Framework.h +++ b/CFramework/Cheat/Framework.h @@ -92,6 +92,11 @@ private: uintptr_t m_diagBestAddr = 0; // 最近一帧锁定的目标地址 float m_diagBestDist = 0.f;// 最近一帧目标到屏幕中心的距离 + // 鼠标自瞄的亚像素累积,避免小误差因 LONG 截断而永远到不了目标。 + uintptr_t m_aimLastTarget = 0; + float m_aimResidualX = 0.f; + float m_aimResidualY = 0.f; + void DrawLine(ImVec2 a, ImVec2 b, ImColor color, float width) { ImGui::GetBackgroundDrawList()->AddLine(ImVec2((int)a.x, (int)a.y), ImVec2((int)b.x, (int)b.y), color, width); diff --git a/CFramework/Cheat/Menu.cpp b/CFramework/Cheat/Menu.cpp index de69c62..64d1a08 100644 --- a/CFramework/Cheat/Menu.cpp +++ b/CFramework/Cheat/Menu.cpp @@ -57,7 +57,7 @@ void CFramework::RenderMenu() ImGui::Checkbox("启用", &g.g_Aim); ImGui::Combo("方式", &g.g_AimMethod, AimMethodList, IM_ARRAYSIZE(AimMethodList)); ImGui::CustomSliderFloat("锁定范围", "##AimFov", &g.g_AimFov, 10.f, 500.f); - ImGui::CustomSliderFloat("平滑", "##AimSmooth", &g.g_AimSmooth, 0.f, 1.f); + ImGui::CustomSliderFloat("平滑(0-100)", "##AimSmooth", &g.g_AimSmooth, 0.f, 100.f); ImGui::CustomSliderFloat("最大距离", "##AimDist", &g.g_AimMaxDist, 50.f, 2000.f); ImGui::Combo("瞄准部位", &g.g_AimBone, AimBoneList, IM_ARRAYSIZE(AimBoneList)); diff --git a/CFramework/Framework/Config/Config.h b/CFramework/Framework/Config/Config.h index abc4714..8008ea6 100644 --- a/CFramework/Framework/Config/Config.h +++ b/CFramework/Framework/Config/Config.h @@ -35,7 +35,7 @@ struct Globals bool g_Aim = false; int g_AimMethod = 1; // 0 = Memory(写旋转), 1 = Mouse(SendInput) float g_AimFov = 150.f; // FOV 半径 [px] - float g_AimSmooth = 0.45f; // 0=瞬移 1=最慢 + float g_AimSmooth = 45.f; // 0-100: 自适应平滑,0=快但有限幅,100=慢但不会瞬移 float g_AimSens = 1.0f; // Mouse 模式灵敏度倍率 float g_AimMaxSpeed = 80.f; // Mouse 模式单帧最大移动 [px] float g_AimMaxDist = 500.f; // 最大锁定距离 [m]