From 91dd3bfaa18e62819babc75b1127e09d8c5dcb6d Mon Sep 17 00:00:00 2001 From: Yggdrasill Date: Tue, 1 Apr 2025 13:38:02 +0100 Subject: [PATCH] Add tests for premium item generation Rename functions Comments --- CMake/Tests.cmake | 1 + Source/stores.h | 12 +- test/vendor_test.cpp | 546 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 553 insertions(+), 6 deletions(-) create mode 100644 test/vendor_test.cpp diff --git a/CMake/Tests.cmake b/CMake/Tests.cmake index 5af78ae88..3498113ac 100644 --- a/CMake/Tests.cmake +++ b/CMake/Tests.cmake @@ -35,6 +35,7 @@ set(tests tile_properties_test timedemo_test writehero_test + vendor_test ) set(standalone_tests codec_test diff --git a/Source/stores.h b/Source/stores.h index ea13be716..f9eb0be75 100644 --- a/Source/stores.h +++ b/Source/stores.h @@ -72,19 +72,19 @@ extern int8_t PlayerItemIndexes[48]; extern DVL_API_FOR_TEST Item PlayerItems[48]; /** Items sold by Griswold */ -extern Item SmithItems[NumSmithBasicItemsHf]; +extern DVL_API_FOR_TEST Item SmithItems[NumSmithBasicItemsHf]; /** Number of premium items for sale by Griswold */ -extern int PremiumItemCount; +extern DVL_API_FOR_TEST int PremiumItemCount; /** Base level of current premium items sold by Griswold */ -extern int PremiumItemLevel; +extern DVL_API_FOR_TEST int PremiumItemLevel; /** Premium items sold by Griswold */ -extern Item PremiumItems[NumSmithItemsHf]; +extern DVL_API_FOR_TEST Item PremiumItems[NumSmithItemsHf]; /** Items sold by Pepin */ -extern Item HealerItems[20]; +extern DVL_API_FOR_TEST Item HealerItems[20]; /** Items sold by Adria */ -extern Item WitchItems[NumWitchItemsHf]; +extern DVL_API_FOR_TEST Item WitchItems[NumWitchItemsHf]; /** Current level of the item sold by Wirt */ extern int BoyItemLevel; diff --git a/test/vendor_test.cpp b/test/vendor_test.cpp new file mode 100644 index 000000000..88c5a858a --- /dev/null +++ b/test/vendor_test.cpp @@ -0,0 +1,546 @@ +#include +#include + +#include "items.h" +#include "player.h" +#include "playerdat.hpp" +#include "stores.h" + +#include "engine/assets.hpp" +#include "engine/random.hpp" + +namespace devilution { +namespace { + +using ::testing::AnyOf; +using ::testing::Eq; + +constexpr int SEED = 75357; + +std::string itemtype_str(ItemType type); +std::string misctype_str(item_misc_id type); + +MATCHER_P(SmithTypeMatch, i, "Valid Diablo item type from Griswold") +{ + if (arg >= ItemType::Sword && arg <= ItemType::HeavyArmor) return true; + + *result_listener << "At index " << i << ": Invalid item type " << itemtype_str(arg); + return false; +} + +MATCHER_P(SmithTypeMatchHf, i, "Valid Hellfire item type from Griswold") +{ + if (arg >= ItemType::Sword && arg <= ItemType::Staff) return true; + + *result_listener << "At index " << i << ": Invalid item type " << itemtype_str(arg); + return false; +} + +MATCHER_P(PremiumTypeMatch, i, "Valid premium items from Griswold") +{ + if (arg >= ItemType::Ring && arg <= ItemType::Amulet) return true; + + *result_listener << "At index " << i << ": Invalid item type " << itemtype_str(arg); + return false; +} + +MATCHER_P(WitchTypeMatch, i, "Valid item type from Adria") +{ + if (arg == ItemType::Misc || arg == ItemType::Staff) return true; + + *result_listener << "At index " << i << ": Invalid item type " << itemtype_str(arg); + return false; +} + +MATCHER_P(WitchMiscMatch, i, "Valid misc. item type from Adria") +{ + if (arg >= IMISC_ELIXSTR && arg <= IMISC_ELIXVIT) return true; + if (arg >= IMISC_REJUV && arg <= IMISC_FULLREJUV) return true; + if (arg >= IMISC_SCROLL && arg <= IMISC_SCROLLT) return true; + if (arg >= IMISC_RUNEFIRST && arg <= IMISC_RUNELAST) return true; + if (arg == IMISC_BOOK) return true; + + *result_listener << "At index " << i << ": Invalid misc. item type " << misctype_str(arg); + return false; +} + +MATCHER_P(HealerMiscMatch, i, "Valid misc. item type from Pepin") +{ + if (arg >= IMISC_ELIXSTR && arg <= IMISC_ELIXVIT) return true; + if (arg >= IMISC_REJUV && arg <= IMISC_FULLREJUV) return true; + if (arg >= IMISC_SCROLL && arg <= IMISC_SCROLLT) return true; + + *result_listener << "At index " << i << ": Invalid misc. item type " << misctype_str(arg); + return false; +} + +class VendorTest : public ::testing::Test { +public: + void SetUp() override + { + Players.resize(1); + MyPlayer = &Players[0]; + gbIsHellfire = false; + CreatePlayer(*MyPlayer, HeroClass::Warrior); + SetRndSeed(SEED); + } + + static void SetUpTestSuite() + { + LoadCoreArchives(); + LoadGameArchives(); + ASSERT_TRUE(HaveMainData()); + LoadPlayerDataFiles(); + LoadItemData(); + LoadSpellData(); + } +}; + +std::string itemtype_str(ItemType type) +{ + const std::string ITEM_TYPES[] = { + "ItemType::Misc", + "ItemType::Sword", + "ItemType::Axe", + "ItemType::Bow", + "ItemType::Mace", + "ItemType::Shield", + "ItemType::LightArmor", + "ItemType::Helm", + "ItemType::MediumArmor", + "ItemType::HeavyArmor", + "ItemType::Staff", + "ItemType::Gold", + "ItemType::Ring", + "ItemType::Amulet", + }; + + if (type == ItemType::None) return "ItemType::None"; + if (type < ItemType::Misc || type > ItemType::Amulet) return "ItemType does not exist!"; + return ITEM_TYPES[static_cast(type)]; +} + +std::string misctype_str(item_misc_id type) +{ + const std::string MISC_TYPES[] = { + // clang-format off + "IMISC_NONE", "IMISC_USEFIRST", "IMISC_FULLHEAL", "IMISC_HEAL", + "IMISC_0x4", "IMISC_0x5", "IMISC_MANA", "IMISC_FULLMANA", + "IMISC_0x8", "IMISC_0x9", "IMISC_ELIXSTR", "IMISC_ELIXMAG", + "IMISC_ELIXDEX", "IMISC_ELIXVIT", "IMISC_0xE", "IMISC_0xF", + "IMISC_0x10", "IMISC_0x11", "IMISC_REJUV", "IMISC_FULLREJUV", + "IMISC_USELAST", "IMISC_SCROLL", "IMISC_SCROLLT", "IMISC_STAFF", + "IMISC_BOOK", "IMISC_RING", "IMISC_AMULET", "IMISC_UNIQUE", + "IMISC_0x1C", "IMISC_OILFIRST", "IMISC_OILOF", "IMISC_OILACC", + "IMISC_OILMAST", "IMISC_OILSHARP", "IMISC_OILDEATH", "IMISC_OILSKILL", + "IMISC_OILBSMTH", "IMISC_OILFORT", "IMISC_OILPERM", "IMISC_OILHARD", + "IMISC_OILIMP", "IMISC_OILLAST", "IMISC_MAPOFDOOM", "IMISC_EAR", + "IMISC_SPECELIX", "IMISC_0x2D", "IMISC_RUNEFIRST", "IMISC_RUNEF", + "IMISC_RUNEL", "IMISC_GR_RUNEL", "IMISC_GR_RUNEF", "IMISC_RUNES", + "IMISC_RUNELAST", "IMISC_AURIC", "IMISC_NOTE", "IMISC_ARENAPOT" + // clang-format on + }; + + if (type == IMISC_INVALID) return "IMISC_INVALID"; + if (type < IMISC_NONE || type > IMISC_ARENAPOT) return "IMISC does not exist!"; + return MISC_TYPES[static_cast(type)]; +} + +TEST_F(VendorTest, SmithGen) +{ + MyPlayer->setCharacterLevel(25); + + // Clear global state for test, and force Diablo game mode + for (int i = 0; i < NumSmithBasicItemsHf; i++) { + SmithItems[i].clear(); + } + gbIsHellfire = false; + + SpawnSmith(16); + + SetRndSeed(SEED); + const int N_ITEMS = RandomIntBetween(10, NumSmithBasicItems); + int n_items = 0; + + for (int i = 0; i < NumSmithBasicItems; i++) { + if (SmithItems[i].isEmpty()) break; + EXPECT_THAT(SmithItems[i]._itype, SmithTypeMatch(i)); + n_items++; + } + EXPECT_EQ(n_items, N_ITEMS); +} + +TEST_F(VendorTest, SmithGenHf) +{ + MyPlayer->setCharacterLevel(25); + + // Clear global state for test, and force Hellfire game mode + for (int i = 0; i < NumSmithBasicItemsHf; i++) { + SmithItems[i].clear(); + } + gbIsHellfire = true; + + SpawnSmith(16); + + SetRndSeed(SEED); + const int N_ITEMS = RandomIntBetween(10, NumSmithBasicItemsHf); + int n_items = 0; + + for (int i = 0; i < NumSmithBasicItemsHf; i++) { + if (SmithItems[i].isEmpty()) break; + EXPECT_THAT(SmithItems[i]._itype, SmithTypeMatchHf(i)); + n_items++; + } + EXPECT_EQ(n_items, N_ITEMS); +} + +TEST_F(VendorTest, PremiumQlvl1to5) +{ + for (int i = 0; i < NumSmithItems; i++) { + PremiumItems[i].clear(); + } + PremiumItemLevel = 1; + + // Test level 1 character item qlvl + MyPlayer->setCharacterLevel(1); + SpawnPremium(*MyPlayer); + for (int i = 0; i < NumSmithItems; i++) { + constexpr int QLVLS[] = { 1, 1, 1, 1, 2, 3 }; + EXPECT_EQ(PremiumItems[i]._iCreateInfo & CF_LEVEL, QLVLS[i]) << "Index: " << i; + EXPECT_THAT(PremiumItems[i]._itype, AnyOf(SmithTypeMatch(i), PremiumTypeMatch(i))); + } + + // Test level ups + MyPlayer->setCharacterLevel(5); + SpawnPremium(*MyPlayer); + for (int i = 0; i < NumSmithItems; i++) { + constexpr int QLVLS[] = { 4, 4, 5, 5, 6, 7 }; + EXPECT_EQ(PremiumItems[i]._iCreateInfo & CF_LEVEL, QLVLS[i]) << "Index: " << i; + EXPECT_THAT(PremiumItems[i]._itype, AnyOf(SmithTypeMatch(i), PremiumTypeMatch(i))); + } +} + +TEST_F(VendorTest, PremiumQlvl25) +{ + constexpr int QLVLS[] = { 24, 24, 25, 25, 26, 27 }; + + for (int i = 0; i < NumSmithItems; i++) { + PremiumItems[i].clear(); + } + PremiumItemLevel = 1; + + // Test starting game as a level 25 character + MyPlayer->setCharacterLevel(25); + SpawnPremium(*MyPlayer); + for (int i = 0; i < NumSmithItems; i++) { + EXPECT_EQ(PremiumItems[i]._iCreateInfo & CF_LEVEL, QLVLS[i]) << "Index: " << i; + EXPECT_THAT(PremiumItems[i]._itype, AnyOf(SmithTypeMatch(i), PremiumTypeMatch(i))); + } + + // Test buying select items + PremiumItems[0].clear(); + PremiumItems[3].clear(); + PremiumItems[5].clear(); + PremiumItemCount -= 3; + SpawnPremium(*MyPlayer); + for (int i = 0; i < NumSmithItems; i++) { + EXPECT_EQ(PremiumItems[i]._iCreateInfo & CF_LEVEL, QLVLS[i]) << "Index: " << i; + EXPECT_THAT(PremiumItems[i]._itype, AnyOf(SmithTypeMatch(i), PremiumTypeMatch(i))); + } +} + +TEST_F(VendorTest, PremiumQlvl30Plus) +{ + constexpr int QLVLS[] = { 30, 30, 30, 30, 30, 30 }; + + for (int i = 0; i < NumSmithItems; i++) { + PremiumItems[i].clear(); + } + PremiumItemLevel = 1; + + // Finally test level 30+ characters + MyPlayer->setCharacterLevel(31); + SpawnPremium(*MyPlayer); + for (int i = 0; i < NumSmithItems; i++) { + EXPECT_EQ(PremiumItems[i]._iCreateInfo & CF_LEVEL, QLVLS[i]) << "Index: " << i; + EXPECT_THAT(PremiumItems[i]._itype, AnyOf(SmithTypeMatch(i), PremiumTypeMatch(i))); + } + + // Test buying select items + PremiumItems[0].clear(); + PremiumItems[3].clear(); + PremiumItems[5].clear(); + PremiumItemCount -= 3; + SpawnPremium(*MyPlayer); + for (int i = 0; i < NumSmithItems; i++) { + EXPECT_EQ(PremiumItems[i]._iCreateInfo & CF_LEVEL, QLVLS[i]) << "Index: " << i; + EXPECT_THAT(PremiumItems[i]._itype, AnyOf(SmithTypeMatch(i), PremiumTypeMatch(i))); + } + + // Test 30+ levelling + MyPlayer->setCharacterLevel(35); + SpawnPremium(*MyPlayer); + for (int i = 0; i < NumSmithItems; i++) { + EXPECT_EQ(PremiumItems[i]._iCreateInfo & CF_LEVEL, QLVLS[i]) << "Index: " << i; + EXPECT_THAT(PremiumItems[i]._itype, AnyOf(SmithTypeMatch(i), PremiumTypeMatch(i))); + } + + // Test buying select items + PremiumItems[0].clear(); + PremiumItems[3].clear(); + PremiumItems[5].clear(); + PremiumItemCount -= 3; + SpawnPremium(*MyPlayer); + for (int i = 0; i < NumSmithItems; i++) { + EXPECT_EQ(PremiumItems[i]._iCreateInfo & CF_LEVEL, QLVLS[i]) << "Index: " << i; + EXPECT_THAT(PremiumItems[i]._itype, AnyOf(SmithTypeMatch(i), PremiumTypeMatch(i))); + } +} + +TEST_F(VendorTest, HfPremiumQlvl1to5) +{ + for (int i = 0; i < NumSmithItemsHf; i++) { + PremiumItems[i].clear(); + } + PremiumItemLevel = 1; + gbIsHellfire = true; + + // Test level 1 character item qlvl + MyPlayer->setCharacterLevel(1); + SpawnPremium(*MyPlayer); + for (int i = 0; i < NumSmithItemsHf; i++) { + constexpr int QLVLS[] = { 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4, 4 }; + EXPECT_EQ(PremiumItems[i]._iCreateInfo & CF_LEVEL, QLVLS[i]) << "Index: " << i; + EXPECT_THAT(PremiumItems[i]._itype, AnyOf(SmithTypeMatchHf(i), PremiumTypeMatch(i))); + } + + // Test level ups + MyPlayer->setCharacterLevel(5); + SpawnPremium(*MyPlayer); + for (int i = 0; i < NumSmithItemsHf; i++) { + constexpr int QLVLS[] = { 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 8 }; + EXPECT_EQ(PremiumItems[i]._iCreateInfo & CF_LEVEL, QLVLS[i]) << "Index: " << i; + EXPECT_THAT(PremiumItems[i]._itype, AnyOf(SmithTypeMatchHf(i), PremiumTypeMatch(i))); + } +} + +TEST_F(VendorTest, HfPremiumQlvl25) +{ + for (int i = 0; i < NumSmithItemsHf; i++) { + PremiumItems[i].clear(); + } + PremiumItemLevel = 1; + gbIsHellfire = true; + + // Test starting game as a level 25 character + MyPlayer->setCharacterLevel(25); + SpawnPremium(*MyPlayer); + for (int i = 0; i < NumSmithItemsHf; i++) { + constexpr int QLVLS[] = { 23, 23, 23, 24, 24, 24, 25, 25, 25, 26, 26, 26, 27, 27, 28 }; + EXPECT_EQ(PremiumItems[i]._iCreateInfo & CF_LEVEL, QLVLS[i]) << "Index: " << i; + EXPECT_THAT(PremiumItems[i]._itype, AnyOf(SmithTypeMatchHf(i), PremiumTypeMatch(i))); + } + + // Test buying select items + PremiumItems[0].clear(); + PremiumItems[7].clear(); + PremiumItems[14].clear(); + PremiumItemCount -= 3; + SpawnPremium(*MyPlayer); + for (int i = 0; i < NumSmithItemsHf; i++) { + constexpr int QLVLS[] = { 24, 23, 23, 24, 24, 24, 25, 26, 25, 26, 26, 26, 27, 27, 28 }; + EXPECT_EQ(PremiumItems[i]._iCreateInfo & CF_LEVEL, QLVLS[i]) << "Index: " << i; + EXPECT_THAT(PremiumItems[i]._itype, AnyOf(SmithTypeMatchHf(i), PremiumTypeMatch(i))); + } +} + +TEST_F(VendorTest, HfPremiumQlvl30Plus) +{ + for (int i = 0; i < NumSmithItemsHf; i++) { + PremiumItems[i].clear(); + } + PremiumItemLevel = 1; + gbIsHellfire = true; + + // Finally test level 30+ characters + MyPlayer->setCharacterLevel(31); + SpawnPremium(*MyPlayer); + for (int i = 0; i < NumSmithItemsHf; i++) { + constexpr int QLVLS[] = { 29, 29, 29, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30 }; + EXPECT_EQ(PremiumItems[i]._iCreateInfo & CF_LEVEL, QLVLS[i]) << "Index: " << i; + EXPECT_THAT(PremiumItems[i]._itype, AnyOf(SmithTypeMatchHf(i), PremiumTypeMatch(i))); + } + + // Test buying select items + PremiumItems[0].clear(); + PremiumItems[7].clear(); + PremiumItems[14].clear(); + PremiumItemCount -= 3; + SpawnPremium(*MyPlayer); + for (int i = 0; i < NumSmithItemsHf; i++) { + constexpr int QLVLS[] = { 30, 29, 29, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30 }; + EXPECT_EQ(PremiumItems[i]._iCreateInfo & CF_LEVEL, QLVLS[i]) << "Index: " << i; + EXPECT_THAT(PremiumItems[i]._itype, AnyOf(SmithTypeMatchHf(i), PremiumTypeMatch(i))); + } + + constexpr int QLVLS[] = { 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30 }; + + // Test 30+ levelling + MyPlayer->setCharacterLevel(35); + SpawnPremium(*MyPlayer); + for (int i = 0; i < NumSmithItems; i++) { + EXPECT_EQ(PremiumItems[i]._iCreateInfo & CF_LEVEL, QLVLS[i]) << "Index: " << i; + EXPECT_THAT(PremiumItems[i]._itype, AnyOf(SmithTypeMatchHf(i), PremiumTypeMatch(i))); + } + + // Test buying select items + PremiumItems[0].clear(); + PremiumItems[7].clear(); + PremiumItems[14].clear(); + PremiumItemCount -= 3; + SpawnPremium(*MyPlayer); + for (int i = 0; i < NumSmithItems; i++) { + EXPECT_EQ(PremiumItems[i]._iCreateInfo & CF_LEVEL, QLVLS[i]) << "Index: " << i; + EXPECT_THAT(PremiumItems[i]._itype, AnyOf(SmithTypeMatchHf(i), PremiumTypeMatch(i))); + } +} + +TEST_F(VendorTest, WitchGen) +{ + constexpr _item_indexes PINNED_ITEMS[] = { IDI_MANA, IDI_FULLMANA, IDI_PORTAL }; + + MyPlayer->setCharacterLevel(25); + + // Clear global state for test, and force Diablo game mode + for (int i = 0; i < NumWitchItemsHf; i++) { + WitchItems[i].clear(); + } + gbIsHellfire = false; + + SpawnWitch(16); + + SetRndSeed(SEED); + const int N_ITEMS = RandomIntBetween(10, NumWitchItems); + + int n_items = NumWitchPinnedItems; + + for (int i = 0; i < NumWitchPinnedItems; i++) { + EXPECT_EQ(WitchItems[i].IDidx, PINNED_ITEMS[i]) << "Index: " << i; + } + + for (int i = NumWitchPinnedItems; i < NumWitchItems; i++) { + if (WitchItems[i].isEmpty()) break; + EXPECT_THAT(WitchItems[i]._itype, WitchTypeMatch(i)); + + if (WitchItems[i]._itype == ItemType::Misc) { + EXPECT_THAT(WitchItems[i]._iMiscId, WitchMiscMatch(i)); + } + n_items++; + } + EXPECT_EQ(n_items, N_ITEMS); +} + +TEST_F(VendorTest, WitchGenHf) +{ + constexpr _item_indexes PINNED_ITEMS[] = { IDI_MANA, IDI_FULLMANA, IDI_PORTAL }; + constexpr int MAX_PINNED_BOOKS = 4; + + MyPlayer->setCharacterLevel(25); + gbIsHellfire = true; + + // Clear global state for test, and force Hellfire game mode + for (int i = 0; i < NumWitchItemsHf; i++) { + WitchItems[i].clear(); + } + + SpawnWitch(16); + + SetRndSeed(SEED); + const int N_PINNED_BOOKS = RandomIntLessThan(MAX_PINNED_BOOKS); + const int N_ITEMS = RandomIntBetween(10, NumWitchItemsHf); + + int n_books = 0; + int n_items = NumWitchPinnedItems; + + for (int i = 0; i < NumWitchPinnedItems; i++) { + EXPECT_EQ(WitchItems[i].IDidx, PINNED_ITEMS[i]) << "Index: " << i; + } + + for (int i = NumWitchPinnedItems; i < NumWitchItemsHf; i++) { + if (WitchItems[i].isEmpty()) break; + EXPECT_THAT(WitchItems[i]._itype, WitchTypeMatch(i)); + + if (WitchItems[i]._itype == ItemType::Misc) { + EXPECT_THAT(WitchItems[i]._iMiscId, WitchMiscMatch(i)); + } + if (WitchItems[i]._iMiscId == IMISC_BOOK) n_books++; + n_items++; + } + EXPECT_GE(n_books, N_PINNED_BOOKS); + EXPECT_EQ(n_items, N_ITEMS); +} + +TEST_F(VendorTest, HealerGen) +{ + constexpr _item_indexes PINNED_ITEMS[] = { IDI_HEAL, IDI_FULLHEAL, IDI_RESURRECT }; + + MyPlayer->setCharacterLevel(25); + + // Clear global state for test, and force Diablo game mode + for (int i = 0; i < NumHealerItemsHf; i++) { + HealerItems[i].clear(); + } + gbIsHellfire = false; + + SpawnHealer(16); + + SetRndSeed(SEED); + const int N_ITEMS = RandomIntBetween(10, NumHealerItems); + int n_items = NumHealerPinnedItems; + + for (int i = 0; i < NumHealerPinnedItems; i++) { + EXPECT_EQ(HealerItems[i].IDidx, PINNED_ITEMS[i]) << "Index: " << i; + } + + for (int i = NumHealerPinnedItems; i < NumHealerItems; i++) { + if (HealerItems[i].isEmpty()) break; + EXPECT_THAT(HealerItems[i]._itype, Eq(ItemType::Misc)); + EXPECT_THAT(HealerItems[i]._iMiscId, HealerMiscMatch(i)); + n_items++; + } + EXPECT_EQ(n_items, N_ITEMS); +} + +TEST_F(VendorTest, HealerGenHf) +{ + constexpr _item_indexes PINNED_ITEMS[] = { IDI_HEAL, IDI_FULLHEAL, IDI_RESURRECT }; + + MyPlayer->setCharacterLevel(25); + + // Clear global state for test, and force Hellfire game mode + for (int i = 0; i < NumHealerItemsHf; i++) { + HealerItems[i].clear(); + } + gbIsHellfire = true; + + SpawnHealer(16); + + SetRndSeed(SEED); + const int N_ITEMS = RandomIntBetween(10, NumHealerItemsHf); + int n_items = NumHealerPinnedItems; + + for (int i = 0; i < NumHealerPinnedItems; i++) { + EXPECT_EQ(HealerItems[i].IDidx, PINNED_ITEMS[i]) << "Index: " << i; + } + + for (int i = NumHealerPinnedItems; i < NumHealerItemsHf; i++) { + if (HealerItems[i].isEmpty()) break; + EXPECT_THAT(HealerItems[i]._itype, Eq(ItemType::Misc)); + EXPECT_THAT(HealerItems[i]._iMiscId, HealerMiscMatch(i)); + n_items++; + } + EXPECT_EQ(n_items, N_ITEMS); +} + +} // namespace +} // namespace devilution