From 48ff39339f11d87cb9eee1f70f1bca38f2bbdc75 Mon Sep 17 00:00:00 2001 From: elmegaard Date: Wed, 30 Sep 2026 19:31:00 +0200 Subject: [PATCH 1/2] Add discovery/complete filter conditions backed by ProjectDiablo.dll Adds the loot filter tokens [discovered], [allDiscovered], [owned] and [allOwned], evaluated via the D2Client_GetItemDiscoveryState export (DISCOVERYSTATE_* bitmask) resolved from ProjectDiablo.dll in BH::CheckForPD2, following the same GetProcAddress pattern as the PD2 sound hook. Conditions evaluate to false when the dll is absent. The predicates are variant-group aware and also cover unidentified unique/set drops: those have no file index client-side, so the dll aggregates the state of every grail-eligible variant on the base item's code instead. Rules can now highlight anything not yet discovered/owned and hide drops whose base is fully completed. --- BH/BH.cpp | 3 +++ BH/BH.h | 2 ++ BH/Modules/Item/ItemDisplay.cpp | 40 +++++++++++++++++++++++++++++++++ BH/Modules/Item/ItemDisplay.h | 19 ++++++++++++++++ 4 files changed, 64 insertions(+) diff --git a/BH/BH.cpp b/BH/BH.cpp index 984d385b..e0422fa8 100644 --- a/BH/BH.cpp +++ b/BH/BH.cpp @@ -268,6 +268,9 @@ void BH::CheckForPD2() { FARPROC proc = GetProcAddress(pd2Handle, "_D2Client_PlaySoundWithCustomVolumeOrPriority@20"); App.pd2.pd2PlaySoundImpl = proc ? (pd2PlaySoundImpl_t)proc : NULL; + + proc = GetProcAddress(pd2Handle, "_D2Client_GetItemDiscoveryState@12"); + App.pd2.pd2GetItemDiscoveryStateImpl = proc ? (pd2GetItemDiscoveryStateImpl_t)proc : NULL; } } diff --git a/BH/BH.h b/BH/BH.h index ecf3cb63..3cff17bc 100644 --- a/BH/BH.h +++ b/BH/BH.h @@ -45,6 +45,7 @@ namespace BH }; typedef BOOL(__stdcall* pd2PlaySoundImpl_t)(UnitAny* pUnit, int nSound, int nVolume, int nPriority, BOOL bDropSound); +typedef DWORD(__stdcall* pd2GetItemDiscoveryStateImpl_t)(DWORD dwFileIndex, DWORD dwQuality, DWORD dwBaseId); struct BHApp { @@ -187,6 +188,7 @@ struct BHApp struct { pd2PlaySoundImpl_t pd2PlaySoundImpl = NULL; + pd2GetItemDiscoveryStateImpl_t pd2GetItemDiscoveryStateImpl = NULL; } pd2; std::vector hotkeyToggles; diff --git a/BH/Modules/Item/ItemDisplay.cpp b/BH/Modules/Item/ItemDisplay.cpp index e0c12198..6db2ccd1 100644 --- a/BH/Modules/Item/ItemDisplay.cpp +++ b/BH/Modules/Item/ItemDisplay.cpp @@ -562,6 +562,10 @@ enum FilterCondition COND_UPSTAT, COND_MAXSOCKETS, COND_FORMULA, + COND_DISCOVERED, + COND_ALLDISCOVERED, + COND_OWNED, + COND_ALLOWNED, COND_NULL }; @@ -751,6 +755,10 @@ std::map condition_map = {L"WIDTH", COND_WIDTH}, {L"HEIGHT", COND_HEIGHT}, {L"AREA", COND_AREA}, + {L"DISCOVERED", COND_DISCOVERED}, + {L"ALLDISCOVERED", COND_ALLDISCOVERED}, + {L"OWNED", COND_OWNED}, + {L"ALLOWNED", COND_ALLOWNED}, // These have a number as part of the key, handled separately //{"SK", COND_SK}, //{"OS", COND_OS}, @@ -3997,6 +4005,18 @@ void Condition::BuildConditions(vector& conditions, case COND_ID: Condition::AddOperand(conditions, new FlagsCondition(ITEM_IDENTIFIED)); break; + case COND_DISCOVERED: + Condition::AddOperand(conditions, new DiscoveryStateCondition(DISCOVERYSTATE_ANYDISCOVERED)); + break; + case COND_ALLDISCOVERED: + Condition::AddOperand(conditions, new DiscoveryStateCondition(DISCOVERYSTATE_ALLDISCOVERED)); + break; + case COND_OWNED: + Condition::AddOperand(conditions, new DiscoveryStateCondition(DISCOVERYSTATE_ANYOWNED)); + break; + case COND_ALLOWNED: + Condition::AddOperand(conditions, new DiscoveryStateCondition(DISCOVERYSTATE_ALLOWNED)); + break; case COND_ILVL: Condition::AddOperand(conditions, new ItemLevelCondition(operation, value, value2)); break; @@ -4535,6 +4555,26 @@ bool QualityCondition::EvaluateInternal(UnitItemInfo* uInfo, return (uInfo->item->pItemData->dwQuality == quality); } +bool DiscoveryStateCondition::EvaluateInternal(UnitItemInfo* uInfo, + Condition* arg1, + Condition* arg2) { + if (!uInfo->item || !uInfo->item->pItemData) + { + return false; + } + // Backed by the ProjectDiablo.dll discovery export (resolved in + // BH::CheckForPD2); the conditions evaluate to false when + // ProjectDiablo.dll is absent. + if (!App.pd2.pd2GetItemDiscoveryStateImpl) { return false; } + ItemData* pItemData = uInfo->item->pItemData; + // Unidentified unique/set drops have no file index client-side; pass the + // unset sentinel so the dll covers "all the variants it could be" via + // the base item's code. + DWORD dwFileIndex = (pItemData->dwFlags & ITEM_IDENTIFIED) ? pItemData->dwFileIndex : 0xFFFFFFFF; + DWORD dwState = App.pd2.pd2GetItemDiscoveryStateImpl(dwFileIndex, pItemData->dwQuality, uInfo->item->dwTxtFileNo); + return (dwState & discoveryFlag) > 0; +} + bool NonMagicalCondition::EvaluateInternal(UnitItemInfo* uInfo, Condition* arg1, Condition* arg2) diff --git a/BH/Modules/Item/ItemDisplay.h b/BH/Modules/Item/ItemDisplay.h index 5fedfba4..42386f50 100644 --- a/BH/Modules/Item/ItemDisplay.h +++ b/BH/Modules/Item/ItemDisplay.h @@ -191,6 +191,25 @@ class QualityCondition : public Condition Condition* arg2); }; +enum DiscoveryStateFlag +{ + DISCOVERYSTATE_ANYDISCOVERED = 0x1, // any candidate variant group discovered (grail bit set) + DISCOVERYSTATE_ALLDISCOVERED = 0x2, // every candidate variant group discovered + DISCOVERYSTATE_ANYOWNED = 0x4, // any candidate variant group owned (stored in compendium) + DISCOVERYSTATE_ALLOWNED = 0x8, // every candidate variant group owned +}; + +class DiscoveryStateCondition : public Condition +{ +public: + DiscoveryStateCondition(unsigned int flag) : discoveryFlag(flag) { conditionType = CT_Operand; }; +private: + unsigned int discoveryFlag; + bool EvaluateInternal(UnitItemInfo* uInfo, + Condition* arg1, + Condition* arg2); +}; + class NonMagicalCondition : public Condition { public: From 5d85a8d6327f16576568ee09da96d57f40090815 Mon Sep 17 00:00:00 2001 From: elmegaard Date: Fri, 2 Oct 2026 15:43:52 +0200 Subject: [PATCH 2/2] Rename discovery tokens to anyDiscovered/anyOwned for explicitness [discovered]/[owned] become [anyDiscovered]/[anyOwned], matching the [allDiscovered]/[allOwned] pair so any-vs-all semantics is explicit in filter rules. --- BH/Modules/Item/ItemDisplay.cpp | 12 ++++++------ BH/Modules/Item/ItemDisplay.h | 4 +--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/BH/Modules/Item/ItemDisplay.cpp b/BH/Modules/Item/ItemDisplay.cpp index 6db2ccd1..ec79cc71 100644 --- a/BH/Modules/Item/ItemDisplay.cpp +++ b/BH/Modules/Item/ItemDisplay.cpp @@ -562,9 +562,9 @@ enum FilterCondition COND_UPSTAT, COND_MAXSOCKETS, COND_FORMULA, - COND_DISCOVERED, + COND_ANYDISCOVERED, COND_ALLDISCOVERED, - COND_OWNED, + COND_ANYOWNED, COND_ALLOWNED, COND_NULL @@ -755,9 +755,9 @@ std::map condition_map = {L"WIDTH", COND_WIDTH}, {L"HEIGHT", COND_HEIGHT}, {L"AREA", COND_AREA}, - {L"DISCOVERED", COND_DISCOVERED}, + {L"ANYDISCOVERED", COND_ANYDISCOVERED}, {L"ALLDISCOVERED", COND_ALLDISCOVERED}, - {L"OWNED", COND_OWNED}, + {L"ANYOWNED", COND_ANYOWNED}, {L"ALLOWNED", COND_ALLOWNED}, // These have a number as part of the key, handled separately //{"SK", COND_SK}, @@ -4005,13 +4005,13 @@ void Condition::BuildConditions(vector& conditions, case COND_ID: Condition::AddOperand(conditions, new FlagsCondition(ITEM_IDENTIFIED)); break; - case COND_DISCOVERED: + case COND_ANYDISCOVERED: Condition::AddOperand(conditions, new DiscoveryStateCondition(DISCOVERYSTATE_ANYDISCOVERED)); break; case COND_ALLDISCOVERED: Condition::AddOperand(conditions, new DiscoveryStateCondition(DISCOVERYSTATE_ALLDISCOVERED)); break; - case COND_OWNED: + case COND_ANYOWNED: Condition::AddOperand(conditions, new DiscoveryStateCondition(DISCOVERYSTATE_ANYOWNED)); break; case COND_ALLOWNED: diff --git a/BH/Modules/Item/ItemDisplay.h b/BH/Modules/Item/ItemDisplay.h index 42386f50..d992214f 100644 --- a/BH/Modules/Item/ItemDisplay.h +++ b/BH/Modules/Item/ItemDisplay.h @@ -197,9 +197,7 @@ enum DiscoveryStateFlag DISCOVERYSTATE_ALLDISCOVERED = 0x2, // every candidate variant group discovered DISCOVERYSTATE_ANYOWNED = 0x4, // any candidate variant group owned (stored in compendium) DISCOVERYSTATE_ALLOWNED = 0x8, // every candidate variant group owned -}; - -class DiscoveryStateCondition : public Condition +};class DiscoveryStateCondition : public Condition { public: DiscoveryStateCondition(unsigned int flag) : discoveryFlag(flag) { conditionType = CT_Operand; };