diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index af0fac2e..1ca12c72 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -456,6 +456,27 @@ public: void dismissPet(); bool hasPet() const { return petGuid_ != 0; } uint64_t getPetGuid() const { return petGuid_; } + + // ---- Pet state (populated by SMSG_PET_SPELLS / SMSG_PET_MODE) ---- + // 10 action bar slots; each entry is a packed uint32: + // bits 0-23 = spell ID (or 0 for empty) + // bits 24-31 = action type (0x00=cast, 0xC0=autocast on, 0x40=autocast off) + static constexpr int PET_ACTION_BAR_SLOTS = 10; + uint32_t getPetActionSlot(int idx) const { + if (idx < 0 || idx >= PET_ACTION_BAR_SLOTS) return 0; + return petActionSlots_[idx]; + } + // Pet command/react state from SMSG_PET_MODE or SMSG_PET_SPELLS + uint8_t getPetCommand() const { return petCommand_; } // 0=stay,1=follow,2=attack,3=dismiss + uint8_t getPetReact() const { return petReact_; } // 0=passive,1=defensive,2=aggressive + // Spells the pet knows (from SMSG_PET_SPELLS spell list) + const std::vector& getPetSpells() const { return petSpellList_; } + // Pet autocast set (spellIds that have autocast enabled) + bool isPetSpellAutocast(uint32_t spellId) const { + return petAutocastSpells_.count(spellId) != 0; + } + // Send CMSG_PET_ACTION to issue a pet command + void sendPetAction(uint32_t action, uint64_t targetGuid = 0); const std::unordered_set& getKnownSpells() const { return knownSpells; } // Player proficiency bitmasks (from SMSG_SET_PROFICIENCY) @@ -1763,6 +1784,11 @@ private: std::vector playerAuras; std::vector targetAuras; uint64_t petGuid_ = 0; + uint32_t petActionSlots_[10] = {}; // SMSG_PET_SPELLS action bar (10 slots) + uint8_t petCommand_ = 1; // 0=stay,1=follow,2=attack,3=dismiss + uint8_t petReact_ = 1; // 0=passive,1=defensive,2=aggressive + std::vector petSpellList_; // known pet spells + std::unordered_set petAutocastSpells_; // spells with autocast on // ---- Battleground queue state ---- struct BgQueueSlot { diff --git a/include/game/world_packets.hpp b/include/game/world_packets.hpp index e4a796e1..a65fd4aa 100644 --- a/include/game/world_packets.hpp +++ b/include/game/world_packets.hpp @@ -1729,7 +1729,8 @@ public: /** CMSG_PET_ACTION packet builder */ class PetActionPacket { public: - static network::Packet build(uint64_t petGuid, uint32_t action); + /** CMSG_PET_ACTION: petGuid + action + targetGuid (0 = no target) */ + static network::Packet build(uint64_t petGuid, uint32_t action, uint64_t targetGuid = 0); }; /** SMSG_CAST_FAILED data */ diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 52ad0e29..b309891f 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -5111,15 +5111,69 @@ void GameHandler::handlePacket(network::Packet& packet) { packet.setReadPos(packet.getSize()); break; - // ---- Pet system (not yet implemented) ---- - case Opcode::SMSG_PET_GUIDS: - case Opcode::SMSG_PET_MODE: + // ---- Pet system ---- + case Opcode::SMSG_PET_MODE: { + // uint64 petGuid, uint32 mode + // mode bits: low byte = command state, next byte = react state + if (packet.getSize() - packet.getReadPos() >= 12) { + uint64_t modeGuid = packet.readUInt64(); + uint32_t mode = packet.readUInt32(); + if (modeGuid == petGuid_) { + petCommand_ = static_cast(mode & 0xFF); + petReact_ = static_cast((mode >> 8) & 0xFF); + LOG_DEBUG("SMSG_PET_MODE: command=", (int)petCommand_, + " react=", (int)petReact_); + } + } + packet.setReadPos(packet.getSize()); + break; + } case Opcode::SMSG_PET_BROKEN: - case Opcode::SMSG_PET_CAST_FAILED: + // Pet bond broken (died or forcibly dismissed) — clear pet state + petGuid_ = 0; + petSpellList_.clear(); + petAutocastSpells_.clear(); + memset(petActionSlots_, 0, sizeof(petActionSlots_)); + addSystemChatMessage("Your pet has died."); + LOG_INFO("SMSG_PET_BROKEN: pet bond broken"); + packet.setReadPos(packet.getSize()); + break; + case Opcode::SMSG_PET_LEARNED_SPELL: { + if (packet.getSize() - packet.getReadPos() >= 4) { + uint32_t spellId = packet.readUInt32(); + petSpellList_.push_back(spellId); + LOG_DEBUG("SMSG_PET_LEARNED_SPELL: spellId=", spellId); + } + packet.setReadPos(packet.getSize()); + break; + } + case Opcode::SMSG_PET_UNLEARNED_SPELL: { + if (packet.getSize() - packet.getReadPos() >= 4) { + uint32_t spellId = packet.readUInt32(); + petSpellList_.erase( + std::remove(petSpellList_.begin(), petSpellList_.end(), spellId), + petSpellList_.end()); + petAutocastSpells_.erase(spellId); + LOG_DEBUG("SMSG_PET_UNLEARNED_SPELL: spellId=", spellId); + } + packet.setReadPos(packet.getSize()); + break; + } + case Opcode::SMSG_PET_CAST_FAILED: { + if (packet.getSize() - packet.getReadPos() >= 5) { + uint8_t castCount = packet.readUInt8(); + uint32_t spellId = packet.readUInt32(); + uint32_t reason = (packet.getSize() - packet.getReadPos() >= 4) + ? packet.readUInt32() : 0; + LOG_DEBUG("SMSG_PET_CAST_FAILED: spell=", spellId, + " reason=", reason, " castCount=", (int)castCount); + } + packet.setReadPos(packet.getSize()); + break; + } + case Opcode::SMSG_PET_GUIDS: case Opcode::SMSG_PET_DISMISS_SOUND: case Opcode::SMSG_PET_ACTION_SOUND: - case Opcode::SMSG_PET_LEARNED_SPELL: - case Opcode::SMSG_PET_UNLEARNED_SPELL: case Opcode::SMSG_PET_UNLEARN_CONFIRM: case Opcode::SMSG_PET_NAME_INVALID: case Opcode::SMSG_PET_RENAMEABLE: @@ -12419,14 +12473,72 @@ void GameHandler::cancelAura(uint32_t spellId) { } void GameHandler::handlePetSpells(network::Packet& packet) { - if (packet.getSize() - packet.getReadPos() < 8) { - // Empty packet = pet dismissed/died + const size_t remaining = packet.getSize() - packet.getReadPos(); + if (remaining < 8) { + // Empty or undersized → pet cleared (dismissed / died) petGuid_ = 0; - LOG_INFO("SMSG_PET_SPELLS: pet cleared (empty packet)"); + petSpellList_.clear(); + petAutocastSpells_.clear(); + memset(petActionSlots_, 0, sizeof(petActionSlots_)); + LOG_INFO("SMSG_PET_SPELLS: pet cleared"); return; } + petGuid_ = packet.readUInt64(); - LOG_INFO("SMSG_PET_SPELLS: petGuid=0x", std::hex, petGuid_, std::dec); + if (petGuid_ == 0) { + petSpellList_.clear(); + petAutocastSpells_.clear(); + memset(petActionSlots_, 0, sizeof(petActionSlots_)); + LOG_INFO("SMSG_PET_SPELLS: pet cleared (guid=0)"); + return; + } + + // uint16 duration (ms, 0 = permanent), uint16 timer (ms) + if (packet.getSize() - packet.getReadPos() < 4) goto done; + /*uint16_t dur =*/ packet.readUInt16(); + /*uint16_t timer =*/ packet.readUInt16(); + + // uint8 reactState, uint8 commandState (packed order varies; WotLK: react first) + if (packet.getSize() - packet.getReadPos() < 2) goto done; + petReact_ = packet.readUInt8(); // 0=passive, 1=defensive, 2=aggressive + petCommand_ = packet.readUInt8(); // 0=stay, 1=follow, 2=attack, 3=dismiss + + // 10 × uint32 action bar slots + if (packet.getSize() - packet.getReadPos() < PET_ACTION_BAR_SLOTS * 4u) goto done; + for (int i = 0; i < PET_ACTION_BAR_SLOTS; ++i) { + petActionSlots_[i] = packet.readUInt32(); + } + + // uint8 spell count, then per-spell: uint32 spellId, uint16 active flags + if (packet.getSize() - packet.getReadPos() < 1) goto done; + { + uint8_t spellCount = packet.readUInt8(); + petSpellList_.clear(); + petAutocastSpells_.clear(); + for (uint8_t i = 0; i < spellCount; ++i) { + if (packet.getSize() - packet.getReadPos() < 6) break; + uint32_t spellId = packet.readUInt32(); + uint16_t activeFlags = packet.readUInt16(); + petSpellList_.push_back(spellId); + // activeFlags bit 0 = autocast on + if (activeFlags & 0x0001) { + petAutocastSpells_.insert(spellId); + } + } + } + +done: + LOG_INFO("SMSG_PET_SPELLS: petGuid=0x", std::hex, petGuid_, std::dec, + " react=", (int)petReact_, " command=", (int)petCommand_, + " spells=", petSpellList_.size()); +} + +void GameHandler::sendPetAction(uint32_t action, uint64_t targetGuid) { + if (!hasPet() || state != WorldState::IN_WORLD || !socket) return; + auto pkt = PetActionPacket::build(petGuid_, action, targetGuid); + socket->send(pkt); + LOG_DEBUG("sendPetAction: petGuid=0x", std::hex, petGuid_, + " action=0x", action, " target=0x", targetGuid, std::dec); } void GameHandler::dismissPet() { diff --git a/src/game/world_packets.cpp b/src/game/world_packets.cpp index b71c2834..46fdcde4 100644 --- a/src/game/world_packets.cpp +++ b/src/game/world_packets.cpp @@ -2934,10 +2934,12 @@ network::Packet CancelAuraPacket::build(uint32_t spellId) { return packet; } -network::Packet PetActionPacket::build(uint64_t petGuid, uint32_t action) { +network::Packet PetActionPacket::build(uint64_t petGuid, uint32_t action, uint64_t targetGuid) { + // CMSG_PET_ACTION: petGuid(8) + action(4) + targetGuid(8) network::Packet packet(wireOpcode(Opcode::CMSG_PET_ACTION)); packet.writeUInt64(petGuid); packet.writeUInt32(action); + packet.writeUInt64(targetGuid); return packet; }