mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-30 16:37:14 +00:00
feat(inventory): enforce DeletionRestrictions when deleting items
Deleting an item now follows its ItemComponent delResIndex row in the DeletionRestrictions table, the way the client's shared inventory code decides it (LWOInventoryComponent_Common::CanRemoveFromInventory @ 0x00ce0d20, CheckDeletionRestrictionIndex @ 0x00c94c20): - missing, unrestricted, unknown-type or empty rows allow it; - LOTS_INCLUDED: another item of any listed LOT must remain; - LOTS_EXCLUDED: other items of every listed LOT must remain; - ANY_RESTRICTION / ALL_RESTRICTIONS: any / all listed rows allow it; - ZONE: only in the listed maps; ALWAYS_RESTRICTED: never. Operators (GM level 9) may delete anything, as in the client. A refused delete is logged and the item stays. ItemComponent.minNumRequired is not used: the client never reads it, so its meaning can't be verified. Issue 960: the rocket (6416, row 8) and the classic rocket parts (rows 1-3) have rows that keep at least one rocket or part, so the last rocket can no longer be deleted and strand the player. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,7 @@
|
|||||||
#include "CDMissionEmailTable.h"
|
#include "CDMissionEmailTable.h"
|
||||||
#include "CDRewardsTable.h"
|
#include "CDRewardsTable.h"
|
||||||
#include "CDPropertyEntranceComponentTable.h"
|
#include "CDPropertyEntranceComponentTable.h"
|
||||||
|
#include "CDDeletionRestrictionsTable.h"
|
||||||
#include "CDPropertyTemplateTable.h"
|
#include "CDPropertyTemplateTable.h"
|
||||||
#include "CDModularBuildComponentTable.h"
|
#include "CDModularBuildComponentTable.h"
|
||||||
#include "CDFeatureGatingTable.h"
|
#include "CDFeatureGatingTable.h"
|
||||||
@@ -93,6 +94,7 @@ DEFINE_TABLE_STORAGE(CDPackageComponentTable);
|
|||||||
DEFINE_TABLE_STORAGE(CDPetComponentTable);
|
DEFINE_TABLE_STORAGE(CDPetComponentTable);
|
||||||
DEFINE_TABLE_STORAGE(CDProximityMonitorComponentTable);
|
DEFINE_TABLE_STORAGE(CDProximityMonitorComponentTable);
|
||||||
DEFINE_TABLE_STORAGE(CDPropertyEntranceComponentTable);
|
DEFINE_TABLE_STORAGE(CDPropertyEntranceComponentTable);
|
||||||
|
DEFINE_TABLE_STORAGE(CDDeletionRestrictionsTable);
|
||||||
DEFINE_TABLE_STORAGE(CDPropertyTemplateTable);
|
DEFINE_TABLE_STORAGE(CDPropertyTemplateTable);
|
||||||
DEFINE_TABLE_STORAGE(CDModularBuildComponentTable);
|
DEFINE_TABLE_STORAGE(CDModularBuildComponentTable);
|
||||||
DEFINE_TABLE_STORAGE(CDRailActivatorComponentTable);
|
DEFINE_TABLE_STORAGE(CDRailActivatorComponentTable);
|
||||||
@@ -118,6 +120,7 @@ void CDClientManager::LoadValuesFromDatabase() {
|
|||||||
CDBrickIDTableTable::Instance().LoadValuesFromDatabase();
|
CDBrickIDTableTable::Instance().LoadValuesFromDatabase();
|
||||||
CDCLIENT_DONT_CACHE_TABLE(CDComponentsRegistryTable::Instance().LoadValuesFromDatabase());
|
CDCLIENT_DONT_CACHE_TABLE(CDComponentsRegistryTable::Instance().LoadValuesFromDatabase());
|
||||||
CDCurrencyTableTable::Instance().LoadValuesFromDatabase();
|
CDCurrencyTableTable::Instance().LoadValuesFromDatabase();
|
||||||
|
CDDeletionRestrictionsTable::Instance().LoadValuesFromDatabase();
|
||||||
CDDestructibleComponentTable::Instance().LoadValuesFromDatabase();
|
CDDestructibleComponentTable::Instance().LoadValuesFromDatabase();
|
||||||
CDEmoteTableTable::Instance().LoadValuesFromDatabase();
|
CDEmoteTableTable::Instance().LoadValuesFromDatabase();
|
||||||
CDFeatureGatingTable::Instance().LoadValuesFromDatabase();
|
CDFeatureGatingTable::Instance().LoadValuesFromDatabase();
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#include "CDDeletionRestrictionsTable.h"
|
||||||
|
|
||||||
|
void CDDeletionRestrictionsTable::LoadValuesFromDatabase() {
|
||||||
|
auto& entries = GetEntriesMutable();
|
||||||
|
auto tableData = CDClientDatabase::ExecuteQuery("SELECT id, restricted, ids, checkType FROM DeletionRestrictions;");
|
||||||
|
while (!tableData.eof()) {
|
||||||
|
CDDeletionRestriction entry;
|
||||||
|
entry.restricted = tableData.getIntField("restricted", 0) != 0;
|
||||||
|
entry.ids = tableData.getStringField("ids", "");
|
||||||
|
entry.checkType = tableData.getIntField("checkType", 0);
|
||||||
|
entries.insert_or_assign(tableData.getIntField("id", -1), std::move(entry));
|
||||||
|
tableData.nextRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
tableData.finalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
const CDDeletionRestriction* CDDeletionRestrictionsTable::Get(const int32_t id) const {
|
||||||
|
const auto& entries = GetEntries();
|
||||||
|
const auto it = entries.find(id);
|
||||||
|
return it == entries.end() ? nullptr : &it->second;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "CDTable.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// How a DeletionRestrictions row decides whether an item may be deleted (eDeletionRestrictionCheckType in the client)
|
||||||
|
enum class eDeletionRestrictionCheckType : int32_t {
|
||||||
|
LOTS_INCLUDED = 0, // allowed if another item of one of the LOTs is owned
|
||||||
|
LOTS_EXCLUDED = 1, // allowed if other items of every one of the LOTs are owned
|
||||||
|
ANY_RESTRICTION = 2, // allowed if any of the listed rows allows it
|
||||||
|
ALL_RESTRICTIONS = 3, // allowed if all of the listed rows allow it
|
||||||
|
ZONE = 4, // allowed in the listed map IDs
|
||||||
|
ALWAYS_RESTRICTED = 5, // never allowed
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CDDeletionRestriction {
|
||||||
|
bool restricted{};
|
||||||
|
std::string ids; // comma separated LOTs, row IDs or map IDs, depending on checkType
|
||||||
|
int32_t checkType{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class CDDeletionRestrictionsTable : public CDTable<CDDeletionRestrictionsTable, std::map<int32_t, CDDeletionRestriction>> {
|
||||||
|
public:
|
||||||
|
void LoadValuesFromDatabase();
|
||||||
|
// The row with this ID, or nullptr
|
||||||
|
const CDDeletionRestriction* Get(int32_t id) const;
|
||||||
|
};
|
||||||
@@ -33,7 +33,7 @@ struct CDItemComponent {
|
|||||||
uint32_t itemRating; //!< ???
|
uint32_t itemRating; //!< ???
|
||||||
bool isTwoHanded; //!< Whether or not the item is double handed
|
bool isTwoHanded; //!< Whether or not the item is double handed
|
||||||
uint32_t minNumRequired; //!< Maybe the minimum number required for a mission, or to own this object?
|
uint32_t minNumRequired; //!< Maybe the minimum number required for a mission, or to own this object?
|
||||||
uint32_t delResIndex; //!< ???
|
uint32_t delResIndex; //!< Row of DeletionRestrictions, -1 (as unsigned) for none
|
||||||
uint32_t currencyLOT; //!< ???
|
uint32_t currencyLOT; //!< ???
|
||||||
uint32_t altCurrencyCost; //!< ???
|
uint32_t altCurrencyCost; //!< ???
|
||||||
std::string subItems; //!< A comma seperate string of sub items (maybe for multi-itemed things like faction test gear set)
|
std::string subItems; //!< A comma seperate string of sub items (maybe for multi-itemed things like faction test gear set)
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ set(DDATABASE_CDCLIENTDATABASE_CDCLIENTTABLES_SOURCES "CDActivitiesTable.cpp"
|
|||||||
"CDPlayerFlagsTable.cpp"
|
"CDPlayerFlagsTable.cpp"
|
||||||
"CDPhysicsComponentTable.cpp"
|
"CDPhysicsComponentTable.cpp"
|
||||||
"CDPropertyEntranceComponentTable.cpp"
|
"CDPropertyEntranceComponentTable.cpp"
|
||||||
|
"CDDeletionRestrictionsTable.cpp"
|
||||||
"CDPropertyTemplateTable.cpp"
|
"CDPropertyTemplateTable.cpp"
|
||||||
"CDModularBuildComponentTable.cpp"
|
"CDModularBuildComponentTable.cpp"
|
||||||
"CDProximityMonitorComponentTable.cpp"
|
"CDProximityMonitorComponentTable.cpp"
|
||||||
|
|||||||
@@ -11,6 +11,9 @@
|
|||||||
#include "dServer.h"
|
#include "dServer.h"
|
||||||
#include "Logger.h"
|
#include "Logger.h"
|
||||||
#include "CDClientManager.h"
|
#include "CDClientManager.h"
|
||||||
|
#include "CDDeletionRestrictionsTable.h"
|
||||||
|
#include "eGameMasterLevel.h"
|
||||||
|
#include <set>
|
||||||
#include "ObjectIDManager.h"
|
#include "ObjectIDManager.h"
|
||||||
#include "MissionComponent.h"
|
#include "MissionComponent.h"
|
||||||
#include "GameMessages.h"
|
#include "GameMessages.h"
|
||||||
@@ -2063,6 +2066,11 @@ void InventoryComponent::OnRemoveItemFromInventory(const GameMessages::RemoveIte
|
|||||||
|
|
||||||
const auto iStackCount = std::min<uint32_t>(item->GetCount(), msg.iStackCount);
|
const auto iStackCount = std::min<uint32_t>(item->GetCount(), msg.iStackCount);
|
||||||
|
|
||||||
|
if (msg.bConfirmed && !CanDelete(*item)) {
|
||||||
|
LOG("%llu tried to delete %llu (LOT %i), which its deletion restriction does not allow", m_Parent->GetObjectID(), item->GetId(), item->GetLot());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (msg.bConfirmed) {
|
if (msg.bConfirmed) {
|
||||||
const auto itemType = static_cast<eItemType>(item->GetInfo().itemType);
|
const auto itemType = static_cast<eItemType>(item->GetInfo().itemType);
|
||||||
if (itemType == eItemType::MODEL || itemType == eItemType::LOOT_MODEL) {
|
if (itemType == eItemType::MODEL || itemType == eItemType::LOOT_MODEL) {
|
||||||
@@ -2213,3 +2221,76 @@ void InventoryComponent::OnUpdateInventoryGroupContents(const GameMessages::Upda
|
|||||||
|
|
||||||
UpdateGroup(groupUpdate);
|
UpdateGroup(groupUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// A comma separated list of numbers; anything that is not a number is skipped
|
||||||
|
std::vector<int64_t> ParseDeletionRestrictionIds(const std::string& ids) {
|
||||||
|
std::vector<int64_t> parsed;
|
||||||
|
for (auto part : GeneralUtils::SplitString(ids, ',')) {
|
||||||
|
const auto first = part.find_first_not_of(" \t\r\n");
|
||||||
|
if (first == std::string::npos) continue;
|
||||||
|
part = part.substr(first, part.find_last_not_of(" \t\r\n") - first + 1);
|
||||||
|
const auto value = GeneralUtils::TryParse<int64_t>(part);
|
||||||
|
if (value) parsed.push_back(*value);
|
||||||
|
}
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InventoryComponent::CanDelete(const Item& item) const {
|
||||||
|
if (m_Parent->GetGMLevel() == eGameMasterLevel::OPERATOR) return true;
|
||||||
|
|
||||||
|
const auto row = static_cast<int32_t>(item.GetInfo().delResIndex);
|
||||||
|
if (row < 0) return true;
|
||||||
|
|
||||||
|
std::set<int32_t> visited;
|
||||||
|
return CheckDeletionRestriction(item.GetId(), row, visited);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InventoryComponent::CheckDeletionRestriction(const LWOOBJID itemId, const int32_t row, std::set<int32_t>& visited) const {
|
||||||
|
// Rows that are missing, not restricted, of an unknown type or without IDs don't restrict anything
|
||||||
|
const auto* const restriction = CDClientManager::GetTable<CDDeletionRestrictionsTable>()->Get(row);
|
||||||
|
if (!restriction || !restriction->restricted) return true;
|
||||||
|
|
||||||
|
const auto checkType = static_cast<eDeletionRestrictionCheckType>(restriction->checkType);
|
||||||
|
if (checkType == eDeletionRestrictionCheckType::ALWAYS_RESTRICTED) return false;
|
||||||
|
if (restriction->checkType < 0 || restriction->checkType > GeneralUtils::ToUnderlying(eDeletionRestrictionCheckType::ALWAYS_RESTRICTED)) return true;
|
||||||
|
|
||||||
|
const auto ids = ParseDeletionRestrictionIds(restriction->ids);
|
||||||
|
if (ids.empty()) return true;
|
||||||
|
|
||||||
|
switch (checkType) {
|
||||||
|
case eDeletionRestrictionCheckType::ANY_RESTRICTION:
|
||||||
|
case eDeletionRestrictionCheckType::ALL_RESTRICTIONS: {
|
||||||
|
if (!visited.insert(row).second) return true;
|
||||||
|
const bool any = checkType == eDeletionRestrictionCheckType::ANY_RESTRICTION;
|
||||||
|
bool result = !any;
|
||||||
|
for (const auto id : ids) {
|
||||||
|
const bool allowed = CheckDeletionRestriction(itemId, static_cast<int32_t>(id), visited);
|
||||||
|
result = any ? (result || allowed) : (result && allowed);
|
||||||
|
}
|
||||||
|
visited.erase(row);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
case eDeletionRestrictionCheckType::ZONE: {
|
||||||
|
const auto mapId = Game::zoneManager->GetZoneID().GetMapID();
|
||||||
|
return std::ranges::find(ids, static_cast<int64_t>(mapId)) != ids.end();
|
||||||
|
}
|
||||||
|
case eDeletionRestrictionCheckType::LOTS_INCLUDED:
|
||||||
|
case eDeletionRestrictionCheckType::LOTS_EXCLUDED: {
|
||||||
|
// The other items the player has, in any inventory, not counting the one being deleted
|
||||||
|
std::set<int64_t> missing(ids.begin(), ids.end());
|
||||||
|
for (const auto& [type, inventory] : m_Inventories) {
|
||||||
|
for (const auto& [id, other] : inventory->GetItems()) {
|
||||||
|
if (id == itemId || !missing.contains(other->GetLot())) continue;
|
||||||
|
if (checkType == eDeletionRestrictionCheckType::LOTS_INCLUDED) return true;
|
||||||
|
missing.erase(other->GetLot());
|
||||||
|
if (missing.empty()) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#define INVENTORYCOMPONENT_H
|
#define INVENTORYCOMPONENT_H
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <set>
|
||||||
#include <stack>
|
#include <stack>
|
||||||
|
|
||||||
|
|
||||||
@@ -333,6 +334,12 @@ public:
|
|||||||
*/
|
*/
|
||||||
std::vector<uint32_t> FindBuffs(Item* item, bool castOnEquip) const;
|
std::vector<uint32_t> FindBuffs(Item* item, bool castOnEquip) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One DeletionRestrictions row (CheckDeletionRestrictionIndex @ 00c94c20). visited stops rows that refer back to
|
||||||
|
* themselves; the client lets such rows through.
|
||||||
|
*/
|
||||||
|
bool CheckDeletionRestriction(LWOOBJID itemId, int32_t row, std::set<int32_t>& visited) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the equipped items with a list of items
|
* Initializes the equipped items with a list of items
|
||||||
* @param items the items to equip
|
* @param items the items to equip
|
||||||
@@ -451,6 +458,12 @@ public:
|
|||||||
void OnEquipInventory(const GameMessages::EquipInventory& msg);
|
void OnEquipInventory(const GameMessages::EquipInventory& msg);
|
||||||
void OnUnEquipInventory(const GameMessages::UnEquipInventory& msg);
|
void OnUnEquipInventory(const GameMessages::UnEquipInventory& msg);
|
||||||
void OnRemoveItemFromInventory(const GameMessages::RemoveItemFromInventory& msg);
|
void OnRemoveItemFromInventory(const GameMessages::RemoveItemFromInventory& msg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
bool CanDelete(const Item& item) const;
|
||||||
void OnMoveItemInInventory(const GameMessages::MoveItemInInventory& msg);
|
void OnMoveItemInInventory(const GameMessages::MoveItemInInventory& msg);
|
||||||
void OnMoveItemBetweenInventoryTypes(const GameMessages::MoveItemBetweenInventoryTypes& msg);
|
void OnMoveItemBetweenInventoryTypes(const GameMessages::MoveItemBetweenInventoryTypes& msg);
|
||||||
// Answers the client at sysAddr with ResponseMoveItemBetweenInventoryTypes.
|
// Answers the client at sysAddr with ResponseMoveItemBetweenInventoryTypes.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
set(DCOMPONENTS_TESTS
|
set(DCOMPONENTS_TESTS
|
||||||
|
"DeletionRestrictionTests.cpp"
|
||||||
"DestroyableComponentTests.cpp"
|
"DestroyableComponentTests.cpp"
|
||||||
"PetComponentTests.cpp"
|
"PetComponentTests.cpp"
|
||||||
"SimplePhysicsComponentTests.cpp"
|
"SimplePhysicsComponentTests.cpp"
|
||||||
|
|||||||
125
tests/dGameTests/dComponentsTests/DeletionRestrictionTests.cpp
Normal file
125
tests/dGameTests/dComponentsTests/DeletionRestrictionTests.cpp
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
#include "GameDependencies.h"
|
||||||
|
|
||||||
|
#include "CDClientManager.h"
|
||||||
|
#include "CDComponentsRegistryTable.h"
|
||||||
|
#include "CDDeletionRestrictionsTable.h"
|
||||||
|
#include "CDItemComponentTable.h"
|
||||||
|
#include "Entity.h"
|
||||||
|
#include "InventoryComponent.h"
|
||||||
|
#include "Item.h"
|
||||||
|
#include "eGameMasterLevel.h"
|
||||||
|
#include "eReplicaComponentType.h"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
// The deletion checks follow the client's LWOInventoryComponent_Common::CheckDeletionRestrictionIndex (0x00c94c20).
|
||||||
|
class DeletionRestrictionTests : public GameDependenciesTest {
|
||||||
|
protected:
|
||||||
|
std::unique_ptr<Entity> entity;
|
||||||
|
InventoryComponent* inventory{};
|
||||||
|
LWOOBJID nextId = 0x6000;
|
||||||
|
|
||||||
|
void SetUp() override {
|
||||||
|
SetUpDependencies();
|
||||||
|
// Rows as the CDClient has them
|
||||||
|
auto& rows = CDClientManager::GetEntriesMutable<CDDeletionRestrictionsTable>();
|
||||||
|
rows.clear();
|
||||||
|
rows[0] = { false, "", 0 };
|
||||||
|
rows[500] = { true, "2001, 2002", GeneralUtils::ToUnderlying(eDeletionRestrictionCheckType::LOTS_INCLUDED) };
|
||||||
|
rows[501] = { true, "2001,2002", GeneralUtils::ToUnderlying(eDeletionRestrictionCheckType::LOTS_EXCLUDED) };
|
||||||
|
rows[502] = { true, "", GeneralUtils::ToUnderlying(eDeletionRestrictionCheckType::ALWAYS_RESTRICTED) };
|
||||||
|
rows[503] = { true, "500, 502", GeneralUtils::ToUnderlying(eDeletionRestrictionCheckType::ANY_RESTRICTION) };
|
||||||
|
rows[504] = { true, "500, 502", GeneralUtils::ToUnderlying(eDeletionRestrictionCheckType::ALL_RESTRICTIONS) };
|
||||||
|
rows[505] = { true, "505", GeneralUtils::ToUnderlying(eDeletionRestrictionCheckType::ALL_RESTRICTIONS) };
|
||||||
|
rows[506] = { true, "", GeneralUtils::ToUnderlying(eDeletionRestrictionCheckType::LOTS_INCLUDED) };
|
||||||
|
|
||||||
|
RegisterLot(1000, 0);
|
||||||
|
RegisterLot(1500, 500);
|
||||||
|
RegisterLot(1501, 501);
|
||||||
|
RegisterLot(1502, 502);
|
||||||
|
RegisterLot(1503, 503);
|
||||||
|
RegisterLot(1504, 504);
|
||||||
|
RegisterLot(1505, 505);
|
||||||
|
RegisterLot(1506, 506);
|
||||||
|
RegisterLot(1599, 599); // no such row
|
||||||
|
RegisterLot(2001, -1);
|
||||||
|
RegisterLot(2002, -1);
|
||||||
|
|
||||||
|
// The entity's own LOT has no components, so InventoryComponent doesn't look it up in a database
|
||||||
|
CDClientManager::GetEntriesMutable<CDComponentsRegistryTable>().insert_or_assign(static_cast<uint64_t>(info.lot), 0);
|
||||||
|
entity = std::make_unique<Entity>(1, info);
|
||||||
|
inventory = entity->AddComponent<InventoryComponent>(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override {
|
||||||
|
entity.reset();
|
||||||
|
TearDownDependencies();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RegisterLot(const LOT lot, const int32_t delResIndex) {
|
||||||
|
const auto componentID = static_cast<uint32_t>(95000 + lot);
|
||||||
|
auto& registry = CDClientManager::GetEntriesMutable<CDComponentsRegistryTable>();
|
||||||
|
registry.insert_or_assign(static_cast<uint64_t>(lot), componentID);
|
||||||
|
registry.insert_or_assign(static_cast<uint64_t>(eReplicaComponentType::ITEM) << 32 | static_cast<uint64_t>(lot), componentID);
|
||||||
|
CDItemComponent component{};
|
||||||
|
component.id = componentID;
|
||||||
|
component.delResIndex = static_cast<uint32_t>(delResIndex);
|
||||||
|
CDClientManager::GetEntriesMutable<CDItemComponentTable>().insert_or_assign(componentID, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The inventory owns the item
|
||||||
|
Item* Give(const LOT lot) {
|
||||||
|
auto* const items = inventory->GetInventory(eInventoryType::ITEMS);
|
||||||
|
return new Item(nextId++, lot, items, static_cast<uint32_t>(items->GetItems().size()), 1, false, {}, LWOOBJID_EMPTY, LWOOBJID_EMPTY, eLootSourceType::NONE);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(DeletionRestrictionTests, UnrestrictedRowsAllowDeleting) {
|
||||||
|
EXPECT_TRUE(inventory->CanDelete(*Give(1000)));
|
||||||
|
EXPECT_TRUE(inventory->CanDelete(*Give(1599)));
|
||||||
|
EXPECT_TRUE(inventory->CanDelete(*Give(2001)));
|
||||||
|
EXPECT_TRUE(inventory->CanDelete(*Give(1506))); // restricted row without IDs
|
||||||
|
EXPECT_TRUE(inventory->CanDelete(*Give(1505))); // refers to itself
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DeletionRestrictionTests, AlwaysRestricted) {
|
||||||
|
EXPECT_FALSE(inventory->CanDelete(*Give(1502)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DeletionRestrictionTests, OperatorsDeleteAnything) {
|
||||||
|
entity->SetGMLevel(eGameMasterLevel::OPERATOR);
|
||||||
|
EXPECT_TRUE(inventory->CanDelete(*Give(1502)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DeletionRestrictionTests, AnyOfTheLotsMustRemain) {
|
||||||
|
auto* const item = Give(1500);
|
||||||
|
EXPECT_FALSE(inventory->CanDelete(*item));
|
||||||
|
Give(2002);
|
||||||
|
EXPECT_TRUE(inventory->CanDelete(*item));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DeletionRestrictionTests, AllOfTheLotsMustRemain) {
|
||||||
|
auto* const item = Give(1501);
|
||||||
|
Give(2001);
|
||||||
|
EXPECT_FALSE(inventory->CanDelete(*item));
|
||||||
|
Give(2002);
|
||||||
|
EXPECT_TRUE(inventory->CanDelete(*item));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DeletionRestrictionTests, TheDeletedItemDoesNotCount) {
|
||||||
|
auto* const item = Give(2001);
|
||||||
|
RegisterLot(2001, 500);
|
||||||
|
EXPECT_FALSE(inventory->CanDelete(*item));
|
||||||
|
Give(2001);
|
||||||
|
EXPECT_TRUE(inventory->CanDelete(*item));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DeletionRestrictionTests, AnyAndAllOfOtherRows) {
|
||||||
|
auto* const any = Give(1503);
|
||||||
|
auto* const all = Give(1504);
|
||||||
|
EXPECT_FALSE(inventory->CanDelete(*any));
|
||||||
|
EXPECT_FALSE(inventory->CanDelete(*all));
|
||||||
|
Give(2001); // row 500 now allows it, row 502 never does
|
||||||
|
EXPECT_TRUE(inventory->CanDelete(*any));
|
||||||
|
EXPECT_FALSE(inventory->CanDelete(*all));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user