From 91794f421e00086df9ca212754dc2f6dd0111c81 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 21 Mar 2026 04:20:58 -0700 Subject: [PATCH] feat: add spell power cost to SpellDataResolver; fix IsUsableSpell mana check Extend SpellDataInfo with manaCost and powerType fields, extracted from Spell.dbc ManaCost and PowerType columns. This enables IsUsableSpell() to properly check if the player has enough mana/rage/energy to cast. Previously IsUsableSpell always returned notEnoughMana=false since cost data wasn't available. Now it compares the spell's DBC mana cost against the player's current power, returning accurate usability and mana state. This fixes action bar addons showing abilities as usable when the player lacks sufficient power, and enables OmniCC-style cooldown text to properly dim insufficient-power abilities. --- include/game/game_handler.hpp | 2 +- src/addons/lua_engine.cpp | 18 ++++++++++++++++-- src/core/application.cpp | 19 ++++++++++++++++++- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index f98c47aa..158c4274 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -295,7 +295,7 @@ public: } // Spell data resolver: spellId -> {castTimeMs, minRange, maxRange} - struct SpellDataInfo { uint32_t castTimeMs = 0; float minRange = 0; float maxRange = 0; }; + struct SpellDataInfo { uint32_t castTimeMs = 0; float minRange = 0; float maxRange = 0; uint32_t manaCost = 0; uint8_t powerType = 0; }; using SpellDataResolver = std::function; void setSpellDataResolver(SpellDataResolver r) { spellDataResolver_ = std::move(r); } SpellDataInfo getSpellData(uint32_t spellId) const { diff --git a/src/addons/lua_engine.cpp b/src/addons/lua_engine.cpp index e43f5dd9..42dd640a 100644 --- a/src/addons/lua_engine.cpp +++ b/src/addons/lua_engine.cpp @@ -2189,8 +2189,22 @@ static int lua_IsUsableSpell(lua_State* L) { float cd = gh->getSpellCooldown(spellId); bool onCooldown = (cd > 0.1f); - lua_pushboolean(L, onCooldown ? 0 : 1); // usable (not on cooldown) - lua_pushboolean(L, 0); // noMana (can't determine without spell cost data) + // Check mana/power cost + bool noMana = false; + if (!onCooldown) { + auto spellData = gh->getSpellData(spellId); + if (spellData.manaCost > 0) { + auto playerEntity = gh->getEntityManager().getEntity(gh->getPlayerGuid()); + if (playerEntity) { + auto* unit = dynamic_cast(playerEntity.get()); + if (unit && unit->getPower() < spellData.manaCost) { + noMana = true; + } + } + } + } + lua_pushboolean(L, (onCooldown || noMana) ? 0 : 1); // usable + lua_pushboolean(L, noMana ? 1 : 0); // notEnoughMana return 2; } diff --git a/src/core/application.cpp b/src/core/application.cpp index c8ee0903..2a8579ad 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -445,9 +445,11 @@ bool Application::initialize() { auto rangeMap = std::make_shared>>(); auto spellCastIdx = std::make_shared>(); // spellId→castTimeIdx auto spellRangeIdx = std::make_shared>(); // spellId→rangeIdx + struct SpellCostEntry { uint32_t manaCost = 0; uint8_t powerType = 0; }; + auto spellCostMap = std::make_shared>(); auto loaded = std::make_shared(false); auto* am = assetManager.get(); - gameHandler->setSpellDataResolver([castTimeMap, rangeMap, spellCastIdx, spellRangeIdx, loaded, am](uint32_t spellId) -> game::GameHandler::SpellDataInfo { + gameHandler->setSpellDataResolver([castTimeMap, rangeMap, spellCastIdx, spellRangeIdx, spellCostMap, loaded, am](uint32_t spellId) -> game::GameHandler::SpellDataInfo { if (!am) return {}; if (!*loaded) { *loaded = true; @@ -480,6 +482,12 @@ bool Application::initialize() { uint32_t idF = spL ? (*spL)["ID"] : 0; uint32_t ctF = spL ? (*spL)["CastingTimeIndex"] : 134; // WotLK default uint32_t rF = spL ? (*spL)["RangeIndex"] : 132; + uint32_t ptF = UINT32_MAX, mcF = UINT32_MAX; + if (spL) { + try { ptF = (*spL)["PowerType"]; } catch (...) {} + try { mcF = (*spL)["ManaCost"]; } catch (...) {} + } + uint32_t fc = sDbc->getFieldCount(); for (uint32_t i = 0; i < sDbc->getRecordCount(); ++i) { uint32_t id = sDbc->getUInt32(i, idF); if (id == 0) continue; @@ -487,6 +495,10 @@ bool Application::initialize() { uint32_t ri = sDbc->getUInt32(i, rF); if (ct > 0) (*spellCastIdx)[id] = ct; if (ri > 0) (*spellRangeIdx)[id] = ri; + // Extract power cost + uint32_t mc = (mcF < fc) ? sDbc->getUInt32(i, mcF) : 0; + uint8_t pt = (ptF < fc) ? static_cast(sDbc->getUInt32(i, ptF)) : 0; + if (mc > 0) (*spellCostMap)[id] = {mc, pt}; } } LOG_INFO("SpellDataResolver: loaded ", spellCastIdx->size(), " cast indices, ", @@ -506,6 +518,11 @@ bool Application::initialize() { info.maxRange = rIt->second.second; } } + auto mcIt = spellCostMap->find(spellId); + if (mcIt != spellCostMap->end()) { + info.manaCost = mcIt->second.manaCost; + info.powerType = mcIt->second.powerType; + } return info; }); }