mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Handle SMSG_PARTY_MEMBER_STATS to show group health out of visual range
Parse SMSG_PARTY_MEMBER_STATS and SMSG_PARTY_MEMBER_STATS_FULL packets so party frames display health, power, level, and online status even when group members are not nearby. Expansion-aware field sizes: uint16 health for Classic/TBC, uint32 for WotLK, plus per-expansion aura and vehicle seat handling.
This commit is contained in:
parent
1cf485d149
commit
a1f73fdd39
4 changed files with 237 additions and 15 deletions
|
|
@ -1127,6 +1127,7 @@ private:
|
||||||
void handleGroupList(network::Packet& packet);
|
void handleGroupList(network::Packet& packet);
|
||||||
void handleGroupUninvite(network::Packet& packet);
|
void handleGroupUninvite(network::Packet& packet);
|
||||||
void handlePartyCommandResult(network::Packet& packet);
|
void handlePartyCommandResult(network::Packet& packet);
|
||||||
|
void handlePartyMemberStats(network::Packet& packet, bool isFull);
|
||||||
|
|
||||||
// ---- Guild handlers ----
|
// ---- Guild handlers ----
|
||||||
void handleGuildInfo(network::Packet& packet);
|
void handleGuildInfo(network::Packet& packet);
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,19 @@ struct GroupMember {
|
||||||
uint8_t subGroup = 0; // Raid subgroup (0 for party)
|
uint8_t subGroup = 0; // Raid subgroup (0 for party)
|
||||||
uint8_t flags = 0; // Assistant, main tank, etc.
|
uint8_t flags = 0; // Assistant, main tank, etc.
|
||||||
uint8_t roles = 0; // LFG roles (3.3.5a)
|
uint8_t roles = 0; // LFG roles (3.3.5a)
|
||||||
|
|
||||||
|
// Party member stats (from SMSG_PARTY_MEMBER_STATS)
|
||||||
|
uint32_t curHealth = 0;
|
||||||
|
uint32_t maxHealth = 0;
|
||||||
|
uint8_t powerType = 0;
|
||||||
|
uint16_t curPower = 0;
|
||||||
|
uint16_t maxPower = 0;
|
||||||
|
uint16_t level = 0;
|
||||||
|
uint16_t zoneId = 0;
|
||||||
|
int16_t posX = 0;
|
||||||
|
int16_t posY = 0;
|
||||||
|
uint16_t onlineStatus = 0; // GROUP_UPDATE_FLAG_STATUS bitmask
|
||||||
|
bool hasPartyStats = false; // true once we've received stats
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1789,6 +1789,12 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
case Opcode::SMSG_PARTY_COMMAND_RESULT:
|
case Opcode::SMSG_PARTY_COMMAND_RESULT:
|
||||||
handlePartyCommandResult(packet);
|
handlePartyCommandResult(packet);
|
||||||
break;
|
break;
|
||||||
|
case Opcode::SMSG_PARTY_MEMBER_STATS:
|
||||||
|
handlePartyMemberStats(packet, false);
|
||||||
|
break;
|
||||||
|
case Opcode::SMSG_PARTY_MEMBER_STATS_FULL:
|
||||||
|
handlePartyMemberStats(packet, true);
|
||||||
|
break;
|
||||||
case Opcode::MSG_RAID_READY_CHECK:
|
case Opcode::MSG_RAID_READY_CHECK:
|
||||||
// Server ready-check prompt (minimal handling for now).
|
// Server ready-check prompt (minimal handling for now).
|
||||||
packet.setReadPos(packet.getSize());
|
packet.setReadPos(packet.getSize());
|
||||||
|
|
@ -9713,6 +9719,174 @@ void GameHandler::handlePartyCommandResult(network::Packet& packet) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameHandler::handlePartyMemberStats(network::Packet& packet, bool isFull) {
|
||||||
|
auto remaining = [&]() { return packet.getSize() - packet.getReadPos(); };
|
||||||
|
|
||||||
|
// Classic/TBC use uint16 for health fields and simpler aura format;
|
||||||
|
// WotLK uses uint32 health and uint32+uint8 per aura.
|
||||||
|
const bool isWotLK = isActiveExpansion("wotlk");
|
||||||
|
|
||||||
|
// SMSG_PARTY_MEMBER_STATS_FULL has a leading padding byte
|
||||||
|
if (isFull) {
|
||||||
|
if (remaining() < 1) return;
|
||||||
|
packet.readUInt8();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t memberGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||||
|
if (remaining() < 4) return;
|
||||||
|
uint32_t updateFlags = packet.readUInt32();
|
||||||
|
|
||||||
|
// Find matching group member
|
||||||
|
game::GroupMember* member = nullptr;
|
||||||
|
for (auto& m : partyData.members) {
|
||||||
|
if (m.guid == memberGuid) {
|
||||||
|
member = &m;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!member) {
|
||||||
|
packet.setReadPos(packet.getSize());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse each flag field in order
|
||||||
|
if (updateFlags & 0x0001) { // STATUS
|
||||||
|
if (remaining() >= 2)
|
||||||
|
member->onlineStatus = packet.readUInt16();
|
||||||
|
}
|
||||||
|
if (updateFlags & 0x0002) { // CUR_HP
|
||||||
|
if (isWotLK) {
|
||||||
|
if (remaining() >= 4)
|
||||||
|
member->curHealth = packet.readUInt32();
|
||||||
|
} else {
|
||||||
|
if (remaining() >= 2)
|
||||||
|
member->curHealth = packet.readUInt16();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updateFlags & 0x0004) { // MAX_HP
|
||||||
|
if (isWotLK) {
|
||||||
|
if (remaining() >= 4)
|
||||||
|
member->maxHealth = packet.readUInt32();
|
||||||
|
} else {
|
||||||
|
if (remaining() >= 2)
|
||||||
|
member->maxHealth = packet.readUInt16();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updateFlags & 0x0008) { // POWER_TYPE
|
||||||
|
if (remaining() >= 1)
|
||||||
|
member->powerType = packet.readUInt8();
|
||||||
|
}
|
||||||
|
if (updateFlags & 0x0010) { // CUR_POWER
|
||||||
|
if (remaining() >= 2)
|
||||||
|
member->curPower = packet.readUInt16();
|
||||||
|
}
|
||||||
|
if (updateFlags & 0x0020) { // MAX_POWER
|
||||||
|
if (remaining() >= 2)
|
||||||
|
member->maxPower = packet.readUInt16();
|
||||||
|
}
|
||||||
|
if (updateFlags & 0x0040) { // LEVEL
|
||||||
|
if (remaining() >= 2)
|
||||||
|
member->level = packet.readUInt16();
|
||||||
|
}
|
||||||
|
if (updateFlags & 0x0080) { // ZONE
|
||||||
|
if (remaining() >= 2)
|
||||||
|
member->zoneId = packet.readUInt16();
|
||||||
|
}
|
||||||
|
if (updateFlags & 0x0100) { // POSITION
|
||||||
|
if (remaining() >= 4) {
|
||||||
|
member->posX = static_cast<int16_t>(packet.readUInt16());
|
||||||
|
member->posY = static_cast<int16_t>(packet.readUInt16());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updateFlags & 0x0200) { // AURAS
|
||||||
|
if (remaining() >= 8) {
|
||||||
|
uint64_t auraMask = packet.readUInt64();
|
||||||
|
for (int i = 0; i < 64; ++i) {
|
||||||
|
if (auraMask & (uint64_t(1) << i)) {
|
||||||
|
if (isWotLK) {
|
||||||
|
// WotLK: uint32 spellId + uint8 auraFlags
|
||||||
|
if (remaining() < 5) break;
|
||||||
|
packet.readUInt32();
|
||||||
|
packet.readUInt8();
|
||||||
|
} else {
|
||||||
|
// Classic/TBC: uint16 spellId only
|
||||||
|
if (remaining() < 2) break;
|
||||||
|
packet.readUInt16();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updateFlags & 0x0400) { // PET_GUID
|
||||||
|
if (remaining() >= 8)
|
||||||
|
packet.readUInt64();
|
||||||
|
}
|
||||||
|
if (updateFlags & 0x0800) { // PET_NAME
|
||||||
|
if (remaining() > 0)
|
||||||
|
packet.readString();
|
||||||
|
}
|
||||||
|
if (updateFlags & 0x1000) { // PET_MODEL_ID
|
||||||
|
if (remaining() >= 2)
|
||||||
|
packet.readUInt16();
|
||||||
|
}
|
||||||
|
if (updateFlags & 0x2000) { // PET_CUR_HP
|
||||||
|
if (isWotLK) {
|
||||||
|
if (remaining() >= 4)
|
||||||
|
packet.readUInt32();
|
||||||
|
} else {
|
||||||
|
if (remaining() >= 2)
|
||||||
|
packet.readUInt16();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updateFlags & 0x4000) { // PET_MAX_HP
|
||||||
|
if (isWotLK) {
|
||||||
|
if (remaining() >= 4)
|
||||||
|
packet.readUInt32();
|
||||||
|
} else {
|
||||||
|
if (remaining() >= 2)
|
||||||
|
packet.readUInt16();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updateFlags & 0x8000) { // PET_POWER_TYPE
|
||||||
|
if (remaining() >= 1)
|
||||||
|
packet.readUInt8();
|
||||||
|
}
|
||||||
|
if (updateFlags & 0x10000) { // PET_CUR_POWER
|
||||||
|
if (remaining() >= 2)
|
||||||
|
packet.readUInt16();
|
||||||
|
}
|
||||||
|
if (updateFlags & 0x20000) { // PET_MAX_POWER
|
||||||
|
if (remaining() >= 2)
|
||||||
|
packet.readUInt16();
|
||||||
|
}
|
||||||
|
if (updateFlags & 0x40000) { // PET_AURAS
|
||||||
|
if (remaining() >= 8) {
|
||||||
|
uint64_t petAuraMask = packet.readUInt64();
|
||||||
|
for (int i = 0; i < 64; ++i) {
|
||||||
|
if (petAuraMask & (uint64_t(1) << i)) {
|
||||||
|
if (isWotLK) {
|
||||||
|
if (remaining() < 5) break;
|
||||||
|
packet.readUInt32();
|
||||||
|
packet.readUInt8();
|
||||||
|
} else {
|
||||||
|
if (remaining() < 2) break;
|
||||||
|
packet.readUInt16();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isWotLK && (updateFlags & 0x80000)) { // VEHICLE_SEAT (WotLK only)
|
||||||
|
if (remaining() >= 4)
|
||||||
|
packet.readUInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
member->hasPartyStats = true;
|
||||||
|
LOG_DEBUG("Party member stats for ", member->name,
|
||||||
|
": HP=", member->curHealth, "/", member->maxHealth,
|
||||||
|
" Level=", member->level);
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Guild Handlers
|
// Guild Handlers
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -4213,27 +4213,61 @@ void GameScreen::renderPartyFrames(game::GameHandler& gameHandler) {
|
||||||
for (const auto& member : partyData.members) {
|
for (const auto& member : partyData.members) {
|
||||||
ImGui::PushID(static_cast<int>(member.guid));
|
ImGui::PushID(static_cast<int>(member.guid));
|
||||||
|
|
||||||
|
// Name with level and status info
|
||||||
|
std::string label = member.name;
|
||||||
|
if (member.hasPartyStats && member.level > 0) {
|
||||||
|
label += " [" + std::to_string(member.level) + "]";
|
||||||
|
}
|
||||||
|
if (member.hasPartyStats) {
|
||||||
|
bool isOnline = (member.onlineStatus & 0x0001) != 0;
|
||||||
|
bool isDead = (member.onlineStatus & 0x0020) != 0;
|
||||||
|
bool isGhost = (member.onlineStatus & 0x0010) != 0;
|
||||||
|
if (!isOnline) label += " (offline)";
|
||||||
|
else if (isDead || isGhost) label += " (dead)";
|
||||||
|
}
|
||||||
|
|
||||||
// Clickable name to target
|
// Clickable name to target
|
||||||
if (ImGui::Selectable(member.name.c_str(), gameHandler.getTargetGuid() == member.guid)) {
|
if (ImGui::Selectable(label.c_str(), gameHandler.getTargetGuid() == member.guid)) {
|
||||||
gameHandler.setTarget(member.guid);
|
gameHandler.setTarget(member.guid);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to show health from entity
|
// Health bar: prefer party stats, fall back to entity
|
||||||
auto entity = gameHandler.getEntityManager().getEntity(member.guid);
|
uint32_t hp = 0, maxHp = 0;
|
||||||
if (entity && (entity->getType() == game::ObjectType::PLAYER || entity->getType() == game::ObjectType::UNIT)) {
|
if (member.hasPartyStats && member.maxHealth > 0) {
|
||||||
auto unit = std::static_pointer_cast<game::Unit>(entity);
|
hp = member.curHealth;
|
||||||
uint32_t hp = unit->getHealth();
|
maxHp = member.maxHealth;
|
||||||
uint32_t maxHp = unit->getMaxHealth();
|
} else {
|
||||||
if (maxHp > 0) {
|
auto entity = gameHandler.getEntityManager().getEntity(member.guid);
|
||||||
float pct = static_cast<float>(hp) / static_cast<float>(maxHp);
|
if (entity && (entity->getType() == game::ObjectType::PLAYER || entity->getType() == game::ObjectType::UNIT)) {
|
||||||
ImGui::PushStyleColor(ImGuiCol_PlotHistogram,
|
auto unit = std::static_pointer_cast<game::Unit>(entity);
|
||||||
pct > 0.5f ? ImVec4(0.2f, 0.8f, 0.2f, 1.0f) :
|
hp = unit->getHealth();
|
||||||
pct > 0.2f ? ImVec4(0.8f, 0.8f, 0.2f, 1.0f) :
|
maxHp = unit->getMaxHealth();
|
||||||
ImVec4(0.8f, 0.2f, 0.2f, 1.0f));
|
|
||||||
ImGui::ProgressBar(pct, ImVec2(-1, 12), "");
|
|
||||||
ImGui::PopStyleColor();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (maxHp > 0) {
|
||||||
|
float pct = static_cast<float>(hp) / static_cast<float>(maxHp);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_PlotHistogram,
|
||||||
|
pct > 0.5f ? ImVec4(0.2f, 0.8f, 0.2f, 1.0f) :
|
||||||
|
pct > 0.2f ? ImVec4(0.8f, 0.8f, 0.2f, 1.0f) :
|
||||||
|
ImVec4(0.8f, 0.2f, 0.2f, 1.0f));
|
||||||
|
ImGui::ProgressBar(pct, ImVec2(-1, 12), "");
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Power bar (mana/rage/energy) from party stats
|
||||||
|
if (member.hasPartyStats && member.maxPower > 0) {
|
||||||
|
float powerPct = static_cast<float>(member.curPower) / static_cast<float>(member.maxPower);
|
||||||
|
ImVec4 powerColor;
|
||||||
|
switch (member.powerType) {
|
||||||
|
case 0: powerColor = ImVec4(0.2f, 0.2f, 0.9f, 1.0f); break; // Mana (blue)
|
||||||
|
case 1: powerColor = ImVec4(0.9f, 0.2f, 0.2f, 1.0f); break; // Rage (red)
|
||||||
|
case 3: powerColor = ImVec4(0.9f, 0.9f, 0.2f, 1.0f); break; // Energy (yellow)
|
||||||
|
default: powerColor = ImVec4(0.5f, 0.5f, 0.5f, 1.0f); break;
|
||||||
|
}
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, powerColor);
|
||||||
|
ImGui::ProgressBar(powerPct, ImVec2(-1, 8), "");
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue