diff --git a/dGame/dComponents/ModelComponent.cpp b/dGame/dComponents/ModelComponent.cpp index 2b4aa0231..c403b92fc 100644 --- a/dGame/dComponents/ModelComponent.cpp +++ b/dGame/dComponents/ModelComponent.cpp @@ -78,6 +78,12 @@ bool ModelComponent::OnRequestUse(GameMessages::RequestUse& requestUse) { void ModelComponent::Update(float deltaTime) { if (m_IsPaused) return; + // Arrived once this frame's movement reached or passed the target + if (m_Move.target && (*m_Move.target - m_Parent->GetPosition()).DotProduct(m_Move.targetDirection) <= 0.0f) { + m_Parent->SetPosition(*m_Move.target); + m_Move.target.reset(); + } + for (auto& behavior : m_Behaviors) { behavior.Update(deltaTime, *this); } @@ -297,6 +303,7 @@ bool ModelComponent::TryStartMove(const int axis, const float direction, const f if (axis < 0 || axis > 2 || direction == 0.0f || speed <= 0.0f) return false; if (m_Move.velocity[axis] != 0.0f) return false; + m_Move.target.reset(); m_Move.velocity[axis] = std::copysign(speed, direction); return true; } @@ -306,6 +313,13 @@ void ModelComponent::StopMove(const int axis) { m_Move.velocity[axis] = 0.0f; } +void ModelComponent::StartMoveTo(const NiPoint3& target, const float speed) { + m_Move.velocity = NiPoint3Constant::ZERO; + m_Move.interruptCount++; + m_Move.target = target; + m_Move.targetSpeed = speed; +} + void ModelComponent::SyncLinearVelocity() { const auto& rotation = m_Parent->GetRotation(); m_Move.basis = { QuatUtils::Right(rotation), QuatUtils::Up(rotation), QuatUtils::Forward(rotation) }; @@ -313,6 +327,13 @@ void ModelComponent::SyncLinearVelocity() { NiPoint3 velocity = NiPoint3Constant::ZERO; for (int axis = 0; axis < 3; axis++) velocity += m_Move.basis[axis] * m_Move.velocity[axis]; + if (m_Move.target) { + const auto toTarget = *m_Move.target - m_Parent->GetPosition(); + const float distance = toTarget.Length(); + m_Move.targetDirection = distance > 0.0f ? toTarget / distance : NiPoint3Constant::ZERO; + velocity += m_Move.targetDirection * m_Move.targetSpeed; + } + // Leave velocity alone unless a move owns it, e.g. pets are driven elsewhere const bool isMoving = velocity != NiPoint3Constant::ZERO; if (!isMoving && !m_Move.wasMoving) return; diff --git a/dGame/dComponents/ModelComponent.h b/dGame/dComponents/ModelComponent.h index 7947890e2..153f533b0 100644 --- a/dGame/dComponents/ModelComponent.h +++ b/dGame/dComponents/ModelComponent.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "dCommonVars.h" #include "RakNetTypes.h" @@ -141,7 +142,7 @@ public: void Resume(); // Attempts to claim a local axis (0 = right, 1 = up, 2 = forward) for movement in direction (+1 or -1) at speed units per second. - // Returns false if the axis is already controlled by a behavior. + // Returns false if the axis is already controlled by a behavior. Cancels any active StartMoveTo. bool TryStartMove(const int axis, const float direction, const float speed); // Releases the local axis so another behavior can move along it. @@ -150,6 +151,15 @@ public: // World space direction of the local axis used for the most recently applied velocity. const NiPoint3& GetMoveAxis(const int axis) const { return m_Move.basis[axis]; } + // Interrupts all active moves and moves in a straight line to target at speed units per second. + void StartMoveTo(const NiPoint3& target, const float speed); + + // Whether a StartMoveTo move has yet to arrive or be overridden. + bool IsMovingToTarget() const noexcept { return m_Move.target.has_value(); } + + // Changes whenever active moves are interrupted, so strips can tell their move was cancelled. + uint32_t GetMoveInterruptCount() const noexcept { return m_Move.interruptCount; } + // Attempts to claim a world axis (0 = x, 1 = y, 2 = z) for rotation in direction (+1 or -1) at the given behavior speed. // Returns false if the axis is already controlled by a behavior. bool TryStartRotation(const int axis, const float direction, const float speed); @@ -204,6 +214,18 @@ private: // Whether the last velocity update came from an active move bool wasMoving{ false }; + + // World position of an active StartMoveTo move + std::optional target; + + // Units per second of the move to target + float targetSpeed{}; + + // Direction to target as of the last velocity update, zero once there + NiPoint3 targetDirection{}; + + // Incremented each time StartMoveTo interrupts the active moves + uint32_t interruptCount{}; }; // Sends the client-side angular velocity for the currently active rotation axes. diff --git a/dGame/dPropertyBehaviors/Strip.cpp b/dGame/dPropertyBehaviors/Strip.cpp index bebd0f7d8..57d7bdf9a 100644 --- a/dGame/dPropertyBehaviors/Strip.cpp +++ b/dGame/dPropertyBehaviors/Strip.cpp @@ -111,6 +111,7 @@ void Strip::HandleMsg(GameMessages::ResetModelToDefaults& msg) { m_RotationProgress = 0.0f; m_Speed = DEFAULT_SPEED; m_PausedFromOnTimer = false; + m_MovingToStart = false; } void Strip::OnChatMessageReceived(const std::string& sMessage) { @@ -180,6 +181,7 @@ void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent, Up const bool isMoveLeft = nextActionType == "MoveLeft"; if (modelComponent.TryStartMove(0, isMoveLeft ? -1.0f : 1.0f, m_Speed)) { m_PreviousFramePosition = entity.GetPosition(); + m_MoveInterruptCount = modelComponent.GetMoveInterruptCount(); m_InActionTranslation.x = isMoveLeft ? -number : number; } } else if (nextActionType == "FlyUp" || nextActionType == "FlyDown") { @@ -187,6 +189,7 @@ void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent, Up const bool isFlyDown = nextActionType == "FlyDown"; if (modelComponent.TryStartMove(1, isFlyDown ? -1.0f : 1.0f, m_Speed)) { m_PreviousFramePosition = entity.GetPosition(); + m_MoveInterruptCount = modelComponent.GetMoveInterruptCount(); m_InActionTranslation.y = isFlyDown ? -number : number; } } else if (nextActionType == "MoveForward" || nextActionType == "MoveBackward") { @@ -194,6 +197,7 @@ void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent, Up const bool isMoveBackward = nextActionType == "MoveBackward"; if (modelComponent.TryStartMove(2, isMoveBackward ? -1.0f : 1.0f, m_Speed)) { m_PreviousFramePosition = entity.GetPosition(); + m_MoveInterruptCount = modelComponent.GetMoveInterruptCount(); m_InActionTranslation.z = isMoveBackward ? -number : number; } } @@ -228,6 +232,9 @@ void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent, Up else if (nextActionType == "SetSpeed") { // Floored so a move or rotation can never stall forever m_Speed = std::max(static_cast(number), MIN_SPEED); + } else if (nextActionType == "MoveBackToStart") { + modelComponent.StartMoveTo(modelComponent.GetOriginalPosition(), m_Speed); + m_MovingToStart = true; } /* END Navigation */ @@ -325,6 +332,14 @@ void Strip::RemoveStates(ModelComponent& modelComponent) const { } bool Strip::CheckMovement(float deltaTime, ModelComponent& modelComponent) { + if (m_MovingToStart) { + if (modelComponent.IsMovingToTarget()) return false; + m_MovingToStart = false; + } + + // A MoveBackToStart cancelled our move, so skip to the next action + if (m_MoveInterruptCount != modelComponent.GetMoveInterruptCount()) m_InActionTranslation = NiPoint3Constant::ZERO; + auto& entity = *modelComponent.GetParent(); const auto& currentPos = entity.GetPosition(); const auto diff = currentPos - m_PreviousFramePosition; diff --git a/dGame/dPropertyBehaviors/Strip.h b/dGame/dPropertyBehaviors/Strip.h index 24ee55b64..e9ace639c 100644 --- a/dGame/dPropertyBehaviors/Strip.h +++ b/dGame/dPropertyBehaviors/Strip.h @@ -79,6 +79,12 @@ private: // The signed degrees the current rotation action has progressed so far float m_RotationProgress{ 0.0f }; + // Whether this strip is waiting on a MoveBackToStart to arrive + bool m_MovingToStart{ false }; + + // The model's move interrupt count when this strip's current move started + uint32_t m_MoveInterruptCount{}; + static constexpr float DEFAULT_SPEED = 3.0f; static constexpr float MIN_SPEED = 0.1f;