mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
test(loot): item source keys and the quickbuild item cost's source
Covers the key names and name value types, a pickup from an object, from the player and from an object that is gone, metrics after an item's config (and not kept on the item), a removal's loot source, and a quickbuild taking its item cost with itself as the source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ set(DCOMPONENTS_TESTS
|
||||
"ReplicaConstructionTests.cpp"
|
||||
"ReplicaDecoderTests.cpp"
|
||||
"ItemRemovalTests.cpp"
|
||||
"LootMetricsTests.cpp"
|
||||
)
|
||||
|
||||
# Get the folder name and prepend it to the files above
|
||||
|
||||
247
tests/dGameTests/dComponentsTests/LootMetricsTests.cpp
Normal file
247
tests/dGameTests/dComponentsTests/LootMetricsTests.cpp
Normal file
@@ -0,0 +1,247 @@
|
||||
#include "GameDependencies.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "CDClientDatabase.h"
|
||||
#include "CDComponentsRegistryTable.h"
|
||||
#include "CDItemComponentTable.h"
|
||||
#include "Character.h"
|
||||
#include "CharacterComponent.h"
|
||||
#include "Entity.h"
|
||||
#include "EntityManager.h"
|
||||
#include "InventoryComponent.h"
|
||||
#include "InventoryMessages.h"
|
||||
#include "Item.h"
|
||||
#include "LootMetrics.h"
|
||||
#include "QuickBuildComponent.h"
|
||||
#include "MissionComponent.h"
|
||||
#include "DestroyableComponent.h"
|
||||
#include "LevelProgressionComponent.h"
|
||||
#include "eQuickBuildState.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
|
||||
#include "dGameMessagesTests/GameMessageTestUtils.h"
|
||||
|
||||
using namespace GameMessageTestUtils;
|
||||
|
||||
// The _Metric_ keys live put in AddItemToInventoryClientSync's extra info saying where items came from
|
||||
// (LootMetrics), and the loot source of RemoveItemFromInventory.
|
||||
class LootMetricsTests : public GameDependenciesTest {
|
||||
protected:
|
||||
static constexpr LOT ITEM = 7000;
|
||||
static constexpr LOT SMASHABLE = 4712;
|
||||
static constexpr LWOOBJID PLAYER = 0x1000000000000001LL;
|
||||
|
||||
std::unique_ptr<Entity> entity;
|
||||
std::unique_ptr<Character> character;
|
||||
InventoryComponent* inventory{};
|
||||
|
||||
void SetUp() override {
|
||||
SetUpDependencies();
|
||||
CDClientDatabase::Connect(":memory:");
|
||||
CDClientDatabase::ExecuteDML("CREATE TABLE ItemSets (setID INTEGER, itemIDs TEXT);");
|
||||
CDClientDatabase::ExecuteDML("CREATE TABLE Objects (id INTEGER, name TEXT, type TEXT);");
|
||||
CDClientDatabase::ExecuteDML("CREATE TABLE ComponentsRegistry (id INTEGER, component_type INTEGER, component_id INTEGER);");
|
||||
CDClientDatabase::ExecuteDML("CREATE TABLE ObjectSkills (objectTemplate INTEGER, skillID INTEGER, castOnType INTEGER, AICombatWeight INTEGER);");
|
||||
CDClientDatabase::ExecuteDML("CREATE TABLE SkillBehavior (skillID INTEGER, behaviorID INTEGER);");
|
||||
CDClientDatabase::ExecuteDML("CREATE TABLE Missions (id INTEGER, isMission INTEGER);");
|
||||
RegisterLot(ITEM);
|
||||
|
||||
info.lot = 1; // a player
|
||||
CDClientManager::GetEntriesMutable<CDComponentsRegistryTable>().insert_or_assign(static_cast<uint64_t>(info.lot), 0);
|
||||
entity = std::make_unique<Entity>(PLAYER, info);
|
||||
inventory = entity->AddComponent<InventoryComponent>(-1);
|
||||
character = std::make_unique<Character>(1, nullptr);
|
||||
entity->SetCharacter(character.get());
|
||||
entity->AddComponent<CharacterComponent>(-1, character.get(), ClientAddress())->InitializeStatisticsFromString("");
|
||||
Game::entityManager->_addEntity(entity.get()); // drops from the player, and the quickbuild, look it up there
|
||||
ClearSent();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
Game::entityManager->_removeEntity(PLAYER);
|
||||
entity->SetCharacter(nullptr);
|
||||
entity.reset();
|
||||
character.reset();
|
||||
TearDownDependencies();
|
||||
}
|
||||
|
||||
static void RegisterLot(const LOT lot) {
|
||||
const auto componentID = static_cast<uint32_t>(96000 + 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.stackSize = 999;
|
||||
CDClientManager::GetEntriesMutable<CDItemComponentTable>().insert_or_assign(componentID, component);
|
||||
}
|
||||
|
||||
static dServerMock& Server() { return *static_cast<dServerMock*>(Game::server); }
|
||||
|
||||
static void ClearSent() { Server().ClearSentPackets(); }
|
||||
|
||||
// The extra info of every AddItemToInventoryClientSync sent
|
||||
static std::vector<std::u16string> AddedExtraInfo() {
|
||||
std::vector<std::u16string> out;
|
||||
for (const auto& packet : Server().GetSentPackets()) {
|
||||
RakNet::BitStream bitStream(const_cast<uint8_t*>(packet.bytes.data()), packet.bytes.size(), false);
|
||||
LWOOBJID target{};
|
||||
MessageType::Game id{};
|
||||
if (!GameMessages::NetGameMsg::ReadPacketHeader(bitStream, target, id)) continue;
|
||||
if (id != MessageType::Game::ADD_ITEM_TO_INVENTORY_CLIENT_SYNC) continue;
|
||||
GameMessages::AddItemToInventoryClientSync msg;
|
||||
EXPECT_TRUE(msg.Deserialize(bitStream));
|
||||
out.push_back(msg.extraInfo);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::vector<GameMessages::RemoveItemFromInventory> Removed() {
|
||||
std::vector<GameMessages::RemoveItemFromInventory> out;
|
||||
for (const auto& packet : Server().GetSentPackets()) {
|
||||
RakNet::BitStream bitStream(const_cast<uint8_t*>(packet.bytes.data()), packet.bytes.size(), false);
|
||||
LWOOBJID target{};
|
||||
MessageType::Game id{};
|
||||
if (!GameMessages::NetGameMsg::ReadPacketHeader(bitStream, target, id)) continue;
|
||||
if (id != MessageType::Game::REMOVE_ITEM_FROM_INVENTORY) continue;
|
||||
auto& msg = out.emplace_back();
|
||||
EXPECT_TRUE(msg.Deserialize(bitStream));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Drops an item for the player from the given source object and picks it up
|
||||
void DropAndPickUp(const LWOOBJID source, const LWOOBJID lootId) {
|
||||
GameMessages::DropClientLoot drop;
|
||||
drop.target = PLAYER;
|
||||
drop.ownerID = PLAYER;
|
||||
drop.sourceID = source;
|
||||
drop.item = ITEM;
|
||||
drop.count = 1;
|
||||
drop.lootID = lootId;
|
||||
GameMessages::DropClientLootEvent event(drop);
|
||||
entity->MsgDropClientLoot(event);
|
||||
ClearSent();
|
||||
entity->PickupItem(lootId);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(LootMetricsTests, KeysAsLiveWroteThem) {
|
||||
EXPECT_TRUE(LootMetrics{}.Empty());
|
||||
EXPECT_EQ(LootMetrics{}.ToExtraInfo(), u"");
|
||||
|
||||
LootMetrics pickup{};
|
||||
pickup.sourceLot = 1;
|
||||
EXPECT_EQ(pickup.ToExtraInfo(), u"_Metric_Souce_LOT_Int=1:1");
|
||||
|
||||
LootMetrics mission{};
|
||||
mission.sourceLot = 1;
|
||||
mission.missionId = 889;
|
||||
EXPECT_EQ(mission.ToExtraInfo(), u"_Metric_Souce_LOT_Int=1:1,_Metric_Mission_ID_Int=1:889");
|
||||
|
||||
LootMetrics vendor{};
|
||||
vendor.sourceLot = 13380;
|
||||
vendor.currencyDelta = -20000;
|
||||
EXPECT_EQ(vendor.ToExtraInfo(), u"_Metric_Souce_LOT_Int=1:13380,_Metric_Currency_Delta_Int=1:-20000");
|
||||
|
||||
// Live: the mail ID as name value type 8, the trade ID as type 9
|
||||
LootMetrics mail{};
|
||||
mail.mailId = 1152921510716557582ULL;
|
||||
EXPECT_EQ(mail.ToExtraInfo(), u"_Metric_Mail_ID_Int64=8:1152921510716557582");
|
||||
LootMetrics trade{};
|
||||
trade.transactionId = 1152921510834827094LL;
|
||||
EXPECT_EQ(trade.ToExtraInfo(), u"_Metric_Transaction_ID_Int64=9:1152921510834827094");
|
||||
}
|
||||
|
||||
TEST_F(LootMetricsTests, PickupSaysWhichObjectItDroppedFrom) {
|
||||
info.lot = SMASHABLE;
|
||||
auto* const smashable = Game::entityManager->CreateEntity(info, nullptr, nullptr, false, 0x40040000000100LL);
|
||||
ASSERT_NE(smashable, nullptr);
|
||||
|
||||
DropAndPickUp(smashable->GetObjectID(), 0x5000);
|
||||
EXPECT_EQ(AddedExtraInfo(), std::vector<std::u16string>{ u"_Metric_Souce_LOT_Int=1:4712" });
|
||||
|
||||
// A second one joins the stack; the message still says where it came from
|
||||
DropAndPickUp(smashable->GetObjectID(), 0x5001);
|
||||
EXPECT_EQ(AddedExtraInfo(), std::vector<std::u16string>{ u"_Metric_Souce_LOT_Int=1:4712" });
|
||||
}
|
||||
|
||||
TEST_F(LootMetricsTests, PlayerSourcedPickupIsLotOne) {
|
||||
// Activity rewards and chests drop from the player (live: 1 on 741 pickups)
|
||||
DropAndPickUp(PLAYER, 0x5002);
|
||||
EXPECT_EQ(AddedExtraInfo(), std::vector<std::u16string>{ u"_Metric_Souce_LOT_Int=1:1" });
|
||||
}
|
||||
|
||||
TEST_F(LootMetricsTests, UnknownSourceSendsNoKey) {
|
||||
DropAndPickUp(0x40040000000999LL, 0x5003);
|
||||
EXPECT_EQ(AddedExtraInfo(), std::vector<std::u16string>{ u"" });
|
||||
}
|
||||
|
||||
TEST_F(LootMetricsTests, MetricsFollowTheItemConfig) {
|
||||
LwoNameValue config;
|
||||
config.Insert<int32_t>(u"a", 5);
|
||||
LootMetrics metrics{};
|
||||
metrics.sourceLot = 1;
|
||||
inventory->AddItem(ITEM, 1, eLootSourceType::PICKUP, eInventoryType::INVALID, config, LWOOBJID_EMPTY, true, false, LWOOBJID_EMPTY, eInventoryType::INVALID, 0, false, -1, metrics);
|
||||
EXPECT_EQ(AddedExtraInfo(), std::vector<std::u16string>{ u"a=1:5,_Metric_Souce_LOT_Int=1:1" });
|
||||
// Only the message carries them
|
||||
const auto* const item = inventory->FindItemByLot(ITEM);
|
||||
ASSERT_NE(item, nullptr);
|
||||
EXPECT_EQ(item->GetConfig().values.size(), 1u);
|
||||
}
|
||||
|
||||
TEST_F(LootMetricsTests, RemovalSaysWhatTookTheItems) {
|
||||
inventory->AddItem(ITEM, 5, eLootSourceType::NONE);
|
||||
ClearSent();
|
||||
constexpr LWOOBJID QUICKBUILD = 0x40040000000200LL;
|
||||
ASSERT_TRUE(inventory->RemoveItem(ITEM, 5, eInventoryType::ALL, false, false, { eLootSourceType::QUICKBUILD, QUICKBUILD }));
|
||||
const auto removed = Removed();
|
||||
ASSERT_EQ(removed.size(), 1u);
|
||||
EXPECT_EQ(removed[0].eLootTypeSource, static_cast<int32_t>(eLootSourceType::QUICKBUILD));
|
||||
EXPECT_EQ(removed[0].iLootTypeSource, QUICKBUILD);
|
||||
EXPECT_EQ(removed[0].iStackCount, 5u);
|
||||
|
||||
// Without a source, as before
|
||||
inventory->AddItem(ITEM, 1, eLootSourceType::NONE);
|
||||
ClearSent();
|
||||
ASSERT_TRUE(inventory->RemoveItem(ITEM, 1));
|
||||
ASSERT_EQ(Removed().size(), 1u);
|
||||
EXPECT_EQ(Removed()[0].eLootTypeSource, LOOTTYPE_NONE);
|
||||
EXPECT_EQ(Removed()[0].iLootTypeSource, LWOOBJID_EMPTY);
|
||||
}
|
||||
|
||||
// Live: the FV Stone Warrior pedestal's 5 Maelstrom Infected Bricks were taken with the loot source Quickbuild and the
|
||||
// quickbuild as the source object
|
||||
TEST_F(LootMetricsTests, QuickbuildTakesItsCostAsTheSource) {
|
||||
constexpr LOT BRICK = 6194;
|
||||
constexpr int32_t PRECONDITION = 90099; // stands in for 99: have 5 of LOT 6194
|
||||
RegisterLot(BRICK);
|
||||
CDClientDatabase::ExecuteDML("CREATE TABLE IF NOT EXISTS Preconditions (id INTEGER, type INTEGER, targetLOT TEXT, targetCount INTEGER);");
|
||||
CDClientDatabase::ExecuteDML("INSERT INTO Preconditions VALUES (" + std::to_string(PRECONDITION) + ", 2, '6194', 5);");
|
||||
inventory->AddItem(BRICK, 7, eLootSourceType::NONE);
|
||||
// What the precondition check looks at besides the inventory
|
||||
entity->AddComponent<MissionComponent>(-1);
|
||||
entity->AddComponent<DestroyableComponent>(-1);
|
||||
entity->AddComponent<LevelProgressionComponent>(-1);
|
||||
|
||||
auto pedestalInfo = info;
|
||||
pedestalInfo.lot = 8551;
|
||||
auto pedestal = std::make_unique<Entity>(0x0102030405060708LL, pedestalInfo);
|
||||
pedestal->SetVar<std::u16string>(u"CheckPrecondition", GeneralUtils::ASCIIToUTF16(std::to_string(PRECONDITION)));
|
||||
auto* const quickBuild = pedestal->AddComponent<QuickBuildComponent>(-1);
|
||||
quickBuild->SetResetTime(20.0f);
|
||||
quickBuild->SetCompleteTime(10.0f);
|
||||
ClearSent();
|
||||
|
||||
quickBuild->OnUse(entity.get());
|
||||
EXPECT_EQ(quickBuild->GetState(), eQuickBuildState::BUILDING);
|
||||
const auto removed = Removed();
|
||||
ASSERT_EQ(removed.size(), 1u);
|
||||
EXPECT_EQ(removed[0].iObjTemplate, BRICK);
|
||||
EXPECT_EQ(removed[0].iStackCount, 5u);
|
||||
EXPECT_EQ(removed[0].eLootTypeSource, static_cast<int32_t>(eLootSourceType::QUICKBUILD));
|
||||
EXPECT_EQ(removed[0].iLootTypeSource, pedestal->GetObjectID());
|
||||
EXPECT_EQ(inventory->GetLotCount(BRICK), 2u);
|
||||
|
||||
pedestal.reset();
|
||||
}
|
||||
Reference in New Issue
Block a user