From 5bf637b02eafb245a8896e49e8f0678d6b973ea1 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Fri, 2 Oct 2026 10:21:15 -0500 Subject: [PATCH] fix(inventory): the ridden object and the mount item can't be deleted, as the client LWOInventoryComponent_Common::CanRemoveFromInventory (0x00ce0d20) refuses the possessed object (GetPossessedObject) and the mount item (GetMountInventoryID) before the operator bypass and the deletion restrictions. The server let a player delete the mount they were riding. Co-Authored-By: Claude Opus 5.5 --- dGame/dComponents/InventoryComponent.cpp | 4 ++++ dGame/dComponents/InventoryComponent.h | 3 ++- .../DeletionRestrictionTests.cpp | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index ae850fdd3..304524699 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -2405,6 +2405,10 @@ namespace { } bool InventoryComponent::CanDelete(const Item& item) const { + // The object being ridden and the mount item are never deleted, not even by operators + const auto* const possessor = m_Parent->GetComponent(); + if (possessor && (possessor->GetPossessable() == item.GetId() || possessor->GetMountItemID() == item.GetId())) return false; + if (m_Parent->GetGMLevel() == eGameMasterLevel::OPERATOR) return true; const auto row = static_cast(item.GetInfo().delResIndex); diff --git a/dGame/dComponents/InventoryComponent.h b/dGame/dComponents/InventoryComponent.h index 363c9de61..c6074cde8 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -478,7 +478,8 @@ public: /** * Whether the player may delete this item: its ItemComponent's DeletionRestrictions row, as the client checks it - * (LWOInventoryComponent_Common::CanRemoveFromInventory @ 00ce0d20 in 1.10.64). Operators may delete anything. + * (LWOInventoryComponent_Common::CanRemoveFromInventory @ 00ce0d20 in 1.10.64). The possessed object and the mount + * item are never deleted; otherwise operators may delete anything. */ bool CanDelete(const Item& item) const; void OnMoveItemInInventory(const GameMessages::MoveItemInInventory& msg); diff --git a/tests/dGameTests/dComponentsTests/DeletionRestrictionTests.cpp b/tests/dGameTests/dComponentsTests/DeletionRestrictionTests.cpp index c4faced69..00d06e26c 100644 --- a/tests/dGameTests/dComponentsTests/DeletionRestrictionTests.cpp +++ b/tests/dGameTests/dComponentsTests/DeletionRestrictionTests.cpp @@ -7,6 +7,7 @@ #include "Entity.h" #include "InventoryComponent.h" #include "Item.h" +#include "PossessorComponent.h" #include "eGameMasterLevel.h" #include "eReplicaComponentType.h" @@ -123,3 +124,18 @@ TEST_F(DeletionRestrictionTests, AnyAndAllOfOtherRows) { EXPECT_TRUE(inventory->CanDelete(*any)); EXPECT_FALSE(inventory->CanDelete(*all)); } + +TEST_F(DeletionRestrictionTests, ThePossessedObjectAndTheMountItemAreNeverDeleted) { + auto* const ridden = Give(1000); + auto* const mount = Give(1000); + auto* const other = Give(1000); + auto* const possessor = entity->AddComponent(-1); + possessor->SetPossessable(ridden->GetId()); + possessor->SetMountItemID(mount->GetId()); + entity->SetGMLevel(eGameMasterLevel::OPERATOR); + + EXPECT_FALSE(inventory->CanDelete(*ridden)); + EXPECT_FALSE(inventory->CanDelete(*mount)); + EXPECT_TRUE(inventory->CanDelete(*other)); + possessor->SetPossessable(LWOOBJID_EMPTY); // nothing to dismount when the entity goes +}