mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Add Tier 1 utility commands: ignore, sit/stand, and logout
Ignore Commands: - Add /ignore <name> to block messages from players - Add /unignore <name> to unblock players - Maintain ignoreCache for name-to-GUID lookups - Show confirmation and error messages for ignore actions - Use CMSG_ADD_IGNORE (0x6C) and CMSG_DEL_IGNORE (0x6D) Sit/Stand/Kneel Commands: - Add /sit to sit down (stand state 1) - Add /stand to stand up (stand state 0) - Add /kneel to kneel (stand state 8) - Instant visual feedback with CMSG_STAND_STATE_CHANGE (0x101) - Support for additional stand states (chair, sleep, etc.) Logout Commands: - Add /logout and /camp to initiate logout with countdown - Add /cancellogout to cancel pending logout - Show "Logging out in 20 seconds..." or "Logout complete" messages - Track logout state with loggingOut_ flag to prevent duplicate requests - Handle instant logout (in inn/city) vs countdown logout - Use opcodes: - CMSG_LOGOUT_REQUEST (0x4B) - CMSG_LOGOUT_CANCEL (0x4E) - SMSG_LOGOUT_RESPONSE (0x4C) - SMSG_LOGOUT_COMPLETE (0x4D) Implementation: - Add LogoutRequestPacket, LogoutCancelPacket builders - Add LogoutResponseParser to parse server logout responses - Add StandStateChangePacket builder for stance changes - Add AddIgnorePacket and DelIgnorePacket for ignore list management - Add handleLogoutResponse() and handleLogoutComplete() handlers - Add ignoreCache map and loggingOut_ state tracking - All commands display feedback in chat window
This commit is contained in:
parent
6f45c6ab69
commit
ec32286b0d
6 changed files with 325 additions and 0 deletions
|
|
@ -214,10 +214,19 @@ public:
|
|||
void addFriend(const std::string& playerName, const std::string& note = "");
|
||||
void removeFriend(const std::string& playerName);
|
||||
void setFriendNote(const std::string& playerName, const std::string& note);
|
||||
void addIgnore(const std::string& playerName);
|
||||
void removeIgnore(const std::string& playerName);
|
||||
|
||||
// Random roll
|
||||
void randomRoll(uint32_t minRoll = 1, uint32_t maxRoll = 100);
|
||||
|
||||
// Logout commands
|
||||
void requestLogout();
|
||||
void cancelLogout();
|
||||
|
||||
// Stand state
|
||||
void setStandState(uint8_t state); // 0=stand, 1=sit, 2=sit_chair, 3=sleep, 4=sit_low_chair, 5=sit_medium_chair, 6=sit_high_chair, 7=dead, 8=kneel, 9=submerged
|
||||
|
||||
// ---- Phase 1: Name queries ----
|
||||
void queryPlayerName(uint64_t guid);
|
||||
void queryCreatureInfo(uint32_t entry, uint64_t guid);
|
||||
|
|
@ -525,6 +534,10 @@ private:
|
|||
void handleFriendStatus(network::Packet& packet);
|
||||
void handleRandomRoll(network::Packet& packet);
|
||||
|
||||
// ---- Logout handlers ----
|
||||
void handleLogoutResponse(network::Packet& packet);
|
||||
void handleLogoutComplete(network::Packet& packet);
|
||||
|
||||
void addCombatText(CombatTextEntry::Type type, int32_t amount, uint32_t spellId, bool isPlayerSource);
|
||||
void addSystemChatMessage(const std::string& message);
|
||||
|
||||
|
|
@ -607,6 +620,12 @@ private:
|
|||
// ---- Friend list cache ----
|
||||
std::unordered_map<std::string, uint64_t> friendsCache; // name -> guid
|
||||
|
||||
// ---- Ignore list cache ----
|
||||
std::unordered_map<std::string, uint64_t> ignoreCache; // name -> guid
|
||||
|
||||
// ---- Logout state ----
|
||||
bool loggingOut_ = false;
|
||||
|
||||
// ---- Online item tracking ----
|
||||
struct OnlineItemInfo {
|
||||
uint32_t entry = 0;
|
||||
|
|
|
|||
|
|
@ -71,6 +71,16 @@ enum class Opcode : uint16_t {
|
|||
CMSG_ADD_IGNORE = 0x06C,
|
||||
CMSG_DEL_IGNORE = 0x06D,
|
||||
|
||||
// ---- Logout Commands ----
|
||||
CMSG_PLAYER_LOGOUT = 0x04A,
|
||||
CMSG_LOGOUT_REQUEST = 0x04B,
|
||||
CMSG_LOGOUT_CANCEL = 0x04E,
|
||||
SMSG_LOGOUT_RESPONSE = 0x04C,
|
||||
SMSG_LOGOUT_COMPLETE = 0x04D,
|
||||
|
||||
// ---- Stand State ----
|
||||
CMSG_STAND_STATE_CHANGE = 0x101,
|
||||
|
||||
// ---- Random Roll ----
|
||||
MSG_RANDOM_ROLL = 0x1FB,
|
||||
|
||||
|
|
|
|||
|
|
@ -736,6 +736,56 @@ public:
|
|||
static bool parse(network::Packet& packet, FriendStatusData& data);
|
||||
};
|
||||
|
||||
/** CMSG_ADD_IGNORE packet builder */
|
||||
class AddIgnorePacket {
|
||||
public:
|
||||
static network::Packet build(const std::string& playerName);
|
||||
};
|
||||
|
||||
/** CMSG_DEL_IGNORE packet builder */
|
||||
class DelIgnorePacket {
|
||||
public:
|
||||
static network::Packet build(uint64_t ignoreGuid);
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Logout Commands
|
||||
// ============================================================
|
||||
|
||||
/** CMSG_LOGOUT_REQUEST packet builder */
|
||||
class LogoutRequestPacket {
|
||||
public:
|
||||
static network::Packet build();
|
||||
};
|
||||
|
||||
/** CMSG_LOGOUT_CANCEL packet builder */
|
||||
class LogoutCancelPacket {
|
||||
public:
|
||||
static network::Packet build();
|
||||
};
|
||||
|
||||
/** SMSG_LOGOUT_RESPONSE data */
|
||||
struct LogoutResponseData {
|
||||
uint32_t result = 0; // 0 = success, 1 = failure
|
||||
uint8_t instant = 0; // 1 = instant logout
|
||||
};
|
||||
|
||||
/** SMSG_LOGOUT_RESPONSE parser */
|
||||
class LogoutResponseParser {
|
||||
public:
|
||||
static bool parse(network::Packet& packet, LogoutResponseData& data);
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Stand State
|
||||
// ============================================================
|
||||
|
||||
/** CMSG_STAND_STATE_CHANGE packet builder */
|
||||
class StandStateChangePacket {
|
||||
public:
|
||||
static network::Packet build(uint8_t state);
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Random Roll
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -317,6 +317,14 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
}
|
||||
break;
|
||||
|
||||
case Opcode::SMSG_LOGOUT_RESPONSE:
|
||||
handleLogoutResponse(packet);
|
||||
break;
|
||||
|
||||
case Opcode::SMSG_LOGOUT_COMPLETE:
|
||||
handleLogoutComplete(packet);
|
||||
break;
|
||||
|
||||
// ---- Phase 1: Foundation ----
|
||||
case Opcode::SMSG_NAME_QUERY_RESPONSE:
|
||||
handleNameQueryResponse(packet);
|
||||
|
|
@ -1679,6 +1687,95 @@ void GameHandler::randomRoll(uint32_t minRoll, uint32_t maxRoll) {
|
|||
LOG_INFO("Rolled ", minRoll, "-", maxRoll);
|
||||
}
|
||||
|
||||
void GameHandler::addIgnore(const std::string& playerName) {
|
||||
if (state != WorldState::IN_WORLD || !socket) {
|
||||
LOG_WARNING("Cannot add ignore: not in world or not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
if (playerName.empty()) {
|
||||
addSystemChatMessage("You must specify a player name.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto packet = AddIgnorePacket::build(playerName);
|
||||
socket->send(packet);
|
||||
addSystemChatMessage("Adding " + playerName + " to ignore list...");
|
||||
LOG_INFO("Sent ignore request for: ", playerName);
|
||||
}
|
||||
|
||||
void GameHandler::removeIgnore(const std::string& playerName) {
|
||||
if (state != WorldState::IN_WORLD || !socket) {
|
||||
LOG_WARNING("Cannot remove ignore: not in world or not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
if (playerName.empty()) {
|
||||
addSystemChatMessage("You must specify a player name.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Look up GUID from cache
|
||||
auto it = ignoreCache.find(playerName);
|
||||
if (it == ignoreCache.end()) {
|
||||
addSystemChatMessage(playerName + " is not in your ignore list.");
|
||||
LOG_WARNING("Ignored player not found in cache: ", playerName);
|
||||
return;
|
||||
}
|
||||
|
||||
auto packet = DelIgnorePacket::build(it->second);
|
||||
socket->send(packet);
|
||||
addSystemChatMessage("Removing " + playerName + " from ignore list...");
|
||||
ignoreCache.erase(it);
|
||||
LOG_INFO("Sent remove ignore request for: ", playerName, " (GUID: 0x", std::hex, it->second, std::dec, ")");
|
||||
}
|
||||
|
||||
void GameHandler::requestLogout() {
|
||||
if (!socket) {
|
||||
LOG_WARNING("Cannot logout: not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
if (loggingOut_) {
|
||||
addSystemChatMessage("Already logging out.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto packet = LogoutRequestPacket::build();
|
||||
socket->send(packet);
|
||||
loggingOut_ = true;
|
||||
LOG_INFO("Sent logout request");
|
||||
}
|
||||
|
||||
void GameHandler::cancelLogout() {
|
||||
if (!socket) {
|
||||
LOG_WARNING("Cannot cancel logout: not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!loggingOut_) {
|
||||
addSystemChatMessage("Not currently logging out.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto packet = LogoutCancelPacket::build();
|
||||
socket->send(packet);
|
||||
loggingOut_ = false;
|
||||
addSystemChatMessage("Logout cancelled.");
|
||||
LOG_INFO("Cancelled logout");
|
||||
}
|
||||
|
||||
void GameHandler::setStandState(uint8_t standState) {
|
||||
if (state != WorldState::IN_WORLD || !socket) {
|
||||
LOG_WARNING("Cannot change stand state: not in world or not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
auto packet = StandStateChangePacket::build(standState);
|
||||
socket->send(packet);
|
||||
LOG_INFO("Changed stand state to: ", (int)standState);
|
||||
}
|
||||
|
||||
void GameHandler::releaseSpirit() {
|
||||
if (!playerDead_) return;
|
||||
if (socket && state == WorldState::IN_WORLD) {
|
||||
|
|
@ -3123,6 +3220,36 @@ void GameHandler::handleRandomRoll(network::Packet& packet) {
|
|||
LOG_INFO("Random roll: ", rollerName, " rolled ", data.result, " (", data.minRoll, "-", data.maxRoll, ")");
|
||||
}
|
||||
|
||||
void GameHandler::handleLogoutResponse(network::Packet& packet) {
|
||||
LogoutResponseData data;
|
||||
if (!LogoutResponseParser::parse(packet, data)) {
|
||||
LOG_WARNING("Failed to parse SMSG_LOGOUT_RESPONSE");
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.result == 0) {
|
||||
// Success - logout initiated
|
||||
if (data.instant) {
|
||||
addSystemChatMessage("Logging out...");
|
||||
} else {
|
||||
addSystemChatMessage("Logging out in 20 seconds...");
|
||||
}
|
||||
LOG_INFO("Logout response: success, instant=", (int)data.instant);
|
||||
} else {
|
||||
// Failure
|
||||
addSystemChatMessage("Cannot logout right now.");
|
||||
loggingOut_ = false;
|
||||
LOG_WARNING("Logout failed, result=", data.result);
|
||||
}
|
||||
}
|
||||
|
||||
void GameHandler::handleLogoutComplete(network::Packet& /*packet*/) {
|
||||
addSystemChatMessage("Logout complete.");
|
||||
loggingOut_ = false;
|
||||
LOG_INFO("Logout complete");
|
||||
// Server will disconnect us
|
||||
}
|
||||
|
||||
uint32_t GameHandler::generateClientSeed() {
|
||||
// Generate cryptographically random seed
|
||||
std::random_device rd;
|
||||
|
|
|
|||
|
|
@ -1268,6 +1268,54 @@ bool FriendStatusParser::parse(network::Packet& packet, FriendStatusData& data)
|
|||
return true;
|
||||
}
|
||||
|
||||
network::Packet AddIgnorePacket::build(const std::string& playerName) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_ADD_IGNORE));
|
||||
packet.writeString(playerName);
|
||||
LOG_DEBUG("Built CMSG_ADD_IGNORE: player=", playerName);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet DelIgnorePacket::build(uint64_t ignoreGuid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_DEL_IGNORE));
|
||||
packet.writeUInt64(ignoreGuid);
|
||||
LOG_DEBUG("Built CMSG_DEL_IGNORE: guid=0x", std::hex, ignoreGuid, std::dec);
|
||||
return packet;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Logout Commands
|
||||
// ============================================================
|
||||
|
||||
network::Packet LogoutRequestPacket::build() {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_LOGOUT_REQUEST));
|
||||
LOG_DEBUG("Built CMSG_LOGOUT_REQUEST");
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet LogoutCancelPacket::build() {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_LOGOUT_CANCEL));
|
||||
LOG_DEBUG("Built CMSG_LOGOUT_CANCEL");
|
||||
return packet;
|
||||
}
|
||||
|
||||
bool LogoutResponseParser::parse(network::Packet& packet, LogoutResponseData& data) {
|
||||
data.result = packet.readUInt32();
|
||||
data.instant = packet.readUInt8();
|
||||
LOG_DEBUG("Parsed SMSG_LOGOUT_RESPONSE: result=", data.result, " instant=", (int)data.instant);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Stand State
|
||||
// ============================================================
|
||||
|
||||
network::Packet StandStateChangePacket::build(uint8_t state) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_STAND_STATE_CHANGE));
|
||||
packet.writeUInt32(state);
|
||||
LOG_DEBUG("Built CMSG_STAND_STATE_CHANGE: state=", (int)state);
|
||||
return packet;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Random Roll
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -1079,6 +1079,77 @@ void GameScreen::sendChatMessage(game::GameHandler& gameHandler) {
|
|||
return;
|
||||
}
|
||||
|
||||
// /ignore command
|
||||
if (cmdLower == "ignore") {
|
||||
if (spacePos != std::string::npos) {
|
||||
std::string playerName = command.substr(spacePos + 1);
|
||||
gameHandler.addIgnore(playerName);
|
||||
chatInputBuffer[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
game::MessageChatData msg;
|
||||
msg.type = game::ChatType::SYSTEM;
|
||||
msg.language = game::ChatLanguage::UNIVERSAL;
|
||||
msg.message = "Usage: /ignore <name>";
|
||||
gameHandler.addLocalChatMessage(msg);
|
||||
chatInputBuffer[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
// /unignore command
|
||||
if (cmdLower == "unignore") {
|
||||
if (spacePos != std::string::npos) {
|
||||
std::string playerName = command.substr(spacePos + 1);
|
||||
gameHandler.removeIgnore(playerName);
|
||||
chatInputBuffer[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
game::MessageChatData msg;
|
||||
msg.type = game::ChatType::SYSTEM;
|
||||
msg.language = game::ChatLanguage::UNIVERSAL;
|
||||
msg.message = "Usage: /unignore <name>";
|
||||
gameHandler.addLocalChatMessage(msg);
|
||||
chatInputBuffer[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
// /sit command
|
||||
if (cmdLower == "sit") {
|
||||
gameHandler.setStandState(1); // 1 = sit
|
||||
chatInputBuffer[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
// /stand command
|
||||
if (cmdLower == "stand") {
|
||||
gameHandler.setStandState(0); // 0 = stand
|
||||
chatInputBuffer[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
// /kneel command
|
||||
if (cmdLower == "kneel") {
|
||||
gameHandler.setStandState(8); // 8 = kneel
|
||||
chatInputBuffer[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
// /logout command (already exists but using /logout instead of going to login)
|
||||
if (cmdLower == "logout" || cmdLower == "camp") {
|
||||
gameHandler.requestLogout();
|
||||
chatInputBuffer[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
// /cancellogout command
|
||||
if (cmdLower == "cancellogout") {
|
||||
gameHandler.cancelLogout();
|
||||
chatInputBuffer[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
// Chat channel slash commands
|
||||
bool isChannelCommand = false;
|
||||
if (cmdLower == "s" || cmdLower == "say") {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue