Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Randomize Unique Item Generation (Reverse compatible) #7060

Merged
merged 46 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
4749e07
Randomize Unique Item Generation 2
kphoenix137 Apr 10, 2024
404fffc
refactor and add flag
kphoenix137 Apr 10, 2024
03d73a9
Recreate item with dwBuff
kphoenix137 Apr 10, 2024
6fa8cee
Add calls to new function
kphoenix137 Apr 10, 2024
f0d9b6f
Fixes
kphoenix137 Apr 10, 2024
8a4eac1
Add new function to header file
kphoenix137 Apr 10, 2024
12db2b7
Fixes
kphoenix137 Apr 10, 2024
20e1d6d
Fixes
kphoenix137 Apr 11, 2024
f0cc11f
Only call SetupItem() once
kphoenix137 Apr 11, 2024
16e6291
Fix item position
kphoenix137 Apr 11, 2024
84155f0
Fix logic
kphoenix137 Apr 11, 2024
32de182
Remove debug code
kphoenix137 Apr 11, 2024
1b0a37a
Remove debug code
kphoenix137 Apr 11, 2024
2149b65
Fix error and formatting
kphoenix137 Apr 11, 2024
970788c
Remove unused function param
kphoenix137 Apr 11, 2024
ed524d5
Remove debug
kphoenix137 May 9, 2024
db125f7
Remove debug
kphoenix137 May 9, 2024
410e04f
Remove debug
kphoenix137 May 9, 2024
ace8d5f
Commit suggestion
kphoenix137 May 9, 2024
66d31d7
Consistency for call order
kphoenix137 May 9, 2024
bd8f3bd
Fix issues with uidOffset
kphoenix137 May 10, 2024
01fae32
Add handling for when item shouldn't be unique
kphoenix137 May 10, 2024
7a963d5
Update items.cpp
kphoenix137 May 10, 2024
e42bc44
Update items.cpp
kphoenix137 May 10, 2024
ade183f
Force uper 15 if possible
kphoenix137 May 10, 2024
68b5c68
REfactor
kphoenix137 May 10, 2024
965edf0
Reduce nesting in TryRandomUniqueItem()
StephenCWills May 12, 2024
4a12169
Eliminate the loop to select a random uid
StephenCWills May 12, 2024
67e8ea9
Only compute uid offset for the randomly selected uid
StephenCWills May 12, 2024
7bb0f48
Use type names instead of auto
StephenCWills May 12, 2024
0b5324b
Isolate RNG to avoid closed loops in TryRandomUniqueItem()
StephenCWills May 12, 2024
b095bf2
Fix error in lambda
StephenCWills May 12, 2024
2e7215f
Specify type for std::max()
StephenCWills May 12, 2024
04e31f6
fix recreation
kphoenix137 May 12, 2024
b7784d2
fix error
kphoenix137 May 12, 2024
20ded32
Fix error 2
kphoenix137 May 12, 2024
6e9385f
Fix recreation 2
kphoenix137 May 12, 2024
093a170
Fix 3
kphoenix137 May 12, 2024
29e71aa
Refactor
kphoenix137 May 12, 2024
821f4b1
Refactor
kphoenix137 May 12, 2024
f9e9dbb
Fix RNG for forceNotUnique
StephenCWills May 12, 2024
d236bf5
Fix compiler warning
StephenCWills May 12, 2024
4fc6c69
Don't recreate unique items unless it's necessary
StephenCWills May 12, 2024
d68acc9
Fix comment
kphoenix137 May 12, 2024
9f6695a
Update Source/items.h
AJenbo Jun 18, 2024
89a67f9
Use item seed instead of LCG engine state when rerolling uniques
StephenCWills Jun 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 119 additions & 31 deletions Source/items.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1440,44 +1440,39 @@
});
}

_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 @@
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)
{
item._iSeed = iseed;
SetRndSeed(iseed);
Expand All @@ -1539,7 +1534,7 @@
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 = CheckUnique(item, iblvl, uper, uidOffset);
if (uid == UITEM_INVALID) {
GetItemBonus(player, item, iblvl / 2, iblvl, onlygood, true);
} else {
Expand All @@ -1558,7 +1553,6 @@
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 +1565,9 @@
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 +2242,9 @@

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 +3281,9 @@
_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 +3292,87 @@
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; // Contains uid and uidOffset.

// 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(GetLCGEngineState());
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.

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 = std::count_if(UniqueItems.begin() + uid + 1, UniqueItems.end(), [&uniqueItem](UniqueItem &potentialMatch) {

Check warning on line 3355 in Source/items.cpp

View workflow job for this annotation

GitHub Actions / build

'initializing': conversion from '__int64' to 'int', possible loss of data

Check warning on line 3355 in Source/items.cpp

View workflow job for this annotation

GitHub Actions / build

'initializing': conversion from '__int64' to 'const int', possible loss of data
return uniqueItem.UIItemId == potentialMatch.UIItemId && uniqueItem.UIMinLvl == potentialMatch.UIMinLvl;
});

Point itemPos = item.position;

// Force generate items until we find a uid match.
DiabloGenerator itemGenerator(GetLCGEngineState());
StephenCWills marked this conversation as resolved.
Show resolved Hide resolved
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 +3402,13 @@
// 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 +3427,8 @@
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 +3516,10 @@
uper = 15;

bool onlygood = (icreateinfo & CF_ONLYGOOD) != 0;
bool recreate = (icreateinfo & CF_UNIQUE) != 0;
bool pregen = (icreateinfo & CF_PREGEN) != 0;

SetupAllItems(player, item, idx, iseed, level, uper, onlygood, recreate, pregen);
SetupAllItems(player, item, idx, iseed, level, uper, onlygood, pregen, (item.dwBuff & CF_UIDOFFSET) >> 1);
SetupItem(item);
gbIsHellfire = tmpIsHellfire;
}

Expand Down Expand Up @@ -4561,7 +4644,8 @@

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 +4757,9 @@
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 +4832,9 @@
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 << 5) - 1 - CF_HELLFIRE,
AJenbo marked this conversation as resolved.
Show resolved Hide resolved
// 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
Loading