diff --git a/dGame/dComponents/BaseCombatAIComponent.cpp b/dGame/dComponents/BaseCombatAIComponent.cpp index 986bd7471..abb402483 100644 --- a/dGame/dComponents/BaseCombatAIComponent.cpp +++ b/dGame/dComponents/BaseCombatAIComponent.cpp @@ -551,7 +551,7 @@ void BaseCombatAIComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsI if (m_DirtyStateOrTarget || bIsInitialUpdate) { outBitStream.Write(m_State); outBitStream.Write(m_Target); - m_DirtyStateOrTarget = false; + if (!bIsInitialUpdate) m_DirtyStateOrTarget = false; } } diff --git a/dGame/dComponents/Character/LevelProgressionComponent.cpp b/dGame/dComponents/Character/LevelProgressionComponent.cpp index c8ffd2f05..30b374f39 100644 --- a/dGame/dComponents/Character/LevelProgressionComponent.cpp +++ b/dGame/dComponents/Character/LevelProgressionComponent.cpp @@ -41,7 +41,8 @@ void LevelProgressionComponent::LoadFromSaveData(const tinyxml2::XMLDocument& do void LevelProgressionComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { outBitStream.Write(bIsInitialUpdate || m_DirtyLevelInfo); if (bIsInitialUpdate || m_DirtyLevelInfo) outBitStream.Write(m_Level); - m_DirtyLevelInfo = false; + // A construction goes to one client; the players already watching still need a waiting change + if (!bIsInitialUpdate) m_DirtyLevelInfo = false; } void LevelProgressionComponent::HandleLevelUp() { diff --git a/dGame/dComponents/Character/PlayerForcedMovementComponent.cpp b/dGame/dComponents/Character/PlayerForcedMovementComponent.cpp index 82c46a6d5..6a0c8fbe4 100644 --- a/dGame/dComponents/Character/PlayerForcedMovementComponent.cpp +++ b/dGame/dComponents/Character/PlayerForcedMovementComponent.cpp @@ -14,7 +14,7 @@ void PlayerForcedMovementComponent::Serialize(RakNet::BitStream& outBitStream, b outBitStream.Write(m_PlayerOnRail); outBitStream.Write(m_ShowBillboard); } - m_DirtyInfo = false; + if (!bIsInitialUpdate) m_DirtyInfo = false; } void PlayerForcedMovementComponent::Allocator(Entity& entity, ObjectLoadState& state) { diff --git a/dGame/dComponents/Character/PossessorComponent.cpp b/dGame/dComponents/Character/PossessorComponent.cpp index 8e9a3c95a..56d72acdc 100644 --- a/dGame/dComponents/Character/PossessorComponent.cpp +++ b/dGame/dComponents/Character/PossessorComponent.cpp @@ -38,7 +38,7 @@ PossessorComponent::~PossessorComponent() { void PossessorComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { outBitStream.Write(m_DirtyPossesor || bIsInitialUpdate); if (m_DirtyPossesor || bIsInitialUpdate) { - m_DirtyPossesor = false; + if (!bIsInitialUpdate) m_DirtyPossesor = false; outBitStream.Write(m_Possessable != LWOOBJID_EMPTY); if (m_Possessable != LWOOBJID_EMPTY) { outBitStream.Write(m_Possessable); diff --git a/dGame/dComponents/PossessableComponent.cpp b/dGame/dComponents/PossessableComponent.cpp index 86330f5c4..61fb6d54a 100644 --- a/dGame/dComponents/PossessableComponent.cpp +++ b/dGame/dComponents/PossessableComponent.cpp @@ -37,7 +37,7 @@ void PossessableComponent::LoadDataFromTemplate() { void PossessableComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { outBitStream.Write(m_DirtyPossessable || bIsInitialUpdate); if (m_DirtyPossessable || bIsInitialUpdate) { - m_DirtyPossessable = false; // reset flag + if (!bIsInitialUpdate) m_DirtyPossessable = false; // reset flag outBitStream.Write(m_Possessor != LWOOBJID_EMPTY); if (m_Possessor != LWOOBJID_EMPTY) outBitStream.Write(m_Possessor); @@ -45,7 +45,7 @@ void PossessableComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsIn if (m_AnimationFlag != eAnimationFlags::IDLE_NONE) outBitStream.Write(m_AnimationFlag); outBitStream.Write(m_ImmediatelyDepossess); - m_ImmediatelyDepossess = false; // reset flag + if (!bIsInitialUpdate) m_ImmediatelyDepossess = false; // reset flag } } diff --git a/tests/dGameTests/dComponentsTests/ReplicaConstructionTests.cpp b/tests/dGameTests/dComponentsTests/ReplicaConstructionTests.cpp index a3f6913ee..b0e9b7970 100644 --- a/tests/dGameTests/dComponentsTests/ReplicaConstructionTests.cpp +++ b/tests/dGameTests/dComponentsTests/ReplicaConstructionTests.cpp @@ -18,6 +18,8 @@ #include "Entity.h" #include "InventoryComponent.h" #include "ItemComponent.h" +#include "LevelProgressionComponent.h" +#include "PlayerForcedMovementComponent.h" #include "Item.h" #include "ModelComponent.h" #include "MovingPlatformComponent.h" @@ -338,6 +340,36 @@ TEST_F(ReplicaConstructionTest, PhantomPhysicsConstructionKeepsThePendingEffect) EXPECT_LE(again.GetNumberOfBitsUsed(), idle); } +// A construction goes to the one client the object came into range of. It must not use up a change the clients +// already watching are still waiting for (the level, rail and billboard flags are each read behind a flag bit). +TEST_F(ReplicaConstructionTest, AConstructionKeepsPendingLevelAndForcedMovementChanges) { + Entity player(1152921506064087008, info); + auto* const level = player.AddComponent(-1); + auto* const forcedMovement = player.AddComponent(-1); + + level->SetLevel(7); + forcedMovement->SetPlayerOnRail(true); + RakNet::BitStream construction; + level->Serialize(construction, true); + forcedMovement->Serialize(construction, true); + + RakNet::BitStream update; + level->Serialize(update, false); + forcedMovement->Serialize(update, false); + RakNet::BitStream expected; + expected.Write1(); + expected.Write(7); + expected.Write1(); + expected.Write1(); // on rail + expected.Write0(); // show billboard + ExpectSameBits(update, expected); + + RakNet::BitStream again; + level->Serialize(again, false); + forcedMovement->Serialize(again, false); + EXPECT_EQ(again.GetNumberOfBitsUsed(), 2u); +} + // 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) {