Implement immediate settings application and fix escape menu behavior

Settings Changes:
- All settings now apply immediately without Apply button
- Each slider/checkbox instantly updates the game state and saves
- Escape key closes settings window if open (no longer shows escape menu behind it)
- Restore Defaults buttons now also apply settings immediately

Audio Changes:
- Increased NPC voice volume from 0.6 to 1.0 (60% to 100% base volume)
- Helper lambda in Audio tab for efficient volume application
- Master volume multiplier applied to all audio systems in real-time

UX Improvements:
- Removed redundant Apply button
- Settings save automatically on every change
- More intuitive settings flow - see changes instantly
- Cleaner escape key handling priority
This commit is contained in:
Kelsi 2026-02-09 17:12:35 -08:00
parent 9741c8ee7c
commit 016cc01c68
2 changed files with 181 additions and 120 deletions

View file

@ -306,7 +306,7 @@ void NpcVoiceManager::playSound(uint64_t npcGuid, VoiceType voiceType, SoundCate
bool success = AudioEngine::instance().playSound3D( bool success = AudioEngine::instance().playSound3D(
sample.data, sample.data,
position, position,
0.6f * volumeScale_, 1.0f * volumeScale_,
pitchDist(rng_), pitchDist(rng_),
60.0f 60.0f
); );

View file

@ -677,10 +677,12 @@ void GameScreen::processTargetInput(game::GameHandler& gameHandler) {
} }
if (input.isKeyJustPressed(SDL_SCANCODE_ESCAPE)) { if (input.isKeyJustPressed(SDL_SCANCODE_ESCAPE)) {
if (showEscapeMenu) { if (showSettingsWindow) {
// Close settings window if open
showSettingsWindow = false;
} else if (showEscapeMenu) {
showEscapeMenu = false; showEscapeMenu = false;
showEscapeSettingsNotice = false; showEscapeSettingsNotice = false;
showSettingsWindow = false;
} else if (gameHandler.isCasting()) { } else if (gameHandler.isCasting()) {
gameHandler.cancelCast(); gameHandler.cancelCast();
} else if (gameHandler.isLootWindowOpen()) { } else if (gameHandler.isLootWindowOpen()) {
@ -4122,9 +4124,19 @@ void GameScreen::renderSettingsWindow() {
// ============================================================ // ============================================================
if (ImGui::BeginTabItem("Video")) { if (ImGui::BeginTabItem("Video")) {
ImGui::Spacing(); ImGui::Spacing();
ImGui::Checkbox("Fullscreen", &pendingFullscreen);
ImGui::Checkbox("VSync", &pendingVsync); if (ImGui::Checkbox("Fullscreen", &pendingFullscreen)) {
ImGui::Checkbox("Shadows", &pendingShadows); window->setFullscreen(pendingFullscreen);
saveSettings();
}
if (ImGui::Checkbox("VSync", &pendingVsync)) {
window->setVsync(pendingVsync);
saveSettings();
}
if (ImGui::Checkbox("Shadows", &pendingShadows)) {
if (renderer) renderer->setShadowsEnabled(pendingShadows);
saveSettings();
}
const char* resLabel = "Resolution"; const char* resLabel = "Resolution";
const char* resItems[kResCount]; const char* resItems[kResCount];
@ -4133,7 +4145,10 @@ void GameScreen::renderSettingsWindow() {
snprintf(resBuf[i], sizeof(resBuf[i]), "%dx%d", kResolutions[i][0], kResolutions[i][1]); snprintf(resBuf[i], sizeof(resBuf[i]), "%dx%d", kResolutions[i][0], kResolutions[i][1]);
resItems[i] = resBuf[i]; resItems[i] = resBuf[i];
} }
ImGui::Combo(resLabel, &pendingResIndex, resItems, kResCount); if (ImGui::Combo(resLabel, &pendingResIndex, resItems, kResCount)) {
window->applyResolution(kResolutions[pendingResIndex][0], kResolutions[pendingResIndex][1]);
saveSettings();
}
ImGui::Spacing(); ImGui::Spacing();
ImGui::Separator(); ImGui::Separator();
@ -4144,6 +4159,11 @@ void GameScreen::renderSettingsWindow() {
pendingVsync = kDefaultVsync; pendingVsync = kDefaultVsync;
pendingShadows = kDefaultShadows; pendingShadows = kDefaultShadows;
pendingResIndex = defaultResIndex; pendingResIndex = defaultResIndex;
window->setFullscreen(pendingFullscreen);
window->setVsync(pendingVsync);
window->applyResolution(kResolutions[pendingResIndex][0], kResolutions[pendingResIndex][1]);
if (renderer) renderer->setShadowsEnabled(pendingShadows);
saveSettings();
} }
ImGui::EndTabItem(); ImGui::EndTabItem();
@ -4154,72 +4174,132 @@ void GameScreen::renderSettingsWindow() {
// ============================================================ // ============================================================
if (ImGui::BeginTabItem("Audio")) { if (ImGui::BeginTabItem("Audio")) {
ImGui::Spacing(); ImGui::Spacing();
ImGui::BeginChild("AudioSettings", ImVec2(0, 360), true); ImGui::BeginChild("AudioSettings", ImVec2(0, 360), true);
ImGui::Text("Master Volume"); // Helper lambda to apply audio settings
ImGui::SliderInt("##MasterVolume", &pendingMasterVolume, 0, 100, "%d%%"); auto applyAudioSettings = [&]() {
ImGui::Separator(); if (!renderer) return;
float masterScale = static_cast<float>(pendingMasterVolume) / 100.0f;
if (auto* music = renderer->getMusicManager()) {
music->setVolume(static_cast<int>(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);
}
if (auto* footstep = renderer->getFootstepManager()) {
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(pendingActivityVolume / 100.0f * masterScale);
}
saveSettings();
};
ImGui::Text("Music"); ImGui::Text("Master Volume");
ImGui::SliderInt("##MusicVolume", &pendingMusicVolume, 0, 100, "%d%%"); if (ImGui::SliderInt("##MasterVolume", &pendingMasterVolume, 0, 100, "%d%%")) {
applyAudioSettings();
}
ImGui::Separator();
ImGui::Spacing(); ImGui::Text("Music");
ImGui::Text("Ambient Sounds"); if (ImGui::SliderInt("##MusicVolume", &pendingMusicVolume, 0, 100, "%d%%")) {
ImGui::SliderInt("##AmbientVolume", &pendingAmbientVolume, 0, 100, "%d%%"); applyAudioSettings();
ImGui::TextWrapped("Weather, zones, cities, emitters"); }
ImGui::Spacing(); ImGui::Spacing();
ImGui::Text("UI Sounds"); ImGui::Text("Ambient Sounds");
ImGui::SliderInt("##UiVolume", &pendingUiVolume, 0, 100, "%d%%"); if (ImGui::SliderInt("##AmbientVolume", &pendingAmbientVolume, 0, 100, "%d%%")) {
ImGui::TextWrapped("Buttons, loot, quest complete"); applyAudioSettings();
}
ImGui::TextWrapped("Weather, zones, cities, emitters");
ImGui::Spacing(); ImGui::Spacing();
ImGui::Text("Combat Sounds"); ImGui::Text("UI Sounds");
ImGui::SliderInt("##CombatVolume", &pendingCombatVolume, 0, 100, "%d%%"); if (ImGui::SliderInt("##UiVolume", &pendingUiVolume, 0, 100, "%d%%")) {
ImGui::TextWrapped("Weapon swings, impacts, grunts"); applyAudioSettings();
}
ImGui::TextWrapped("Buttons, loot, quest complete");
ImGui::Spacing(); ImGui::Spacing();
ImGui::Text("Spell Sounds"); ImGui::Text("Combat Sounds");
ImGui::SliderInt("##SpellVolume", &pendingSpellVolume, 0, 100, "%d%%"); if (ImGui::SliderInt("##CombatVolume", &pendingCombatVolume, 0, 100, "%d%%")) {
ImGui::TextWrapped("Magic casting and impacts"); applyAudioSettings();
}
ImGui::TextWrapped("Weapon swings, impacts, grunts");
ImGui::Spacing(); ImGui::Spacing();
ImGui::Text("Movement Sounds"); ImGui::Text("Spell Sounds");
ImGui::SliderInt("##MovementVolume", &pendingMovementVolume, 0, 100, "%d%%"); if (ImGui::SliderInt("##SpellVolume", &pendingSpellVolume, 0, 100, "%d%%")) {
ImGui::TextWrapped("Water splashes, jump/land"); applyAudioSettings();
}
ImGui::TextWrapped("Magic casting and impacts");
ImGui::Spacing(); ImGui::Spacing();
ImGui::Text("Footsteps"); ImGui::Text("Movement Sounds");
ImGui::SliderInt("##FootstepVolume", &pendingFootstepVolume, 0, 100, "%d%%"); if (ImGui::SliderInt("##MovementVolume", &pendingMovementVolume, 0, 100, "%d%%")) {
applyAudioSettings();
}
ImGui::TextWrapped("Water splashes, jump/land");
ImGui::Spacing(); ImGui::Spacing();
ImGui::Text("NPC Voices"); ImGui::Text("Footsteps");
ImGui::SliderInt("##NpcVoiceVolume", &pendingNpcVoiceVolume, 0, 100, "%d%%"); if (ImGui::SliderInt("##FootstepVolume", &pendingFootstepVolume, 0, 100, "%d%%")) {
applyAudioSettings();
}
ImGui::Spacing(); ImGui::Spacing();
ImGui::Text("Mount Sounds"); ImGui::Text("NPC Voices");
ImGui::SliderInt("##MountVolume", &pendingMountVolume, 0, 100, "%d%%"); if (ImGui::SliderInt("##NpcVoiceVolume", &pendingNpcVoiceVolume, 0, 100, "%d%%")) {
applyAudioSettings();
}
ImGui::Spacing(); ImGui::Spacing();
ImGui::Text("Activity Sounds"); ImGui::Text("Mount Sounds");
ImGui::SliderInt("##ActivityVolume", &pendingActivityVolume, 0, 100, "%d%%"); if (ImGui::SliderInt("##MountVolume", &pendingMountVolume, 0, 100, "%d%%")) {
ImGui::TextWrapped("Swimming, eating, drinking"); applyAudioSettings();
}
ImGui::EndChild(); ImGui::Spacing();
ImGui::Text("Activity Sounds");
if (ImGui::SliderInt("##ActivityVolume", &pendingActivityVolume, 0, 100, "%d%%")) {
applyAudioSettings();
}
ImGui::TextWrapped("Swimming, eating, drinking");
if (ImGui::Button("Restore Audio Defaults", ImVec2(-1, 0))) { ImGui::EndChild();
pendingMasterVolume = 100;
pendingMusicVolume = kDefaultMusicVolume; if (ImGui::Button("Restore Audio Defaults", ImVec2(-1, 0))) {
pendingAmbientVolume = 100; pendingMasterVolume = 100;
pendingUiVolume = 100; pendingMusicVolume = kDefaultMusicVolume;
pendingCombatVolume = 100; pendingAmbientVolume = 100;
pendingSpellVolume = 100; pendingUiVolume = 100;
pendingMovementVolume = 100; pendingCombatVolume = 100;
pendingFootstepVolume = 100; pendingSpellVolume = 100;
pendingNpcVoiceVolume = 100; pendingMovementVolume = 100;
pendingMountVolume = 100; pendingFootstepVolume = 100;
pendingActivityVolume = 100; pendingNpcVoiceVolume = 100;
} pendingMountVolume = 100;
pendingActivityVolume = 100;
applyAudioSettings();
}
ImGui::EndTabItem(); ImGui::EndTabItem();
} }
@ -4232,16 +4312,41 @@ void GameScreen::renderSettingsWindow() {
ImGui::Text("Controls"); ImGui::Text("Controls");
ImGui::Separator(); ImGui::Separator();
ImGui::SliderFloat("Mouse Sensitivity", &pendingMouseSensitivity, 0.05f, 1.0f, "%.2f"); if (ImGui::SliderFloat("Mouse Sensitivity", &pendingMouseSensitivity, 0.05f, 1.0f, "%.2f")) {
ImGui::Checkbox("Invert Mouse", &pendingInvertMouse); if (renderer) {
if (auto* cameraController = renderer->getCameraController()) {
cameraController->setMouseSensitivity(pendingMouseSensitivity);
}
}
saveSettings();
}
if (ImGui::Checkbox("Invert Mouse", &pendingInvertMouse)) {
if (renderer) {
if (auto* cameraController = renderer->getCameraController()) {
cameraController->setInvertMouse(pendingInvertMouse);
}
}
saveSettings();
}
ImGui::Spacing(); ImGui::Spacing();
ImGui::Spacing(); ImGui::Spacing();
ImGui::Text("Interface"); ImGui::Text("Interface");
ImGui::Separator(); ImGui::Separator();
ImGui::SliderInt("UI Opacity", &pendingUiOpacity, 20, 100, "%d%%"); if (ImGui::SliderInt("UI Opacity", &pendingUiOpacity, 20, 100, "%d%%")) {
ImGui::Checkbox("Rotate Minimap", &pendingMinimapRotate); uiOpacity_ = static_cast<float>(pendingUiOpacity) / 100.0f;
saveSettings();
}
if (ImGui::Checkbox("Rotate Minimap", &pendingMinimapRotate)) {
minimapRotate_ = pendingMinimapRotate;
if (renderer) {
if (auto* minimap = renderer->getMinimap()) {
minimap->setRotateWithCamera(minimapRotate_);
}
}
saveSettings();
}
ImGui::Spacing(); ImGui::Spacing();
ImGui::Separator(); ImGui::Separator();
@ -4252,6 +4357,18 @@ void GameScreen::renderSettingsWindow() {
pendingInvertMouse = kDefaultInvertMouse; pendingInvertMouse = kDefaultInvertMouse;
pendingUiOpacity = 65; pendingUiOpacity = 65;
pendingMinimapRotate = false; pendingMinimapRotate = false;
uiOpacity_ = 0.65f;
minimapRotate_ = false;
if (renderer) {
if (auto* cameraController = renderer->getCameraController()) {
cameraController->setMouseSensitivity(pendingMouseSensitivity);
cameraController->setInvertMouse(pendingInvertMouse);
}
if (auto* minimap = renderer->getMinimap()) {
minimap->setRotateWithCamera(minimapRotate_);
}
}
saveSettings();
} }
ImGui::EndTabItem(); ImGui::EndTabItem();
@ -4260,62 +4377,6 @@ void GameScreen::renderSettingsWindow() {
ImGui::EndTabBar(); ImGui::EndTabBar();
} }
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
if (ImGui::Button("Apply", ImVec2(-1, 0))) {
uiOpacity_ = static_cast<float>(pendingUiOpacity) / 100.0f;
minimapRotate_ = pendingMinimapRotate;
saveSettings();
window->setVsync(pendingVsync);
window->setFullscreen(pendingFullscreen);
window->applyResolution(kResolutions[pendingResIndex][0], kResolutions[pendingResIndex][1]);
if (renderer) {
renderer->setShadowsEnabled(pendingShadows);
if (auto* minimap = renderer->getMinimap()) {
minimap->setRotateWithCamera(minimapRotate_);
}
// Apply all audio volume settings
float masterScale = static_cast<float>(pendingMasterVolume) / 100.0f;
if (auto* music = renderer->getMusicManager()) {
music->setVolume(static_cast<int>(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);
}
if (auto* footstep = renderer->getFootstepManager()) {
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(pendingActivityVolume / 100.0f * masterScale);
}
if (auto* cameraController = renderer->getCameraController()) {
cameraController->setMouseSensitivity(pendingMouseSensitivity);
cameraController->setInvertMouse(pendingInvertMouse);
}
}
}
ImGui::Spacing(); ImGui::Spacing();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 10.0f)); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 10.0f));
if (ImGui::Button("Back to Game", ImVec2(-1, 0))) { if (ImGui::Button("Back to Game", ImVec2(-1, 0))) {