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:
1 parent
c0eedbce7f
commit
401e20b1f2
5 files changed
+61
-28
No files matched your search
+12
-4
@@ -13,12 +13,21 @@ Decided with the user: the filter replaces the game's PICKUPAUTO choice
|
|||||||
(all / valuable / gold only; its radio buttons are hidden, the animation
|
(all / valuable / gold only; its radio buttons are hidden, the animation
|
||||||
checkbox stays); Void deletes loot only (monster drops, chests), not items
|
checkbox stays); Void deletes loot only (monster drops, chests), not items
|
||||||
the player puts down. Settings: `[loot]` in `sacredK.ini`
|
the player puts down. Settings: `[loot]` in `sacredK.ini`
|
||||||
(`Gold=Default`, keys Gold, Potions, White, Blue, Yellow, Brown, Green;
|
(`Gold=Default`, keys Quest, Gold, Potions, White, Blue, Yellow, Brown, Green;
|
||||||
values Void, Ignore, Default, AlwaysShow). Code: `src/mods/lootfilter.cpp`,
|
values Void, Ignore, Default, AlwaysShow). Code: `src/mods/lootfilter.cpp`,
|
||||||
page in `src/mods/options.cpp`.
|
page in `src/mods/options.cpp`.
|
||||||
|
|
||||||
## What decides the category (read from the game)
|
## What decides the category (read from the game)
|
||||||
|
|
||||||
|
- Quest items (request 2026-10-25: "Quest Items are currently potentially
|
||||||
|
being destroyed, it needs to be top priority, also Löschen is not an option
|
||||||
|
for those"), checked first: the type's quest number (type table +0x1c) or
|
||||||
|
the scripts' QuestItem / MainQuestItem marks (+0x61 / +0x62; the script
|
||||||
|
parser 0x4584a9 turns those words into tokens 0x61 / 0x62), as the quest
|
||||||
|
book reads them (0x43ad80). The game's always-collect test `0x428550`
|
||||||
|
requires type flag 0x1000000 besides, so quest items without it fell into
|
||||||
|
their colour's category and could be voided. Their row has no *Löschen*
|
||||||
|
box; a `Void` in the ini is read as Default.
|
||||||
- Gold: `0x43ae20` (thiscall on the item type table, (type)): one of four
|
- Gold: `0x43ae20` (thiscall on the item type table, (type)): one of four
|
||||||
types listed at `0x947920`. The labels show its amount, the collect mode
|
types listed at `0x947920`. The labels show its amount, the collect mode
|
||||||
takes it without inventory space, the loot drop sets the amount after
|
takes it without inventory space, the loot drop sets the amount after
|
||||||
@@ -31,9 +40,8 @@ page in `src/mods/options.cpp`.
|
|||||||
`ffa0a0ff`, 12..14 yellow `fff0e040`, 15 brown `ffc19a33`; set green
|
`ffa0a0ff`, 12..14 yellow `fff0e040`, 15 brown `ffc19a33`; set green
|
||||||
`ff15ff25`. (Without UNIQUE_COLOR, items with special bits get other
|
`ff15ff25`. (Without UNIQUE_COLOR, items with special bits get other
|
||||||
colours from `0x5c1260`; the filter uses the tier regardless.)
|
colours from `0x5c1260`; the filter uses the tier regardless.)
|
||||||
- Not filtered: runes (class 0x1c), objects with flag 0x800 and the types
|
- Not filtered: runes (class 0x1c) and objects with flag 0x800 (the game's
|
||||||
`0x428550` reports (CanCollectItem always takes both; quest items,
|
CanCollectItem always takes those).
|
||||||
presumably).
|
|
||||||
|
|
||||||
## Where it acts
|
## Where it acts
|
||||||
|
|
||||||
|
|||||||
+15
-7
@@ -79,13 +79,21 @@ inline bool ItemIsSetItem(uint32_t type) {
|
|||||||
return group && group[0x23] != 0;
|
return group && group[0x23] != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 0x428550 (thiscall on the type table, (type), ret 4): cCreature::
|
// Quest items: the type's quest number (+0x1c) or the scripts' QuestItem /
|
||||||
// CanCollectItem takes items of such types whatever the pickup setting (type
|
// MainQuestItem marks (+0x61 / +0x62; tokens of those script words, 0x4584a9),
|
||||||
// flag 0x1000000 with +0x61 / +0x62 / +0x1c set; quest items, presumably).
|
// as the quest book reads them (0x43ad80). The game's CanCollectItem
|
||||||
inline bool ItemAlwaysCollected(uint32_t type) {
|
// (0x428550) takes them always, but only with type flag 0x1000000 set too.
|
||||||
using Fn = bool(__fastcall*)(void* table, void* edx, uint32_t type);
|
constexpr size_t kItemTypeQuest = 0x1c;
|
||||||
void* table = *reinterpret_cast<void**>(kItemTypeTable);
|
constexpr size_t kItemTypeQuestItem = 0x61;
|
||||||
return table && reinterpret_cast<Fn>(0x00428550)(table, nullptr, type);
|
constexpr size_t kItemTypeMainQuestItem = 0x62;
|
||||||
|
inline bool ItemIsQuestItem(uint32_t type) {
|
||||||
|
const auto* table = *reinterpret_cast<const uint8_t* const*>(kItemTypeTable);
|
||||||
|
if (!table || type == 0 || type >= kItemTypeCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const uint8_t* entry = table + type * kItemTypeEntrySize;
|
||||||
|
return *reinterpret_cast<const uint32_t*>(entry + kItemTypeQuest) != 0 || entry[kItemTypeQuestItem] ||
|
||||||
|
entry[kItemTypeMainQuestItem];
|
||||||
}
|
}
|
||||||
|
|
||||||
// The item's quality block (+0x118, cArmor3D::vf24 case 2 hands it to the
|
// The item's quality block (+0x118, cArmor3D::vf24 case 2 hands it to the
|
||||||
|
|||||||
+25
-13
@@ -1,6 +1,6 @@
|
|||||||
// Loot filter (request of 2026-10-19), set on the options window's Beute page
|
// 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,
|
// (mods/options.cpp): per category - Questgegenstände, Gold, Tränke, and the
|
||||||
// blue, yellow, brown, green - one of
|
// item colours white, blue, yellow, brown, green - one of
|
||||||
// Void a drop is deleted at once (monster loot, chests; items the
|
// Void a drop is deleted at once (monster loot, chests; items the
|
||||||
// player puts down stay),
|
// player puts down stay),
|
||||||
// Ignore the pickup key (Q, the collect mode) leaves it,
|
// 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),
|
// It replaces the game's PICKUPAUTO setting (all / valuable / gold only),
|
||||||
// whose radio buttons are gone from the page.
|
// whose radio buttons are gone from the page.
|
||||||
//
|
//
|
||||||
// - Categories from the game's data: gold by type (0x43ae20), potions by item
|
// - Categories from the game's data: quest items first (request of
|
||||||
// class 9, set items green (0x433f00 +0x23), otherwise the quality tier
|
// 2026-10-25: they were voided as their colour's category; "it needs to be
|
||||||
// that picks the item colour (0x5c1330, game/engine/items.h). Runes and the
|
// top priority, also Löschen is not an option for those"; type's quest
|
||||||
// types CanCollectItem always takes (quest items) are not filtered.
|
// 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
|
// - Pickup: cCreature::CanCollectItem (0x4ec170) is the collect mode's filter
|
||||||
// (both of its scans; a clicked item does not ask it).
|
// (both of its scans; a clicked item does not ask it).
|
||||||
// - Void: items made by the loot drop (cCreature 0x559a30, via
|
// - Void: items made by the loot drop (cCreature 0x559a30, via
|
||||||
@@ -44,7 +48,7 @@
|
|||||||
namespace mods::lootfilter {
|
namespace mods::lootfilter {
|
||||||
namespace {
|
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 {
|
struct CategoryInfo {
|
||||||
const char* key; // in [loot]
|
const char* key; // in [loot]
|
||||||
@@ -53,6 +57,7 @@ struct CategoryInfo {
|
|||||||
};
|
};
|
||||||
// Gold and potions as the game's labels show them: white.
|
// Gold and potions as the game's labels show them: white.
|
||||||
constexpr CategoryInfo kCategories[kCategoryCount] = {
|
constexpr CategoryInfo kCategories[kCategoryCount] = {
|
||||||
|
{"Quest", L"Questgegenstände", game::kItemColourWhite},
|
||||||
{"Gold", L"Gold", game::kItemColourWhite}, {"Potions", L"Tränke", game::kItemColourWhite},
|
{"Gold", L"Gold", game::kItemColourWhite}, {"Potions", L"Tränke", game::kItemColourWhite},
|
||||||
{"White", L"Weiß", game::kItemColourWhite}, {"Blue", L"Blau", game::kItemColourBlue},
|
{"White", L"Weiß", game::kItemColourWhite}, {"Blue", L"Blau", game::kItemColourBlue},
|
||||||
{"Yellow", L"Gelb", game::kItemColourYellow}, {"Brown", L"Braun", game::kItemColourBrown},
|
{"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"}};
|
{"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 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
|
// cObject +0x14 bit 0x800: the game's CanCollectItem takes such objects
|
||||||
// pickup setting (as the types of ItemAlwaysCollected).
|
// whatever the pickup setting.
|
||||||
constexpr uint32_t kObjectAlwaysCollected = 0x800;
|
constexpr uint32_t kObjectAlwaysCollected = 0x800;
|
||||||
|
|
||||||
int CategoryOf(void* object) {
|
int CategoryOf(void* object) {
|
||||||
@@ -82,8 +87,11 @@ int CategoryOf(void* object) {
|
|||||||
if (type == 0 || type >= game::kItemTypeCount) {
|
if (type == 0 || type >= game::kItemTypeCount) {
|
||||||
return kNone;
|
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);
|
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;
|
return kNone;
|
||||||
}
|
}
|
||||||
if (game::ItemIsGold(type)) {
|
if (game::ItemIsGold(type)) {
|
||||||
@@ -125,7 +133,7 @@ void Load() {
|
|||||||
for (int i = 0; i < kCategoryCount; ++i) {
|
for (int i = 0; i < kCategoryCount; ++i) {
|
||||||
const std::string text = re::ConfigString("loot", kCategories[i].key, kModes[int(Mode::Default)].key);
|
const std::string text = re::ConfigString("loot", kCategories[i].key, kModes[int(Mode::Default)].key);
|
||||||
for (int m = 0; m < kModeCount; ++m) {
|
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);
|
g_modes[i] = static_cast<Mode>(m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,6 +215,10 @@ const wchar_t* ModeLabel(Mode mode) {
|
|||||||
return kModes[static_cast<int>(mode)].label;
|
return kModes[static_cast<int>(mode)].label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ModeAllowed(int category, Mode mode) {
|
||||||
|
return category != kQuest || mode != Mode::Void;
|
||||||
|
}
|
||||||
|
|
||||||
Mode GetMode(int category) {
|
Mode GetMode(int category) {
|
||||||
return g_modes[category];
|
return g_modes[category];
|
||||||
}
|
}
|
||||||
@@ -225,7 +237,7 @@ namespace game {
|
|||||||
using namespace mods::lootfilter;
|
using namespace mods::lootfilter;
|
||||||
|
|
||||||
// cCreature::CanCollectItem (0x4ec170, (item), ret 4): carried items never;
|
// 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);
|
bool __fastcall Creature_CanCollectItem(void* self, void* edx, uint8_t* item);
|
||||||
RE_HOOK(0x004ec170, Creature_CanCollectItem);
|
RE_HOOK(0x004ec170, Creature_CanCollectItem);
|
||||||
bool __fastcall Creature_CanCollectItem(void*, void*, uint8_t* item) {
|
bool __fastcall Creature_CanCollectItem(void*, void*, uint8_t* item) {
|
||||||
|
|||||||
@@ -13,13 +13,14 @@ enum class Mode : int {
|
|||||||
};
|
};
|
||||||
constexpr int kModeCount = 4;
|
constexpr int kModeCount = 4;
|
||||||
|
|
||||||
// Categories: Gold, Tränke, then the item colours white, blue, yellow, brown,
|
// Categories: Questgegenstände (first: checked before the others, never
|
||||||
// green.
|
// Void), Gold, Tränke, then the item colours white, blue, yellow, brown, green.
|
||||||
constexpr int kCategoryCount = 7;
|
constexpr int kCategoryCount = 8;
|
||||||
|
|
||||||
const wchar_t* CategoryLabel(int category);
|
const wchar_t* CategoryLabel(int category);
|
||||||
uint32_t CategoryColour(int category); // as the game colours their names
|
uint32_t CategoryColour(int category); // as the game colours their names
|
||||||
const wchar_t* ModeLabel(Mode mode);
|
const wchar_t* ModeLabel(Mode mode);
|
||||||
|
bool ModeAllowed(int category, Mode mode);
|
||||||
|
|
||||||
Mode GetMode(int category);
|
Mode GetMode(int category);
|
||||||
// Sets all modes (`modes[kCategoryCount]`) and writes them to sacredK.ini.
|
// Sets all modes (`modes[kCategoryCount]`) and writes them to sacredK.ini.
|
||||||
|
|||||||
@@ -523,7 +523,8 @@ game::UiRect LootBoxRect(void* window, int row, int mode) {
|
|||||||
int LootBoxAt(void* window, int x, int y) {
|
int LootBoxAt(void* window, int x, int y) {
|
||||||
for (int category = 0; category < mods::lootfilter::kCategoryCount; ++category) {
|
for (int category = 0; category < mods::lootfilter::kCategoryCount; ++category) {
|
||||||
for (int mode = 0; mode < mods::lootfilter::kModeCount; ++mode) {
|
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;
|
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)),
|
game::DrawText(device, kFont, game::CenterLineVertically(kFont, LootRowRect(window, row, 0, kLootLabelWidth)),
|
||||||
CategoryLabel(category), CategoryColour(category), 0);
|
CategoryLabel(category), CategoryColour(category), 0);
|
||||||
for (int mode = 0; mode < kModeCount; ++mode) {
|
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 selected = g_lootPending[category] == static_cast<Mode>(mode);
|
||||||
const bool hover = g_lootHover == category * kModeCount + mode;
|
const bool hover = g_lootHover == category * kModeCount + mode;
|
||||||
FillBox(device, LootBoxRect(window, row, mode),
|
FillBox(device, LootBoxRect(window, row, mode),
|
||||||
|
|||||||
Reference in new issue
Block a user