diff --git a/dGame/dComponents/InventoryComponent.h b/dGame/dComponents/InventoryComponent.h index 66305108c..202399065 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -301,6 +301,11 @@ public: */ void PopEquippedItems(); + /** + * Marks the equipped items to be sent again with the next serialization (ResyncEquipment) + */ + void ResyncEquipment() { m_Dirty = true; } + /** * Returns if the entity has an item equipped of the given lot * @param lot to lot to search for diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index 9e4767855..9813c4b4a 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -98,6 +98,7 @@ namespace { { REQUEST_MOVE_ITEM_BETWEEN_INVENTORY_TYPES, []() { return std::make_unique(); } }, { PUSH_EQUIPPED_ITEMS_STATE, []() { return std::make_unique(); } }, { POP_EQUIPPED_ITEMS_STATE, []() { return std::make_unique(); } }, + { RESYNC_EQUIPMENT, []() { return std::make_unique(); } }, { CLIENT_ITEM_CONSUMED, []() { return std::make_unique(); } }, { USE_NON_EQUIPMENT_ITEM, []() { return std::make_unique(); } }, { SET_CONSUMABLE_ITEM, []() { return std::make_unique(); } }, diff --git a/dGame/dGameMessages/InventoryMessages.cpp b/dGame/dGameMessages/InventoryMessages.cpp index ea9e2f058..83fbf5693 100644 --- a/dGame/dGameMessages/InventoryMessages.cpp +++ b/dGame/dGameMessages/InventoryMessages.cpp @@ -422,6 +422,13 @@ namespace GameMessages { Game::entityManager->SerializeEntity(&entity); // so it updates on client side } + void ResyncEquipment::Handle(Entity& entity, const SystemAddress& sysAddr) { + auto* const inventoryComponent = entity.GetComponent(); + if (!inventoryComponent) return; + inventoryComponent->ResyncEquipment(); + Game::entityManager->SerializeEntity(&entity); + } + void UpdateInventoryGroup::Serialize(RakNet::BitStream& bitStream) const { BitStreamUtils::WriteLengthPrefixed(bitStream, action); BitStreamUtils::WriteLengthPrefixed(bitStream, groupID); diff --git a/dGame/dGameMessages/InventoryMessages.h b/dGame/dGameMessages/InventoryMessages.h index 136761278..ad6e7925d 100644 --- a/dGame/dGameMessages/InventoryMessages.h +++ b/dGame/dGameMessages/InventoryMessages.h @@ -288,6 +288,13 @@ namespace GameMessages { void Handle(Entity& entity, const SystemAddress& sysAddr) override; }; + // Client -> server. No payload. The property editor sends it after the player picks up or sets down a model + // they carry (live: right after PlaceModelResponse). Live answered with a serialization of the player, no message. + struct ResyncEquipment : public NetGameMsg { + ResyncEquipment() : NetGameMsg(MessageType::Game::RESYNC_EQUIPMENT) {} + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + }; + // Client -> server. Adds, modifies or removes an inventory group. struct UpdateInventoryGroup : public NetGameMsg { UpdateInventoryGroup() : NetGameMsg(MessageType::Game::UPDATE_INVENTORY_GROUP) {} diff --git a/tests/dGameTests/dGameMessagesTests/ClientMessagesTests.cpp b/tests/dGameTests/dGameMessagesTests/ClientMessagesTests.cpp index 0317725a0..9d332850b 100644 --- a/tests/dGameTests/dGameMessagesTests/ClientMessagesTests.cpp +++ b/tests/dGameTests/dGameMessagesTests/ClientMessagesTests.cpp @@ -10,6 +10,8 @@ #include "SkillComponent.h" #include "SkillMessages.h" #include "Entity.h" +#include "InventoryComponent.h" +#include "InventoryMessages.h" #include "PlayerMessages.h" #include @@ -121,3 +123,24 @@ TEST_F(ClientMessagesTests, CasterDeadEndsTheDeadCastersSkill) { EXPECT_FALSE(skills->HasSkill(1)); EXPECT_TRUE(skills->HasSkill(2)); } + +// ResyncEquipment (1238): no payload (59 live packets). The next serialization carries the equipped items again. +TEST_F(ClientMessagesTests, ResyncEquipmentResendsTheEquipment) { + auto msg = FromLiveClientCapture(std::string(PLAYER_HEADER) + "d604"); + CDClientDatabase::Connect(":memory:"); // the inventory looks its component up + CDClientDatabase::ExecuteDML("CREATE TABLE ComponentsRegistry (id INTEGER, component_type INTEGER, component_id INTEGER);"); + auto* const inventory = player->AddComponent(-1); + RakNet::BitStream first; + inventory->Serialize(first, false); // clears the dirty flag it starts with + RakNet::BitStream clean; + inventory->Serialize(clean, false); + bool equipmentSent = true; + ASSERT_TRUE(clean.Read(equipmentSent)); + EXPECT_FALSE(equipmentSent); + + msg.Handle(*player, UNASSIGNED_SYSTEM_ADDRESS); + RakNet::BitStream resent; + inventory->Serialize(resent, false); + ASSERT_TRUE(resent.Read(equipmentSent)); + EXPECT_TRUE(equipmentSent); +}