Add Tier 6 commands: party/raid management

- Uninvite/kick: /uninvite, /kick <player name> to remove player from party/raid
- Leave party: /leave, /leaveparty to leave current group
- Main tank: /maintank, /mt to set target as main tank (uses raid marker index 0)
- Main assist: /mainassist, /ma to set target as main assist (uses raid marker index 1)
- Clear markers: /clearmaintank, /clearmainassist to remove designations
- Raid info: /raidinfo to display raid lockouts and saved instances

Added opcodes:
- CMSG_REQUEST_RAID_INFO (0x2CD) for requesting raid lockout info
- SMSG_RAID_INSTANCE_INFO (0x2CC) for receiving raid info response

New packet builders:
- GroupUninvitePacket for removing players from group
- GroupDisbandPacket for leaving party (with logging)
- RaidTargetUpdatePacket for setting raid markers (main tank/assist)
- RequestRaidInfoPacket for querying raid lockouts

All commands include proper validation and user feedback.
This commit is contained in:
kelsi davis 2026-02-07 13:28:46 -08:00
parent 627c2fe0f6
commit d5b734a591
6 changed files with 246 additions and 11 deletions

View file

@ -261,6 +261,15 @@ public:
std::string getLastWhisperSender() const { return lastWhisperSender_; }
void setLastWhisperSender(const std::string& name) { lastWhisperSender_ = name; }
// Party/Raid management
void uninvitePlayer(const std::string& playerName);
void leaveParty();
void setMainTank(uint64_t targetGuid);
void setMainAssist(uint64_t targetGuid);
void clearMainTank();
void clearMainAssist();
void requestRaidInfo();
// ---- Phase 1: Name queries ----
void queryPlayerName(uint64_t guid);
void queryCreatureInfo(uint32_t entry, uint64_t guid);

View file

