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.
This commit is contained in:
Kelsi 2026-02-07 15:58:18 -08:00
parent 6516fd777d
commit fd64ca7445
3 changed files with 82 additions and 27 deletions

View file

@ -64,7 +64,7 @@ private:
std::function<void()> onCreateCharacter;
std::function<void()> onBack;
std::function<void(uint64_t)> onDeleteCharacter;
bool confirmDelete = false;
int deleteConfirmStage = 0; // 0=none, 1=first warning, 2=final warning
/**
* Get faction color based on race

View file

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

View file

@ -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<int>(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();
}