You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
698 lines
18 KiB
698 lines
18 KiB
|
22 hours ago
|
/**
|
||
|
|
* @file stash_test.cpp
|
||
|
|
*
|
||
|
|
* Tests for the player stash system.
|
||
|
|
*
|
||
|
|
* These tests verify the functional behaviour of the shared item stash:
|
||
|
|
* item placement, removal, page navigation, gold storage, transfer
|
||
|
|
* operations between stash and inventory, and the dirty flag.
|
||
|
|
*
|
||
|
|
* All assertions are on game state (stash contents, grid cells, gold
|
||
|
|
* values, dirty flag, inventory contents). No assertions on rendering,
|
||
|
|
* pixel positions, or widget layout.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <gtest/gtest.h>
|
||
|
|
|
||
|
|
#include "ui_test.hpp"
|
||
|
|
|
||
|
|
#include "inv.h"
|
||
|
|
#include "items.h"
|
||
|
|
#include "player.h"
|
||
|
|
#include "qol/stash.h"
|
||
|
|
|
||
|
|
namespace devilution {
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Test fixture
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
class StashTest : public UITest {
|
||
|
|
protected:
|
||
|
|
void SetUp() override
|
||
|
|
{
|
||
|
|
UITest::SetUp();
|
||
|
|
|
||
|
|
// Start each test with a completely clean stash.
|
||
|
|
Stash = {};
|
||
|
|
Stash.gold = 0;
|
||
|
|
Stash.dirty = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// --- helpers ---
|
||
|
|
|
||
|
|
/** @brief Create a simple 1×1 item (healing potion). */
|
||
|
|
static Item MakeSmallItem()
|
||
|
|
{
|
||
|
|
Item item {};
|
||
|
|
InitializeItem(item, IDI_HEAL);
|
||
|
|
return item;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief Create a sword (larger than 1×1). */
|
||
|
|
static Item MakeSword()
|
||
|
|
{
|
||
|
|
Item item {};
|
||
|
|
InitializeItem(item, IDI_BARDSWORD);
|
||
|
|
item._iIdentified = true;
|
||
|
|
return item;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief Create a gold item with the given value. */
|
||
|
|
static Item MakeGold(int value)
|
||
|
|
{
|
||
|
|
Item item {};
|
||
|
|
item._itype = ItemType::Gold;
|
||
|
|
item._ivalue = value;
|
||
|
|
item._iMiscId = IMISC_NONE;
|
||
|
|
return item;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief Count the number of non-empty cells in a stash grid page. */
|
||
|
|
static int CountOccupiedCells(const StashStruct::StashGrid &grid)
|
||
|
|
{
|
||
|
|
int count = 0;
|
||
|
|
for (const auto &row : grid) {
|
||
|
|
for (StashStruct::StashCell cell : row) {
|
||
|
|
if (cell != 0)
|
||
|
|
count++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return count;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief Fill a stash page completely with 1×1 items. */
|
||
|
|
void FillStashPage(unsigned page)
|
||
|
|
{
|
||
|
|
for (int x = 0; x < 10; x++) {
|
||
|
|
for (int y = 0; y < 10; y++) {
|
||
|
|
Item item = MakeSmallItem();
|
||
|
|
Stash.SetPage(page);
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, item, true))
|
||
|
|
<< "Failed to place item at logical position on page " << page;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// AutoPlaceItemInStash
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
TEST_F(StashTest, PlaceItem_EmptyStash)
|
||
|
|
{
|
||
|
|
Item item = MakeSmallItem();
|
||
|
|
|
||
|
|
bool placed = AutoPlaceItemInStash(*MyPlayer, item, true);
|
||
|
|
|
||
|
|
EXPECT_TRUE(placed);
|
||
|
|
EXPECT_FALSE(Stash.stashList.empty());
|
||
|
|
EXPECT_EQ(Stash.stashList.size(), 1u);
|
||
|
|
EXPECT_TRUE(Stash.dirty);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, PlaceItem_DryRunDoesNotMutate)
|
||
|
|
{
|
||
|
|
Item item = MakeSmallItem();
|
||
|
|
|
||
|
|
bool canPlace = AutoPlaceItemInStash(*MyPlayer, item, false);
|
||
|
|
|
||
|
|
EXPECT_TRUE(canPlace);
|
||
|
|
EXPECT_TRUE(Stash.stashList.empty()) << "Dry-run should not add item to stashList";
|
||
|
|
EXPECT_FALSE(Stash.dirty) << "Dry-run should not set dirty flag";
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, PlaceItem_GridCellOccupied)
|
||
|
|
{
|
||
|
|
Item item = MakeSmallItem();
|
||
|
|
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, item, true));
|
||
|
|
|
||
|
|
// The item should occupy at least one cell on the current page.
|
||
|
|
const auto &grid = Stash.stashGrids[Stash.GetPage()];
|
||
|
|
EXPECT_GT(CountOccupiedCells(grid), 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, PlaceItem_MultipleItemsOnSamePage)
|
||
|
|
{
|
||
|
|
Item item1 = MakeSmallItem();
|
||
|
|
Item item2 = MakeSmallItem();
|
||
|
|
Item item3 = MakeSword();
|
||
|
|
|
||
|
|
EXPECT_TRUE(AutoPlaceItemInStash(*MyPlayer, item1, true));
|
||
|
|
EXPECT_TRUE(AutoPlaceItemInStash(*MyPlayer, item2, true));
|
||
|
|
EXPECT_TRUE(AutoPlaceItemInStash(*MyPlayer, item3, true));
|
||
|
|
|
||
|
|
EXPECT_EQ(Stash.stashList.size(), 3u);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, PlaceItem_FullPageOverflowsToNextPage)
|
||
|
|
{
|
||
|
|
Stash.SetPage(0);
|
||
|
|
Stash.dirty = false;
|
||
|
|
|
||
|
|
FillStashPage(0);
|
||
|
|
|
||
|
|
size_t itemsAfterPage0 = Stash.stashList.size();
|
||
|
|
|
||
|
|
// Page 0 should be completely full now. Placing another item should go to page 1.
|
||
|
|
Item overflow = MakeSmallItem();
|
||
|
|
Stash.SetPage(0); // Reset to page 0 so AutoPlace starts searching from page 0.
|
||
|
|
EXPECT_TRUE(AutoPlaceItemInStash(*MyPlayer, overflow, true));
|
||
|
|
|
||
|
|
EXPECT_EQ(Stash.stashList.size(), itemsAfterPage0 + 1);
|
||
|
|
|
||
|
|
// The overflow item should be on page 1 (or later), not page 0.
|
||
|
|
// Page 0 should still have only the original cells occupied.
|
||
|
|
const auto &grid0 = Stash.stashGrids[0];
|
||
|
|
EXPECT_EQ(CountOccupiedCells(grid0), 100) << "Page 0 should remain fully occupied";
|
||
|
|
|
||
|
|
// Page 1 should have the overflow item.
|
||
|
|
EXPECT_TRUE(Stash.stashGrids.count(1) > 0) << "Page 1 should have been created";
|
||
|
|
EXPECT_GT(CountOccupiedCells(Stash.stashGrids[1]), 0) << "Overflow item should be on page 1";
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, PlaceItem_SwordOccupiesCorrectArea)
|
||
|
|
{
|
||
|
|
Item sword = MakeSword();
|
||
|
|
const Size swordSize = GetInventorySize(sword);
|
||
|
|
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, sword, true));
|
||
|
|
|
||
|
|
const auto &grid = Stash.stashGrids[Stash.GetPage()];
|
||
|
|
int occupiedCells = CountOccupiedCells(grid);
|
||
|
|
EXPECT_EQ(occupiedCells, swordSize.width * swordSize.height)
|
||
|
|
<< "Sword should occupy exactly " << swordSize.width << "×" << swordSize.height << " cells";
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Gold in stash
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
TEST_F(StashTest, PlaceGold_AddsToStashGold)
|
||
|
|
{
|
||
|
|
Item gold = MakeGold(5000);
|
||
|
|
|
||
|
|
EXPECT_TRUE(AutoPlaceItemInStash(*MyPlayer, gold, true));
|
||
|
|
|
||
|
|
EXPECT_EQ(Stash.gold, 5000);
|
||
|
|
EXPECT_TRUE(Stash.stashList.empty()) << "Gold should not be added to stashList";
|
||
|
|
EXPECT_TRUE(Stash.dirty);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, PlaceGold_DryRunDoesNotMutate)
|
||
|
|
{
|
||
|
|
Item gold = MakeGold(3000);
|
||
|
|
|
||
|
|
EXPECT_TRUE(AutoPlaceItemInStash(*MyPlayer, gold, false));
|
||
|
|
|
||
|
|
EXPECT_EQ(Stash.gold, 0) << "Dry-run should not change stash gold";
|
||
|
|
EXPECT_FALSE(Stash.dirty);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, PlaceGold_AccumulatesMultipleDeposits)
|
||
|
|
{
|
||
|
|
Item gold1 = MakeGold(1000);
|
||
|
|
Item gold2 = MakeGold(2500);
|
||
|
|
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, gold1, true));
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, gold2, true));
|
||
|
|
|
||
|
|
EXPECT_EQ(Stash.gold, 3500);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, PlaceGold_RejectsOverflow)
|
||
|
|
{
|
||
|
|
Stash.gold = std::numeric_limits<int>::max() - 100;
|
||
|
|
|
||
|
|
Item gold = MakeGold(200);
|
||
|
|
|
||
|
|
EXPECT_FALSE(AutoPlaceItemInStash(*MyPlayer, gold, true))
|
||
|
|
<< "Should reject gold that would cause integer overflow";
|
||
|
|
EXPECT_EQ(Stash.gold, std::numeric_limits<int>::max() - 100)
|
||
|
|
<< "Stash gold should be unchanged after rejected deposit";
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// RemoveStashItem
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
TEST_F(StashTest, RemoveItem_ClearsGridAndList)
|
||
|
|
{
|
||
|
|
Item item = MakeSmallItem();
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, item, true));
|
||
|
|
ASSERT_EQ(Stash.stashList.size(), 1u);
|
||
|
|
|
||
|
|
Stash.dirty = false;
|
||
|
|
Stash.RemoveStashItem(0);
|
||
|
|
|
||
|
|
EXPECT_TRUE(Stash.stashList.empty());
|
||
|
|
EXPECT_EQ(CountOccupiedCells(Stash.stashGrids[Stash.GetPage()]), 0)
|
||
|
|
<< "Grid cells should be cleared after removing item";
|
||
|
|
EXPECT_TRUE(Stash.dirty);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, RemoveItem_LastItemSwap)
|
||
|
|
{
|
||
|
|
// Place two items, then remove the first. The second item should be
|
||
|
|
// moved to index 0 in stashList, and grid references updated.
|
||
|
|
Item item1 = MakeSmallItem();
|
||
|
|
Item item2 = MakeSword();
|
||
|
|
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, item1, true));
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, item2, true));
|
||
|
|
ASSERT_EQ(Stash.stashList.size(), 2u);
|
||
|
|
|
||
|
|
// Remember the type of the second item.
|
||
|
|
const ItemType secondItemType = Stash.stashList[1]._itype;
|
||
|
|
|
||
|
|
Stash.RemoveStashItem(0);
|
||
|
|
|
||
|
|
ASSERT_EQ(Stash.stashList.size(), 1u);
|
||
|
|
// The former item at index 1 should now be at index 0.
|
||
|
|
EXPECT_EQ(Stash.stashList[0]._itype, secondItemType);
|
||
|
|
|
||
|
|
// Grid should reference the moved item correctly (cell value = index + 1 = 1).
|
||
|
|
const auto &grid = Stash.stashGrids[Stash.GetPage()];
|
||
|
|
bool foundReference = false;
|
||
|
|
for (const auto &row : grid) {
|
||
|
|
for (StashStruct::StashCell cell : row) {
|
||
|
|
if (cell == 1) { // index 0 + 1
|
||
|
|
foundReference = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
EXPECT_TRUE(foundReference)
|
||
|
|
<< "Grid should have updated references to the swapped item";
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, RemoveItem_MiddleOfThree)
|
||
|
|
{
|
||
|
|
// Place three items, remove the middle one. The last item should be
|
||
|
|
// swapped into slot 1, and stashList should have size 2.
|
||
|
|
Item item1 = MakeSmallItem();
|
||
|
|
Item item2 = MakeSmallItem();
|
||
|
|
Item item3 = MakeSmallItem();
|
||
|
|
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, item1, true));
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, item2, true));
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, item3, true));
|
||
|
|
ASSERT_EQ(Stash.stashList.size(), 3u);
|
||
|
|
|
||
|
|
Stash.RemoveStashItem(1);
|
||
|
|
|
||
|
|
EXPECT_EQ(Stash.stashList.size(), 2u);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Page navigation
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
TEST_F(StashTest, SetPage_SetsCorrectPage)
|
||
|
|
{
|
||
|
|
Stash.SetPage(5);
|
||
|
|
EXPECT_EQ(Stash.GetPage(), 5u);
|
||
|
|
|
||
|
|
Stash.SetPage(42);
|
||
|
|
EXPECT_EQ(Stash.GetPage(), 42u);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, SetPage_ClampsToLastPage)
|
||
|
|
{
|
||
|
|
// LastStashPage = 99 (CountStashPages - 1).
|
||
|
|
Stash.SetPage(200);
|
||
|
|
EXPECT_EQ(Stash.GetPage(), 99u);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, SetPage_SetsDirtyFlag)
|
||
|
|
{
|
||
|
|
Stash.dirty = false;
|
||
|
|
Stash.SetPage(3);
|
||
|
|
EXPECT_TRUE(Stash.dirty);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, NextPage_AdvancesByOne)
|
||
|
|
{
|
||
|
|
Stash.SetPage(0);
|
||
|
|
Stash.dirty = false;
|
||
|
|
|
||
|
|
Stash.NextPage();
|
||
|
|
EXPECT_EQ(Stash.GetPage(), 1u);
|
||
|
|
EXPECT_TRUE(Stash.dirty);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, NextPage_AdvancesByOffset)
|
||
|
|
{
|
||
|
|
Stash.SetPage(5);
|
||
|
|
|
||
|
|
Stash.NextPage(10);
|
||
|
|
EXPECT_EQ(Stash.GetPage(), 15u);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, NextPage_ClampsAtLastPage)
|
||
|
|
{
|
||
|
|
Stash.SetPage(98);
|
||
|
|
|
||
|
|
Stash.NextPage(5);
|
||
|
|
EXPECT_EQ(Stash.GetPage(), 99u) << "Should clamp to last page, not wrap around";
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, NextPage_AlreadyAtLastPage)
|
||
|
|
{
|
||
|
|
Stash.SetPage(99);
|
||
|
|
|
||
|
|
Stash.NextPage();
|
||
|
|
EXPECT_EQ(Stash.GetPage(), 99u) << "Should stay at last page";
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, PreviousPage_GoesBackByOne)
|
||
|
|
{
|
||
|
|
Stash.SetPage(5);
|
||
|
|
Stash.dirty = false;
|
||
|
|
|
||
|
|
Stash.PreviousPage();
|
||
|
|
EXPECT_EQ(Stash.GetPage(), 4u);
|
||
|
|
EXPECT_TRUE(Stash.dirty);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, PreviousPage_GoesBackByOffset)
|
||
|
|
{
|
||
|
|
Stash.SetPage(20);
|
||
|
|
|
||
|
|
Stash.PreviousPage(10);
|
||
|
|
EXPECT_EQ(Stash.GetPage(), 10u);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, PreviousPage_ClampsAtPageZero)
|
||
|
|
{
|
||
|
|
Stash.SetPage(2);
|
||
|
|
|
||
|
|
Stash.PreviousPage(5);
|
||
|
|
EXPECT_EQ(Stash.GetPage(), 0u) << "Should clamp to page 0, not underflow";
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, PreviousPage_AlreadyAtPageZero)
|
||
|
|
{
|
||
|
|
Stash.SetPage(0);
|
||
|
|
|
||
|
|
Stash.PreviousPage();
|
||
|
|
EXPECT_EQ(Stash.GetPage(), 0u) << "Should stay at page 0";
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Grid query helpers
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
TEST_F(StashTest, GetItemIdAtPosition_EmptyCell)
|
||
|
|
{
|
||
|
|
Stash.SetPage(0);
|
||
|
|
|
||
|
|
StashStruct::StashCell id = Stash.GetItemIdAtPosition({ 0, 0 });
|
||
|
|
EXPECT_EQ(id, StashStruct::EmptyCell);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, IsItemAtPosition_EmptyCell)
|
||
|
|
{
|
||
|
|
Stash.SetPage(0);
|
||
|
|
|
||
|
|
EXPECT_FALSE(Stash.IsItemAtPosition({ 0, 0 }));
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, GetItemIdAtPosition_OccupiedCell)
|
||
|
|
{
|
||
|
|
Item item = MakeSmallItem();
|
||
|
|
Stash.SetPage(0);
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, item, true));
|
||
|
|
|
||
|
|
// The first item should be placed at (0,0) in an empty stash.
|
||
|
|
StashStruct::StashCell id = Stash.GetItemIdAtPosition({ 0, 0 });
|
||
|
|
EXPECT_NE(id, StashStruct::EmptyCell);
|
||
|
|
EXPECT_EQ(id, 0u) << "First item should have stashList index 0";
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, IsItemAtPosition_OccupiedCell)
|
||
|
|
{
|
||
|
|
Item item = MakeSmallItem();
|
||
|
|
Stash.SetPage(0);
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, item, true));
|
||
|
|
|
||
|
|
EXPECT_TRUE(Stash.IsItemAtPosition({ 0, 0 }));
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// TransferItemToInventory
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
TEST_F(StashTest, TransferToInventory_Success)
|
||
|
|
{
|
||
|
|
// Clear inventory so there is room.
|
||
|
|
StripPlayer();
|
||
|
|
|
||
|
|
Item item = MakeSmallItem();
|
||
|
|
Stash.SetPage(0);
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, item, true));
|
||
|
|
ASSERT_EQ(Stash.stashList.size(), 1u);
|
||
|
|
|
||
|
|
TransferItemToInventory(*MyPlayer, 0);
|
||
|
|
|
||
|
|
EXPECT_TRUE(Stash.stashList.empty()) << "Item should be removed from stash";
|
||
|
|
EXPECT_EQ(CountOccupiedCells(Stash.stashGrids[0]), 0)
|
||
|
|
<< "Grid should be cleared";
|
||
|
|
|
||
|
|
// Item should now be in the player's inventory.
|
||
|
|
bool foundInInventory = false;
|
||
|
|
for (int i = 0; i < MyPlayer->_pNumInv; i++) {
|
||
|
|
if (!MyPlayer->InvList[i].isEmpty()) {
|
||
|
|
foundInInventory = true;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
EXPECT_TRUE(foundInInventory) << "Item should appear in player inventory";
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, TransferToInventory_EmptyCell)
|
||
|
|
{
|
||
|
|
// Transferring EmptyCell should be a no-op.
|
||
|
|
TransferItemToInventory(*MyPlayer, StashStruct::EmptyCell);
|
||
|
|
|
||
|
|
// Nothing should crash and stash should remain unchanged.
|
||
|
|
EXPECT_TRUE(Stash.stashList.empty());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, TransferToInventory_InventoryFull)
|
||
|
|
{
|
||
|
|
// Fill inventory completely so there's no room.
|
||
|
|
ClearInventory();
|
||
|
|
for (int i = 0; i < InventoryGridCells; i++) {
|
||
|
|
Item filler = MakeSmallItem();
|
||
|
|
MyPlayer->InvList[i] = filler;
|
||
|
|
MyPlayer->InvGrid[i] = static_cast<int8_t>(i + 1);
|
||
|
|
}
|
||
|
|
MyPlayer->_pNumInv = InventoryGridCells;
|
||
|
|
|
||
|
|
Item item = MakeSmallItem();
|
||
|
|
Stash.SetPage(0);
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, item, true));
|
||
|
|
ASSERT_EQ(Stash.stashList.size(), 1u);
|
||
|
|
|
||
|
|
TransferItemToInventory(*MyPlayer, 0);
|
||
|
|
|
||
|
|
// Item should remain in stash because inventory is full.
|
||
|
|
EXPECT_EQ(Stash.stashList.size(), 1u)
|
||
|
|
<< "Item should remain in stash when inventory is full";
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// TransferItemToStash
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
TEST_F(StashTest, TransferToStash_Success)
|
||
|
|
{
|
||
|
|
StripPlayer();
|
||
|
|
IsStashOpen = true;
|
||
|
|
|
||
|
|
// Place an item in inventory slot 0.
|
||
|
|
Item sword = MakeSword();
|
||
|
|
int idx = PlaceItemInInventory(sword);
|
||
|
|
ASSERT_GE(idx, 0);
|
||
|
|
|
||
|
|
int invLocation = INVITEM_INV_FIRST + idx;
|
||
|
|
|
||
|
|
TransferItemToStash(*MyPlayer, invLocation);
|
||
|
|
|
||
|
|
// Item should now be in stash.
|
||
|
|
EXPECT_FALSE(Stash.stashList.empty()) << "Item should appear in stash";
|
||
|
|
|
||
|
|
// Item should be removed from inventory.
|
||
|
|
EXPECT_TRUE(MyPlayer->InvList[idx].isEmpty() || MyPlayer->_pNumInv == 0)
|
||
|
|
<< "Item should be removed from inventory";
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, TransferToStash_InvalidLocation)
|
||
|
|
{
|
||
|
|
// Transferring from location -1 should be a no-op.
|
||
|
|
TransferItemToStash(*MyPlayer, -1);
|
||
|
|
|
||
|
|
EXPECT_TRUE(Stash.stashList.empty());
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Dirty flag
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
TEST_F(StashTest, DirtyFlag_SetOnPlaceItem)
|
||
|
|
{
|
||
|
|
Stash.dirty = false;
|
||
|
|
|
||
|
|
Item item = MakeSmallItem();
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, item, true));
|
||
|
|
|
||
|
|
EXPECT_TRUE(Stash.dirty);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, DirtyFlag_SetOnPlaceGold)
|
||
|
|
{
|
||
|
|
Stash.dirty = false;
|
||
|
|
|
||
|
|
Item gold = MakeGold(100);
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, gold, true));
|
||
|
|
|
||
|
|
EXPECT_TRUE(Stash.dirty);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, DirtyFlag_SetOnRemoveItem)
|
||
|
|
{
|
||
|
|
Item item = MakeSmallItem();
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, item, true));
|
||
|
|
Stash.dirty = false;
|
||
|
|
|
||
|
|
Stash.RemoveStashItem(0);
|
||
|
|
|
||
|
|
EXPECT_TRUE(Stash.dirty);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, DirtyFlag_SetOnPageChange)
|
||
|
|
{
|
||
|
|
Stash.dirty = false;
|
||
|
|
|
||
|
|
Stash.SetPage(1);
|
||
|
|
|
||
|
|
EXPECT_TRUE(Stash.dirty);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, DirtyFlag_NotSetOnDryRun)
|
||
|
|
{
|
||
|
|
Stash.dirty = false;
|
||
|
|
|
||
|
|
Item item = MakeSmallItem();
|
||
|
|
AutoPlaceItemInStash(*MyPlayer, item, false);
|
||
|
|
|
||
|
|
EXPECT_FALSE(Stash.dirty);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// IsStashOpen flag
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
TEST_F(StashTest, IsStashOpen_InitiallyClosed)
|
||
|
|
{
|
||
|
|
EXPECT_FALSE(IsStashOpen);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, IsStashOpen_CanBeToggled)
|
||
|
|
{
|
||
|
|
IsStashOpen = true;
|
||
|
|
EXPECT_TRUE(IsStashOpen);
|
||
|
|
|
||
|
|
IsStashOpen = false;
|
||
|
|
EXPECT_FALSE(IsStashOpen);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Edge cases
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
TEST_F(StashTest, PlaceItem_CurrentPagePreferred)
|
||
|
|
{
|
||
|
|
// When the stash is empty, the item should be placed on the current page.
|
||
|
|
Stash.SetPage(5);
|
||
|
|
Stash.dirty = false;
|
||
|
|
|
||
|
|
Item item = MakeSmallItem();
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, item, true));
|
||
|
|
|
||
|
|
EXPECT_TRUE(Stash.stashGrids.count(5) > 0)
|
||
|
|
<< "Item should be placed on the current page (5)";
|
||
|
|
EXPECT_GT(CountOccupiedCells(Stash.stashGrids[5]), 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, PlaceItem_WrapsAroundPages)
|
||
|
|
{
|
||
|
|
// Set page to 99 (last page), fill it, then place another item.
|
||
|
|
// It should wrap around to page 0.
|
||
|
|
Stash.SetPage(99);
|
||
|
|
FillStashPage(99);
|
||
|
|
|
||
|
|
Item overflow = MakeSmallItem();
|
||
|
|
Stash.SetPage(99); // Reset to page 99 so search starts there.
|
||
|
|
EXPECT_TRUE(AutoPlaceItemInStash(*MyPlayer, overflow, true));
|
||
|
|
|
||
|
|
// The item should have been placed on page 0 (wrapped around).
|
||
|
|
EXPECT_TRUE(Stash.stashGrids.count(0) > 0)
|
||
|
|
<< "Item should wrap around to page 0";
|
||
|
|
EXPECT_GT(CountOccupiedCells(Stash.stashGrids[0]), 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, MultipleItemTypes_CoexistOnSamePage)
|
||
|
|
{
|
||
|
|
Stash.SetPage(0);
|
||
|
|
|
||
|
|
Item potion = MakeSmallItem();
|
||
|
|
Item sword = MakeSword();
|
||
|
|
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, potion, true));
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, sword, true));
|
||
|
|
|
||
|
|
EXPECT_EQ(Stash.stashList.size(), 2u);
|
||
|
|
|
||
|
|
// Both items should be on page 0.
|
||
|
|
const auto &grid = Stash.stashGrids[0];
|
||
|
|
const Size swordSize = GetInventorySize(sword);
|
||
|
|
int expectedCells = 1 + (swordSize.width * swordSize.height);
|
||
|
|
EXPECT_EQ(CountOccupiedCells(grid), expectedCells);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, RemoveItem_ThenPlaceNew)
|
||
|
|
{
|
||
|
|
// Place an item, remove it, then place a new one. The stash should
|
||
|
|
// reuse the slot correctly.
|
||
|
|
Item item1 = MakeSmallItem();
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, item1, true));
|
||
|
|
ASSERT_EQ(Stash.stashList.size(), 1u);
|
||
|
|
|
||
|
|
Stash.RemoveStashItem(0);
|
||
|
|
ASSERT_TRUE(Stash.stashList.empty());
|
||
|
|
|
||
|
|
Item item2 = MakeSword();
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, item2, true));
|
||
|
|
EXPECT_EQ(Stash.stashList.size(), 1u);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(StashTest, GoldStorageIndependentOfItems)
|
||
|
|
{
|
||
|
|
// Gold and items use separate storage. Verify they don't interfere.
|
||
|
|
Stash.SetPage(0);
|
||
|
|
|
||
|
|
Item gold = MakeGold(5000);
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, gold, true));
|
||
|
|
|
||
|
|
Item item = MakeSmallItem();
|
||
|
|
ASSERT_TRUE(AutoPlaceItemInStash(*MyPlayer, item, true));
|
||
|
|
|
||
|
|
EXPECT_EQ(Stash.gold, 5000) << "Gold should be tracked separately";
|
||
|
|
EXPECT_EQ(Stash.stashList.size(), 1u) << "Only the non-gold item should be in stashList";
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
} // namespace devilution
|