diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 7221e85b..569261b2 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -609,6 +609,26 @@ public: uint32_t getPetitionCost() const { return petitionCost_; } uint64_t getPetitionNpcGuid() const { return petitionNpcGuid_; } + // Petition signatures (guild charter signing flow) + struct PetitionSignature { + uint64_t playerGuid = 0; + std::string playerName; // resolved later or empty + }; + struct PetitionInfo { + uint64_t petitionGuid = 0; + uint64_t ownerGuid = 0; + std::string guildName; + uint32_t signatureCount = 0; + uint32_t signaturesRequired = 9; // guild default; arena teams differ + std::vector signatures; + bool showUI = false; + }; + const PetitionInfo& getPetitionInfo() const { return petitionInfo_; } + bool hasPetitionSignaturesUI() const { return petitionInfo_.showUI; } + void clearPetitionSignaturesUI() { petitionInfo_.showUI = false; } + void signPetition(uint64_t petitionGuid); + void turnInPetition(uint64_t petitionGuid); + // Guild name lookup for other players' nameplates // Returns the guild name for a given guildId, or empty if unknown. // Automatically queries the server for unknown guild IDs. @@ -2375,6 +2395,9 @@ private: void handleGuildInvite(network::Packet& packet); void handleGuildCommandResult(network::Packet& packet); void handlePetitionShowlist(network::Packet& packet); + void handlePetitionQueryResponse(network::Packet& packet); + void handlePetitionShowSignatures(network::Packet& packet); + void handlePetitionSignResults(network::Packet& packet); void handlePetSpells(network::Packet& packet); void handleTurnInPetitionResults(network::Packet& packet); @@ -2999,6 +3022,7 @@ private: bool showPetitionDialog_ = false; uint32_t petitionCost_ = 0; uint64_t petitionNpcGuid_ = 0; + PetitionInfo petitionInfo_; uint64_t activeCharacterGuid_ = 0; Race playerRace_ = Race::HUMAN; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 660c9d0a..f3ca595f 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -7630,9 +7630,13 @@ void GameHandler::handlePacket(network::Packet& packet) { break; } case Opcode::SMSG_PETITION_QUERY_RESPONSE: + handlePetitionQueryResponse(packet); + break; case Opcode::SMSG_PETITION_SHOW_SIGNATURES: + handlePetitionShowSignatures(packet); + break; case Opcode::SMSG_PETITION_SIGN_RESULTS: - packet.setReadPos(packet.getSize()); + handlePetitionSignResults(packet); break; // ---- Pet system ---- @@ -19742,6 +19746,118 @@ void GameHandler::handlePetitionShowlist(network::Packet& packet) { LOG_INFO("Petition showlist: cost=", data.cost); } +void GameHandler::handlePetitionQueryResponse(network::Packet& packet) { + // SMSG_PETITION_QUERY_RESPONSE (3.3.5a): + // uint32 petitionEntry, uint64 petitionGuid, string guildName, + // string bodyText (empty), uint32 flags, uint32 minSignatures, + // uint32 maxSignatures, ...plus more fields we can skip + auto rem = [&]() { return packet.getSize() - packet.getReadPos(); }; + if (rem() < 12) return; + + /*uint32_t entry =*/ packet.readUInt32(); + uint64_t petGuid = packet.readUInt64(); + std::string guildName = packet.readString(); + /*std::string body =*/ packet.readString(); + + // Update petition info if it matches our current petition + if (petitionInfo_.petitionGuid == petGuid) { + petitionInfo_.guildName = guildName; + } + + LOG_INFO("SMSG_PETITION_QUERY_RESPONSE: guid=", petGuid, " name=", guildName); + packet.setReadPos(packet.getSize()); // skip remaining fields +} + +void GameHandler::handlePetitionShowSignatures(network::Packet& packet) { + // SMSG_PETITION_SHOW_SIGNATURES (3.3.5a): + // uint64 itemGuid (petition item in inventory) + // uint64 ownerGuid + // uint32 petitionGuid (low part / entry) + // uint8 signatureCount + // For each signature: + // uint64 playerGuid + // uint32 unk (always 0) + auto rem = [&]() { return packet.getSize() - packet.getReadPos(); }; + if (rem() < 21) return; + + petitionInfo_ = PetitionInfo{}; + petitionInfo_.petitionGuid = packet.readUInt64(); + petitionInfo_.ownerGuid = packet.readUInt64(); + /*uint32_t petEntry =*/ packet.readUInt32(); + uint8_t sigCount = packet.readUInt8(); + + petitionInfo_.signatureCount = sigCount; + petitionInfo_.signatures.reserve(sigCount); + + for (uint8_t i = 0; i < sigCount; ++i) { + if (rem() < 12) break; + PetitionSignature sig; + sig.playerGuid = packet.readUInt64(); + /*uint32_t unk =*/ packet.readUInt32(); + petitionInfo_.signatures.push_back(sig); + } + + petitionInfo_.showUI = true; + LOG_INFO("SMSG_PETITION_SHOW_SIGNATURES: petGuid=", petitionInfo_.petitionGuid, + " owner=", petitionInfo_.ownerGuid, + " sigs=", sigCount); +} + +void GameHandler::handlePetitionSignResults(network::Packet& packet) { + // SMSG_PETITION_SIGN_RESULTS (3.3.5a): + // uint64 petitionGuid, uint64 playerGuid, uint32 result + auto rem = [&]() { return packet.getSize() - packet.getReadPos(); }; + if (rem() < 20) return; + + uint64_t petGuid = packet.readUInt64(); + uint64_t playerGuid = packet.readUInt64(); + uint32_t result = packet.readUInt32(); + + switch (result) { + case 0: // PETITION_SIGN_OK + addSystemChatMessage("Petition signed successfully."); + // Increment local count + if (petitionInfo_.petitionGuid == petGuid) { + petitionInfo_.signatureCount++; + PetitionSignature sig; + sig.playerGuid = playerGuid; + petitionInfo_.signatures.push_back(sig); + } + break; + case 1: // PETITION_SIGN_ALREADY_SIGNED + addSystemChatMessage("You have already signed that petition."); + break; + case 2: // PETITION_SIGN_ALREADY_IN_GUILD + addSystemChatMessage("You are already in a guild."); + break; + case 3: // PETITION_SIGN_CANT_SIGN_OWN + addSystemChatMessage("You cannot sign your own petition."); + break; + default: + addSystemChatMessage("Cannot sign petition (error " + std::to_string(result) + ")."); + break; + } + LOG_INFO("SMSG_PETITION_SIGN_RESULTS: pet=", petGuid, " player=", playerGuid, + " result=", result); +} + +void GameHandler::signPetition(uint64_t petitionGuid) { + if (!socket || state != WorldState::IN_WORLD) return; + network::Packet pkt(wireOpcode(Opcode::CMSG_PETITION_SIGN)); + pkt.writeUInt64(petitionGuid); + pkt.writeUInt8(0); // unk + socket->send(pkt); + LOG_INFO("Signing petition: ", petitionGuid); +} + +void GameHandler::turnInPetition(uint64_t petitionGuid) { + if (!socket || state != WorldState::IN_WORLD) return; + network::Packet pkt(wireOpcode(Opcode::CMSG_TURN_IN_PETITION)); + pkt.writeUInt64(petitionGuid); + socket->send(pkt); + LOG_INFO("Turning in petition: ", petitionGuid); +} + void GameHandler::handleTurnInPetitionResults(network::Packet& packet) { uint32_t result = 0; if (!TurnInPetitionResultsParser::parse(packet, result)) return; diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index c8250564..cc29c799 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -13942,6 +13942,62 @@ void GameScreen::renderGuildRoster(game::GameHandler& gameHandler) { ImGui::EndPopup(); } + // Petition signatures window (shown when a petition item is used or offered) + if (gameHandler.hasPetitionSignaturesUI()) { + ImGui::OpenPopup("PetitionSignatures"); + gameHandler.clearPetitionSignaturesUI(); + } + if (ImGui::BeginPopupModal("PetitionSignatures", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + const auto& pInfo = gameHandler.getPetitionInfo(); + if (!pInfo.guildName.empty()) + ImGui::Text("Guild Charter: %s", pInfo.guildName.c_str()); + else + ImGui::Text("Guild Charter"); + ImGui::Separator(); + + ImGui::Text("Signatures: %u / %u", pInfo.signatureCount, pInfo.signaturesRequired); + ImGui::Spacing(); + + if (!pInfo.signatures.empty()) { + for (size_t i = 0; i < pInfo.signatures.size(); ++i) { + const auto& sig = pInfo.signatures[i]; + // Try to resolve name from entity manager + std::string sigName; + if (sig.playerGuid != 0) { + auto entity = gameHandler.getEntityManager().getEntity(sig.playerGuid); + if (entity) { + auto* unit = dynamic_cast(entity.get()); + if (unit) sigName = unit->getName(); + } + } + if (sigName.empty()) + sigName = "Player " + std::to_string(i + 1); + ImGui::BulletText("%s", sigName.c_str()); + } + ImGui::Spacing(); + } + + // If we're not the owner, show Sign button + bool isOwner = (pInfo.ownerGuid == gameHandler.getPlayerGuid()); + if (!isOwner) { + if (ImGui::Button("Sign", ImVec2(120, 0))) { + gameHandler.signPetition(pInfo.petitionGuid); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + } else if (pInfo.signatureCount >= pInfo.signaturesRequired) { + // Owner with enough sigs — turn in + if (ImGui::Button("Turn In", ImVec2(120, 0))) { + gameHandler.turnInPetition(pInfo.petitionGuid); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + } + if (ImGui::Button("Close", ImVec2(120, 0))) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + if (!showGuildRoster_) return; // Get zone manager for name lookup