mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-05-14 03:15:05 +00:00
Refactor MovingPlatformComponent to support subcomponents for movement and rotation
- Introduced PlatformSubComponent as a base class for platform movement logic. - Added MoverSubComponent for standard path-following behavior. - Implemented SimpleMoverSubComponent for auto-generating two-waypoint paths. - Created RotatorSubComponent to handle angular velocity and rotation along paths. - Updated MovingPlatformComponent to manage multiple subcomponents and their states. - Modified serialization and update logic to accommodate new subcomponent architecture. - Adjusted GameMessages to include additional parameters for platform state synchronization. - Enhanced SimplePhysicsComponent to prevent double movement when on a moving platform. - Added new CMakeLists.txt for organizing MovingPlatformComponent files.
This commit is contained in:
7
dGame/dComponents/MovingPlatformComponent/CMakeLists.txt
Normal file
7
dGame/dComponents/MovingPlatformComponent/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
set(DGAME_DCOMPONENTS_MOVINGPLATFORMCOMPONENT
|
||||
"PlatformSubComponent.cpp"
|
||||
"MoverSubComponent.cpp"
|
||||
"SimpleMoverSubComponent.cpp"
|
||||
"RotatorSubComponent.cpp"
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
#include "MoverSubComponent.h"
|
||||
|
||||
MoverSubComponent::MoverSubComponent(Entity* parentEntity, const Path* path)
|
||||
: PlatformSubComponent(parentEntity, path) {
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef MOVERSUBCOMPONENT_H
|
||||
#define MOVERSUBCOMPONENT_H
|
||||
|
||||
#include "PlatformSubComponent.h"
|
||||
|
||||
/**
|
||||
* Standard mover - follows a pre-defined path from zone data.
|
||||
* Corresponds to client LWOPlatformMover (type 4).
|
||||
*/
|
||||
class MoverSubComponent final : public PlatformSubComponent {
|
||||
public:
|
||||
MoverSubComponent(Entity* parentEntity, const Path* path);
|
||||
|
||||
private:
|
||||
bool m_AllowPosSnap = true;
|
||||
float m_MaxLerpDistance = 6.0f;
|
||||
};
|
||||
|
||||
#endif // MOVERSUBCOMPONENT_H
|
||||
@@ -0,0 +1,484 @@
|
||||
#include "PlatformSubComponent.h"
|
||||
|
||||
#include "BitStream.h"
|
||||
#include "BitStreamUtils.h"
|
||||
#include "Entity.h"
|
||||
#include "Game.h"
|
||||
#include "dServer.h"
|
||||
#include "GameMessages.h"
|
||||
#include "CppScripts.h"
|
||||
#include "SimplePhysicsComponent.h"
|
||||
#include "Zone.h"
|
||||
#include "MessageType/Client.h"
|
||||
#include "MessageType/Game.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
PlatformSubComponent::PlatformSubComponent(Entity* parentEntity, const Path* path)
|
||||
: m_ParentEntity(parentEntity)
|
||||
, m_Path(path) {
|
||||
|
||||
if (m_Path) {
|
||||
m_TimeBasedMovement = m_Path->movingPlatform.timeBasedMovement != 0;
|
||||
}
|
||||
|
||||
m_Position = parentEntity ? parentEntity->GetPosition() : NiPoint3{};
|
||||
}
|
||||
|
||||
void PlatformSubComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) {
|
||||
outBitStream.Write<bool>(true);
|
||||
|
||||
outBitStream.Write<uint32_t>(GetSerializedState());
|
||||
outBitStream.Write<int32_t>(m_DesiredWaypointIndex);
|
||||
outBitStream.Write<bool>(m_ShouldStopAtDesiredWaypoint);
|
||||
outBitStream.Write<bool>(m_InReverse);
|
||||
|
||||
outBitStream.Write<float>(m_PercentBetweenPoints);
|
||||
|
||||
outBitStream.Write<float>(m_Position.x);
|
||||
outBitStream.Write<float>(m_Position.y);
|
||||
outBitStream.Write<float>(m_Position.z);
|
||||
|
||||
outBitStream.Write<uint32_t>(m_CurrentWaypointIndex);
|
||||
outBitStream.Write<uint32_t>(m_NextWaypointIndex);
|
||||
|
||||
outBitStream.Write<float>(m_IdleTimeElapsed);
|
||||
outBitStream.Write<float>(m_MoveTimeElapsed);
|
||||
}
|
||||
|
||||
uint32_t PlatformSubComponent::GetSerializedState() const {
|
||||
if (m_State & PlatformState::Stopped) return PlatformState::StoppedSerialized;
|
||||
if (m_State & PlatformState::Travelling) return PlatformState::MovingSerialized;
|
||||
return PlatformState::StationarySerialized;
|
||||
}
|
||||
|
||||
void PlatformSubComponent::Update(float deltaTime, bool& dirtyOut) {
|
||||
if (!m_Active) return;
|
||||
if (m_State == 0) return;
|
||||
if (!m_Path || m_Path->pathWaypoints.empty()) return;
|
||||
|
||||
if (IncrementWaitingTime(deltaTime)) {
|
||||
StartTravelling();
|
||||
dirtyOut = true;
|
||||
}
|
||||
|
||||
if (m_State & PlatformState::Travelling) {
|
||||
UpdatePositionAlongPath(deltaTime);
|
||||
|
||||
bool arrived = false;
|
||||
if (m_TimeBasedMovement) {
|
||||
arrived = m_TravelTime > 0.0f && std::abs(m_TravelTime - m_MoveTimeElapsed) < 0.001f;
|
||||
} else {
|
||||
arrived = CloseToNextWaypoint();
|
||||
}
|
||||
|
||||
if (arrived) {
|
||||
ArrivedAtWaypoint(dirtyOut);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Movement control ---
|
||||
|
||||
void PlatformSubComponent::StartPathing() {
|
||||
if (!m_Path || m_Path->pathWaypoints.empty()) return;
|
||||
|
||||
m_Active = true;
|
||||
SetupWaypointSegment(m_CurrentWaypointIndex);
|
||||
|
||||
m_State = PlatformState::Waiting | PlatformState::Stopped;
|
||||
m_IdleTimeElapsed = 0.0f;
|
||||
m_MoveTimeElapsed = 0.0f;
|
||||
m_PercentBetweenPoints = 0.0f;
|
||||
m_HasStartedTravelling = false;
|
||||
}
|
||||
|
||||
void PlatformSubComponent::StopPathing() {
|
||||
m_State = PlatformState::Stopped;
|
||||
m_DesiredWaypointIndex = -1;
|
||||
m_ShouldStopAtDesiredWaypoint = false;
|
||||
m_MoveTimeElapsed = 0.0f;
|
||||
m_HasStartedTravelling = false;
|
||||
|
||||
ZeroPhysicsVelocity();
|
||||
}
|
||||
|
||||
void PlatformSubComponent::GotoWaypoint(uint32_t index, bool stopAtWaypoint) {
|
||||
m_DesiredWaypointIndex = static_cast<int32_t>(index);
|
||||
m_NextWaypointIndex = index;
|
||||
m_ShouldStopAtDesiredWaypoint = stopAtWaypoint;
|
||||
|
||||
StartPathing();
|
||||
}
|
||||
|
||||
void PlatformSubComponent::WarpToWaypoint(size_t index) {
|
||||
if (!m_Path || index >= m_Path->pathWaypoints.size()) return;
|
||||
|
||||
const auto& waypoint = m_Path->pathWaypoints[index];
|
||||
m_Position = waypoint.position;
|
||||
m_CurrentWaypointIndex = static_cast<uint32_t>(index);
|
||||
m_PercentBetweenPoints = 0.0f;
|
||||
m_MoveTimeElapsed = 0.0f;
|
||||
|
||||
if (m_ParentEntity) {
|
||||
m_ParentEntity->SetPosition(waypoint.position);
|
||||
m_ParentEntity->SetRotation(waypoint.rotation);
|
||||
}
|
||||
}
|
||||
|
||||
size_t PlatformSubComponent::GetLastWaypointIndex() const {
|
||||
if (!m_Path || m_Path->pathWaypoints.empty()) return 0;
|
||||
return m_Path->pathWaypoints.size() - 1;
|
||||
}
|
||||
|
||||
// --- Waypoint segment setup (mirrors client ProcessStateChange) ---
|
||||
|
||||
void PlatformSubComponent::SetupWaypointSegment(uint32_t waypointIndex) {
|
||||
if (!m_Path || m_Path->pathWaypoints.empty()) return;
|
||||
|
||||
m_CurrentWaypointIndex = waypointIndex;
|
||||
|
||||
const auto& currentWP = m_Path->pathWaypoints[m_CurrentWaypointIndex];
|
||||
m_CurrentWaypointPosition = currentWP.position;
|
||||
m_CurrentWaypointRotation = currentWP.rotation;
|
||||
m_WaitTime = currentWP.movingPlatform.wait;
|
||||
m_Position = currentWP.position;
|
||||
|
||||
bool changedDirection = false;
|
||||
if (!m_InReverse) {
|
||||
m_NextWaypointIndex = GetNextWaypoint(m_CurrentWaypointIndex, changedDirection);
|
||||
if (changedDirection) m_InReverse = true;
|
||||
} else {
|
||||
m_NextWaypointIndex = GetNextReversedWaypoint(m_CurrentWaypointIndex, changedDirection);
|
||||
if (changedDirection) m_InReverse = false;
|
||||
}
|
||||
|
||||
const auto& nextWP = m_Path->pathWaypoints[m_NextWaypointIndex];
|
||||
m_NextWaypointPosition = nextWP.position;
|
||||
m_NextWaypointRotation = nextWP.rotation;
|
||||
|
||||
m_DirectionVector = m_NextWaypointPosition - m_CurrentWaypointPosition;
|
||||
m_TotalDistance = m_DirectionVector.Length();
|
||||
if (m_TotalDistance > 0.0f) {
|
||||
m_DirectionVector = m_DirectionVector / m_TotalDistance;
|
||||
}
|
||||
|
||||
CalculateWaypointSpeeds();
|
||||
|
||||
m_MoveTimeElapsed = 0.0f;
|
||||
m_IdleTimeElapsed = 0.0f;
|
||||
m_HasStartedTravelling = false;
|
||||
|
||||
if (m_TimeBasedMovement) {
|
||||
m_PercentBetweenPoints = 0.0f;
|
||||
} else if (m_TotalDistance > 0.0f) {
|
||||
m_PercentBetweenPoints = (m_Position - m_CurrentWaypointPosition).Length() / m_TotalDistance;
|
||||
} else {
|
||||
m_PercentBetweenPoints = 0.0f;
|
||||
}
|
||||
|
||||
if (m_ParentEntity) {
|
||||
m_ParentEntity->SetPosition(m_CurrentWaypointPosition);
|
||||
m_ParentEntity->SetRotation(m_CurrentWaypointRotation);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Waypoint navigation (exact match of client decompilation) ---
|
||||
|
||||
uint32_t PlatformSubComponent::GetNextWaypoint(uint32_t current, bool& changedDirection) const {
|
||||
changedDirection = false;
|
||||
uint32_t next = current + 1;
|
||||
const auto numWaypoints = static_cast<uint32_t>(m_Path->pathWaypoints.size());
|
||||
|
||||
if (next >= numWaypoints) {
|
||||
switch (m_Path->pathBehavior) {
|
||||
case PathBehavior::Once:
|
||||
next = numWaypoints - 1;
|
||||
break;
|
||||
case PathBehavior::Bounce:
|
||||
next = numWaypoints >= 2 ? numWaypoints - 2 : 0;
|
||||
changedDirection = true;
|
||||
break;
|
||||
case PathBehavior::Loop:
|
||||
default:
|
||||
next = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
uint32_t PlatformSubComponent::GetNextReversedWaypoint(uint32_t current, bool& changedDirection) const {
|
||||
changedDirection = false;
|
||||
|
||||
if (current == 0) {
|
||||
switch (m_Path->pathBehavior) {
|
||||
case PathBehavior::Once:
|
||||
return 0;
|
||||
case PathBehavior::Bounce:
|
||||
changedDirection = true;
|
||||
return 1;
|
||||
case PathBehavior::Loop:
|
||||
default:
|
||||
return static_cast<uint32_t>(m_Path->pathWaypoints.size()) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return current - 1;
|
||||
}
|
||||
|
||||
// --- Arrival detection ---
|
||||
|
||||
bool PlatformSubComponent::CloseToNextWaypoint() const {
|
||||
if (m_TimeBasedMovement) return false;
|
||||
|
||||
const NiPoint3 toNext = m_NextWaypointPosition - m_Position;
|
||||
const float distSq = toNext.SquaredLength();
|
||||
|
||||
if (distSq <= 0.001f) return true;
|
||||
|
||||
const float dot = toNext.DotProduct(m_DirectionVector);
|
||||
return dot <= 0.0f;
|
||||
}
|
||||
|
||||
// --- Travel time calculation ---
|
||||
|
||||
float PlatformSubComponent::CalculateAcceleration(float vi, float vf, float d) {
|
||||
if (d < 0.0001f) return 0.0f;
|
||||
return (vf * vf - vi * vi) / (2.0f * d);
|
||||
}
|
||||
|
||||
float PlatformSubComponent::CalculateTime(float vi, float a, float d) {
|
||||
if (d < 0.0001f) return 0.0f;
|
||||
if (std::abs(a) < 0.0001f) {
|
||||
return vi > 0.0f ? d / vi : 0.0f;
|
||||
}
|
||||
const float discriminant = 2.0f * a * d + vi * vi;
|
||||
if (discriminant < 0.0f) return 0.0f;
|
||||
return (std::sqrt(discriminant) - vi) / a;
|
||||
}
|
||||
|
||||
void PlatformSubComponent::CalculateWaypointSpeeds() {
|
||||
if (m_CurrentWaypointIndex == m_NextWaypointIndex) {
|
||||
m_TravelTime = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_TimeBasedMovement) {
|
||||
uint32_t minIdx = std::min(m_CurrentWaypointIndex, m_NextWaypointIndex);
|
||||
m_CurrentSpeed = m_Path->pathWaypoints[minIdx].speed;
|
||||
m_NextSpeed = 0.0f;
|
||||
m_TravelTime = m_CurrentSpeed;
|
||||
} else {
|
||||
m_CurrentSpeed = m_Path->pathWaypoints[m_CurrentWaypointIndex].speed;
|
||||
m_NextSpeed = m_Path->pathWaypoints[m_NextWaypointIndex].speed;
|
||||
|
||||
float a = CalculateAcceleration(m_CurrentSpeed, m_NextSpeed, m_TotalDistance);
|
||||
m_TravelTime = CalculateTime(m_CurrentSpeed, a, m_TotalDistance);
|
||||
}
|
||||
}
|
||||
|
||||
float PlatformSubComponent::CalculateCurrentSpeed() const {
|
||||
if (m_TimeBasedMovement) {
|
||||
if (m_CurrentSpeed > 0.0f) {
|
||||
return m_TotalDistance / m_CurrentSpeed;
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return (m_NextSpeed - m_CurrentSpeed) * m_PercentBetweenPoints + m_CurrentSpeed;
|
||||
}
|
||||
|
||||
// --- State machine helpers ---
|
||||
|
||||
bool PlatformSubComponent::IncrementWaitingTime(float deltaTime) {
|
||||
if (!(m_State & PlatformState::Waiting)) return false;
|
||||
if (m_State & PlatformState::Travelling) return false;
|
||||
|
||||
m_IdleTimeElapsed += deltaTime;
|
||||
if (m_IdleTimeElapsed >= m_WaitTime) {
|
||||
m_IdleTimeElapsed = 0.0f;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PlatformSubComponent::StartTravelling() {
|
||||
m_State = (m_State & ~(PlatformState::Stopped | PlatformState::Waiting)) | PlatformState::Travelling;
|
||||
m_MoveTimeElapsed = 0.0f;
|
||||
m_HasStartedTravelling = false;
|
||||
}
|
||||
|
||||
void PlatformSubComponent::ArrivedAtWaypoint(bool& dirtyOut) {
|
||||
dirtyOut = true;
|
||||
|
||||
m_Position = m_NextWaypointPosition;
|
||||
m_PercentBetweenPoints = 1.0f;
|
||||
|
||||
if (m_ParentEntity) {
|
||||
m_ParentEntity->SetPosition(m_NextWaypointPosition);
|
||||
m_ParentEntity->SetRotation(m_NextWaypointRotation);
|
||||
}
|
||||
|
||||
PlayArriveSound();
|
||||
|
||||
if (m_ParentEntity) {
|
||||
m_ParentEntity->GetScript()->OnWaypointReached(m_ParentEntity, m_NextWaypointIndex);
|
||||
}
|
||||
|
||||
bool isAtDesiredWaypoint = false;
|
||||
bool stopAtDesired = false;
|
||||
if (m_DesiredWaypointIndex >= 0 &&
|
||||
static_cast<uint32_t>(m_DesiredWaypointIndex) == m_NextWaypointIndex) {
|
||||
isAtDesiredWaypoint = true;
|
||||
stopAtDesired = m_ShouldStopAtDesiredWaypoint;
|
||||
m_ShouldStopAtDesiredWaypoint = false;
|
||||
m_DesiredWaypointIndex = -1;
|
||||
}
|
||||
|
||||
if (isAtDesiredWaypoint && m_ParentEntity) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
bitStream.Write(m_ParentEntity->GetObjectID());
|
||||
bitStream.Write(MessageType::Game::ARRIVED_AT_DESIRED_WAYPOINT);
|
||||
SEND_PACKET_BROADCAST;
|
||||
}
|
||||
|
||||
bool atEnd = false;
|
||||
const auto numWaypoints = static_cast<uint32_t>(m_Path->pathWaypoints.size());
|
||||
if (m_NextWaypointIndex == 0 || m_NextWaypointIndex == numWaypoints - 1) {
|
||||
atEnd = true;
|
||||
}
|
||||
|
||||
if (atEnd && m_ParentEntity) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
bitStream.Write(m_ParentEntity->GetObjectID());
|
||||
bitStream.Write(MessageType::Game::PLATFORM_AT_LAST_WAYPOINT);
|
||||
SEND_PACKET_BROADCAST;
|
||||
}
|
||||
|
||||
bool stopOnce = false;
|
||||
if (atEnd && m_Path->pathBehavior == PathBehavior::Once) {
|
||||
stopOnce = true;
|
||||
m_InReverse = !m_InReverse;
|
||||
}
|
||||
|
||||
if (stopAtDesired || stopOnce) {
|
||||
m_State = PlatformState::Stopped;
|
||||
m_MoveTimeElapsed = 0.0f;
|
||||
m_HasStartedTravelling = false;
|
||||
ZeroPhysicsVelocity();
|
||||
|
||||
if (m_ParentEntity) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
bitStream.Write(m_ParentEntity->GetObjectID());
|
||||
bitStream.Write(MessageType::Game::ARRIVED);
|
||||
SEND_PACKET_BROADCAST;
|
||||
}
|
||||
} else {
|
||||
SetupWaypointSegment(m_NextWaypointIndex);
|
||||
m_State = PlatformState::Waiting;
|
||||
}
|
||||
}
|
||||
|
||||
void PlatformSubComponent::UpdatePositionAlongPath(float deltaTime) {
|
||||
if (m_TotalDistance <= 0.0f && !m_TimeBasedMovement) return;
|
||||
|
||||
m_MoveTimeElapsed += deltaTime;
|
||||
|
||||
// Calculate percent between waypoints matching client CalculatePercentTravelledToWaypoint:
|
||||
// Distance-based: percent = dist(position, currentWP) / dist(nextWP, currentWP)
|
||||
// Time-based: percent = moveTimeElapsed / travelTime
|
||||
if (m_TimeBasedMovement) {
|
||||
if (m_TravelTime > 0.0f) {
|
||||
m_MoveTimeElapsed = std::min(m_MoveTimeElapsed, m_TravelTime);
|
||||
m_PercentBetweenPoints = m_MoveTimeElapsed / m_TravelTime;
|
||||
}
|
||||
} else if (m_TotalDistance > 0.0f) {
|
||||
float distanceTravelled = (m_Position - m_CurrentWaypointPosition).Length();
|
||||
m_PercentBetweenPoints = std::min(distanceTravelled / m_TotalDistance, 1.0f);
|
||||
}
|
||||
|
||||
// Send Departed message on first travel frame (matching client RunPlatform)
|
||||
if (!m_HasStartedTravelling) {
|
||||
m_HasStartedTravelling = true;
|
||||
PlayDepartSound();
|
||||
|
||||
if (m_ParentEntity) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
bitStream.Write(m_ParentEntity->GetObjectID());
|
||||
bitStream.Write(MessageType::Game::DEPARTED);
|
||||
SEND_PACKET_BROADCAST;
|
||||
}
|
||||
}
|
||||
|
||||
// Advance position using velocity and deltaTime (matching client physics model)
|
||||
// The client sets velocity then lets the physics engine move the object.
|
||||
// We do the same: calculate speed, derive velocity, advance position.
|
||||
float speed = CalculateCurrentSpeed();
|
||||
NiPoint3 velocity = m_DirectionVector * speed;
|
||||
m_Position = m_Position + velocity * deltaTime;
|
||||
|
||||
// Clamp position to not overshoot the next waypoint
|
||||
float distToNext = (m_NextWaypointPosition - m_Position).DotProduct(m_DirectionVector);
|
||||
if (distToNext <= 0.0f) {
|
||||
m_Position = m_NextWaypointPosition;
|
||||
}
|
||||
|
||||
if (m_ParentEntity) {
|
||||
m_ParentEntity->SetPosition(m_Position);
|
||||
SetPhysicsVelocity(velocity);
|
||||
|
||||
// Slerp rotation between waypoints
|
||||
auto interpRot = glm::slerp(m_CurrentWaypointRotation, m_NextWaypointRotation, m_PercentBetweenPoints);
|
||||
m_ParentEntity->SetRotation(interpRot);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Physics velocity helpers ---
|
||||
|
||||
void PlatformSubComponent::SetPhysicsVelocity(const NiPoint3& velocity) {
|
||||
if (!m_ParentEntity) return;
|
||||
auto* simplePhysics = m_ParentEntity->GetComponent<SimplePhysicsComponent>();
|
||||
if (simplePhysics) {
|
||||
simplePhysics->SetVelocity(velocity);
|
||||
}
|
||||
}
|
||||
|
||||
void PlatformSubComponent::ZeroPhysicsVelocity() {
|
||||
if (!m_ParentEntity) return;
|
||||
auto* simplePhysics = m_ParentEntity->GetComponent<SimplePhysicsComponent>();
|
||||
if (simplePhysics) {
|
||||
simplePhysics->SetVelocity(NiPoint3Constant::ZERO);
|
||||
simplePhysics->SetAngularVelocity(NiPoint3Constant::ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Sound helpers ---
|
||||
|
||||
void PlatformSubComponent::PlayDepartSound() {
|
||||
if (!m_ParentEntity || !m_Path) return;
|
||||
if (m_CurrentWaypointIndex >= m_Path->pathWaypoints.size()) return;
|
||||
|
||||
const auto& sound = m_Path->pathWaypoints[m_CurrentWaypointIndex].movingPlatform.departSound;
|
||||
if (!sound.empty()) {
|
||||
GameMessages::SendPlayNDAudioEmitter(m_ParentEntity, UNASSIGNED_SYSTEM_ADDRESS, sound);
|
||||
}
|
||||
}
|
||||
|
||||
void PlatformSubComponent::PlayArriveSound() {
|
||||
if (!m_ParentEntity || !m_Path) return;
|
||||
if (m_NextWaypointIndex >= m_Path->pathWaypoints.size()) return;
|
||||
|
||||
const auto& sound = m_Path->pathWaypoints[m_NextWaypointIndex].movingPlatform.arriveSound;
|
||||
if (!sound.empty()) {
|
||||
GameMessages::SendPlayNDAudioEmitter(m_ParentEntity, UNASSIGNED_SYSTEM_ADDRESS, sound);
|
||||
}
|
||||
}
|
||||
127
dGame/dComponents/MovingPlatformComponent/PlatformSubComponent.h
Normal file
127
dGame/dComponents/MovingPlatformComponent/PlatformSubComponent.h
Normal file
@@ -0,0 +1,127 @@
|
||||
#ifndef PLATFORMSUBCOMPONENT_H
|
||||
#define PLATFORMSUBCOMPONENT_H
|
||||
|
||||
#include "RakNetTypes.h"
|
||||
#include "NiPoint3.h"
|
||||
#include "NiQuaternion.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class Entity;
|
||||
class Path;
|
||||
class SimplePhysicsComponent;
|
||||
|
||||
/**
|
||||
* Platform state flags (bitmask matching client LWOPlatform state bits)
|
||||
*/
|
||||
namespace PlatformState {
|
||||
constexpr uint32_t Waiting = 1 << 0; // 0x01 - Waiting at waypoint
|
||||
constexpr uint32_t Travelling = 1 << 1; // 0x02 - Moving between waypoints
|
||||
constexpr uint32_t Stopped = 1 << 2; // 0x04 - Movement halted
|
||||
|
||||
// These map to the old eMovementPlatformState values for serialization
|
||||
constexpr uint32_t MovingSerialized = 0b00010; // Travelling
|
||||
constexpr uint32_t StationarySerialized = 0b11001; // Waiting
|
||||
constexpr uint32_t StoppedSerialized = 0b01100; // Stopped
|
||||
};
|
||||
|
||||
/**
|
||||
* Base class for platform subcomponents. Mirrors the client's LWOPlatform base.
|
||||
* Handles the core state machine: waiting at waypoints, travelling between them,
|
||||
* arrival detection, and waypoint navigation (loop/bounce/once).
|
||||
*/
|
||||
class PlatformSubComponent {
|
||||
public:
|
||||
PlatformSubComponent(Entity* parentEntity, const Path* path);
|
||||
virtual ~PlatformSubComponent() = default;
|
||||
|
||||
virtual void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate);
|
||||
virtual void Update(float deltaTime, bool& dirtyOut);
|
||||
|
||||
void StartPathing();
|
||||
void StopPathing();
|
||||
void GotoWaypoint(uint32_t index, bool stopAtWaypoint = true);
|
||||
void WarpToWaypoint(size_t index);
|
||||
void SetupWaypointSegment(uint32_t waypointIndex);
|
||||
|
||||
// --- State accessors ---
|
||||
|
||||
uint32_t GetState() const { return m_State; }
|
||||
void SetState(uint32_t state) { m_State = state; }
|
||||
|
||||
int32_t GetDesiredWaypointIndex() const { return m_DesiredWaypointIndex; }
|
||||
bool GetInReverse() const { return m_InReverse; }
|
||||
bool GetShouldStopAtDesiredWaypoint() const { return m_ShouldStopAtDesiredWaypoint; }
|
||||
float GetPercentBetweenPoints() const { return m_PercentBetweenPoints; }
|
||||
NiPoint3 GetPosition() const { return m_Position; }
|
||||
uint32_t GetCurrentWaypointIndex() const { return m_CurrentWaypointIndex; }
|
||||
uint32_t GetNextWaypointIndex() const { return m_NextWaypointIndex; }
|
||||
float GetIdleTimeElapsed() const { return m_IdleTimeElapsed; }
|
||||
float GetMoveTimeElapsed() const { return m_MoveTimeElapsed; }
|
||||
float GetSpeed() const { return m_CurrentSpeed; }
|
||||
float GetWaitTime() const { return m_WaitTime; }
|
||||
size_t GetLastWaypointIndex() const;
|
||||
bool IsActive() const { return m_Active; }
|
||||
|
||||
void SetDesiredWaypointIndex(int32_t index) { m_DesiredWaypointIndex = index; }
|
||||
void SetShouldStopAtDesiredWaypoint(bool value) { m_ShouldStopAtDesiredWaypoint = value; }
|
||||
void SetInReverse(bool value) { m_InReverse = value; }
|
||||
void SetActive(bool value) { m_Active = value; }
|
||||
|
||||
uint32_t GetSerializedState() const;
|
||||
|
||||
protected:
|
||||
uint32_t GetNextWaypoint(uint32_t current, bool& changedDirection) const;
|
||||
uint32_t GetNextReversedWaypoint(uint32_t current, bool& changedDirection) const;
|
||||
bool CloseToNextWaypoint() const;
|
||||
|
||||
static float CalculateAcceleration(float vi, float vf, float d);
|
||||
static float CalculateTime(float vi, float a, float d);
|
||||
void CalculateWaypointSpeeds();
|
||||
float CalculateCurrentSpeed() const;
|
||||
|
||||
bool IncrementWaitingTime(float deltaTime);
|
||||
void StartTravelling();
|
||||
void ArrivedAtWaypoint(bool& dirtyOut);
|
||||
virtual void UpdatePositionAlongPath(float deltaTime);
|
||||
|
||||
void SetPhysicsVelocity(const NiPoint3& velocity);
|
||||
void ZeroPhysicsVelocity();
|
||||
void PlayDepartSound();
|
||||
void PlayArriveSound();
|
||||
|
||||
Entity* m_ParentEntity = nullptr;
|
||||
const Path* m_Path = nullptr;
|
||||
bool m_Active = false;
|
||||
|
||||
uint32_t m_State = PlatformState::Stopped;
|
||||
int32_t m_DesiredWaypointIndex = -1;
|
||||
bool m_InReverse = false;
|
||||
bool m_ShouldStopAtDesiredWaypoint = false;
|
||||
|
||||
float m_PercentBetweenPoints = 0.0f;
|
||||
NiPoint3 m_Position{};
|
||||
|
||||
uint32_t m_CurrentWaypointIndex = 0;
|
||||
uint32_t m_NextWaypointIndex = 0;
|
||||
|
||||
float m_IdleTimeElapsed = 0.0f;
|
||||
float m_MoveTimeElapsed = 0.0f;
|
||||
|
||||
float m_CurrentSpeed = 0.0f;
|
||||
float m_NextSpeed = 0.0f;
|
||||
float m_WaitTime = 0.0f;
|
||||
|
||||
NiPoint3 m_CurrentWaypointPosition{};
|
||||
NiPoint3 m_NextWaypointPosition{};
|
||||
NiQuaternion m_CurrentWaypointRotation = QuatUtils::IDENTITY;
|
||||
NiQuaternion m_NextWaypointRotation = QuatUtils::IDENTITY;
|
||||
NiPoint3 m_DirectionVector{};
|
||||
float m_TotalDistance = 0.0f;
|
||||
float m_TravelTime = 0.0f;
|
||||
|
||||
bool m_TimeBasedMovement = false;
|
||||
bool m_HasStartedTravelling = false;
|
||||
};
|
||||
|
||||
#endif // PLATFORMSUBCOMPONENT_H
|
||||
@@ -0,0 +1,93 @@
|
||||
#include "RotatorSubComponent.h"
|
||||
#include "Entity.h"
|
||||
#include "SimplePhysicsComponent.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
RotatorSubComponent::RotatorSubComponent(Entity* parentEntity, const Path* path)
|
||||
: PlatformSubComponent(parentEntity, path) {
|
||||
}
|
||||
|
||||
void RotatorSubComponent::UpdatePositionAlongPath(float deltaTime) {
|
||||
// Do the base linear movement (position, velocity, departed message)
|
||||
PlatformSubComponent::UpdatePositionAlongPath(deltaTime);
|
||||
|
||||
if (!m_ParentEntity) return;
|
||||
|
||||
// Angular velocity calculation matching client LWOPlatform::SetAngularVelocity:
|
||||
// 1. If current and next rotations are the same, just set current rotation and zero angular velocity
|
||||
// 2. Otherwise, SLERP to the target rotation based on percent, then compute angular velocity
|
||||
// to reach the next waypoint rotation in the remaining travel time
|
||||
|
||||
if (m_CurrentWaypointRotation == m_NextWaypointRotation) {
|
||||
m_ParentEntity->SetRotation(m_CurrentWaypointRotation);
|
||||
auto* simplePhysics = m_ParentEntity->GetComponent<SimplePhysicsComponent>();
|
||||
if (simplePhysics) {
|
||||
simplePhysics->SetAngularVelocity(NiPoint3Constant::ZERO);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// SLERP to get the current target rotation (matching client CalculateSlerp)
|
||||
NiQuaternion targetRot = glm::slerp(m_CurrentWaypointRotation, m_NextWaypointRotation, m_PercentBetweenPoints);
|
||||
|
||||
// Check if we're already close enough to snap (matching client's angle threshold check)
|
||||
NiQuaternion currentRot = m_ParentEntity->GetRotation();
|
||||
float dotProduct = glm::dot(targetRot, currentRot);
|
||||
if (dotProduct < 0.0f) dotProduct = -dotProduct;
|
||||
float angleDiff = std::acos(std::clamp(dotProduct, 0.0f, 1.0f));
|
||||
|
||||
bool snappedToTarget = false;
|
||||
if (angleDiff < m_MaxLerpAngle) {
|
||||
// Close enough — snap to current rotation, use the actual current rotation for angular vel calc
|
||||
snappedToTarget = true;
|
||||
targetRot = currentRot;
|
||||
} else {
|
||||
// Set the SLERP'd rotation on the entity
|
||||
m_ParentEntity->SetRotation(targetRot);
|
||||
}
|
||||
|
||||
// Calculate remaining travel time for angular velocity
|
||||
float remainingTime = 0.0f;
|
||||
if (!m_TimeBasedMovement && m_TotalDistance > 0.0f) {
|
||||
// Distance-based: calculate remaining time from remaining distance and speeds
|
||||
NiPoint3 toNext = m_NextWaypointPosition - m_Position;
|
||||
float remainingDist = toNext.Length();
|
||||
float currentSpeed = CalculateCurrentSpeed();
|
||||
if (currentSpeed > 0.0f) {
|
||||
remainingTime = remainingDist / currentSpeed;
|
||||
}
|
||||
} else if (m_TimeBasedMovement) {
|
||||
remainingTime = m_TravelTime - m_MoveTimeElapsed;
|
||||
}
|
||||
|
||||
if (remainingTime > 0.0f) {
|
||||
// Compute angular velocity from quaternion difference (matching client LWOPhysicsCalcAngularVelocity)
|
||||
// Angular velocity = axis * (angle / time)
|
||||
NiQuaternion rotDiff = m_NextWaypointRotation * glm::inverse(targetRot);
|
||||
|
||||
// Normalize to ensure valid quaternion
|
||||
rotDiff = glm::normalize(rotDiff);
|
||||
|
||||
float angle = 2.0f * std::acos(std::clamp(rotDiff.w, -1.0f, 1.0f));
|
||||
if (std::abs(angle) > 0.001f) {
|
||||
float sinHalf = std::sqrt(1.0f - rotDiff.w * rotDiff.w);
|
||||
NiPoint3 axis;
|
||||
if (sinHalf > 0.001f) {
|
||||
axis = NiPoint3(rotDiff.x / sinHalf, rotDiff.y / sinHalf, rotDiff.z / sinHalf);
|
||||
} else {
|
||||
axis = NiPoint3(0.0f, 1.0f, 0.0f);
|
||||
}
|
||||
|
||||
m_AngularVelocity = axis * (angle / remainingTime);
|
||||
|
||||
auto* simplePhysics = m_ParentEntity->GetComponent<SimplePhysicsComponent>();
|
||||
if (simplePhysics) {
|
||||
simplePhysics->SetAngularVelocity(m_AngularVelocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#ifndef ROTATORSUBCOMPONENT_H
|
||||
#define ROTATORSUBCOMPONENT_H
|
||||
|
||||
#include "PlatformSubComponent.h"
|
||||
|
||||
/**
|
||||
* Rotator - follows a path like Mover but also applies angular velocity.
|
||||
* Corresponds to client LWOPlatformRotator (type 6).
|
||||
*/
|
||||
class RotatorSubComponent final : public PlatformSubComponent {
|
||||
public:
|
||||
RotatorSubComponent(Entity* parentEntity, const Path* path);
|
||||
|
||||
void UpdatePositionAlongPath(float deltaTime) override;
|
||||
|
||||
private:
|
||||
NiPoint3 m_RotationAxis{};
|
||||
float m_Rate = 0.0f;
|
||||
NiPoint3 m_AngularVelocity{};
|
||||
bool m_AllowRotSnap = true;
|
||||
float m_MaxLerpAngle = 0.785398f; // ~45 degrees
|
||||
};
|
||||
|
||||
#endif // ROTATORSUBCOMPONENT_H
|
||||
@@ -0,0 +1,55 @@
|
||||
#include "SimpleMoverSubComponent.h"
|
||||
#include "Zone.h"
|
||||
|
||||
SimpleMoverSubComponent::SimpleMoverSubComponent(Entity* parentEntity, const NiPoint3& startPos,
|
||||
const NiQuaternion& startRot, const NiPoint3& platformMove, float platformMoveTime)
|
||||
: PlatformSubComponent(parentEntity, nullptr) {
|
||||
GeneratePath(startPos, startRot, platformMove, platformMoveTime);
|
||||
m_Path = m_GeneratedPath.get();
|
||||
m_TimeBasedMovement = false;
|
||||
|
||||
// Auto-activate and set up initial segment (matching client GenerateSimpleMoverPath)
|
||||
if (m_Path && !m_Path->pathWaypoints.empty()) {
|
||||
m_Active = true;
|
||||
SetupWaypointSegment(m_CurrentWaypointIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleMoverSubComponent::GeneratePath(const NiPoint3& startPos, const NiQuaternion& startRot,
|
||||
const NiPoint3& platformMove, float platformMoveTime) {
|
||||
|
||||
auto path = std::make_unique<Path>();
|
||||
path->pathType = PathType::MovingPlatform;
|
||||
path->pathBehavior = PathBehavior::Once;
|
||||
path->pathName = "SimpleMoverPath";
|
||||
path->movingPlatform.timeBasedMovement = 0;
|
||||
|
||||
// Waypoint 0: start position
|
||||
PathWaypoint wp0;
|
||||
wp0.position = startPos;
|
||||
wp0.rotation = startRot;
|
||||
wp0.movingPlatform.wait = 0.0f;
|
||||
|
||||
// Waypoint 1: start + rotated platformMove
|
||||
NiPoint3 move = platformMove;
|
||||
NiPoint3 rotatedMove = move.RotateByQuaternion(startRot);
|
||||
PathWaypoint wp1;
|
||||
wp1.position = startPos + rotatedMove;
|
||||
wp1.rotation = startRot;
|
||||
wp1.movingPlatform.wait = 0.0f;
|
||||
|
||||
// Calculate speed: length(platformMove) / platformMoveTime
|
||||
float speed = 0.0f;
|
||||
if (move.SquaredLength() > 0.0f && platformMoveTime > 0.0f) {
|
||||
speed = platformMove.Length() / platformMoveTime;
|
||||
}
|
||||
|
||||
wp0.speed = speed;
|
||||
wp1.speed = speed;
|
||||
|
||||
path->pathWaypoints.push_back(wp0);
|
||||
path->pathWaypoints.push_back(wp1);
|
||||
path->waypointCount = 2;
|
||||
|
||||
m_GeneratedPath = std::move(path);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef SIMPLEMOVERSUBCOMPONENT_H
|
||||
#define SIMPLEMOVERSUBCOMPONENT_H
|
||||
|
||||
#include "PlatformSubComponent.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
class Path;
|
||||
|
||||
/**
|
||||
* Simple mover - auto-generates a 2-waypoint path from component properties.
|
||||
* Corresponds to client LWOPlatformSimpleMover (type 5).
|
||||
*/
|
||||
class SimpleMoverSubComponent final : public PlatformSubComponent {
|
||||
public:
|
||||
SimpleMoverSubComponent(Entity* parentEntity, const NiPoint3& startPos,
|
||||
const NiQuaternion& startRot, const NiPoint3& platformMove, float platformMoveTime);
|
||||
|
||||
const Path* GetGeneratedPath() const { return m_GeneratedPath.get(); }
|
||||
|
||||
private:
|
||||
void GeneratePath(const NiPoint3& startPos, const NiQuaternion& startRot,
|
||||
const NiPoint3& platformMove, float platformMoveTime);
|
||||
|
||||
std::unique_ptr<Path> m_GeneratedPath;
|
||||
};
|
||||
|
||||
#endif // SIMPLEMOVERSUBCOMPONENT_H
|
||||
Reference in New Issue
Block a user