From 62f5f138462ccd0daa79652ca1158af859fcf553 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Tue, 29 Sep 2026 17:37:45 -0500 Subject: [PATCH] feat(physics): simple and phantom physics objects register as movement blockers A fixed simple physics object, or a phantom volume, that its data marks as a wall for enemies (a navmesh carver, or solid with a group only enemies touch) adds its shape to the world's movement blockers, and takes it out again when it goes away. Only shapes the server knows are used; an unknown asset's stand in cube would be a guess, so it is logged and left out. Co-Authored-By: Claude Opus 5.5 --- dGame/dComponents/PhantomPhysicsComponent.cpp | 3 ++ dGame/dComponents/PhysicsComponent.cpp | 38 ++++++++++++++++++- dGame/dComponents/PhysicsComponent.h | 15 +++++++- dGame/dComponents/SimplePhysicsComponent.cpp | 3 ++ 4 files changed, 56 insertions(+), 3 deletions(-) diff --git a/dGame/dComponents/PhantomPhysicsComponent.cpp b/dGame/dComponents/PhantomPhysicsComponent.cpp index e367255b0..77488e90c 100644 --- a/dGame/dComponents/PhantomPhysicsComponent.cpp +++ b/dGame/dComponents/PhantomPhysicsComponent.cpp @@ -50,6 +50,9 @@ PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent, const int32_t c m_IsDirectional = false; m_Direction = NiPoint3(); // * m_DirectionalMultiplier + // A phantom volume enemies can't walk into when it carves the navmesh (e.g. the Sentinel camp walls) + RegisterMovementBlocker(ComponentType, false, m_Scale); + if (m_Parent->GetVar(u"create_physics")) { m_dpEntity = CreatePhysicsLnv(m_Scale, ComponentType); } diff --git a/dGame/dComponents/PhysicsComponent.cpp b/dGame/dComponents/PhysicsComponent.cpp index cd3a8663e..cbdf4e1f6 100644 --- a/dGame/dComponents/PhysicsComponent.cpp +++ b/dGame/dComponents/PhysicsComponent.cpp @@ -12,11 +12,14 @@ #include "dpEntity.h" #include "dpWorld.h" +#include "dpMovementBlockers.h" #include "dpShapeBox.h" #include "dpShapeSphere.h" #include "EntityInfo.h" #include "Amf3.h" +#include "Entity.h" +#include "Logger.h" PhysicsComponent::PhysicsComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { m_Position = NiPoint3Constant::ZERO; @@ -37,6 +40,37 @@ PhysicsComponent::PhysicsComponent(Entity* parent, const int32_t componentID) : RegisterMsg(&PhysicsComponent::OnGetPosition); } +PhysicsComponent::~PhysicsComponent() { + if (!m_MovementBlocker) return; + dpWorld::RemoveMovementBlocker(m_MovementBlocker); + delete m_MovementBlocker; +} + +void PhysicsComponent::RegisterMovementBlocker(const eReplicaComponentType type, const bool solid, const float scale) { + if (m_MovementBlocker) return; + const auto filter = dpMovementBlockers::BlockingFilter(m_Parent->GetVar(u"navmesh_carver"), solid, static_cast(m_CollisionGroup)); + if (!filter) return; + + // Some shapes move the object's position to line up with the client's; that is for the shape only + const auto placed = m_Position; + bool isFallback = false; + auto* const blocker = CreatePhysicsEntity(type, &isFallback); + const auto shapePosition = m_Position; + m_Position = placed; + if (!blocker) return; + if (isFallback) { + LOG_DEBUG("%llu (LOT %i) should block movement, but the server doesn't know its shape", m_Parent->GetObjectID(), m_Parent->GetLOT()); + delete blocker; + return; + } + + blocker->SetScale(scale); + blocker->SetRotation(m_Rotation); + blocker->SetPosition(shapePosition); + m_MovementBlocker = blocker; + dpWorld::AddMovementBlocker(m_MovementBlocker, *filter); +} + bool PhysicsComponent::OnGetPosition(GameMessages::GetPosition& msg) { msg.pos = GetPosition(); return true; @@ -56,7 +90,8 @@ void PhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitia } } -dpEntity* PhysicsComponent::CreatePhysicsEntity(eReplicaComponentType type) { +dpEntity* PhysicsComponent::CreatePhysicsEntity(eReplicaComponentType type, bool* isFallback) { + if (isFallback) *isFallback = false; CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable(); auto componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), type); @@ -115,6 +150,7 @@ dpEntity* PhysicsComponent::CreatePhysicsEntity(eReplicaComponentType type) { //add fallback cube: toReturn = new dpEntity(m_Parent->GetObjectID(), 2.0f, 2.0f, 2.0f); + if (isFallback) *isFallback = true; } // Only touch what the client lets this group touch (e.g. POI walls ignore enemies, threat clearing walls ignore players) toReturn->SetCollisionGroup(static_cast(m_CollisionGroup)); diff --git a/dGame/dComponents/PhysicsComponent.h b/dGame/dComponents/PhysicsComponent.h index 066451da9..18dd793c9 100644 --- a/dGame/dComponents/PhysicsComponent.h +++ b/dGame/dComponents/PhysicsComponent.h @@ -21,7 +21,7 @@ class dpEntity; class PhysicsComponent : public Component { public: PhysicsComponent(Entity* parent, const int32_t componentID); - virtual ~PhysicsComponent() = default; + virtual ~PhysicsComponent(); void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; @@ -36,7 +36,15 @@ public: protected: bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& msg); - dpEntity* CreatePhysicsEntity(eReplicaComponentType type); + // isFallback, when given, says whether the asset had no known shape and got a stand in cube + dpEntity* CreatePhysicsEntity(eReplicaComponentType type, bool* isFallback = nullptr); + + /** + * Makes this object a wall the server's movers can't walk through when its data says it is one + * (dpMovementBlockers::BlockingFilter: a navmesh carver, or a solid object only enemies collide with). Only + * shapes the server knows are used; a stand in cube would be a guess. + */ + void RegisterMovementBlocker(eReplicaComponentType type, bool solid, float scale); dpEntity* CreatePhysicsLnv(const float scale, const eReplicaComponentType type) const; @@ -51,6 +59,9 @@ protected: bool m_DirtyPosition; int32_t m_CollisionGroup{}; + + // This object's shape as a movement blocker, owned here and not stepped with the physics world + dpEntity* m_MovementBlocker{}; }; #endif //!__PHYSICSCOMPONENT__H__ diff --git a/dGame/dComponents/SimplePhysicsComponent.cpp b/dGame/dComponents/SimplePhysicsComponent.cpp index 95e58735e..744bdd0ca 100644 --- a/dGame/dComponents/SimplePhysicsComponent.cpp +++ b/dGame/dComponents/SimplePhysicsComponent.cpp @@ -33,6 +33,9 @@ SimplePhysicsComponent::SimplePhysicsComponent(Entity* parent, const int32_t com } // Without a motionType the object is fixed (live: Fixed on 38,970 of 40,786 constructions, never the invalid 0) if (m_Parent->HasVar(u"motionType")) m_PhysicsMotionState = m_Parent->GetVarAs(u"motionType"); + + // A fixed solid object can be a wall enemies walk into (an enemy only blocker, or a navmesh carver) + if (m_PhysicsMotionState == MOTION_TYPE_FIXED) RegisterMovementBlocker(eReplicaComponentType::SIMPLE_PHYSICS, true, m_Parent->GetDefaultScale()); } SimplePhysicsComponent::~SimplePhysicsComponent() {