From fd64ca74457fa8bbdc38fcde586ce93f6dc7eb88 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 7 Feb 2026 15:58:18 -0800 Subject: [PATCH] Fix underground spawns in Stormwind, redesign delete character button Spawn fix: floor probe used terrain height (underground) instead of server position when searching for spawn floor. Now probes from max(terrain, serverZ) so WMO city surfaces above terrain are found correctly. Also invalidates floor cache on respawn. Delete button: moved from details panel to bottom row (small, red, far right). Two-stage confirmation with modal popups: first asks "are you sure", second warns "THIS CANNOT BE UNDONE" with red DELETE PERMANENTLY button. --- include/ui/character_screen.hpp | 2 +- src/rendering/camera_controller.cpp | 12 +++- src/ui/character_screen.cpp | 95 ++++++++++++++++++++++------- 3 files changed, 82 insertions(+), 27 deletions(-) diff --git a/include/ui/character_screen.hpp b/include/ui/character_screen.hpp index f9f3b12e..b872b7bf 100644 --- a/include/ui/character_screen.hpp +++ b/include/ui/character_screen.hpp @@ -64,7 +64,7 @@ private: std::function onCreateCharacter; std::function onBack; std::function onDeleteCharacter; - bool confirmDelete = false; + int deleteConfirmStage = 0; // 0=none, 1=first warning, 2=final warning /** * Get faction color based on race diff --git a/src/rendering/camera_controller.cpp b/src/rendering/camera_controller.cpp index a7153444..e407d78d 100644 --- a/src/rendering/camera_controller.cpp +++ b/src/rendering/camera_controller.cpp @@ -1051,12 +1051,14 @@ void CameraController::reset() { if (terrainManager) { terrainH = terrainManager->getHeightAt(x, y); } - float floorProbeZ = terrainH.value_or(refZ); + // Probe from the highest of terrain, refZ (server position), and defaultPosition.z + // so we don't miss WMO floors above terrain (e.g. Stormwind city surface). + float floorProbeZ = std::max(terrainH.value_or(refZ), refZ); if (wmoRenderer) { - wmoH = wmoRenderer->getFloorHeight(x, y, floorProbeZ + 2.0f); + wmoH = wmoRenderer->getFloorHeight(x, y, floorProbeZ + 4.0f); } if (m2Renderer) { - m2H = m2Renderer->getFloorHeight(x, y, floorProbeZ + 2.0f); + m2H = m2Renderer->getFloorHeight(x, y, floorProbeZ + 4.0f); } auto h = selectReachableFloor(terrainH, wmoH, refZ, 16.0f); if (!h) { @@ -1177,6 +1179,10 @@ void CameraController::reset() { lastGroundZ = spawnPos.z - 0.05f; } + // Invalidate inter-frame floor cache so the first frame probes fresh. + cachedFloorHeight.reset(); + cachedFloorPos = glm::vec2(0.0f); + camera->setRotation(yaw, pitch); glm::vec3 forward3D = camera->getForward(); diff --git a/src/ui/character_screen.cpp b/src/ui/character_screen.cpp index 1f563c45..9e8bd67e 100644 --- a/src/ui/character_screen.cpp +++ b/src/ui/character_screen.cpp @@ -200,29 +200,6 @@ void CharacterScreen::render(game::GameHandler& gameHandler) { if (onCharacterSelected) onCharacterSelected(character.guid); } - ImGui::Spacing(); - - // Delete - if (!confirmDelete) { - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.6f, 0.1f, 0.1f, 1.0f)); - if (ImGui::Button("Delete Character", ImVec2(btnW, 36))) { - confirmDelete = true; - } - ImGui::PopStyleColor(); - } else { - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.0f, 0.0f, 1.0f)); - if (ImGui::Button("Confirm Delete?", ImVec2(btnW, 36))) { - if (onDeleteCharacter) onDeleteCharacter(character.guid); - confirmDelete = false; - selectedCharacterIndex = -1; - selectedCharacterGuid = 0; - } - ImGui::PopStyleColor(); - if (ImGui::Button("Cancel", ImVec2(btnW, 30))) { - confirmDelete = false; - } - } - ImGui::EndChild(); } @@ -242,6 +219,78 @@ void CharacterScreen::render(game::GameHandler& gameHandler) { if (onCreateCharacter) onCreateCharacter(); } + // Delete button — small, red, far right, only when a character is selected + if (selectedCharacterIndex >= 0 && + selectedCharacterIndex < static_cast(characters.size())) { + float deleteW = 80.0f; + ImGui::SameLine(ImGui::GetContentRegionMax().x - deleteW); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.5f, 0.08f, 0.08f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.7f, 0.1f, 0.1f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.9f, 0.5f, 0.5f, 1.0f)); + if (ImGui::Button("Delete", ImVec2(deleteW, 28))) { + deleteConfirmStage = 1; + ImGui::OpenPopup("DeleteConfirm1"); + } + ImGui::PopStyleColor(3); + } + + // First confirmation popup + if (ImGui::BeginPopupModal("DeleteConfirm1", nullptr, + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) { + const auto& ch = characters[selectedCharacterIndex]; + ImGui::Text("Are you sure you want to delete"); + ImGui::TextColored(getFactionColor(ch.race), "%s", ch.name.c_str()); + ImGui::Text("Level %d %s %s?", + ch.level, game::getRaceName(ch.race), game::getClassName(ch.characterClass)); + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + if (ImGui::Button("Yes, delete this character", ImVec2(240, 32))) { + ImGui::CloseCurrentPopup(); + deleteConfirmStage = 2; + ImGui::OpenPopup("DeleteConfirm2"); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(100, 32))) { + deleteConfirmStage = 0; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + + // Second (final) confirmation popup + if (deleteConfirmStage == 2) { + ImGui::OpenPopup("DeleteConfirm2"); + } + if (ImGui::BeginPopupModal("DeleteConfirm2", nullptr, + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) { + const auto& ch = characters[selectedCharacterIndex]; + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.3f, 0.3f, 1.0f)); + ImGui::Text("THIS CANNOT BE UNDONE!"); + ImGui::PopStyleColor(); + ImGui::Spacing(); + ImGui::Text("Are you REALLY sure you want to permanently"); + ImGui::Text("delete %s? This character will be gone forever.", ch.name.c_str()); + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.7f, 0.0f, 0.0f, 1.0f)); + if (ImGui::Button("DELETE PERMANENTLY", ImVec2(240, 32))) { + if (onDeleteCharacter) onDeleteCharacter(ch.guid); + deleteConfirmStage = 0; + selectedCharacterIndex = -1; + selectedCharacterGuid = 0; + ImGui::CloseCurrentPopup(); + } + ImGui::PopStyleColor(); + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(100, 32))) { + deleteConfirmStage = 0; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + ImGui::End(); }