diff --git a/CFramework/CFramework.vcxproj b/CFramework/CFramework.vcxproj index 37e7dd6..cc0052b 100644 --- a/CFramework/CFramework.vcxproj +++ b/CFramework/CFramework.vcxproj @@ -143,6 +143,9 @@ + + + @@ -169,6 +172,8 @@ + + diff --git a/CFramework/Cheat/Framework.h b/CFramework/Cheat/Framework.h index a286a9e..55adb45 100644 --- a/CFramework/Cheat/Framework.h +++ b/CFramework/Cheat/Framework.h @@ -260,6 +260,13 @@ private: void AimMemory(const Vector3& target, const Vector3& localPos, uintptr_t localAddress); void ScanRotationOffset(); // 2阶段扫描: Player->MC 与 MC->Rotation 偏移 + // Menu pages are UI-only renderers. They keep the existing Globals/config bindings intact. + void RenderAimPage(float columnWidth, float gap); + void RenderVisualPage(float columnWidth, float gap); + void RenderFeaturesPage(float columnWidth, float gap); + void RenderSystemPage(float columnWidth, float gap, const char*& configStatus); + void RenderDebugPage(float columnWidth, float gap); + // Runtime feature backup state is declared above. diff --git a/CFramework/Cheat/Menu.cpp b/CFramework/Cheat/Menu.cpp index f6d729f..4229c1f 100644 --- a/CFramework/Cheat/Menu.cpp +++ b/CFramework/Cheat/Menu.cpp @@ -1,357 +1,192 @@ -#include "Framework.h" -#include "../Framework/Input/Input.h" +#include "Framework.h" +#include "UI/MenuTheme.h" +#include "UI/MenuWidgets.h" +#include "../Framework/Config/Config.h" -// ImGui::Combo/ImGui::List等で使う文字列群 -const char* BoxTypeList[] = { "简单", "棱角" }; -const char* CrosshairList[] = { "十字", "圆圈" }; -const char* AimMethodList[] = { "内存", "鼠标" }; -const char* AimBoneList[] = { "头部", "颈部", "身体" }; -std::vector MenuStringList = { "自瞄", "视觉", "功能", "设置" }; -std::vector MenuIconList = { ICON_FA_CROSSHAIRS, ICON_FA_EYE, ICON_FA_BARS, ICON_FA_GEAR }; +#include + +namespace +{ + enum class MenuPage + { + Aim, + Visual, + Features, + System, + Debug, + }; +} void CFramework::RenderMenu() { - // Setup - static int Index = 0; - const auto runtimeSnapshot = GetDynamicSnapshot(); - const bool runtimeFresh = runtimeSnapshot && IsSnapshotFresh(runtimeSnapshot->capturedAt); - const bool runtimeInRaid = runtimeFresh && runtimeSnapshot->inRaid; - const uintptr_t runtimeLocalAddress = runtimeFresh ? runtimeSnapshot->local.m_address : 0; - const std::size_t runtimeEntityCount = runtimeFresh ? runtimeSnapshot->players.size() : 0; + static MenuPage currentPage = MenuPage::Aim; + static const char* configStatus = "自动加载;可手动保存/读取"; - ImGui::SetNextWindowBgAlpha(0.975f); - ImGui::SetNextWindowSize(ImVec2(725.f, 450.f)); - ImGui::Begin("塔科夫 PvE [ 外部辅助 ]", &g.ShowMenu, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + const bool debug = g.g_DebugMode; + if (currentPage == MenuPage::Debug && !debug) + currentPage = MenuPage::System; - //---// Clild 0 //-----------------------------------// - ImGui::BeginChild("##SelectChild", ImVec2(150.f, ImGui::GetContentRegionAvail().y), false); + UI::Theme::ApplyTheme(); + ImGui::SetNextWindowPos(ImVec2(70.f, 40.f), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(920.f, 620.f), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSizeConstraints(ImVec2(760.f, 520.f), ImVec2(FLT_MAX, FLT_MAX)); + ImGui::SetNextWindowBgAlpha(1.f); - ImGui::NewLine(); + const ImGuiWindowFlags windowFlags = + ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoSavedSettings; - ImGui::PushFont(icon); - - for (int i = 0; i < MenuIconList.size(); i++) { - if (ImGui::CustomButton(MenuIconList[i], MenuStringList[i], ImVec2(ImGui::GetContentRegionAvail().x, 35.f), Index == i ? true : false)) - Index = i; + if (!ImGui::Begin("##CFW_ControlDeck", nullptr, windowFlags)) + { + ImGui::End(); + return; } - ImGui::PopFont(); - - ImGui::EndChild(); - //---// Clild 0 End //-------------------------------// - - ImGui::SameLine(); - - //---// Clild 1 //-----------------------------------// - ImGui::BeginChild("##ContextChild", ImVec2(ImGui::GetContentRegionAvail()), false); - - //---// Left //--------------------------------------// - ImGui::BeginChild("##LeftChild", ImVec2(ImGui::GetContentRegionAvail().x / 2.f - 16.f, ImGui::GetContentRegionAvail().y), false); - - ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.1f, 0.1f, 0.1f, 1.f)); - - switch (Index) + const auto drawHeader = [&]() { - case 0: // AimBot - ImGui::BeginChild("##C000", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y / 2.f), true); + const ImVec2 headerPos = ImGui::GetCursorScreenPos(); + const float headerWidth = ImGui::GetContentRegionAvail().x; + const float headerHeight = UI::Theme::HeaderHeight; + ImDrawList* drawList = ImGui::GetWindowDrawList(); - ImGui::Text("自瞄"); - ImGui::Separator(); - ImGui::Spacing(); + drawList->AddRectFilled( + headerPos, + ImVec2(headerPos.x + headerWidth, headerPos.y + headerHeight), + UI::Theme::U32(UI::Theme::CardBackground), + UI::Theme::CardRounding); + drawList->AddRectFilled( + headerPos, + ImVec2(headerPos.x + headerWidth, headerPos.y + 2.f), + UI::Theme::U32(UI::Theme::Accent), + 1.f); - 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("平滑(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)); + ImGui::SetCursorScreenPos(ImVec2(headerPos.x + 16.f, headerPos.y + 14.f)); + if (icon) + { + ImGui::PushFont(icon); + ImGui::TextColored(UI::Theme::Accent, "%s", ICON_FA_SHIELD_HALVED); + ImGui::PopFont(); + ImGui::SameLine(0.f, 9.f); + } + ImGui::TextColored(UI::Theme::TextPrimary, "PvE 战术控制台"); + ImGui::SameLine(0.f, 8.f); + ImGui::TextColored(UI::Theme::TextSecondary, "SPT-AKI 3.11.3"); + ImGui::SameLine(0.f, 8.f); + ImGui::TextColored(UI::Theme::AccentStrong, "v2.1"); - ImGui::EndChild(); - ImGui::BeginChild("##C001", ImVec2(ImGui::GetContentRegionAvail()), true); + const float statusX = headerPos.x + headerWidth * 0.53f; + ImGui::SetCursorScreenPos(ImVec2(statusX, headerPos.y + 15.f)); + UI::StatusBadge("ESP", g.g_ESP); + ImGui::SameLine(0.f, 6.f); + UI::StatusBadge("AIM", g.g_Aim); + ImGui::SameLine(0.f, 6.f); + UI::StatusBadge("RECOIL", g.g_Recoil); + ImGui::SameLine(0.f, 6.f); + UI::StatusBadge("STREAM", g.g_StreamProof); - ImGui::Text("鼠标模式"); - ImGui::Separator(); - ImGui::Spacing(); + const float hideWidth = 66.f; + ImGui::SetCursorScreenPos(ImVec2(headerPos.x + headerWidth - hideWidth - 12.f, headerPos.y + 12.f)); + if (UI::Button("隐藏", ImVec2(hideWidth, 28.f))) + g.ShowMenu = false; - ImGui::CustomSliderFloat("灵敏度", "##AimSens", &g.g_AimSens, 0.1f, 5.f); - ImGui::CustomSliderFloat("最大速度", "##AimMaxSpeed", &g.g_AimMaxSpeed, 1.f, 300.f); + const bool headerHovered = ImGui::IsMouseHoveringRect( + headerPos, + ImVec2(headerPos.x + headerWidth, headerPos.y + headerHeight)); + if (headerHovered && !ImGui::IsItemHovered() && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) + { + const ImVec2 delta = ImGui::GetIO().MouseDelta; + const ImVec2 currentWindowPos = ImGui::GetWindowPos(); + ImGui::SetWindowPos(ImVec2(currentWindowPos.x + delta.x, currentWindowPos.y + delta.y)); + } - ImGui::Separator(); - ImGui::Spacing(); - ImGui::Text("压枪"); - ImGui::Separator(); - ImGui::Spacing(); + ImGui::SetCursorScreenPos(ImVec2(headerPos.x, headerPos.y + headerHeight + 12.f)); + }; + drawHeader(); - ImGui::Checkbox("启用", &g.g_Recoil); - ImGui::CustomSliderFloat("力度", "##RecStr", &g.g_RecoilStrength, 0.f, 20.f); - ImGui::CustomSliderInt("间隔(ms)", "##RecInt", &g.g_RecoilInterval, 1, 100); - - ImGui::EndChild(); - break; - case 1: // Visual - ImGui::BeginChild("##C010", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y / 3.f), true); - - ImGui::Text("视觉"); - ImGui::Separator(); - ImGui::Spacing(); - - ImGui::Checkbox("ESP", &g.g_ESP); - ImGui::Checkbox("物资ESP", &g.g_ESP_Item); - ImGui::Checkbox("尸体ESP", &g.g_ESP_Corpse); - ImGui::Checkbox("容器ESP", &g.g_ESP_Container); - ImGui::Checkbox("撤离点ESP", &g.g_ESP_Exfil); - ImGui::Checkbox("手雷ESP", &g.g_ESP_Grenade); - - ImGui::EndChild(); - ImGui::BeginChild("##C011", ImVec2(ImGui::GetContentRegionAvail()), true); - - ImGui::Text("ESP选项"); - ImGui::Separator(); - ImGui::Spacing(); - - ImGui::Checkbox("连线", &g.g_ESP_Line); - ImGui::Checkbox("方框", &g.g_ESP_Box); - ImGui::Checkbox("填充方框", &g.g_ESP_BoxFilled); - ImGui::Checkbox("骨骼", &g.g_ESP_Skeleton); - ImGui::Checkbox("距离", &g.g_ESP_Distance); - ImGui::Checkbox("名称", &g.g_ESP_Name); - ImGui::Checkbox("血条", &g.g_ESP_HealthBar); - ImGui::CustomSliderFloat("物资最大距离", "##ItemDist", &g.g_ESP_MaxItemDistance, 0.f, 1000.f); - ImGui::CustomSliderFloat("容器最大距离", "##ContainerDist", &g.g_ESP_MaxContainerDistance, 0.f, 1000.f); - - ImGui::EndChild(); - break; - case 2: // Misc - ImGui::BeginChild("##C020", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y / 3.f), true); - - ImGui::Text("功能"); - ImGui::Separator(); - ImGui::Spacing(); - - ImGui::Checkbox("无限耐力", &g.g_InfStamina); - ImGui::Checkbox("无跌落伤害", &g.g_NoFallDamage); - ImGui::Checkbox("无摇摆", &g.g_NoSway); - ImGui::Checkbox("无后座(内存)", &g.g_NoRecoil); - ImGui::Checkbox("无故障/卡弹", &g.g_NoMalfunction); - - ImGui::Spacing(); - ImGui::Separator(); - ImGui::TextDisabled("只写入 dump 已确认的对象字段"); - ImGui::TextDisabled("弹道追踪:暂不写入,等待 runtime 验证"); - ImGui::EndChild(); - - ImGui::BeginChild("##C021", ImVec2(ImGui::GetContentRegionAvail()), true); - - ImGui::EndChild(); - break; - case 3: // Setting - ImGui::BeginChild("##C030", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y / 3.f), true); - ImGui::Text("系统"); - ImGui::Separator(); - ImGui::Spacing(); - - ImGui::Checkbox("串流隐藏", &g.g_StreamProof); - - ImGui::EndChild(); - ImGui::BeginChild("##C031", ImVec2(ImGui::GetContentRegionAvail()), true); - - ImGui::Text("准星"); - ImGui::Separator(); - ImGui::Spacing(); - - ImGui::Checkbox("准星", &g.g_Crosshair); - ImGui::CustomSliderInt("准星大小", "##SizeCH", &g.g_CrosshairSize, 1, 10); - ImGui::ColorEdit4("颜色##C", &CrosshairColor.Value.x); - ImGui::Combo("类型##C", &g.g_CrosshairType, CrosshairList, IM_ARRAYSIZE(CrosshairList)); - - ImGui::EndChild(); - break; - default: - break; + if (UI::Tab("自瞄", currentPage == MenuPage::Aim)) + currentPage = MenuPage::Aim; + ImGui::SameLine(0.f, 6.f); + if (UI::Tab("视觉", currentPage == MenuPage::Visual)) + currentPage = MenuPage::Visual; + ImGui::SameLine(0.f, 6.f); + if (UI::Tab("功能", currentPage == MenuPage::Features)) + currentPage = MenuPage::Features; + ImGui::SameLine(0.f, 6.f); + if (UI::Tab("系统", currentPage == MenuPage::System)) + currentPage = MenuPage::System; + if (debug) + { + ImGui::SameLine(0.f, 6.f); + if (UI::Tab("调试", currentPage == MenuPage::Debug)) + currentPage = MenuPage::Debug; } + UI::Divider(); - ImGui::PopStyleColor(); - - ImGui::EndChild(); - //---------------------------------------------------// - - ImGui::SameLine(); - - //---// Right //--------------------------------------// - ImGui::BeginChild("##RightChild", ImVec2(ImGui::GetContentRegionAvail()), false); - - ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.1f, 0.1f, 0.1f, 1.f)); - - switch (Index) + const float footerHeight = UI::Theme::FooterHeight; + if (ImGui::BeginChild( + "##CFW_Content", + ImVec2(0.f, -footerHeight), + ImGuiChildFlags_None, + ImGuiWindowFlags_NoBackground)) { - case 0: // AimBot - ImGui::BeginChild("##C100", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y / 2.f), true); - - ImGui::Text("目标过滤"); - ImGui::Separator(); - ImGui::Spacing(); - - ImGui::Checkbox("PMC", &g.g_AimTargetPMC); - ImGui::Checkbox("SCAV", &g.g_AimTargetScav); - ImGui::Checkbox("BOSS", &g.g_AimTargetBoss); - ImGui::Checkbox("忽略血量", &g.g_AimIgnoreHealth); - ImGui::Checkbox("强制开启(免按键)", &g.g_AimForce); - ImGui::Checkbox("显示范围圈", &g.g_AimDrawFov); - ImGui::Checkbox("显示目标线", &g.g_AimDrawTarget); - - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - // 数据链路状态: 哪一环断了看这里 - ImGui::Text("自瞄状态"); - ImGui::Text("局内 : %s", runtimeInRaid ? "是" : "否"); - ImGui::Text("本地 : 0x%llX", (unsigned long long)runtimeLocalAddress); - ImGui::Text("实体 : %zu", runtimeEntityCount); - ImGui::Text("扫描 : %d 实体 / %d 血量 / %d 跳过", m_diagTotal, m_diagHealth, m_diagSkipped); - ImGui::Text("目标 : %s (距 %0.fpx)", m_diagBestAddr ? "锁定" : "无", m_diagBestDist); + const float gap = UI::Theme::ColumnGap; + const float columnWidth = UI::ColumnWidth(gap); + switch (currentPage) { - bool k2 = (GetAsyncKeyState(g.g_AimKey2) & 0x8000) != 0; - ImGui::Text("按键 : %s (右键/左键/F9)", (IsKeyDown(g.g_AimKey0) || IsKeyDown(g.g_AimKey1) || k2) ? "按住" : "---"); + case MenuPage::Aim: + RenderAimPage(columnWidth, gap); + break; + case MenuPage::Visual: + RenderVisualPage(columnWidth, gap); + break; + case MenuPage::Features: + RenderFeaturesPage(columnWidth, gap); + break; + case MenuPage::System: + RenderSystemPage(columnWidth, gap, configStatus); + break; + case MenuPage::Debug: + RenderDebugPage(columnWidth, gap); + break; } + } + ImGui::EndChild(); - ImGui::EndChild(); - ImGui::BeginChild("##C101", ImVec2(ImGui::GetContentRegionAvail()), true); + ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::Theme::WindowBackground); + if (ImGui::BeginChild( + "##CFW_Footer", + ImVec2(0.f, footerHeight), + ImGuiChildFlags_AlwaysUseWindowPadding, + ImGuiWindowFlags_NoScrollbar)) + { + UI::Divider(); + const float rowStart = ImGui::GetCursorPosX(); + const float rightEdge = rowStart + ImGui::GetContentRegionAvail().x; + constexpr float buttonWidth = 88.f; + constexpr float buttonGap = 8.f; + constexpr float actionsWidth = buttonWidth * 3.f + buttonGap * 2.f; - ImGui::Text("偏移调试(内存)"); - ImGui::Separator(); - ImGui::Spacing(); - - if (runtimeInRaid && runtimeLocalAddress != 0) - { - uintptr_t mc = m.Read(runtimeLocalAddress + (uintptr_t)g.g_AimMcOffset); - if (mc) - { - Vector2 rot = m.Read(mc + (uintptr_t)g.g_AimRotOffset); - ImGui::Text("水平 : %.3f", rot.x); - ImGui::Text("俯仰 : %.3f", rot.y); - ImGui::TextDisabled("移动鼠标验证偏移是否正确"); - } - else - { - ImGui::TextDisabled("MC 为空 (偏移错误?)"); - } - } - else - { - ImGui::TextDisabled("仅限局内显示"); - } - - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - ImGui::Text("偏移(十六进制)"); - ImGui::InputInt("MC", &g.g_AimMcOffset, 8, 8, ImGuiInputTextFlags_CharsHexadecimal); - ImGui::InputInt("旋转", &g.g_AimRotOffset, 4, 4, ImGuiInputTextFlags_CharsHexadecimal); - - // dump.txt: MovementContext = Player +0x58, Rotation_1 = MovementContext +0x27C - if (ImGui::Button("重置偏移 (0x58/0x27C)", ImVec2(ImGui::GetContentRegionAvail().x, 0.f))) - { - g.g_AimMcOffset = 0x58; - g.g_AimRotOffset = 0x27C; - } - - if (ImGui::Button("扫描旋转偏移(两步)", ImVec2(ImGui::GetContentRegionAvail().x, 0.f))) - ScanRotationOffset(); - - if (m_scanPhase == 1) - ImGui::TextColored(TEXT_COLOR_ATTENTION.Value, "水平转动鼠标后,再点一次扫描"); - - if (m_scanResult[0]) - ImGui::TextWrapped("%s", m_scanResult); - - ImGui::EndChild(); - break; - case 1: // Visual - ImGui::BeginChild("##C110", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y / 2.5f), true); - - ImGui::Text("ESP设置"); - ImGui::Separator(); - ImGui::Spacing(); - - ImGui::CustomSliderFloat("玩家", "##Dist", &g.g_ESP_MaxDistance, 100.f, 2000.f); - ImGui::CustomSliderFloat("物资", "##ItemDist", &g.g_ESP_MaxItemDistance, 10.f, 1000.f); - - ImGui::Spacing(); - ImGui::Spacing(); - - ImGui::Combo("方框样式", &g.g_ESP_BoxStyle, BoxTypeList, IM_ARRAYSIZE(BoxTypeList)); - - ImGui::EndChild(); - ImGui::BeginChild("##C111", ImVec2(ImGui::GetContentRegionAvail()), true); - - ImGui::Text("ESP颜色"); - ImGui::Separator(); - ImGui::Spacing(); - - if (ImGui::TreeNode("玩家/SCAV")) - { - ImGui::ColorEdit4("PMC", &Col_ESP_PMC.Value.x); - ImGui::ColorEdit4("SCAV", &Col_ESP_Scav.Value.x); - ImGui::ColorEdit4("玩家SCAV", &Col_ESP_pScav.Value.x); - ImGui::ColorEdit4("精英SCAV", &Col_ESP_SpecialScav.Value.x); - ImGui::ColorEdit4("BOSS", &Col_ESP_Boss.Value.x); - ImGui::ColorEdit4("填充", &Col_ESP_Filled.Value.x); - - ImGui::TreePop(); - } - if (ImGui::TreeNode("游戏")) - { - ImGui::Text("[+] 物资"); - ImGui::ColorEdit4("稀有", &Col_ESP_RareItem.Value.x); - - ImGui::NewLine(); - ImGui::Spacing(); - - ImGui::Text("[+] 撤离点"); - ImGui::ColorEdit4("开启", &Col_ESP_ExfilOpen.Value.x); - ImGui::ColorEdit4("关闭", &Col_ESP_ExfilClose.Value.x); - - ImGui::TreePop(); - } - - ImGui::EndChild(); - break; - case 2: // Misc - ImGui::BeginChild("##C200", ImVec2(ImGui::GetContentRegionAvail()), true); - - ImGui::Text("功能状态"); - ImGui::Separator(); - ImGui::Spacing(); - ImGui::Text("局内:%s", runtimeInRaid ? "是" : "否"); - ImGui::Text("本地:0x%llX", (unsigned long long)runtimeLocalAddress); - ImGui::TextDisabled("关闭开关或离开局内会恢复原值"); - ImGui::TextDisabled("非法/不完整指针链不会写入"); - - ImGui::EndChild(); - break; - case 3: // Setting - - ImGui::BeginChild("##C300", ImVec2(ImGui::GetContentRegionAvail()), true); - - ImGui::Text("退出"); - ImGui::Separator(); - ImGui::Spacing(); - if (ImGui::Button("退出", ImVec2(ImGui::GetContentRegionAvail().x, 30.f))) + ImGui::TextColored( + UI::Theme::TextSecondary, + "配置 %s · %s", + Config::FileName(), + configStatus); + const float actionX = (std::max)(ImGui::GetCursorPosX() + 12.f, rightEdge - actionsWidth); + ImGui::SameLine(actionX); + if (UI::Button("读取配置", ImVec2(buttonWidth, 30.f))) + configStatus = Config::Load() ? "配置已读取" : "配置读取失败(文件不存在或不可读)"; + ImGui::SameLine(0.f, buttonGap); + if (UI::Button("保存配置", ImVec2(buttonWidth, 30.f), false, true)) + configStatus = Config::Save() ? "配置已保存" : "配置保存失败"; + ImGui::SameLine(0.f, buttonGap); + if (UI::Button("退出", ImVec2(buttonWidth, 30.f), true)) g.process_active = false; - - ImGui::EndChild(); - break; - default: - break; } - + ImGui::EndChild(); ImGui::PopStyleColor(); - ImGui::EndChild(); - ImGui::EndChild(); - //---------------------------------------------------// - ImGui::End(); -} \ No newline at end of file +} diff --git a/CFramework/Cheat/MenuPages.cpp b/CFramework/Cheat/MenuPages.cpp new file mode 100644 index 0000000..6c371b9 --- /dev/null +++ b/CFramework/Cheat/MenuPages.cpp @@ -0,0 +1,309 @@ +#include "Framework.h" +#include "UI/MenuWidgets.h" +#include "../Framework/Config/Config.h" +#include "../Framework/Input/Input.h" +#include "../Framework/Memory/Memory.h" + +namespace +{ + const char* kBoxTypeList[] = { "简单", "棱角" }; + const char* kCrosshairList[] = { "十字", "圆圈" }; + const char* kAimMethodList[] = { "内存", "鼠标" }; + const char* kAimBoneList[] = { "头部", "颈部", "身体" }; + + void EndRow() + { + ImGui::Spacing(); + ImGui::Spacing(); + } +} + +void CFramework::RenderAimPage(float columnWidth, float gap) +{ + if (UI::BeginCard("##aim_basic", "基础设置", UI::Theme::Accent, columnWidth)) + { + UI::Toggle("自瞄", &g.g_Aim); + UI::Combo("方式", &g.g_AimMethod, kAimMethodList, IM_ARRAYSIZE(kAimMethodList)); + UI::Combo("部位", &g.g_AimBone, kAimBoneList, IM_ARRAYSIZE(kAimBoneList)); + UI::SliderFloat("锁定范围", &g.g_AimFov, 10.f, 500.f); + UI::SliderFloat("平滑", &g.g_AimSmooth, 0.f, 100.f); + UI::SliderFloat("最大距离", &g.g_AimMaxDist, 50.f, 2000.f); + UI::SliderFloat("灵敏度", &g.g_AimSens, 0.1f, 5.f, "%.2f"); + UI::SliderFloat("最大速度", &g.g_AimMaxSpeed, 1.f, 300.f); + } + UI::EndCard(); + UI::NextColumn(gap); + + if (UI::BeginCard("##aim_behavior", "行为设置", UI::Theme::AccentStrong, columnWidth)) + { + UI::Toggle("PMC", &g.g_AimTargetPMC); + UI::Toggle("SCAV", &g.g_AimTargetScav); + UI::Toggle("BOSS", &g.g_AimTargetBoss); + UI::Toggle("忽略血量", &g.g_AimIgnoreHealth); + UI::Toggle("强制开启(免按键)", &g.g_AimForce); + UI::Toggle("显示范围圈", &g.g_AimDrawFov); + UI::Toggle("显示目标线", &g.g_AimDrawTarget); + ImGui::Spacing(); + UI::StatusBadge("自瞄", g.g_Aim); + ImGui::SameLine(0.f, 8.f); + UI::StatusBadge("压枪", g.g_Recoil); + UI::Note("强制开启会绕过触发键;范围圈和目标线只影响可视化。 "); + } + UI::EndCard(); + EndRow(); + + if (UI::BeginCard("##aim_recoil", "压枪 / 后座", ImVec4(0.95f, 0.62f, 0.31f, 1.f), columnWidth)) + { + UI::Toggle("压枪", &g.g_Recoil); + UI::SliderFloat("压枪力度", &g.g_RecoilStrength, 0.f, 20.f); + UI::SliderInt("压枪间隔(ms)", &g.g_RecoilInterval, 1, 100); + UI::Note("力度和间隔直接绑定现有压枪参数,不改变原有更新路径。 "); + } + UI::EndCard(); + UI::NextColumn(gap); + + if (UI::BeginCard("##aim_hotkeys", "按键绑定", ImVec4(0.40f, 0.82f, 0.62f, 1.f), columnWidth)) + { + UI::Hotkey("菜单键", &g.g_MenuKey); + UI::Hotkey("触发键 1", &g.g_AimKey0, g.g_MenuKey); + UI::Hotkey("触发键 2", &g.g_AimKey1, g.g_MenuKey); + UI::Hotkey("备用键", &g.g_AimKey2, g.g_MenuKey); + UI::Note("点击右侧按键框后输入新键;显示 Press a key... 时按 Escape 取消。 "); + } + UI::EndCard(); + EndRow(); +} + +void CFramework::RenderVisualPage(float columnWidth, float gap) +{ + if (UI::BeginCard("##visual_modules", "ESP 模块", UI::Theme::Accent, columnWidth)) + { + UI::Toggle("ESP", &g.g_ESP); + UI::Toggle("物资 ESP", &g.g_ESP_Item); + UI::Toggle("尸体 ESP", &g.g_ESP_Corpse); + UI::Toggle("容器 ESP", &g.g_ESP_Container); + UI::Toggle("撤离点 ESP", &g.g_ESP_Exfil); + UI::Toggle("手雷 ESP", &g.g_ESP_Grenade); + } + UI::EndCard(); + UI::NextColumn(gap); + + if (UI::BeginCard("##visual_options", "显示选项", UI::Theme::AccentStrong, columnWidth)) + { + UI::Toggle("连线", &g.g_ESP_Line); + UI::Toggle("方框", &g.g_ESP_Box); + UI::Toggle("填充方框", &g.g_ESP_BoxFilled); + UI::Toggle("骨骼", &g.g_ESP_Skeleton); + UI::Toggle("距离", &g.g_ESP_Distance); + UI::Toggle("名称", &g.g_ESP_Name); + UI::Toggle("血条", &g.g_ESP_HealthBar); + } + UI::EndCard(); + EndRow(); + + if (UI::BeginCard("##visual_distance", "距离与样式", ImVec4(0.95f, 0.62f, 0.31f, 1.f), columnWidth)) + { + UI::SliderFloat("玩家距离", &g.g_ESP_MaxDistance, 100.f, 2000.f); + UI::SliderFloat("物资距离", &g.g_ESP_MaxItemDistance, 0.f, 1000.f); + UI::SliderFloat("容器距离", &g.g_ESP_MaxContainerDistance, 0.f, 1000.f); + UI::Combo("方框样式", &g.g_ESP_BoxStyle, kBoxTypeList, IM_ARRAYSIZE(kBoxTypeList)); + } + UI::EndCard(); + UI::NextColumn(gap); + + if (UI::BeginCard("##visual_crosshair", "准星", ImVec4(0.78f, 0.55f, 0.95f, 1.f), columnWidth)) + { + UI::Toggle("准星", &g.g_Crosshair); + UI::SliderInt("准星大小", &g.g_CrosshairSize, 1, 10); + UI::ColorEdit("准星颜色", &CrosshairColor.Value); + UI::Combo("准星类型", &g.g_CrosshairType, kCrosshairList, IM_ARRAYSIZE(kCrosshairList)); + } + UI::EndCard(); + EndRow(); + + if (UI::BeginCard("##visual_player_colors", "玩家颜色", UI::Theme::Accent, columnWidth)) + { + UI::ColorEdit("PMC", &Col_ESP_PMC.Value); + UI::ColorEdit("SCAV", &Col_ESP_Scav.Value); + UI::ColorEdit("玩家 SCAV", &Col_ESP_pScav.Value); + UI::ColorEdit("精英 SCAV", &Col_ESP_SpecialScav.Value); + UI::ColorEdit("BOSS", &Col_ESP_Boss.Value); + UI::ColorEdit("填充", &Col_ESP_Filled.Value); + } + UI::EndCard(); + UI::NextColumn(gap); + + if (UI::BeginCard("##visual_object_colors", "游戏对象颜色", ImVec4(0.40f, 0.82f, 0.62f, 1.f), columnWidth)) + { + UI::ColorEdit("稀有物资", &Col_ESP_RareItem.Value); + UI::ColorEdit("撤离点开启", &Col_ESP_ExfilOpen.Value); + UI::ColorEdit("撤离点关闭", &Col_ESP_ExfilClose.Value); + UI::Note("颜色编辑仍写入现有 ImColor 对象,渲染端无需改动。 "); + } + UI::EndCard(); + EndRow(); +} + +void CFramework::RenderFeaturesPage(float columnWidth, float gap) +{ + if (UI::BeginCard("##features_player", "角色增强", UI::Theme::Accent, columnWidth)) + { + UI::Toggle("无限耐力", &g.g_InfStamina); + UI::Toggle("无跌落伤害", &g.g_NoFallDamage); + UI::Toggle("无摇摆", &g.g_NoSway); + } + UI::EndCard(); + UI::NextColumn(gap); + + if (UI::BeginCard("##features_weapon", "武器增强", UI::Theme::AccentStrong, columnWidth)) + { + UI::Toggle("无后座(内存)", &g.g_NoRecoil); + UI::Toggle("无故障 / 卡弹", &g.g_NoMalfunction); + ImGui::Spacing(); + UI::StatusBadge("无限耐力", g.g_InfStamina); + ImGui::SameLine(0.f, 8.f); + UI::StatusBadge("无后座", g.g_NoRecoil); + ImGui::SameLine(0.f, 8.f); + UI::StatusBadge("无故障", g.g_NoMalfunction); + } + UI::EndCard(); + EndRow(); +} + +void CFramework::RenderSystemPage(float columnWidth, float gap, const char*& configStatus) +{ + if (UI::BeginCard("##system_switches", "系统", UI::Theme::Accent, columnWidth)) + { + UI::Toggle("串流隐藏", &g.g_StreamProof); + UI::Toggle("调试模式", &g.g_DebugMode); + UI::Toggle("准星", &g.g_Crosshair); + UI::Note("调试模式打开后会显示额外的偏移扫描与运行时诊断页。 "); + } + UI::EndCard(); + UI::NextColumn(gap); + + if (UI::BeginCard("##system_config", "配置", ImVec4(0.95f, 0.62f, 0.31f, 1.f), columnWidth)) + { + ImGui::TextColored(UI::Theme::TextPrimary, "文件 %s", Config::FileName()); + if (UI::Button("读取配置", ImVec2(112.f, 28.f))) + configStatus = Config::Load() ? "配置已读取" : "配置读取失败(文件不存在或不可读)"; + ImGui::SameLine(0.f, 8.f); + if (UI::Button("保存配置", ImVec2(112.f, 28.f), false, true)) + configStatus = Config::Save() ? "配置已保存" : "配置保存失败"; + UI::Note(configStatus); + UI::Note("配置文件保存/读取路径和字段集合保持不变。 "); + } + UI::EndCard(); + EndRow(); + + if (UI::BeginCard("##system_hotkeys", "快捷键", ImVec4(0.40f, 0.82f, 0.62f, 1.f), columnWidth)) + { + UI::Hotkey("菜单键", &g.g_MenuKey); + UI::Hotkey("触发键 1", &g.g_AimKey0, g.g_MenuKey); + UI::Hotkey("触发键 2", &g.g_AimKey1, g.g_MenuKey); + UI::Hotkey("备用键", &g.g_AimKey2, g.g_MenuKey); + } + UI::EndCard(); + UI::NextColumn(gap); + + if (UI::BeginCard("##system_crosshair", "准星", UI::Theme::AccentStrong, columnWidth)) + { + UI::SliderInt("准星大小", &g.g_CrosshairSize, 1, 10); + UI::ColorEdit("准星颜色", &CrosshairColor.Value); + UI::Combo("准星类型", &g.g_CrosshairType, kCrosshairList, IM_ARRAYSIZE(kCrosshairList)); + } + UI::EndCard(); + EndRow(); + + if (UI::BeginCard("##system_about", "关于", ImVec4(0.40f, 0.82f, 0.62f, 1.f), columnWidth)) + { + ImGui::TextColored(UI::Theme::TextPrimary, "PvE 战术控制台 v2.1"); + UI::Note("SPT-AKI 3.11.3 / 只读快照渲染"); + UI::Note("调试页签仅在调试模式开启时出现。 "); + } + UI::EndCard(); + UI::NextColumn(gap); + + if (UI::BeginCard("##system_status", "运行状态", UI::Theme::Accent, columnWidth)) + { + UI::StatusBadge("ESP", g.g_ESP); + ImGui::SameLine(0.f, 8.f); + UI::StatusBadge("串流", g.g_StreamProof); + ImGui::SameLine(0.f, 8.f); + UI::StatusBadge("调试", g.g_DebugMode); + } + UI::EndCard(); + EndRow(); +} + +void CFramework::RenderDebugPage(float columnWidth, float gap) +{ + if (UI::BeginCard("##debug_offsets", "偏移调试(内存)", UI::Theme::Danger, columnWidth)) + { + ImGui::TextColored(UI::Theme::TextSecondary, "运行时偏移仅在调试页修改,绑定保持原样。"); + ImGui::SetNextItemWidth(-1.f); + ImGui::InputInt("MC", &g.g_AimMcOffset, 8, 8, ImGuiInputTextFlags_CharsHexadecimal); + ImGui::SetNextItemWidth(-1.f); + ImGui::InputInt("旋转", &g.g_AimRotOffset, 4, 4, ImGuiInputTextFlags_CharsHexadecimal); + if (UI::Button("重置偏移(0x58 / 0x27C)", ImVec2(-1.f, 28.f))) + { + g.g_AimMcOffset = 0x58; + g.g_AimRotOffset = 0x27C; + } + if (UI::Button("扫描旋转偏移(两步)", ImVec2(-1.f, 28.f))) + ScanRotationOffset(); + if (m_scanPhase == 1) + ImGui::TextColored(TEXT_COLOR_ATTENTION.Value, "水平转动鼠标后,再点一次扫描"); + if (m_scanResult[0]) + ImGui::TextWrapped("%s", m_scanResult); + } + UI::EndCard(); + UI::NextColumn(gap); + + if (UI::BeginCard("##debug_status", "自瞄状态", UI::Theme::Danger, columnWidth)) + { + const auto runtimeSnapshot = GetDynamicSnapshot(); + const bool runtimeFresh = runtimeSnapshot && IsSnapshotFresh(runtimeSnapshot->capturedAt); + const bool runtimeInRaid = runtimeFresh && runtimeSnapshot->inRaid; + const uintptr_t runtimeLocalAddress = runtimeFresh ? runtimeSnapshot->local.m_address : 0; + const std::size_t runtimeEntityCount = runtimeFresh ? runtimeSnapshot->players.size() : 0; + + ImGui::Text("局内 : %s", runtimeInRaid ? "是" : "否"); + ImGui::Text("本地 : 0x%llX", static_cast(runtimeLocalAddress)); + ImGui::Text("实体 : %zu", runtimeEntityCount); + ImGui::Text("扫描 : %d 实体 / %d 血量 / %d 跳过", m_diagTotal, m_diagHealth, m_diagSkipped); + ImGui::Text("目标 : %s(距 %.0fpx)", m_diagBestAddr ? "锁定" : "无", m_diagBestDist); + const bool k2 = (GetAsyncKeyState(g.g_AimKey2) & 0x8000) != 0; + ImGui::Text("按键 : %s(当前触发键)", + (IsKeyDown(g.g_AimKey0) || IsKeyDown(g.g_AimKey1) || k2) ? "按住" : "---"); + + if (runtimeInRaid && runtimeLocalAddress != 0) + { + const uintptr_t mc = m.Read(runtimeLocalAddress + static_cast(g.g_AimMcOffset)); + if (mc) + { + const Vector2 rot = m.Read(mc + static_cast(g.g_AimRotOffset)); + ImGui::Text("水平 : %.3f", rot.x); + ImGui::Text("俯仰 : %.3f", rot.y); + UI::Note("移动鼠标验证偏移是否正确。 "); + } + else + { + UI::Note("MC 为空(偏移错误?)"); + } + } + else + { + UI::Note("仅限局内显示。 "); + } + } + UI::EndCard(); + EndRow(); + + if (UI::BeginCard("##debug_notes", "调试说明", UI::Theme::Danger, columnWidth)) + { + UI::Note("此页仅调试模式可见。 "); + UI::Note("偏移调试、扫描与诊断读取都在此页。 "); + } + UI::EndCard(); +} diff --git a/CFramework/Cheat/UI/MenuTheme.cpp b/CFramework/Cheat/UI/MenuTheme.cpp new file mode 100644 index 0000000..a7cf7f1 --- /dev/null +++ b/CFramework/Cheat/UI/MenuTheme.cpp @@ -0,0 +1,71 @@ +#include "MenuTheme.h" + +namespace UI::Theme +{ + const ImVec4 WindowBackground{ 0.067f, 0.075f, 0.094f, 1.f }; // #111318 + const ImVec4 CardBackground{ 0.090f, 0.102f, 0.125f, 1.f }; // #171A20 + const ImVec4 SecondaryBackground{ 0.114f, 0.129f, 0.161f, 1.f }; // #1D2129 + const ImVec4 SecondaryHover{ 0.150f, 0.173f, 0.216f, 1.f }; + const ImVec4 TextPrimary{ 0.910f, 0.922f, 0.941f, 1.f }; // #E8EBF0 + const ImVec4 TextSecondary{ 0.545f, 0.576f, 0.647f, 1.f }; // #8B93A5 + const ImVec4 Accent{ 0.286f, 0.827f, 0.729f, 1.f }; // cyan teal + const ImVec4 AccentStrong{ 0.400f, 0.898f, 0.804f, 1.f }; + const ImVec4 AccentSoft{ 0.286f, 0.827f, 0.729f, 0.18f }; + const ImVec4 Border{ 1.f, 1.f, 1.f, 0.07f }; + const ImVec4 ToggleOff{ 0.235f, 0.255f, 0.306f, 1.f }; + const ImVec4 Danger{ 0.930f, 0.330f, 0.350f, 1.f }; + const ImVec4 DangerSoft{ 0.390f, 0.150f, 0.170f, 1.f }; + + ImU32 U32(const ImVec4& color) + { + return ImGui::GetColorU32(color); + } + + void ApplyTheme() + { + ImGuiStyle& style = ImGui::GetStyle(); + style.WindowRounding = WindowRounding; + style.ChildRounding = CardRounding; + style.FrameRounding = FrameRounding; + style.PopupRounding = PopupRounding; + style.ScrollbarRounding = ScrollbarRounding; + style.GrabRounding = GrabRounding; + style.WindowPadding = ImVec2(16.f, 14.f); + style.FramePadding = ImVec2(10.f, 7.f); + style.ItemSpacing = ImVec2(10.f, 8.f); + style.ItemInnerSpacing = ImVec2(8.f, 6.f); + style.ScrollbarSize = 10.f; + style.GrabMinSize = 10.f; + style.WindowBorderSize = 0.f; + style.ChildBorderSize = 1.f; + style.FrameBorderSize = 0.f; + style.PopupBorderSize = 1.f; + + style.Colors[ImGuiCol_WindowBg] = WindowBackground; + style.Colors[ImGuiCol_ChildBg] = CardBackground; + style.Colors[ImGuiCol_PopupBg] = SecondaryBackground; + style.Colors[ImGuiCol_Border] = Border; + style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.f, 0.f, 0.f, 0.f); + style.Colors[ImGuiCol_FrameBg] = SecondaryBackground; + style.Colors[ImGuiCol_FrameBgHovered] = SecondaryHover; + style.Colors[ImGuiCol_FrameBgActive] = AccentSoft; + style.Colors[ImGuiCol_TitleBg] = WindowBackground; + style.Colors[ImGuiCol_TitleBgActive] = WindowBackground; + style.Colors[ImGuiCol_Button] = SecondaryBackground; + style.Colors[ImGuiCol_ButtonHovered] = SecondaryHover; + style.Colors[ImGuiCol_ButtonActive] = AccentSoft; + style.Colors[ImGuiCol_Header] = SecondaryBackground; + style.Colors[ImGuiCol_HeaderHovered] = SecondaryHover; + style.Colors[ImGuiCol_HeaderActive] = AccentSoft; + style.Colors[ImGuiCol_SliderGrab] = Accent; + style.Colors[ImGuiCol_SliderGrabActive] = AccentStrong; + style.Colors[ImGuiCol_CheckMark] = Accent; + style.Colors[ImGuiCol_Separator] = Border; + style.Colors[ImGuiCol_SeparatorHovered] = AccentSoft; + style.Colors[ImGuiCol_SeparatorActive] = Accent; + style.Colors[ImGuiCol_Text] = TextPrimary; + style.Colors[ImGuiCol_TextDisabled] = TextSecondary; + style.Colors[ImGuiCol_TextSelectedBg] = AccentSoft; + style.Colors[ImGuiCol_NavHighlight] = AccentSoft; + } +} diff --git a/CFramework/Cheat/UI/MenuTheme.h b/CFramework/Cheat/UI/MenuTheme.h new file mode 100644 index 0000000..d348435 --- /dev/null +++ b/CFramework/Cheat/UI/MenuTheme.h @@ -0,0 +1,33 @@ +#pragma once + +#include "../../Framework/ImGui/imgui.h" + +namespace UI::Theme +{ + extern const ImVec4 WindowBackground; + extern const ImVec4 CardBackground; + extern const ImVec4 SecondaryBackground; + extern const ImVec4 SecondaryHover; + extern const ImVec4 TextPrimary; + extern const ImVec4 TextSecondary; + extern const ImVec4 Accent; + extern const ImVec4 AccentStrong; + extern const ImVec4 AccentSoft; + extern const ImVec4 Border; + extern const ImVec4 ToggleOff; + extern const ImVec4 Danger; + extern const ImVec4 DangerSoft; + + constexpr float WindowRounding = 10.f; + constexpr float CardRounding = 8.f; + constexpr float FrameRounding = 6.f; + constexpr float PopupRounding = 8.f; + constexpr float ScrollbarRounding = 6.f; + constexpr float GrabRounding = 6.f; + constexpr float HeaderHeight = 52.f; + constexpr float FooterHeight = 80.f; + constexpr float ColumnGap = 14.f; + + ImU32 U32(const ImVec4& color); + void ApplyTheme(); +} diff --git a/CFramework/Cheat/UI/MenuWidgets.cpp b/CFramework/Cheat/UI/MenuWidgets.cpp new file mode 100644 index 0000000..cad7a0f --- /dev/null +++ b/CFramework/Cheat/UI/MenuWidgets.cpp @@ -0,0 +1,447 @@ +#include "MenuWidgets.h" + +#include +#include +#include +#include +#include +#include + +namespace +{ + std::unordered_map g_toggleAnimation; + ImGuiID g_captureId = 0; + bool g_captureArmed = false; + + ImVec4 Mix(const ImVec4& a, const ImVec4& b, float t) + { + return ImVec4( + a.x + (b.x - a.x) * t, + a.y + (b.y - a.y) * t, + a.z + (b.z - a.z) * t, + a.w + (b.w - a.w) * t); + } + + bool AnyVirtualKeyDown() + { + for (int vk = 1; vk < 256; ++vk) + { + if ((GetAsyncKeyState(vk) & 0x8000) != 0) + return true; + } + return false; + } + + int ReadVirtualKey() + { + for (int vk = 1; vk < 256; ++vk) + { + if ((GetAsyncKeyState(vk) & 0x8000) != 0) + return vk; + } + return 0; + } + + std::string VirtualKeyName(int vk) + { + switch (vk) + { + case VK_LBUTTON: return "LMB"; + case VK_RBUTTON: return "RMB"; + case VK_MBUTTON: return "MMB"; + case VK_XBUTTON1: return "X1"; + case VK_XBUTTON2: return "X2"; + default: break; + } + + char keyName[64]{}; + const UINT scanCode = MapVirtualKeyA(static_cast(vk), MAPVK_VK_TO_VSC); + LONG keyData = static_cast(scanCode << 16); + switch (vk) + { + case VK_LEFT: + case VK_UP: + case VK_RIGHT: + case VK_DOWN: + case VK_PRIOR: + case VK_NEXT: + case VK_END: + case VK_HOME: + case VK_INSERT: + case VK_DELETE: + case VK_DIVIDE: + case VK_NUMLOCK: + keyData |= 1 << 24; + break; + default: + break; + } + + if (GetKeyNameTextA(keyData, keyName, static_cast(sizeof(keyName))) > 0) + return keyName; + + char fallback[16]{}; + sprintf_s(fallback, "VK %d", vk); + return fallback; + } + + bool DrawSliderTrack(const char* label, float& fraction, const char* valueText) + { + ImGui::PushID(label); + const float rowStart = ImGui::GetCursorPosX(); + const float contentRight = rowStart + ImGui::GetContentRegionAvail().x; + + ImGui::TextUnformatted(label); + const float labelEnd = ImGui::GetCursorPosX(); + const float valueWidth = 54.f; + const float gap = 8.f; + ImGui::SameLine(labelEnd + 10.f); + const float barStartX = ImGui::GetCursorPosX(); + const float barWidth = (std::max)(64.f, contentRight - barStartX - valueWidth - gap); + const ImVec2 barPos = ImGui::GetCursorScreenPos(); + ImGui::InvisibleButton("##slider", ImVec2(barWidth, 24.f)); + + const bool hovered = ImGui::IsItemHovered(); + const bool active = ImGui::IsItemActive(); + bool changed = false; + if ((hovered || active) && ImGui::IsMouseDown(ImGuiMouseButton_Left)) + { + const float next = (std::clamp)((ImGui::GetIO().MousePos.x - barPos.x) / barWidth, 0.f, 1.f); + changed = next != fraction; + fraction = next; + } + + const ImVec2 trackMin(barPos.x, barPos.y + 9.f); + const ImVec2 trackMax(barPos.x + barWidth, barPos.y + 15.f); + const ImVec2 fillMax(barPos.x + barWidth * fraction, barPos.y + 15.f); + const ImU32 trackColor = UI::Theme::U32(hovered ? UI::Theme::SecondaryHover : UI::Theme::ToggleOff); + const ImU32 fillColor = UI::Theme::U32(hovered ? UI::Theme::AccentStrong : UI::Theme::Accent); + ImDrawList* drawList = ImGui::GetWindowDrawList(); + drawList->AddRectFilled(trackMin, trackMax, trackColor, 3.f); + if (fraction > 0.f) + drawList->AddRectFilled(trackMin, fillMax, fillColor, 3.f); + drawList->AddCircleFilled(ImVec2(fillMax.x, barPos.y + 12.f), hovered || active ? 5.f : 4.f, fillColor, 16); + + ImGui::SameLine(0.f, gap); + ImGui::TextColored(UI::Theme::TextPrimary, "%s", valueText); + ImGui::PopID(); + return changed; + } +} + +namespace UI +{ + bool BeginCard(const char* id, const char* title, const ImVec4& accent, float width) + { + ImGui::PushStyleColor(ImGuiCol_ChildBg, Theme::CardBackground); + ImGui::PushStyleColor(ImGuiCol_Border, Theme::Border); + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, Theme::CardRounding); + ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 1.f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(14.f, 12.f)); + + const float cardWidth = width > 0.f ? width : ImGui::GetContentRegionAvail().x; + const bool visible = ImGui::BeginChild( + id, + ImVec2(cardWidth, 0.f), + ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysUseWindowPadding, + ImGuiWindowFlags_NoScrollbar); + if (visible) + SectionTitle(title, accent); + return visible; + } + + void EndCard() + { + ImGui::EndChild(); + ImGui::PopStyleVar(3); + ImGui::PopStyleColor(2); + } + + bool Toggle(const char* label, bool* value) + { + if (!value) + return false; + + ImGui::PushID(label); + const float rowStart = ImGui::GetCursorPosX(); + const float contentRight = rowStart + ImGui::GetContentRegionAvail().x; + const float switchWidth = 44.f; + const float switchHeight = 24.f; + + ImGui::TextColored(Theme::TextPrimary, "%s", label); + const float labelEnd = ImGui::GetCursorPosX(); + ImGui::SameLine((std::max)(labelEnd + 8.f, contentRight - switchWidth)); + const ImVec2 switchPos = ImGui::GetCursorScreenPos(); + const bool clicked = ImGui::InvisibleButton("##toggle", ImVec2(switchWidth, switchHeight)); + if (clicked) + *value = !*value; + + const ImGuiID animationId = ImGui::GetID("##toggle"); + float& transition = g_toggleAnimation[animationId]; + const float target = *value ? 1.f : 0.f; + transition += (target - transition) * (std::min)(1.f, ImGui::GetIO().DeltaTime * 14.f); + if (std::abs(target - transition) < 0.001f) + transition = target; + + const ImVec4 track = Mix(Theme::ToggleOff, Theme::Accent, transition); + const ImVec2 trackMax(switchPos.x + switchWidth, switchPos.y + switchHeight); + const float knobX = switchPos.x + 4.f + transition * (switchWidth - switchHeight); + ImDrawList* drawList = ImGui::GetWindowDrawList(); + drawList->AddRectFilled(switchPos, trackMax, Theme::U32(track), switchHeight * 0.5f); + drawList->AddCircleFilled( + ImVec2(knobX + (switchHeight - 8.f) * 0.5f, switchPos.y + switchHeight * 0.5f), + (switchHeight - 8.f) * 0.5f, + Theme::U32(Theme::TextPrimary), + 16); + + ImGui::PopID(); + return clicked; + } + + bool SliderFloat(const char* label, float* value, float vmin, float vmax, const char* format) + { + if (!value || vmax <= vmin) + return false; + + char valueText[32]{}; + sprintf_s(valueText, format, *value); + float fraction = (std::clamp)((*value - vmin) / (vmax - vmin), 0.f, 1.f); + const bool changed = DrawSliderTrack(label, fraction, valueText); + if (changed) + *value = vmin + fraction * (vmax - vmin); + return changed; + } + + bool SliderInt(const char* label, int* value, int vmin, int vmax, const char* format) + { + if (!value || vmax <= vmin) + return false; + + char valueText[32]{}; + sprintf_s(valueText, format, *value); + float fraction = (std::clamp)(static_cast(*value - vmin) / static_cast(vmax - vmin), 0.f, 1.f); + const bool changed = DrawSliderTrack(label, fraction, valueText); + if (changed) + *value = vmin + static_cast(fraction * static_cast(vmax - vmin) + 0.5f); + return changed; + } + + bool Combo(const char* label, int* value, const char* const items[], int count) + { + if (!value || !items || count <= 0) + return false; + + ImGui::PushID(label); + const float rowStart = ImGui::GetCursorPosX(); + const float contentRight = rowStart + ImGui::GetContentRegionAvail().x; + ImGui::TextColored(Theme::TextPrimary, "%s", label); + const float labelEnd = ImGui::GetCursorPosX(); + const float comboWidth = (std::min)(160.f, (std::max)(92.f, contentRight - labelEnd - 10.f)); + ImGui::SameLine(contentRight - comboWidth); + ImGui::SetNextItemWidth(comboWidth); + const bool changed = ImGui::Combo("##combo", value, items, count); + ImGui::PopID(); + return changed; + } + + bool ColorEdit(const char* label, ImVec4* color) + { + if (!color) + return false; + + ImGui::PushID(label); + const float rowStart = ImGui::GetCursorPosX(); + const float contentRight = rowStart + ImGui::GetContentRegionAvail().x; + const float swatchWidth = 28.f; + ImGui::TextColored(Theme::TextPrimary, "%s", label); + const float labelEnd = ImGui::GetCursorPosX(); + ImGui::SameLine((std::max)(labelEnd + 8.f, contentRight - swatchWidth)); + const ImVec2 pos = ImGui::GetCursorScreenPos(); + const bool clicked = ImGui::InvisibleButton("##color", ImVec2(swatchWidth, 24.f)); + if (clicked) + ImGui::OpenPopup("##color_popup"); + + ImDrawList* drawList = ImGui::GetWindowDrawList(); + drawList->AddRectFilled(pos, ImVec2(pos.x + swatchWidth, pos.y + 24.f), ImGui::GetColorU32(*color), 5.f); + drawList->AddRect(pos, ImVec2(pos.x + swatchWidth, pos.y + 24.f), Theme::U32(Theme::Border), 5.f); + + bool changed = false; + if (ImGui::BeginPopup("##color_popup")) + { + changed = ImGui::ColorEdit4("##picker", &color->x, ImGuiColorEditFlags_NoInputs); + ImGui::EndPopup(); + } + ImGui::PopID(); + return changed; + } + + bool Hotkey(const char* label, int* value, int blockedKey) + { + if (!value) + return false; + + ImGui::PushID(label); + const ImGuiID id = ImGui::GetID("##hotkey"); + const float rowStart = ImGui::GetCursorPosX(); + const float contentRight = rowStart + ImGui::GetContentRegionAvail().x; + const float controlWidth = 132.f; + const float controlHeight = 26.f; + ImGui::TextColored(Theme::TextPrimary, "%s", label); + const float labelEnd = ImGui::GetCursorPosX(); + ImGui::SameLine((std::max)(labelEnd + 8.f, contentRight - controlWidth)); + const ImVec2 pos = ImGui::GetCursorScreenPos(); + const bool clicked = ImGui::InvisibleButton("##hotkey", ImVec2(controlWidth, controlHeight)); + if (clicked) + { + g_captureId = id; + g_captureArmed = false; + } + + if (g_captureId == id) + { + if (!g_captureArmed) + { + if (!AnyVirtualKeyDown()) + g_captureArmed = true; + } + else + { + const int captured = ReadVirtualKey(); + if (captured != 0) + { + if (captured == VK_ESCAPE) + { + g_captureId = 0; + } + else if (captured != blockedKey) + { + *value = captured; + g_captureId = 0; + } + } + } + } + + const bool waiting = g_captureId == id; + const std::string text = waiting ? "Press a key..." : VirtualKeyName(*value); + const ImU32 fill = waiting ? Theme::U32(Theme::AccentSoft) : Theme::U32(Theme::SecondaryBackground); + const ImU32 border = waiting ? Theme::U32(Theme::Accent) : Theme::U32(Theme::Border); + ImDrawList* drawList = ImGui::GetWindowDrawList(); + drawList->AddRectFilled(pos, ImVec2(pos.x + controlWidth, pos.y + controlHeight), fill, 6.f); + drawList->AddRect(pos, ImVec2(pos.x + controlWidth, pos.y + controlHeight), border, 6.f); + const ImVec2 textSize = ImGui::CalcTextSize(text.c_str()); + drawList->AddText( + ImVec2(pos.x + (controlWidth - textSize.x) * 0.5f, pos.y + (controlHeight - textSize.y) * 0.5f), + Theme::U32(waiting ? Theme::AccentStrong : Theme::TextPrimary), + text.c_str()); + + ImGui::PopID(); + return clicked; + } + + bool Tab(const char* label, bool active) + { + ImGui::PushID(label); + const ImVec2 textSize = ImGui::CalcTextSize(label); + const ImVec2 pos = ImGui::GetCursorScreenPos(); + const ImVec2 size(textSize.x + 28.f, 36.f); + const bool clicked = ImGui::InvisibleButton("##tab", size); + const bool hovered = ImGui::IsItemHovered(); + ImDrawList* drawList = ImGui::GetWindowDrawList(); + if (active || hovered) + { + const ImVec4 bg = active ? Theme::AccentSoft : Theme::SecondaryBackground; + drawList->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y), Theme::U32(bg), 6.f); + } + if (active) + { + drawList->AddRectFilled( + ImVec2(pos.x + 8.f, pos.y + size.y - 3.f), + ImVec2(pos.x + size.x - 8.f, pos.y + size.y), + Theme::U32(Theme::Accent), + 1.5f); + } + const ImVec4 textColor = active ? Theme::TextPrimary : (hovered ? Theme::TextPrimary : Theme::TextSecondary); + drawList->AddText( + ImVec2(pos.x + (size.x - textSize.x) * 0.5f, pos.y + (size.y - textSize.y) * 0.5f), + Theme::U32(textColor), + label); + ImGui::PopID(); + return clicked; + } + + bool Button(const char* label, const ImVec2& size, bool danger, bool primary) + { + const ImVec4 normal = danger ? Theme::DangerSoft : (primary ? Theme::Accent : Theme::SecondaryBackground); + const ImVec4 hovered = danger ? Theme::Danger : (primary ? Theme::AccentStrong : Theme::SecondaryHover); + const ImVec4 active = danger ? Theme::Danger : Theme::AccentStrong; + ImGui::PushStyleColor(ImGuiCol_Button, normal); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, hovered); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, active); + ImGui::PushStyleColor(ImGuiCol_Text, primary ? Theme::WindowBackground : Theme::TextPrimary); + const bool clicked = ImGui::Button(label, size); + ImGui::PopStyleColor(4); + return clicked; + } + + void SectionTitle(const char* title, const ImVec4& accent) + { + const ImVec2 pos = ImGui::GetCursorScreenPos(); + ImGui::GetWindowDrawList()->AddRectFilled( + pos, + ImVec2(pos.x + 30.f, pos.y + 3.f), + Theme::U32(accent), + 1.5f); + ImGui::Dummy(ImVec2(0.f, 7.f)); + ImGui::TextColored(Theme::TextPrimary, "%s", title); + ImGui::Dummy(ImVec2(0.f, 3.f)); + } + + void StatusBadge(const char* label, bool on) + { + const char* suffix = on ? "ON" : "OFF"; + const ImVec2 labelSize = ImGui::CalcTextSize(label); + const ImVec2 suffixSize = ImGui::CalcTextSize(suffix); + const float width = labelSize.x + suffixSize.x + 31.f; + const float height = 22.f; + const ImVec2 pos = ImGui::GetCursorScreenPos(); + ImGui::Dummy(ImVec2(width, height)); + + const ImVec4 background = on ? Theme::AccentSoft : Theme::SecondaryBackground; + const ImVec4 foreground = on ? Theme::AccentStrong : Theme::TextSecondary; + ImDrawList* drawList = ImGui::GetWindowDrawList(); + drawList->AddRectFilled(pos, ImVec2(pos.x + width, pos.y + height), Theme::U32(background), 11.f); + drawList->AddCircleFilled(ImVec2(pos.x + 10.f, pos.y + height * 0.5f), 3.f, Theme::U32(foreground), 12); + drawList->AddText(ImVec2(pos.x + 17.f, pos.y + 3.f), Theme::U32(Theme::TextPrimary), label); + drawList->AddText(ImVec2(pos.x + 17.f + labelSize.x + 5.f, pos.y + 3.f), Theme::U32(foreground), suffix); + } + + void Note(const char* text) + { + ImGui::PushTextWrapPos(0.f); + ImGui::TextColored(Theme::TextSecondary, "%s", text); + ImGui::PopTextWrapPos(); + } + + void Divider() + { + const ImVec2 pos = ImGui::GetCursorScreenPos(); + const float width = ImGui::GetContentRegionAvail().x; + ImGui::GetWindowDrawList()->AddLine( + ImVec2(pos.x, pos.y + 4.f), + ImVec2(pos.x + width, pos.y + 4.f), + Theme::U32(Theme::Border), + 1.f); + ImGui::Dummy(ImVec2(width, 12.f)); + } + + float ColumnWidth(float gap) + { + return (std::max)(220.f, (ImGui::GetContentRegionAvail().x - gap) * 0.5f); + } + + void NextColumn(float gap) + { + ImGui::SameLine(0.f, gap); + } +} diff --git a/CFramework/Cheat/UI/MenuWidgets.h b/CFramework/Cheat/UI/MenuWidgets.h new file mode 100644 index 0000000..46cf011 --- /dev/null +++ b/CFramework/Cheat/UI/MenuWidgets.h @@ -0,0 +1,27 @@ +#pragma once + +#include "../../Framework/ImGui/imgui.h" +#include "MenuTheme.h" + +namespace UI +{ + bool BeginCard(const char* id, const char* title, const ImVec4& accent, float width = 0.f); + void EndCard(); + + bool Toggle(const char* label, bool* value); + bool SliderFloat(const char* label, float* value, float vmin, float vmax, const char* format = "%.0f"); + bool SliderInt(const char* label, int* value, int vmin, int vmax, const char* format = "%d"); + bool Combo(const char* label, int* value, const char* const items[], int count); + bool ColorEdit(const char* label, ImVec4* color); + bool Hotkey(const char* label, int* value, int blockedKey = 0); + + bool Tab(const char* label, bool active); + bool Button(const char* label, const ImVec2& size, bool danger = false, bool primary = false); + void SectionTitle(const char* title, const ImVec4& accent); + void StatusBadge(const char* label, bool on); + void Note(const char* text); + void Divider(); + + float ColumnWidth(float gap = Theme::ColumnGap); + void NextColumn(float gap = Theme::ColumnGap); +}