@ -175,6 +175,8 @@ enum class Opcode : uint16_t {
SMSG_GROUP_LIST = 0x07D,
SMSG_PARTY_COMMAND_RESULT = 0x07E,
MSG_RAID_TARGET_UPDATE = 0x321,
CMSG_REQUEST_RAID_INFO = 0x2CD,
SMSG_RAID_INSTANCE_INFO = 0x2CC,
// ---- Phase 5: Loot ----
CMSG_AUTOSTORE_LOOT_ITEM = 0x108,

View file

@ -884,6 +884,39 @@ public:
static network::Packet build();
};
// ============================================================
// Party/Raid Management
// ============================================================
/** CMSG_GROUP_UNINVITE_GUID packet builder */
class GroupUninvitePacket {
public:
static network::Packet build(const std::string& playerName);
};
/** CMSG_GROUP_DISBAND packet builder */
class GroupDisbandPacket {
public:
static network::Packet build();
};
/** MSG_RAID_TARGET_UPDATE packet builder */
class RaidTargetUpdatePacket {
public:
/**
* Build raid target marker update packet
* @param targetIndex 0-7 for raid icons, 0 = MainTank, 1 = MainAssist
* @param targetGuid GUID to mark, or 0 to clear
*/
static network::Packet build(uint8_t targetIndex, uint64_t targetGuid);
};
/** CMSG_REQUEST_RAID_INFO packet builder */
class RequestRaidInfoPacket {
public:
static network::Packet build();
};
// ============================================================
// Random Roll
// ============================================================
@ -1315,12 +1348,6 @@ public:
static network::Packet build();
};
/** CMSG_GROUP_DISBAND (leave party) packet builder */
class GroupDisbandPacket {
public:
static network::Packet build();
};
/** SMSG_GROUP_LIST parser */
class GroupListParser {
public:

View file

@ -2116,6 +2116,109 @@ void GameHandler::replyToLastWhisper(const std::string& message) {
LOG_INFO("Replied to ", lastWhisperSender_, ": ", message);
}
void GameHandler::uninvitePlayer(const std::string& playerName) {
if (state != WorldState::IN_WORLD || !socket) {
LOG_WARNING("Cannot uninvite player: not in world or not connected");
return;
}
if (playerName.empty()) {
addSystemChatMessage("You must specify a player name to uninvite.");
return;
}
auto packet = GroupUninvitePacket::build(playerName);
socket->send(packet);
addSystemChatMessage("Removed " + playerName + " from the group.");
LOG_INFO("Uninvited player: ", playerName);
}
void GameHandler::leaveParty() {
if (state != WorldState::IN_WORLD || !socket) {
LOG_WARNING("Cannot leave party: not in world or not connected");
return;
}
auto packet = GroupDisbandPacket::build();
socket->send(packet);
addSystemChatMessage("You have left the group.");
LOG_INFO("Left party/raid");
}
void GameHandler::setMainTank(uint64_t targetGuid) {
if (state != WorldState::IN_WORLD || !socket) {
LOG_WARNING("Cannot set main tank: not in world or not connected");
return;
}
if (targetGuid == 0) {
addSystemChatMessage("You must have a target selected.");
return;
}
// Main tank uses index 0
auto packet = RaidTargetUpdatePacket::build(0, targetGuid);
socket->send(packet);
addSystemChatMessage("Main tank set.");
LOG_INFO("Set main tank: 0x", std::hex, targetGuid, std::dec);
}
void GameHandler::setMainAssist(uint64_t targetGuid) {
if (state != WorldState::IN_WORLD || !socket) {
LOG_WARNING("Cannot set main assist: not in world or not connected");
return;
}
if (targetGuid == 0) {
addSystemChatMessage("You must have a target selected.");
return;
}
// Main assist uses index 1
auto packet = RaidTargetUpdatePacket::build(1, targetGuid);
socket->send(packet);
addSystemChatMessage("Main assist set.");
LOG_INFO("Set main assist: 0x", std::hex, targetGuid, std::dec);
}
void GameHandler::clearMainTank() {
if (state != WorldState::IN_WORLD || !socket) {
LOG_WARNING("Cannot clear main tank: not in world or not connected");
return;
}
// Clear main tank by setting GUID to 0
auto packet = RaidTargetUpdatePacket::build(0, 0);
socket->send(packet);
addSystemChatMessage("Main tank cleared.");
LOG_INFO("Cleared main tank");
}
void GameHandler::clearMainAssist() {
if (state != WorldState::IN_WORLD || !socket) {
LOG_WARNING("Cannot clear main assist: not in world or not connected");
return;
}
// Clear main assist by setting GUID to 0
auto packet = RaidTargetUpdatePacket::build(1, 0);
socket->send(packet);
addSystemChatMessage("Main assist cleared.");
LOG_INFO("Cleared main assist");
}
void GameHandler::requestRaidInfo() {
if (state != WorldState::IN_WORLD || !socket) {
LOG_WARNING("Cannot request raid info: not in world or not connected");
return;
}
auto packet = RequestRaidInfoPacket::build();
socket->send(packet);
addSystemChatMessage("Requesting raid lockout information...");
LOG_INFO("Requested raid info");
}
void GameHandler::releaseSpirit() {
if (!playerDead_) return;
if (socket && state == WorldState::IN_WORLD) {

View file

@ -1421,6 +1421,37 @@ network::Packet DuelCancelPacket::build() {
return packet;
}
// ============================================================
// Party/Raid Management
// ============================================================
network::Packet GroupUninvitePacket::build(const std::string& playerName) {
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_GROUP_UNINVITE_GUID));
packet.writeString(playerName);
LOG_DEBUG("Built CMSG_GROUP_UNINVITE_GUID for player: ", playerName);
return packet;
}
network::Packet GroupDisbandPacket::build() {
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_GROUP_DISBAND));
LOG_DEBUG("Built CMSG_GROUP_DISBAND");
return packet;
}
network::Packet RaidTargetUpdatePacket::build(uint8_t targetIndex, uint64_t targetGuid) {
network::Packet packet(static_cast<uint16_t>(Opcode::MSG_RAID_TARGET_UPDATE));
packet.writeUInt8(targetIndex);
packet.writeUInt64(targetGuid);
LOG_DEBUG("Built MSG_RAID_TARGET_UPDATE, index: ", (uint32_t)targetIndex, ", guid: 0x", std::hex, targetGuid, std::dec);
return packet;
}
network::Packet RequestRaidInfoPacket::build() {
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_REQUEST_RAID_INFO));
LOG_DEBUG("Built CMSG_REQUEST_RAID_INFO");
return packet;
}
// ============================================================
// Random Roll
// ============================================================
@ -2092,11 +2123,6 @@ network::Packet GroupDeclinePacket::build() {
return packet;
}
network::Packet GroupDisbandPacket::build() {
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_GROUP_DISBAND));
return packet;
}
bool GroupListParser::parse(network::Packet& packet, GroupListData& data) {
data.groupType = packet.readUInt8();
data.subGroup = packet.readUInt8();

View file

@ -1330,6 +1330,74 @@ void GameScreen::sendChatMessage(game::GameHandler& gameHandler) {
return;
}
// Party/Raid management commands
if (cmdLower == "uninvite" || cmdLower == "kick") {
if (spacePos != std::string::npos) {
std::string playerName = command.substr(spacePos + 1);
gameHandler.uninvitePlayer(playerName);
} else {
game::MessageChatData msg;
msg.type = game::ChatType::SYSTEM;
msg.language = game::ChatLanguage::UNIVERSAL;
msg.message = "Usage: /uninvite <player name>";
gameHandler.addLocalChatMessage(msg);
}
chatInputBuffer[0] = '\0';
return;
}
if (cmdLower == "leave" || cmdLower == "leaveparty") {
gameHandler.leaveParty();
chatInputBuffer[0] = '\0';
return;
}
if (cmdLower == "maintank" || cmdLower == "mt") {
if (gameHandler.hasTarget()) {
gameHandler.setMainTank(gameHandler.getTargetGuid());
} else {
game::MessageChatData msg;
msg.type = game::ChatType::SYSTEM;
msg.language = game::ChatLanguage::UNIVERSAL;
msg.message = "You must target a player to set as main tank.";
gameHandler.addLocalChatMessage(msg);
}
chatInputBuffer[0] = '\0';
return;
}
if (cmdLower == "mainassist" || cmdLower == "ma") {
if (gameHandler.hasTarget()) {
gameHandler.setMainAssist(gameHandler.getTargetGuid());
} else {
game::MessageChatData msg;
msg.type = game::ChatType::SYSTEM;
msg.language = game::ChatLanguage::UNIVERSAL;
msg.message = "You must target a player to set as main assist.";
gameHandler.addLocalChatMessage(msg);
}
chatInputBuffer[0] = '\0';
return;
}
if (cmdLower == "clearmaintank") {
gameHandler.clearMainTank();
chatInputBuffer[0] = '\0';
return;
}
if (cmdLower == "clearmainassist") {
gameHandler.clearMainAssist();
chatInputBuffer[0] = '\0';
return;
}
if (cmdLower == "raidinfo") {
gameHandler.requestRaidInfo();
chatInputBuffer[0] = '\0';
return;
}
// Chat channel slash commands
bool isChannelCommand = false;
if (cmdLower == "s" || cmdLower == "say") {