/* * Darkflame Universe * Copyright 2019 */ #ifndef MOVINGPLATFORMCOMPONENT_H #define MOVINGPLATFORMCOMPONENT_H #include #include #include #include "RakNetTypes.h" #include "NiPoint3.h" #include "NiQuaternion.h" #include #include "dCommonVars.h" #include "EntityManager.h" #include "Component.h" #include "eMovementPlatformState.h" #include "eReplicaComponentType.h" struct ObjectLoadState; class Path; namespace GameMessages { struct PlatformResync; } /** * Different types of available platforms */ enum class eMoverSubComponentType : uint32_t { mover = 4, /** * A two point mover set up from the MovingPlatforms table or the platformMove* settings (used in NJ) */ simpleMover = 5, /** * A mover that turns between rotations along its path (same data as a mover) */ rotater = 6, /** * The level config set the platform flags all false: the client makes no subcomponent */ none = 0, }; /** * The client's platform state flags (LWOPlatform), as live sent them: a platform that was never started is stopped at * its waypoint (12), one waiting out a waypoint's wait is 9 (25 at either end of its path), one on its way is 2 and * one that stopped at an end of a path run once is 28. */ namespace ePlatformStateFlag { constexpr uint32_t Waiting = 1; constexpr uint32_t Travelling = 2; constexpr uint32_t Stopped = 4; constexpr uint32_t ReachedDesiredWaypoint = 8; constexpr uint32_t ReachedFinalDestination = 16; // What LWOPlatform::UpdateVelocities 0x00bd4330 keeps when the platform sets off (it drops the two "reached" bits) constexpr uint32_t MovementMask = Waiting | Travelling | Stopped; }; /** * How the client picks the subcomponents (LWOMovingPlatformComponent::LoadConfigData and SetupPlatform): one for each * of the platformIsMover, platformIsSimpleMover and platformIsRotater settings that is true, in that order, and when * none is set a mover for a platform with no registry component (set up from its attached path) or a simple mover for * one with a registry component. */ std::vector ChooseSubComponentTypes(int32_t componentID, const Entity& entity); // The first of ChooseSubComponentTypes (none when there are none): the one the server runs eMoverSubComponentType ChooseMoverSubComponentType(int32_t componentID, const Entity& entity); /** * The base of the platform subcomponents, as the client's LWOPlatform (mover, simple mover and rotator are built on * it): the platform's state between the waypoints of its path, which LWOPlatform::Deserialize 0x00c870e0 reads. */ class PlatformSubComponent { public: virtual ~PlatformSubComponent() = default; virtual eMoverSubComponentType GetType() const = 0; // LWOPlatform::Deserialize 0x00c870e0: a dirty bit, the state, desired waypoint, whether to stop there, reverse, the // percent between the waypoints, where it is, the current and next waypoints, the time waited and the time travelled virtual void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate); // ePlatformStateFlag bits uint32_t mState = ePlatformStateFlag::Stopped | ePlatformStateFlag::ReachedDesiredWaypoint; int32_t mDesiredWaypointIndex = -1; bool mInReverse = false; bool mShouldStopAtDesiredWaypoint = false; // From the current waypoint (0) to the next (1) float mPercentBetweenPoints = 0; // Where it is now (current waypoint + percent towards the next) NiPoint3 mPosition{}; uint32_t mCurrentWaypointIndex = 0; uint32_t mNextWaypointIndex = 0; // Time spent at the waypoint, and how long it waits there (LWOPlatform::SetWaitingTime 0x00bdc860: the waypoint's // wait, at least 0.1 s); the wait only counts once it was set float mIdleTimeElapsed = 0; float mTimeToWait = 0; bool mWaitSet = false; // The current waypoint's wait float mWaitTime = 0; // Whether it runs: a quick build's platform does not until it is built, nor while the quick build is open again // (LWOPlatform::SendMessage 0x00bdc930, RebuildNotifyState) bool mActive = true; // Time travelled since the current waypoint (time based paths only) float mMoveTimeElapsed = 0; // The current and next waypoints' speeds; time based paths: the seconds from one to the next in mCurrentSpeed float mCurrentSpeed = 0; float mNextSpeed = 0; // From the current waypoint to the next float mSegmentLength = 0; bool mTimeBased = false; }; /** * A mover along its path (4), as the client's LWOPlatformMover, run by the server as live ran it: the client's * LWOPlatform logic (RunPlatform 0x00c86e10, ArrivedAtWaypoint 0x00c86c30, HandleWaypointArrived 0x00c17bc0, * ResumeTravelling 0x00c86b90, ProcessStateChange 0x00cce380, GetNextWaypoint 0x00bd4490 / GetNextReversedWaypoint * 0x00bd4420, the speeds of GetWaypointSpeed 0x00be90d0). Live serialized it when it began waiting at a waypoint and * when it left one, not when it stopped (the client stops by itself). */ class MoverSubComponent final : public PlatformSubComponent { public: MoverSubComponent(const NiPoint3& startPos) { mPosition = startPos; } eMoverSubComponentType GetType() const override { return eMoverSubComponentType::mover; } }; /** * The angular velocity of a rotator, as the client's LWOPlatformRotator::SetAngularVelocity 0x00bfa380 works it out: * the rotation NiMatrix3::FromEulerAnglesXYZ 0x0116c3a0 makes of the three angles (radians; the matrix about x times * the one about y times the one about z, each turning the other way round than the usual right-handed one), as a * quaternion (NiQuaternion::FromRotation), and the velocity that turns from no rotation to it (from it to none in * reverse) in half the rate, by the shortest way (hkpKeyFrameUtility::KeyFrameInfo::setUsingPositionOrientationPair * 0x012a4050: the axis times the angle, at most half a turn, over the time). Angles that add up to whole turns give * no velocity. The client divides by a rate of 0; here that gives none. */ NiPoint3 CalcRotatorAngularVelocity(float rotX, float rotY, float rotZ, float rate, bool reverse); /** * A rotator (6), as the client's LWOPlatformRotator: on its path it does not move, it turns at its angular velocity * while travelling (rotX/rotY/rotZ in half turns, at rate), and never waits or arrives (IncrementWaitingTime * 0x004a0390, HandleStoppedLogic 0x00bdc660); it sends the LWOPlatform data a mover sends. */ class RotatorSubComponent final : public PlatformSubComponent { public: RotatorSubComponent(const NiPoint3& startPos) { mPosition = startPos; } eMoverSubComponentType GetType() const override { return eMoverSubComponentType::rotater; } // LWOPlatformRotator::LoadConfigData 0x00c33e10: rotX, rotY and rotZ times pi, and rate (seconds) float mRotationX = 0.0f; float mRotationY = 0.0f; float mRotationZ = 0.0f; float mRate = 0.0f; // Whether its object was put at its waypoint (LWOPlatform::ProcessStateChange 0x00cce380, from SetupPath) bool mPlaced = false; NiPoint3 GetAngularVelocity() const { return CalcRotatorAngularVelocity(mRotationX, mRotationY, mRotationZ, mRate, mInReverse); } }; /** * A simple mover (5), as the client's LWOPlatformSimpleMover: between a start point and that point moved by * platformMove (in the start rotation), taking platformMoveTime seconds, as GenerateSimpleMoverPath builds its two * waypoint path. At an end it has stopped there (28), as any platform on a path run once. */ class SimpleMoverSubComponent final : public PlatformSubComponent { public: // Live constructed the NJ counterweights waiting at their waypoint (9) SimpleMoverSubComponent() { mState = ePlatformStateFlag::Waiting | ePlatformStateFlag::ReachedDesiredWaypoint; } eMoverSubComponentType GetType() const override { return eMoverSubComponentType::simpleMover; } // LWOPlatformSimpleMover::Deserialize: a dirty bit, then whether there is a starting point with its position and // rotation (w, x, y, z); a dirty bit, then the state, the current waypoint and whether it is reversing void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; NiPoint3 mStartPosition{}; NiQuaternion mStartRotation{}; NiPoint3 mMove{}; float mMoveTime = 0.0f; bool mStartAtEnd = false; bool mDirtyStartingPoint = false; bool mDirtyState = false; /** * The world position of a waypoint (0 the start, 1 the end) */ NiPoint3 GetWaypointPosition(int32_t index) const; }; /** * Represents entities that may be moving platforms, indicating how they should move through the world. * NOTE: the logic in this component hardly does anything, apparently the client can figure most of this stuff out * if you just serialize it correctly, resulting in smoother results anyway. Don't be surprised if the exposed APIs * don't at all do what you expect them to as we don't instruct the client of changes made here. * ^^^ Trivia: This made the red blocks platform and property platforms a pain to implement. */ class MovingPlatformComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::MOVING_PLATFORM; // The client's allocator for this type (ObjectLoader2::DoObjectComponentLoad calls it in load order) static void Allocator(Entity& entity, ObjectLoadState& state); MovingPlatformComponent(Entity* parent, const int32_t componentID, const std::string& pathName); void LoadDataFromTemplate() override; void LoadConfigData() override; ~MovingPlatformComponent() override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; /** * Stops all pathing, called when an entity starts a quick build associated with this platform */ void OnQuickBuildInitilized(); /** * Starts the pathing, called when an entity completed a quick build associated with this platform */ void OnCompleteQuickBuild(); /** * Updates the movement state for the moving platform * @param value the movement state to set */ void SetMovementState(eMovementPlatformState value); /** * Instructs the moving platform to go to some waypoint * @param index the index of the waypoint * @param stopAtWaypoint determines if the platform should stop at the waypoint */ void GotoWaypoint(uint32_t index, bool stopAtWaypoint = true); /** * Puts the platform on a path of the zone, at one of its waypoints (the SetCurrentPath game message): a mover * starts over on it. False when the platform is not a mover or the zone has no such path. */ bool SetCurrentPath(const std::string& pathName, uint32_t startPoint = 0, bool reverse = false); /** * Starts the pathing of this platform, setting appropriate waypoints and speeds */ void StartPathing(); /** * Stops the platform from moving, waiting for it to be activated again. */ void StopPathing(); /** * Determines if the entity should be serialized on the next update * @param value whether to serialize the entity or not */ void SetSerialized(bool value); /** * Returns if this platform will start automatically after spawn * @return if this platform will start automatically after spawn */ bool GetNoAutoStart() const; /** * Sets the auto start value for this platform * @param value the auto start value to set */ void SetNoAutoStart(bool value); /** * Warps the platform to a waypoint index, skipping its current path * @param index the index to go to */ void WarpToWaypoint(size_t index); /** * Returns the waypoint this platform was previously at * @return the waypoint this platform was previously at */ size_t GetLastWaypointIndex() const; /** * The mover or rotator the server runs (the first subcomponent), if it is one */ PlatformSubComponent* GetMoverSubComponent() const; /** * Returns the simple mover sub component, if this platform is a simple mover */ SimpleMoverSubComponent* GetSimpleMoverSubComponent() const; eMoverSubComponentType GetMoverSubComponentType() const { return m_MoverSubComponentType; } /** * Calls back whenever the platform is at the last waypoint of its path (the PlatformAtLastWaypoint game message, * which live's scripts ask to be notified of on the NJ counterweights) */ void AddPlatformAtLastWaypointCallback(const std::function& callback); /** * Called when a simple mover gets to the waypoint it was sent to: the client's LWOPlatform::ArrivedAtWaypoint * and HandleWaypointArrived (Arrived, then ArrivedAtDesiredWaypoint and PlatformAtLastWaypoint) */ void OnSimpleMoverArrived(int32_t index); private: /** * The path this platform is currently on */ const Path* m_Path = nullptr; /** * The name of the path this platform is currently on */ std::u16string m_PathName; /** * Whether the platform has stopped pathing */ bool m_PathingStopped = false; // The waypoint the path starts at (attached_path_start), written with the path uint32_t m_StartWaypoint = 0; // Whether it runs the path backwards from there (SetCurrentPath), written with the path bool m_PathReverse = false; // The path changed since it was last written bool m_DirtyPath = false; /** * The type of the first subcomponent, the one the server runs (none without one) */ eMoverSubComponentType m_MoverSubComponentType = eMoverSubComponentType::none; /** * The subcomponents, one per type the client makes (ChooseSubComponentTypes), in its order */ std::vector> m_SubComponents; std::vector> m_AtLastWaypointCallbacks; // The object's quick build, looked up when the platform first runs class QuickBuildComponent* m_QuickBuild = nullptr; bool m_CheckedQuickBuild = false; /** * Counts sends so an arrival timer from an older send does nothing */ uint32_t m_MoveGeneration = 0; void SimpleMoverGotoWaypoint(int32_t index); void FireArrivalHooks(uint32_t index, bool atDesiredWaypoint, bool atLastWaypoint); // LWOPlatform::ProcessStateChange: index becomes the current waypoint (the next from the path's behavior and direction) void MoverProcessStateChange(uint32_t index); // The mover at its starting waypoint on the path, started unless startPathingOnLoad is false void SetupMoverPath(); // Puts the object where the mover is, without a serialization of its physics void MoveObject(); // A rotator's settings from the level config, and where it is on its path void SetupRotator(); // Turns a travelling rotator's object by its angular velocity void RunRotator(float deltaTime); // LWOPlatform::SetupPath and SendMessage (RebuildNotifyState): whether the platform of a quick build runs yet bool UpdateActive(PlatformSubComponent& platform); // LWOPlatform::SetWaitingTime: starts a wait of at least 0.1 s void MoverSetWaitingTime(float waitTime, float idleTimeElapsed); // LWOPlatform::ResumeTravelling: stopped between two waypoints it travels on; at a waypoint it first waits there void MoverResumeTravelling(); // LWOPlatform::ArrivedAtWaypoint: once it is at its next waypoint void MoverArrivedAtWaypoint(); // LWOPlatform::HandleWaypointArrived: tells the scripts, then a platform that travels on waits at the waypoint void MoverHandleWaypointArrived(uint32_t index, bool atDesiredWaypoint, bool atLastWaypoint); public: void Run(float deltaTime) override; // The mover's state as a PlatformResync gives it (RequestPlatformResync) void FillResync(GameMessages::PlatformResync& resync) const; // A mover on a path, run by the server (a rotater only spins: no waypoints) bool IsMover() const; // Whether the platform follows a path (its attached_path); a simple mover without one has no waypoints bool HasPath() const { return m_Path != nullptr; } private: /** * Whether the platform shouldn't auto start */ bool m_NoAutoStart; /** * Whether to serialize the entity on the next update */ bool m_Serialize = false; }; #endif // MOVINGPLATFORMCOMPONENT_H