Loot filter: quest items are their own category, first and never voided

Quest items are recognized by the type's quest number or the scripts'
QuestItem / MainQuestItem marks, checked before the other categories.
Their row in the Beute tab has no Löschen box; Void in the ini reads as
Default.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
khallaandClaude Opus 5.5 committed 2026-10-25 18:24:28 +01:00
1 parent c0eedbce7f
commit 401e20b1f2
5 files changed
+61 -28

No files matched your search

+25 -13
View File
@@ -1,6 +1,6 @@
// Loot filter (request of 2026-10-19), set on the options window's Beute page
// (mods/options.cpp): per category - Gold, Tränke, and the item colours white,
// blue, yellow, brown, green - one of
// (mods/options.cpp): per category - Questgegenstände, Gold, Tränke, and the
// item colours white, blue, yellow, brown, green - one of
// Void a drop is deleted at once (monster loot, chests; items the
// player puts down stay),
// Ignore the pickup key (Q, the collect mode) leaves it,
@@ -9,10 +9,14 @@
// It replaces the game's PICKUPAUTO setting (all / valuable / gold only),
// whose radio buttons are gone from the page.
//
// - Categories from the game's data: gold by type (0x43ae20), potions by item
// class 9, set items green (0x433f00 +0x23), otherwise the quality tier
// that picks the item colour (0x5c1330, game/engine/items.h). Runes and the
// types CanCollectItem always takes (quest items) are not filtered.
// - Categories from the game's data: quest items first (request of
// 2026-10-25: they were voided as their colour's category; "it needs to be
// top priority, also Löschen is not an option for those"; type's quest
// number or quest-item mark, game::ItemIsQuestItem), gold by type
// (0x43ae20), potions by item class 9, set items green (0x433f00 +0x23),
// otherwise the quality tier that picks the item colour (0x5c1330,
// game/engine/items.h). Runes and objects flagged always-collected are not
// filtered.
// - Pickup: cCreature::CanCollectItem (0x4ec170) is the collect mode's filter
// (both of its scans; a clicked item does not ask it).
// - Void: items made by the loot drop (cCreature 0x559a30, via
@@ -44,7 +48,7 @@
namespace mods::lootfilter {
namespace {
enum Category : int { kGold, kPotions, kWhite, kBlue, kYellow, kBrown, kGreen, kNone = -1 };
enum Category : int { kQuest, kGold, kPotions, kWhite, kBlue, kYellow, kBrown, kGreen, kNone = -1 };
struct CategoryInfo {
const char* key; // in [loot]
@@ -53,6 +57,7 @@ struct CategoryInfo {
};
// Gold and potions as the game's labels show them: white.
constexpr CategoryInfo kCategories[kCategoryCount] = {
{"Quest", L"Questgegenstände", game::kItemColourWhite},
{"Gold", L"Gold", game::kItemColourWhite}, {"Potions", L"Tränke", game::kItemColourWhite},
{"White", L"Weiß", game::kItemColourWhite}, {"Blue", L"Blau", game::kItemColourBlue},
{"Yellow", L"Gelb", game::kItemColourYellow}, {"Brown", L"Braun", game::kItemColourBrown},
@@ -67,10 +72,10 @@ constexpr ModeInfo kModes[kModeCount] = {
{"Void", L"Löschen"}, {"Ignore", L"Ignorieren"}, {"Default", L"Standard"}, {"AlwaysShow", L"Immer anzeigen"}};
Mode g_modes[kCategoryCount] = {Mode::Default, Mode::Default, Mode::Default, Mode::Default,
Mode::Default, Mode::Default, Mode::Default};
Mode::Default, Mode::Default, Mode::Default, Mode::Default};
// cObject +0x14 bit 0x800: CanCollectItem takes such objects whatever the
// pickup setting (as the types of ItemAlwaysCollected).
// cObject +0x14 bit 0x800: the game's CanCollectItem takes such objects
// whatever the pickup setting.
constexpr uint32_t kObjectAlwaysCollected = 0x800;
int CategoryOf(void* object) {
@@ -82,8 +87,11 @@ int CategoryOf(void* object) {
if (type == 0 || type >= game::kItemTypeCount) {
return kNone;
}
if (game::ItemIsQuestItem(type)) {
return kQuest;
}
const uint32_t flags = *reinterpret_cast<const uint32_t*>(static_cast<const uint8_t*>(object) + game::kObjectFlags);
if ((flags & kObjectAlwaysCollected) || game::ItemAlwaysCollected(type)) {
if (flags & kObjectAlwaysCollected) {
return kNone;
}
if (game::ItemIsGold(type)) {
@@ -125,7 +133,7 @@ void Load() {
for (int i = 0; i < kCategoryCount; ++i) {
const std::string text = re::ConfigString("loot", kCategories[i].key, kModes[int(Mode::Default)].key);
for (int m = 0; m < kModeCount; ++m) {
if (_stricmp(text.c_str(), kModes[m].key) == 0) {
if (_stricmp(text.c_str(), kModes[m].key) == 0 && ModeAllowed(i, static_cast<Mode>(m))) {
g_modes[i] = static_cast<Mode>(m);
}
}
@@ -207,6 +215,10 @@ const wchar_t* ModeLabel(Mode mode) {
return kModes[static_cast<int>(mode)].label;
}
bool ModeAllowed(int category, Mode mode) {
return category != kQuest || mode != Mode::Void;
}
Mode GetMode(int category) {
return g_modes[category];
}
@@ -225,7 +237,7 @@ namespace game {
using namespace mods::lootfilter;
// cCreature::CanCollectItem (0x4ec170, (item), ret 4): carried items never;
// quest items, runes and anything not filtered always; else by the mode.
// runes and anything not filtered always; else by the mode.
bool __fastcall Creature_CanCollectItem(void* self, void* edx, uint8_t* item);
RE_HOOK(0x004ec170, Creature_CanCollectItem);
bool __fastcall Creature_CanCollectItem(void*, void*, uint8_t* item) {
+4 -3
View File
@@ -13,13 +13,14 @@ enum class Mode : int {
};
constexpr int kModeCount = 4;
// Categories: Gold, Tränke, then the item colours white, blue, yellow, brown,
// green.
constexpr int kCategoryCount = 7;
// Categories: Questgegenstände (first: checked before the others, never
// Void), Gold, Tränke, then the item colours white, blue, yellow, brown, green.
constexpr int kCategoryCount = 8;
const wchar_t* CategoryLabel(int category);
uint32_t CategoryColour(int category); // as the game colours their names
const wchar_t* ModeLabel(Mode mode);
bool ModeAllowed(int category, Mode mode);
Mode GetMode(int category);
// Sets all modes (`modes[kCategoryCount]`) and writes them to sacredK.ini.
+5 -1
View File
@@ -523,7 +523,8 @@ game::UiRect LootBoxRect(void* window, int row, int mode) {
int LootBoxAt(void* window, int x, int y) {
for (int category = 0; category < mods::lootfilter::kCategoryCount; ++category) {
for (int mode = 0; mode < mods::lootfilter::kModeCount; ++mode) {
if (game::Inside(LootBoxRect(window, category + 1, mode), x, y)) {
if (mods::lootfilter::ModeAllowed(category, static_cast<mods::lootfilter::Mode>(mode)) &&
game::Inside(LootBoxRect(window, category + 1, mode), x, y)) {
return category * mods::lootfilter::kModeCount + mode;
}
}
@@ -548,6 +549,9 @@ void DrawLootPage(const Window& w, void* device) {
game::DrawText(device, kFont, game::CenterLineVertically(kFont, LootRowRect(window, row, 0, kLootLabelWidth)),
CategoryLabel(category), CategoryColour(category), 0);
for (int mode = 0; mode < kModeCount; ++mode) {
if (!ModeAllowed(category, static_cast<Mode>(mode))) {
continue;
}
const bool selected = g_lootPending[category] == static_cast<Mode>(mode);
const bool hover = g_lootHover == category * kModeCount + mode;
FillBox(device, LootBoxRect(window, row, mode),