mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 19:03:43 +00:00
The ActivatePhysics trigger command was a TODO. The client activates or deactivates the object's physics component (LWOPhysicsSystemComponent::msgActivatePhysics, 1.10.64 0x00ccf970); the server now does the same to phantom physics: switching it off takes the volume out of the physics world (dpWorld::DetachEntity, without deleting it) and makes whatever was inside leave, switching it on adds it back and whatever is inside enters on the next step. This lets trigger driven volumes like the monument lasers turn on and off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
280 lines
8.3 KiB
C++
280 lines
8.3 KiB
C++
/*
|
|
* Darkflame Universe
|
|
* Copyright 2018
|
|
*/
|
|
|
|
#include <sstream>
|
|
#include <iostream>
|
|
|
|
#include "PhantomPhysicsComponent.h"
|
|
#include "Game.h"
|
|
#include "LDFFormat.h"
|
|
#include "Logger.h"
|
|
#include "Entity.h"
|
|
#include "EntityManager.h"
|
|
#include "ControllablePhysicsComponent.h"
|
|
#include "GameMessages.h"
|
|
#include "ePhysicsEffectType.h"
|
|
|
|
#include "CDClientManager.h"
|
|
#include "CDComponentsRegistryTable.h"
|
|
#include "CDPhysicsComponentTable.h"
|
|
#include "dServer.h"
|
|
#include "EntityInfo.h"
|
|
#include "Amf3.h"
|
|
|
|
#include "dpWorld.h"
|
|
#include "dpEntity.h"
|
|
#include "dpShapeBox.h"
|
|
#include "dpShapeSphere.h"
|
|
|
|
PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent, const int32_t componentID) : PhysicsComponent(parent, componentID) {
|
|
RegisterMsg(&PhantomPhysicsComponent::OnGetObjectReportInfo);
|
|
|
|
m_Position = m_Parent->GetDefaultPosition();
|
|
m_Rotation = m_Parent->GetDefaultRotation();
|
|
m_Scale = m_Parent->GetDefaultScale();
|
|
m_dpEntity = nullptr;
|
|
|
|
m_EffectInfoDirty = false;
|
|
|
|
m_IsPhysicsEffectActive = false;
|
|
m_EffectType = ePhysicsEffectType::PUSH;
|
|
m_DirectionalMultiplier = 0.0f;
|
|
|
|
m_MinMax = false;
|
|
m_Min = 0;
|
|
m_Max = 1;
|
|
|
|
m_IsDirectional = false;
|
|
m_Direction = NiPoint3(); // * m_DirectionalMultiplier
|
|
|
|
if (m_Parent->GetVar<bool>(u"create_physics")) {
|
|
m_dpEntity = CreatePhysicsLnv(m_Scale, ComponentType);
|
|
}
|
|
|
|
if (m_Parent->GetVar<bool>(u"respawnVol")) {
|
|
m_IsRespawnVolume = true;
|
|
}
|
|
|
|
if (m_IsRespawnVolume) {
|
|
const auto respawnPos = GeneralUtils::SplitString(m_Parent->GetVarAsString(u"rspPos"), '\x1f');
|
|
m_RespawnPos = GeneralUtils::TryParse(respawnPos, NiPoint3Constant::ZERO);
|
|
|
|
const auto respawnRot = GeneralUtils::SplitString(m_Parent->GetVarAsString(u"rspRot"), '\x1f');
|
|
m_RespawnRot = respawnRot.size() >= 4 ? NiQuaternion(
|
|
GeneralUtils::TryParse(respawnRot[0], 1.0f),
|
|
GeneralUtils::TryParse(respawnRot[1], 0.0f),
|
|
GeneralUtils::TryParse(respawnRot[2], 0.0f),
|
|
GeneralUtils::TryParse(respawnRot[3], 0.0f))
|
|
: QuatUtils::IDENTITY;
|
|
}
|
|
|
|
// HF - RespawnPoints. Legacy respawn entity.
|
|
if (m_Parent->GetLOT() == 4945) {
|
|
m_IsRespawnVolume = true;
|
|
m_RespawnPos = m_Position;
|
|
m_RespawnRot = m_Rotation;
|
|
}
|
|
|
|
if (!m_dpEntity) {
|
|
m_dpEntity = CreatePhysicsEntity(ComponentType);
|
|
if (!m_dpEntity) return;
|
|
m_dpEntity->SetScale(m_Scale);
|
|
m_dpEntity->SetRotation(m_Rotation);
|
|
m_dpEntity->SetPosition(m_Position);
|
|
dpWorld::AddEntity(m_dpEntity);
|
|
}
|
|
}
|
|
|
|
void ApplyCollisionEffect(const LWOOBJID& target, const ePhysicsEffectType effectType, const float effectScale);
|
|
|
|
PhantomPhysicsComponent::~PhantomPhysicsComponent() {
|
|
if (!m_dpEntity) return;
|
|
|
|
if (m_PhysicsActive) dpWorld::RemoveEntity(m_dpEntity);
|
|
else delete m_dpEntity;
|
|
}
|
|
|
|
void PhantomPhysicsComponent::SetPhysicsActive(const bool active) {
|
|
if (!m_dpEntity || m_PhysicsActive == active) return;
|
|
m_PhysicsActive = active;
|
|
|
|
if (active) {
|
|
// Anything already inside enters on the next physics step
|
|
dpWorld::AddEntity(m_dpEntity);
|
|
return;
|
|
}
|
|
|
|
dpWorld::DetachEntity(m_dpEntity);
|
|
const std::vector<LWOOBJID> inside(m_dpEntity->GetCurrentlyCollidingObjects().begin(), m_dpEntity->GetCurrentlyCollidingObjects().end());
|
|
m_dpEntity->ClearCollisions();
|
|
for (const auto id : inside) {
|
|
ApplyCollisionEffect(id, m_EffectType, 1.0f);
|
|
m_Parent->OnCollisionLeavePhantom(id);
|
|
}
|
|
}
|
|
|
|
void PhantomPhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) {
|
|
PhysicsComponent::Serialize(outBitStream, bIsInitialUpdate);
|
|
|
|
outBitStream.Write(m_EffectInfoDirty || bIsInitialUpdate);
|
|
if (m_EffectInfoDirty || bIsInitialUpdate) {
|
|
outBitStream.Write(m_IsPhysicsEffectActive);
|
|
|
|
if (m_IsPhysicsEffectActive) {
|
|
outBitStream.Write(m_EffectType);
|
|
outBitStream.Write(m_DirectionalMultiplier);
|
|
|
|
// forgive me father for i have sinned
|
|
outBitStream.Write0();
|
|
//outBitStream.Write(m_MinMax);
|
|
//if (m_MinMax) {
|
|
//outBitStream.Write(m_Min);
|
|
//outBitStream.Write(m_Max);
|
|
//}
|
|
|
|
outBitStream.Write(m_IsDirectional);
|
|
if (m_IsDirectional) {
|
|
outBitStream.Write(m_Direction.x);
|
|
outBitStream.Write(m_Direction.y);
|
|
outBitStream.Write(m_Direction.z);
|
|
}
|
|
}
|
|
|
|
m_EffectInfoDirty = false;
|
|
}
|
|
}
|
|
|
|
// Even if we were to implement Friction server side,
|
|
// it also defaults to 1.0f in the last argument, so we dont need two functions to do the same thing.
|
|
void ApplyCollisionEffect(const LWOOBJID& target, const ePhysicsEffectType effectType, const float effectScale) {
|
|
switch (effectType) {
|
|
case ePhysicsEffectType::GRAVITY_SCALE: {
|
|
auto* targetEntity = Game::entityManager->GetEntity(target);
|
|
if (targetEntity) {
|
|
auto* controllablePhysicsComponent = targetEntity->GetComponent<ControllablePhysicsComponent>();
|
|
// dont want to apply an effect to nothing.
|
|
if (!controllablePhysicsComponent) return;
|
|
controllablePhysicsComponent->SetGravityScale(effectScale);
|
|
GameMessages::SendSetGravityScale(target, effectScale, targetEntity->GetSystemAddress());
|
|
}
|
|
break;
|
|
}
|
|
|
|
case ePhysicsEffectType::ATTRACT:
|
|
case ePhysicsEffectType::FRICTION:
|
|
case ePhysicsEffectType::PUSH:
|
|
case ePhysicsEffectType::REPULSE:
|
|
default:
|
|
break;
|
|
}
|
|
// The other types are not handled by the server and are here to handle all cases of the enum.
|
|
}
|
|
|
|
void PhantomPhysicsComponent::Update(float deltaTime) {
|
|
if (!m_dpEntity || !m_PhysicsActive) return;
|
|
|
|
//Process enter events
|
|
for (const auto id : m_dpEntity->GetNewObjects()) {
|
|
ApplyCollisionEffect(id, m_EffectType, m_DirectionalMultiplier);
|
|
m_Parent->OnCollisionPhantom(id);
|
|
|
|
//If we are a respawn volume, inform the client:
|
|
if (m_IsRespawnVolume) {
|
|
auto* const entity = Game::entityManager->GetEntity(id);
|
|
|
|
if (entity) {
|
|
GameMessages::SendPlayerReachedRespawnCheckpoint(entity, m_RespawnPos, m_RespawnRot);
|
|
entity->SetRespawnPos(m_RespawnPos);
|
|
entity->SetRespawnRot(m_RespawnRot);
|
|
}
|
|
}
|
|
}
|
|
|
|
//Process exit events
|
|
for (const auto id : m_dpEntity->GetRemovedObjects()) {
|
|
ApplyCollisionEffect(id, m_EffectType, 1.0f);
|
|
m_Parent->OnCollisionLeavePhantom(id);
|
|
}
|
|
}
|
|
|
|
void PhantomPhysicsComponent::SetDirection(const NiPoint3& pos) {
|
|
m_Direction = pos;
|
|
m_Direction.x *= m_DirectionalMultiplier;
|
|
m_Direction.y *= m_DirectionalMultiplier;
|
|
m_Direction.z *= m_DirectionalMultiplier;
|
|
|
|
m_EffectInfoDirty = true;
|
|
m_IsDirectional = true;
|
|
}
|
|
|
|
void PhantomPhysicsComponent::SpawnVertices() const {
|
|
if (!m_dpEntity) {
|
|
LOG("No dpEntity to spawn vertices for %llu:%i", m_Parent->GetObjectID(), m_Parent->GetLOT());
|
|
return;
|
|
}
|
|
PhysicsComponent::SpawnVertices(m_dpEntity);
|
|
}
|
|
|
|
void PhantomPhysicsComponent::SetDirectionalMultiplier(float mul) {
|
|
m_DirectionalMultiplier = mul;
|
|
m_EffectInfoDirty = true;
|
|
}
|
|
|
|
void PhantomPhysicsComponent::SetEffectType(ePhysicsEffectType type) {
|
|
m_EffectType = type;
|
|
m_EffectInfoDirty = true;
|
|
}
|
|
|
|
void PhantomPhysicsComponent::SetMin(uint32_t min) {
|
|
m_Min = min;
|
|
m_MinMax = true;
|
|
m_EffectInfoDirty = true;
|
|
}
|
|
|
|
void PhantomPhysicsComponent::SetMax(uint32_t max) {
|
|
m_Max = max;
|
|
m_MinMax = true;
|
|
m_EffectInfoDirty = true;
|
|
}
|
|
|
|
void PhantomPhysicsComponent::SetPosition(const NiPoint3& pos) {
|
|
PhysicsComponent::SetPosition(pos);
|
|
if (m_dpEntity) m_dpEntity->SetPosition(pos);
|
|
}
|
|
|
|
void PhantomPhysicsComponent::SetRotation(const NiQuaternion& rot) {
|
|
PhysicsComponent::SetRotation(rot);
|
|
if (m_dpEntity) m_dpEntity->SetRotation(rot);
|
|
}
|
|
|
|
bool PhantomPhysicsComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
|
|
PhysicsComponent::OnGetObjectReportInfo(reportInfo);
|
|
if (!reportInfo.subCategory) {
|
|
return false;
|
|
}
|
|
auto& info = reportInfo.subCategory->PushDebug("Phantom Physics Info");
|
|
info.PushDebug<AMFDoubleValue>("Scale") = m_Scale;
|
|
info.PushDebug<AMFBoolValue>("Is Physics Effect Active") = m_IsPhysicsEffectActive;
|
|
info.PushDebug<AMFIntValue>("Effect Type") = static_cast<int>(m_EffectType);
|
|
info.PushDebug<AMFDoubleValue>("Directional Multiplier") = m_DirectionalMultiplier;
|
|
info.PushDebug<AMFBoolValue>("Is Directional") = m_IsDirectional;
|
|
auto& direction = info.PushDebug("Direction").PushDebug(m_Direction);
|
|
|
|
if (m_MinMax) {
|
|
auto& minMaxInfo = info.PushDebug("Min Max Info");
|
|
minMaxInfo.PushDebug<AMFIntValue>("Min") = m_Min;
|
|
minMaxInfo.PushDebug<AMFIntValue>("Max") = m_Max;
|
|
}
|
|
|
|
if (m_IsRespawnVolume) {
|
|
auto& respawnInfo = info.PushDebug("Respawn Info");
|
|
respawnInfo.PushDebug<AMFBoolValue>("Is Respawn Volume") = m_IsRespawnVolume;
|
|
respawnInfo.PushDebug("Respawn Position").PushDebug(m_RespawnPos);
|
|
respawnInfo.PushDebug("Respawn Rotation").PushDebug(m_RespawnRot);
|
|
}
|
|
|
|
return true;
|
|
}
|