From 13be051af9a7009fb07d3a44cad815fd12bdda4a Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Fri, 16 Oct 2026 20:09:54 -0500 Subject: [PATCH] fix: write model behaviors only for property and inventory models The client makes the mutable model component, which reads the behaviors and editing info, only when the object's config has propertyObjectID or inInventory (ObjectLoader2::LoadModelBehaviorsComponent); every other model gets the plain one, which reads only the model block. Writing the behaviors for every model made the client read them as the next component's data. Co-Authored-By: Claude Opus 5.5 --- dGame/dComponents/ModelComponent.cpp | 5 ++ .../dComponentsTests/ModelComponentTests.cpp | 55 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 tests/dGameTests/dComponentsTests/ModelComponentTests.cpp diff --git a/dGame/dComponents/ModelComponent.cpp b/dGame/dComponents/ModelComponent.cpp index b09c655a0..eb5f06927 100644 --- a/dGame/dComponents/ModelComponent.cpp +++ b/dGame/dComponents/ModelComponent.cpp @@ -156,6 +156,11 @@ void ModelComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialU outBitStream.Write(m_OriginalPosition); // Original position outBitStream.Write(m_OriginalRotation); // Original rotation + // The client makes the mutable model component, which reads the behaviors and editing info, only for objects whose + // config (the settings sent with a model) has propertyObjectID or inInventory; the plain one it makes for every + // other model reads only the block above, as live wrote it + if (!m_Parent->HasVar(u"propertyObjectID") && !m_Parent->GetVar(u"inInventory")) return; + outBitStream.Write1(); // We are writing behavior info outBitStream.Write(m_Behaviors.size()); // Number of behaviors outBitStream.Write(m_IsPaused); // Is this model paused diff --git a/tests/dGameTests/dComponentsTests/ModelComponentTests.cpp b/tests/dGameTests/dComponentsTests/ModelComponentTests.cpp new file mode 100644 index 000000000..23373fa93 --- /dev/null +++ b/tests/dGameTests/dComponentsTests/ModelComponentTests.cpp @@ -0,0 +1,55 @@ +#include "GameDependencies.h" + +#include "BitStream.h" +#include "CDClientManager.h" +#include "CDComponentsRegistryTable.h" +#include "Entity.h" +#include "GameMessages.h" +#include "ModelComponent.h" + +#include + +class ModelComponentTests : public GameDependenciesTest { +protected: + void SetUp() override { + SetUpDependencies(); + CDClientManager::GetEntriesMutable().insert_or_assign(static_cast(info.lot), 0); + } + + void TearDown() override { + TearDownDependencies(); + } + + // The bits a model's serialization writes + static uint32_t SerializedBits(Entity& entity) { + auto* model = entity.AddComponent(-1); + RakNet::BitStream bitStream; + model->Serialize(bitStream, true); + return bitStream.GetNumberOfBitsUsed(); + } +}; + +// Item block (1 + 64 + 32 + 1), model block (1 + 1 + 32 + 96 + 128) +constexpr uint32_t ModelBlockBits = 98 + 258; +// Behavior info: flag, count, paused, and on the initial update the editing info flag +constexpr uint32_t BehaviorBits = 1 + 32 + 1 + 1; + +// The client reads the behaviors only for property and inventory models (ObjectLoader2::LoadModelBehaviorsComponent) +TEST_F(ModelComponentTests, OtherModelsHaveNoBehaviorInfo) { + Entity entity(1, info); + EXPECT_EQ(SerializedBits(entity), ModelBlockBits); +} + +TEST_F(ModelComponentTests, PropertyModelsHaveBehaviorInfo) { + EntityInfo propertyInfo = info; + propertyInfo.settings.Insert(u"propertyObjectID", true); + Entity entity(1, propertyInfo); + EXPECT_EQ(SerializedBits(entity), ModelBlockBits + BehaviorBits); +} + +TEST_F(ModelComponentTests, InventoryModelsHaveBehaviorInfo) { + EntityInfo inventoryInfo = info; + inventoryInfo.settings.Insert(u"inInventory", true); + Entity entity(1, inventoryInfo); + EXPECT_EQ(SerializedBits(entity), ModelBlockBits + BehaviorBits); +}