diff --git a/dGame/dComponents/Physics/PhantomPhysicsComponent.cpp b/dGame/dComponents/Physics/PhantomPhysicsComponent.cpp index 6e56088f6..c5b25ffc1 100644 --- a/dGame/dComponents/Physics/PhantomPhysicsComponent.cpp +++ b/dGame/dComponents/Physics/PhantomPhysicsComponent.cpp @@ -3,6 +3,7 @@ * Copyright 2018 */ +#include #include #include @@ -136,13 +137,13 @@ void PhantomPhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bI 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); - //} + // Live wrote the distances as the client keeps them: "true,150,175" went out as 1 and 175 + outBitStream.Write(m_MinMax); + if (m_MinMax) { + const float min = std::min(m_Min, 1.0f); + outBitStream.Write(min); + outBitStream.Write(std::max(m_Max, min)); + } outBitStream.Write(m_IsDirectional); if (m_IsDirectional) { @@ -244,18 +245,50 @@ void PhantomPhysicsComponent::SetEffectType(ePhysicsEffectType type) { m_EffectInfoDirty = true; } -void PhantomPhysicsComponent::SetMin(uint32_t min) { +void PhantomPhysicsComponent::SetMin(const float min) { m_Min = min; m_MinMax = true; m_EffectInfoDirty = true; } -void PhantomPhysicsComponent::SetMax(uint32_t max) { +void PhantomPhysicsComponent::SetMax(const float max) { m_Max = max; m_MinMax = true; m_EffectInfoDirty = true; } +void PhantomPhysicsComponent::SetVolumeEffect(std::vector args) { + for (auto& arg : args) { + arg.erase(0, arg.find_first_not_of(' ')); + arg.erase(arg.find_last_not_of(' ') + 1); + } + + SetPhysicsEffectActive(true); + ePhysicsEffectType effectType = ePhysicsEffectType::PUSH; + if (!args.empty()) { + std::transform(args.at(0).begin(), args.at(0).end(), args.at(0).begin(), ::tolower); + if (args.at(0) == "push") effectType = ePhysicsEffectType::PUSH; + else if (args.at(0) == "attract") effectType = ePhysicsEffectType::ATTRACT; + else if (args.at(0) == "repulse") effectType = ePhysicsEffectType::REPULSE; + else if (args.at(0) == "gravity") effectType = ePhysicsEffectType::GRAVITY_SCALE; + else if (args.at(0) == "friction") effectType = ePhysicsEffectType::FRICTION; + } + + SetEffectType(effectType); + if (args.size() > 1) SetDirectionalMultiplier(GeneralUtils::TryParse(args.at(1), 0.0f)); + if (args.size() > 4) { + // Live sent the direction with the amount as its length ("Push,50,0.91,-0.34,-0.22" as 45.68, -17.07, -11.04) + // and none when it was zero ("Repulse,150,0,0,0,true,25,100") + const NiPoint3 direction = GeneralUtils::TryParse(args.at(2), args.at(3), args.at(4), NiPoint3Constant::ZERO); + if (direction != NiPoint3Constant::ZERO) SetDirection(direction.Unitize()); + } + // Live sent the distances for "true" only: volumes with "1,20,50" went out without them + if (args.size() > 7 && args.at(5) == "true") { + SetMin(GeneralUtils::TryParse(args.at(6), 0.0f)); + SetMax(GeneralUtils::TryParse(args.at(7), 0.0f)); + } +} + void PhantomPhysicsComponent::SetPosition(const NiPoint3& pos) { PhysicsComponent::SetPosition(pos); if (m_dpEntity) m_dpEntity->SetPosition(pos); @@ -281,8 +314,8 @@ bool PhantomPhysicsComponent::OnGetObjectReportInfo(GameMessages::GetObjectRepor if (m_MinMax) { auto& minMaxInfo = info.PushDebug("Min Max Info"); - minMaxInfo.PushDebug("Min") = m_Min; - minMaxInfo.PushDebug("Max") = m_Max; + minMaxInfo.PushDebug("Min") = m_Min; + minMaxInfo.PushDebug("Max") = m_Max; } if (m_IsRespawnVolume) { diff --git a/dGame/dComponents/Physics/PhantomPhysicsComponent.h b/dGame/dComponents/Physics/PhantomPhysicsComponent.h index 5fdb9d8be..bf739f875 100644 --- a/dGame/dComponents/Physics/PhantomPhysicsComponent.h +++ b/dGame/dComponents/Physics/PhantomPhysicsComponent.h @@ -120,14 +120,16 @@ public: void SpawnVertices() const; /** - * Legacy stuff no clue what this does + * The distances the effect scales between; written only once one of them is set. + * LWOPhantomPhysicsComponent::Deserialize (0x00c34db0) keeps the least of the min and 1, and a max of at least that. */ - void SetMin(uint32_t min); + void SetMin(float min); + void SetMax(float max); /** - * Legacy stuff no clue what this does + * A trigger's SetPhysicsVolumeEffect: type, amount, direction x, y, z, use distance, min, max */ - void SetMax(uint32_t max); + void SetVolumeEffect(std::vector args); private: bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo); @@ -158,8 +160,8 @@ private: float m_DirectionalMultiplier; bool m_MinMax; - uint32_t m_Min; - uint32_t m_Max; + float m_Min; + float m_Max; /** * Whether or not this physics object is pointed in some direction diff --git a/dGame/dComponents/TriggerComponent.cpp b/dGame/dComponents/TriggerComponent.cpp index 7aa97f65c..dd1f7fd94 100644 --- a/dGame/dComponents/TriggerComponent.cpp +++ b/dGame/dComponents/TriggerComponent.cpp @@ -407,34 +407,7 @@ void TriggerComponent::HandleSetPhysicsVolumeEffect(Entity* targetEntity, std::v LOG_DEBUG("Phantom Physics component not found!"); return; } - phantomPhysicsComponent->SetPhysicsEffectActive(true); - ePhysicsEffectType effectType = ePhysicsEffectType::PUSH; - if (!argArray.empty()) { - std::transform(argArray.at(0).begin(), argArray.at(0).end(), argArray.at(0).begin(), ::tolower); //Transform to lowercase - if (argArray.at(0) == "push") effectType = ePhysicsEffectType::PUSH; - else if (argArray.at(0) == "attract") effectType = ePhysicsEffectType::ATTRACT; - else if (argArray.at(0) == "repulse") effectType = ePhysicsEffectType::REPULSE; - else if (argArray.at(0) == "gravity") effectType = ePhysicsEffectType::GRAVITY_SCALE; - else if (argArray.at(0) == "friction") effectType = ePhysicsEffectType::FRICTION; - } - - phantomPhysicsComponent->SetEffectType(effectType); - if (argArray.size() > 1) { - phantomPhysicsComponent->SetDirectionalMultiplier(GeneralUtils::TryParse(argArray.at(1), 0.0f)); - } - if (argArray.size() > 4) { - const NiPoint3 direction = - GeneralUtils::TryParse(argArray.at(2), argArray.at(3), argArray.at(4), NiPoint3Constant::ZERO); - - phantomPhysicsComponent->SetDirection(direction); - } - if (argArray.size() > 6) { - const uint32_t min = GeneralUtils::TryParse(argArray.at(6), 0); - phantomPhysicsComponent->SetMin(min); - - const uint32_t max = GeneralUtils::TryParse(argArray.at(7), 0); - phantomPhysicsComponent->SetMax(max); - } + phantomPhysicsComponent->SetVolumeEffect(argArray); Game::entityManager->SerializeEntity(targetEntity); } diff --git a/tests/dGameTests/dComponentsTests/ReplicaConstructionTests.cpp b/tests/dGameTests/dComponentsTests/ReplicaConstructionTests.cpp index c55bc7ada..7b0f3d016 100644 --- a/tests/dGameTests/dComponentsTests/ReplicaConstructionTests.cpp +++ b/tests/dGameTests/dComponentsTests/ReplicaConstructionTests.cpp @@ -21,6 +21,7 @@ #include "Item.h" #include "ModelComponent.h" #include "MovingPlatformComponent.h" +#include "PhantomPhysicsComponent.h" #include "SimplePhysicsComponent.h" #include "User.h" #include "eGameActivity.h" @@ -210,6 +211,111 @@ TEST_F(ReplicaConstructionTest, ControllablePhysicsConstructionCarriesJetPackMod EXPECT_EQ(construct(), plain); } +namespace { + struct VolumeEffect { + uint32_t type{}; + float amount{}; + bool hasDistance{}; + float min{}; + float max{}; + bool isDirectional{}; + NiPoint3 direction{}; + }; + + // A phantom volume looks its LOT up in the registry when it is made + void ConnectRegistry() { + if (CDClientDatabase::isConnected) return; + CDClientDatabase::Connect(":memory:"); + CDClientDatabase::ExecuteDML("CREATE TABLE ComponentsRegistry (id INTEGER, component_type INTEGER, component_id INTEGER);"); + } + + // The effect block of a phantom physics update made from a trigger's SetPhysicsVolumeEffect arguments + VolumeEffect ReadVolumeEffect(Entity& volume, const std::vector& args) { + auto* const phantom = volume.AddComponent(-1); + phantom->SetVolumeEffect(args); + RakNet::BitStream stream; + phantom->Serialize(stream, false); + + VolumeEffect effect; + bool flag{}; + stream.Read(flag); + if (flag) stream.IgnoreBits(7 * 32); // position and rotation + stream.Read(flag); + EXPECT_TRUE(flag); // the effect block + stream.Read(flag); + EXPECT_TRUE(flag); // active + stream.Read(effect.type); + stream.Read(effect.amount); + stream.Read(effect.hasDistance); + if (effect.hasDistance) { + stream.Read(effect.min); + stream.Read(effect.max); + } + stream.Read(effect.isDirectional); + if (effect.isDirectional) { + stream.Read(effect.direction.x); + stream.Read(effect.direction.y); + stream.Read(effect.direction.z); + } + EXPECT_EQ(stream.GetNumberOfUnreadBits(), 0u); + return effect; + } +} + +// Trigger volume effects as live sent them (phantom physics constructions in the live captures; the client reads +// them in LWOPhantomPhysicsComponent::Deserialize): the direction has the amount as its length, the distances go out +// for "true" only and as 1 and the max, and a zero direction is no direction. +TEST_F(ReplicaConstructionTest, PhantomPhysicsVolumeEffectsAsLive) { + ConnectRegistry(); + Entity pushWithDistance(1001, info); + auto effect = ReadVolumeEffect(pushWithDistance, { "Push", "50", "0.91", "-0.34", "-0.22", "true", "20", "50" }); + EXPECT_EQ(effect.type, 0u); + EXPECT_EQ(effect.amount, 50.0f); + EXPECT_TRUE(effect.hasDistance); + EXPECT_EQ(effect.min, 1.0f); + EXPECT_EQ(effect.max, 50.0f); + EXPECT_TRUE(effect.isDirectional); + EXPECT_NEAR(effect.direction.x, 45.68f, 0.01f); + EXPECT_NEAR(effect.direction.y, -17.07f, 0.01f); + EXPECT_NEAR(effect.direction.z, -11.04f, 0.01f); + + Entity pushFar(1002, info); + effect = ReadVolumeEffect(pushFar, { "Push", "200", "1", "0", "0", "true", "150", "175" }); + EXPECT_TRUE(effect.hasDistance); + EXPECT_EQ(effect.min, 1.0f); + EXPECT_EQ(effect.max, 175.0f); + EXPECT_EQ(effect.direction, NiPoint3(200.0f, 0.0f, 0.0f)); + + // "1" is not "true": no distances + Entity updraft(1003, info); + effect = ReadVolumeEffect(updraft, { "Push", "40.0", "0", "1", "0", "1", "20", "50" }); + EXPECT_EQ(effect.amount, 40.0f); + EXPECT_FALSE(effect.hasDistance); + EXPECT_TRUE(effect.isDirectional); + EXPECT_EQ(effect.direction, NiPoint3(0.0f, 40.0f, 0.0f)); + + Entity repulse(1004, info); + effect = ReadVolumeEffect(repulse, { "Repulse", "150", "0", "0", "0", "true", "25", "100" }); + EXPECT_EQ(effect.type, 2u); + EXPECT_EQ(effect.amount, 150.0f); + EXPECT_TRUE(effect.hasDistance); + EXPECT_EQ(effect.min, 1.0f); + EXPECT_EQ(effect.max, 100.0f); + EXPECT_FALSE(effect.isDirectional); + + Entity slanted(1005, info); + effect = ReadVolumeEffect(slanted, { "Push", " 35", " .79", " 0", " .61" }); + EXPECT_FALSE(effect.hasDistance); + EXPECT_NEAR(effect.direction.x, 27.70f, 0.01f); + EXPECT_NEAR(effect.direction.z, 21.39f, 0.01f); + + Entity friction(1006, info); + effect = ReadVolumeEffect(friction, { "Friction", "0" }); + EXPECT_EQ(effect.type, 4u); + EXPECT_FALSE(effect.hasDistance); + EXPECT_FALSE(effect.isDirectional); +} + // A guild change (docs/Guilds.md) is written once: the client reads guild id, a u8 length and 16-bit characters, and // redraws the name billboard every time it reads a name. TEST_F(ReplicaConstructionTest, CharacterGuildChangeIsWrittenOnce) {