From fe1dc5e02ba4c8e5c69eb723d441b968cc557fbf Mon Sep 17 00:00:00 2001 From: Pavel Okhlopkov Date: Fri, 10 Apr 2026 22:22:14 +0300 Subject: [PATCH] make a user friendly delete message Signed-off-by: Pavel Okhlopkov --- include/game/game_handler.hpp | 6 +++- src/core/ui_screen_callback_handler.cpp | 9 ++---- src/game/game_handler.cpp | 37 +++++++++++++++++++++++++ src/game/game_handler_callbacks.cpp | 5 +++- src/game/game_handler_packets.cpp | 15 +++++++++- 5 files changed, 62 insertions(+), 10 deletions(-) diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 7cff33f0..f93b8a62 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -199,7 +199,7 @@ public: using CharCreateCallback = std::function; void setCharCreateCallback(CharCreateCallback cb) { charCreateCallback_ = std::move(cb); } - using CharDeleteCallback = std::function; + using CharDeleteCallback = std::function; void setCharDeleteCallback(CharDeleteCallback cb) { charDeleteCallback_ = std::move(cb); } uint8_t getLastCharDeleteResult() const { return lastCharDeleteResult_; } @@ -3336,6 +3336,10 @@ private: CharDeleteCallback charDeleteCallback_; CharLoginFailCallback charLoginFailCallback_; uint8_t lastCharDeleteResult_ = 0xFF; + bool pendingCharDeleteResponse_ = false; + uint64_t pendingDeleteGuid_ = 0; + float pendingDeleteTimer_ = 0.0f; + bool pendingDeleteFallbackEnum_ = false; bool pendingCharCreateResult_ = false; bool pendingCharCreateSuccess_ = false; std::string pendingCharCreateMsg_; diff --git a/src/core/ui_screen_callback_handler.cpp b/src/core/ui_screen_callback_handler.cpp index f2016991..16de9e57 100644 --- a/src/core/ui_screen_callback_handler.cpp +++ b/src/core/ui_screen_callback_handler.cpp @@ -166,15 +166,10 @@ void UIScreenCallbackHandler::setupCallbacks() { }); // Character delete result callback - gameHandler_.setCharDeleteCallback([this](bool success) { + gameHandler_.setCharDeleteCallback([this](bool success, const std::string& message) { + uiManager_.getCharacterScreen().setStatus(message, !success); if (success) { - uiManager_.getCharacterScreen().setStatus("Character deleted."); - // Refresh character list gameHandler_.requestCharacterList(); - } else { - uint8_t code = gameHandler_.getLastCharDeleteResult(); - uiManager_.getCharacterScreen().setStatus( - "Delete failed (code " + std::to_string(static_cast(code)) + ").", true); } }); } diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 9eeb836b..1e6bd3b6 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -756,6 +756,43 @@ void GameHandler::update(float deltaTime) { updateNetworking(deltaTime); if (!socket) return; // disconnect() may have been called + // Fallback for CMSG_CHAR_DELETE with no server response: if the server + // doesn't send SMSG_CHAR_DELETE within 3 seconds, re-request the character + // list. Some server cores silently process the delete without responding. + if (pendingCharDeleteResponse_) { + pendingDeleteTimer_ += deltaTime; + if (pendingDeleteTimer_ >= 3.0f) { + LOG_WARNING("No SMSG_CHAR_DELETE response after 3s — requesting character list to verify"); + pendingCharDeleteResponse_ = false; + pendingDeleteFallbackEnum_ = true; + requestCharacterList(); + } + } + + // After the fallback SMSG_CHAR_ENUM has been processed, check if the + // character was actually removed and fire the delete callback. + if (pendingDeleteFallbackEnum_ && state == WorldState::CHAR_LIST_RECEIVED) { + pendingDeleteFallbackEnum_ = false; + uint64_t deletedGuid = pendingDeleteGuid_; + pendingDeleteGuid_ = 0; + bool found = false; + for (const auto& ch : characters) { + if (ch.guid == deletedGuid) { found = true; break; } + } + bool deleted = !found; + LOG_INFO("Char delete fallback: GUID 0x", std::hex, deletedGuid, std::dec, + deleted ? " was deleted" : " still exists"); + std::string msg; + if (deleted) { + msg = "Character deleted."; + } else { + msg = "Delete failed: the server did not respond. " + "This usually happens if you recently logged out — " + "wait 20-30 seconds and try again."; + } + if (charDeleteCallback_) charDeleteCallback_(deleted, msg); + } + // Validate target still exists if (targetGuid != 0 && !entityController_->getEntityManager().hasEntity(targetGuid)) { clearTarget(); diff --git a/src/game/game_handler_callbacks.cpp b/src/game/game_handler_callbacks.cpp index 8eb1c43b..53236e9b 100644 --- a/src/game/game_handler_callbacks.cpp +++ b/src/game/game_handler_callbacks.cpp @@ -342,13 +342,16 @@ void GameHandler::handleCharCreateResponse(network::Packet& packet) { void GameHandler::deleteCharacter(uint64_t characterGuid) { if (!socket) { - if (charDeleteCallback_) charDeleteCallback_(false); + if (charDeleteCallback_) charDeleteCallback_(false, "Delete failed: not connected to server."); return; } network::Packet packet(wireOpcode(Opcode::CMSG_CHAR_DELETE)); packet.writeUInt64(characterGuid); socket->send(packet); + pendingCharDeleteResponse_ = true; + pendingDeleteGuid_ = characterGuid; + pendingDeleteTimer_ = 0.0f; LOG_INFO("CMSG_CHAR_DELETE sent for GUID: 0x", std::hex, characterGuid, std::dec); } diff --git a/src/game/game_handler_packets.cpp b/src/game/game_handler_packets.cpp index 469aeb8f..84b65727 100644 --- a/src/game/game_handler_packets.cpp +++ b/src/game/game_handler_packets.cpp @@ -151,10 +151,23 @@ void GameHandler::registerOpcodeHandlers() { dispatchTable_[Opcode::SMSG_CHAR_DELETE] = [this](network::Packet& packet) { uint8_t result = packet.readUInt8(); lastCharDeleteResult_ = result; + pendingCharDeleteResponse_ = false; bool success = (result == 0x00 || result == 0x47); LOG_INFO("SMSG_CHAR_DELETE result: ", static_cast(result), success ? " (success)" : " (failed)"); requestCharacterList(); - if (charDeleteCallback_) charDeleteCallback_(success); + std::string msg; + if (success) { + msg = "Character deleted."; + } else { + // Map known CHAR_DELETE_* result codes to user-friendly messages + switch (result) { + case 0x31: msg = "Delete failed: character is a guild leader. Transfer leadership first."; break; + case 0x32: msg = "Delete failed: character is in an arena team."; break; + case 0x3A: msg = "Delete failed: character has mail. Check mailbox first."; break; + default: msg = "Delete failed (server error code " + std::to_string(static_cast(result)) + ")."; break; + } + } + if (charDeleteCallback_) charDeleteCallback_(success, msg); }; dispatchTable_[Opcode::SMSG_CHAR_ENUM] = [this](network::Packet& packet) { if (state == WorldState::CHAR_LIST_REQUESTED)