mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 17:43:52 +00:00
feat: add GetSpellInfo, GetSpellTexture, GetItemInfo and more Lua API functions
Add spell icon path resolution via SpellIcon.dbc + Spell.dbc lazy loading, wired through GameHandler callback. Fix UnitBuff/UnitDebuff to return icon texture paths instead of nil. Add GetLocale, GetBuildInfo, GetCurrentMapAreaID.
This commit is contained in:
parent
3ff43a530f
commit
22b0cc8a3c
3 changed files with 202 additions and 1 deletions
|
|
@ -287,6 +287,13 @@ public:
|
||||||
using AddonEventCallback = std::function<void(const std::string&, const std::vector<std::string>&)>;
|
using AddonEventCallback = std::function<void(const std::string&, const std::vector<std::string>&)>;
|
||||||
void setAddonEventCallback(AddonEventCallback cb) { addonEventCallback_ = std::move(cb); }
|
void setAddonEventCallback(AddonEventCallback cb) { addonEventCallback_ = std::move(cb); }
|
||||||
|
|
||||||
|
// Spell icon path resolver: spellId -> texture path string (e.g., "Interface\\Icons\\Spell_Fire_Fireball01")
|
||||||
|
using SpellIconPathResolver = std::function<std::string(uint32_t)>;
|
||||||
|
void setSpellIconPathResolver(SpellIconPathResolver r) { spellIconPathResolver_ = std::move(r); }
|
||||||
|
std::string getSpellIconPath(uint32_t spellId) const {
|
||||||
|
return spellIconPathResolver_ ? spellIconPathResolver_(spellId) : std::string{};
|
||||||
|
}
|
||||||
|
|
||||||
// Emote animation callback: (entityGuid, animationId)
|
// Emote animation callback: (entityGuid, animationId)
|
||||||
using EmoteAnimCallback = std::function<void(uint64_t, uint32_t)>;
|
using EmoteAnimCallback = std::function<void(uint64_t, uint32_t)>;
|
||||||
void setEmoteAnimCallback(EmoteAnimCallback cb) { emoteAnimCallback_ = std::move(cb); }
|
void setEmoteAnimCallback(EmoteAnimCallback cb) { emoteAnimCallback_ = std::move(cb); }
|
||||||
|
|
@ -2644,6 +2651,7 @@ private:
|
||||||
ChatBubbleCallback chatBubbleCallback_;
|
ChatBubbleCallback chatBubbleCallback_;
|
||||||
AddonChatCallback addonChatCallback_;
|
AddonChatCallback addonChatCallback_;
|
||||||
AddonEventCallback addonEventCallback_;
|
AddonEventCallback addonEventCallback_;
|
||||||
|
SpellIconPathResolver spellIconPathResolver_;
|
||||||
EmoteAnimCallback emoteAnimCallback_;
|
EmoteAnimCallback emoteAnimCallback_;
|
||||||
|
|
||||||
// Targeting
|
// Targeting
|
||||||
|
|
|
||||||
|
|
@ -360,7 +360,9 @@ static int lua_UnitAura(lua_State* L, bool wantBuff) {
|
||||||
std::string name = gh->getSpellName(aura.spellId);
|
std::string name = gh->getSpellName(aura.spellId);
|
||||||
lua_pushstring(L, name.empty() ? "Unknown" : name.c_str()); // name
|
lua_pushstring(L, name.empty() ? "Unknown" : name.c_str()); // name
|
||||||
lua_pushstring(L, ""); // rank
|
lua_pushstring(L, ""); // rank
|
||||||
lua_pushnil(L); // icon (texture path — not implemented)
|
std::string iconPath = gh->getSpellIconPath(aura.spellId);
|
||||||
|
if (!iconPath.empty()) lua_pushstring(L, iconPath.c_str());
|
||||||
|
else lua_pushnil(L); // icon texture path
|
||||||
lua_pushnumber(L, aura.charges); // count
|
lua_pushnumber(L, aura.charges); // count
|
||||||
lua_pushnil(L); // debuffType
|
lua_pushnil(L); // debuffType
|
||||||
lua_pushnumber(L, aura.maxDurationMs > 0 ? aura.maxDurationMs / 1000.0 : 0); // duration
|
lua_pushnumber(L, aura.maxDurationMs > 0 ? aura.maxDurationMs / 1000.0 : 0); // duration
|
||||||
|
|
@ -477,6 +479,144 @@ static int lua_HasTarget(lua_State* L) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- GetSpellInfo / GetSpellTexture ---
|
||||||
|
// GetSpellInfo(spellIdOrName) -> name, rank, icon, castTime, minRange, maxRange, spellId
|
||||||
|
static int lua_GetSpellInfo(lua_State* L) {
|
||||||
|
auto* gh = getGameHandler(L);
|
||||||
|
if (!gh) { lua_pushnil(L); return 1; }
|
||||||
|
|
||||||
|
uint32_t spellId = 0;
|
||||||
|
if (lua_isnumber(L, 1)) {
|
||||||
|
spellId = static_cast<uint32_t>(lua_tonumber(L, 1));
|
||||||
|
} else if (lua_isstring(L, 1)) {
|
||||||
|
const char* name = lua_tostring(L, 1);
|
||||||
|
if (!name || !*name) { lua_pushnil(L); return 1; }
|
||||||
|
std::string nameLow(name);
|
||||||
|
for (char& c : nameLow) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||||
|
int bestRank = -1;
|
||||||
|
for (uint32_t sid : gh->getKnownSpells()) {
|
||||||
|
std::string sn = gh->getSpellName(sid);
|
||||||
|
for (char& c : sn) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||||
|
if (sn != nameLow) continue;
|
||||||
|
int rank = 0;
|
||||||
|
const std::string& rk = gh->getSpellRank(sid);
|
||||||
|
if (!rk.empty()) {
|
||||||
|
std::string rkl = rk;
|
||||||
|
for (char& c : rkl) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||||
|
if (rkl.rfind("rank ", 0) == 0) {
|
||||||
|
try { rank = std::stoi(rkl.substr(5)); } catch (...) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rank > bestRank) { bestRank = rank; spellId = sid; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spellId == 0) { lua_pushnil(L); return 1; }
|
||||||
|
std::string name = gh->getSpellName(spellId);
|
||||||
|
if (name.empty()) { lua_pushnil(L); return 1; }
|
||||||
|
|
||||||
|
lua_pushstring(L, name.c_str()); // 1: name
|
||||||
|
const std::string& rank = gh->getSpellRank(spellId);
|
||||||
|
lua_pushstring(L, rank.c_str()); // 2: rank
|
||||||
|
std::string iconPath = gh->getSpellIconPath(spellId);
|
||||||
|
if (!iconPath.empty()) lua_pushstring(L, iconPath.c_str());
|
||||||
|
else lua_pushnil(L); // 3: icon texture path
|
||||||
|
lua_pushnumber(L, 0); // 4: castTime (ms) — not tracked
|
||||||
|
lua_pushnumber(L, 0); // 5: minRange
|
||||||
|
lua_pushnumber(L, 0); // 6: maxRange
|
||||||
|
lua_pushnumber(L, spellId); // 7: spellId
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSpellTexture(spellIdOrName) -> icon texture path string
|
||||||
|
static int lua_GetSpellTexture(lua_State* L) {
|
||||||
|
auto* gh = getGameHandler(L);
|
||||||
|
if (!gh) { lua_pushnil(L); return 1; }
|
||||||
|
|
||||||
|
uint32_t spellId = 0;
|
||||||
|
if (lua_isnumber(L, 1)) {
|
||||||
|
spellId = static_cast<uint32_t>(lua_tonumber(L, 1));
|
||||||
|
} else if (lua_isstring(L, 1)) {
|
||||||
|
const char* name = lua_tostring(L, 1);
|
||||||
|
if (!name || !*name) { lua_pushnil(L); return 1; }
|
||||||
|
std::string nameLow(name);
|
||||||
|
for (char& c : nameLow) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||||
|
for (uint32_t sid : gh->getKnownSpells()) {
|
||||||
|
std::string sn = gh->getSpellName(sid);
|
||||||
|
for (char& c : sn) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||||
|
if (sn == nameLow) { spellId = sid; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (spellId == 0) { lua_pushnil(L); return 1; }
|
||||||
|
std::string iconPath = gh->getSpellIconPath(spellId);
|
||||||
|
if (!iconPath.empty()) lua_pushstring(L, iconPath.c_str());
|
||||||
|
else lua_pushnil(L);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetItemInfo(itemId) -> name, link, quality, iLevel, reqLevel, class, subclass, maxStack, equipSlot, texture, vendorPrice
|
||||||
|
static int lua_GetItemInfo(lua_State* L) {
|
||||||
|
auto* gh = getGameHandler(L);
|
||||||
|
if (!gh) { lua_pushnil(L); return 1; }
|
||||||
|
|
||||||
|
uint32_t itemId = 0;
|
||||||
|
if (lua_isnumber(L, 1)) {
|
||||||
|
itemId = static_cast<uint32_t>(lua_tonumber(L, 1));
|
||||||
|
} else if (lua_isstring(L, 1)) {
|
||||||
|
// Try to parse "item:12345" link format
|
||||||
|
const char* s = lua_tostring(L, 1);
|
||||||
|
std::string str(s ? s : "");
|
||||||
|
auto pos = str.find("item:");
|
||||||
|
if (pos != std::string::npos) {
|
||||||
|
try { itemId = static_cast<uint32_t>(std::stoul(str.substr(pos + 5))); } catch (...) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (itemId == 0) { lua_pushnil(L); return 1; }
|
||||||
|
|
||||||
|
const auto* info = gh->getItemInfo(itemId);
|
||||||
|
if (!info) { lua_pushnil(L); return 1; }
|
||||||
|
|
||||||
|
lua_pushstring(L, info->name.c_str()); // 1: name
|
||||||
|
// Build item link string: |cFFFFFFFF|Hitem:ID:0:0:0:0:0:0:0|h[Name]|h|r
|
||||||
|
char link[256];
|
||||||
|
snprintf(link, sizeof(link), "|cFFFFFFFF|Hitem:%u:0:0:0:0:0:0:0|h[%s]|h|r",
|
||||||
|
itemId, info->name.c_str());
|
||||||
|
lua_pushstring(L, link); // 2: link
|
||||||
|
lua_pushnumber(L, info->quality); // 3: quality
|
||||||
|
lua_pushnumber(L, info->itemLevel); // 4: iLevel
|
||||||
|
lua_pushnumber(L, info->requiredLevel); // 5: requiredLevel
|
||||||
|
lua_pushstring(L, ""); // 6: class (type string)
|
||||||
|
lua_pushstring(L, ""); // 7: subclass
|
||||||
|
lua_pushnumber(L, info->maxStack > 0 ? info->maxStack : 1); // 8: maxStack
|
||||||
|
lua_pushstring(L, ""); // 9: equipSlot
|
||||||
|
lua_pushnil(L); // 10: texture (icon path — no ItemDisplayInfo icon resolver yet)
|
||||||
|
lua_pushnumber(L, info->sellPrice); // 11: vendorPrice
|
||||||
|
return 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Locale/Build/Realm info ---
|
||||||
|
|
||||||
|
static int lua_GetLocale(lua_State* L) {
|
||||||
|
lua_pushstring(L, "enUS");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lua_GetBuildInfo(lua_State* L) {
|
||||||
|
// Return WotLK defaults; expansion-specific version detection would need
|
||||||
|
// access to the expansion registry which isn't available here.
|
||||||
|
lua_pushstring(L, "3.3.5a"); // 1: version
|
||||||
|
lua_pushnumber(L, 12340); // 2: buildNumber
|
||||||
|
lua_pushstring(L, "Jan 1 2025");// 3: date
|
||||||
|
lua_pushnumber(L, 30300); // 4: tocVersion
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lua_GetCurrentMapAreaID(lua_State* L) {
|
||||||
|
auto* gh = getGameHandler(L);
|
||||||
|
lua_pushnumber(L, gh ? gh->getCurrentMapId() : 0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
// --- Frame System ---
|
// --- Frame System ---
|
||||||
// Minimal WoW-compatible frame objects with RegisterEvent/SetScript/GetScript.
|
// Minimal WoW-compatible frame objects with RegisterEvent/SetScript/GetScript.
|
||||||
// Frames are Lua tables with a metatable that provides methods.
|
// Frames are Lua tables with a metatable that provides methods.
|
||||||
|
|
@ -812,6 +952,12 @@ void LuaEngine::registerCoreAPI() {
|
||||||
{"UnitDebuff", lua_UnitDebuff},
|
{"UnitDebuff", lua_UnitDebuff},
|
||||||
{"GetNumAddOns", lua_GetNumAddOns},
|
{"GetNumAddOns", lua_GetNumAddOns},
|
||||||
{"GetAddOnInfo", lua_GetAddOnInfo},
|
{"GetAddOnInfo", lua_GetAddOnInfo},
|
||||||
|
{"GetSpellInfo", lua_GetSpellInfo},
|
||||||
|
{"GetSpellTexture", lua_GetSpellTexture},
|
||||||
|
{"GetItemInfo", lua_GetItemInfo},
|
||||||
|
{"GetLocale", lua_GetLocale},
|
||||||
|
{"GetBuildInfo", lua_GetBuildInfo},
|
||||||
|
{"GetCurrentMapAreaID", lua_GetCurrentMapAreaID},
|
||||||
// Utilities
|
// Utilities
|
||||||
{"strsplit", lua_strsplit},
|
{"strsplit", lua_strsplit},
|
||||||
{"strtrim", lua_strtrim},
|
{"strtrim", lua_strtrim},
|
||||||
|
|
|
||||||
|
|
@ -366,6 +366,53 @@ bool Application::initialize() {
|
||||||
addonManager_->fireEvent(event, args);
|
addonManager_->fireEvent(event, args);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// Wire spell icon path resolver for Lua API (GetSpellInfo, UnitBuff icon, etc.)
|
||||||
|
{
|
||||||
|
auto spellIconPaths = std::make_shared<std::unordered_map<uint32_t, std::string>>();
|
||||||
|
auto spellIconIds = std::make_shared<std::unordered_map<uint32_t, uint32_t>>();
|
||||||
|
auto loaded = std::make_shared<bool>(false);
|
||||||
|
auto* am = assetManager.get();
|
||||||
|
gameHandler->setSpellIconPathResolver([spellIconPaths, spellIconIds, loaded, am](uint32_t spellId) -> std::string {
|
||||||
|
if (!am) return {};
|
||||||
|
// Lazy-load SpellIcon.dbc + Spell.dbc icon IDs on first call
|
||||||
|
if (!*loaded) {
|
||||||
|
*loaded = true;
|
||||||
|
auto iconDbc = am->loadDBC("SpellIcon.dbc");
|
||||||
|
const auto* iconL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("SpellIcon") : nullptr;
|
||||||
|
if (iconDbc && iconDbc->isLoaded()) {
|
||||||
|
for (uint32_t i = 0; i < iconDbc->getRecordCount(); i++) {
|
||||||
|
uint32_t id = iconDbc->getUInt32(i, iconL ? (*iconL)["ID"] : 0);
|
||||||
|
std::string path = iconDbc->getString(i, iconL ? (*iconL)["Path"] : 1);
|
||||||
|
if (!path.empty() && id > 0) (*spellIconPaths)[id] = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto spellDbc = am->loadDBC("Spell.dbc");
|
||||||
|
const auto* spellL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("Spell") : nullptr;
|
||||||
|
if (spellDbc && spellDbc->isLoaded()) {
|
||||||
|
uint32_t fieldCount = spellDbc->getFieldCount();
|
||||||
|
uint32_t iconField = 133; // WotLK default
|
||||||
|
uint32_t idField = 0;
|
||||||
|
if (spellL) {
|
||||||
|
uint32_t layoutIcon = (*spellL)["IconID"];
|
||||||
|
if (layoutIcon < fieldCount && fieldCount <= layoutIcon + 20) {
|
||||||
|
iconField = layoutIcon;
|
||||||
|
idField = (*spellL)["ID"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (uint32_t i = 0; i < spellDbc->getRecordCount(); i++) {
|
||||||
|
uint32_t id = spellDbc->getUInt32(i, idField);
|
||||||
|
uint32_t iconId = spellDbc->getUInt32(i, iconField);
|
||||||
|
if (id > 0 && iconId > 0) (*spellIconIds)[id] = iconId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto iit = spellIconIds->find(spellId);
|
||||||
|
if (iit == spellIconIds->end()) return {};
|
||||||
|
auto pit = spellIconPaths->find(iit->second);
|
||||||
|
if (pit == spellIconPaths->end()) return {};
|
||||||
|
return pit->second;
|
||||||
|
});
|
||||||
|
}
|
||||||
LOG_INFO("Addon system initialized, found ", addonManager_->getAddons().size(), " addon(s)");
|
LOG_INFO("Addon system initialized, found ", addonManager_->getAddons().size(), " addon(s)");
|
||||||
} else {
|
} else {
|
||||||
LOG_WARNING("Failed to initialize addon system");
|
LOG_WARNING("Failed to initialize addon system");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue