mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-11-15 00:07:13 +00:00
fix(moving platform): a mover keeps its velocity through a wait between the ends, as live
Live serialized a mover leaving a waypoint not where the waypoint is but past it: the GF shooting gallery subs 2 units on (speed 15), the FV dragons 5.33 (speed 40), each along the stretch they came by, and a dragon met 2.2 s into its 5 s wait was 88 units past its waypoint (1.219 of the next stretch). At either end of a path they left from the waypoint itself. It is the client's LWOPlatform at work, which the server ran too: - SetWaitingTime (0x00bdc860) takes the object's velocities away only when the platform has stopped or is at an end of its path, so through a wait in between the object goes on as it came; - the percent is how far the object is from the waypoint (CalculatePercentTravelledToWaypoint 0x00be9050); - UpdateVelocities (0x00bd4330) on leaving turns it the next stretch's way from where it is, and it arrives when it passes the next waypoint (CloseToNextWaypoint 0x00bd4140), where ProcessStateChange (0x00cce380) puts it at the waypoint; - a platform that has not stopped looks whether it arrived while it waits too (HandleStoppedLogic 0x00bd16a0): the dragons were at waypoint 5 without having left 4. The server's mover now moves by its velocity in the same way, so the state it sends on leaving carries the place and percent live sent. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
@@ -363,6 +363,7 @@ void MovingPlatformComponent::GotoWaypoint(uint32_t index, bool stopAtWaypoint)
|
||||
mover->mShouldStopAtDesiredWaypoint = false;
|
||||
if (stopAtWaypoint) {
|
||||
mover->mState = (mover->mState & ~(ePlatformStateFlag::Waiting | ePlatformStateFlag::Travelling)) | ePlatformStateFlag::Stopped;
|
||||
mover->mVelocity = NiPoint3Constant::ZERO;
|
||||
}
|
||||
FireArrivalHooks(index, true, index == m_Path->pathWaypoints.size() - 1);
|
||||
return;
|
||||
@@ -444,6 +445,7 @@ void MovingPlatformComponent::StopPathing() {
|
||||
auto* const mover = GetMoverSubComponent();
|
||||
// LWOPlatform::StopPlatform 0x00c86b70: stopped where it is (the client stops on the stopped bit, 4)
|
||||
mover->mState = (mover->mState & ~(ePlatformStateFlag::Waiting | ePlatformStateFlag::Travelling)) | ePlatformStateFlag::Stopped;
|
||||
mover->mVelocity = NiPoint3Constant::ZERO;
|
||||
mover->mDesiredWaypointIndex = -1;
|
||||
mover->mShouldStopAtDesiredWaypoint = false;
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
@@ -507,6 +509,24 @@ void MovingPlatformComponent::MoverSetWaitingTime(const float waitTime, const fl
|
||||
mover->mTimeToWait = std::max(0.1f, waitTime);
|
||||
mover->mIdleTimeElapsed = idleTimeElapsed;
|
||||
mover->mWaitSet = true;
|
||||
// Only at an end of the path, or stopped, does the object lose its velocities here. Waiting at a waypoint in between
|
||||
// it keeps them: live's platforms went on as they came for the wait (the GF shooting gallery subs 2 units past each
|
||||
// waypoint, the FV dragons 5.33; a dragon met 2.2 s into a 5 s wait was 88 past its waypoint).
|
||||
if ((mover->mState & (ePlatformStateFlag::ReachedFinalDestination | ePlatformStateFlag::Stopped)) != 0) mover->mVelocity = NiPoint3Constant::ZERO;
|
||||
}
|
||||
|
||||
void MovingPlatformComponent::MoverUpdateVelocities() {
|
||||
auto* const mover = GetMoverSubComponent();
|
||||
auto direction = m_Path->pathWaypoints[mover->mNextWaypointIndex].position - m_Path->pathWaypoints[mover->mCurrentWaypointIndex].position;
|
||||
const float distance = direction.Length();
|
||||
if (distance > 0.0f) direction = direction / distance;
|
||||
// LWOPlatform::GetSpeed 0x00bd40e0: a time based path's mCurrentSpeed is the seconds to the next waypoint; else from
|
||||
// the waypoint's speed to the next one's along the way
|
||||
float speed{};
|
||||
if (mover->mTimeBased) speed = mover->mCurrentSpeed > 0.0f ? distance / mover->mCurrentSpeed : 0.0f;
|
||||
else speed = mover->mCurrentSpeed + (mover->mNextSpeed - mover->mCurrentSpeed) * mover->mPercentBetweenPoints;
|
||||
mover->mVelocity = direction * speed;
|
||||
mover->mState &= ePlatformStateFlag::MovementMask;
|
||||
}
|
||||
|
||||
void MovingPlatformComponent::MoverResumeTravelling() {
|
||||
@@ -516,8 +536,7 @@ void MovingPlatformComponent::MoverResumeTravelling() {
|
||||
mover->mState = (mover->mState & ~(ePlatformStateFlag::Waiting | ePlatformStateFlag::Stopped)) | ePlatformStateFlag::Travelling;
|
||||
}
|
||||
if ((mover->mState & ePlatformStateFlag::Travelling) != 0) {
|
||||
// LWOPlatform::UpdateVelocities
|
||||
mover->mState &= ePlatformStateFlag::MovementMask;
|
||||
MoverUpdateVelocities();
|
||||
} else {
|
||||
mover->mState = (mover->mState & ~(ePlatformStateFlag::Travelling | ePlatformStateFlag::Stopped)) | ePlatformStateFlag::Waiting;
|
||||
MoverSetWaitingTime(mover->mWaitTime, 0.0f);
|
||||
@@ -564,6 +583,7 @@ void MovingPlatformComponent::MoverArrivedAtWaypoint() {
|
||||
}
|
||||
if (stopAtDesiredWaypoint || turnedAtEnd) {
|
||||
mover->mState = (mover->mState & ~(ePlatformStateFlag::Waiting | ePlatformStateFlag::Travelling)) | ePlatformStateFlag::Stopped;
|
||||
mover->mVelocity = NiPoint3Constant::ZERO;
|
||||
} else {
|
||||
mover->mState = (mover->mState & ~(ePlatformStateFlag::Waiting | ePlatformStateFlag::Stopped)) | ePlatformStateFlag::Travelling;
|
||||
}
|
||||
@@ -718,27 +738,27 @@ void MovingPlatformComponent::Run(const float deltaTime) {
|
||||
if (!IsMover()) return;
|
||||
auto* mover = GetMoverSubComponent();
|
||||
UpdateActive(*mover);
|
||||
if (!mover->mActive || mover->mState == 0) return;
|
||||
if (!mover->mActive) {
|
||||
// RebuildNotifyState: an open quick build's platform loses its velocities
|
||||
mover->mVelocity = NiPoint3Constant::ZERO;
|
||||
return;
|
||||
}
|
||||
if (mover->mState == 0) return;
|
||||
|
||||
const bool travelling = (mover->mState & ePlatformStateFlag::Travelling) != 0;
|
||||
const auto& from = m_Path->pathWaypoints[mover->mCurrentWaypointIndex].position;
|
||||
const auto& to = m_Path->pathWaypoints[mover->mNextWaypointIndex].position;
|
||||
if (mover->mTimeBased && travelling) mover->mMoveTimeElapsed += deltaTime;
|
||||
// The object's physics: it goes at its velocity, travelling or not
|
||||
mover->mPosition = mover->mPosition + mover->mVelocity * deltaTime;
|
||||
|
||||
if (mover->mTimeBased) {
|
||||
// LWOPlatform::TimeBasedCalculatePercentToNextWaypoint 0x00bd4090: mCurrentSpeed is the seconds to the next waypoint
|
||||
if (travelling) mover->mMoveTimeElapsed += deltaTime;
|
||||
mover->mMoveTimeElapsed = std::min(mover->mMoveTimeElapsed, mover->mCurrentSpeed);
|
||||
mover->mPercentBetweenPoints = mover->mCurrentSpeed > 0.0f ? mover->mMoveTimeElapsed / mover->mCurrentSpeed : 1.0f;
|
||||
if (travelling) mover->mPosition = from + (to - from) * mover->mPercentBetweenPoints;
|
||||
} else if (travelling) {
|
||||
// LWOPlatform::GetSpeed 0x00bd40e0: from the waypoint's speed to the next one's along the way
|
||||
if (mover->mSegmentLength > 0.0f) {
|
||||
const float speed = mover->mCurrentSpeed + (mover->mNextSpeed - mover->mCurrentSpeed) * mover->mPercentBetweenPoints;
|
||||
const float travelled = mover->mPercentBetweenPoints * mover->mSegmentLength + std::max(speed, 0.0f) * deltaTime;
|
||||
mover->mPercentBetweenPoints = std::min(travelled / mover->mSegmentLength, 1.0f);
|
||||
} else {
|
||||
mover->mPercentBetweenPoints = 1.0f;
|
||||
}
|
||||
mover->mPosition = from + (to - from) * mover->mPercentBetweenPoints;
|
||||
} else {
|
||||
// LWOPlatform::CalculatePercentTravelledToWaypoint 0x00be9050: how far the object is from the current waypoint,
|
||||
// over the way to the next (live sent 1.219 for a dragon that far past its waypoint)
|
||||
const auto& from = m_Path->pathWaypoints[mover->mCurrentWaypointIndex].position;
|
||||
mover->mPercentBetweenPoints = mover->mSegmentLength > 0.0f ? Vector3::Distance(from, mover->mPosition) / mover->mSegmentLength : 0.0f;
|
||||
}
|
||||
|
||||
MoveObject();
|
||||
@@ -755,9 +775,11 @@ void MovingPlatformComponent::Run(const float deltaTime) {
|
||||
}
|
||||
if (doneWaiting) {
|
||||
mover->mState = (mover->mState & ~(ePlatformStateFlag::Waiting | ePlatformStateFlag::Stopped)) | ePlatformStateFlag::Travelling;
|
||||
} else if (!stopped && travelling) {
|
||||
// LWOPlatform::HandleStoppedLogic 0x00bd16a0. The client also checks while it waits, which only matters where
|
||||
// two waypoints are in one place: there it would arrive every frame.
|
||||
} else if (!stopped && (travelling || mover->mSegmentLength > 0.0f)) {
|
||||
// LWOPlatform::HandleStoppedLogic 0x00bd16a0: a platform that has not stopped looks whether it arrived, also while
|
||||
// it waits. Live's FV dragons, going on as they came through the 5 s wait at their waypoint 4, were at waypoint 5
|
||||
// (waiting there, then going on in the direction they came to 4 in) without ever having left 4. Where two
|
||||
// waypoints are in one place the client arrives every frame of the wait; here only a travelling one arrives there.
|
||||
MoverArrivedAtWaypoint();
|
||||
// A script told of the arrival may have put the platform on another path
|
||||
mover = GetMoverSubComponent();
|
||||
@@ -765,8 +787,8 @@ void MovingPlatformComponent::Run(const float deltaTime) {
|
||||
}
|
||||
|
||||
if ((mover->mState & ePlatformStateFlag::Travelling) != 0 && (doneWaiting || mover->mCurrentSpeed != mover->mNextSpeed)) {
|
||||
mover->mState &= ePlatformStateFlag::MovementMask;
|
||||
// Live serialized a platform leaving its waypoint
|
||||
MoverUpdateVelocities();
|
||||
// Live serialized a platform leaving its waypoint, where it then was: as far past the waypoint as the wait took it
|
||||
if (doneWaiting) Game::entityManager->SerializeEntity(m_Parent);
|
||||
}
|
||||
if (IsMover()) MoveObject();
|
||||
@@ -820,6 +842,7 @@ void MovingPlatformComponent::WarpToWaypoint(size_t index) {
|
||||
auto* const mover = GetMoverSubComponent();
|
||||
const bool atEnd = index == 0 || index == m_Path->pathWaypoints.size() - 1;
|
||||
mover->mState = ePlatformStateFlag::Stopped | ePlatformStateFlag::ReachedDesiredWaypoint | (atEnd ? ePlatformStateFlag::ReachedFinalDestination : 0);
|
||||
mover->mVelocity = NiPoint3Constant::ZERO;
|
||||
|
||||
m_Parent->SetPosition(waypoint.position);
|
||||
m_Parent->SetRotation(waypoint.rotation);
|
||||
|
||||
@@ -95,8 +95,12 @@ public:
|
||||
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)
|
||||
// Where it is now: its object's position, which its linear velocity moves
|
||||
NiPoint3 mPosition{};
|
||||
// The linear velocity LWOPlatform::SetLinearVelocity 0x00be9200 gave its object when it last set off (or last looked,
|
||||
// between waypoints of different speeds). It is only taken away where the platform stops and at either end of the
|
||||
// path (SetWaitingTime 0x00bdc860), so through a wait at a waypoint in between the object goes on as it came.
|
||||
NiPoint3 mVelocity{};
|
||||
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
|
||||
@@ -373,6 +377,8 @@ private:
|
||||
bool UpdateActive(PlatformSubComponent& platform);
|
||||
// LWOPlatform::SetWaitingTime: starts a wait of at least 0.1 s
|
||||
void MoverSetWaitingTime(float waitTime, float idleTimeElapsed);
|
||||
// LWOPlatform::UpdateVelocities 0x00bd4330: the velocity towards the next waypoint at the speed for where it is
|
||||
void MoverUpdateVelocities();
|
||||
// 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
|
||||
|
||||
@@ -517,3 +517,61 @@ TEST_F(MovingPlatformComponentTest, RotatorTurnsItsObject) {
|
||||
EXPECT_NEAR(std::abs(entity.GetRotation().y), 1.0f, 0.001f);
|
||||
EXPECT_EQ(entity.GetPosition(), NiPoint3(1.0f, 2.0f, 3.0f));
|
||||
}
|
||||
|
||||
// Live's movers at a waypoint between the ends of their path: the object is put at the waypoint and serialized waiting
|
||||
// there (9), keeps the velocity it came with for the wait (LWOPlatform::SetWaitingTime 0x00bdc860 only takes it away at
|
||||
// an end or when stopped), and is serialized leaving (2) from where that took it, with the part of the next stretch it
|
||||
// is from the waypoint: the GF shooting gallery subs (speed 15, no wait, so the shortest, 0.1 s: two of live's frames)
|
||||
// left 2 units past each waypoint. From there it goes the next stretch's direction and arrives as it passes the
|
||||
// waypoint. At the end of the path it stops where the waypoint is.
|
||||
TEST_F(MovingPlatformComponentTest, MoverKeepsItsVelocityThroughAWaitLikeLive) {
|
||||
Path path;
|
||||
path.pathType = PathType::MovingPlatform;
|
||||
path.pathName = "TestCornerPath";
|
||||
path.pathBehavior = PathBehavior::Once;
|
||||
PathWaypoint first;
|
||||
first.position = NiPoint3(0.0f, 0.0f, 0.0f);
|
||||
first.speed = 15.0f;
|
||||
PathWaypoint second = first;
|
||||
second.position = NiPoint3(30.0f, 0.0f, 0.0f);
|
||||
PathWaypoint third = first;
|
||||
third.position = NiPoint3(30.0f, 0.0f, 30.0f);
|
||||
path.pathWaypoints = { first, second, third };
|
||||
const_cast<Zone*>(Game::zoneManager->GetZone())->AddPath(path);
|
||||
|
||||
info.pos = NiPoint3(0.0f, 0.0f, 0.0f);
|
||||
Entity entity(288300744895900114, info);
|
||||
entity.AddComponent<SimplePhysicsComponent>(-1);
|
||||
auto* const platform = entity.AddComponent<MovingPlatformComponent>(-1, "TestCornerPath");
|
||||
auto* const mover = platform->GetMoverSubComponent();
|
||||
platform->Run(0.11f);
|
||||
EXPECT_EQ(mover->mState, 2u);
|
||||
EXPECT_EQ(mover->mPercentBetweenPoints, 0.0f);
|
||||
|
||||
platform->Run(2.0f);
|
||||
EXPECT_EQ(mover->mState, 9u);
|
||||
EXPECT_EQ(mover->mCurrentWaypointIndex, 1u);
|
||||
EXPECT_EQ(mover->mPosition, NiPoint3(30.0f, 0.0f, 0.0f));
|
||||
EXPECT_EQ(mover->mPercentBetweenPoints, 0.0f);
|
||||
|
||||
platform->Run(1.0f / 15.0f);
|
||||
EXPECT_EQ(mover->mState, 9u);
|
||||
EXPECT_NEAR(mover->mPosition.x, 31.0f, 0.001f);
|
||||
platform->Run(1.0f / 15.0f);
|
||||
EXPECT_EQ(mover->mState, 2u);
|
||||
EXPECT_NEAR(mover->mPosition.x, 32.0f, 0.001f);
|
||||
EXPECT_NEAR(mover->mPosition.z, 0.0f, 0.001f);
|
||||
EXPECT_NEAR(mover->mPercentBetweenPoints, 2.0f / 30.0f, 0.001f);
|
||||
EXPECT_NEAR(entity.GetPosition().x, 32.0f, 0.001f);
|
||||
|
||||
platform->Run(1.0f);
|
||||
EXPECT_NEAR(mover->mPosition.x, 32.0f, 0.001f);
|
||||
EXPECT_NEAR(mover->mPosition.z, 15.0f, 0.001f);
|
||||
platform->Run(1.0f);
|
||||
EXPECT_EQ(mover->mState, 28u);
|
||||
EXPECT_EQ(mover->mCurrentWaypointIndex, 2u);
|
||||
EXPECT_EQ(mover->mPosition, NiPoint3(30.0f, 0.0f, 30.0f));
|
||||
platform->Run(1.0f);
|
||||
EXPECT_EQ(mover->mPosition, NiPoint3(30.0f, 0.0f, 30.0f));
|
||||
EXPECT_EQ(entity.GetPosition(), NiPoint3(30.0f, 0.0f, 30.0f));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user