From a55399fad62f89db244e012b61f770dd41fa51d7 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 17 Feb 2026 16:26:49 -0800 Subject: [PATCH] Add mute button and original soundtrack toggle - Mute button: small [M] button alongside minimap zoom controls, turns red when active; directly sets AudioEngine master volume to 0, restores on unmute; persists in settings.cfg - Original Soundtrack: checkbox in Settings > Audio that controls whether custom original music tracks (file: prefix) are included in zone music rotation; when disabled, only WoW MPQ tracks play; persists in settings.cfg - ZoneManager.getRandomMusic() now filters file: paths when OST is disabled, falling back to full list if zone has no non-file tracks --- include/game/zone_manager.hpp | 5 +++ include/rendering/renderer.hpp | 1 + include/ui/game_screen.hpp | 5 +++ src/game/zone_manager.cpp | 30 +++++++++++----- src/ui/game_screen.cpp | 63 +++++++++++++++++++++++++++++++--- 5 files changed, 91 insertions(+), 13 deletions(-) diff --git a/include/game/zone_manager.hpp b/include/game/zone_manager.hpp index 0b02820e..281aad16 100644 --- a/include/game/zone_manager.hpp +++ b/include/game/zone_manager.hpp @@ -23,11 +23,16 @@ public: std::string getRandomMusic(uint32_t zoneId); std::vector getAllMusicPaths() const; + // When false, file: (original soundtrack) tracks are excluded from the pool + void setUseOriginalSoundtrack(bool use) { useOriginalSoundtrack_ = use; } + bool getUseOriginalSoundtrack() const { return useOriginalSoundtrack_; } + private: // tile key = tileX * 100 + tileY std::unordered_map tileToZone; std::unordered_map zones; std::string lastPlayedMusic_; + bool useOriginalSoundtrack_ = true; }; } // namespace game diff --git a/include/rendering/renderer.hpp b/include/rendering/renderer.hpp index d3c8c8a5..b6b517c3 100644 --- a/include/rendering/renderer.hpp +++ b/include/rendering/renderer.hpp @@ -155,6 +155,7 @@ public: double getLastWMORenderMs() const { return lastWMORenderMs; } double getLastM2RenderMs() const { return lastM2RenderMs; } audio::MusicManager* getMusicManager() { return musicManager.get(); } + game::ZoneManager* getZoneManager() { return zoneManager.get(); } audio::FootstepManager* getFootstepManager() { return footstepManager.get(); } audio::ActivitySoundManager* getActivitySoundManager() { return activitySoundManager.get(); } audio::MountSoundManager* getMountSoundManager() { return mountSoundManager.get(); } diff --git a/include/ui/game_screen.hpp b/include/ui/game_screen.hpp index d1833c63..f0b48f34 100644 --- a/include/ui/game_screen.hpp +++ b/include/ui/game_screen.hpp @@ -97,6 +97,7 @@ private: bool pendingMinimapRotate = false; bool pendingMinimapSquare = false; bool pendingSeparateBags = true; + bool pendingUseOriginalSoundtrack = true; // UI element transparency (0.0 = fully transparent, 1.0 = fully opaque) float uiOpacity_ = 0.65f; @@ -104,6 +105,10 @@ private: bool minimapSquare_ = false; bool minimapSettingsApplied_ = false; + // Mute state: mute bypasses master volume without touching slider values + bool soundMuted_ = false; + float preMuteVolume_ = 1.0f; // AudioEngine master volume before muting + /** * Render player info window */ diff --git a/src/game/zone_manager.cpp b/src/game/zone_manager.cpp index 4208f10a..de7d2bfa 100644 --- a/src/game/zone_manager.cpp +++ b/src/game/zone_manager.cpp @@ -436,20 +436,32 @@ std::string ZoneManager::getRandomMusic(uint32_t zoneId) { return ""; } - const auto& paths = it->second.musicPaths; - if (paths.size() == 1) { - lastPlayedMusic_ = paths[0]; - return paths[0]; + // Build filtered pool: exclude file: (original soundtrack) tracks if disabled + const auto& all = it->second.musicPaths; + std::vector pool; + pool.reserve(all.size()); + for (const auto& p : all) { + if (!useOriginalSoundtrack_ && p.rfind("file:", 0) == 0) continue; + pool.push_back(&p); + } + // Fall back to full list if filtering left nothing + if (pool.empty()) { + for (const auto& p : all) pool.push_back(&p); + } + + if (pool.size() == 1) { + lastPlayedMusic_ = *pool[0]; + return lastPlayedMusic_; } // Avoid playing the same track back-to-back - std::string pick; + const std::string* pick = pool[0]; for (int attempts = 0; attempts < 5; ++attempts) { - pick = paths[std::rand() % paths.size()]; - if (pick != lastPlayedMusic_) break; + pick = pool[std::rand() % pool.size()]; + if (*pick != lastPlayedMusic_) break; } - lastPlayedMusic_ = pick; - return pick; + lastPlayedMusic_ = *pick; + return lastPlayedMusic_; } std::vector ZoneManager::getAllMusicPaths() const { diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 98c8a530..eb09d8de 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -9,7 +9,9 @@ #include "rendering/character_renderer.hpp" #include "rendering/camera.hpp" #include "rendering/camera_controller.hpp" +#include "audio/audio_engine.hpp" #include "audio/music_manager.hpp" +#include "game/zone_manager.hpp" #include "audio/footstep_manager.hpp" #include "audio/activity_sound_manager.hpp" #include "audio/mount_sound_manager.hpp" @@ -190,7 +192,7 @@ void GameScreen::render(game::GameHandler& gameHandler) { float prevAlpha = ImGui::GetStyle().Alpha; ImGui::GetStyle().Alpha = uiOpacity_; - // Apply initial minimap settings when renderer becomes available + // Apply initial settings when renderer becomes available if (!minimapSettingsApplied_) { auto* renderer = core::Application::getInstance().getRenderer(); if (renderer) { @@ -201,6 +203,16 @@ void GameScreen::render(game::GameHandler& gameHandler) { minimap->setSquareShape(minimapSquare_); minimapSettingsApplied_ = true; } + if (auto* zm = renderer->getZoneManager()) { + zm->setUseOriginalSoundtrack(pendingUseOriginalSoundtrack); + } + // Restore mute state: save actual master volume first, then apply mute + if (soundMuted_) { + float actual = audio::AudioEngine::instance().getMasterVolume(); + preMuteVolume_ = (actual > 0.0f) ? actual + : static_cast(pendingMasterVolume) / 100.0f; + audio::AudioEngine::instance().setMasterVolume(0.0f); + } } } @@ -5416,6 +5428,9 @@ void GameScreen::renderSettingsWindow() { minimap->setRotateWithCamera(minimapRotate_); minimap->setSquareShape(minimapSquare_); } + if (auto* zm = renderer->getZoneManager()) { + pendingUseOriginalSoundtrack = zm->getUseOriginalSoundtrack(); + } } settingsInit = true; } @@ -5536,6 +5551,18 @@ void GameScreen::renderSettingsWindow() { } ImGui::Separator(); + if (ImGui::Checkbox("Original Soundtrack", &pendingUseOriginalSoundtrack)) { + if (renderer) { + if (auto* zm = renderer->getZoneManager()) { + zm->setUseOriginalSoundtrack(pendingUseOriginalSoundtrack); + } + } + saveSettings(); + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Include original music tracks in zone music rotation"); + ImGui::Separator(); + ImGui::Text("Music"); if (ImGui::SliderInt("##MusicVolume", &pendingMusicVolume, 0, 100, "%d%%")) { applyAudioSettings(); @@ -5976,15 +6003,33 @@ void GameScreen::renderMinimapMarkers(game::GameHandler& gameHandler) { IM_COL32(0, 0, 0, 255), marker); } - // Add zoom buttons at the bottom edge of the minimap - ImGui::SetNextWindowPos(ImVec2(centerX - 30, centerY + mapRadius - 30), ImGuiCond_Always); - ImGui::SetNextWindowSize(ImVec2(60, 24), ImGuiCond_Always); + // Add zoom + mute buttons at the bottom edge of the minimap + ImGui::SetNextWindowPos(ImVec2(centerX - 45, centerY + mapRadius - 30), ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(90, 24), ImGuiCond_Always); ImGuiWindowFlags zoomFlags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoBackground; if (ImGui::Begin("##MinimapZoom", nullptr, zoomFlags)) { ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2, 0)); + + // Mute toggle button: red tint when muted + if (soundMuted_) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.7f, 0.15f, 0.15f, 0.9f)); + if (ImGui::SmallButton(soundMuted_ ? "[M]" : " M ")) { + soundMuted_ = !soundMuted_; + auto& engine = audio::AudioEngine::instance(); + if (soundMuted_) { + preMuteVolume_ = engine.getMasterVolume(); + engine.setMasterVolume(0.0f); + } else { + engine.setMasterVolume(preMuteVolume_); + } + saveSettings(); + } + if (soundMuted_) ImGui::PopStyleColor(); + if (ImGui::IsItemHovered()) ImGui::SetTooltip(soundMuted_ ? "Unmute" : "Mute"); + + ImGui::SameLine(); if (ImGui::SmallButton("-")) { if (minimap) minimap->zoomOut(); } @@ -6245,6 +6290,8 @@ void GameScreen::saveSettings() { out << "separate_bags=" << (pendingSeparateBags ? 1 : 0) << "\n"; // Audio + out << "sound_muted=" << (soundMuted_ ? 1 : 0) << "\n"; + out << "use_original_soundtrack=" << (pendingUseOriginalSoundtrack ? 1 : 0) << "\n"; out << "master_volume=" << pendingMasterVolume << "\n"; out << "music_volume=" << pendingMusicVolume << "\n"; out << "ambient_volume=" << pendingAmbientVolume << "\n"; @@ -6307,6 +6354,14 @@ void GameScreen::loadSettings() { inventoryScreen.setSeparateBags(pendingSeparateBags); } // Audio + else if (key == "sound_muted") { + soundMuted_ = (std::stoi(val) != 0); + if (soundMuted_) { + // Apply mute on load; preMuteVolume_ will be set when AudioEngine is available + audio::AudioEngine::instance().setMasterVolume(0.0f); + } + } + else if (key == "use_original_soundtrack") pendingUseOriginalSoundtrack = (std::stoi(val) != 0); else if (key == "master_volume") pendingMasterVolume = std::clamp(std::stoi(val), 0, 100); else if (key == "music_volume") pendingMusicVolume = std::clamp(std::stoi(val), 0, 100); else if (key == "ambient_volume") pendingAmbientVolume = std::clamp(std::stoi(val), 0, 100);