#pragma once #include #include #include #include #include #include namespace wowee::rendering { class WMORenderer; class M2Renderer; } namespace wowee::pipeline { class AssetManager; } namespace wowee::game { struct TimedPoint { uint32_t tMs; // Time in milliseconds from DBC glm::vec3 pos; // Position at this time }; struct TransportPath { uint32_t pathId; std::vector points; // Time-indexed waypoints (includes duplicate first point at end for wrap) bool looping; // Set to false after adding explicit wrap point uint32_t durationMs; // Total loop duration in ms (includes wrap segment if added) bool zOnly; // True if path only has Z movement (elevator/bobbing), false if real XY travel bool fromDBC; // True if loaded from TransportAnimation.dbc, false for runtime fallback/custom paths bool worldCoords = false; // True if points are absolute world coords (TaxiPathNode), not local offsets }; 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 float 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); glm::vec3 evalTimedCatmullRom(const TransportPath& path, uint32_t pathTimeMs); glm::quat orientationFromTangent(const TransportPath& path, uint32_t pathTimeMs); void updateTransformMatrices(ActiveTransport& transport); std::unordered_map transports_; std::unordered_map paths_; // Indexed by transportEntry (pathId from TransportAnimation.dbc) std::unordered_map taxiPaths_; // Indexed by TaxiPath.dbc ID (world-coord paths for MO_TRANSPORT) rendering::WMORenderer* wmoRenderer_ = nullptr; rendering::M2Renderer* m2Renderer_ = nullptr; bool clientSideAnimation_ = false; // DISABLED - use server positions instead of client prediction float elapsedTime_ = 0.0f; // Total elapsed time (seconds) }; } // namespace wowee::game