mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-11-14 15:57:13 +00:00
LWOInventoryComponent_Common::msgUpdateInventoryGroup (0x00c955b0) stores the ADD's locked flag in the group; locked groups don't count toward the 50 user groups (msgGetInventoryGroupCount 0x00bde4b0 counts only unlocked ones) and msgUpdateInventoryGroupContents (0x00ce1f50) applies the 1000-LOT cap only to unlocked groups. LoadFromSaveData (0x00d1ec40) reads it from the grp element's u attribute. The server dropped the flag and always saved u="0"; saves without u load as unlocked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
135 lines
4.7 KiB
C++
135 lines
4.7 KiB
C++
#include "GameDependencies.h"
|
|
|
|
#include "CDClientManager.h"
|
|
#include "CDComponentsRegistryTable.h"
|
|
#include "Entity.h"
|
|
#include "InventoryComponent.h"
|
|
#include "InventoryMessages.h"
|
|
|
|
#include <gtest/gtest.h>
|
|
#include <tinyxml2.h>
|
|
|
|
// Inventory groups as LWOInventoryComponent_Common::msgUpdateInventoryGroup (0x00c955b0) and
|
|
// msgUpdateInventoryGroupContents (0x00ce1f50) keep them.
|
|
class InventoryGroupTests : public GameDependenciesTest {
|
|
protected:
|
|
std::unique_ptr<Entity> entity;
|
|
InventoryComponent* inventory{};
|
|
|
|
void SetUp() override {
|
|
SetUpDependencies();
|
|
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();
|
|
}
|
|
|
|
void Group(const std::string& action, const std::string& id, const std::u16string& name, const eInventoryType type = eInventoryType::BRICKS, const bool locked = false) {
|
|
GameMessages::UpdateInventoryGroup msg;
|
|
msg.action = action;
|
|
msg.groupID = id;
|
|
msg.groupName = name;
|
|
msg.inventoryType = type;
|
|
msg.locked = locked;
|
|
inventory->OnUpdateInventoryGroup(msg);
|
|
}
|
|
|
|
void Contents(const std::string& action, const std::string& id, const LOT lot, const eInventoryType type = eInventoryType::BRICKS) {
|
|
GameMessages::UpdateInventoryGroupContents msg;
|
|
msg.action = action;
|
|
msg.groupID = id;
|
|
msg.lot = lot;
|
|
msg.inventoryType = type;
|
|
inventory->OnUpdateInventoryGroupContents(msg);
|
|
}
|
|
|
|
const std::vector<InventoryComponent::Group>& Groups(const eInventoryType type = eInventoryType::BRICKS) {
|
|
return inventory->GetGroups().at(type);
|
|
}
|
|
};
|
|
|
|
TEST_F(InventoryGroupTests, AddingAGroupThatExistsDoesNothing) {
|
|
Group("ADD", "user_group1", u"first");
|
|
Contents("ADD", "user_group1", 3001);
|
|
Group("ADD", "user_group1", u"second");
|
|
|
|
ASSERT_EQ(Groups().size(), 1u);
|
|
EXPECT_EQ(Groups()[0].groupName, "first");
|
|
EXPECT_EQ(Groups()[0].lots.size(), 1u);
|
|
}
|
|
|
|
TEST_F(InventoryGroupTests, NamesAreCutTo64Characters) {
|
|
const std::u16string longName(70, u'a');
|
|
Group("ADD", "user_group1", longName);
|
|
EXPECT_EQ(Groups()[0].groupName, std::string(64, 'a'));
|
|
|
|
Group("MODIFY", "user_group1", std::u16string(65, u'b'));
|
|
EXPECT_EQ(Groups()[0].groupName, std::string(64, 'b'));
|
|
}
|
|
|
|
TEST_F(InventoryGroupTests, TheModelVaultUsesTheModelsGroups) {
|
|
Group("ADD", "user_group1", u"models", eInventoryType::VAULT_MODELS);
|
|
Contents("ADD", "user_group1", 3001, eInventoryType::VAULT_MODELS);
|
|
|
|
ASSERT_EQ(Groups(eInventoryType::MODELS).size(), 1u);
|
|
EXPECT_EQ(Groups(eInventoryType::MODELS)[0].lots.size(), 1u);
|
|
EXPECT_FALSE(inventory->GetGroups().contains(eInventoryType::VAULT_MODELS));
|
|
}
|
|
|
|
TEST_F(InventoryGroupTests, ClearEmptiesAGroup) {
|
|
Group("ADD", "user_group1", u"bricks");
|
|
Contents("ADD", "user_group1", 3001);
|
|
Contents("ADD", "user_group1", 3002);
|
|
Contents("CLEAR", "user_group1", 0);
|
|
|
|
ASSERT_EQ(Groups().size(), 1u);
|
|
EXPECT_TRUE(Groups()[0].lots.empty());
|
|
}
|
|
|
|
TEST_F(InventoryGroupTests, AGroupHoldsAtMost1000Lots) {
|
|
Group("ADD", "user_group1", u"bricks");
|
|
for (LOT lot = 1; lot <= 1001; lot++) Contents("ADD", "user_group1", lot);
|
|
|
|
EXPECT_EQ(Groups()[0].lots.size(), 1000u);
|
|
EXPECT_FALSE(Groups()[0].lots.contains(1001));
|
|
}
|
|
|
|
TEST_F(InventoryGroupTests, LockedGroupsHaveNoLimits) {
|
|
for (uint32_t i = 0; i < InventoryComponent::MaximumGroupCount; i++) Group("ADD", "user_group" + std::to_string(i), u"g");
|
|
Group("ADD", "over", u"g");
|
|
EXPECT_EQ(Groups().size(), InventoryComponent::MaximumGroupCount);
|
|
|
|
Group("ADD", "locked", u"g", eInventoryType::BRICKS, true);
|
|
ASSERT_EQ(Groups().size(), InventoryComponent::MaximumGroupCount + 1);
|
|
EXPECT_TRUE(Groups().back().locked);
|
|
for (LOT lot = 1; lot <= 1001; lot++) Contents("ADD", "locked", lot);
|
|
EXPECT_EQ(Groups().back().lots.size(), 1001u);
|
|
}
|
|
|
|
TEST_F(InventoryGroupTests, TheLockedFlagIsSavedAndLoaded) {
|
|
Group("ADD", "locked", u"a", eInventoryType::BRICKS, true);
|
|
Group("ADD", "unlocked", u"b");
|
|
|
|
tinyxml2::XMLDocument doc;
|
|
doc.Parse("<obj><inv><bag/><items/></inv></obj>");
|
|
inventory->WriteSaveData(doc);
|
|
auto* const grp = doc.FirstChildElement("obj")->FirstChildElement("inv")->FirstChildElement("grps")->FirstChildElement("grp");
|
|
ASSERT_NE(grp, nullptr);
|
|
EXPECT_STREQ(grp->Attribute("u"), "1");
|
|
EXPECT_STREQ(grp->NextSiblingElement("grp")->Attribute("u"), "0");
|
|
|
|
// Saves from before have u="0" or no u
|
|
grp->NextSiblingElement("grp")->DeleteAttribute("u");
|
|
auto loaded = std::make_unique<Entity>(2, info);
|
|
auto* const loadedInventory = loaded->AddComponent<InventoryComponent>(-1);
|
|
loadedInventory->LoadXml(doc);
|
|
const auto& groups = loadedInventory->GetGroups().at(eInventoryType::BRICKS);
|
|
ASSERT_EQ(groups.size(), 2u);
|
|
EXPECT_TRUE(groups[0].locked);
|
|
EXPECT_FALSE(groups[1].locked);
|
|
}
|