Skip to content

Instantly share code, notes, and snippets.

@SeanSullivan86
Created October 5, 2022 23:00
Show Gist options
  • Save SeanSullivan86/e912b40bb85f9822b9ef507d91534c88 to your computer and use it in GitHub Desktop.
Save SeanSullivan86/e912b40bb85f9822b9ef507d91534c88 to your computer and use it in GitHub Desktop.
D2 TreasureClassDropper
#include "D2Structs.h"
#include <WinDef.h>
void __fastcall TreasureClassDropper_0055a6d0
(D2GameStrc* pGame, D2UnitStrc* pItemDropper, D2UnitStrc* pItemReceiver, D2TCExShortStrc* pParentTC,
uint presetQuality, uint ilvl, BOOL ignoreNoDrop, D2UnitStrc** pOutput, uint* itemCount, uint maxItems) {
D2Assert(pParentTC != nullptr, 0xf3a);
D2Assert(pOutput == nullptr || maxItems > 0, 0xf44);
D2Assert(pGame->bExpansion); // this function has been simplified to remove support for D2Classic
uint howManyItemsCreatedSoFar;
if (itemCount == nullptr) {
itemCount = &howManyItemsCreatedSoFar;
}
*itemCount = 0;
if (pOutput == nullptr) {
if (maxItems < 1) {
maxItems = 6;
}
}
D2TCStackInfo tcStack[64];
initializeTreasureClassStackEntry(tcStack, 0, pParentTC);
int depth = 0;
// each iteration makes progress by doing 1 pick, or popping a TC off the stack
while (depth >= 0) {
D2TCStackInfo tc = tcStack[depth];
if (tc.nPicksRemaining == 0 || tc.pTCData->nProb == 0) {
depth--;
continue;
}
tc.nPicksRemaining--;
int targetCumulativeFrequency;
if (tc.pTCData->nPicks < 0) {
// picks < 0 ==> Choose target of each pick deterministically
targetCumulativeFrequency = (-tc.pTCData->nPicks) - tc.nPicksRemaining - 1;
} else {
// picks > 0 ==> Choose random target each time
int rand = getRandomPositionWithinTCFrequencySumOrNodrop(tc.pTCData, pGame, pItemReceiver, pItemDropper, ignoreNoDrop);
if (rand < 0) { // no-drop
continue;
}
targetCumulativeFrequency = rand;
}
D2TCItemInfoStrc* tcItem = getTreasureClassItemFromCumulativeFrequency(tc.pTCData, targetCumulativeFrequency);
if (tcItem->nFlags & 0x4) { // tcItem is another TC
D2TCExShortStrc* newTC = GetTreasureClassShortObjById_00654e00(tcItem->nItemId, 0);
depth++;
D2Assert(depth < 64, 0xfea);
initializeTreasureClassStackEntry(tcStack, depth, newTC);
} else { // tcItem is an item (weapon/armor/misc)
D2UnitStrc* createdItem = generateAnItemFromATreasureClassItem(pGame, pItemReceiver, pItemDropper, tcItem,
&tcStack[depth].qualityInfo, presetQuality, ilvl);
if (createdItem != nullptr) {
if (pOutput != nullptr) {
pOutput[*itemCount] = createdItem;
}
(*itemCount)++;
if (*itemCount >= maxItems) {
return;
}
}
}
}
}
void initializeTreasureClassStackEntry(D2TCStackInfo* tcStack, int insertIndex, D2TCExShortStrc* tc) {
D2TCStackInfo* tcStackEntry = tcStack + insertIndex;
/* Make nPicksRemaining always positive on the TC Stack.If we need to know if it was originally
negative (to decide whether or not to do a random roll), check the D2TCExShortStrc.nPicks again. */
int picks = tc->nPicks;
if (picks < 0) {
picks = -picks;
} else if (picks == 0) {
picks = 1;
}
tcStackEntry->pTCData = tc;
tcStackEntry->nPicksRemaining = picks;
if (insertIndex == 0) {
memcpy(&(tcStackEntry->qualityInfo), &(tc->qualityInfo), sizeof(D2TCQualityInfo));
} else {
/* Iterate through the 'chance to drop' modifiers for each of the 6 item qualities in the TC struct
For each quality, either inherit the value from the parent of the current TC, or take the value
from the new TC's config, whichever is better/greater. */
for (int i = 0; i < 6; i++) {
((short*)(&tcStackEntry->qualityInfo))[i] = max(
((short*)(&(tcStackEntry - 1)->qualityInfo))[i],
((short*)(&tc->qualityInfo))[i]);
}
}
}
D2UnitStrc* generateAnItemFromATreasureClassItem(D2GameStrc* pGame, D2UnitStrc* pItemReceiver, D2UnitStrc* pItemDropper,
D2TCItemInfoStrc* tcItem, D2TCQualityInfo* tcQualityInfo, int presetQuality, int ilvl) {
ushort itemId = tcItem->nItemId;
if (itemId == 0xffff) {
return nullptr;
}
int setOrUniqueTxtRow = 0, itemQuality;
if ((tcItem->nFlags & 1) == 0) {
if ((tcItem->nFlags & 2) == 0) {
itemQuality = presetQuality;
if (presetQuality == 0) {
itemQuality = GenerateItemQuality_00558640(itemId, ilvl, pGame, pItemDropper, pItemReceiver, tcQualityInfo);
}
} else {
itemQuality = ITEMQUAL_SET;
setOrUniqueTxtRow = tcItem->nTxtRow + 1;
}
} else {
itemQuality = ITEMQUAL_UNIQUE;
setOrUniqueTxtRow = tcItem->nTxtRow + 1;
}
uint itemQualityFlags = 0;
if (tcQualityInfo->nSuperior != 0) {
int rand = Seed_GetRand0ToN_0045C390(pItemDropper->pSeed, 1024);
if (rand < tcQualityInfo->nSuperior) {
itemQualityFlags |= 0x4;
}
}
if (tcQualityInfo->nNormal != 0) {
int rand = Seed_GetRand0ToN_0045C390(pItemDropper->pSeed, 1024);
if (rand < tcQualityInfo->nNormal) {
itemQualityFlags |= 0x10;
}
}
D2UnitStrc* createdItem = CreateItem_0055a550(pItemDropper, itemId, pGame, itemQuality, setOrUniqueTxtRow, itemQualityFlags);
if (createdItem != nullptr) {
if (IsItemUnitBelongingToItemTypePerhapsRecursively_00629bb0(createdItem, 4) && tcItem->nTxtRow != 0) {
// for a gold drop, the parameter in the TC Item Name (ie. "gld,mul=2048") is in txtRow field
// It's a multiplier expressed in 256ths, so mul=2048 multiplies the gold value by 8
int goldAmount = GetStatValueFromUnit_WIP_00625480(createdItem, 0xe);
int adjustedGold = (tcItem->nTxtRow * goldAmount) >> 8;
Unit_SetStat_MaybeOnlyForGoldAmount_00530ea0(createdItem, 0xe, adjustedGold);
}
if (*itemsCreated >= maxItems) {
return;
}
// oops, item doesnt get adjusted for goldfind if it made us hit maxItems ?
Item_AdjustGoldDropForPlayerGoldfind_005589a0(player, createdItem);
}
return createdItem;
}
int getRandomPositionWithinTCFrequencySumOrNodrop(D2TCExShortStrc* pTreasureClass,
D2GameStrc* pGame, D2UnitStrc* pItemReceiver, D2UnitStrc* pItemDropper, BOOL ignoreNoDrop) {
int adjustedNodropFreq = getAdjustedNoDropFrequency(pTreasureClass, pGame, pItemReceiver, pItemDropper);
int frequencySum = pTreasureClass->nProb + (ignoreNoDrop ? 0 : adjustedNodropFreq);
int rand;
if (frequencySum < 1) {
rand = 0;
} else {
rand = GetAPlusRandModB_00472280(&pItemDropper->pSeed, 0, frequencySum);
}
if (ignoreNoDrop) return rand;
if (rand >= adjustedNodropFreq) {
return rand - adjustedNodropFreq;
}
return -1; // roll result was no-drop
}
D2TCItemInfoStrc* getTreasureClassItemFromCumulativeFrequency(D2TCExShortStrc* tc, int targetCumulativeFrequency) {
int left = 0, right = tc->nTypes, idx;
int selectedIndex = -1;
if (tc->nTypes > 0) {
do {
idx = (right - left) / 2 + left;
int partialSum = tc->pInfo[idx].nProb;
if (partialSum == targetCumulativeFrequency) {
if (left < right) {
selectedIndex = idx;
}
break;
} else if (partialSum < targetCumulativeFrequency) {
left = idx + 1;
idx = right;
} else {
right = idx;
}
right = idx;
} while (left < idx);
}
if (selectedIndex == -1) {
selectedIndex = (left <= 0) ? 0 : (left - 1);
}
// original code had a case for looping to the next pick if selectedIndex >= nTypes
// TODO: See whether that's possible with their version of binary search
return &tc->pInfo[selectedIndex];
}
int getAdjustedNoDropFrequency(D2TCExShortStrc* pTreasureClass, D2GameStrc* pGame, D2UnitStrc* pItemReceiver, D2UnitStrc* pItemDropper) {
int adjustedPlayerCount = getAdjustedPlayerCountForNodropCalculation(pGame, pItemReceiver, pItemDropper);
if (pTreasureClass->nNoDrop == 0) {
return 0;
}
if (adjustedPlayerCount <= 1) {
return pTreasureClass->nNoDrop;
}
double originalNodropRate = ((double)pTreasureClass->nNoDrop) / (double)(pTreasureClass->nNoDrop + pTreasureClass->nProb);
// newNodropRate = pow(originalNodropRate, adjustedPlayerCount)
// It was implemented in assembly as repeated multiplication
double newNodropRate = originalNodropRate;
for (int n = 1; n < adjustedPlayerCount; n++) {
newNodropRate *= originalNodropRate;
}
// To avoid division by 0, I suppose. This would only happen if originalNodropRate==1
if ((1.0 - newNodropRate) == 0.0) {
return 0;
}
// we want to choose noDropFreq such that noDropFreq / (noDropFreq + itemFreq) = newNodropRate
// nodropFreq = newNodropRate * (nodropFreq + itemFreq)
// nodropFreq = newNodropRrate * nodropFreq + newNodropRate * itemFreq
// nodropFreq * (1 - newNodropRate) = newNodropRate * itemFreq
// nodropFreq = newNodropRate * itemFreq / (1 - newNodropRate)
return (int)((newNodropRate * pTreasureClass->nProb) / (1.0 - newNodropRate));
}
int getAdjustedPlayerCountForNodropCalculation(D2GameStrc* pGame, D2UnitStrc* pItemReceiver, D2UnitStrc* pItemDropper) {
int nearbyPlayerCount = 1;
if (pItemReceiver != nullptr) {
D2UnitStrc* pPlayer = pItemReceiver;
if (pPlayer->dwUnitType != 0) { // if itemReceiver is not a player (for example, mercenary)
pPlayer = GetMonsterOwner_0058f0d0(pItemReceiver);
}
if (pPlayer != nullptr && pPlayer->dwUnitType == 0) {
nearbyPlayerCount = Player_GetPartiedPlayerCountInSameDrlgLevel_WIP_005408e0(pGame, pPlayer);
if (nearbyPlayerCount < 1) {
nearbyPlayerCount = 1;
} else if (nearbyPlayerCount > 8) {
nearbyPlayerCount = 8;
}
}
}
int playerCount = Game_GetMaxOfPlayersNAndLivingPlayerCount_00535790(pGame);
// playerCount ~= nearbyPlayers + farPlayers
// adjPlayerCount ~= nearbyPlayers + (farPlayers/2)
int adjustedPlayerCount = nearbyPlayerCount + (playerCount - nearbyPlayerCount) / 2;
if (pItemDropper != nullptr && GetUnitTypeOr6IfNull_0044be50(pItemDropper) == 1) {
int monsterPlayerCount = GetStatValueFromUnit_WIP_00625480(pDroppedFromUnit, 100);
if (monsterPlayerCount < 1) {
monsterPlayerCount = 1;
}
if (monsterPlayerCount < adjustedPlayerCount) {
adjustedPlayerCount = monsterPlayerCount;
}
}
return adjustedPlayerCount;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment