// Potion counts on the potion slots of the bottom bar. // // Request (2026-10-17): the potion selector always shows how many potions of // each kind the hero carries (only when there are any). // // The bar shows 5 potion kinds: cUI_Taskbar2::RenderPotionSlots (0x6e39e0) // draws control k-1 (embedded at +0x258, stride 0xbc) when // cInventoryEntry::GetPotionRef(k) finds a potion of kind k, else the empty // control behind it (+0x3ac). GetPotionRef walks the inventory grid and asks // ItemPotionKind for each item's kind; the count here does the same and adds // up the potion stacks (itemstack.cpp). // // Their tooltips (game::PotionTooltipText, set on the slots by the taskbar, // 0x6e6060; shown as "\dFFFFFF80Heiltrank \wFFFFFFFF[SPC]") name the key // bound to the kind (mods/input.cpp) instead of the game's own, which is // switched off; no key part while unbound. #include #include #include #include "game/engine/items.h" #include "game/engine/objects.h" #include "game/ui/text.h" #include "game/ui/texts.h" #include "mods/input.h" #include "mods/itemstack.h" #include "mods/skillslots.h" #include "re/hook.h" namespace mods::potioncounts { namespace { // cUI_Taskbar2 fields. constexpr size_t kInventoryIndex = 0x158; // u16: the hero's inventory constexpr size_t kPotionSlots = 0x258; // game::kPotionKinds embedded controls constexpr size_t kPotionSlotSize = 0xbc; constexpr uint16_t kCountFont = 1; // as the art cooldown labels (skillbar.cpp) constexpr uint32_t kCountColor = 0xffffffff; constexpr int kCountInset = 3; // from the slot's bottom-right corner void RenderCounts(uint8_t* taskbar, void* renderer) { const uint8_t* inventory = skillslots::Inventory(*reinterpret_cast(taskbar + kInventoryIndex)); if (!inventory || !game::GetPlayerHero()) { return; } unsigned counts[game::kPotionKinds + 1] = {}; game::ForEachGridItem(inventory, [&](uint32_t id) { const int kind = game::PotionKindOf(id); if (kind >= 1 && kind <= game::kPotionKinds) { counts[kind] += itemstack::StackCount(id); } }); const int lineHeight = game::FontLineHeight(kCountFont); for (int kind = 1; kind <= game::kPotionKinds; ++kind) { if (counts[kind] == 0) { continue; } game::UiRect rect = game::ControlScreenRect(taskbar + kPotionSlots + (kind - 1) * kPotionSlotSize); rect.y += rect.h - lineHeight - kCountInset; rect.w = static_cast(rect.w - kCountInset); wchar_t text[8]; swprintf_s(text, L"%u", counts[kind]); game::DrawText(renderer, kCountFont, rect, text, kCountColor, game::kTextRight); } } // The game's tooltip with its "[key]" end replaced by the bound key's. std::wstring TooltipWithKey(uint32_t text, int vk) { std::wstring tooltip = game::ShownText(text); if (const size_t key = tooltip.rfind(L'['); key != std::wstring::npos && tooltip.back() == L']') { tooltip.erase(key); } if (vk) { tooltip += L"[" + input::KeyLabel(vk) + L"]"; } return tooltip; } void UpdateTooltips() { for (int kind = 1; kind <= game::kPotionKinds; ++kind) { if (const uint32_t text = game::PotionTooltipText(kind)) { game::SetTextOverride(text, TooltipWithKey(text, input::PotionKey(kind))); } } } } // namespace } // namespace mods::potioncounts // cUI_Taskbar2::RenderPotionSlots (0x6e39e0, ret 4): then the counts. void __fastcall UiTaskbar_RenderPotionSlots(void* self, void* edx, void* renderer); RE_HOOK(0x006e39e0, UiTaskbar_RenderPotionSlots); void __fastcall UiTaskbar_RenderPotionSlots(void* self, void* edx, void* renderer) { UiTaskbar_RenderPotionSlots_original(self, edx, renderer); if (self) { mods::potioncounts::RenderCounts(static_cast(self), renderer); mods::potioncounts::UpdateTooltips(); } }