mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-25 21:03:51 +00:00
Fix guild roster, /who, /inspect, and character preview bugs
Guild O tab: fallback to character guildId when guildName_ not yet queried, re-query guild info on roster open. /who: add missing stringCount field and fix maxLevel default (0→100). /inspect: add SMSG_INSPECT_TALENT opcode (0x3F4) and rewrite parser for WotLK PackedGUID+talent format. Character preview: reset all tracking variables in setAssetManager() to force model reload on login.
This commit is contained in:
parent
be425c94dc
commit
a90c130d6e
12 changed files with 108 additions and 65 deletions
|
|
@ -237,5 +237,6 @@
|
||||||
"CMSG_LEAVE_CHANNEL": "0x098",
|
"CMSG_LEAVE_CHANNEL": "0x098",
|
||||||
"SMSG_CHANNEL_NOTIFY": "0x099",
|
"SMSG_CHANNEL_NOTIFY": "0x099",
|
||||||
"CMSG_CHANNEL_LIST": "0x09A",
|
"CMSG_CHANNEL_LIST": "0x09A",
|
||||||
"SMSG_CHANNEL_LIST": "0x09B"
|
"SMSG_CHANNEL_LIST": "0x09B",
|
||||||
|
"SMSG_INSPECT_TALENT": "0x3F4"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -266,5 +266,6 @@
|
||||||
"CMSG_LEAVE_CHANNEL": "0x098",
|
"CMSG_LEAVE_CHANNEL": "0x098",
|
||||||
"SMSG_CHANNEL_NOTIFY": "0x099",
|
"SMSG_CHANNEL_NOTIFY": "0x099",
|
||||||
"CMSG_CHANNEL_LIST": "0x09A",
|
"CMSG_CHANNEL_LIST": "0x09A",
|
||||||
"SMSG_CHANNEL_LIST": "0x09B"
|
"SMSG_CHANNEL_LIST": "0x09B",
|
||||||
|
"SMSG_INSPECT_TALENT": "0x3F4"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -237,5 +237,6 @@
|
||||||
"CMSG_LEAVE_CHANNEL": "0x098",
|
"CMSG_LEAVE_CHANNEL": "0x098",
|
||||||
"SMSG_CHANNEL_NOTIFY": "0x099",
|
"SMSG_CHANNEL_NOTIFY": "0x099",
|
||||||
"CMSG_CHANNEL_LIST": "0x09A",
|
"CMSG_CHANNEL_LIST": "0x09A",
|
||||||
"SMSG_CHANNEL_LIST": "0x09B"
|
"SMSG_CHANNEL_LIST": "0x09B",
|
||||||
|
"SMSG_INSPECT_TALENT": "0x3F4"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -266,5 +266,6 @@
|
||||||
"CMSG_LEAVE_CHANNEL": "0x098",
|
"CMSG_LEAVE_CHANNEL": "0x098",
|
||||||
"SMSG_CHANNEL_NOTIFY": "0x099",
|
"SMSG_CHANNEL_NOTIFY": "0x099",
|
||||||
"CMSG_CHANNEL_LIST": "0x09A",
|
"CMSG_CHANNEL_LIST": "0x09A",
|
||||||
"SMSG_CHANNEL_LIST": "0x09B"
|
"SMSG_CHANNEL_LIST": "0x09B",
|
||||||
|
"SMSG_INSPECT_TALENT": "0x3F4"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -330,7 +330,11 @@ public:
|
||||||
void queryGuildInfo(uint32_t guildId);
|
void queryGuildInfo(uint32_t guildId);
|
||||||
|
|
||||||
// Guild state accessors
|
// Guild state accessors
|
||||||
bool isInGuild() const { return !guildName_.empty(); }
|
bool isInGuild() const {
|
||||||
|
if (!guildName_.empty()) return true;
|
||||||
|
const Character* ch = getActiveCharacter();
|
||||||
|
return ch && ch->hasGuild();
|
||||||
|
}
|
||||||
const std::string& getGuildName() const { return guildName_; }
|
const std::string& getGuildName() const { return guildName_; }
|
||||||
const GuildRosterData& getGuildRoster() const { return guildRoster_; }
|
const GuildRosterData& getGuildRoster() const { return guildRoster_; }
|
||||||
bool hasGuildRoster() const { return hasGuildRoster_; }
|
bool hasGuildRoster() const { return hasGuildRoster_; }
|
||||||
|
|
|
||||||
|
|
@ -279,6 +279,7 @@ enum class LogicalOpcode : uint16_t {
|
||||||
SMSG_INVENTORY_CHANGE_FAILURE,
|
SMSG_INVENTORY_CHANGE_FAILURE,
|
||||||
CMSG_INSPECT,
|
CMSG_INSPECT,
|
||||||
SMSG_INSPECT_RESULTS,
|
SMSG_INSPECT_RESULTS,
|
||||||
|
SMSG_INSPECT_TALENT,
|
||||||
|
|
||||||
// ---- Death/Respawn ----
|
// ---- Death/Respawn ----
|
||||||
CMSG_REPOP_REQUEST,
|
CMSG_REPOP_REQUEST,
|
||||||
|
|
|
||||||
|
|
@ -823,7 +823,7 @@ public:
|
||||||
/** CMSG_WHO packet builder */
|
/** CMSG_WHO packet builder */
|
||||||
class WhoPacket {
|
class WhoPacket {
|
||||||
public:
|
public:
|
||||||
static network::Packet build(uint32_t minLevel = 0, uint32_t maxLevel = 0,
|
static network::Packet build(uint32_t minLevel = 0, uint32_t maxLevel = 100,
|
||||||
const std::string& playerName = "",
|
const std::string& playerName = "",
|
||||||
const std::string& guildName = "",
|
const std::string& guildName = "",
|
||||||
uint32_t raceMask = 0xFFFFFFFF,
|
uint32_t raceMask = 0xFFFFFFFF,
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,11 @@ public:
|
||||||
void setAssetManager(pipeline::AssetManager* am) {
|
void setAssetManager(pipeline::AssetManager* am) {
|
||||||
assetManager_ = am;
|
assetManager_ = am;
|
||||||
previewInitialized_ = false;
|
previewInitialized_ = false;
|
||||||
|
previewGuid_ = 0;
|
||||||
|
previewAppearanceBytes_ = 0;
|
||||||
|
previewFacialFeatures_ = 0;
|
||||||
|
previewUseFemaleModel_ = false;
|
||||||
|
previewEquipHash_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -801,6 +801,7 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Opcode::SMSG_INSPECT_RESULTS:
|
case Opcode::SMSG_INSPECT_RESULTS:
|
||||||
|
case Opcode::SMSG_INSPECT_TALENT:
|
||||||
handleInspectResults(packet);
|
handleInspectResults(packet);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -5259,76 +5260,92 @@ void GameHandler::handleItemQueryResponse(network::Packet& packet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameHandler::handleInspectResults(network::Packet& packet) {
|
void GameHandler::handleInspectResults(network::Packet& packet) {
|
||||||
// Best-effort parsing across Classic/TBC/WotLK variants.
|
// SMSG_INSPECT_TALENT (WotLK 3.3.5a) format:
|
||||||
// We only care about item entry IDs per equip slot.
|
// PackedGUID, uint32 unspentTalents, uint8 talentGroupCount, uint8 activeTalentGroup
|
||||||
if (packet.getSize() - packet.getReadPos() < 8) return;
|
// Per talent group: uint8 talentCount, [talentId(u32) + rank(u8)]..., uint8 glyphCount, [glyphId(u16)]...
|
||||||
|
// Then enchantment bitmask + enchant IDs
|
||||||
|
if (packet.getSize() - packet.getReadPos() < 4) return;
|
||||||
|
|
||||||
uint64_t guid = packet.readUInt64();
|
uint64_t guid = UpdateObjectParser::readPackedGuid(packet);
|
||||||
if (guid == 0) return;
|
if (guid == 0) return;
|
||||||
|
|
||||||
const size_t remaining = packet.getSize() - packet.getReadPos();
|
size_t bytesLeft = packet.getSize() - packet.getReadPos();
|
||||||
|
if (bytesLeft < 6) {
|
||||||
auto tryParseFixed = [&](size_t perSlotBytes, size_t itemIdOffset) -> std::optional<std::array<uint32_t, 19>> {
|
LOG_WARNING("SMSG_INSPECT_TALENT: too short after guid, ", bytesLeft, " bytes");
|
||||||
if (remaining < 19 * perSlotBytes) return std::nullopt;
|
// Show basic inspect message even without talent data
|
||||||
auto saved = packet.getReadPos();
|
auto entity = entityManager.getEntity(guid);
|
||||||
std::array<uint32_t, 19> items{};
|
std::string name = "Target";
|
||||||
bool plausible = false;
|
if (entity) {
|
||||||
|
auto player = std::dynamic_pointer_cast<Player>(entity);
|
||||||
for (int i = 0; i < 19; i++) {
|
if (player && !player->getName().empty()) name = player->getName();
|
||||||
if (perSlotBytes == 4) {
|
|
||||||
items[i] = packet.readUInt32();
|
|
||||||
} else if (perSlotBytes == 8) {
|
|
||||||
uint32_t a = packet.readUInt32();
|
|
||||||
uint32_t b = packet.readUInt32();
|
|
||||||
items[i] = (itemIdOffset == 0) ? a : b;
|
|
||||||
} else {
|
|
||||||
packet.setReadPos(saved);
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
if (items[i] > 0 && items[i] < 5000000u) plausible = true;
|
|
||||||
}
|
}
|
||||||
|
addSystemChatMessage("Inspecting " + name + " (no talent data available).");
|
||||||
// Rewind to allow other attempts if implausible.
|
|
||||||
if (!plausible) {
|
|
||||||
packet.setReadPos(saved);
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
return items;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::optional<std::array<uint32_t, 19>> parsed;
|
|
||||||
// Common shapes: [guid][19*uint32 itemId] or [guid][19*(uint32 itemId, uint32 enchant)].
|
|
||||||
parsed = tryParseFixed(4, 0);
|
|
||||||
if (!parsed) parsed = tryParseFixed(8, 0);
|
|
||||||
if (!parsed) parsed = tryParseFixed(8, 4); // sometimes itemId is second dword
|
|
||||||
|
|
||||||
if (!parsed) {
|
|
||||||
LOG_WARNING("SMSG_INSPECT_RESULTS: unrecognized payload size=", remaining, " for guid=0x", std::hex, guid, std::dec);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
inspectedPlayerItemEntries_[guid] = *parsed;
|
uint32_t unspentTalents = packet.readUInt32();
|
||||||
|
uint8_t talentGroupCount = packet.readUInt8();
|
||||||
|
uint8_t activeTalentGroup = packet.readUInt8();
|
||||||
|
|
||||||
// Query item templates so we can resolve displayInfoId/inventoryType.
|
// Resolve player name
|
||||||
for (uint32_t entry : *parsed) {
|
auto entity = entityManager.getEntity(guid);
|
||||||
if (entry == 0) continue;
|
std::string playerName = "Target";
|
||||||
queryItemInfo(entry, 0);
|
if (entity) {
|
||||||
|
auto player = std::dynamic_pointer_cast<Player>(entity);
|
||||||
|
if (player && !player->getName().empty()) playerName = player->getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If templates already exist, emit immediately.
|
// Parse talent groups
|
||||||
if (playerEquipmentCallback_) {
|
uint32_t totalTalents = 0;
|
||||||
std::array<uint32_t, 19> displayIds{};
|
for (uint8_t g = 0; g < talentGroupCount && g < 2; ++g) {
|
||||||
std::array<uint8_t, 19> invTypes{};
|
bytesLeft = packet.getSize() - packet.getReadPos();
|
||||||
for (int s = 0; s < 19; s++) {
|
if (bytesLeft < 1) break;
|
||||||
uint32_t entry = (*parsed)[s];
|
|
||||||
if (entry == 0) continue;
|
uint8_t talentCount = packet.readUInt8();
|
||||||
auto infoIt = itemInfoCache_.find(entry);
|
for (uint8_t t = 0; t < talentCount; ++t) {
|
||||||
if (infoIt == itemInfoCache_.end()) continue;
|
bytesLeft = packet.getSize() - packet.getReadPos();
|
||||||
displayIds[s] = infoIt->second.displayInfoId;
|
if (bytesLeft < 5) break;
|
||||||
invTypes[s] = static_cast<uint8_t>(infoIt->second.inventoryType);
|
packet.readUInt32(); // talentId
|
||||||
|
packet.readUInt8(); // rank
|
||||||
|
totalTalents++;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesLeft = packet.getSize() - packet.getReadPos();
|
||||||
|
if (bytesLeft < 1) break;
|
||||||
|
uint8_t glyphCount = packet.readUInt8();
|
||||||
|
for (uint8_t gl = 0; gl < glyphCount; ++gl) {
|
||||||
|
bytesLeft = packet.getSize() - packet.getReadPos();
|
||||||
|
if (bytesLeft < 2) break;
|
||||||
|
packet.readUInt16(); // glyphId
|
||||||
}
|
}
|
||||||
playerEquipmentCallback_(guid, displayIds, invTypes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse enchantment slot mask + enchant IDs
|
||||||
|
bytesLeft = packet.getSize() - packet.getReadPos();
|
||||||
|
if (bytesLeft >= 4) {
|
||||||
|
uint32_t slotMask = packet.readUInt32();
|
||||||
|
for (int slot = 0; slot < 19; ++slot) {
|
||||||
|
if (slotMask & (1u << slot)) {
|
||||||
|
bytesLeft = packet.getSize() - packet.getReadPos();
|
||||||
|
if (bytesLeft < 2) break;
|
||||||
|
packet.readUInt16(); // enchantId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display inspect results
|
||||||
|
std::string msg = "Inspect: " + playerName;
|
||||||
|
msg += " - " + std::to_string(totalTalents) + " talent points spent";
|
||||||
|
if (unspentTalents > 0) {
|
||||||
|
msg += ", " + std::to_string(unspentTalents) + " unspent";
|
||||||
|
}
|
||||||
|
if (talentGroupCount > 1) {
|
||||||
|
msg += " (dual spec, active: " + std::to_string(activeTalentGroup + 1) + ")";
|
||||||
|
}
|
||||||
|
addSystemChatMessage(msg);
|
||||||
|
|
||||||
|
LOG_INFO("Inspect results for ", playerName, ": ", totalTalents, " talents, ",
|
||||||
|
unspentTalents, " unspent, ", (int)talentGroupCount, " specs");
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t GameHandler::resolveOnlineItemGuid(uint32_t itemId) const {
|
uint64_t GameHandler::resolveOnlineItemGuid(uint32_t itemId) const {
|
||||||
|
|
|
||||||
|
|
@ -290,6 +290,7 @@ static const OpcodeNameEntry kOpcodeNames[] = {
|
||||||
{"SMSG_CHANNEL_NOTIFY", LogicalOpcode::SMSG_CHANNEL_NOTIFY},
|
{"SMSG_CHANNEL_NOTIFY", LogicalOpcode::SMSG_CHANNEL_NOTIFY},
|
||||||
{"CMSG_CHANNEL_LIST", LogicalOpcode::CMSG_CHANNEL_LIST},
|
{"CMSG_CHANNEL_LIST", LogicalOpcode::CMSG_CHANNEL_LIST},
|
||||||
{"SMSG_CHANNEL_LIST", LogicalOpcode::SMSG_CHANNEL_LIST},
|
{"SMSG_CHANNEL_LIST", LogicalOpcode::SMSG_CHANNEL_LIST},
|
||||||
|
{"SMSG_INSPECT_TALENT", LogicalOpcode::SMSG_INSPECT_TALENT},
|
||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
|
|
@ -581,6 +582,7 @@ void OpcodeTable::loadWotlkDefaults() {
|
||||||
{LogicalOpcode::SMSG_CHANNEL_NOTIFY, 0x099},
|
{LogicalOpcode::SMSG_CHANNEL_NOTIFY, 0x099},
|
||||||
{LogicalOpcode::CMSG_CHANNEL_LIST, 0x09A},
|
{LogicalOpcode::CMSG_CHANNEL_LIST, 0x09A},
|
||||||
{LogicalOpcode::SMSG_CHANNEL_LIST, 0x09B},
|
{LogicalOpcode::SMSG_CHANNEL_LIST, 0x09B},
|
||||||
|
{LogicalOpcode::SMSG_INSPECT_TALENT, 0x3F4},
|
||||||
};
|
};
|
||||||
|
|
||||||
logicalToWire_.clear();
|
logicalToWire_.clear();
|
||||||
|
|
|
||||||
|
|
@ -1431,7 +1431,10 @@ network::Packet WhoPacket::build(uint32_t minLevel, uint32_t maxLevel,
|
||||||
packet.writeString(guildName);
|
packet.writeString(guildName);
|
||||||
packet.writeUInt32(raceMask);
|
packet.writeUInt32(raceMask);
|
||||||
packet.writeUInt32(classMask);
|
packet.writeUInt32(classMask);
|
||||||
packet.writeUInt32(zones); // Number of zones
|
packet.writeUInt32(zones); // Number of zone IDs (0 = no zone filter)
|
||||||
|
// Zone ID array would go here if zones > 0
|
||||||
|
packet.writeUInt32(0); // stringCount (number of search strings)
|
||||||
|
// String array would go here if stringCount > 0
|
||||||
LOG_DEBUG("Built CMSG_WHO: player=", playerName);
|
LOG_DEBUG("Built CMSG_WHO: player=", playerName);
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3369,6 +3369,13 @@ void GameScreen::renderGuildRoster(game::GameHandler& gameHandler) {
|
||||||
showGuildRoster_ = false;
|
showGuildRoster_ = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Re-query guild name if we have guildId but no name yet
|
||||||
|
if (gameHandler.getGuildName().empty()) {
|
||||||
|
const auto* ch = gameHandler.getActiveCharacter();
|
||||||
|
if (ch && ch->hasGuild()) {
|
||||||
|
gameHandler.queryGuildInfo(ch->guildId);
|
||||||
|
}
|
||||||
|
}
|
||||||
gameHandler.requestGuildRoster();
|
gameHandler.requestGuildRoster();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue