Add item text reading (books/notes): SMSG_ITEM_TEXT_QUERY_RESPONSE + renderItemTextWindow

- Parse SMSG_ITEM_TEXT_QUERY_RESPONSE (guid + isEmpty + string text),
  store in itemText_ and open book window when non-empty
- queryItemText(guid) sends CMSG_ITEM_TEXT_QUERY for readable items
- renderItemTextWindow(): scrollable book window with parchment-toned
  text, "Close" button; opens via isItemTextOpen() flag
This commit is contained in:
Kelsi 2026-03-09 14:15:59 -07:00
parent acde6070cf
commit 001c80a3db
4 changed files with 79 additions and 0 deletions

View file

@ -1997,6 +1997,9 @@ void GameHandler::handlePacket(network::Packet& packet) {
case Opcode::SMSG_QUEST_CONFIRM_ACCEPT:
handleQuestConfirmAccept(packet);
break;
case Opcode::SMSG_ITEM_TEXT_QUERY_RESPONSE:
handleItemTextQueryResponse(packet);
break;
case Opcode::SMSG_SUMMON_REQUEST:
handleSummonRequest(packet);
break;
@ -15046,6 +15049,33 @@ void GameHandler::handleAuctionCommandResult(network::Packet& packet) {
" error=", result.errorCode);
}
// ---------------------------------------------------------------------------
// Item text (SMSG_ITEM_TEXT_QUERY_RESPONSE)
// uint64 itemGuid + uint8 isEmpty + string text (when !isEmpty)
// ---------------------------------------------------------------------------
void GameHandler::handleItemTextQueryResponse(network::Packet& packet) {
size_t rem = packet.getSize() - packet.getReadPos();
if (rem < 9) return; // guid(8) + isEmpty(1)
/*uint64_t guid =*/ packet.readUInt64();
uint8_t isEmpty = packet.readUInt8();
if (!isEmpty) {
itemText_ = packet.readString();
itemTextOpen_= !itemText_.empty();
}
LOG_DEBUG("SMSG_ITEM_TEXT_QUERY_RESPONSE: isEmpty=", (int)isEmpty,
" len=", itemText_.size());
}
void GameHandler::queryItemText(uint64_t itemGuid) {
if (state != WorldState::IN_WORLD || !socket) return;
network::Packet pkt(wireOpcode(Opcode::CMSG_ITEM_TEXT_QUERY));
pkt.writeUInt64(itemGuid);
socket->send(pkt);
LOG_DEBUG("CMSG_ITEM_TEXT_QUERY: guid=0x", std::hex, itemGuid, std::dec);
}
// ---------------------------------------------------------------------------
// SMSG_QUEST_CONFIRM_ACCEPT (shared quest from group member)
// uint32 questId + string questTitle + uint64 sharerGuid

View file

@ -401,6 +401,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
renderTradeRequestPopup(gameHandler);
renderSummonRequestPopup(gameHandler);
renderSharedQuestPopup(gameHandler);
renderItemTextWindow(gameHandler);
renderGuildInvitePopup(gameHandler);
renderGuildRoster(gameHandler);
renderBuffBar(gameHandler);
@ -4405,6 +4406,42 @@ void GameScreen::renderDuelRequestPopup(game::GameHandler& gameHandler) {
ImGui::End();
}
void GameScreen::renderItemTextWindow(game::GameHandler& gameHandler) {
if (!gameHandler.isItemTextOpen()) return;
auto* window = core::Application::getInstance().getWindow();
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
ImGui::SetNextWindowPos(ImVec2(screenW * 0.5f - 200, screenH * 0.15f),
ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(400, 300), ImGuiCond_FirstUseEver);
bool open = true;
if (!ImGui::Begin("Book", &open, ImGuiWindowFlags_NoCollapse)) {
ImGui::End();
if (!open) gameHandler.closeItemText();
return;
}
if (!open) {
ImGui::End();
gameHandler.closeItemText();
return;
}
// Parchment-toned background text
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.2f, 0.1f, 0.0f, 1.0f));
ImGui::TextWrapped("%s", gameHandler.getItemText().c_str());
ImGui::PopStyleColor();
ImGui::Spacing();
if (ImGui::Button("Close", ImVec2(80, 0))) {
gameHandler.closeItemText();
}
ImGui::End();
}
void GameScreen::renderSharedQuestPopup(game::GameHandler& gameHandler) {
if (!gameHandler.hasPendingSharedQuest()) return;