#pragma once #include "game/transport_path_repository.hpp" #include "game/transport_clock_sync.hpp" #include "game/transport_animator.hpp" #include #include #include #include #include #include namespace wowee::rendering { class WMORenderer; class M2Renderer; } namespace wowee::pipeline { class AssetManager; } namespace wowee::game { struct ActiveTransport { uint64_t guid; // Entity GUID uint32_t wmoInstanceId; // WMO renderer instance ID uint32_t pathId; // Current path uint32_t entry = 0; // GameObject entry (for MO_TRANSPORT path updates) glm::vec3 basePosition; // Spawn position (base offset for path) glm::vec3 position; // Current world position glm::quat rotation; // Current world rotation glm::mat4 transform; // Cached world transform glm::mat4 invTransform; // Cached inverse for collision // Player attachment state bool playerOnBoard; glm::vec3 playerLocalOffset; // Optional deck boundaries glm::vec3 deckMin; glm::vec3 deckMax; bool hasDeckBounds; // Time-based animation (deterministic, no drift) uint32_t localClockMs; // Local path time in milliseconds bool hasServerClock; // Whether we've synced with server time int32_t serverClockOffsetMs; // Offset: serverClock - localNow bool useClientAnimation; // Use client-side path animation bool clientAnimationReverse; // Run client animation in reverse along the selected path float serverYaw; // Server-authoritative yaw (radians) bool hasServerYaw; // Whether we've received server yaw bool serverYawFlipped180; // Auto-correction when server yaw is consistently opposite movement int serverYawAlignmentScore; // Hysteresis score for yaw flip detection double lastServerUpdate; // Time of last server movement update int serverUpdateCount; // Number of server updates received // Dead-reckoning from latest authoritative updates (used only when updates are sparse). glm::vec3 serverLinearVelocity; float serverAngularVelocity; bool hasServerVelocity; bool allowBootstrapVelocity; // Disable DBC bootstrap when spawn/path mismatch is clearly invalid bool isM2 = false; // True if rendered as M2 (not WMO), uses M2Renderer for transforms }; class TransportManager { public: TransportManager(); ~TransportManager(); void setWMORenderer(rendering::WMORenderer* renderer) { wmoRenderer_ = renderer; } void setM2Renderer(rendering::M2Renderer* renderer) { m2Renderer_ = renderer; } void update(float deltaTime); void registerTransport(uint64_t guid, uint32_t wmoInstanceId, uint32_t pathId, const glm::vec3& spawnWorldPos, uint32_t entry = 0); void unregisterTransport(uint64_t guid); ActiveTransport* getTransport(uint64_t guid); const std::unordered_map& getTransports() const { return transports_; } glm::vec3 getPlayerWorldPosition(uint64_t transportGuid, const glm::vec3& localOffset); glm::mat4 getTransportInvTransform(uint64_t transportGuid); void loadPathFromNodes(uint32_t pathId, const std::vector& waypoints, bool looping = true, float speed = 18.0f); void setDeckBounds(uint64_t guid, const glm::vec3& min, const glm::vec3& max); // Load transport paths from TransportAnimation.dbc bool loadTransportAnimationDBC(pipeline::AssetManager* assetMgr); // Load transport paths from TaxiPathNode.dbc (world-coordinate paths for MO_TRANSPORT) bool loadTaxiPathNodeDBC(pipeline::AssetManager* assetMgr); // Check if a TaxiPathNode path exists for a given taxiPathId bool hasTaxiPath(uint32_t taxiPathId) const; // Assign a TaxiPathNode path to an existing transport (called when GO query response arrives) // Returns true if the transport was updated bool assignTaxiPathToTransport(uint32_t entry, uint32_t taxiPathId); // Check if a path exists for a given GameObject entry bool hasPathForEntry(uint32_t entry) const; // Check if a path has meaningful XY travel (used to reject near-stationary false positives). bool hasUsableMovingPathForEntry(uint32_t entry, float minXYRange = 1.0f) const; // Infer a real moving DBC path by spawn position (for servers whose transport entry IDs // don't map 1:1 to TransportAnimation.dbc entry IDs). // Returns 0 when no suitable path match is found. uint32_t inferMovingPathForSpawn(const glm::vec3& spawnWorldPos, float maxDistance = 1200.0f) const; // Infer a DBC path by spawn position, optionally including z-only elevator paths. // Returns 0 when no suitable path match is found. uint32_t inferDbcPathForSpawn(const glm::vec3& spawnWorldPos, float maxDistance, bool allowZOnly) const; // Choose a deterministic fallback moving DBC path for known server transport entries/displayIds. // Returns 0 when no suitable moving path is available. uint32_t pickFallbackMovingPath(uint32_t entry, uint32_t displayId) const; // Update server-controlled transport position/rotation directly (bypasses path movement) void updateServerTransport(uint64_t guid, const glm::vec3& position, float orientation); // Enable/disable client-side animation for transports without server updates void setClientSideAnimation(bool enabled) { clientSideAnimation_ = enabled; } bool isClientSideAnimation() const { return clientSideAnimation_; } private: void updateTransportMovement(ActiveTransport& transport, float deltaTime); void updateTransformMatrices(ActiveTransport& transport); void pushTransform(ActiveTransport& transport); TransportPathRepository pathRepo_; TransportClockSync clockSync_; TransportAnimator animator_; std::unordered_map transports_; rendering::WMORenderer* wmoRenderer_ = nullptr; rendering::M2Renderer* m2Renderer_ = nullptr; bool clientSideAnimation_ = false; // DISABLED - use server positions instead of client prediction // double: float loses millisecond precision after ~4.5 hours (2^23 / 1000), // causing transport path interpolation to visibly jerk in long play sessions. double elapsedTime_ = 0.0; }; } // namespace wowee::game