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 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-11-10 13:07:28 -06:00
parent 9bbe335acc
commit 102abc32f5
2 changed files with 48 additions and 1 deletions

View File

@@ -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);

View File

@@ -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<CharacterComponent>(-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<uint8_t>(0); // gm_level
expected.Write0(); // editor_enabled
expected.Write<uint8_t>(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) {