make a user friendly delete message

Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
This commit is contained in:
Pavel Okhlopkov 2026-04-10 22:22:14 +03:00
parent 5b47d034c5
commit fe1dc5e02b
5 changed files with 62 additions and 10 deletions

View file

@ -199,7 +199,7 @@ public:
using CharCreateCallback = std::function<void(bool success, const std::string& message)>;
void setCharCreateCallback(CharCreateCallback cb) { charCreateCallback_ = std::move(cb); }
using CharDeleteCallback = std::function<void(bool success)>;
using CharDeleteCallback = std::function<void(bool success, const std::string& message)>;
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_;

View file

@ -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<int>(code)) + ").", true);
}
});
}

View file

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

View file

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

View file

@ -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<int>(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<int>(result)) + ")."; break;
}
}
if (charDeleteCallback_) charDeleteCallback_(success, msg);
};
dispatchTable_[Opcode::SMSG_CHAR_ENUM] = [this](network::Packet& packet) {
if (state == WorldState::CHAR_LIST_REQUESTED)