mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 09:33:51 +00:00
Add /roll and friend management commands
Roll Command: - Add /roll, /random, /rnd commands for random number generation - Support multiple formats: /roll, /roll 100, /roll 1-100, /roll 10 50 - Broadcasts rolls to party/raid with "[Name] rolls X (min-max)" format - Cap max roll at 10,000 to prevent abuse - Use MSG_RANDOM_ROLL (0x1FB) bidirectional opcode Friend Commands: - Add /friend add <name>, /addfriend <name> to add friends - Add /friend remove <name>, /removefriend <name> to remove friends - Support aliases: /delfriend, /remfriend - Maintain local friends cache mapping names to GUIDs for lookups - Display status messages for all friend actions: - Friend added/removed confirmations - Friend online/offline notifications - Error messages (not found, already friends, list full, ignoring) Social Opcodes: - Add CMSG_ADD_FRIEND (0x69) and SMSG_FRIEND_STATUS (0x68) - Add CMSG_DEL_FRIEND (0x6A) for friend removal - Add CMSG_SET_CONTACT_NOTES (0x6B) for friend notes (future use) - Add CMSG_ADD_IGNORE (0x6C) and CMSG_DEL_IGNORE (0x6D) (future use) Implementation: - Add RandomRollPacket builder and RandomRollParser for roll data - Add AddFriendPacket and DelFriendPacket builders - Add FriendStatusParser to handle server friend status updates - Add friendsCache map to store friend name-to-GUID mappings - Add handleRandomRoll() and handleFriendStatus() packet handlers - Comprehensive slash command parsing with multiple formats and aliases
This commit is contained in:
parent
f9c4cbddee
commit
6f45c6ab69
6 changed files with 427 additions and 0 deletions
|
|
@ -210,6 +210,14 @@ public:
|
|||
void requestPlayedTime();
|
||||
void queryWho(const std::string& playerName = "");
|
||||
|
||||
// Social commands
|
||||
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);
|
||||
|
||||
// Random roll
|
||||
void randomRoll(uint32_t minRoll = 1, uint32_t maxRoll = 100);
|
||||
|
||||
// ---- Phase 1: Name queries ----
|
||||
void queryPlayerName(uint64_t guid);
|
||||
void queryCreatureInfo(uint32_t entry, uint64_t guid);
|
||||
|
|
@ -513,6 +521,10 @@ private:
|
|||
void handlePlayedTime(network::Packet& packet);
|
||||
void handleWho(network::Packet& packet);
|
||||
|
||||
// ---- Social handlers ----
|
||||
void handleFriendStatus(network::Packet& packet);
|
||||
void handleRandomRoll(network::Packet& packet);
|
||||
|
||||
void addCombatText(CombatTextEntry::Type type, int32_t amount, uint32_t spellId, bool isPlayerSource);
|
||||
void addSystemChatMessage(const std::string& message);
|
||||
|
||||
|
|
@ -592,6 +604,9 @@ private:
|
|||
std::unordered_map<uint32_t, CreatureQueryResponseData> creatureInfoCache;
|
||||
std::unordered_set<uint32_t> pendingCreatureQueries;
|
||||
|
||||
// ---- Friend list cache ----
|
||||
std::unordered_map<std::string, uint64_t> friendsCache; // name -> guid
|
||||
|
||||
// ---- Online item tracking ----
|
||||
struct OnlineItemInfo {
|
||||
uint32_t entry = 0;
|
||||
|
|
|
|||
|
|
@ -63,6 +63,17 @@ enum class Opcode : uint16_t {
|
|||
CMSG_QUERY_TIME = 0x1CE,
|
||||
SMSG_QUERY_TIME_RESPONSE = 0x1CF,
|
||||
|
||||
// ---- Social Commands ----
|
||||
SMSG_FRIEND_STATUS = 0x068,
|
||||
CMSG_ADD_FRIEND = 0x069,
|
||||
CMSG_DEL_FRIEND = 0x06A,
|
||||
CMSG_SET_CONTACT_NOTES = 0x06B,
|
||||
CMSG_ADD_IGNORE = 0x06C,
|
||||
CMSG_DEL_IGNORE = 0x06D,
|
||||
|
||||
// ---- Random Roll ----
|
||||
MSG_RANDOM_ROLL = 0x1FB,
|
||||
|
||||
// ---- Phase 1: Foundation (Targeting, Queries) ----
|
||||
CMSG_SET_SELECTION = 0x13D,
|
||||
CMSG_NAME_QUERY = 0x050,
|
||||
|
|
|
|||
|
|
@ -700,6 +700,67 @@ public:
|
|||
uint32_t zones = 0);
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Social Commands
|
||||
// ============================================================
|
||||
|
||||
/** CMSG_ADD_FRIEND packet builder */
|
||||
class AddFriendPacket {
|
||||
public:
|
||||
static network::Packet build(const std::string& playerName, const std::string& note = "");
|
||||
};
|
||||
|
||||
/** CMSG_DEL_FRIEND packet builder */
|
||||
class DelFriendPacket {
|
||||
public:
|
||||
static network::Packet build(uint64_t friendGuid);
|
||||
};
|
||||
|
||||
/** CMSG_SET_CONTACT_NOTES packet builder */
|
||||
class SetContactNotesPacket {
|
||||
public:
|
||||
static network::Packet build(uint64_t friendGuid, const std::string& note);
|
||||
};
|
||||
|
||||
/** SMSG_FRIEND_STATUS data */
|
||||
struct FriendStatusData {
|
||||
uint8_t status = 0; // 0 = offline, 1 = online, etc.
|
||||
uint64_t guid = 0;
|
||||
std::string note;
|
||||
uint8_t chatFlag = 0;
|
||||
};
|
||||
|
||||
/** SMSG_FRIEND_STATUS parser */
|
||||
class FriendStatusParser {
|
||||
public:
|
||||
static bool parse(network::Packet& packet, FriendStatusData& data);
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Random Roll
|
||||
// ============================================================
|
||||
|
||||
/** CMSG_RANDOM_ROLL packet builder */
|
||||
class RandomRollPacket {
|
||||
public:
|
||||
static network::Packet build(uint32_t minRoll, uint32_t maxRoll);
|
||||
};
|
||||
|
||||
/** SMSG_RANDOM_ROLL data */
|
||||
struct RandomRollData {
|
||||
uint64_t rollerGuid = 0;
|
||||
uint64_t targetGuid = 0; // 0 for party roll
|
||||
uint32_t minRoll = 0;
|
||||
uint32_t maxRoll = 0;
|
||||
uint32_t result = 0;
|
||||
};
|
||||
|
||||
/** SMSG_RANDOM_ROLL parser */
|
||||
class RandomRollParser {
|
||||
public:
|
||||
static bool parse(network::Packet& packet, RandomRollData& data);
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Phase 1: Foundation — Targeting, Name Queries
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -305,6 +305,18 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
}
|
||||
break;
|
||||
|
||||
case Opcode::SMSG_FRIEND_STATUS:
|
||||
if (state == WorldState::IN_WORLD) {
|
||||
handleFriendStatus(packet);
|
||||
}
|
||||
break;
|
||||
|
||||
case Opcode::MSG_RANDOM_ROLL:
|
||||
if (state == WorldState::IN_WORLD) {
|
||||
handleRandomRoll(packet);
|
||||
}
|
||||
break;
|
||||
|
||||
// ---- Phase 1: Foundation ----
|
||||
case Opcode::SMSG_NAME_QUERY_RESPONSE:
|
||||
handleNameQueryResponse(packet);
|
||||
|
|
@ -1582,6 +1594,91 @@ void GameHandler::queryWho(const std::string& playerName) {
|
|||
LOG_INFO("Sent WHO query", playerName.empty() ? "" : " for: " + playerName);
|
||||
}
|
||||
|
||||
void GameHandler::addFriend(const std::string& playerName, const std::string& note) {
|
||||
if (state != WorldState::IN_WORLD || !socket) {
|
||||
LOG_WARNING("Cannot add friend: not in world or not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
if (playerName.empty()) {
|
||||
addSystemChatMessage("You must specify a player name.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto packet = AddFriendPacket::build(playerName, note);
|
||||
socket->send(packet);
|
||||
addSystemChatMessage("Sending friend request to " + playerName + "...");
|
||||
LOG_INFO("Sent friend request to: ", playerName);
|
||||
}
|
||||
|
||||
void GameHandler::removeFriend(const std::string& playerName) {
|
||||
if (state != WorldState::IN_WORLD || !socket) {
|
||||
LOG_WARNING("Cannot remove friend: 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 = friendsCache.find(playerName);
|
||||
if (it == friendsCache.end()) {
|
||||
addSystemChatMessage(playerName + " is not in your friends list.");
|
||||
LOG_WARNING("Friend not found in cache: ", playerName);
|
||||
return;
|
||||
}
|
||||
|
||||
auto packet = DelFriendPacket::build(it->second);
|
||||
socket->send(packet);
|
||||
addSystemChatMessage("Removing " + playerName + " from friends list...");
|
||||
LOG_INFO("Sent remove friend request for: ", playerName, " (GUID: 0x", std::hex, it->second, std::dec, ")");
|
||||
}
|
||||
|
||||
void GameHandler::setFriendNote(const std::string& playerName, const std::string& note) {
|
||||
if (state != WorldState::IN_WORLD || !socket) {
|
||||
LOG_WARNING("Cannot set friend note: 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 = friendsCache.find(playerName);
|
||||
if (it == friendsCache.end()) {
|
||||
addSystemChatMessage(playerName + " is not in your friends list.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto packet = SetContactNotesPacket::build(it->second, note);
|
||||
socket->send(packet);
|
||||
addSystemChatMessage("Updated note for " + playerName);
|
||||
LOG_INFO("Set friend note for: ", playerName);
|
||||
}
|
||||
|
||||
void GameHandler::randomRoll(uint32_t minRoll, uint32_t maxRoll) {
|
||||
if (state != WorldState::IN_WORLD || !socket) {
|
||||
LOG_WARNING("Cannot roll: not in world or not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
if (minRoll > maxRoll) {
|
||||
std::swap(minRoll, maxRoll);
|
||||
}
|
||||
|
||||
if (maxRoll > 10000) {
|
||||
maxRoll = 10000; // Cap at reasonable value
|
||||
}
|
||||
|
||||
auto packet = RandomRollPacket::build(minRoll, maxRoll);
|
||||
socket->send(packet);
|
||||
LOG_INFO("Rolled ", minRoll, "-", maxRoll);
|
||||
}
|
||||
|
||||
void GameHandler::releaseSpirit() {
|
||||
if (!playerDead_) return;
|
||||
if (socket && state == WorldState::IN_WORLD) {
|
||||
|
|
@ -2935,6 +3032,97 @@ void GameHandler::handleWho(network::Packet& packet) {
|
|||
}
|
||||
}
|
||||
|
||||
void GameHandler::handleFriendStatus(network::Packet& packet) {
|
||||
FriendStatusData data;
|
||||
if (!FriendStatusParser::parse(packet, data)) {
|
||||
LOG_WARNING("Failed to parse SMSG_FRIEND_STATUS");
|
||||
return;
|
||||
}
|
||||
|
||||
// Look up player name from GUID
|
||||
std::string playerName;
|
||||
auto it = playerNameCache.find(data.guid);
|
||||
if (it != playerNameCache.end()) {
|
||||
playerName = it->second;
|
||||
} else {
|
||||
playerName = "Unknown";
|
||||
}
|
||||
|
||||
// Update friends cache
|
||||
if (data.status == 1 || data.status == 2) { // Added or online
|
||||
friendsCache[playerName] = data.guid;
|
||||
} else if (data.status == 0) { // Removed
|
||||
friendsCache.erase(playerName);
|
||||
}
|
||||
|
||||
// Status messages
|
||||
switch (data.status) {
|
||||
case 0:
|
||||
addSystemChatMessage(playerName + " has been removed from your friends list.");
|
||||
break;
|
||||
case 1:
|
||||
addSystemChatMessage(playerName + " has been added to your friends list.");
|
||||
break;
|
||||
case 2:
|
||||
addSystemChatMessage(playerName + " is now online.");
|
||||
break;
|
||||
case 3:
|
||||
addSystemChatMessage(playerName + " is now offline.");
|
||||
break;
|
||||
case 4:
|
||||
addSystemChatMessage("Player not found.");
|
||||
break;
|
||||
case 5:
|
||||
addSystemChatMessage(playerName + " is already in your friends list.");
|
||||
break;
|
||||
case 6:
|
||||
addSystemChatMessage("Your friends list is full.");
|
||||
break;
|
||||
case 7:
|
||||
addSystemChatMessage(playerName + " is ignoring you.");
|
||||
break;
|
||||
default:
|
||||
LOG_INFO("Friend status: ", (int)data.status, " for ", playerName);
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_INFO("Friend status update: ", playerName, " status=", (int)data.status);
|
||||
}
|
||||
|
||||
void GameHandler::handleRandomRoll(network::Packet& packet) {
|
||||
RandomRollData data;
|
||||
if (!RandomRollParser::parse(packet, data)) {
|
||||
LOG_WARNING("Failed to parse SMSG_RANDOM_ROLL");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get roller name
|
||||
std::string rollerName;
|
||||
if (data.rollerGuid == playerGuid) {
|
||||
rollerName = "You";
|
||||
} else {
|
||||
auto it = playerNameCache.find(data.rollerGuid);
|
||||
if (it != playerNameCache.end()) {
|
||||
rollerName = it->second;
|
||||
} else {
|
||||
rollerName = "Someone";
|
||||
}
|
||||
}
|
||||
|
||||
// Build message
|
||||
std::string msg = rollerName;
|
||||
if (data.rollerGuid == playerGuid) {
|
||||
msg += " roll ";
|
||||
} else {
|
||||
msg += " rolls ";
|
||||
}
|
||||
msg += std::to_string(data.result);
|
||||
msg += " (" + std::to_string(data.minRoll) + "-" + std::to_string(data.maxRoll) + ")";
|
||||
|
||||
addSystemChatMessage(msg);
|
||||
LOG_INFO("Random roll: ", rollerName, " rolled ", data.result, " (", data.minRoll, "-", data.maxRoll, ")");
|
||||
}
|
||||
|
||||
uint32_t GameHandler::generateClientSeed() {
|
||||
// Generate cryptographically random seed
|
||||
std::random_device rd;
|
||||
|
|
|
|||
|
|
@ -1230,6 +1230,67 @@ network::Packet WhoPacket::build(uint32_t minLevel, uint32_t maxLevel,
|
|||
return packet;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Social Commands
|
||||
// ============================================================
|
||||
|
||||
network::Packet AddFriendPacket::build(const std::string& playerName, const std::string& note) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_ADD_FRIEND));
|
||||
packet.writeString(playerName);
|
||||
packet.writeString(note);
|
||||
LOG_DEBUG("Built CMSG_ADD_FRIEND: player=", playerName);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet DelFriendPacket::build(uint64_t friendGuid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_DEL_FRIEND));
|
||||
packet.writeUInt64(friendGuid);
|
||||
LOG_DEBUG("Built CMSG_DEL_FRIEND: guid=0x", std::hex, friendGuid, std::dec);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet SetContactNotesPacket::build(uint64_t friendGuid, const std::string& note) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_SET_CONTACT_NOTES));
|
||||
packet.writeUInt64(friendGuid);
|
||||
packet.writeString(note);
|
||||
LOG_DEBUG("Built CMSG_SET_CONTACT_NOTES: guid=0x", std::hex, friendGuid, std::dec);
|
||||
return packet;
|
||||
}
|
||||
|
||||
bool FriendStatusParser::parse(network::Packet& packet, FriendStatusData& data) {
|
||||
data.status = packet.readUInt8();
|
||||
data.guid = packet.readUInt64();
|
||||
if (data.status == 1) { // Online
|
||||
data.note = packet.readString();
|
||||
data.chatFlag = packet.readUInt8();
|
||||
}
|
||||
LOG_DEBUG("Parsed SMSG_FRIEND_STATUS: status=", (int)data.status, " guid=0x", std::hex, data.guid, std::dec);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Random Roll
|
||||
// ============================================================
|
||||
|
||||
network::Packet RandomRollPacket::build(uint32_t minRoll, uint32_t maxRoll) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::MSG_RANDOM_ROLL));
|
||||
packet.writeUInt32(minRoll);
|
||||
packet.writeUInt32(maxRoll);
|
||||
LOG_DEBUG("Built MSG_RANDOM_ROLL: ", minRoll, "-", maxRoll);
|
||||
return packet;
|
||||
}
|
||||
|
||||
bool RandomRollParser::parse(network::Packet& packet, RandomRollData& data) {
|
||||
data.rollerGuid = packet.readUInt64();
|
||||
data.targetGuid = packet.readUInt64();
|
||||
data.minRoll = packet.readUInt32();
|
||||
data.maxRoll = packet.readUInt32();
|
||||
data.result = packet.readUInt32();
|
||||
LOG_DEBUG("Parsed SMSG_RANDOM_ROLL: roller=0x", std::hex, data.rollerGuid, std::dec,
|
||||
" result=", data.result, " (", data.minRoll, "-", data.maxRoll, ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
network::Packet NameQueryPacket::build(uint64_t playerGuid) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_NAME_QUERY));
|
||||
packet.writeUInt64(playerGuid);
|
||||
|
|
|
|||
|
|
@ -988,6 +988,97 @@ void GameScreen::sendChatMessage(game::GameHandler& gameHandler) {
|
|||
return;
|
||||
}
|
||||
|
||||
// /roll command
|
||||
if (cmdLower == "roll" || cmdLower == "random" || cmdLower == "rnd") {
|
||||
uint32_t minRoll = 1;
|
||||
uint32_t maxRoll = 100;
|
||||
|
||||
if (spacePos != std::string::npos) {
|
||||
std::string args = command.substr(spacePos + 1);
|
||||
size_t dashPos = args.find('-');
|
||||
size_t spacePos2 = args.find(' ');
|
||||
|
||||
if (dashPos != std::string::npos) {
|
||||
// Format: /roll 1-100
|
||||
try {
|
||||
minRoll = std::stoul(args.substr(0, dashPos));
|
||||
maxRoll = std::stoul(args.substr(dashPos + 1));
|
||||
} catch (...) {}
|
||||
} else if (spacePos2 != std::string::npos) {
|
||||
// Format: /roll 1 100
|
||||
try {
|
||||
minRoll = std::stoul(args.substr(0, spacePos2));
|
||||
maxRoll = std::stoul(args.substr(spacePos2 + 1));
|
||||
} catch (...) {}
|
||||
} else {
|
||||
// Format: /roll 100 (means 1-100)
|
||||
try {
|
||||
maxRoll = std::stoul(args);
|
||||
} catch (...) {}
|
||||
}
|
||||
}
|
||||
|
||||
gameHandler.randomRoll(minRoll, maxRoll);
|
||||
chatInputBuffer[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
// /friend or /addfriend command
|
||||
if (cmdLower == "friend" || cmdLower == "addfriend") {
|
||||
if (spacePos != std::string::npos) {
|
||||
std::string args = command.substr(spacePos + 1);
|
||||
size_t subCmdSpace = args.find(' ');
|
||||
|
||||
if (cmdLower == "friend" && subCmdSpace != std::string::npos) {
|
||||
std::string subCmd = args.substr(0, subCmdSpace);
|
||||
std::transform(subCmd.begin(), subCmd.end(), subCmd.begin(), ::tolower);
|
||||
|
||||
if (subCmd == "add") {
|
||||
std::string playerName = args.substr(subCmdSpace + 1);
|
||||
gameHandler.addFriend(playerName);
|
||||
chatInputBuffer[0] = '\0';
|
||||
return;
|
||||
} else if (subCmd == "remove" || subCmd == "delete" || subCmd == "rem") {
|
||||
std::string playerName = args.substr(subCmdSpace + 1);
|
||||
gameHandler.removeFriend(playerName);
|
||||
chatInputBuffer[0] = '\0';
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// /addfriend name or /friend name (assume add)
|
||||
gameHandler.addFriend(args);
|
||||
chatInputBuffer[0] = '\0';
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
game::MessageChatData msg;
|
||||
msg.type = game::ChatType::SYSTEM;
|
||||
msg.language = game::ChatLanguage::UNIVERSAL;
|
||||
msg.message = "Usage: /friend add <name> or /friend remove <name>";
|
||||
gameHandler.addLocalChatMessage(msg);
|
||||
chatInputBuffer[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
// /removefriend or /delfriend command
|
||||
if (cmdLower == "removefriend" || cmdLower == "delfriend" || cmdLower == "remfriend") {
|
||||
if (spacePos != std::string::npos) {
|
||||
std::string playerName = command.substr(spacePos + 1);
|
||||
gameHandler.removeFriend(playerName);
|
||||
chatInputBuffer[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
game::MessageChatData msg;
|
||||
msg.type = game::ChatType::SYSTEM;
|
||||
msg.language = game::ChatLanguage::UNIVERSAL;
|
||||
msg.message = "Usage: /removefriend <name>";
|
||||
gameHandler.addLocalChatMessage(msg);
|
||||
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