From 49b77211b58790e9aa80063fffed6191431374bd Mon Sep 17 00:00:00 2001 From: alva98 <59375624+xalva98@users.noreply.github.com> Date: Sat, 18 Jul 2026 02:13:12 +0200 Subject: [PATCH] feat(extra-natives-five): add a global vehicle audio volume control Scale all vehicle audio (engine, exhaust, tyres, horns, ...) via a controller on the RAGE VEHICLES category, without affecting other audio. Exposed as the archived convar profile_vehicleVolume and an F8 slider under Game. Also adds int/float slider rendering to the dev menu. --- code/components/conhost-v2/src/DevGui.cpp | 84 +++++++++++++- .../src/VehicleAudioVolume.cpp | 107 ++++++++++++++++++ 2 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 code/components/extra-natives-five/src/VehicleAudioVolume.cpp diff --git a/code/components/conhost-v2/src/DevGui.cpp b/code/components/conhost-v2/src/DevGui.cpp index df5aee35fa..5342c19f1a 100644 --- a/code/components/conhost-v2/src/DevGui.cpp +++ b/code/components/conhost-v2/src/DevGui.cpp @@ -69,6 +69,10 @@ struct DevGuiNode std::string name; std::string commandOrConVar; + float sliderMin = 0.0f; + float sliderMax = 0.0f; + float sliderStep = 0.0f; + std::list> children; inline DevGuiNode* RegisterMenu(const std::string& name) @@ -185,7 +189,23 @@ static void DevGui_Draw(const DevGuiNode* node) auto value = intEntry->GetRawValue(); auto initialValue = value; - ImGui::InputScalar(node->name.c_str(), ImGuiDataType_S32, &value); + bool useSlider = node->sliderMax > node->sliderMin; + + if (useSlider) + { + ImGui::SetNextItemWidth(160.0f); + ImGui::SliderInt(node->name.c_str(), &value, (int)node->sliderMin, (int)node->sliderMax); + + if (node->sliderStep > 0.0f) + { + int step = (int)node->sliderStep; + value = (int)node->sliderMin + (int)std::round((value - (int)node->sliderMin) / (float)step) * step; + } + } + else + { + ImGui::InputScalar(node->name.c_str(), ImGuiDataType_S32, &value); + } if (initialValue != value) { @@ -194,6 +214,38 @@ static void DevGui_Draw(const DevGuiNode* node) return; } + + auto floatEntry = std::dynamic_pointer_cast>(entry); + + if (floatEntry) + { + auto value = floatEntry->GetRawValue(); + auto initialValue = value; + + bool useSlider = node->sliderMax > node->sliderMin; + + if (useSlider) + { + ImGui::SetNextItemWidth(160.0f); + ImGui::SliderFloat(node->name.c_str(), &value, node->sliderMin, node->sliderMax, "%.2f"); + + if (node->sliderStep > 0.0f) + { + value = node->sliderMin + std::round((value - node->sliderMin) / node->sliderStep) * node->sliderStep; + } + } + else + { + ImGui::InputScalar(node->name.c_str(), ImGuiDataType_Float, &value); + } + + if (initialValue != value) + { + floatEntry->SetRawValue(value); + } + + return; + } } catch (std::bad_cast& e) { @@ -246,6 +298,28 @@ static InitFunction initFunction([]() node->commandOrConVar = convarName; }); + // devgui_convar with an explicit slider range: devgui_convar_slider "path" convar min max [step] + static ConsoleCommand devguiAddConVarSlider("devgui_convar_slider", [](const DevGuiPath& path, const std::string& convarName, float min, float max) + { + auto node = DevGui_InstantiatePath(path); + + node->type = DevGuiNode::DevGuiNode_ConVar; + node->commandOrConVar = convarName; + node->sliderMin = min; + node->sliderMax = max; + }); + + static ConsoleCommand devguiAddConVarSliderStep("devgui_convar_slider", [](const DevGuiPath& path, const std::string& convarName, float min, float max, float step) + { + auto node = DevGui_InstantiatePath(path); + + node->type = DevGuiNode::DevGuiNode_ConVar; + node->commandOrConVar = convarName; + node->sliderMin = min; + node->sliderMax = max; + node->sliderStep = step; + }); + #ifndef IS_FXSERVER static ConVar uiConnectHost("uiConnectHost", ConVar_Archive | ConVar_UserPref, ""); @@ -277,7 +351,7 @@ devgui_convar "Tools/Windowed Console" con_winConsole set "game_mute" "profile_sfxVolume 0; profile_musicVolumeInMp 0; profile_musicVolume 0" set "game_unmute" "profile_sfxVolume 25; profile_musicVolumeInMp 10; profile_musicVolume 10" -devgui_convar "Game/SFX Volume" profile_sfxVolume +devgui_convar_slider "Game/SFX Volume" profile_sfxVolume 0 25 devgui_cmd "Game/Mute" "vstr game_mute" devgui_cmd "Game/Unmute" "vstr game_unmute" @@ -285,6 +359,12 @@ devgui_convar "Overlays/Performance/Draw Performance" cl_drawPerf devgui_cmd "Overlays/Performance/--------------------" "wait 1" )"); +#ifdef GTA_FIVE + console::GetDefaultContext()->AddToBuffer(R"( +devgui_convar_slider "Game/Vehicle Volume" profile_vehicleVolume 0.0 1.0 0.05 +)"); +#endif + if (IsNonProduction()) { console::GetDefaultContext()->AddToBuffer(R"( diff --git a/code/components/extra-natives-five/src/VehicleAudioVolume.cpp b/code/components/extra-natives-five/src/VehicleAudioVolume.cpp new file mode 100644 index 0000000000..f8747dd24d --- /dev/null +++ b/code/components/extra-natives-five/src/VehicleAudioVolume.cpp @@ -0,0 +1,107 @@ +/* +* This file is part of the CitizenFX project - http://citizen.re/ +* +* See LICENSE and MENTIONS in the root of the source tree for information +* regarding licensing. +*/ + +#include "StdInc.h" + +#include +#include +#include +#include +#include + +#include + +static float g_vehicleVolume = 1.0f; + +// "VEHICLES" - parent category of every vehicle sound +static constexpr uint32_t kVehiclesCategory = 0x74B31BE3; + +namespace +{ + void** g_audCategoryControllerManager = nullptr; + + hook::thiscall_stub _createController([]() + { + return hook::get_call(hook::get_pattern("45 33 C0 BA 90 1C E2 44 E8", 8)); + }); + + char* CreateVehicleController() + { + auto mgr = g_audCategoryControllerManager ? *g_audCategoryControllerManager : nullptr; + if (!mgr) + { + return nullptr; + } + + return _createController(mgr, kVehiclesCategory); + } +} + +static char* GetVehicleController() +{ + static char* controller = nullptr; + if (!controller) + { + controller = CreateVehicleController(); + } + return controller; +} + +static void ApplyVehicleVolume() +{ + if (auto controller = reinterpret_cast(GetVehicleController())) + { + controller->Set(0, g_vehicleVolume); + controller->Set(4, 0.0f); + } +} + +static HookFunction hookFunction([]() +{ + g_audCategoryControllerManager = hook::get_address(hook::get_pattern("45 33 C0 BA 90 1C E2 44 E8", -4)); + + OnMainGameFrame.Connect([]() + { + static bool applied = false; + if (!applied && GetVehicleController()) + { + ApplyVehicleVolume(); + applied = true; + } + }); +}); + +static InitFunction initFunction([]() +{ + static ConVar vehicleVolumeVar("profile_vehicleVolume", ConVar_Archive, 1.0f, &g_vehicleVolume); + + OnMainGameFrame.Connect([]() + { + static float last = -1.0f; + if (g_vehicleVolume != last) + { + g_vehicleVolume = std::clamp(g_vehicleVolume, 0.0f, 1.0f); + ApplyVehicleVolume(); + last = g_vehicleVolume; + } + }); + + // no new natives will be added as of now but here they are in case that changes. + + // global vehicle audio volume: 0.0 silent .. 1.0 normal + // kept disabled - gen8 is in feature freeze, no new natives for now + // fx::ScriptEngine::RegisterNativeHandler("SET_AUDIO_VEHICLE_VOLUME", [](fx::ScriptContext& context) + // { + // g_vehicleVolume = std::clamp(context.GetArgument(0), 0.0f, 1.0f); + // ApplyVehicleVolume(); + // }); + + // fx::ScriptEngine::RegisterNativeHandler("GET_AUDIO_VEHICLE_VOLUME", [](fx::ScriptContext& context) + // { + // context.SetResult(g_vehicleVolume); + // }); +});