From 9741c8ee7cf8fd8221a67012fa9c928c436616b7 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 9 Feb 2026 17:07:22 -0800 Subject: [PATCH] Implement comprehensive audio control panel with tabbed settings interface Adds complete audio volume controls for all 11 audio systems with master volume. Reorganizes settings window into Video, Audio, and Gameplay tabs for better UX. Audio Features: - Master volume control affecting all audio systems - Individual volume sliders for: Music, Ambient, UI, Combat, Spell, Movement, Footsteps, NPC Voices, Mounts, Activity sounds - Real-time volume adjustment with master volume multiplier - Restore defaults button per tab Technical Changes: - Added getVolumeScale() getters to all audio managers - Integrated all 10 audio managers into renderer (UI, Combat, Spell, Movement added) - Expanded game_screen.hpp with 11 pending volume variables - Reorganized settings window using ImGui tab bars (Video/Audio/Gameplay) - Audio settings uses scrollable child window for 11 volume controls - Settings window expanded to 520x720px to accommodate comprehensive controls --- CMakeLists.txt | 6 + include/audio/ambient_sound_manager.hpp | 1 + include/audio/combat_sound_manager.hpp | 1 + include/audio/mount_sound_manager.hpp | 1 + include/audio/movement_sound_manager.hpp | 1 + include/audio/spell_sound_manager.hpp | 1 + include/audio/ui_sound_manager.hpp | 7 +- include/rendering/renderer.hpp | 10 +- include/ui/game_screen.hpp | 11 +- src/audio/ui_sound_manager.cpp | 72 +++---- src/rendering/renderer.cpp | 32 +++ src/ui/game_screen.cpp | 250 +++++++++++++++++------ 12 files changed, 295 insertions(+), 98 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3921416c..39e25756 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -208,6 +208,12 @@ set(WOWEE_HEADERS include/audio/footstep_manager.hpp include/audio/activity_sound_manager.hpp include/audio/mount_sound_manager.hpp + include/audio/npc_voice_manager.hpp + include/audio/ambient_sound_manager.hpp + include/audio/ui_sound_manager.hpp + include/audio/combat_sound_manager.hpp + include/audio/spell_sound_manager.hpp + include/audio/movement_sound_manager.hpp include/pipeline/mpq_manager.hpp include/pipeline/blp_loader.hpp diff --git a/include/audio/ambient_sound_manager.hpp b/include/audio/ambient_sound_manager.hpp index 78590d01..88f326aa 100644 --- a/include/audio/ambient_sound_manager.hpp +++ b/include/audio/ambient_sound_manager.hpp @@ -82,6 +82,7 @@ public: // Volume control void setVolumeScale(float scale); + float getVolumeScale() const { return volumeScale_; } private: struct AmbientEmitter { diff --git a/include/audio/combat_sound_manager.hpp b/include/audio/combat_sound_manager.hpp index 8bc1063c..eec2bf67 100644 --- a/include/audio/combat_sound_manager.hpp +++ b/include/audio/combat_sound_manager.hpp @@ -23,6 +23,7 @@ public: // Volume control void setVolumeScale(float scale); + float getVolumeScale() const { return volumeScale_; } // Weapon swing sounds (whoosh sounds before impact) enum class WeaponSize { diff --git a/include/audio/mount_sound_manager.hpp b/include/audio/mount_sound_manager.hpp index 6a603e89..3eb96560 100644 --- a/include/audio/mount_sound_manager.hpp +++ b/include/audio/mount_sound_manager.hpp @@ -42,6 +42,7 @@ public: bool isMounted() const { return mounted_; } void setVolumeScale(float scale) { volumeScale_ = scale; } + float getVolumeScale() const { return volumeScale_; } private: MountType detectMountType(uint32_t creatureDisplayId) const; diff --git a/include/audio/movement_sound_manager.hpp b/include/audio/movement_sound_manager.hpp index 87b179dd..caea8ffc 100644 --- a/include/audio/movement_sound_manager.hpp +++ b/include/audio/movement_sound_manager.hpp @@ -23,6 +23,7 @@ public: // Volume control void setVolumeScale(float scale); + float getVolumeScale() const { return volumeScale_; } // Character size (for water splash intensity) enum class CharacterSize { diff --git a/include/audio/spell_sound_manager.hpp b/include/audio/spell_sound_manager.hpp index e3969719..1933a7aa 100644 --- a/include/audio/spell_sound_manager.hpp +++ b/include/audio/spell_sound_manager.hpp @@ -23,6 +23,7 @@ public: // Volume control void setVolumeScale(float scale); + float getVolumeScale() const { return volumeScale_; } // Magic school types enum class MagicSchool { diff --git a/include/audio/ui_sound_manager.hpp b/include/audio/ui_sound_manager.hpp index 5d314a68..1ab91ebd 100644 --- a/include/audio/ui_sound_manager.hpp +++ b/include/audio/ui_sound_manager.hpp @@ -12,10 +12,10 @@ class AssetManager; namespace audio { -class UISoundManager { +class UiSoundManager { public: - UISoundManager() = default; - ~UISoundManager() = default; + UiSoundManager() = default; + ~UiSoundManager() = default; // Initialization bool initialize(pipeline::AssetManager* assets); @@ -23,6 +23,7 @@ public: // Volume control void setVolumeScale(float scale); + float getVolumeScale() const { return volumeScale_; } // Window sounds void playBagOpen(); diff --git a/include/rendering/renderer.hpp b/include/rendering/renderer.hpp index 3211a910..ada36027 100644 --- a/include/rendering/renderer.hpp +++ b/include/rendering/renderer.hpp @@ -8,7 +8,7 @@ namespace wowee { namespace core { class Window; } namespace game { class World; class ZoneManager; } -namespace audio { class MusicManager; class FootstepManager; class ActivitySoundManager; class MountSoundManager; class NpcVoiceManager; class AmbientSoundManager; enum class FootstepSurface : uint8_t; enum class VoiceType; } +namespace audio { class MusicManager; class FootstepManager; class ActivitySoundManager; class MountSoundManager; class NpcVoiceManager; class AmbientSoundManager; class UiSoundManager; class CombatSoundManager; class SpellSoundManager; class MovementSoundManager; enum class FootstepSurface : uint8_t; enum class VoiceType; } namespace pipeline { class AssetManager; } namespace rendering { @@ -151,6 +151,10 @@ public: audio::MountSoundManager* getMountSoundManager() { return mountSoundManager.get(); } audio::NpcVoiceManager* getNpcVoiceManager() { return npcVoiceManager.get(); } audio::AmbientSoundManager* getAmbientSoundManager() { return ambientSoundManager.get(); } + audio::UiSoundManager* getUiSoundManager() { return uiSoundManager.get(); } + audio::CombatSoundManager* getCombatSoundManager() { return combatSoundManager.get(); } + audio::SpellSoundManager* getSpellSoundManager() { return spellSoundManager.get(); } + audio::MovementSoundManager* getMovementSoundManager() { return movementSoundManager.get(); } private: core::Window* window = nullptr; @@ -179,6 +183,10 @@ private: std::unique_ptr mountSoundManager; std::unique_ptr npcVoiceManager; std::unique_ptr ambientSoundManager; + std::unique_ptr uiSoundManager; + std::unique_ptr combatSoundManager; + std::unique_ptr spellSoundManager; + std::unique_ptr movementSoundManager; std::unique_ptr zoneManager; std::unique_ptr underwaterOverlayShader; uint32_t underwaterOverlayVAO = 0; diff --git a/include/ui/game_screen.hpp b/include/ui/game_screen.hpp index dd5faf43..a8303a44 100644 --- a/include/ui/game_screen.hpp +++ b/include/ui/game_screen.hpp @@ -65,8 +65,17 @@ private: bool pendingVsync = false; int pendingResIndex = 0; bool pendingShadows = false; + int pendingMasterVolume = 100; int pendingMusicVolume = 30; - int pendingSfxVolume = 100; + int pendingAmbientVolume = 100; + int pendingUiVolume = 100; + int pendingCombatVolume = 100; + int pendingSpellVolume = 100; + int pendingMovementVolume = 100; + int pendingFootstepVolume = 100; + int pendingNpcVoiceVolume = 100; + int pendingMountVolume = 100; + int pendingActivityVolume = 100; float pendingMouseSensitivity = 0.2f; bool pendingInvertMouse = false; int pendingUiOpacity = 65; diff --git a/src/audio/ui_sound_manager.cpp b/src/audio/ui_sound_manager.cpp index cdc12975..626263d3 100644 --- a/src/audio/ui_sound_manager.cpp +++ b/src/audio/ui_sound_manager.cpp @@ -6,7 +6,7 @@ namespace wowee { namespace audio { -bool UISoundManager::initialize(pipeline::AssetManager* assets) { +bool UiSoundManager::initialize(pipeline::AssetManager* assets) { if (!assets) { LOG_ERROR("UISoundManager: AssetManager is null"); return false; @@ -136,11 +136,11 @@ bool UISoundManager::initialize(pipeline::AssetManager* assets) { return true; } -void UISoundManager::shutdown() { +void UiSoundManager::shutdown() { initialized_ = false; } -bool UISoundManager::loadSound(const std::string& path, UISample& sample, pipeline::AssetManager* assets) { +bool UiSoundManager::loadSound(const std::string& path, UISample& sample, pipeline::AssetManager* assets) { sample.path = path; sample.loaded = false; @@ -157,63 +157,63 @@ bool UISoundManager::loadSound(const std::string& path, UISample& sample, pipeli return false; } -void UISoundManager::playSound(const std::vector& library) { +void UiSoundManager::playSound(const std::vector& library) { if (!initialized_ || library.empty() || !library[0].loaded) return; float volume = 0.7f * volumeScale_; AudioEngine::instance().playSound2D(library[0].data, volume, 1.0f); } -void UISoundManager::setVolumeScale(float scale) { +void UiSoundManager::setVolumeScale(float scale) { volumeScale_ = std::max(0.0f, std::min(1.0f, scale)); } // Window sounds -void UISoundManager::playBagOpen() { playSound(bagOpenSounds_); } -void UISoundManager::playBagClose() { playSound(bagCloseSounds_); } -void UISoundManager::playQuestLogOpen() { playSound(questLogOpenSounds_); } -void UISoundManager::playQuestLogClose() { playSound(questLogCloseSounds_); } -void UISoundManager::playCharacterSheetOpen() { playSound(characterSheetOpenSounds_); } -void UISoundManager::playCharacterSheetClose() { playSound(characterSheetCloseSounds_); } -void UISoundManager::playAuctionHouseOpen() { playSound(auctionOpenSounds_); } -void UISoundManager::playAuctionHouseClose() { playSound(auctionCloseSounds_); } -void UISoundManager::playGuildBankOpen() { playSound(guildBankOpenSounds_); } -void UISoundManager::playGuildBankClose() { playSound(guildBankCloseSounds_); } +void UiSoundManager::playBagOpen() { playSound(bagOpenSounds_); } +void UiSoundManager::playBagClose() { playSound(bagCloseSounds_); } +void UiSoundManager::playQuestLogOpen() { playSound(questLogOpenSounds_); } +void UiSoundManager::playQuestLogClose() { playSound(questLogCloseSounds_); } +void UiSoundManager::playCharacterSheetOpen() { playSound(characterSheetOpenSounds_); } +void UiSoundManager::playCharacterSheetClose() { playSound(characterSheetCloseSounds_); } +void UiSoundManager::playAuctionHouseOpen() { playSound(auctionOpenSounds_); } +void UiSoundManager::playAuctionHouseClose() { playSound(auctionCloseSounds_); } +void UiSoundManager::playGuildBankOpen() { playSound(guildBankOpenSounds_); } +void UiSoundManager::playGuildBankClose() { playSound(guildBankCloseSounds_); } // Button sounds -void UISoundManager::playButtonClick() { playSound(buttonClickSounds_); } -void UISoundManager::playMenuButtonClick() { playSound(menuButtonSounds_); } +void UiSoundManager::playButtonClick() { playSound(buttonClickSounds_); } +void UiSoundManager::playMenuButtonClick() { playSound(menuButtonSounds_); } // Quest sounds -void UISoundManager::playQuestActivate() { playSound(questActivateSounds_); } -void UISoundManager::playQuestComplete() { playSound(questCompleteSounds_); } -void UISoundManager::playQuestFailed() { playSound(questFailedSounds_); } -void UISoundManager::playQuestUpdate() { playSound(questUpdateSounds_); } +void UiSoundManager::playQuestActivate() { playSound(questActivateSounds_); } +void UiSoundManager::playQuestComplete() { playSound(questCompleteSounds_); } +void UiSoundManager::playQuestFailed() { playSound(questFailedSounds_); } +void UiSoundManager::playQuestUpdate() { playSound(questUpdateSounds_); } // Loot sounds -void UISoundManager::playLootCoinSmall() { playSound(lootCoinSmallSounds_); } -void UISoundManager::playLootCoinLarge() { playSound(lootCoinLargeSounds_); } -void UISoundManager::playLootItem() { playSound(lootItemSounds_); } +void UiSoundManager::playLootCoinSmall() { playSound(lootCoinSmallSounds_); } +void UiSoundManager::playLootCoinLarge() { playSound(lootCoinLargeSounds_); } +void UiSoundManager::playLootItem() { playSound(lootItemSounds_); } // Item sounds -void UISoundManager::playDropOnGround() { playSound(dropSounds_); } -void UISoundManager::playPickupBag() { playSound(pickupBagSounds_); } -void UISoundManager::playPickupBook() { playSound(pickupBookSounds_); } -void UISoundManager::playPickupCloth() { playSound(pickupClothSounds_); } -void UISoundManager::playPickupFood() { playSound(pickupFoodSounds_); } -void UISoundManager::playPickupGem() { playSound(pickupGemSounds_); } +void UiSoundManager::playDropOnGround() { playSound(dropSounds_); } +void UiSoundManager::playPickupBag() { playSound(pickupBagSounds_); } +void UiSoundManager::playPickupBook() { playSound(pickupBookSounds_); } +void UiSoundManager::playPickupCloth() { playSound(pickupClothSounds_); } +void UiSoundManager::playPickupFood() { playSound(pickupFoodSounds_); } +void UiSoundManager::playPickupGem() { playSound(pickupGemSounds_); } // Eating/drinking -void UISoundManager::playEating() { playSound(eatingSounds_); } -void UISoundManager::playDrinking() { playSound(drinkingSounds_); } +void UiSoundManager::playEating() { playSound(eatingSounds_); } +void UiSoundManager::playDrinking() { playSound(drinkingSounds_); } // Level up -void UISoundManager::playLevelUp() { playSound(levelUpSounds_); } +void UiSoundManager::playLevelUp() { playSound(levelUpSounds_); } // Error/feedback -void UISoundManager::playError() { playSound(errorSounds_); } -void UISoundManager::playTargetSelect() { playSound(selectTargetSounds_); } -void UISoundManager::playTargetDeselect() { playSound(deselectTargetSounds_); } +void UiSoundManager::playError() { playSound(errorSounds_); } +void UiSoundManager::playTargetSelect() { playSound(selectTargetSounds_); } +void UiSoundManager::playTargetDeselect() { playSound(deselectTargetSounds_); } } // namespace audio } // namespace wowee diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index 0daeb67c..bea857a7 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -39,6 +39,10 @@ #include "audio/mount_sound_manager.hpp" #include "audio/npc_voice_manager.hpp" #include "audio/ambient_sound_manager.hpp" +#include "audio/ui_sound_manager.hpp" +#include "audio/combat_sound_manager.hpp" +#include "audio/spell_sound_manager.hpp" +#include "audio/movement_sound_manager.hpp" #include #include #include @@ -346,6 +350,10 @@ bool Renderer::initialize(core::Window* win) { mountSoundManager = std::make_unique(); npcVoiceManager = std::make_unique(); ambientSoundManager = std::make_unique(); + uiSoundManager = std::make_unique(); + combatSoundManager = std::make_unique(); + spellSoundManager = std::make_unique(); + movementSoundManager = std::make_unique(); // Underwater full-screen tint overlay (applies to all world geometry). underwaterOverlayShader = std::make_unique(); @@ -2204,6 +2212,18 @@ bool Renderer::loadTestTerrain(pipeline::AssetManager* assetManager, const std:: if (ambientSoundManager) { ambientSoundManager->initialize(assetManager); } + if (uiSoundManager) { + uiSoundManager->initialize(assetManager); + } + if (combatSoundManager) { + combatSoundManager->initialize(assetManager); + } + if (spellSoundManager) { + spellSoundManager->initialize(assetManager); + } + if (movementSoundManager) { + movementSoundManager->initialize(assetManager); + } cachedAssetManager = assetManager; } @@ -2291,6 +2311,18 @@ bool Renderer::loadTerrainArea(const std::string& mapName, int centerX, int cent if (ambientSoundManager && cachedAssetManager) { ambientSoundManager->initialize(cachedAssetManager); } + if (uiSoundManager && cachedAssetManager) { + uiSoundManager->initialize(cachedAssetManager); + } + if (combatSoundManager && cachedAssetManager) { + combatSoundManager->initialize(cachedAssetManager); + } + if (spellSoundManager && cachedAssetManager) { + spellSoundManager->initialize(cachedAssetManager); + } + if (movementSoundManager && cachedAssetManager) { + movementSoundManager->initialize(cachedAssetManager); + } // Wire ambient sound manager to terrain manager for emitter registration if (terrainManager && ambientSoundManager) { diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 8abb8cac..a0cb58d9 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -12,6 +12,13 @@ #include "audio/music_manager.hpp" #include "audio/footstep_manager.hpp" #include "audio/activity_sound_manager.hpp" +#include "audio/mount_sound_manager.hpp" +#include "audio/npc_voice_manager.hpp" +#include "audio/ambient_sound_manager.hpp" +#include "audio/ui_sound_manager.hpp" +#include "audio/combat_sound_manager.hpp" +#include "audio/spell_sound_manager.hpp" +#include "audio/movement_sound_manager.hpp" #include "pipeline/asset_manager.hpp" #include "pipeline/dbc_loader.hpp" #include "pipeline/blp_loader.hpp" @@ -4039,19 +4046,36 @@ void GameScreen::renderSettingsWindow() { pendingVsync = window->isVsyncEnabled(); pendingShadows = renderer ? renderer->areShadowsEnabled() : true; if (renderer) { + // Load volumes from all audio managers if (auto* music = renderer->getMusicManager()) { pendingMusicVolume = music->getVolume(); } + if (auto* ambient = renderer->getAmbientSoundManager()) { + pendingAmbientVolume = static_cast(ambient->getVolumeScale() * 100.0f + 0.5f); + } + if (auto* ui = renderer->getUiSoundManager()) { + pendingUiVolume = static_cast(ui->getVolumeScale() * 100.0f + 0.5f); + } + if (auto* combat = renderer->getCombatSoundManager()) { + pendingCombatVolume = static_cast(combat->getVolumeScale() * 100.0f + 0.5f); + } + if (auto* spell = renderer->getSpellSoundManager()) { + pendingSpellVolume = static_cast(spell->getVolumeScale() * 100.0f + 0.5f); + } + if (auto* movement = renderer->getMovementSoundManager()) { + pendingMovementVolume = static_cast(movement->getVolumeScale() * 100.0f + 0.5f); + } if (auto* footstep = renderer->getFootstepManager()) { - float scale = footstep->getVolumeScale(); - pendingSfxVolume = static_cast(scale * 100.0f + 0.5f); - if (pendingSfxVolume < 0) pendingSfxVolume = 0; - if (pendingSfxVolume > 100) pendingSfxVolume = 100; - } else if (auto* activity = renderer->getActivitySoundManager()) { - float scale = activity->getVolumeScale(); - pendingSfxVolume = static_cast(scale * 100.0f + 0.5f); - if (pendingSfxVolume < 0) pendingSfxVolume = 0; - if (pendingSfxVolume > 100) pendingSfxVolume = 100; + pendingFootstepVolume = static_cast(footstep->getVolumeScale() * 100.0f + 0.5f); + } + if (auto* npcVoice = renderer->getNpcVoiceManager()) { + pendingNpcVoiceVolume = static_cast(npcVoice->getVolumeScale() * 100.0f + 0.5f); + } + if (auto* mount = renderer->getMountSoundManager()) { + pendingMountVolume = static_cast(mount->getVolumeScale() * 100.0f + 0.5f); + } + if (auto* activity = renderer->getActivitySoundManager()) { + pendingActivityVolume = static_cast(activity->getVolumeScale() * 100.0f + 0.5f); } if (auto* cameraController = renderer->getCameraController()) { pendingMouseSensitivity = cameraController->getMouseSensitivity(); @@ -4080,7 +4104,7 @@ void GameScreen::renderSettingsWindow() { ImGuiIO& io = ImGui::GetIO(); float screenW = io.DisplaySize.x; float screenH = io.DisplaySize.y; - ImVec2 size(440.0f, 520.0f); + ImVec2 size(520.0f, std::min(screenH * 0.9f, 720.0f)); ImVec2 pos((screenW - size.x) * 0.5f, (screenH - size.y) * 0.5f); ImGui::SetNextWindowPos(pos, ImGuiCond_Always); @@ -4092,60 +4116,148 @@ void GameScreen::renderSettingsWindow() { ImGui::Text("Settings"); ImGui::Separator(); - ImGui::Text("Video"); - ImGui::Checkbox("Fullscreen", &pendingFullscreen); - ImGui::Checkbox("VSync", &pendingVsync); - ImGui::Checkbox("Shadows", &pendingShadows); + if (ImGui::BeginTabBar("SettingsTabs", ImGuiTabBarFlags_None)) { + // ============================================================ + // VIDEO TAB + // ============================================================ + if (ImGui::BeginTabItem("Video")) { + ImGui::Spacing(); + ImGui::Checkbox("Fullscreen", &pendingFullscreen); + ImGui::Checkbox("VSync", &pendingVsync); + ImGui::Checkbox("Shadows", &pendingShadows); - const char* resLabel = "Resolution"; - const char* resItems[kResCount]; - char resBuf[kResCount][16]; - for (int i = 0; i < kResCount; i++) { - snprintf(resBuf[i], sizeof(resBuf[i]), "%dx%d", kResolutions[i][0], kResolutions[i][1]); - resItems[i] = resBuf[i]; - } - ImGui::Combo(resLabel, &pendingResIndex, resItems, kResCount); - if (ImGui::Button("Restore Video Defaults", ImVec2(-1, 0))) { - pendingFullscreen = kDefaultFullscreen; - pendingVsync = kDefaultVsync; - pendingShadows = kDefaultShadows; - pendingResIndex = defaultResIndex; - } + const char* resLabel = "Resolution"; + const char* resItems[kResCount]; + char resBuf[kResCount][16]; + for (int i = 0; i < kResCount; i++) { + snprintf(resBuf[i], sizeof(resBuf[i]), "%dx%d", kResolutions[i][0], kResolutions[i][1]); + resItems[i] = resBuf[i]; + } + ImGui::Combo(resLabel, &pendingResIndex, resItems, kResCount); - ImGui::Spacing(); + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + if (ImGui::Button("Restore Video Defaults", ImVec2(-1, 0))) { + pendingFullscreen = kDefaultFullscreen; + pendingVsync = kDefaultVsync; + pendingShadows = kDefaultShadows; + pendingResIndex = defaultResIndex; + } + + ImGui::EndTabItem(); + } + + // ============================================================ + // AUDIO TAB + // ============================================================ + if (ImGui::BeginTabItem("Audio")) { + ImGui::Spacing(); + ImGui::BeginChild("AudioSettings", ImVec2(0, 360), true); + + ImGui::Text("Master Volume"); + ImGui::SliderInt("##MasterVolume", &pendingMasterVolume, 0, 100, "%d%%"); ImGui::Separator(); - ImGui::Spacing(); - ImGui::Text("Audio"); - ImGui::SliderInt("Music Volume", &pendingMusicVolume, 0, 100, "%d"); - ImGui::SliderInt("SFX Volume", &pendingSfxVolume, 0, 100, "%d"); + ImGui::Text("Music"); + ImGui::SliderInt("##MusicVolume", &pendingMusicVolume, 0, 100, "%d%%"); + + ImGui::Spacing(); + ImGui::Text("Ambient Sounds"); + ImGui::SliderInt("##AmbientVolume", &pendingAmbientVolume, 0, 100, "%d%%"); + ImGui::TextWrapped("Weather, zones, cities, emitters"); + + ImGui::Spacing(); + ImGui::Text("UI Sounds"); + ImGui::SliderInt("##UiVolume", &pendingUiVolume, 0, 100, "%d%%"); + ImGui::TextWrapped("Buttons, loot, quest complete"); + + ImGui::Spacing(); + ImGui::Text("Combat Sounds"); + ImGui::SliderInt("##CombatVolume", &pendingCombatVolume, 0, 100, "%d%%"); + ImGui::TextWrapped("Weapon swings, impacts, grunts"); + + ImGui::Spacing(); + ImGui::Text("Spell Sounds"); + ImGui::SliderInt("##SpellVolume", &pendingSpellVolume, 0, 100, "%d%%"); + ImGui::TextWrapped("Magic casting and impacts"); + + ImGui::Spacing(); + ImGui::Text("Movement Sounds"); + ImGui::SliderInt("##MovementVolume", &pendingMovementVolume, 0, 100, "%d%%"); + ImGui::TextWrapped("Water splashes, jump/land"); + + ImGui::Spacing(); + ImGui::Text("Footsteps"); + ImGui::SliderInt("##FootstepVolume", &pendingFootstepVolume, 0, 100, "%d%%"); + + ImGui::Spacing(); + ImGui::Text("NPC Voices"); + ImGui::SliderInt("##NpcVoiceVolume", &pendingNpcVoiceVolume, 0, 100, "%d%%"); + + ImGui::Spacing(); + ImGui::Text("Mount Sounds"); + ImGui::SliderInt("##MountVolume", &pendingMountVolume, 0, 100, "%d%%"); + + ImGui::Spacing(); + ImGui::Text("Activity Sounds"); + ImGui::SliderInt("##ActivityVolume", &pendingActivityVolume, 0, 100, "%d%%"); + ImGui::TextWrapped("Swimming, eating, drinking"); + + ImGui::EndChild(); + if (ImGui::Button("Restore Audio Defaults", ImVec2(-1, 0))) { + pendingMasterVolume = 100; pendingMusicVolume = kDefaultMusicVolume; - pendingSfxVolume = kDefaultSfxVolume; + pendingAmbientVolume = 100; + pendingUiVolume = 100; + pendingCombatVolume = 100; + pendingSpellVolume = 100; + pendingMovementVolume = 100; + pendingFootstepVolume = 100; + pendingNpcVoiceVolume = 100; + pendingMountVolume = 100; + pendingActivityVolume = 100; } - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); + ImGui::EndTabItem(); + } - ImGui::Text("Controls"); - ImGui::SliderFloat("Mouse Sensitivity", &pendingMouseSensitivity, 0.05f, 1.0f, "%.2f"); - ImGui::Checkbox("Invert Mouse", &pendingInvertMouse); - if (ImGui::Button("Restore Control Defaults", ImVec2(-1, 0))) { - pendingMouseSensitivity = kDefaultMouseSensitivity; - pendingInvertMouse = kDefaultInvertMouse; - } + // ============================================================ + // GAMEPLAY TAB + // ============================================================ + if (ImGui::BeginTabItem("Gameplay")) { + ImGui::Spacing(); - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); + ImGui::Text("Controls"); + ImGui::Separator(); + ImGui::SliderFloat("Mouse Sensitivity", &pendingMouseSensitivity, 0.05f, 1.0f, "%.2f"); + ImGui::Checkbox("Invert Mouse", &pendingInvertMouse); - ImGui::Text("Interface"); - ImGui::SliderInt("UI Opacity", &pendingUiOpacity, 20, 100, "%d%%"); - ImGui::Checkbox("Rotate Minimap", &pendingMinimapRotate); - if (ImGui::Button("Restore Interface Defaults", ImVec2(-1, 0))) { - pendingUiOpacity = 65; - pendingMinimapRotate = false; + ImGui::Spacing(); + ImGui::Spacing(); + + ImGui::Text("Interface"); + ImGui::Separator(); + ImGui::SliderInt("UI Opacity", &pendingUiOpacity, 20, 100, "%d%%"); + ImGui::Checkbox("Rotate Minimap", &pendingMinimapRotate); + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + if (ImGui::Button("Restore Gameplay Defaults", ImVec2(-1, 0))) { + pendingMouseSensitivity = kDefaultMouseSensitivity; + pendingInvertMouse = kDefaultInvertMouse; + pendingUiOpacity = 65; + pendingMinimapRotate = false; + } + + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); } ImGui::Spacing(); @@ -4164,16 +4276,40 @@ void GameScreen::renderSettingsWindow() { if (auto* minimap = renderer->getMinimap()) { minimap->setRotateWithCamera(minimapRotate_); } + + // Apply all audio volume settings + float masterScale = static_cast(pendingMasterVolume) / 100.0f; if (auto* music = renderer->getMusicManager()) { - music->setVolume(pendingMusicVolume); + music->setVolume(static_cast(pendingMusicVolume * masterScale)); + } + if (auto* ambient = renderer->getAmbientSoundManager()) { + ambient->setVolumeScale(pendingAmbientVolume / 100.0f * masterScale); + } + if (auto* ui = renderer->getUiSoundManager()) { + ui->setVolumeScale(pendingUiVolume / 100.0f * masterScale); + } + if (auto* combat = renderer->getCombatSoundManager()) { + combat->setVolumeScale(pendingCombatVolume / 100.0f * masterScale); + } + if (auto* spell = renderer->getSpellSoundManager()) { + spell->setVolumeScale(pendingSpellVolume / 100.0f * masterScale); + } + if (auto* movement = renderer->getMovementSoundManager()) { + movement->setVolumeScale(pendingMovementVolume / 100.0f * masterScale); } - float sfxScale = static_cast(pendingSfxVolume) / 100.0f; if (auto* footstep = renderer->getFootstepManager()) { - footstep->setVolumeScale(sfxScale); + footstep->setVolumeScale(pendingFootstepVolume / 100.0f * masterScale); + } + if (auto* npcVoice = renderer->getNpcVoiceManager()) { + npcVoice->setVolumeScale(pendingNpcVoiceVolume / 100.0f * masterScale); + } + if (auto* mount = renderer->getMountSoundManager()) { + mount->setVolumeScale(pendingMountVolume / 100.0f * masterScale); } if (auto* activity = renderer->getActivitySoundManager()) { - activity->setVolumeScale(sfxScale); + activity->setVolumeScale(pendingActivityVolume / 100.0f * masterScale); } + if (auto* cameraController = renderer->getCameraController()) { cameraController->setMouseSensitivity(pendingMouseSensitivity); cameraController->setInvertMouse(pendingInvertMouse);