mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-03 08:03:50 +00:00
Handle SMSG_CHARACTER_LOGIN_FAILED (0x041) for AzerothCore/Playerbot compatibility
Previously this opcode was unrecognised and silently dropped, leaving the client stuck in ENTERING_WORLD with no feedback when the server rejected a login (duplicate session, world down, disabled race/class, etc.). Now we decode the LoginFailureReason byte, reset state to CHAR_LIST_RECEIVED so the player can retry, and surface a red error message on the character screen via the new CharLoginFailCallback. Also adds isError colour support to CharacterScreen::setStatus so failures show in red and successes in green.
This commit is contained in:
parent
36fc1df706
commit
7cf060a9f6
11 changed files with 61 additions and 4 deletions
|
|
@ -25,6 +25,7 @@
|
||||||
"SMSG_CHAR_CREATE": "0x03A",
|
"SMSG_CHAR_CREATE": "0x03A",
|
||||||
"SMSG_CHAR_ENUM": "0x03B",
|
"SMSG_CHAR_ENUM": "0x03B",
|
||||||
"SMSG_CHAR_DELETE": "0x03C",
|
"SMSG_CHAR_DELETE": "0x03C",
|
||||||
|
"SMSG_CHARACTER_LOGIN_FAILED": "0x041",
|
||||||
"SMSG_PONG": "0x1DD",
|
"SMSG_PONG": "0x1DD",
|
||||||
"SMSG_LOGIN_VERIFY_WORLD": "0x236",
|
"SMSG_LOGIN_VERIFY_WORLD": "0x236",
|
||||||
"SMSG_LOGIN_SETTIMESPEED": "0x042",
|
"SMSG_LOGIN_SETTIMESPEED": "0x042",
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@
|
||||||
"SMSG_CHAR_CREATE": "0x03A",
|
"SMSG_CHAR_CREATE": "0x03A",
|
||||||
"SMSG_CHAR_ENUM": "0x03B",
|
"SMSG_CHAR_ENUM": "0x03B",
|
||||||
"SMSG_CHAR_DELETE": "0x03C",
|
"SMSG_CHAR_DELETE": "0x03C",
|
||||||
|
"SMSG_CHARACTER_LOGIN_FAILED": "0x041",
|
||||||
"SMSG_PONG": "0x1DD",
|
"SMSG_PONG": "0x1DD",
|
||||||
"SMSG_LOGIN_VERIFY_WORLD": "0x236",
|
"SMSG_LOGIN_VERIFY_WORLD": "0x236",
|
||||||
"SMSG_LOGIN_SETTIMESPEED": "0x042",
|
"SMSG_LOGIN_SETTIMESPEED": "0x042",
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@
|
||||||
"SMSG_CHAR_CREATE": "0x03A",
|
"SMSG_CHAR_CREATE": "0x03A",
|
||||||
"SMSG_CHAR_ENUM": "0x03B",
|
"SMSG_CHAR_ENUM": "0x03B",
|
||||||
"SMSG_CHAR_DELETE": "0x03C",
|
"SMSG_CHAR_DELETE": "0x03C",
|
||||||
|
"SMSG_CHARACTER_LOGIN_FAILED": "0x041",
|
||||||
"SMSG_PONG": "0x1DD",
|
"SMSG_PONG": "0x1DD",
|
||||||
"SMSG_LOGIN_VERIFY_WORLD": "0x236",
|
"SMSG_LOGIN_VERIFY_WORLD": "0x236",
|
||||||
"SMSG_LOGIN_SETTIMESPEED": "0x042",
|
"SMSG_LOGIN_SETTIMESPEED": "0x042",
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@
|
||||||
"SMSG_CHAR_CREATE": "0x03A",
|
"SMSG_CHAR_CREATE": "0x03A",
|
||||||
"SMSG_CHAR_ENUM": "0x03B",
|
"SMSG_CHAR_ENUM": "0x03B",
|
||||||
"SMSG_CHAR_DELETE": "0x03C",
|
"SMSG_CHAR_DELETE": "0x03C",
|
||||||
|
"SMSG_CHARACTER_LOGIN_FAILED": "0x041",
|
||||||
"SMSG_PONG": "0x1DD",
|
"SMSG_PONG": "0x1DD",
|
||||||
"SMSG_LOGIN_VERIFY_WORLD": "0x236",
|
"SMSG_LOGIN_VERIFY_WORLD": "0x236",
|
||||||
"SMSG_LOGIN_SETTIMESPEED": "0x042",
|
"SMSG_LOGIN_SETTIMESPEED": "0x042",
|
||||||
|
|
|
||||||
|
|
@ -175,6 +175,9 @@ public:
|
||||||
void setCharDeleteCallback(CharDeleteCallback cb) { charDeleteCallback_ = std::move(cb); }
|
void setCharDeleteCallback(CharDeleteCallback cb) { charDeleteCallback_ = std::move(cb); }
|
||||||
uint8_t getLastCharDeleteResult() const { return lastCharDeleteResult_; }
|
uint8_t getLastCharDeleteResult() const { return lastCharDeleteResult_; }
|
||||||
|
|
||||||
|
using CharLoginFailCallback = std::function<void(const std::string& reason)>;
|
||||||
|
void setCharLoginFailCallback(CharLoginFailCallback cb) { charLoginFailCallback_ = std::move(cb); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select and log in with a character
|
* Select and log in with a character
|
||||||
* @param characterGuid GUID of character to log in with
|
* @param characterGuid GUID of character to log in with
|
||||||
|
|
@ -906,6 +909,11 @@ private:
|
||||||
*/
|
*/
|
||||||
void handleCharEnum(network::Packet& packet);
|
void handleCharEnum(network::Packet& packet);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle SMSG_CHARACTER_LOGIN_FAILED from server
|
||||||
|
*/
|
||||||
|
void handleCharLoginFailed(network::Packet& packet);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle SMSG_LOGIN_VERIFY_WORLD from server
|
* Handle SMSG_LOGIN_VERIFY_WORLD from server
|
||||||
*/
|
*/
|
||||||
|
|
@ -1469,6 +1477,7 @@ private:
|
||||||
WorldConnectFailureCallback onFailure;
|
WorldConnectFailureCallback onFailure;
|
||||||
CharCreateCallback charCreateCallback_;
|
CharCreateCallback charCreateCallback_;
|
||||||
CharDeleteCallback charDeleteCallback_;
|
CharDeleteCallback charDeleteCallback_;
|
||||||
|
CharLoginFailCallback charLoginFailCallback_;
|
||||||
uint8_t lastCharDeleteResult_ = 0xFF;
|
uint8_t lastCharDeleteResult_ = 0xFF;
|
||||||
bool pendingCharCreateResult_ = false;
|
bool pendingCharCreateResult_ = false;
|
||||||
bool pendingCharCreateSuccess_ = false;
|
bool pendingCharCreateSuccess_ = false;
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ enum class LogicalOpcode : uint16_t {
|
||||||
SMSG_CHAR_CREATE,
|
SMSG_CHAR_CREATE,
|
||||||
SMSG_CHAR_ENUM,
|
SMSG_CHAR_ENUM,
|
||||||
SMSG_CHAR_DELETE,
|
SMSG_CHAR_DELETE,
|
||||||
|
SMSG_CHARACTER_LOGIN_FAILED,
|
||||||
SMSG_PONG,
|
SMSG_PONG,
|
||||||
SMSG_LOGIN_VERIFY_WORLD,
|
SMSG_LOGIN_VERIFY_WORLD,
|
||||||
SMSG_LOGIN_SETTIMESPEED,
|
SMSG_LOGIN_SETTIMESPEED,
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ public:
|
||||||
restoredLastCharacter = false;
|
restoredLastCharacter = false;
|
||||||
newlyCreatedCharacterName.clear();
|
newlyCreatedCharacterName.clear();
|
||||||
statusMessage.clear();
|
statusMessage.clear();
|
||||||
|
statusIsError = false;
|
||||||
deleteConfirmStage = 0;
|
deleteConfirmStage = 0;
|
||||||
previewInitialized_ = false;
|
previewInitialized_ = false;
|
||||||
previewGuid_ = 0;
|
previewGuid_ = 0;
|
||||||
|
|
@ -80,7 +81,7 @@ public:
|
||||||
/**
|
/**
|
||||||
* Update status message
|
* Update status message
|
||||||
*/
|
*/
|
||||||
void setStatus(const std::string& message);
|
void setStatus(const std::string& message, bool isError = false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select character by name (used after character creation)
|
* Select character by name (used after character creation)
|
||||||
|
|
@ -97,6 +98,7 @@ private:
|
||||||
|
|
||||||
// Status
|
// Status
|
||||||
std::string statusMessage;
|
std::string statusMessage;
|
||||||
|
bool statusIsError = false;
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
std::function<void(uint64_t)> onCharacterSelected;
|
std::function<void(uint64_t)> onCharacterSelected;
|
||||||
|
|
|
||||||
|
|
@ -1006,6 +1006,13 @@ void Application::setupUICallbacks() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Character login failure callback
|
||||||
|
gameHandler->setCharLoginFailCallback([this](const std::string& reason) {
|
||||||
|
LOG_WARNING("Character login failed: ", reason);
|
||||||
|
setState(AppState::CHARACTER_SELECTION);
|
||||||
|
uiManager->getCharacterScreen().setStatus("Login failed: " + reason, true);
|
||||||
|
});
|
||||||
|
|
||||||
// World entry callback (online mode) - load terrain when entering world
|
// World entry callback (online mode) - load terrain when entering world
|
||||||
gameHandler->setWorldEntryCallback([this](uint32_t mapId, float x, float y, float z) {
|
gameHandler->setWorldEntryCallback([this](uint32_t mapId, float x, float y, float z) {
|
||||||
LOG_INFO("Online world entry: mapId=", mapId, " pos=(", x, ", ", y, ", ", z, ")");
|
LOG_INFO("Online world entry: mapId=", mapId, " pos=(", x, ", ", y, ", ", z, ")");
|
||||||
|
|
@ -1784,7 +1791,7 @@ void Application::setupUICallbacks() {
|
||||||
} else {
|
} else {
|
||||||
uint8_t code = gameHandler ? gameHandler->getLastCharDeleteResult() : 0xFF;
|
uint8_t code = gameHandler ? gameHandler->getLastCharDeleteResult() : 0xFF;
|
||||||
uiManager->getCharacterScreen().setStatus(
|
uiManager->getCharacterScreen().setStatus(
|
||||||
"Delete failed (code " + std::to_string(static_cast<int>(code)) + ").");
|
"Delete failed (code " + std::to_string(static_cast<int>(code)) + ").", true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -699,6 +699,10 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Opcode::SMSG_CHARACTER_LOGIN_FAILED:
|
||||||
|
handleCharLoginFailed(packet);
|
||||||
|
break;
|
||||||
|
|
||||||
case Opcode::SMSG_LOGIN_VERIFY_WORLD:
|
case Opcode::SMSG_LOGIN_VERIFY_WORLD:
|
||||||
if (state == WorldState::ENTERING_WORLD || state == WorldState::IN_WORLD) {
|
if (state == WorldState::ENTERING_WORLD || state == WorldState::IN_WORLD) {
|
||||||
handleLoginVerifyWorld(packet);
|
handleLoginVerifyWorld(packet);
|
||||||
|
|
@ -1889,6 +1893,32 @@ const Character* GameHandler::getFirstCharacter() const {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void GameHandler::handleCharLoginFailed(network::Packet& packet) {
|
||||||
|
uint8_t reason = packet.readUInt8();
|
||||||
|
|
||||||
|
static const char* reasonNames[] = {
|
||||||
|
"Login failed", // 0
|
||||||
|
"World server is down", // 1
|
||||||
|
"Duplicate character", // 2 (session still active)
|
||||||
|
"No instance servers", // 3
|
||||||
|
"Login disabled", // 4
|
||||||
|
"Character not found", // 5
|
||||||
|
"Locked for transfer", // 6
|
||||||
|
"Locked by billing", // 7
|
||||||
|
"Using remote", // 8
|
||||||
|
};
|
||||||
|
const char* msg = (reason < 9) ? reasonNames[reason] : "Unknown reason";
|
||||||
|
|
||||||
|
LOG_ERROR("SMSG_CHARACTER_LOGIN_FAILED: reason=", (int)reason, " (", msg, ")");
|
||||||
|
|
||||||
|
// Allow the player to re-select a character
|
||||||
|
setState(WorldState::CHAR_LIST_RECEIVED);
|
||||||
|
|
||||||
|
if (charLoginFailCallback_) {
|
||||||
|
charLoginFailCallback_(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GameHandler::selectCharacter(uint64_t characterGuid) {
|
void GameHandler::selectCharacter(uint64_t characterGuid) {
|
||||||
if (state != WorldState::CHAR_LIST_RECEIVED) {
|
if (state != WorldState::CHAR_LIST_RECEIVED) {
|
||||||
LOG_WARNING("Cannot select character in state: ", (int)state);
|
LOG_WARNING("Cannot select character in state: ", (int)state);
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ static const OpcodeNameEntry kOpcodeNames[] = {
|
||||||
{"SMSG_CHAR_CREATE", LogicalOpcode::SMSG_CHAR_CREATE},
|
{"SMSG_CHAR_CREATE", LogicalOpcode::SMSG_CHAR_CREATE},
|
||||||
{"SMSG_CHAR_ENUM", LogicalOpcode::SMSG_CHAR_ENUM},
|
{"SMSG_CHAR_ENUM", LogicalOpcode::SMSG_CHAR_ENUM},
|
||||||
{"SMSG_CHAR_DELETE", LogicalOpcode::SMSG_CHAR_DELETE},
|
{"SMSG_CHAR_DELETE", LogicalOpcode::SMSG_CHAR_DELETE},
|
||||||
|
{"SMSG_CHARACTER_LOGIN_FAILED", LogicalOpcode::SMSG_CHARACTER_LOGIN_FAILED},
|
||||||
{"SMSG_PONG", LogicalOpcode::SMSG_PONG},
|
{"SMSG_PONG", LogicalOpcode::SMSG_PONG},
|
||||||
{"SMSG_LOGIN_VERIFY_WORLD", LogicalOpcode::SMSG_LOGIN_VERIFY_WORLD},
|
{"SMSG_LOGIN_VERIFY_WORLD", LogicalOpcode::SMSG_LOGIN_VERIFY_WORLD},
|
||||||
{"SMSG_LOGIN_SETTIMESPEED", LogicalOpcode::SMSG_LOGIN_SETTIMESPEED},
|
{"SMSG_LOGIN_SETTIMESPEED", LogicalOpcode::SMSG_LOGIN_SETTIMESPEED},
|
||||||
|
|
@ -386,6 +387,7 @@ void OpcodeTable::loadWotlkDefaults() {
|
||||||
{LogicalOpcode::SMSG_CHAR_CREATE, 0x03A},
|
{LogicalOpcode::SMSG_CHAR_CREATE, 0x03A},
|
||||||
{LogicalOpcode::SMSG_CHAR_ENUM, 0x03B},
|
{LogicalOpcode::SMSG_CHAR_ENUM, 0x03B},
|
||||||
{LogicalOpcode::SMSG_CHAR_DELETE, 0x03C},
|
{LogicalOpcode::SMSG_CHAR_DELETE, 0x03C},
|
||||||
|
{LogicalOpcode::SMSG_CHARACTER_LOGIN_FAILED, 0x041},
|
||||||
{LogicalOpcode::SMSG_PONG, 0x1DD},
|
{LogicalOpcode::SMSG_PONG, 0x1DD},
|
||||||
{LogicalOpcode::SMSG_LOGIN_VERIFY_WORLD, 0x236},
|
{LogicalOpcode::SMSG_LOGIN_VERIFY_WORLD, 0x236},
|
||||||
{LogicalOpcode::SMSG_LOGIN_SETTIMESPEED, 0x042},
|
{LogicalOpcode::SMSG_LOGIN_SETTIMESPEED, 0x042},
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,8 @@ void CharacterScreen::render(game::GameHandler& gameHandler) {
|
||||||
|
|
||||||
// Status message
|
// Status message
|
||||||
if (!statusMessage.empty()) {
|
if (!statusMessage.empty()) {
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.3f, 1.0f, 0.3f, 1.0f));
|
ImVec4 color = statusIsError ? ImVec4(1.0f, 0.3f, 0.3f, 1.0f) : ImVec4(0.3f, 1.0f, 0.3f, 1.0f);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||||
ImGui::TextWrapped("%s", statusMessage.c_str());
|
ImGui::TextWrapped("%s", statusMessage.c_str());
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
|
|
@ -454,8 +455,9 @@ void CharacterScreen::render(game::GameHandler& gameHandler) {
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterScreen::setStatus(const std::string& message) {
|
void CharacterScreen::setStatus(const std::string& message, bool isError) {
|
||||||
statusMessage = message;
|
statusMessage = message;
|
||||||
|
statusIsError = isError;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterScreen::selectCharacterByName(const std::string& name) {
|
void CharacterScreen::selectCharacterByName(const std::string& name) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue