diff --git a/include/game/packet_parsers.hpp b/include/game/packet_parsers.hpp index fe033101..8efcd5eb 100644 --- a/include/game/packet_parsers.hpp +++ b/include/game/packet_parsers.hpp @@ -26,6 +26,10 @@ public: // Classic: none, TBC: u8, WotLK: u16. virtual uint8_t movementFlags2Size() const { return 2; } + // Wire-format movement flag that gates transport data in MSG_MOVE_* payloads. + // WotLK/TBC: 0x200, Classic/Turtle: 0x02000000. + virtual uint32_t wireOnTransportFlag() const { return 0x00000200; } + // --- Movement --- /** Parse movement block from SMSG_UPDATE_OBJECT */ @@ -380,6 +384,7 @@ public: class ClassicPacketParsers : public TbcPacketParsers { public: uint8_t movementFlags2Size() const override { return 0; } + uint32_t wireOnTransportFlag() const override { return 0x02000000; } bool parseCharEnum(network::Packet& packet, CharEnumResponse& response) override; bool parseMovementBlock(network::Packet& packet, UpdateBlock& block) override; void writeMovementPayload(network::Packet& packet, const MovementInfo& info) override; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 506d80f3..b1ae2ece 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -16855,6 +16855,32 @@ void GameHandler::handleOtherPlayerMovement(network::Packet& packet) { info.z = packet.readFloat(); info.orientation = packet.readFloat(); + // Read transport data if the on-transport flag is set in wire-format move flags. + // The flag bit position differs between expansions (0x200 for WotLK/TBC, 0x02000000 for Classic/Turtle). + const uint32_t wireTransportFlag = packetParsers_ ? packetParsers_->wireOnTransportFlag() : 0x00000200; + const bool onTransport = (info.flags & wireTransportFlag) != 0; + uint64_t transportGuid = 0; + float tLocalX = 0, tLocalY = 0, tLocalZ = 0, tLocalO = 0; + if (onTransport) { + transportGuid = UpdateObjectParser::readPackedGuid(packet); + tLocalX = packet.readFloat(); + tLocalY = packet.readFloat(); + tLocalZ = packet.readFloat(); + tLocalO = packet.readFloat(); + // TBC and WotLK include a transport timestamp; Classic does not. + if (flags2Size >= 1) { + /*uint32_t transportTime =*/ packet.readUInt32(); + } + // WotLK adds a transport seat byte. + if (flags2Size >= 2) { + /*int8_t transportSeat =*/ packet.readUInt8(); + // Optional second transport time for interpolated movement. + if (info.flags2 & 0x0200) { + /*uint32_t transportTime2 =*/ packet.readUInt32(); + } + } + } + // Update entity position in entity manager auto entity = entityManager.getEntity(moverGuid); if (!entity) { @@ -16864,6 +16890,20 @@ void GameHandler::handleOtherPlayerMovement(network::Packet& packet) { // Convert server coords to canonical glm::vec3 canonical = core::coords::serverToCanonical(glm::vec3(info.x, info.y, info.z)); float canYaw = core::coords::serverToCanonicalYaw(info.orientation); + + // Handle transport attachment: attach/detach the entity so it follows the transport + // smoothly between movement updates via updateAttachedTransportChildren(). + if (onTransport && transportGuid != 0 && transportManager_) { + glm::vec3 localCanonical = core::coords::serverToCanonical(glm::vec3(tLocalX, tLocalY, tLocalZ)); + setTransportAttachment(moverGuid, entity->getType(), transportGuid, localCanonical, true, + core::coords::serverToCanonicalYaw(tLocalO)); + // Derive world position from transport system for best accuracy. + glm::vec3 worldPos = transportManager_->getPlayerWorldPosition(transportGuid, localCanonical); + canonical = worldPos; + } else if (!onTransport) { + // Player left transport — clear any stale attachment. + clearTransportAttachment(moverGuid); + } // Compute a smoothed interpolation window for this player. // Using a raw packet delta causes jitter when timing spikes (e.g. 50ms then 300ms). // An exponential moving average of intervals gives a stable playback speed that