diff --git a/dGame/dComponents/CharacterComponent.cpp b/dGame/dComponents/CharacterComponent.cpp index 6d7eb5fe9..363a115e4 100644 --- a/dGame/dComponents/CharacterComponent.cpp +++ b/dGame/dComponents/CharacterComponent.cpp @@ -228,8 +228,11 @@ void CharacterComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInit } } - outBitStream.Write(m_DirtyGMInfo); - if (m_DirtyGMInfo) { + // Live wrote the GM, current-activity and social blocks on every player construction (civilians included), + // and afterwards only when they changed. + const bool writeGMInfo = bIsInitialUpdate || m_DirtyGMInfo; + outBitStream.Write(writeGMInfo); + if (writeGMInfo) { outBitStream.Write(m_PvpEnabled); outBitStream.Write(m_IsGM); outBitStream.Write(m_GMLevel); @@ -237,15 +240,16 @@ void CharacterComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInit outBitStream.Write(m_EditorLevel); } - outBitStream.Write(m_DirtyCurrentActivity); - if (m_DirtyCurrentActivity) outBitStream.Write(m_CurrentActivity); + const bool writeCurrentActivity = bIsInitialUpdate || m_DirtyCurrentActivity; + outBitStream.Write(writeCurrentActivity); + if (writeCurrentActivity) outBitStream.Write(m_CurrentActivity); - outBitStream.Write(m_DirtySocialInfo); - if (m_DirtySocialInfo) { + const bool writeSocialInfo = bIsInitialUpdate || m_DirtySocialInfo; + outBitStream.Write(writeSocialInfo); + if (writeSocialInfo) { outBitStream.Write(m_GuildID); - outBitStream.Write(m_GuildName.size()); - if (!m_GuildName.empty()) - outBitStream.WriteBits(reinterpret_cast(m_GuildName.c_str()), static_cast(m_GuildName.size()) * sizeof(wchar_t) * 8); + outBitStream.Write(m_GuildName.size()); + for (const char16_t character : m_GuildName) outBitStream.Write(character); outBitStream.Write(m_IsLEGOClubMember); outBitStream.Write(m_CountryCode); diff --git a/dGame/dComponents/CharacterComponent.h b/dGame/dComponents/CharacterComponent.h index 78afb19ad..86a7613f7 100644 --- a/dGame/dComponents/CharacterComponent.h +++ b/dGame/dComponents/CharacterComponent.h @@ -452,7 +452,7 @@ private: /** * The guild this character is in */ - LWOOBJID m_GuildID; + LWOOBJID m_GuildID = LWOOBJID_EMPTY; /** * The name of the guild this character is in diff --git a/tests/dGameTests/dComponentsTests/ReplicaConstructionTests.cpp b/tests/dGameTests/dComponentsTests/ReplicaConstructionTests.cpp index 74a66e9df..6e114a640 100644 --- a/tests/dGameTests/dComponentsTests/ReplicaConstructionTests.cpp +++ b/tests/dGameTests/dComponentsTests/ReplicaConstructionTests.cpp @@ -84,3 +84,48 @@ TEST_F(ReplicaConstructionTest, TimeSinceCreatedOnServerIsTheObjectsAge) { EXPECT_LT(later, 60000u); EXPECT_GE(entity.GetTimeSinceCreatedMs(), later); } + +// A civilian's character component: live always wrote the GM, current-activity and social blocks on construction +// (9,726 LOT 1 constructions: is_gm false, gm_level 0, current_activity Some(None), social_info always Some with guild 0 +// and an empty guild name). DLU wrote them only after something dirtied them, so a civilian got none of them. +TEST_F(ReplicaConstructionTest, CharacterConstructionAlwaysWritesGmActivityAndSocialBlocks) { + User user(UNASSIGNED_SYSTEM_ADDRESS, "tester", "key"); + Character character(1, &user); + info.lot = 1; + Entity player(1152921506064087003, info); + player.SetCharacter(&character); + character.SetEntity(&player); + auto* const component = player.AddComponent(-1, &character, UNASSIGNED_SYSTEM_ADDRESS); + + RakNet::BitStream construction; + component->Serialize(construction, true); + + // Everything before the GM block: 4 absent claim codes, 10 u32 appearance fields, 4 u64s (account, last logout, + // prop mod display time, u-score), the free-trial bit, 27 u64 statistics and the 2-bit transition state. + constexpr uint32_t beforeGm = 4 + 10 * 32 + 4 * 64 + 1 + 27 * 64 + 2; + RakNet::BitStream tail; + Tail(construction, beforeGm, tail); + + RakNet::BitStream expected; + expected.Write1(); // gm_pvp_info Some + expected.Write0(); // pvp_enabled + expected.Write0(); // is_gm + expected.Write(0); // gm_level + expected.Write0(); // editor_enabled + expected.Write(0); // editor_level + expected.Write1(); // current_activity Some + expected.Write(0); // GameActivity::None + expected.Write1(); // social_info Some + expected.Write(0); // guild_id + expected.Write(0); // guild_name "" + expected.Write1(); // is_lego_club_member (DLU treats everyone as a member) + expected.Write(0); // country code + ExpectSameBits(tail, expected); + + // A serialization with nothing changed still writes none of them + RakNet::BitStream serialization; + component->Serialize(serialization, false); + EXPECT_EQ(serialization.GetNumberOfBitsUsed(), 3u); + + player.SetCharacter(nullptr); +}