/* * Darkflame Universe * Copyright 2019 */ #include "MovingPlatformComponent.h" #include "BitStream.h" #include "GeneralUtils.h" #include "dZoneManager.h" #include "EntityManager.h" #include "Logger.h" #include "GameMessages.h" #include "CppScripts.h" #include "SimplePhysicsComponent.h" #include "Zone.h" #include "CDClientDatabase.h" #include "MovementMessages.h" #include void PlatformSubComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { outBitStream.Write(true); outBitStream.Write(mState); outBitStream.Write(mDesiredWaypointIndex); outBitStream.Write(mShouldStopAtDesiredWaypoint); outBitStream.Write(mInReverse); outBitStream.Write(mPercentBetweenPoints); outBitStream.Write(mPosition.x); outBitStream.Write(mPosition.y); outBitStream.Write(mPosition.z); outBitStream.Write(mCurrentWaypointIndex); outBitStream.Write(mNextWaypointIndex); outBitStream.Write(mIdleTimeElapsed); outBitStream.Write(mMoveTimeElapsed); } //------------- SimpleMoverSubComponent below -------------- // 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 SimpleMoverSubComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { outBitStream.Write(bIsInitialUpdate || mDirtyStartingPoint); if (bIsInitialUpdate || mDirtyStartingPoint) { outBitStream.Write1(); outBitStream.Write(mStartPosition.x); outBitStream.Write(mStartPosition.y); outBitStream.Write(mStartPosition.z); outBitStream.Write(mStartRotation.w); outBitStream.Write(mStartRotation.x); outBitStream.Write(mStartRotation.y); outBitStream.Write(mStartRotation.z); if (!bIsInitialUpdate) mDirtyStartingPoint = false; } outBitStream.Write(bIsInitialUpdate || mDirtyState); if (bIsInitialUpdate || mDirtyState) { outBitStream.Write(mState); outBitStream.Write(static_cast(mCurrentWaypointIndex)); outBitStream.Write(mInReverse); if (!bIsInitialUpdate) mDirtyState = false; } } NiPoint3 SimpleMoverSubComponent::GetWaypointPosition(const int32_t index) const { if (index <= 0) return mStartPosition; const glm::vec3 moved = mStartRotation * glm::vec3(mMove.x, mMove.y, mMove.z); return mStartPosition + NiPoint3(moved.x, moved.y, moved.z); } std::vector ChooseSubComponentTypes(const int32_t componentID, const Entity& entity) { const bool setRotater = entity.HasVar(u"platformIsRotater"); const bool setMover = entity.HasVar(u"platformIsMover"); const bool setSimpleMover = entity.HasVar(u"platformIsSimpleMover"); if (!setRotater && !setMover && !setSimpleMover) { return { componentID < 1 ? eMoverSubComponentType::mover : eMoverSubComponentType::simpleMover }; } std::vector types; if (setMover && entity.GetVar(u"platformIsMover")) types.push_back(eMoverSubComponentType::mover); if (setSimpleMover && entity.GetVar(u"platformIsSimpleMover")) types.push_back(eMoverSubComponentType::simpleMover); if (setRotater && entity.GetVar(u"platformIsRotater")) types.push_back(eMoverSubComponentType::rotater); return types; } eMoverSubComponentType ChooseMoverSubComponentType(const int32_t componentID, const Entity& entity) { const auto types = ChooseSubComponentTypes(componentID, entity); return types.empty() ? eMoverSubComponentType::none : types.front(); } //------------- MovingPlatformComponent below -------------- MovingPlatformComponent::MovingPlatformComponent(Entity* parent, const int32_t componentID, const std::string& pathName) : Component(parent, componentID) { m_PathName = GeneralUtils::ASCIIToUTF16(pathName); m_NoAutoStart = false; } // The subcomponents (LWOMovingPlatformComponent::SetupPlatform: one per type, in the client's order), and a simple // mover's MovingPlatforms row void MovingPlatformComponent::LoadDataFromTemplate() { for (const auto type : ChooseSubComponentTypes(m_ComponentID, *m_Parent)) { if (type == eMoverSubComponentType::simpleMover) { auto simpleMover = std::make_unique(); simpleMover->mStartPosition = m_Parent->GetDefaultPosition(); simpleMover->mStartRotation = m_Parent->GetDefaultRotation(); // LWOPlatformSimpleMover::LoadTemplateData: the MovingPlatforms row, unless the object sets dbonly false const bool dbOnly = m_Parent->GetVar(u"dbonly"); if (m_ComponentID > 0 && (!m_Parent->HasVar(u"dbonly") || dbOnly)) { auto query = CDClientDatabase::CreatePreppedStmt( "SELECT platformMoveX, platformMoveY, platformMoveZ, platformMoveTime, platformStartAtEnd FROM MovingPlatforms WHERE id = ?;"); query.bind(1, m_ComponentID); auto result = query.execQuery(); if (!result.eof()) { simpleMover->mMove = NiPoint3(result.getFloatField(0), result.getFloatField(1), result.getFloatField(2)); simpleMover->mMoveTime = result.getFloatField(3); simpleMover->mStartAtEnd = result.getIntField(4) != 0; } else { LOG("LOT %i couldn't find simple mover %i in MovingPlatforms", m_Parent->GetLOT(), m_ComponentID); } } m_SubComponents.push_back(std::move(simpleMover)); } else if (type == eMoverSubComponentType::rotater) { m_SubComponents.push_back(std::make_unique(m_Parent->GetDefaultPosition())); } else { m_SubComponents.push_back(std::make_unique(m_Parent->GetDefaultPosition())); } } m_MoverSubComponentType = m_SubComponents.empty() ? eMoverSubComponentType::none : m_SubComponents.front()->GetType(); } // The level config: a simple mover's settings, the path and where and whether a mover starts void MovingPlatformComponent::LoadConfigData() { for (const auto& subComponent : m_SubComponents) { if (subComponent->GetType() != eMoverSubComponentType::simpleMover) continue; auto* const simpleMover = static_cast(subComponent.get()); const bool dbOnly = m_Parent->GetVar(u"dbonly"); // LWOPlatformSimpleMover::LoadConfigData: the object's settings over the table's, unless it is db only if (m_Parent->HasVar(u"attached_path_start")) simpleMover->mStartAtEnd = m_Parent->GetVar(u"attached_path_start") != 0; if (m_Parent->HasVar(u"platformStartAtEnd")) simpleMover->mStartAtEnd = m_Parent->GetVar(u"platformStartAtEnd"); if (!dbOnly) { if (m_Parent->HasVar(u"platformMoveX")) simpleMover->mMove.x = m_Parent->GetVar(u"platformMoveX"); if (m_Parent->HasVar(u"platformMoveY")) simpleMover->mMove.y = m_Parent->GetVar(u"platformMoveY"); if (m_Parent->HasVar(u"platformMoveZ")) simpleMover->mMove.z = m_Parent->GetVar(u"platformMoveZ"); if (m_Parent->HasVar(u"platformMoveTime")) simpleMover->mMoveTime = m_Parent->GetVar(u"platformMoveTime"); } simpleMover->mCurrentWaypointIndex = simpleMover->mStartAtEnd ? 1 : 0; } const auto pathName = GeneralUtils::UTF16ToWTF8(m_PathName); m_Path = Game::zoneManager->GetZone()->GetPath(pathName); if (m_Path == nullptr) { LOG("Path not found: %s", pathName.c_str()); } // A mover starts at its attached_path_start waypoint, waiting there, and leaves after the waypoint's wait unless // startPathingOnLoad is false (live: 25 with the time waited in the constructions) if (IsMover()) { auto* const mover = GetMoverSubComponent(); mover->mTimeBased = m_Path->movingPlatform.timeBasedMovement != 0; const auto start = m_Parent->HasVar(u"attached_path_start") ? m_Parent->GetVar(u"attached_path_start") : 0; m_StartWaypoint = start >= 0 && static_cast(start) < m_Path->pathWaypoints.size() ? start : 0; MoverProcessStateChange(m_StartWaypoint); const bool startOnLoad = !m_Parent->HasVar(u"startPathingOnLoad") || m_Parent->GetVar(u"startPathingOnLoad"); MoverStopAtWaypoint(startOnLoad); } else if (m_MoverSubComponentType == eMoverSubComponentType::rotater) { // LWOPlatformRotator: it only spins (rotX/rotY/rotZ at rate, its own path never arrives anywhere: its // HandleStoppedLogic 0x00bdc660 only keeps the angular velocity), while travelling const bool startOnLoad = !m_Parent->HasVar(u"startPathingOnLoad") || m_Parent->GetVar(u"startPathingOnLoad"); GetMoverSubComponent()->mState = startOnLoad ? ePlatformStateFlag::Travelling : ePlatformStateFlag::Stopped; } // Live constructed every moving platform with simple physics as keyframed auto* const simplePhysics = m_Parent->GetComponent(); if (simplePhysics && !m_Parent->HasVar(u"motionType")) simplePhysics->SetPhysicsMotionState(SimplePhysicsComponent::MOTION_TYPE_KEYFRAMED); } MovingPlatformComponent::~MovingPlatformComponent() = default; void MovingPlatformComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { // Live always constructed a simple mover with its starting point (the client builds its path from it), and // serialized it only when its state changed: no path, then the one subcomponent and the 0 bit ending the list if (m_SubComponents.size() == 1 && m_MoverSubComponentType == eMoverSubComponentType::simpleMover) { auto* const simpleMover = GetSimpleMoverSubComponent(); const bool dirty = bIsInitialUpdate || simpleMover->mDirtyStartingPoint || simpleMover->mDirtyState; outBitStream.Write(dirty); outBitStream.Write(bIsInitialUpdate); if (bIsInitialUpdate) outBitStream.Write0(); if (dirty) { outBitStream.Write1(); outBitStream.Write(m_MoverSubComponentType); simpleMover->Serialize(outBitStream, bIsInitialUpdate); outBitStream.Write0(); } return; } // LWOMovingPlatformComponent::Deserialize: whether subcomponents follow, whether the path changed (then whether // there is one: its name, starting waypoint and reverse), then the subcomponents. Live wrote the path with the // construction and the mover's state with every serialization. outBitStream.Write(true); outBitStream.Write(bIsInitialUpdate); if (bIsInitialUpdate) { const auto hasPath = !m_PathName.empty() && m_Path != nullptr; outBitStream.Write(hasPath); if (hasPath) { outBitStream.Write(m_PathName.size()); for (const auto& c : m_PathName) { outBitStream.Write(c); } outBitStream.Write(m_StartWaypoint); outBitStream.Write(false); } } // The subcomponents, each after a 1 bit with its type (a rotater sends a mover's data, both LWOPlatform's); a 0 bit // ends the list. A platform the client made none for gets none: it could not read one. for (const auto& subComponent : m_SubComponents) { outBitStream.Write1(); outBitStream.Write(subComponent->GetType()); subComponent->Serialize(outBitStream, bIsInitialUpdate); } outBitStream.Write0(); } void MovingPlatformComponent::OnQuickBuildInitilized() { // Live left a quick built simple mover (the NJ counterweights) where it was, only resending its state: scripts // send it on if (m_MoverSubComponentType == eMoverSubComponentType::simpleMover) { GetSimpleMoverSubComponent()->mDirtyState = true; return; } StopPathing(); } void MovingPlatformComponent::OnCompleteQuickBuild() { if (m_MoverSubComponentType == eMoverSubComponentType::simpleMover) { GetSimpleMoverSubComponent()->mDirtyState = true; return; } if (m_NoAutoStart) return; StartPathing(); } void MovingPlatformComponent::SetMovementState(eMovementPlatformState value) { if (m_MoverSubComponentType == eMoverSubComponentType::simpleMover) { auto* const simpleMover = GetSimpleMoverSubComponent(); simpleMover->mState = static_cast(value); simpleMover->mDirtyState = true; Game::entityManager->SerializeEntity(m_Parent); return; } if (!IsMover()) return; GetMoverSubComponent()->mState = static_cast(value); Game::entityManager->SerializeEntity(m_Parent); } // Sends the mover to a waypoint, stopping there when stopAtWaypoint: it turns round if the waypoint is behind it void MovingPlatformComponent::GotoWaypoint(uint32_t index, bool stopAtWaypoint) { if (m_MoverSubComponentType == eMoverSubComponentType::simpleMover) { SimpleMoverGotoWaypoint(static_cast(index)); return; } if (!IsMover() || index >= m_Path->pathWaypoints.size()) return; auto* const mover = GetMoverSubComponent(); const bool atWaypoint = (mover->mState & ePlatformStateFlag::ReachedDesiredWaypoint) != 0; if (atWaypoint && mover->mCurrentWaypointIndex == index) { // Already there mover->mDesiredWaypointIndex = -1; mover->mShouldStopAtDesiredWaypoint = false; if (stopAtWaypoint) MoverStopAtWaypoint(false); FireArrivalHooks(index, true, index == m_Path->pathWaypoints.size() - 1); return; } mover->mDesiredWaypointIndex = static_cast(index); mover->mShouldStopAtDesiredWaypoint = stopAtWaypoint; // LWOPlatform::SetMovingForwards / SetMovingBackwards: at a waypoint it sets off in the waypoint's direction; between // two it carries on if the waypoint is ahead, else turns round (the waypoint it was heading to becomes the one it // comes from) if (atWaypoint) { const bool reverse = index < mover->mCurrentWaypointIndex; if (reverse != mover->mInReverse) { mover->mInReverse = reverse; MoverProcessStateChange(mover->mCurrentWaypointIndex); } } else { const bool ahead = mover->mInReverse ? index <= mover->mNextWaypointIndex : index >= mover->mNextWaypointIndex; if (!ahead) { const float percent = mover->mPercentBetweenPoints; const auto heading = mover->mNextWaypointIndex; mover->mInReverse = !mover->mInReverse; mover->mNextWaypointIndex = mover->mCurrentWaypointIndex; mover->mCurrentWaypointIndex = heading; mover->mPercentBetweenPoints = 1.0f - std::min(percent, 1.0f); if (mover->mTimeBased) mover->mMoveTimeElapsed = mover->mCurrentSpeed * mover->mPercentBetweenPoints; else std::swap(mover->mCurrentSpeed, mover->mNextSpeed); } } MoverDepart(); } void MovingPlatformComponent::StartPathing() { if (m_MoverSubComponentType == eMoverSubComponentType::simpleMover) { // A simple mover's path is start to end: starting sends it to the other end SimpleMoverGotoWaypoint(GetSimpleMoverSubComponent()->mCurrentWaypointIndex == 0 ? 1 : 0); return; } if (m_MoverSubComponentType == eMoverSubComponentType::rotater) { // LWOPlatformRotator::StartLinearMovement: spinning again GetMoverSubComponent()->mState = ePlatformStateFlag::Travelling; Game::entityManager->SerializeEntity(m_Parent); return; } if (!IsMover()) return; m_PathingStopped = false; auto* const mover = GetMoverSubComponent(); if ((mover->mState & ePlatformStateFlag::Travelling) != 0) return; // LWOPlatform::ResumeTravelling: stopped between two waypoints it carries on; at a waypoint it first waits there // for the waypoint's wait (issue 964: the AG monument elevator waited about 2 s before rising) if ((mover->mState & ePlatformStateFlag::ReachedDesiredWaypoint) != 0) { MoverStopAtWaypoint(true); Game::entityManager->SerializeEntity(m_Parent); return; } MoverDepart(); } void MovingPlatformComponent::StopPathing() { if (m_MoverSubComponentType == eMoverSubComponentType::simpleMover) { // LWOPlatformSimpleMover::StopPlatform: it stops where it is, no longer travelling auto* const simpleMover = GetSimpleMoverSubComponent(); m_MoveGeneration++; simpleMover->mState = ePlatformStateFlag::Stopped; simpleMover->mDirtyState = true; Game::entityManager->SerializeEntity(m_Parent); return; } if (m_MoverSubComponentType == eMoverSubComponentType::rotater) { // LWOPlatformRotator::StopPlatform: no longer spinning GetMoverSubComponent()->mState = ePlatformStateFlag::Stopped; Game::entityManager->SerializeEntity(m_Parent); return; } if (!IsMover()) return; m_PathingStopped = true; auto* const mover = GetMoverSubComponent(); // LWOPlatform::StopPlatform: stopped where it is, not leaving after a wait mover->mState = (mover->mState & ~(ePlatformStateFlag::Travelling | ePlatformStateFlag::Waiting)) | ePlatformStateFlag::Stopped; mover->mDepartAfterWait = false; mover->mDesiredWaypointIndex = -1; mover->mShouldStopAtDesiredWaypoint = false; Game::entityManager->SerializeEntity(m_Parent); } bool MovingPlatformComponent::IsMover() const { return m_MoverSubComponentType == eMoverSubComponentType::mover && m_Path != nullptr && !m_Path->pathWaypoints.empty(); } // LWOPlatform::ProcessStateChange: the waypoint becomes the current one, the next follows from the path's behavior and // the direction (a bounce turns round at either end), with the two waypoints' speeds (GetWaypointSpeed) void MovingPlatformComponent::MoverProcessStateChange(const uint32_t index) { auto* const mover = GetMoverSubComponent(); const auto& waypoints = m_Path->pathWaypoints; const auto count = static_cast(waypoints.size()); const auto behavior = static_cast(m_Path->pathBehavior); const auto current = std::min(static_cast(index), count - 1); int32_t next{}; if (!mover->mInReverse) { next = current + 1; if (next >= count) { if (behavior == PathBehavior::Once) next = count - 1; else if (behavior == PathBehavior::Bounce) { next = count - 2; mover->mInReverse = true; } else next = 0; } } else { next = current - 1; if (next < 0) { if (behavior == PathBehavior::Once) next = 0; else if (behavior == PathBehavior::Bounce) { next = 1; mover->mInReverse = false; } else next = count - 1; } } next = std::clamp(next, 0, count - 1); mover->mCurrentWaypointIndex = current; mover->mNextWaypointIndex = next; mover->mPercentBetweenPoints = 0.0f; mover->mMoveTimeElapsed = 0.0f; mover->mPosition = waypoints[current].position; mover->mSegmentLength = Vector3::Distance(waypoints[current].position, waypoints[next].position); if (current == next) { mover->mCurrentSpeed = mover->mNextSpeed = 0.0f; } else if (mover->mTimeBased) { mover->mCurrentSpeed = waypoints[std::min(current, next)].speed; mover->mNextSpeed = 0.0f; } else { mover->mCurrentSpeed = waypoints[current].speed; mover->mNextSpeed = waypoints[next].speed; } } void MovingPlatformComponent::MoverStopAtWaypoint(const bool depart) { auto* const mover = GetMoverSubComponent(); const auto last = static_cast(m_Path->pathWaypoints.size() - 1); const bool atEnd = mover->mCurrentWaypointIndex == 0 || mover->mCurrentWaypointIndex == last; mover->mState = ePlatformStateFlag::Stopped | ePlatformStateFlag::ReachedDesiredWaypoint | (atEnd ? ePlatformStateFlag::ReachedFinalDestination : 0); mover->mIdleTimeElapsed = 0.0f; // LWOPlatform::SetWaitingTime: at least 0.1 s mover->mTimeToWait = std::max(0.1f, m_Path->pathWaypoints[mover->mCurrentWaypointIndex].movingPlatform.wait); mover->mDepartAfterWait = depart; } void MovingPlatformComponent::MoverDepart() { auto* const mover = GetMoverSubComponent(); mover->mState = ePlatformStateFlag::Travelling; mover->mIdleTimeElapsed = 0.0f; mover->mDepartAfterWait = false; Game::entityManager->SerializeEntity(m_Parent); } // LWOPlatform::ArrivedAtWaypoint and HandleWaypointArrived: at the next waypoint, it stops there if it was sent there // to stop, or at an end of a path run once (which turns it round); otherwise it waits there and goes on void MovingPlatformComponent::MoverArrive() { auto* const mover = GetMoverSubComponent(); const auto arrived = mover->mNextWaypointIndex; const auto last = static_cast(m_Path->pathWaypoints.size() - 1); const bool atEnd = arrived == last; const bool atDesired = mover->mDesiredWaypointIndex == static_cast(arrived); bool stop = false; if (atDesired) { stop = mover->mShouldStopAtDesiredWaypoint; mover->mShouldStopAtDesiredWaypoint = false; mover->mDesiredWaypointIndex = -1; } if ((arrived == 0 || atEnd) && static_cast(m_Path->pathBehavior) == PathBehavior::Once) { stop = true; mover->mInReverse = !mover->mInReverse; } MoverProcessStateChange(arrived); MoverStopAtWaypoint(!stop); Game::entityManager->SerializeEntity(m_Parent); FireArrivalHooks(arrived, atDesired, atEnd); } void MovingPlatformComponent::Run(const float deltaTime) { if (!IsMover()) return; auto* const mover = GetMoverSubComponent(); if ((mover->mState & ePlatformStateFlag::Travelling) != 0) { // LWOPlatform::Run / RunPlatform: time based paths count the time travelled; others move at the speed between // the two waypoints' (CalculateSpeed), the client by its physics if (mover->mTimeBased) { mover->mMoveTimeElapsed += deltaTime; mover->mPercentBetweenPoints = mover->mCurrentSpeed > 0.0f ? std::min(mover->mMoveTimeElapsed, mover->mCurrentSpeed) / mover->mCurrentSpeed : 1.0f; } else if (mover->mSegmentLength > 0.0f) { const float speed = mover->mCurrentSpeed + (mover->mNextSpeed - mover->mCurrentSpeed) * mover->mPercentBetweenPoints; mover->mPercentBetweenPoints += std::max(speed, 0.0f) * deltaTime / mover->mSegmentLength; } else { mover->mPercentBetweenPoints = 1.0f; } const auto& from = m_Path->pathWaypoints[mover->mCurrentWaypointIndex].position; const auto& to = m_Path->pathWaypoints[mover->mNextWaypointIndex].position; const float percent = std::min(mover->mPercentBetweenPoints, 1.0f); mover->mPosition = from + (to - from) * percent; if (mover->mPercentBetweenPoints >= 1.0f) MoverArrive(); } else if (mover->mDepartAfterWait) { mover->mIdleTimeElapsed += deltaTime; if (mover->mIdleTimeElapsed >= mover->mTimeToWait) MoverDepart(); } else if ((mover->mState & ePlatformStateFlag::ReachedDesiredWaypoint) != 0) { mover->mIdleTimeElapsed += deltaTime; } } void MovingPlatformComponent::FillResync(GameMessages::PlatformResync& resync) const { auto* const mover = GetMoverSubComponent(); resync.bReverse = mover->mInReverse; resync.bStopAtDesiredWaypoint = mover->mShouldStopAtDesiredWaypoint; resync.eState = static_cast(mover->mState); resync.fIdleTimeElapsed = mover->mIdleTimeElapsed; resync.fMoveTimeElapsed = mover->mMoveTimeElapsed; resync.fPercentBetweenPoints = std::min(mover->mPercentBetweenPoints, 1.0f); resync.iDesiredWaypointIndex = mover->mDesiredWaypointIndex; resync.iIndex = static_cast(mover->mCurrentWaypointIndex); resync.iNextIndex = static_cast(mover->mNextWaypointIndex); resync.ptUnexpectedLocation = mover->mPosition; const auto& from = m_Path->pathWaypoints[mover->mCurrentWaypointIndex].rotation; const auto& to = m_Path->pathWaypoints[mover->mNextWaypointIndex].rotation; const auto rotation = glm::slerp(glm::quat(from.w, from.x, from.y, from.z), glm::quat(to.w, to.x, to.y, to.z), resync.fPercentBetweenPoints); resync.qUnexpectedRotation = NiQuaternion(rotation.w, rotation.x, rotation.y, rotation.z); } void MovingPlatformComponent::SetSerialized(bool value) { m_Serialize = value; } bool MovingPlatformComponent::GetNoAutoStart() const { return m_NoAutoStart; } void MovingPlatformComponent::SetNoAutoStart(const bool value) { m_NoAutoStart = value; } void MovingPlatformComponent::WarpToWaypoint(size_t index) { if (m_MoverSubComponentType == eMoverSubComponentType::simpleMover) { auto* const simpleMover = GetSimpleMoverSubComponent(); m_MoveGeneration++; simpleMover->mCurrentWaypointIndex = index == 0 ? 0 : 1; simpleMover->mInReverse = simpleMover->mCurrentWaypointIndex == 1; simpleMover->mState = ePlatformStateFlag::Stopped | ePlatformStateFlag::ReachedDesiredWaypoint | ePlatformStateFlag::ReachedFinalDestination; simpleMover->mDirtyState = true; m_Parent->SetPosition(simpleMover->GetWaypointPosition(simpleMover->mCurrentWaypointIndex)); Game::entityManager->SerializeEntity(m_Parent); return; } if (!IsMover() || index >= m_Path->pathWaypoints.size()) return; const auto& waypoint = m_Path->pathWaypoints[index]; MoverProcessStateChange(static_cast(index)); MoverStopAtWaypoint(false); m_Parent->SetPosition(waypoint.position); m_Parent->SetRotation(waypoint.rotation); Game::entityManager->SerializeEntity(m_Parent); } size_t MovingPlatformComponent::GetLastWaypointIndex() const { if (m_MoverSubComponentType == eMoverSubComponentType::simpleMover) return 1; return m_Path->pathWaypoints.size() - 1; } PlatformSubComponent* MovingPlatformComponent::GetMoverSubComponent() const { if (m_SubComponents.empty() || m_MoverSubComponentType == eMoverSubComponentType::simpleMover) return nullptr; return m_SubComponents.front().get(); } SimpleMoverSubComponent* MovingPlatformComponent::GetSimpleMoverSubComponent() const { if (m_SubComponents.empty() || m_MoverSubComponentType != eMoverSubComponentType::simpleMover) return nullptr; return static_cast(m_SubComponents.front().get()); } // The client runs the two point path at |platformMove| / platformMoveTime, so it takes platformMoveTime seconds from // either end. Sending it travelling from its current waypoint towards the other one is all the client needs. void MovingPlatformComponent::SimpleMoverGotoWaypoint(int32_t index) { auto* const simpleMover = GetSimpleMoverSubComponent(); index = index <= 0 ? 0 : 1; const bool travelling = (simpleMover->mState & ePlatformStateFlag::Travelling) != 0; if (!travelling && index == simpleMover->mCurrentWaypointIndex) return; simpleMover->mCurrentWaypointIndex = index == 1 ? 0 : 1; simpleMover->mInReverse = index == 0; simpleMover->mState = ePlatformStateFlag::Travelling; simpleMover->mDirtyState = true; Game::entityManager->SerializeEntity(m_Parent); const auto generation = ++m_MoveGeneration; m_Parent->AddCallbackTimer(simpleMover->mMoveTime, [this, generation, index] { if (generation == m_MoveGeneration) OnSimpleMoverArrived(index); }); } void MovingPlatformComponent::OnSimpleMoverArrived(const int32_t index) { auto* const simpleMover = GetSimpleMoverSubComponent(); if (!simpleMover) return; // LWOPlatform::ArrivedAtWaypoint: both ends are the path's ends, and a path run once stops there simpleMover->mCurrentWaypointIndex = index; simpleMover->mState = ePlatformStateFlag::Stopped | ePlatformStateFlag::ReachedDesiredWaypoint | ePlatformStateFlag::ReachedFinalDestination; simpleMover->mInReverse = index == 1; simpleMover->mDirtyState = true; m_Parent->SetPosition(simpleMover->GetWaypointPosition(index)); Game::entityManager->SerializeEntity(m_Parent); FireArrivalHooks(index, true, index == 1); } // LWOPlatform::HandleWaypointArrived: Arrived, then ArrivedAtDesiredWaypoint at the waypoint it was sent to, then // PlatformAtLastWaypoint at either end of the path void MovingPlatformComponent::FireArrivalHooks(const uint32_t index, const bool atDesiredWaypoint, const bool atLastWaypoint) { auto* const script = m_Parent->GetScript(); script->OnWaypointReached(m_Parent, index); if (atDesiredWaypoint) script->OnArrivedAtDesiredWaypoint(m_Parent, index); if (atLastWaypoint) script->OnPlatformAtLastWaypoint(m_Parent); }