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);