From 8dd279cb45269800e126c30472d5e8e83afdb43f Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Tue, 10 Nov 2026 13:20:36 -0600 Subject: [PATCH] fix: write a collectible's id on construction only LWOCollectibleComponent::Deserialize (0x00c45ec0) reads the 16-bit id on construction and nothing on an update. DLU wrote the id on every serialization, so the client read 16 stray bits as the flag bits of the components after it. The replica decoder follows. Co-Authored-By: Claude Opus 5.5 --- dGame/dComponents/CollectibleComponent.cpp | 3 ++- dGame/dUtilities/ReplicaDecoder.cpp | 3 ++- .../CollectibleComponentTests.cpp | 19 +++++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/dGame/dComponents/CollectibleComponent.cpp b/dGame/dComponents/CollectibleComponent.cpp index fe9d2759a..3d2137e6c 100644 --- a/dGame/dComponents/CollectibleComponent.cpp +++ b/dGame/dComponents/CollectibleComponent.cpp @@ -42,8 +42,9 @@ bool CollectibleComponent::CountsFor(const Entity& player) const { } } +// LWOCollectibleComponent::Deserialize (0x00c45ec0) reads the id on construction only void CollectibleComponent::Serialize(RakNet::BitStream& outBitStream, bool isConstruction) { - outBitStream.Write(GetCollectibleId()); + if (isConstruction) outBitStream.Write(GetCollectibleId()); } bool CollectibleComponent::MsgGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportMsg) { diff --git a/dGame/dUtilities/ReplicaDecoder.cpp b/dGame/dUtilities/ReplicaDecoder.cpp index 6a2acfde4..6c5f4328a 100644 --- a/dGame/dUtilities/ReplicaDecoder.cpp +++ b/dGame/dUtilities/ReplicaDecoder.cpp @@ -389,7 +389,8 @@ namespace { } if (r.Bit()) j["onThreatList"] = r.Bit(); } }, - { COLLECTIBLE, [](Reader& r, json& j, bool, const auto&) { j["collectibleID"] = r.Get(); } }, + // LWOCollectibleComponent::Deserialize: the id on construction only + { COLLECTIBLE, [](Reader& r, json& j, bool initial, const auto&) { if (initial) j["collectibleID"] = r.Get(); } }, // LWOPetComponent::Deserialize: everything under the dirty bit, the names on updates too { PET, [](Reader& r, json& j, bool, const auto&) { if (!r.Bit()) return; diff --git a/tests/dGameTests/dComponentsTests/CollectibleComponentTests.cpp b/tests/dGameTests/dComponentsTests/CollectibleComponentTests.cpp index 930265c76..74f16c1c8 100644 --- a/tests/dGameTests/dComponentsTests/CollectibleComponentTests.cpp +++ b/tests/dGameTests/dComponentsTests/CollectibleComponentTests.cpp @@ -2,6 +2,7 @@ // the player has that mission. #include "GameDependencies.h" +#include "BitStream.h" #include "GameMessages.h" #include "CDClientDatabase.h" #include "CDClientManager.h" @@ -60,6 +61,24 @@ TEST_F(CollectibleComponentTests, RequirementMissionIsRead) { EXPECT_EQ(other.AddComponent(999, 5)->GetRequirementMission(), -1); // no row } +// LWOCollectibleComponent::Deserialize reads the 16-bit id on construction and nothing on an update. DLU wrote the id +// on updates too, which the client read as the next components' flag bits. +TEST_F(CollectibleComponentTests, TheIdIsWrittenOnConstructionOnly) { + Entity object(22, info); + auto* const collectible = object.AddComponent(78, 5); + + RakNet::BitStream construction; + collectible->Serialize(construction, true); + ASSERT_EQ(construction.GetNumberOfBitsUsed(), 16u); + int16_t id{}; + construction.Read(id); + EXPECT_EQ(id, 5); + + RakNet::BitStream update; + collectible->Serialize(update, false); + EXPECT_EQ(update.GetNumberOfBitsUsed(), 0u); +} + TEST_F(CollectibleComponentTests, AchievementsAndMissingMissionsAlwaysCount) { Entity player(42, info); player.AddComponent(-1);