-
Notifications
You must be signed in to change notification settings - Fork 802
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix infinite loop in unique item randomization
- Loading branch information
1 parent
ca16398
commit 138f937
Showing
5 changed files
with
190 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,7 @@ set(tests | |
drlg_l4_test | ||
effects_test | ||
inv_test | ||
items_test | ||
math_test | ||
missiles_test | ||
pack_test | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
#include <gtest/gtest.h> | ||
#include <random> | ||
|
||
#include "engine/random.hpp" | ||
#include "items.h" | ||
#include "player.h" | ||
#include "spells.h" | ||
#include "utils/str_cat.hpp" | ||
|
||
namespace devilution { | ||
namespace { | ||
|
||
class ItemsTest : public ::testing::Test { | ||
public: | ||
void SetUp() override | ||
{ | ||
Players.resize(1); | ||
MyPlayer = &Players[0]; | ||
for (auto &flag : UniqueItemFlags) | ||
flag = false; | ||
} | ||
|
||
static void SetUpTestSuite() | ||
{ | ||
LoadItemData(); | ||
LoadSpellData(); | ||
gbIsSpawn = false; | ||
gbIsMultiplayer = false; // to also test UniqueItemFlags | ||
} | ||
}; | ||
|
||
void GenerateAllUniques(bool hellfire, const size_t expectedUniques) | ||
{ | ||
gbIsHellfire = hellfire; | ||
|
||
std::mt19937 betterRng; | ||
std::uniform_int_distribution<uint32_t> dist(0, std::numeric_limits<uint32_t>::max()); | ||
|
||
// Get seed for test run from time. If a test run fails, remember the seed and hardcode it here. | ||
uint32_t testRunSeed = static_cast<uint32_t>(time(nullptr)); | ||
betterRng.seed(testRunSeed); | ||
|
||
std::set<int> foundUniques; | ||
|
||
constexpr int max_iterations = 1000000; | ||
int iteration = 0; | ||
|
||
for (int uniqueIndex = 0, n = static_cast<int>(UniqueItems.size()); uniqueIndex < n; ++uniqueIndex) { | ||
|
||
if (!IsUniqueAvailable(uniqueIndex)) | ||
continue; | ||
|
||
if (foundUniques.contains(uniqueIndex)) | ||
continue; | ||
|
||
auto &uniqueItem = UniqueItems[uniqueIndex]; | ||
|
||
_item_indexes uniqueBaseIndex = IDI_GOLD; | ||
for (std::underlying_type_t<_item_indexes> j = IDI_GOLD; j <= IDI_LAST; j++) { | ||
if (!IsItemAvailable(j)) | ||
continue; | ||
if (AllItemsList[j].iItemId != uniqueItem.UIItemId) | ||
continue; | ||
if (AllItemsList[j].iRnd != IDROP_NEVER) | ||
uniqueBaseIndex = static_cast<_item_indexes>(j); | ||
break; | ||
} | ||
|
||
if (uniqueBaseIndex == IDI_GOLD) | ||
continue; // Unique not dropable (Quest-Unique) | ||
|
||
auto &baseItemData = AllItemsList[static_cast<size_t>(uniqueBaseIndex)]; | ||
|
||
while (true) { | ||
iteration += 1; | ||
|
||
if (iteration > max_iterations) | ||
FAIL() << StrCat("Item ", uniqueItem.UIName, " not found in ", max_iterations, " tries with test run seed ", testRunSeed); | ||
|
||
Item testItem = {}; | ||
testItem._iMiscId = baseItemData.iMiscId; | ||
std::uniform_int_distribution<int32_t> dist(0, INT_MAX); | ||
SetRndSeed(dist(betterRng)); | ||
|
||
int targetLvl = uniqueItem.UIMinLvl; | ||
int uper = 1; | ||
if (uniqueItem.UIMinLvl - 4 > 0) { | ||
uper = 15; | ||
targetLvl = uniqueItem.UIMinLvl - 4; | ||
} | ||
|
||
SetupAllItems(*MyPlayer, testItem, uniqueBaseIndex, AdvanceRndSeed(), targetLvl, uper, true, false); | ||
TryRandomUniqueItem(testItem, uniqueBaseIndex, targetLvl, uper, true, false); | ||
SetupItem(testItem); | ||
|
||
if (testItem._iMagical != ITEM_QUALITY_UNIQUE) | ||
continue; | ||
|
||
foundUniques.insert(testItem._iUid); | ||
|
||
if (testItem._iUid == uniqueIndex) | ||
break; | ||
} | ||
} | ||
|
||
EXPECT_EQ(foundUniques.size(), expectedUniques) << StrCat("test run seed ", testRunSeed); | ||
} | ||
|
||
TEST_F(ItemsTest, AllDiabloUniquesCanDrop) | ||
{ | ||
GenerateAllUniques(false, 79); | ||
} | ||
|
||
TEST_F(ItemsTest, AllHellfireUniquesCanDrop) | ||
{ | ||
GenerateAllUniques(true, 99); | ||
} | ||
|
||
} // namespace | ||
} // namespace devilution |