diff --git a/dGame/dComponents/Activity/ActivityComponent.cpp b/dGame/dComponents/Activity/ActivityComponent.cpp index 8279d201a..276a74243 100644 --- a/dGame/dComponents/Activity/ActivityComponent.cpp +++ b/dGame/dComponents/Activity/ActivityComponent.cpp @@ -79,8 +79,11 @@ void ActivityComponent::LoadActivityData(const int32_t activityId) { } void ActivityComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { - outBitStream.Write(m_DirtyActivityInfo); - if (m_DirtyActivityInfo) { + // A construction always carries the players: it is the only way a client coming into range learns who is in the + // activity (LWOActivityComponent::Deserialize 0x00d002b0), and live wrote the block, empty or not, on every one + const bool writeActivityInfo = m_DirtyActivityInfo || bIsInitialUpdate; + outBitStream.Write(writeActivityInfo); + if (writeActivityInfo) { outBitStream.Write(m_ActivityPlayers.size()); if (!m_ActivityPlayers.empty()) { for (const auto& [playerID, values] : m_ActivityPlayers) { diff --git a/tests/dGameTests/dComponentsTests/ReplicaConstructionTests.cpp b/tests/dGameTests/dComponentsTests/ReplicaConstructionTests.cpp index 7a4d87703..8d082274f 100644 --- a/tests/dGameTests/dComponentsTests/ReplicaConstructionTests.cpp +++ b/tests/dGameTests/dComponentsTests/ReplicaConstructionTests.cpp @@ -21,6 +21,7 @@ #include "LevelProgressionComponent.h" #include "LUPExhibitComponent.h" #include "PlayerForcedMovementComponent.h" +#include "ScriptedActivityComponent.h" #include "Item.h" #include "ModelComponent.h" #include "MovingPlatformComponent.h" @@ -439,6 +440,47 @@ TEST_F(ReplicaConstructionTest, CharacterGuildChangeIsWrittenOnce) { player.SetCharacter(nullptr); } +// A client that comes into range of an activity is told its players, whether or not they changed since the last +// update. Live finish line constructions (LOT 4967): flag 1 and the count in 40 of 40, one of them with a player. +TEST_F(ReplicaConstructionTest, AnActivityConstructionAlwaysCarriesItsPlayers) { + ConnectRegistry(); + Entity activity(1010, info); + auto* const component = activity.AddComponent(-1); + const LWOOBJID player = 1152921506064087006; + component->SetActivityValue(player, 1, 42.0f); + + RakNet::BitStream update; + component->Serialize(update, false); + RakNet::BitStream idle; + component->Serialize(idle, false); + EXPECT_EQ(Bits(idle), "0"); // nothing changed since + + RakNet::BitStream construction; + component->Serialize(construction, true); + EXPECT_EQ(Bits(construction), Bits(update)); + EXPECT_TRUE(construction.ReadBit()); + uint32_t count{}; + ASSERT_TRUE(construction.Read(count)); + EXPECT_EQ(count, 1u); + LWOOBJID id{}; + ASSERT_TRUE(construction.Read(id)); + EXPECT_EQ(id, player); + for (uint32_t i = 0; i < 10; i++) { + float value{}; + ASSERT_TRUE(construction.Read(value)); + EXPECT_EQ(value, i == 1 ? 42.0f : 0.0f); + } + EXPECT_EQ(construction.GetNumberOfUnreadBits(), 0u); + + // Without players: the flag and a count of 0 + component->RemoveActivityPlayerData(player); + RakNet::BitStream cleared; + component->Serialize(cleared, false); + RakNet::BitStream empty; + component->Serialize(empty, true); + EXPECT_EQ(Bits(empty), "1" + std::string(32, '0')); +} + // Run speed modifiers stack as in LWOControllablePhysComponent::msgAddRunspeedModifier (0x00be0af0) and // msgRemoveRunSpeedModifier (0x00be0720): the lowest slow wins, otherwise the highest boost; 500 and anything over 1000 // count for nothing