Skip to content

Commit

Permalink
Randomize Unique Item Generation (Reverse compatible) (#7060)
Browse files Browse the repository at this point in the history
Co-authored-by: staphen <[email protected]>
  • Loading branch information
kphoenix137 and StephenCWills authored Jun 18, 2024
1 parent 637b51c commit a30f7c0
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 33 deletions.
163 changes: 132 additions & 31 deletions Source/items.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1440,44 +1440,39 @@ _item_indexes RndTypeItems(ItemType itemType, int imid, int lvl)
});
}

_unique_items CheckUnique(Item &item, int lvl, int uper, bool recreate)
_unique_items CheckUnique(Item &item, int lvl, int uper, int uidOffset = 0)
{
std::bitset<128> uok = {};

if (GenerateRnd(100) > uper)
return UITEM_INVALID;

int numu = 0;
for (int j = 0, n = static_cast<int>(UniqueItems.size()); j < n; ++j) {
std::vector<uint8_t> validUniques;
for (int j = 0; j < static_cast<int>(UniqueItems.size()); ++j) {
if (!IsUniqueAvailable(j))
break;
if (UniqueItems[j].UIItemId == AllItemsList[item.IDidx].iItemId
&& lvl >= UniqueItems[j].UIMinLvl
&& (recreate || !UniqueItemFlags[j] || gbIsMultiplayer)) {
uok[j] = true;
numu++;
if (UniqueItems[j].UIItemId == AllItemsList[item.IDidx].iItemId && lvl >= UniqueItems[j].UIMinLvl) {
validUniques.push_back(j);
}
}

if (numu == 0)
if (validUniques.empty())
return UITEM_INVALID;

DiscardRandomValues(1);
uint8_t itemData = 0;
while (numu > 0) {
if (uok[itemData])
numu--;
if (numu > 0)
itemData = (itemData + 1) % 128;

// Check if uidOffset is out of bounds
if (uidOffset >= validUniques.size()) {
return UITEM_INVALID;
}

return (_unique_items)itemData;
uint8_t selectedUniqueIndex = validUniques[validUniques.size() - 1 - uidOffset];

return static_cast<_unique_items>(selectedUniqueIndex);
}

void GetUniqueItem(const Player &player, Item &item, _unique_items uid)
{
UniqueItemFlags[uid] = true;

const auto &uniqueItemData = UniqueItems[uid];

for (auto power : uniqueItemData.powers) {
Expand Down Expand Up @@ -1519,7 +1514,7 @@ int GetItemBLevel(int lvl, item_misc_id miscId, bool onlygood, bool uper15)
return iblvl;
}

void SetupAllItems(const Player &player, Item &item, _item_indexes idx, uint32_t iseed, int lvl, int uper, bool onlygood, bool recreate, bool pregen)
void SetupAllItems(const Player &player, Item &item, _item_indexes idx, uint32_t iseed, int lvl, int uper, bool onlygood, bool pregen, int uidOffset = 0, bool forceNotUnique = false)
{
item._iSeed = iseed;
SetRndSeed(iseed);
Expand All @@ -1539,7 +1534,12 @@ void SetupAllItems(const Player &player, Item &item, _item_indexes idx, uint32_t
if (item._iMiscId != IMISC_UNIQUE) {
int iblvl = GetItemBLevel(lvl, item._iMiscId, onlygood, uper == 15);
if (iblvl != -1) {
_unique_items uid = CheckUnique(item, iblvl, uper, recreate);
_unique_items uid = UITEM_INVALID;
if (!forceNotUnique) {
uid = CheckUnique(item, iblvl, uper, uidOffset);
} else {
DiscardRandomValues(1);
}
if (uid == UITEM_INVALID) {
GetItemBonus(player, item, iblvl / 2, iblvl, onlygood, true);
} else {
Expand All @@ -1558,7 +1558,6 @@ void SetupAllItems(const Player &player, Item &item, _item_indexes idx, uint32_t
GetUniqueItem(player, item, (_unique_items)iseed); // uid is stored in iseed for uniques
}
}
SetupItem(item);
}

void SetupBaseItem(Point position, _item_indexes idx, bool onlygood, bool sendmsg, bool delta, bool spawn = false)
Expand All @@ -1571,7 +1570,9 @@ void SetupBaseItem(Point position, _item_indexes idx, bool onlygood, bool sendms
GetSuperItemSpace(position, ii);
int curlv = ItemsGetCurrlevel();

SetupAllItems(*MyPlayer, item, idx, AdvanceRndSeed(), 2 * curlv, 1, onlygood, false, delta);
SetupAllItems(*MyPlayer, item, idx, AdvanceRndSeed(), 2 * curlv, 1, onlygood, delta);
TryRandomUniqueItem(item, idx, 2 * curlv, 1, onlygood, delta);
SetupItem(item);

if (sendmsg)
NetSendCmdPItem(false, CMD_DROPITEM, item.position, item);
Expand Down Expand Up @@ -2246,7 +2247,9 @@ void CreateMagicItem(Point position, int lvl, ItemType itemType, int imid, int i

while (true) {
item = {};
SetupAllItems(*MyPlayer, item, idx, AdvanceRndSeed(), 2 * lvl, 1, true, false, delta);
SetupAllItems(*MyPlayer, item, idx, AdvanceRndSeed(), 2 * lvl, 1, true, delta);
TryRandomUniqueItem(item, idx, 2 * lvl, 1, true, delta);
SetupItem(item);
if (item._iCurs == icurs)
break;

Expand Down Expand Up @@ -3283,7 +3286,9 @@ Item *SpawnUnique(_unique_items uid, Point position, std::optional<int> level /*
_item_indexes idx = GetItemIndexForDroppableItem(false, [&uniqueItemData](const ItemData &item) {
return item.itype == uniqueItemData.itype;
});
SetupAllItems(*MyPlayer, item, idx, AdvanceRndSeed(), curlv * 2, 15, true, false, false);
SetupAllItems(*MyPlayer, item, idx, AdvanceRndSeed(), curlv * 2, 15, true, false);
TryRandomUniqueItem(item, idx, curlv * 2, 15, true, false);
SetupItem(item);
}

if (sendmsg)
Expand All @@ -3292,6 +3297,93 @@ Item *SpawnUnique(_unique_items uid, Point position, std::optional<int> level /*
return &item;
}

void TryRandomUniqueItem(Item &item, _item_indexes idx, int8_t mLevel, int uper, bool onlygood, bool pregen)
{
// If the item is a non-quest unique, find a random valid uid and force generate items to get an item with that uid.
if ((item._iCreateInfo & CF_UNIQUE) == 0 || item._iMiscId == IMISC_UNIQUE)
return;

std::vector<int> uids;

// 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<size_t>(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;
// 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<int>(i);
if (IsUniqueAvailable(uid) && isMatchingItemId && meetsLevelRequirement && uniqueNotDroppedAlready)
uids.emplace_back(uid);
}

// If we find at least one unique in uids that hasn't been obtained yet, we can proceed getting a random unique.
if (uids.empty()) {
// Set uper to 1 and make the level adjustment so we have better odds of not generating a unique item.
if (uper == 15)
mLevel += 4;
uper = 1;

Point itemPos = item.position;

// Force generate a non-unique item.
DiabloGenerator itemGenerator(item._iSeed);
do {
item = {}; // Reset item data
item.position = itemPos;
SetupAllItems(*MyPlayer, item, idx, itemGenerator.advanceRndSeed(), mLevel, uper, onlygood, pregen);
} while (item._iMagical == ITEM_QUALITY_UNIQUE);

return;
}

int32_t uidsIdx = std::max<int32_t>(0, GenerateRnd(static_cast<int32_t>(uids.size()))); // Index into uids, used to get a random uid from the uids vector.
int uid = uids[uidsIdx]; // Actual unique id.

// If the selected unique was already generated, there is no need to fiddle with its parameters.
if (item._iUid == uid) {
UniqueItemFlags[uid] = true;
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;
}

// Amount to decrease the final uid by in CheckUnique() to get the desired unique.
const int uidOffset = static_cast<int>(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);

// Set item as obtained to prevent it from being dropped again in SP.
if (!gbIsMultiplayer) {
UniqueItemFlags[uid] = true;
}
item.dwBuff |= (uidOffset << 1) & CF_UIDOFFSET;
}

void SpawnItem(Monster &monster, Point position, bool sendmsg, bool spawn /*= false*/)
{
_item_indexes idx;
Expand Down Expand Up @@ -3321,13 +3413,13 @@ void SpawnItem(Monster &monster, Point position, bool sendmsg, bool spawn /*= fa
// Drop the brain as extra item to ensure that all clients see the brain drop
// When executing SpawnItem is not reliable, cause another client can already have the quest state updated before SpawnItem is executed
Point posBrain = GetSuperItemLoc(position);
SpawnQuestItem(IDI_BRAIN, posBrain, false, false, true);
SpawnQuestItem(IDI_BRAIN, posBrain, 0, 0, true);
}
// Normal monster
if ((monster.data().treasure & T_NODROP) != 0)
return;
onlygood = false;
idx = RndItemForMonsterLevel(monster.level(sgGameInitInfo.nDifficulty));
idx = RndItemForMonsterLevel(static_cast<int8_t>(monster.level(sgGameInitInfo.nDifficulty)));
}

if (idx == IDI_NONE)
Expand All @@ -3346,6 +3438,8 @@ void SpawnItem(Monster &monster, Point position, bool sendmsg, bool spawn /*= fa
mLevel -= 15;

SetupAllItems(*MyPlayer, item, idx, AdvanceRndSeed(), mLevel, uper, onlygood, false, false);
TryRandomUniqueItem(item, idx, mLevel, uper, onlygood, false);
SetupItem(item);

if (sendmsg)
NetSendCmdPItem(false, CMD_DROPITEM, item.position, item);
Expand Down Expand Up @@ -3433,10 +3527,12 @@ void RecreateItem(const Player &player, Item &item, _item_indexes idx, uint16_t
uper = 15;

bool onlygood = (icreateinfo & CF_ONLYGOOD) != 0;
bool recreate = (icreateinfo & CF_UNIQUE) != 0;
bool forceNotUnique = (icreateinfo & CF_UNIQUE) == 0;
bool pregen = (icreateinfo & CF_PREGEN) != 0;
auto uidOffset = static_cast<int>((item.dwBuff & CF_UIDOFFSET) >> 1);

SetupAllItems(player, item, idx, iseed, level, uper, onlygood, recreate, pregen);
SetupAllItems(player, item, idx, iseed, level, uper, onlygood, pregen, uidOffset, forceNotUnique);
SetupItem(item);
gbIsHellfire = tmpIsHellfire;
}

Expand Down Expand Up @@ -4561,7 +4657,8 @@ void CreateSpellBook(Point position, SpellID ispell, bool sendmsg, bool delta)

while (true) {
item = {};
SetupAllItems(*MyPlayer, item, idx, AdvanceRndSeed(), 2 * lvl, 1, true, false, delta);
SetupAllItems(*MyPlayer, item, idx, AdvanceRndSeed(), 2 * lvl, 1, true, delta);
SetupItem(item);
if (item._iMiscId == IMISC_BOOK && item._iSpell == ispell)
break;
}
Expand Down Expand Up @@ -4673,7 +4770,9 @@ std::string DebugSpawnItem(std::string itemName)
continue;

testItem = {};
SetupAllItems(*MyPlayer, testItem, idx, AdvanceRndSeed(), monsterLevel, 1, false, false, false);
SetupAllItems(*MyPlayer, testItem, idx, AdvanceRndSeed(), monsterLevel, 1, false, false);
TryRandomUniqueItem(testItem, idx, monsterLevel, 1, false, false);
SetupItem(testItem);

std::string tmp = AsciiStrToLower(testItem._iIName);
if (tmp.find(itemName) != std::string::npos)
Expand Down Expand Up @@ -4746,7 +4845,9 @@ std::string DebugSpawnUniqueItem(std::string itemName)
for (auto &flag : UniqueItemFlags)
flag = true;
UniqueItemFlags[uniqueIndex] = false;
SetupAllItems(*MyPlayer, testItem, uniqueBaseIndex, testItem._iMiscId == IMISC_UNIQUE ? uniqueIndex : AdvanceRndSeed(), uniqueItem.UIMinLvl, 1, false, false, false);
SetupAllItems(*MyPlayer, testItem, uniqueBaseIndex, testItem._iMiscId == IMISC_UNIQUE ? uniqueIndex : AdvanceRndSeed(), uniqueItem.UIMinLvl, 1, false, false);
TryRandomUniqueItem(testItem, uniqueBaseIndex, uniqueItem.UIMinLvl, 1, false, false);
SetupItem(testItem);
for (auto &flag : UniqueItemFlags)
flag = false;

Expand Down
4 changes: 3 additions & 1 deletion Source/items.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ enum icreateinfo_flag {

enum icreateinfo_flag2 {
// clang-format off
CF_HELLFIRE = 1,
CF_HELLFIRE = 1 << 0,
CF_UIDOFFSET = ((1 << 4) - 1) << 1,
// clang-format on
};

Expand Down Expand Up @@ -512,6 +513,7 @@ Point GetSuperItemLoc(Point position);
void GetItemAttrs(Item &item, _item_indexes itemData, int lvl);
void SetupItem(Item &item);
Item *SpawnUnique(_unique_items uid, Point position, std::optional<int> level = std::nullopt, bool sendmsg = true, bool exactPosition = false);
void TryRandomUniqueItem(Item &item, _item_indexes idx, int8_t mLevel, int uper, bool onlygood, bool pregen);
void SpawnItem(Monster &monster, Point position, bool sendmsg, bool spawn = false);
void CreateRndItem(Point position, bool onlygood, bool sendmsg, bool delta);
void CreateRndUseful(Point position, bool sendmsg);
Expand Down
2 changes: 1 addition & 1 deletion Source/msg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2425,6 +2425,7 @@ void PrepareEarForNetwork(const Item &item, TEar &ear)
void RecreateItem(const Player &player, const TItem &messageItem, Item &item)
{
const uint32_t dwBuff = SDL_SwapLE32(messageItem.dwBuff);
item.dwBuff = dwBuff;
RecreateItem(player, item,
static_cast<_item_indexes>(SDL_SwapLE16(messageItem.wIndx)), SDL_SwapLE16(messageItem.wCI),
SDL_SwapLE32(messageItem.dwSeed), SDL_SwapLE16(messageItem.wValue), (dwBuff & CF_HELLFIRE) != 0);
Expand All @@ -2438,7 +2439,6 @@ void RecreateItem(const Player &player, const TItem &messageItem, Item &item)
item._iPLToHit = ClampToHit(item, static_cast<uint8_t>(SDL_SwapLE16(messageItem.wToHit)));
item._iMaxDam = ClampMaxDam(item, static_cast<uint8_t>(SDL_SwapLE16(messageItem.wMaxDam)));
}
item.dwBuff = dwBuff;
}

void ClearLastSentPlayerCmd()
Expand Down
1 change: 1 addition & 0 deletions Source/pack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ void UnPackItem(const ItemPack &packedItem, const Player &player, Item &item, bo
RecreateEar(item, ic, iseed, ivalue & 0xFF, heroName);
} else {
item = {};
item.dwBuff = SDL_SwapLE32(packedItem.dwBuff);
RecreateItem(player, item, idx, SDL_SwapLE16(packedItem.iCreateInfo), SDL_SwapLE32(packedItem.iSeed), SDL_SwapLE16(packedItem.wValue), isHellfire);
item._iIdentified = (packedItem.bId & 1) != 0;
item._iMaxDur = packedItem.bMDur;
Expand Down

0 comments on commit a30f7c0

Please sign in to comment.