Kelsidavis-WoWee/include/ui/spellbook_screen.hpp
Kelsi 39634f442b feat: add insufficient-power tint to action bar spell slots
Spell icons now render with a purple desaturated tint when the player
lacks enough mana/rage/energy/runic power to cast them. Power cost and
type are read from Spell.dbc via the spellbook's DBC cache. The spell
tooltip also shows "Not enough power" in purple when applicable.

Priority: cooldown > GCD > out-of-range > insufficient-power so states
don't conflict. Adds SpellbookScreen::getSpellPowerInfo() as a public
DBC accessor.
2026-03-12 06:01:42 -07:00

122 lines
4.8 KiB
C++

#pragma once
#include "game/game_handler.hpp"
#include <vulkan/vulkan.h>
#include <imgui.h>
#include <string>
#include <vector>
#include <map>
#include <unordered_map>
namespace wowee {
namespace pipeline { class AssetManager; }
namespace ui {
struct SpellInfo {
uint32_t spellId = 0;
std::string name;
std::string rank;
std::string description; // Tooltip/description text from Spell.dbc
uint32_t iconId = 0; // SpellIconID
uint32_t attributes = 0; // Spell attributes (field 4)
uint32_t castTimeMs = 0; // Cast time in ms (0 = instant)
uint32_t manaCost = 0; // Mana cost
uint32_t powerType = 0; // 0=mana, 1=rage, 2=focus, 3=energy
uint32_t rangeIndex = 0; // Range index from SpellRange.dbc
uint32_t schoolMask = 0; // School bitmask (1=phys,2=holy,4=fire,8=nature,16=frost,32=shadow,64=arcane)
bool isPassive() const { return (attributes & 0x40) != 0; }
};
struct SpellTabInfo {
std::string name;
std::vector<const SpellInfo*> spells;
};
class SpellbookScreen {
public:
void render(game::GameHandler& gameHandler, pipeline::AssetManager* assetManager);
bool isOpen() const { return open; }
void toggle() { open = !open; }
void setOpen(bool o) { open = o; }
// Spell name lookup — triggers DBC load if needed, used by action bar tooltips
std::string lookupSpellName(uint32_t spellId, pipeline::AssetManager* assetManager);
// Rich tooltip — renders a full spell tooltip (inside an already-open BeginTooltip block).
// Triggers DBC load if needed. Returns true if spell data was found.
bool renderSpellInfoTooltip(uint32_t spellId, game::GameHandler& gameHandler,
pipeline::AssetManager* assetManager);
// Drag-and-drop state for action bar assignment
bool isDraggingSpell() const { return draggingSpell_; }
uint32_t getDragSpellId() const { return dragSpellId_; }
void consumeDragSpell() { draggingSpell_ = false; dragSpellId_ = 0; dragSpellIconTex_ = VK_NULL_HANDLE; }
/// Returns the max range in yards for a spell (0 if self-cast, unknown, or melee).
/// Triggers DBC load if needed. Used by the action bar for out-of-range tinting.
uint32_t getSpellMaxRange(uint32_t spellId, pipeline::AssetManager* assetManager);
/// Returns the power cost and type for a spell (cost=0 if unknown/free).
/// powerType: 0=mana, 1=rage, 2=focus, 3=energy, 6=runic power.
/// Triggers DBC load if needed. Used by the action bar for insufficient-power tinting.
void getSpellPowerInfo(uint32_t spellId, pipeline::AssetManager* assetManager,
uint32_t& outCost, uint32_t& outPowerType);
/// Returns a WoW spell link string if the user shift-clicked a spell, then clears it.
std::string getAndClearPendingChatLink() {
std::string out = std::move(pendingChatSpellLink_);
pendingChatSpellLink_.clear();
return out;
}
private:
bool open = false;
bool pKeyWasDown = false;
// Spell data (loaded from Spell.dbc)
bool dbcLoaded = false;
bool dbcLoadAttempted = false;
std::unordered_map<uint32_t, SpellInfo> spellData;
// Icon data (loaded from SpellIcon.dbc)
bool iconDbLoaded = false;
std::unordered_map<uint32_t, std::string> spellIconPaths; // SpellIconID -> path
std::unordered_map<uint32_t, VkDescriptorSet> spellIconCache; // SpellIconID -> texture
// Skill line data (loaded from SkillLine.dbc + SkillLineAbility.dbc)
bool skillLineDbLoaded = false;
std::unordered_map<uint32_t, std::string> skillLineNames;
std::unordered_map<uint32_t, uint32_t> skillLineCategories;
std::unordered_multimap<uint32_t, uint32_t> spellToSkillLine;
// Categorized spell tabs
std::vector<SpellTabInfo> spellTabs;
size_t lastKnownSpellCount = 0;
bool categorizedWithSkillLines = false;
// Search filter
char searchFilter_[128] = "";
// Drag-and-drop from spellbook to action bar
bool draggingSpell_ = false;
uint32_t dragSpellId_ = 0;
VkDescriptorSet dragSpellIconTex_ = VK_NULL_HANDLE;
// Pending chat spell link from shift-click
std::string pendingChatSpellLink_;
void loadSpellDBC(pipeline::AssetManager* assetManager);
void loadSpellIconDBC(pipeline::AssetManager* assetManager);
void loadSkillLineDBCs(pipeline::AssetManager* assetManager);
void categorizeSpells(const std::unordered_set<uint32_t>& knownSpells);
VkDescriptorSet getSpellIcon(uint32_t iconId, pipeline::AssetManager* assetManager);
const SpellInfo* getSpellInfo(uint32_t spellId) const;
// Tooltip rendering helper (showUsageHints=false when called from action bar)
void renderSpellTooltip(const SpellInfo* info, game::GameHandler& gameHandler, bool showUsageHints = true);
};
} // namespace ui
} // namespace wowee