From 102abc32f53cf713ad2948d2ce4a9d0d5952c01a Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Tue, 10 Nov 2026 13:07:28 -0600 Subject: [PATCH] fix: a character's activity or PVP change is serialized once LWOCharacterComponent::Deserialize (0x00ca3a40) reads the GM and PVP block and the current activity each behind its own flag bit. DLU never cleared the two dirty flags, so after the first quick build or pet taming every later serialization of the player carried both blocks again. They are cleared after an update now, as the guild block's is. Co-Authored-By: Claude Opus 5.5 --- .../Character/CharacterComponent.cpp | 6 ++- .../ReplicaConstructionTests.cpp | 43 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/dGame/dComponents/Character/CharacterComponent.cpp b/dGame/dComponents/Character/CharacterComponent.cpp index f363841e9..ebc45df96 100644 --- a/dGame/dComponents/Character/CharacterComponent.cpp +++ b/dGame/dComponents/Character/CharacterComponent.cpp @@ -245,11 +245,15 @@ void CharacterComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInit outBitStream.Write(m_GMLevel); outBitStream.Write(m_EditorEnabled); outBitStream.Write(m_EditorLevel); + if (!bIsInitialUpdate) m_DirtyGMInfo = false; } const bool writeCurrentActivity = bIsInitialUpdate || m_DirtyCurrentActivity; outBitStream.Write(writeCurrentActivity); - if (writeCurrentActivity) outBitStream.Write(m_CurrentActivity); + if (writeCurrentActivity) { + outBitStream.Write(m_CurrentActivity); + if (!bIsInitialUpdate) m_DirtyCurrentActivity = false; + } const bool writeSocialInfo = bIsInitialUpdate || m_DirtySocialInfo; outBitStream.Write(writeSocialInfo); diff --git a/tests/dGameTests/dComponentsTests/ReplicaConstructionTests.cpp b/tests/dGameTests/dComponentsTests/ReplicaConstructionTests.cpp index 798089e8c..1672577cb 100644 --- a/tests/dGameTests/dComponentsTests/ReplicaConstructionTests.cpp +++ b/tests/dGameTests/dComponentsTests/ReplicaConstructionTests.cpp @@ -23,6 +23,7 @@ #include "MovingPlatformComponent.h" #include "SimplePhysicsComponent.h" #include "User.h" +#include "eGameActivity.h" #include "eReplicaComponentType.h" #include "eReplicaPacketType.h" @@ -130,6 +131,48 @@ TEST_F(ReplicaConstructionTest, CharacterConstructionAlwaysWritesGmActivityAndSo player.SetCharacter(nullptr); } +// A current-activity or PVP change is written once, as a guild change is: LWOCharacterComponent::Deserialize reads each +// block behind its own flag bit. +TEST_F(ReplicaConstructionTest, CharacterActivityAndPvpChangesAreWrittenOnce) { + User user(UNASSIGNED_SYSTEM_ADDRESS, "tester", "key"); + Character character(1, &user); + info.lot = 1; + Entity player(1152921506064087005, info); + player.SetCharacter(&character); + character.SetEntity(&player); + auto* const component = player.AddComponent(-1, &character, UNASSIGNED_SYSTEM_ADDRESS); + + component->SetCurrentActivity(eGameActivity::QUICKBUILDING); + component->SetPvpEnabled(true); + RakNet::BitStream serialization; + component->Serialize(serialization, false); + RakNet::BitStream expected; + expected.Write1(); // gm_pvp_info + expected.Write1(); // pvp_enabled + expected.Write0(); // is_gm + expected.Write(0); // gm_level + expected.Write0(); // editor_enabled + expected.Write(0); // editor_level + expected.Write1(); // current_activity + expected.Write(eGameActivity::QUICKBUILDING); + expected.Write0(); // social_info + ExpectSameBits(serialization, expected); + + RakNet::BitStream again; + component->Serialize(again, false); + EXPECT_EQ(again.GetNumberOfBitsUsed(), 3u); + + // A construction in between (another player coming into range) doesn't swallow a pending change + component->SetCurrentActivity(eGameActivity::NONE); + RakNet::BitStream construction; + component->Serialize(construction, true); + RakNet::BitStream after; + component->Serialize(after, false); + EXPECT_EQ(after.GetNumberOfBitsUsed(), 3u + 32u); + + player.SetCharacter(nullptr); +} + // 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) {