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; }); }