mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 17:43:52 +00:00
Fix vendor buying and add quest turn-in flow
CMSG_BUY_ITEM was missing the trailing uint8 bag field, causing the server to silently drop undersized packets. Add handlers for SMSG_QUESTGIVER_REQUEST_ITEMS and SMSG_QUESTGIVER_OFFER_REWARD with UI windows for quest completion and reward selection.
This commit is contained in:
parent
6296c32a47
commit
5cc3d9645c
6 changed files with 447 additions and 0 deletions
|
|
@ -1285,7 +1285,11 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
break;
|
||||
}
|
||||
case Opcode::SMSG_QUESTGIVER_REQUEST_ITEMS:
|
||||
handleQuestRequestItems(packet);
|
||||
break;
|
||||
case Opcode::SMSG_QUESTGIVER_OFFER_REWARD:
|
||||
handleQuestOfferReward(packet);
|
||||
break;
|
||||
case Opcode::SMSG_GROUP_SET_LEADER:
|
||||
LOG_DEBUG("Ignoring known opcode: 0x", std::hex, opcode, std::dec);
|
||||
break;
|
||||
|
|
@ -4144,6 +4148,78 @@ void GameHandler::abandonQuest(uint32_t questId) {
|
|||
}
|
||||
}
|
||||
|
||||
void GameHandler::handleQuestRequestItems(network::Packet& packet) {
|
||||
QuestRequestItemsData data;
|
||||
if (!QuestRequestItemsParser::parse(packet, data)) {
|
||||
LOG_WARNING("Failed to parse SMSG_QUESTGIVER_REQUEST_ITEMS");
|
||||
return;
|
||||
}
|
||||
currentQuestRequestItems_ = data;
|
||||
questRequestItemsOpen_ = true;
|
||||
gossipWindowOpen = false;
|
||||
questDetailsOpen = false;
|
||||
|
||||
// Query item names for required items
|
||||
for (const auto& item : data.requiredItems) {
|
||||
queryItemInfo(item.itemId, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void GameHandler::handleQuestOfferReward(network::Packet& packet) {
|
||||
QuestOfferRewardData data;
|
||||
if (!QuestOfferRewardParser::parse(packet, data)) {
|
||||
LOG_WARNING("Failed to parse SMSG_QUESTGIVER_OFFER_REWARD");
|
||||
return;
|
||||
}
|
||||
currentQuestOfferReward_ = data;
|
||||
questOfferRewardOpen_ = true;
|
||||
questRequestItemsOpen_ = false;
|
||||
gossipWindowOpen = false;
|
||||
questDetailsOpen = false;
|
||||
|
||||
// Query item names for reward items
|
||||
for (const auto& item : data.choiceRewards)
|
||||
queryItemInfo(item.itemId, 0);
|
||||
for (const auto& item : data.fixedRewards)
|
||||
queryItemInfo(item.itemId, 0);
|
||||
}
|
||||
|
||||
void GameHandler::completeQuest() {
|
||||
if (!questRequestItemsOpen_ || state != WorldState::IN_WORLD || !socket) return;
|
||||
auto packet = QuestgiverCompleteQuestPacket::build(
|
||||
currentQuestRequestItems_.npcGuid, currentQuestRequestItems_.questId);
|
||||
socket->send(packet);
|
||||
questRequestItemsOpen_ = false;
|
||||
currentQuestRequestItems_ = QuestRequestItemsData{};
|
||||
}
|
||||
|
||||
void GameHandler::closeQuestRequestItems() {
|
||||
questRequestItemsOpen_ = false;
|
||||
currentQuestRequestItems_ = QuestRequestItemsData{};
|
||||
}
|
||||
|
||||
void GameHandler::chooseQuestReward(uint32_t rewardIndex) {
|
||||
if (!questOfferRewardOpen_ || state != WorldState::IN_WORLD || !socket) return;
|
||||
uint64_t npcGuid = currentQuestOfferReward_.npcGuid;
|
||||
auto packet = QuestgiverChooseRewardPacket::build(
|
||||
npcGuid, currentQuestOfferReward_.questId, rewardIndex);
|
||||
socket->send(packet);
|
||||
questOfferRewardOpen_ = false;
|
||||
currentQuestOfferReward_ = QuestOfferRewardData{};
|
||||
|
||||
// Re-query quest giver status so markers update
|
||||
if (npcGuid) {
|
||||
network::Packet qsPkt(static_cast<uint16_t>(Opcode::CMSG_QUESTGIVER_STATUS_QUERY));
|
||||
qsPkt.writeUInt64(npcGuid);
|
||||
socket->send(qsPkt);
|
||||
}
|
||||
}
|
||||
|
||||
void GameHandler::closeQuestOfferReward() {
|
||||
questOfferRewardOpen_ = false;
|
||||
currentQuestOfferReward_ = QuestOfferRewardData{};
|
||||
}
|
||||
|
||||
void GameHandler::closeGossip() {
|
||||
gossipWindowOpen = false;
|
||||
currentGossip = GossipMessageData{};
|
||||
|
|
|
|||
|
|
@ -2087,6 +2087,124 @@ bool GossipMessageParser::parse(network::Packet& packet, GossipMessageData& data
|
|||
return true;
|
||||
}
|
||||
|
||||
bool QuestRequestItemsParser::parse(network::Packet& packet, QuestRequestItemsData& data) {
|
||||
if (packet.getSize() - packet.getReadPos() < 20) return false;
|
||||
data.npcGuid = packet.readUInt64();
|
||||
data.questId = packet.readUInt32();
|
||||
data.title = packet.readString();
|
||||
data.completionText = packet.readString();
|
||||
|
||||
if (packet.getReadPos() + 20 > packet.getSize()) {
|
||||
LOG_INFO("Quest request items (short): id=", data.questId, " title='", data.title, "'");
|
||||
return true;
|
||||
}
|
||||
|
||||
/*emoteDelay*/ packet.readUInt32();
|
||||
/*emote*/ packet.readUInt32();
|
||||
/*autoCloseOnCancel*/ packet.readUInt32();
|
||||
/*flags*/ packet.readUInt32();
|
||||
/*suggestedPlayers*/ packet.readUInt32();
|
||||
|
||||
if (packet.getReadPos() + 4 > packet.getSize()) return true;
|
||||
data.requiredMoney = packet.readUInt32();
|
||||
|
||||
if (packet.getReadPos() + 4 > packet.getSize()) return true;
|
||||
uint32_t requiredItemCount = packet.readUInt32();
|
||||
for (uint32_t i = 0; i < requiredItemCount; ++i) {
|
||||
if (packet.getReadPos() + 12 > packet.getSize()) break;
|
||||
QuestRewardItem item;
|
||||
item.itemId = packet.readUInt32();
|
||||
item.count = packet.readUInt32();
|
||||
item.displayInfoId = packet.readUInt32();
|
||||
if (item.itemId > 0)
|
||||
data.requiredItems.push_back(item);
|
||||
}
|
||||
|
||||
if (packet.getReadPos() + 4 > packet.getSize()) return true;
|
||||
data.completableFlags = packet.readUInt32();
|
||||
|
||||
LOG_INFO("Quest request items: id=", data.questId, " title='", data.title,
|
||||
"' items=", data.requiredItems.size(), " completable=", data.isCompletable());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QuestOfferRewardParser::parse(network::Packet& packet, QuestOfferRewardData& data) {
|
||||
if (packet.getSize() - packet.getReadPos() < 20) return false;
|
||||
data.npcGuid = packet.readUInt64();
|
||||
data.questId = packet.readUInt32();
|
||||
data.title = packet.readString();
|
||||
data.rewardText = packet.readString();
|
||||
|
||||
if (packet.getReadPos() + 10 > packet.getSize()) {
|
||||
LOG_INFO("Quest offer reward (short): id=", data.questId, " title='", data.title, "'");
|
||||
return true;
|
||||
}
|
||||
|
||||
/*autoFinish*/ packet.readUInt8();
|
||||
/*flags*/ packet.readUInt32();
|
||||
/*suggestedPlayers*/ packet.readUInt32();
|
||||
|
||||
// Emotes
|
||||
if (packet.getReadPos() + 4 > packet.getSize()) return true;
|
||||
uint32_t emoteCount = packet.readUInt32();
|
||||
for (uint32_t i = 0; i < emoteCount; ++i) {
|
||||
if (packet.getReadPos() + 8 > packet.getSize()) break;
|
||||
packet.readUInt32(); // delay
|
||||
packet.readUInt32(); // emote
|
||||
}
|
||||
|
||||
// Choice reward items (pick one): count + 6 * (id, count, displayInfo)
|
||||
if (packet.getReadPos() + 4 > packet.getSize()) return true;
|
||||
/*choiceCount*/ packet.readUInt32();
|
||||
for (uint32_t i = 0; i < 6; ++i) {
|
||||
if (packet.getReadPos() + 12 > packet.getSize()) break;
|
||||
QuestRewardItem item;
|
||||
item.itemId = packet.readUInt32();
|
||||
item.count = packet.readUInt32();
|
||||
item.displayInfoId = packet.readUInt32();
|
||||
if (item.itemId > 0)
|
||||
data.choiceRewards.push_back(item);
|
||||
}
|
||||
|
||||
// Fixed reward items: count + 4 * (id, count, displayInfo)
|
||||
if (packet.getReadPos() + 4 > packet.getSize()) return true;
|
||||
/*rewardCount*/ packet.readUInt32();
|
||||
for (uint32_t i = 0; i < 4; ++i) {
|
||||
if (packet.getReadPos() + 12 > packet.getSize()) break;
|
||||
QuestRewardItem item;
|
||||
item.itemId = packet.readUInt32();
|
||||
item.count = packet.readUInt32();
|
||||
item.displayInfoId = packet.readUInt32();
|
||||
if (item.itemId > 0)
|
||||
data.fixedRewards.push_back(item);
|
||||
}
|
||||
|
||||
// Money and XP
|
||||
if (packet.getReadPos() + 4 <= packet.getSize())
|
||||
data.rewardMoney = packet.readUInt32();
|
||||
if (packet.getReadPos() + 4 <= packet.getSize())
|
||||
data.rewardXp = packet.readUInt32();
|
||||
|
||||
LOG_INFO("Quest offer reward: id=", data.questId, " title='", data.title,
|
||||
"' choices=", data.choiceRewards.size(), " fixed=", data.fixedRewards.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
network::Packet QuestgiverCompleteQuestPacket::build(uint64_t npcGuid, uint32_t questId) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_QUESTGIVER_COMPLETE_QUEST));
|
||||
packet.writeUInt64(npcGuid);
|
||||
packet.writeUInt32(questId);
|
||||
return packet;
|
||||
}
|
||||
|
||||
network::Packet QuestgiverChooseRewardPacket::build(uint64_t npcGuid, uint32_t questId, uint32_t rewardIndex) {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_QUESTGIVER_CHOOSE_REWARD));
|
||||
packet.writeUInt64(npcGuid);
|
||||
packet.writeUInt32(questId);
|
||||
packet.writeUInt32(rewardIndex);
|
||||
return packet;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Phase 5: Vendor
|
||||
// ============================================================
|
||||
|
|
@ -2103,6 +2221,7 @@ network::Packet BuyItemPacket::build(uint64_t vendorGuid, uint32_t itemId, uint3
|
|||
packet.writeUInt32(itemId);
|
||||
packet.writeUInt32(slot);
|
||||
packet.writeUInt8(count);
|
||||
packet.writeUInt8(0); // bag slot (0 = find any available bag slot)
|
||||
return packet;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue