diff --git a/Source/items.cpp b/Source/items.cpp index bd20b0bfee8a..aac7c2f8f16e 100644 --- a/Source/items.cpp +++ b/Source/items.cpp @@ -1439,19 +1439,25 @@ _item_indexes RndTypeItems(ItemType itemType, int imid, int lvl) }); } -_unique_items CheckUnique(Item &item, int lvl, int uper, int uidOffset = 0) +std::vector GetValidUniques(int lvl, unique_base_item baseItemId) { - if (GenerateRnd(100) > uper) - return UITEM_INVALID; - std::vector validUniques; for (int j = 0; j < static_cast(UniqueItems.size()); ++j) { if (!IsUniqueAvailable(j)) break; - if (UniqueItems[j].UIItemId == AllItemsList[item.IDidx].iItemId && lvl >= UniqueItems[j].UIMinLvl) { + if (UniqueItems[j].UIItemId == baseItemId && lvl >= UniqueItems[j].UIMinLvl) { validUniques.push_back(j); } } + return validUniques; +} + +_unique_items CheckUnique(Item &item, int lvl, int uper, int uidOffset = 0) +{ + if (GenerateRnd(100) > uper) + return UITEM_INVALID; + + auto validUniques = GetValidUniques(lvl, AllItemsList[item.IDidx].iItemId); if (validUniques.empty()) return UITEM_INVALID; @@ -3284,22 +3290,21 @@ void TryRandomUniqueItem(Item &item, _item_indexes idx, int8_t mLevel, int uper, if ((item._iCreateInfo & CF_UNIQUE) == 0 || item._iMiscId == IMISC_UNIQUE) return; - std::vector uids; + SetRndSeed(item._iSeed); + + // Get item base level that is used in CheckUnique to get the correct valid uniques for the base item + DiscardRandomValues(1); // GetItemAttrs + int blvl = GetItemBLevel(mLevel, item._iMiscId, onlygood, uper == 15); // Gather all potential unique items. uid is the index into UniqueItems. - for (size_t i = 0; i < UniqueItems.size(); ++i) { - const UniqueItem &uniqueItem = UniqueItems[i]; - // Verify the unique item base item matches idx. - bool isMatchingItemId = uniqueItem.UIItemId == AllItemsList[static_cast(idx)].iItemId; - // Verify itemLvl is at least the unique's minimum required level. - // mLevel remains unadjusted when arriving in this function, and there is no call to set iblvl until CheckUnique(), so we adjust here. - bool meetsLevelRequirement = ((uper == 15) ? mLevel + 4 : mLevel) >= uniqueItem.UIMinLvl; + auto validUniques = GetValidUniques(blvl, AllItemsList[static_cast(idx)].iItemId); + assert(!validUniques.empty()); + std::vector uids; + for (auto &possibleUid : validUniques) { // Verify item hasn't been dropped yet. We set this to true in MP, since uniques previously dropping shouldn't prevent further identical uniques from dropping. - bool uniqueNotDroppedAlready = !UniqueItemFlags[i] || gbIsMultiplayer; - - int uid = static_cast(i); - if (IsUniqueAvailable(uid) && isMatchingItemId && meetsLevelRequirement && uniqueNotDroppedAlready) - uids.emplace_back(uid); + if (!UniqueItemFlags[possibleUid] || gbIsMultiplayer) { + uids.emplace_back(possibleUid); + } } // If we find at least one unique in uids that hasn't been obtained yet, we can proceed getting a random unique. @@ -3331,32 +3336,21 @@ void TryRandomUniqueItem(Item &item, _item_indexes idx, int8_t mLevel, int uper, return; } - const UniqueItem &uniqueItem = UniqueItems[uid]; - int targetLvl = 1; // Target level for reverse compatibility, since vanilla always takes the last applicable uid in the list. - - // Set target level. Ideally we use uper 15 to have a 16% chance of generating a unique item. - if (uniqueItem.UIMinLvl - 4 > 0) { // Negative level will underflow. Lvl 0 items may have unintended consequences. - uper = 15; - targetLvl = uniqueItem.UIMinLvl - 4; - } else { - uper = 1; - targetLvl = uniqueItem.UIMinLvl; + // Find our own id to calculate the offset in validUniques + int uidOffset = -1; + for (size_t i = 0; i < validUniques.size(); i++) { + if (validUniques[i] == uid) { + // vanilla always picks the last unique, so the offset is calculated from the back of the valid unique list + uidOffset = static_cast(validUniques.size() - i - 1); + } } + assert(uidOffset != -1); - // Amount to decrease the final uid by in CheckUnique() to get the desired unique. - const auto uidOffset = static_cast(std::count_if(UniqueItems.begin() + uid + 1, UniqueItems.end(), [&uniqueItem](UniqueItem &potentialMatch) { - return uniqueItem.UIItemId == potentialMatch.UIItemId && uniqueItem.UIMinLvl == potentialMatch.UIMinLvl; - })); - - Point itemPos = item.position; - - // Force generate items until we find a uid match. - DiabloGenerator itemGenerator(item._iSeed); - do { - item = {}; // Reset item data - item.position = itemPos; - SetupAllItems(*MyPlayer, item, idx, itemGenerator.advanceRndSeed(), targetLvl, uper, onlygood, pregen, uidOffset); - } while (item._iUid != uid); + // Recreate the item with new offset, this creates the desired unique item + int seed = item._iSeed; + item = {}; // Reset item data + SetupAllItems(*MyPlayer, item, idx, seed, mLevel, uper, onlygood, pregen, uidOffset); + assert(item._iUid == uid); // Set item as obtained to prevent it from being dropped again in SP. if (!gbIsMultiplayer) {