From 1f00de66fd721461bd8a554d4bfa94724fca5cc3 Mon Sep 17 00:00:00 2001 From: UncraftedName Date: Wed, 18 Sep 2024 18:22:56 -0700 Subject: [PATCH] Add a bunch of QoL/cheat features to imgui - Add QoL & cheat tab - Add IHUD/JHUD tabs in addition to text HUD tab - Add misc section to drawing tab - Add RTA time to timer feature --- spt/features/autojump.cpp | 12 ++ spt/features/boog.cpp | 12 ++ spt/features/collision_group.cpp | 48 ++++-- spt/features/con_notify.cpp | 3 + spt/features/demo.cpp | 9 +- spt/features/game_fixes/fastload.cpp | 14 +- spt/features/game_fixes/free_oob.cpp | 6 +- spt/features/game_fixes/multi_instance.cpp | 65 +++++++- spt/features/game_fixes/noclip_fixes.cpp | 12 ++ spt/features/game_fixes/nosleep.cpp | 3 + spt/features/game_fixes/snapshot_overflow.cpp | 4 + spt/features/game_fixes/vag_crash.cpp | 6 +- spt/features/game_fixes/visual_fixes.cpp | 17 +- spt/features/hops_hud.cpp | 24 ++- spt/features/ihud.cpp | 16 ++ spt/features/isg.cpp | 2 +- spt/features/leafvis.cpp | 37 ++++- spt/features/movement_vars.cpp | 14 +- spt/features/pause.cpp | 14 +- spt/features/shadow.cpp | 21 ++- spt/features/shadow.hpp | 1 + spt/features/tickrate.cpp | 17 +- spt/features/timer.cpp | 78 +++++++-- spt/features/visualizations/draw_seams.cpp | 3 + .../visualizations/imgui/imgui_interface.hpp | 83 ++++++++-- .../imgui/spt_imgui_widgets.cpp | 150 +++++++++++++++++- spt/features/visualizations/oob_ents.cpp | 13 ++ 27 files changed, 615 insertions(+), 69 deletions(-) diff --git a/spt/features/autojump.cpp b/spt/features/autojump.cpp index c89f3fb08..1c918de21 100644 --- a/spt/features/autojump.cpp +++ b/spt/features/autojump.cpp @@ -7,6 +7,7 @@ #include "dbg.h" #include "signals.hpp" #include "game_detection.hpp" +#include "visualizations\imgui\imgui_interface.hpp" #ifdef OE #include "mathlib.h" @@ -187,6 +188,15 @@ void AutojumpFeature::LoadFeature() else // dmomm off_player_ptr = 4; + + SptImGuiGroup::Cheats_Jumping.RegisterUserCallback( + []() + { + SptImGui::CvarCheckbox(y_spt_autojump, "##checkbox_autojump"); + SptImGui::CvarCheckbox(_y_spt_autojump_ensure_legit, "##checkbox_legit"); + const char* opts[] = {"Default", "ABH jumpboost", "OE jumpboost", "No jumpboost"}; + SptImGui::CvarCombo(y_spt_jumpboost, "jumpboost type", opts, ARRAYSIZE(opts)); + }); } else { @@ -202,6 +212,8 @@ void AutojumpFeature::LoadFeature() if (utils::DoesGameLookLikePortal() && ORIG_CGameMovement__AirMove && ORIG_CPortalGameMovement__AirMove) { InitConcommandBase(y_spt_aircontrol); + SptImGuiGroup::Cheats_HL2AirControl.RegisterUserCallback( + []() { SptImGui::CvarCheckbox(y_spt_aircontrol, "##checkbox"); }); } } diff --git a/spt/features/boog.cpp b/spt/features/boog.cpp index c59711aee..ddd74a328 100644 --- a/spt/features/boog.cpp +++ b/spt/features/boog.cpp @@ -10,6 +10,7 @@ #include "playerio.hpp" #include "signals.hpp" #include "game_detection.hpp" +#include "visualizations\imgui\imgui_interface.hpp" ConVar y_spt_hud_edgebug("y_spt_hud_edgebug", "0", @@ -177,6 +178,17 @@ void BoogFeature::LoadFeature() { InitConcommandBase(y_spt_hud_edgebug); InitConcommandBase(y_spt_hud_edgebug_sec); + + SptImGui::RegisterHudCvarCallback( + y_spt_hud_edgebug, + [](ConVar& var) + { + SptImGui::CvarCheckbox(y_spt_hud_edgebug, "##checkbox"); + SptImGui::CvarFloat(y_spt_hud_edgebug_sec, + "edgebug display time", + "enter time in seconds"); + }, + true); } } } diff --git a/spt/features/collision_group.cpp b/spt/features/collision_group.cpp index c91362aa4..02b79a321 100644 --- a/spt/features/collision_group.cpp +++ b/spt/features/collision_group.cpp @@ -1,24 +1,26 @@ #include "stdafx.hpp" #include "ent_utils.hpp" #include "..\feature.hpp" +#include "visualizations\imgui\imgui_interface.hpp" namespace patterns { PATTERNS(SetCollisionGroup, - "4044", - "8B 54 24 ?? 8B 81 ?? ?? ?? ?? 3B C2", - "5135", - "56 8B F1 8B 86 ?? ?? ?? ?? 3B 44 24 08 8D", - "7467727", - "55 8B EC 53 8B 5D ?? 56 57 8B F9 39 9F ?? ?? ?? ?? 74 ?? 8B ?? ?? ?? ?? ?? 8D", - "BMS-Retail", - "55 8B EC 53 8B D9 56 57 8B 7D ?? 39 BB ?? ?? ?? ?? 74 ?? 80 79 ?? 00"); + "4044", + "8B 54 24 ?? 8B 81 ?? ?? ?? ?? 3B C2", + "5135", + "56 8B F1 8B 86 ?? ?? ?? ?? 3B 44 24 08 8D", + "7467727", + "55 8B EC 53 8B 5D ?? 56 57 8B F9 39 9F ?? ?? ?? ?? 74 ?? 8B ?? ?? ?? ?? ?? 8D", + "BMS-Retail", + "55 8B EC 53 8B D9 56 57 8B 7D ?? 39 BB ?? ?? ?? ?? 74 ?? 80 79 ?? 00"); } class CollisionGroup : public FeatureWrapper { public: DECL_MEMBER_THISCALL(void, SetCollisionGroup, void*, int collisionGroup); + protected: virtual bool ShouldLoadFeature() override { @@ -37,18 +39,26 @@ class CollisionGroup : public FeatureWrapper CollisionGroup spt_collisiongroup; -CON_COMMAND_F(y_spt_set_collision_group, "Set player's collision group\nUsually:\n- 5 is normal collisions\n- 10 is quickclip\n", FCVAR_CHEAT) +CON_COMMAND_F(y_spt_set_collision_group, + "Set player's collision group\nUsually:\n- 5 is normal collisions\n- 10 is quickclip\n", + FCVAR_CHEAT) { if (!spt_collisiongroup.ORIG_SetCollisionGroup) return; if (args.ArgC() < 2) { - Warning("Format: spt_set_collision_group \nUsually:\n- 5 is normal collisions\n- 10 is quickclip\n"); + Warning( + "Format: spt_set_collision_group \nUsually:\n- 5 is normal collisions\n- 10 is quickclip\n"); return; } auto playerPtr = utils::GetServerPlayer(); + if (!playerPtr) + { + Warning("Server player not available\n"); + return; + } int collide = atoi(args.Arg(1)); spt_collisiongroup.ORIG_SetCollisionGroup(playerPtr, collide); @@ -57,7 +67,25 @@ CON_COMMAND_F(y_spt_set_collision_group, "Set player's collision group\nUsually: void CollisionGroup::LoadFeature() { if (ORIG_SetCollisionGroup) + { InitCommand(y_spt_set_collision_group); + SptImGuiGroup::Cheats_PlayerCollisionGroup.RegisterUserCallback( + []() + { + static long long val = 5; + SptImGui::InputTextInteger("value", "enter integer", val, 10); + ImGui::SameLine(); + auto playerPtr = utils::GetServerPlayer(); + ImGui::BeginDisabled(!playerPtr); + if (ImGui::Button("set")) + spt_collisiongroup.ORIG_SetCollisionGroup(playerPtr, (int)val); + ImGui::EndDisabled(); + if (!playerPtr) + ImGui::SetItemTooltip("server player not available"); + ImGui::SameLine(); + SptImGui::CmdHelpMarkerWithName(y_spt_set_collision_group_command); + }); + } } void CollisionGroup::UnloadFeature() {} \ No newline at end of file diff --git a/spt/features/con_notify.cpp b/spt/features/con_notify.cpp index 52d1634f4..e01990020 100644 --- a/spt/features/con_notify.cpp +++ b/spt/features/con_notify.cpp @@ -1,5 +1,6 @@ #include "stdafx.hpp" #include "..\feature.hpp" +#include "visualizations\imgui\imgui_interface.hpp" ConVar spt_con_notify_cvar("spt_con_notify", "0", @@ -69,6 +70,8 @@ void ConNotifyFeature::LoadFeature() return; InitConcommandBase(spt_con_notify_cvar); + SptImGuiGroup::QoL_ConNotify.RegisterUserCallback( + []() { SptImGui::CvarCheckbox(spt_con_notify_cvar, "##checkbox"); }); } // just turn on developer before entering these functions diff --git a/spt/features/demo.cpp b/spt/features/demo.cpp index dae546181..4faffcd90 100644 --- a/spt/features/demo.cpp +++ b/spt/features/demo.cpp @@ -7,7 +7,7 @@ #include "..\scripts\srctas_reader.hpp" #include "..\sptlib-wrapper.hpp" #include "..\utils\game_detection.hpp" -#include "dbg.h" +#include "visualizations\imgui\imgui_interface.hpp" DemoStuff spt_demostuff; @@ -360,4 +360,11 @@ void DemoStuff::LoadFeature() { InitConcommandBase(y_spt_demo_block_cmd); } + + SptImGuiGroup::QoL_Demo.RegisterUserCallback( + []() + { + SptImGui::CvarCheckbox(y_spt_demo_block_cmd, "##checkbox"); + SptImGui::CvarInputTextInteger(y_spt_pause_demo_on_tick, "pause demo on tick", "enter tick value"); + }); } diff --git a/spt/features/game_fixes/fastload.cpp b/spt/features/game_fixes/fastload.cpp index b783d0033..e1b812c4b 100644 --- a/spt/features/game_fixes/fastload.cpp +++ b/spt/features/game_fixes/fastload.cpp @@ -2,7 +2,7 @@ #include "..\feature.hpp" #include "..\generic.hpp" #include "signals.hpp" - +#include "..\visualizations\imgui\imgui_interface.hpp" ConVar y_spt_fast_loads( "y_spt_fast_loads", @@ -42,11 +42,13 @@ bool FastLoads::ShouldLoadFeature() void FastLoads::LoadFeature() { - if (SetSignonStateSignal.Works) - { - InitConcommandBase(y_spt_fast_loads); - SetSignonStateSignal.Connect(this, &FastLoads::OnLoad); - } + if (!SetSignonStateSignal.Works) + return; + InitConcommandBase(y_spt_fast_loads); + SetSignonStateSignal.Connect(this, &FastLoads::OnLoad); + + SptImGuiGroup::QoL_FastLoads.RegisterUserCallback( + []() { SptImGui::CvarInputTextInteger(y_spt_fast_loads, "fast loads value", "fps_max value"); }); } void FastLoads::OnLoad(void* thisptr, int state) diff --git a/spt/features/game_fixes/free_oob.cpp b/spt/features/game_fixes/free_oob.cpp index 657b03c78..5ef43bcab 100644 --- a/spt/features/game_fixes/free_oob.cpp +++ b/spt/features/game_fixes/free_oob.cpp @@ -3,8 +3,9 @@ #include "convar.hpp" #include "..\autojump.hpp" #include "interfaces.hpp" +#include "..\visualizations\imgui\imgui_interface.hpp" -#ifndef OE +#ifndef OE static void FreeOOBCVarCallback(IConVar* pConVar, const char* pOldValue, float flOldValue); @@ -111,6 +112,8 @@ void FreeOobFeature::LoadFeature() INIT_BYTE_REPLACE(SecondJump, cur); InitConcommandBase(y_spt_free_oob); + SptImGuiGroup::Cheats_FreeOob.RegisterUserCallback( + []() { SptImGui::CvarCheckbox(y_spt_free_oob, "##checkbox"); }); return; } @@ -139,4 +142,3 @@ void FreeOobFeature::Toggle(bool enabled) } } #endif // ! OE - diff --git a/spt/features/game_fixes/multi_instance.cpp b/spt/features/game_fixes/multi_instance.cpp index 473dd07d9..f222e17bd 100644 --- a/spt/features/game_fixes/multi_instance.cpp +++ b/spt/features/game_fixes/multi_instance.cpp @@ -1,27 +1,54 @@ #include "stdafx.hpp" #ifdef _WIN32 + #include "..\feature.hpp" +#include "signals.hpp" +#include "..\visualizations\imgui\imgui_interface.hpp" #include -CON_COMMAND(y_spt_release_mutex, "Releases \"hl2_singleton_mutex\" to enable running multiple instances.") +static bool SptReleaseMutexImpl(const char** msg) { HANDLE handle = OpenMutexA(SYNCHRONIZE, false, "hl2_singleton_mutex"); if (handle) { if (ReleaseMutex(handle)) - Msg("Released hl2_singleton_mutex. You can start another instance now.\n"); + { + *msg = "Released hl2_singleton_mutex. You can start another instance now."; + return true; + } else - Warning("Failed to release hl2_singleton_mutex.\n"); + { + *msg = "Failed to release hl2_singleton_mutex."; + return false; + } CloseHandle(handle); } else - Warning("Failed to obtain hl2_singleton_mutex handle.\n"); + { + *msg = "Failed to obtain hl2_singleton_mutex handle."; + return false; + } +} + +CON_COMMAND(y_spt_release_mutex, "Releases \"hl2_singleton_mutex\" to enable running multiple instances.") +{ + const char* msg; + if (SptReleaseMutexImpl(&msg)) + Msg("%s\n", msg); + else + Warning("%s\n", msg); } // spt_release_mutex class MultiInstance : public FeatureWrapper { public: + // I don't really understand why, but trying to release the mutex from + // within the ImGui callback doesn't work - do it from FrameSignal instead. + inline static const char* lastMsg = "N/A"; + inline static bool lastResult = true; + inline static bool tryRelease = false; + protected: virtual bool ShouldLoadFeature() override; @@ -30,6 +57,9 @@ class MultiInstance : public FeatureWrapper virtual void LoadFeature() override; virtual void UnloadFeature() override; + +private: + void OnFrameSignal(); }; static MultiInstance spt_multi_instance; @@ -44,6 +74,33 @@ void MultiInstance::InitHooks() {} void MultiInstance::LoadFeature() { InitCommand(y_spt_release_mutex); + + bool imguiEnabled = SptImGuiGroup::QoL_MultiInstance.RegisterUserCallback( + []() + { + if (ImGui::Button("Release HL2 mutex")) + tryRelease = true; + ImGui::SameLine(); + SptImGui::HelpMarker("Help text for %s:\n\n%s", + WrangleLegacyCommandName(y_spt_release_mutex_command.GetName(), true, nullptr), + y_spt_release_mutex_command.GetHelpText()); + ImGui::Text("Last result:"); + ImGui::SameLine(); + if (lastResult) + ImGui::Text("%s", lastMsg); + else + ImGui::TextColored(SPT_IMGUI_WARN_COLOR_YELLOW, "%s", lastMsg); + }); + + if (imguiEnabled) + FrameSignal.Connect(this, &MultiInstance::OnFrameSignal); +} + +void MultiInstance::OnFrameSignal() +{ + if (tryRelease) + lastResult = SptReleaseMutexImpl(&lastMsg); + tryRelease = false; } void MultiInstance::UnloadFeature() {} diff --git a/spt/features/game_fixes/noclip_fixes.cpp b/spt/features/game_fixes/noclip_fixes.cpp index 111674b73..102dbfccd 100644 --- a/spt/features/game_fixes/noclip_fixes.cpp +++ b/spt/features/game_fixes/noclip_fixes.cpp @@ -7,6 +7,7 @@ #include "signals.hpp" #include "..\autojump.hpp" #include "SDK\hl_movedata.h" +#include "..\visualizations\imgui\imgui_interface.hpp" #ifdef OE static void NoclipNofixCVarCallback(ConVar* pConVar, const char* pOldValue); @@ -201,6 +202,17 @@ void NoclipFixesFeature::LoadFeature() } } } + + SptImGuiGroup::QoL_Noclip.RegisterUserCallback( + []() + { + SptImGui::CvarCheckbox(y_spt_noclip_nofix, "##checkbox_nofix"); + SptImGui::CvarCheckbox(spt_noclip_noslowfly, "##checkbox_noslowfly"); + SptImGui::CvarCheckbox(spt_noclip_persist, "##checkbox_persist"); + // whoever decided to put this in a different file deserves stale bread + extern ConVar y_spt_portal_no_ground_snap; + SptImGui::CvarCheckbox(y_spt_portal_no_ground_snap, "##checkbox_no_ground_snap"); + }); } void NoclipFixesFeature::OnTick() diff --git a/spt/features/game_fixes/nosleep.cpp b/spt/features/game_fixes/nosleep.cpp index 0ceadc285..2d4d8b94e 100644 --- a/spt/features/game_fixes/nosleep.cpp +++ b/spt/features/game_fixes/nosleep.cpp @@ -1,6 +1,7 @@ #include "stdafx.hpp" #include "..\feature.hpp" #include "convar.hpp" +#include "..\visualizations\imgui\imgui_interface.hpp" ConVar y_spt_focus_nosleep("y_spt_focus_nosleep", "0", 0, "Improves FPS while alt-tabbed."); @@ -47,6 +48,8 @@ void NoSleepFeature::LoadFeature() if (ORIG_CInputSystem__SleepUntilInput) { InitConcommandBase(y_spt_focus_nosleep); + SptImGuiGroup::QoL_NoSleep.RegisterUserCallback( + []() { SptImGui::CvarCheckbox(y_spt_focus_nosleep, "##checkbox"); }); } } diff --git a/spt/features/game_fixes/snapshot_overflow.cpp b/spt/features/game_fixes/snapshot_overflow.cpp index b8dc53c40..e72086a3d 100644 --- a/spt/features/game_fixes/snapshot_overflow.cpp +++ b/spt/features/game_fixes/snapshot_overflow.cpp @@ -1,5 +1,6 @@ #include "stdafx.hpp" #include "..\feature.hpp" +#include "..\visualizations\imgui\imgui_interface.hpp" ConVar spt_prevent_snapshot_overflow("spt_prevent_snapshot_overflow", "0", @@ -83,6 +84,9 @@ class SnapshotOverflow : public FeatureWrapper extern SnapshotOverflow spt_snapShotOverflow; spt_snapShotOverflow.SetOverwrite(((ConVar*)var)->GetBool()); }); + + SptImGuiGroup::Cheats_SnapshotOverflow.RegisterUserCallback( + []() { SptImGui::CvarCheckbox(spt_prevent_snapshot_overflow, "##checkbox"); }); }; virtual void UnloadFeature() override diff --git a/spt/features/game_fixes/vag_crash.cpp b/spt/features/game_fixes/vag_crash.cpp index cbe370259..3ecc5af32 100644 --- a/spt/features/game_fixes/vag_crash.cpp +++ b/spt/features/game_fixes/vag_crash.cpp @@ -6,8 +6,7 @@ #include "..\utils\game_detection.hpp" #include "..\cvars.hpp" #include "signals.hpp" - -#include "dbg.h" +#include "..\visualizations\imgui\imgui_interface.hpp" ConVar y_spt_prevent_vag_crash( "y_spt_prevent_vag_crash", @@ -74,6 +73,9 @@ void VAG::LoadFeature() } recursiveTeleportCount = 0; InitCommand(spt_touch_grass); + + SptImGuiGroup::Cheats_VagCrash.RegisterUserCallback( + []() { SptImGui::CvarCheckbox(y_spt_prevent_vag_crash, "##checkbox"); }); } void VAG::UnloadFeature() {} diff --git a/spt/features/game_fixes/visual_fixes.cpp b/spt/features/game_fixes/visual_fixes.cpp index 3cc018835..60bc313c6 100644 --- a/spt/features/game_fixes/visual_fixes.cpp +++ b/spt/features/game_fixes/visual_fixes.cpp @@ -4,7 +4,7 @@ #include "convar.hpp" #include "ent_utils.hpp" #include "signals.hpp" -#include "dbg.h" +#include "..\visualizations\imgui\imgui_interface.hpp" typedef void(__cdecl* _DoImageSpaceMotionBlur)(void* view, int x, int y, int w, int h); typedef void(__fastcall* _CViewEffects__Fade)(void* thisptr, int edx, void* data); @@ -222,6 +222,21 @@ void VisualFixes::LoadFeature() InitConcommandBase(spt_viewmodel_offset_y); InitConcommandBase(spt_viewmodel_offset_z); } + + SptImGuiGroup::QoL_Visual.RegisterUserCallback( + []() + { + SptImGui::CvarCheckbox(y_spt_disable_fade, "##checkbox_fade"); + SptImGui::CvarCheckbox(y_spt_disable_shake, "##checkbox_shake"); + SptImGui::CvarCheckbox(y_spt_disable_tone_map_reset, "##checkbox_tone_map"); + SptImGui::CvarInputTextInteger(y_spt_override_tpose, "T-pose sequence override", ""); + + ConVar* cvars[] = {&spt_viewmodel_offset_x, &spt_viewmodel_offset_y, &spt_viewmodel_offset_z}; + float f[ARRAYSIZE(cvars)]; + SptImGui::CvarsDragScalar(cvars, f, ARRAYSIZE(cvars), true, "viewmodel offset", 0.05f); + ImGui::SameLine(); + SptImGui::HelpMarker("Can be set with spt_viewmodel_offset_x/y/z"); + }); } void VisualFixes::UnloadFeature() {} diff --git a/spt/features/hops_hud.cpp b/spt/features/hops_hud.cpp index 2f799663e..4852603da 100644 --- a/spt/features/hops_hud.cpp +++ b/spt/features/hops_hud.cpp @@ -12,6 +12,7 @@ #include "signals.hpp" #include "playerio.hpp" #include "property_getter.hpp" +#include "visualizations\imgui\imgui_interface.hpp" #undef min #undef max @@ -510,7 +511,7 @@ void HopsHud::LoadFeature() OngroundSignal.Connect(this, &HopsHud::OnGround); InitConcommandBase(y_spt_jhud_hops); } - + if (TickSignal.Works || OngroundSignal.Works) { JumpSignal.Connect(this, &HopsHud::OnJump); @@ -526,6 +527,27 @@ void HopsHud::LoadFeature() TickSignal.Connect(ljstats::OnTick); InitConcommandBase(y_spt_jhud_ljstats); } + + SptImGuiGroup::Hud_JHud.RegisterUserCallback( + []() + { + bool hudVel = SptImGui::CvarCheckbox(y_spt_jhud_velocity, "##checkbox_vel"); + bool hudHops = SptImGui::CvarCheckbox(y_spt_jhud_hops, "##checkbox_hops"); + bool hudStats = SptImGui::CvarCheckbox(y_spt_jhud_ljstats, "##checkbox_stats"); + + bool enablePos = hudVel || hudHops || hudStats; + ImGui::BeginDisabled(!enablePos); + ConVar* cvars[] = {&y_spt_jhud_x, &y_spt_jhud_y}; + float f[ARRAYSIZE(cvars)]; + ImGui::BeginGroup(); + SptImGui::CvarsDragScalar(cvars, f, ARRAYSIZE(cvars), true, "HUD pos"); + ImGui::SameLine(); + SptImGui::HelpMarker("Can be set with spt_jhud_x/y"); + ImGui::EndGroup(); + ImGui::EndDisabled(); + if (!enablePos) + ImGui::SetItemTooltip("no jump HUD vars enabled"); + }); } void HopsHud::UnloadFeature() {} diff --git a/spt/features/ihud.cpp b/spt/features/ihud.cpp index 28f419269..de7fb47bb 100644 --- a/spt/features/ihud.cpp +++ b/spt/features/ihud.cpp @@ -11,6 +11,7 @@ #include "spt\utils\interfaces.hpp" #include "spt\utils\signals.hpp" #include "spt\utils\game_detection.hpp" +#include "visualizations\imgui\imgui_interface.hpp" #include "basehandle.h" #include "Color.h" @@ -601,6 +602,21 @@ void InputHud::LoadFeature() InitConcommandBase(y_spt_ihud_grid_padding); InitConcommandBase(y_spt_ihud_x); InitConcommandBase(y_spt_ihud_y); + + SptImGuiGroup::Hud_IHud.RegisterUserCallback( + []() + { + ImGui::BeginDisabled(!SptImGui::CvarCheckbox(y_spt_ihud, "##enabled")); + SptImGui::CvarInputTextInteger(y_spt_ihud_grid_size, "grid size", "enter integer"); + SptImGui::CvarInputTextInteger(y_spt_ihud_grid_padding, "grid padding", "enter integer"); + + ConVar* cvars[] = {&y_spt_ihud_x, &y_spt_ihud_y}; + float f[ARRAYSIZE(cvars)]; + SptImGui::CvarsDragScalar(cvars, f, ARRAYSIZE(cvars), true, "IHUD pos"); + ImGui::SameLine(); + SptImGui::HelpMarker("Can be set with spt_ihud_x/y"); + ImGui::EndDisabled(); + }); } const int IN_ATTACK = (1 << 0); diff --git a/spt/features/isg.cpp b/spt/features/isg.cpp index e0d5c7a7b..3364f1208 100644 --- a/spt/features/isg.cpp +++ b/spt/features/isg.cpp @@ -91,7 +91,7 @@ void ISGFeature::LoadFeature() "isg", [](std::string) { spt_hud_feat.DrawTopHudElement(L"isg: %d", IsISGActive()); }, y_spt_hud_isg); #endif - SptImGuiGroup::GameIo_ISG.RegisterUserCallback(ImGuiCallback); + SptImGuiGroup::Cheats_ISG.RegisterUserCallback(ImGuiCallback); if (hudCallbackEnabled) SptImGui::RegisterHudCvarCheckbox(y_spt_hud_isg); } diff --git a/spt/features/leafvis.cpp b/spt/features/leafvis.cpp index 1836c5118..f356a840f 100644 --- a/spt/features/leafvis.cpp +++ b/spt/features/leafvis.cpp @@ -1,5 +1,6 @@ #include "stdafx.hpp" #include "..\feature.hpp" +#include "visualizations\imgui\imgui_interface.hpp" namespace patterns { @@ -9,9 +10,9 @@ namespace patterns } ConVar y_spt_leafvis_index("y_spt_leafvis_index", - "0", - FCVAR_CHEAT, - "Choose which BSP leaf mat_leafvis will use. Requires mat_leafvis to be set\n"); + "0", + FCVAR_CHEAT, + "Choose which BSP leaf mat_leafvis will use. Requires mat_leafvis to be set\n"); // Enhance mat_leafvis by allowing you to choose the BSP leaf by index class LeafVisFeature : public FeatureWrapper @@ -34,8 +35,36 @@ void LeafVisFeature::InitHooks() void LeafVisFeature::LoadFeature() { - if (ORIG_MiddleOfLeafVisBuild != nullptr) + if (ORIG_MiddleOfLeafVisBuild) + { InitConcommandBase(y_spt_leafvis_index); + + if (g_pCVar && g_pCVar->FindVar("mat_leafvis")) + { + SptImGuiGroup::Draw_Misc_LeafVis.RegisterUserCallback( + []() + { + ConVar* mat_leafvis = g_pCVar->FindVar("mat_leafvis"); + ImGui::BeginDisabled(mat_leafvis->GetInt() != 1); + SptImGui::CvarInputTextInteger(y_spt_leafvis_index, + "spt_leafvis", + "enter integer index"); + ImGui::EndDisabled(); + ImGui::Text("Edit mat_leafvis:"); + ImGui::SameLine(); + if (ImGui::Button("set to 0")) + mat_leafvis->SetValue(0); + ImGui::SameLine(); + if (ImGui::Button("set to 1")) + mat_leafvis->SetValue(1); + ImGui::SameLine(); + if (ImGui::Button("set to 2")) + mat_leafvis->SetValue(2); + ImGui::SameLine(); + SptImGui::CvarValue(*mat_leafvis); + }); + } + } } static __forceinline int GetLeafIndex() diff --git a/spt/features/movement_vars.cpp b/spt/features/movement_vars.cpp index 68603a93e..4828e6535 100644 --- a/spt/features/movement_vars.cpp +++ b/spt/features/movement_vars.cpp @@ -5,13 +5,14 @@ #include "convar.hpp" #include "interfaces.hpp" #include "signals.hpp" +#include "visualizations\imgui\imgui_interface.hpp" static void Change_Maxspeed(CON_COMMAND_CALLBACK_ARGS); static ConVar* _cl_forwardspeed = nullptr; static ConVar* _cl_sidespeed = nullptr; static ConVar* _cl_backspeed = nullptr; -static ConVar* _sv_maxspeed = nullptr; +static ConVar* _sv_maxspeed_cvar = nullptr; ConVar spt_player_maxspeed( "spt_player_maxspeed", @@ -29,14 +30,14 @@ static void Change_Maxspeed(CON_COMMAND_CALLBACK_ARGS) SET_DEFAULT(_cl_forwardspeed); SET_DEFAULT(_cl_sidespeed); SET_DEFAULT(_cl_backspeed); - SET_DEFAULT(_sv_maxspeed); + SET_DEFAULT(_sv_maxspeed_cvar); } else { _cl_forwardspeed->SetValue(maxspeed); _cl_sidespeed->SetValue(maxspeed); _cl_backspeed->SetValue(maxspeed); - _sv_maxspeed->SetValue(maxspeed); + _sv_maxspeed_cvar->SetValue(maxspeed); } } @@ -66,13 +67,16 @@ void VarChanger::LoadFeature() _cl_forwardspeed = interfaces::g_pCVar->FindVar("cl_forwardspeed"); _cl_sidespeed = interfaces::g_pCVar->FindVar("cl_sidespeed"); _cl_backspeed = interfaces::g_pCVar->FindVar("cl_backspeed"); - _sv_maxspeed = interfaces::g_pCVar->FindVar("sv_maxspeed"); + _sv_maxspeed_cvar = interfaces::g_pCVar->FindVar("sv_maxspeed"); } - if (ProcessMovementPre_Signal.Works && _cl_forwardspeed && _cl_sidespeed && _cl_backspeed && _sv_maxspeed) + if (ProcessMovementPre_Signal.Works && _cl_forwardspeed && _cl_sidespeed && _cl_backspeed && _sv_maxspeed_cvar) { ProcessMovementPre_Signal.Connect(ProcessMovementPre); InitConcommandBase(spt_player_maxspeed); + SptImGuiGroup::Cheats_MaxSpeed.RegisterUserCallback( + []() + { SptImGui::CvarFloat(spt_player_maxspeed, "override max speed value", "enter float value"); }); } } diff --git a/spt/features/pause.cpp b/spt/features/pause.cpp index 5d3e904fc..d73021fc5 100644 --- a/spt/features/pause.cpp +++ b/spt/features/pause.cpp @@ -3,7 +3,7 @@ #include "generic.hpp" #include "convar.hpp" #include "signals.hpp" -#include "dbg.h" +#include "visualizations\imgui\imgui_interface.hpp" ConVar y_spt_pause("y_spt_pause", "0", FCVAR_ARCHIVE); @@ -136,6 +136,16 @@ void PauseFeature::LoadFeature() } InitConcommandBase(y_spt_pause); + SptImGuiGroup::Cheats_Pause.RegisterUserCallback( + [pause1_works, pause2_works]() + { + const char* opts[] = { + "Disabled", + pause1_works ? "Pause 1 tick after load" : "N/A", + pause2_works ? "Pause 0 ticks after load" : "N/A", + }; + SptImGui::CvarCombo(y_spt_pause, "##combo", opts, ARRAYSIZE(opts)); + }); } } @@ -150,7 +160,7 @@ void PauseFeature::SV_ActivateServer(bool result) if (spt_generic.ORIG_SetPaused && pM_bLoadgame && pGameServer) { - if ((y_spt_pause.GetInt() == 2) && *pM_bLoadgame) + if ((y_spt_pause.GetInt() >= 2) && *pM_bLoadgame) { spt_generic.ORIG_SetPaused((void*)pGameServer, 0, true); DevMsg("Pausing...\n"); diff --git a/spt/features/shadow.cpp b/spt/features/shadow.cpp index 8b0ae61be..547d77ff1 100644 --- a/spt/features/shadow.cpp +++ b/spt/features/shadow.cpp @@ -20,7 +20,7 @@ CON_COMMAND_F(y_spt_set_shadow_roll, "Sets the player's physics shadow roll in d Warning("Must provide a roll in degrees.\n"); return; } - spt_player_shadow.SetPlayerHavokPos(spt_playerio.m_vecAbsOrigin.GetValue(), QAngle(0, 0, atof(args.Arg(1)))); + spt_player_shadow.SetPlayerHavokRoll(atof(args.Arg(1))); } ShadowPosition spt_player_shadow; @@ -91,7 +91,21 @@ void ShadowPosition::LoadFeature() } if (ORIG_beam_object_to_new_position) + { InitCommand(y_spt_set_shadow_roll); + + SptImGuiGroup::Cheats_PlayerShadow.RegisterUserCallback( + []() + { + static float f = 0.f; + SptImGui::InputFloat("shadow roll in degrees", &f); + ImGui::SameLine(); + if (ImGui::Button("set")) + spt_player_shadow.SetPlayerHavokRoll(f); + ImGui::SameLine(); + SptImGui::CmdHelpMarkerWithName(y_spt_set_shadow_roll_command); + }); + } } void ShadowPosition::GetPlayerHavokPos(Vector* worldPosition, QAngle* angles) @@ -102,6 +116,11 @@ void ShadowPosition::GetPlayerHavokPos(Vector* worldPosition, QAngle* angles) *angles = spt_player_shadow.PlayerHavokAngles; } +void ShadowPosition::SetPlayerHavokRoll(float roll) +{ + SetPlayerHavokPos(spt_playerio.m_vecAbsOrigin.GetValue(), QAngle{0, 0, roll}); +} + void ShadowPosition::SetPlayerHavokPos(const Vector& worldPosition, const QAngle& angles) { if (!ORIG_beam_object_to_new_position) diff --git a/spt/features/shadow.hpp b/spt/features/shadow.hpp index 7fa2a7d49..f7753429c 100644 --- a/spt/features/shadow.hpp +++ b/spt/features/shadow.hpp @@ -51,6 +51,7 @@ class ShadowPosition : public FeatureWrapper */ void GetPlayerHavokPos(Vector* worldPosition, QAngle* angles); + void SetPlayerHavokRoll(float roll); void SetPlayerHavokPos(const Vector& worldPosition, const QAngle& angles); /* diff --git a/spt/features/tickrate.cpp b/spt/features/tickrate.cpp index 16ca62d1b..1c14358a6 100644 --- a/spt/features/tickrate.cpp +++ b/spt/features/tickrate.cpp @@ -2,7 +2,7 @@ #include "..\spt-serverplugin.hpp" #include "tickrate.hpp" #include "convar.hpp" -#include "dbg.h" +#include "visualizations\imgui\imgui_interface.hpp" TickrateMod spt_tickrate; @@ -82,6 +82,21 @@ void TickrateMod::LoadFeature() DevMsg("Found interval_per_tick at %p.\n", pIntervalPerTick); if (pIntervalPerTick) + { InitCommand(_y_spt_tickrate); + + SptImGuiGroup::Cheats_Tickrate.RegisterUserCallback( + []() + { + ImGui::Text("Current tickrate: %g", spt_tickrate.GetTickrate()); + static float f = 0.f; + SptImGui::InputFloat("new tickrate", &f); + ImGui::SameLine(); + if (ImGui::Button("set")) + spt_tickrate.SetTickrate(f); + ImGui::SameLine(); + SptImGui::CmdHelpMarkerWithName(_y_spt_tickrate_command); + }); + } } } diff --git a/spt/features/timer.cpp b/spt/features/timer.cpp index eba1afe4e..fa5454fb2 100644 --- a/spt/features/timer.cpp +++ b/spt/features/timer.cpp @@ -1,57 +1,72 @@ #include "stdafx.hpp" + +#include + #include "..\feature.hpp" #include "generic.hpp" #include "signals.hpp" +#include "tickrate.hpp" +#include "visualizations\imgui\imgui_interface.hpp" #include "convar.h" +using chrono_timer = std::chrono::steady_clock; +using chrono_precision = std::chrono::milliseconds; + class Timer : public FeatureWrapper { public: void StartTimer() { + if (timerRunning) + return; timerRunning = true; + lastResumeTimeRta = chrono_timer::now(); } void StopTimer() { + if (!timerRunning) + return; timerRunning = false; + partialAccumulatedTimeRta += + std::chrono::duration_cast(chrono_timer::now() - lastResumeTimeRta); } void ResetTimer() { ticksPassed = 0; + partialAccumulatedTimeRta = chrono_precision::zero(); timerRunning = false; } unsigned int GetTicksPassed() const { return ticksPassed; } + double GetTimePassedRta() const + { + auto extra = timerRunning + ? std::chrono::duration_cast(chrono_timer::now() - lastResumeTimeRta) + : chrono_precision::zero(); + constexpr double ratio = (double)chrono_precision::period::num / (double)chrono_precision::period::den; + return (partialAccumulatedTimeRta + extra).count() * ratio; + } + bool Running() const + { + return timerRunning; + } protected: - virtual bool ShouldLoadFeature() override; - - virtual void InitHooks() override; - virtual void LoadFeature() override; - virtual void UnloadFeature() override; - private: void Tick(); int ticksPassed = 0; + chrono_precision partialAccumulatedTimeRta; + std::chrono::time_point lastResumeTimeRta; bool timerRunning = false; }; static Timer spt_timer; -bool Timer::ShouldLoadFeature() -{ - return true; -} - -void Timer::InitHooks() {} - -void Timer::UnloadFeature() {} - void Timer::Tick() { if (timerRunning) @@ -66,7 +81,10 @@ CON_COMMAND(y_spt_timer_start, "Starts the SPT timer.") CON_COMMAND(y_spt_timer_stop, "Stops the SPT timer and prints the current time.") { spt_timer.StopTimer(); - Warning("Current time (in ticks): %u\n", spt_timer.GetTicksPassed()); + Warning("Elapsed game time: %u ticks (%.3fs)\nElapsed real time: %.3fs\n", + spt_timer.GetTicksPassed(), + spt_timer.GetTicksPassed() * spt_tickrate.GetTickrate(), + spt_timer.GetTimePassedRta()); } CON_COMMAND(y_spt_timer_reset, "Stops and resets the SPT timer.") @@ -90,5 +108,33 @@ void Timer::LoadFeature() InitCommand(y_spt_timer_stop); InitCommand(y_spt_timer_reset); InitCommand(y_spt_timer_print); + + SptImGuiGroup::QoL_Timer.RegisterUserCallback( + []() + { + unsigned int elapsedTicks = spt_timer.GetTicksPassed(); + double elapsedSecsRta = spt_timer.GetTimePassedRta(); + + ImGui::BeginDisabled(spt_timer.Running()); + if (ImGui::Button(elapsedSecsRta > 0 ? "resume###start" : "start###start")) + spt_timer.StartTimer(); + ImGui::EndDisabled(); + ImGui::BeginDisabled(!spt_timer.Running()); + ImGui::SameLine(); + if (ImGui::Button("pause")) + spt_timer.StopTimer(); + ImGui::EndDisabled(); + ImGui::SameLine(); + if (ImGui::Button("reset")) + spt_timer.ResetTimer(); + ImGui::SameLine(); + SptImGui::HelpMarker( + "Can be triggered with spt_timer_start, spt_timer_stop, spt_timer_reset"); + + ImGui::Text("Elapsed game time: %u ticks (%.3fs)", + elapsedTicks, + elapsedTicks * spt_tickrate.GetTickrate()); + ImGui::Text("Elapsed real time: %.3fs", elapsedSecsRta); + }); } } diff --git a/spt/features/visualizations/draw_seams.cpp b/spt/features/visualizations/draw_seams.cpp index bafaaf2d0..d6997475a 100644 --- a/spt/features/visualizations/draw_seams.cpp +++ b/spt/features/visualizations/draw_seams.cpp @@ -15,6 +15,7 @@ #include "spt\features\generic.hpp" #include "spt\utils\portal_utils.hpp" #include "spt\utils\interfaces.hpp" +#include "imgui\imgui_interface.hpp" ConVar y_spt_draw_seams("y_spt_draw_seams", "0", FCVAR_CHEAT, "Draws seamshot stuff"); @@ -27,6 +28,8 @@ class DrawSeamsFeature : public FeatureWrapper { InitConcommandBase(y_spt_draw_seams); spt_meshRenderer.signal.Connect(this, &DrawSeamsFeature::OnMeshRenderSignal); + SptImGuiGroup::Draw_Misc_Seams.RegisterUserCallback( + []() { SptImGui::CvarCheckbox(y_spt_draw_seams, "##checkbox"); }); } } diff --git a/spt/features/visualizations/imgui/imgui_interface.hpp b/spt/features/visualizations/imgui/imgui_interface.hpp index 27420beb3..baf4a9667 100644 --- a/spt/features/visualizations/imgui/imgui_interface.hpp +++ b/spt/features/visualizations/imgui/imgui_interface.hpp @@ -172,20 +172,48 @@ namespace SptImGuiGroup // drawing visualizations inline Tab Draw{"Drawing", &Root}; inline Tab Draw_Collides{"Collision", &Draw}; - inline Section Draw_Collides_World{"World Collides", &Draw_Collides}; - inline Section Draw_Collides_Ents{"Entity Collides", &Draw_Collides}; + inline Section Draw_Collides_World{"World collides", &Draw_Collides}; + inline Section Draw_Collides_Ents{"Entity collides", &Draw_Collides}; inline Tab Draw_MapOverlay{"Map overlay", &Draw}; inline Tab Draw_Lines{"Draw lines", &Draw}; inline Tab Draw_PpPlacement{"Portal placement", &Draw}; inline Section Draw_PpPlacement_Gun{"Gun portal placement", &Draw_PpPlacement}; inline Section Draw_PpPlacement_Grid{"Portal placement grid", &Draw_PpPlacement}; - - // features that access game state - inline Tab GameIo{"Game IO", &Root}; - inline Tab GameIo_ISG{"ISG", &GameIo}; - - // hud cvars - use the RegisterHudCvarXXX functions below to add cvars - inline Tab Hud{"Text HUD", &Root}; + inline Tab Draw_Misc{"Misc.", &Draw}; + inline Section Draw_Misc_OobEnts{"OOB entities", &Draw_Misc}; + inline Section Draw_Misc_Seams{"Seamshots", &Draw_Misc}; + inline Section Draw_Misc_LeafVis{"Leaf vis", &Draw_Misc}; + + // quality of life and/or purely visual stuff + inline Tab QoL{"QoL", &Root}; + inline Section QoL_Demo{"Demo utils", &QoL}; + inline Section QoL_MultiInstance{"Multiple game instances", &QoL}; + inline Section QoL_Noclip{"Noclip", &QoL}; // QoL instead of in cheats cuz noclip is cheating anyways :) + inline Section QoL_NoSleep{"No sleep", &QoL}; + inline Section QoL_Visual{"Visual fixes", &QoL}; + inline Section QoL_ConNotify{"Console notify", &QoL}; + inline Section QoL_FastLoads{"Fast loads", &QoL}; + inline Section QoL_Timer{"Timer", &QoL}; + + // cheats - stuff that changes gameplay or cannot be done via normal means + inline Tab Cheats{"Cheats", &Root}; + inline Section Cheats_Jumping{"Jumping", &Cheats}; + inline Section Cheats_HL2AirControl{"HL2 air control", &Cheats}; + inline Section Cheats_ISG{"ISG", &Cheats}; + inline Section Cheats_SnapshotOverflow{"Snapshot overflow fix", &Cheats}; + inline Section Cheats_VagCrash{"Prevent VAG crash", &Cheats}; + inline Section Cheats_PlayerShadow{"Player shadow", &Cheats}; + inline Section Cheats_Pause{"Pause on load", &Cheats}; + inline Section Cheats_FreeOob{"Free OOB", &Cheats}; + inline Section Cheats_PlayerCollisionGroup{"Player collision group", &Cheats}; + inline Section Cheats_MaxSpeed{"Max speed", &Cheats}; + inline Section Cheats_Tickrate{"Tickrate", &Cheats}; + + // spt_hud and friends + inline Tab Hud{"HUD", &Root}; + 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}; // development/debugging features inline Tab Dev{"DEV", &Root}; @@ -249,6 +277,13 @@ class SptImGui * For anything more complicated create a draw callback - if it's more than 1 line tall, put * it in a collapsible. You can (and should) reuse this callback for drawing the cvar logic * within the dedicated tab/section for your feature. + * + * Your logic should be: + * + * if (AddHudCallback(..., var)) + * RegisterHudCvarXXX(var, ...); + * + * This ensures that the cvar will only show up in the HUD tab of the cvar was initialized. */ static void RegisterHudCvarCheckbox(ConVar& var); static void RegisterHudCvarCallback(ConVar& var, const SptImGuiHudTextCallback& cb, bool putInCollapsible); @@ -283,11 +318,39 @@ class SptImGui static int CvarCombo(ConVar& c, const char* label, const char* const* opts, size_t nOpts); // a textbox for an integer in base 10 or 16, returns true if the value was modified static bool InputTextInteger(const char* label, const char* hint, long long& val, int radix); - // same as internal imgui help marker - a tooltip with extra info (for cvars & commands) + // a textbox for a cvar which can take on any long long value, returns the value + static long long CvarInputTextInteger(ConVar& c, const char* label, const char* hint, int radix = 10); + // same as ImGui::InputFloat, but less W I D E (returns true if the value was changed) + static bool InputFloat(const char* label, float *f); + // just uses SptImGui::InputFloat + static float CvarFloat(ConVar& c, const char* label, const char* hint); + // uses DragScalarN, puts everything in a CmdGroup scope using the first cvar, does not provide help text + static void CvarsDragScalar( + ConVar* const* cvars, + void* data, // isFloat=true: floats, isFloat=false: long longs; will be filled by this function + int n, + bool isFloat, + const char* label, + float speed = 1.f, + const void* min = nullptr, + const void* max = nullptr, + const char* format = nullptr); + // same as internal imgui help marker - a tooltip with extra info static void HelpMarker(const char* fmt, ...); + // a help marker that says "Help text for :\n\n" + static void CmdHelpMarkerWithName(const ConCommandBase& c); // add a border around an item - this is just a table with a single row/column static bool BeginBordered(const ImVec2& outer_size = {0.0f, 0.0f}, float inner_width = 0.0f); static void EndBordered(); + /* + * A scope for cvar/cmd widgets. Puts the whole widget into a group and disables it if the + * cvar/cmd is not registered. Does not check if the cvar is enabled & does not create a new + * ID scope. All of the above CvarXXX/CmdXXX widgets do this internally. The text HUD tab does + * not do add this for you - instead you should check the return of AddHudCallback (refer to the + * comments for the RegisterHudCvarXXX functions above). + */ + static void BeginCmdGroup(const ConCommandBase& cmdBase); + static void EndCmdGroup(); struct AutocompletePersistData { diff --git a/spt/features/visualizations/imgui/spt_imgui_widgets.cpp b/spt/features/visualizations/imgui/spt_imgui_widgets.cpp index 5e48d309c..868e4df33 100644 --- a/spt/features/visualizations/imgui/spt_imgui_widgets.cpp +++ b/spt/features/visualizations/imgui/spt_imgui_widgets.cpp @@ -3,18 +3,63 @@ #include "imgui_interface.hpp" #include "thirdparty/imgui/imgui_internal.h" +// make float/integer text inputs have a constant width +#define SPT_SET_NUMBER_INPUT_ITEM_WIDTH(maxChars) ImGui::SetNextItemWidth(ImGui::GetFontSize() * (maxChars) * 0.5f) + +// A stack based storage for a single ImGui context for data of arbitrary length. +// When popped, the pointer to the data remains valid until Push/Pop is called again. +class +{ + std::vector userStorage; + std::vector userStorageOffsets; + bool lastUserStorageWasPop = false; + + void InvalidatePoppedData() + { + if (lastUserStorageWasPop) + { + userStorage.resize(userStorageOffsets.back()); + userStorageOffsets.pop_back(); + lastUserStorageWasPop = false; + } + } + +public: + void Push(const void* data, size_t len) + { + InvalidatePoppedData(); + userStorageOffsets.push_back(userStorage.size()); + userStorage.resize(userStorage.size() + len); + memcpy(userStorage.data() + userStorageOffsets.back(), data, len); + } + + void* Pop(size_t* len) + { + InvalidatePoppedData(); + Assert(!userStorage.empty() && !userStorageOffsets.empty()); + if (len) + *len = userStorageOffsets.back(); + lastUserStorageWasPop = true; + return userStorage.data() + userStorageOffsets.back(); + } + +} static g_SptImGuiUserStorage; + bool SptImGui::CmdButton(const char* label, ConCommand& cmd) { + BeginCmdGroup(cmd); bool ret = ImGui::Button(label); ImGui::SameLine(); ImGui::TextDisabled("(%s)", WrangleLegacyCommandName(cmd.GetName(), true, nullptr)); ImGui::SameLine(); HelpMarker("%s", cmd.GetHelpText()); + EndCmdGroup(); return ret; } void SptImGui::CvarValue(const ConVar& c, CvarValueFlags flags) { + BeginCmdGroup(c); const char* v = c.GetString(); const char* surround = ""; if ((flags & CVF_ALWAYS_QUOTE) || !*v || strchr(v, ' ')) @@ -22,10 +67,12 @@ void SptImGui::CvarValue(const ConVar& c, CvarValueFlags flags) ImGui::TextDisabled("(%s %s%s%s)", WrangleLegacyCommandName(c.GetName(), true, nullptr), surround, v, surround); ImGui::SameLine(); HelpMarker("%s", c.GetHelpText()); + EndCmdGroup(); } bool SptImGui::CvarCheckbox(ConVar& c, const char* label) { + BeginCmdGroup(c); bool oldVal = c.GetBool(); bool newVal = oldVal; ImGui::Checkbox(label, &newVal); @@ -33,12 +80,14 @@ bool SptImGui::CvarCheckbox(ConVar& c, const char* label) c.SetValue(newVal); // only set value if it was changed to not spam cvar callbacks ImGui::SameLine(); CvarValue(c); + EndCmdGroup(); return newVal; } int SptImGui::CvarCombo(ConVar& c, const char* label, const char* const* opts, size_t nOpts) { Assert(nOpts >= 1); + BeginCmdGroup(c); int val = clamp(c.GetInt(), 0, (int)nOpts - 1); if (ImGui::BeginCombo(label, opts[val], ImGuiComboFlags_WidthFitPreview)) @@ -58,7 +107,8 @@ int SptImGui::CvarCombo(ConVar& c, const char* label, const char* const* opts, s ImGui::EndCombo(); } ImGui::SameLine(); - SptImGui::CvarValue(c); + CvarValue(c); + EndCmdGroup(); return val; } @@ -68,7 +118,7 @@ bool SptImGui::InputTextInteger(const char* label, const char* hint, long long& char buf[24]; const char* fmtSpecifier = radix == 10 ? "%" PRId64 : "%" PRIx64; snprintf(buf, sizeof buf, fmtSpecifier, val); - ImGui::SetNextItemWidth(ImGui::GetFontSize() * (sizeof(buf) - 1) * 0.6f); + SPT_SET_NUMBER_INPUT_ITEM_WIDTH(sizeof buf); ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_AutoSelectAll; flags |= radix == 10 ? ImGuiInputTextFlags_CharsDecimal : ImGuiInputTextFlags_CharsHexadecimal; @@ -78,6 +128,76 @@ bool SptImGui::InputTextInteger(const char* label, const char* hint, long long& return ret; } +long long SptImGui::CvarInputTextInteger(ConVar& c, const char* label, const char* hint, int radix) +{ + // don't use c.GetInt() since that's cast from a float + Assert(radix == 10 || radix == 16); + BeginCmdGroup(c); + long long val = strtoll(c.GetString(), nullptr, radix); + if (InputTextInteger(label, hint, val, radix)) + { + char buf[24]; + snprintf(buf, sizeof buf, radix == 10 ? "%" PRId64 : "%" PRIx64, val); + c.SetValue(buf); + } + ImGui::SameLine(); + CmdHelpMarkerWithName(c); + EndCmdGroup(); + return val; +} + +bool SptImGui::InputFloat(const char* label, float* f) +{ + SPT_SET_NUMBER_INPUT_ITEM_WIDTH(24); + return ImGui::InputFloat(label, f); +} + +float SptImGui::CvarFloat(ConVar& c, const char* label, const char* hint) +{ + BeginCmdGroup(c); + float val = c.GetFloat(); + if (SptImGui::InputFloat(label, &val)) + c.SetValue(val); + ImGui::SameLine(); + CmdHelpMarkerWithName(c); + EndCmdGroup(); + return val; +} + +void SptImGui::CvarsDragScalar(ConVar* const* cvars, + void* data, + int n, + bool isFloat, + const char* label, + float speed, + const void* min, + const void* max, + const char* format) +{ + Assert(cvars && data && n > 0); + for (int i = 0; i < n; i++) + { + // cvars do string -> float -> int, so always use strtoll + if (isFloat) + ((float*)data)[i] = cvars[i]->GetFloat(); + else + ((long long*)data)[i] = strtoll(cvars[i]->GetString(), nullptr, 10); + } + SptImGui::BeginCmdGroup(*cvars[0]); + ImGuiDataType dataType = isFloat ? ImGuiDataType_Float : ImGuiDataType_S64; + if (ImGui::DragScalarN(label, dataType, data, n, speed, min, max, format, ImGuiSliderFlags_NoRoundToFormat)) + { + for (int i = 0; i < n; i++) + { + if (isFloat) + cvars[i]->SetValue(((float*)data)[i]); + else + cvars[i]->SetValue((int)(((long long*)data)[i])); + } + } + SptImGui::EndCmdGroup(); +} + void SptImGui::HelpMarker(const char* fmt, ...) { va_list va; @@ -93,6 +213,11 @@ void SptImGui::HelpMarker(const char* fmt, ...) va_end(va); } +void SptImGui::CmdHelpMarkerWithName(const ConCommandBase& c) +{ + HelpMarker("Help text for %s:\n\n%s", WrangleLegacyCommandName(c.GetName(), true, nullptr), c.GetHelpText()); +} + bool SptImGui::BeginBordered(const ImVec2& outer_size, float inner_width) { if (ImGui::BeginTable("##table_border", 1, ImGuiTableFlags_BordersOuter, outer_size, inner_width)) @@ -109,6 +234,27 @@ void SptImGui::EndBordered() ImGui::EndTable(); } +void SptImGui::BeginCmdGroup(const ConCommandBase& cmdBase) +{ + ImGui::BeginGroup(); + ImGui::BeginDisabled(!cmdBase.IsRegistered()); + auto adr = &cmdBase; // assume the command has a constant address + g_SptImGuiUserStorage.Push(&adr, sizeof adr); +} + +void SptImGui::EndCmdGroup() +{ + const ConCommandBase* cmdBase = *static_cast(g_SptImGuiUserStorage.Pop(nullptr)); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (!cmdBase->IsRegistered()) + { + ImGui::SetItemTooltip("%s \"%s\" was not initialized", + cmdBase->IsCommand() ? "ConCommand" : "ConVar", + WrangleLegacyCommandName(cmdBase->GetName(), true, nullptr)); + } +} + void SptImGui::TextInputAutocomplete(const char* inputTextLabel, const char* popupId, AutocompletePersistData& persist, diff --git a/spt/features/visualizations/oob_ents.cpp b/spt/features/visualizations/oob_ents.cpp index 6b87de9e3..f3c99cab6 100644 --- a/spt/features/visualizations/oob_ents.cpp +++ b/spt/features/visualizations/oob_ents.cpp @@ -81,6 +81,19 @@ void HudOobEntsFeature::LoadFeature() { InitConcommandBase(spt_draw_oob_ents); spt_meshRenderer.signal.Connect(this, &HudOobEntsFeature::DrawEntsPos); + + SptImGuiGroup::Draw_Misc_OobEnts.RegisterUserCallback( + []() + { + const char* opts[] = { + "Disabled", + "OOB entities", + "OOB entities + no z-test", + "All entities + no z-test", + }; + SptImGui::CvarCombo(spt_draw_oob_ents, "entity position indicator", opts, ARRAYSIZE(opts)); + SptImGui::CvarCheckbox(spt_hud_oob_ents, "##checkbox"); + }); } #endif }