diff --git a/include/addons/addon_manager.hpp b/include/addons/addon_manager.hpp index cfbfd297..681d3822 100644 --- a/include/addons/addon_manager.hpp +++ b/include/addons/addon_manager.hpp @@ -26,7 +26,6 @@ public: bool isInitialized() const { return luaEngine_.isInitialized(); } void saveAllSavedVariables(); - void setCharacterName(const std::string& name) { characterName_ = name; } /// Re-initialize the Lua VM and reload all addons (used by /reload). bool reload(); @@ -39,8 +38,6 @@ private: bool loadAddon(const TocFile& addon); std::string getSavedVariablesPath(const TocFile& addon) const; - std::string getSavedVariablesPerCharacterPath(const TocFile& addon) const; - std::string characterName_; }; } // namespace wowee::addons diff --git a/include/addons/lua_engine.hpp b/include/addons/lua_engine.hpp index 6302a64c..4a0027bb 100644 --- a/include/addons/lua_engine.hpp +++ b/include/addons/lua_engine.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -48,14 +47,9 @@ public: lua_State* getState() { return L_; } bool isInitialized() const { return L_ != nullptr; } - // Optional callback for Lua errors (displayed as UI errors to the player) - using LuaErrorCallback = std::function; - void setLuaErrorCallback(LuaErrorCallback cb) { luaErrorCallback_ = std::move(cb); } - private: lua_State* L_ = nullptr; game::GameHandler* gameHandler_ = nullptr; - LuaErrorCallback luaErrorCallback_; void registerCoreAPI(); void registerEventAPI(); diff --git a/include/addons/toc_parser.hpp b/include/addons/toc_parser.hpp index b19b4a78..7bfff469 100644 --- a/include/addons/toc_parser.hpp +++ b/include/addons/toc_parser.hpp @@ -18,7 +18,6 @@ struct TocFile { std::string getInterface() const; bool isLoadOnDemand() const; std::vector getSavedVariables() const; - std::vector getSavedVariablesPerCharacter() const; }; std::optional parseTocFile(const std::string& tocPath); diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 158c4274..7c4e0918 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -294,21 +294,6 @@ public: return spellIconPathResolver_ ? spellIconPathResolver_(spellId) : std::string{}; } - // Spell data resolver: spellId -> {castTimeMs, minRange, maxRange} - 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 { - return spellDataResolver_ ? spellDataResolver_(spellId) : SpellDataInfo{}; - } - - // Item icon path resolver: displayInfoId -> texture path (e.g., "Interface\\Icons\\INV_Sword_04") - using ItemIconPathResolver = std::function; - void setItemIconPathResolver(ItemIconPathResolver r) { itemIconPathResolver_ = std::move(r); } - std::string getItemIconPath(uint32_t displayInfoId) const { - return itemIconPathResolver_ ? itemIconPathResolver_(displayInfoId) : std::string{}; - } - // Random property/suffix name resolver: randomPropertyId -> suffix name (e.g., "of the Eagle") // Positive IDs → ItemRandomProperties.dbc; negative IDs → ItemRandomSuffix.dbc (abs value) using RandomPropertyNameResolver = std::function; @@ -418,7 +403,7 @@ public: bool hasFocus() const { return focusGuid != 0; } // Mouseover targeting — set each frame by the nameplate renderer - void setMouseoverGuid(uint64_t guid); + void setMouseoverGuid(uint64_t guid) { mouseoverGuid_ = guid; } uint64_t getMouseoverGuid() const { return mouseoverGuid_; } // Advanced targeting @@ -1243,16 +1228,6 @@ public: // Player GUID uint64_t getPlayerGuid() const { return playerGuid; } - // Look up class/race for a player GUID from name query cache. Returns 0 if unknown. - uint8_t lookupPlayerClass(uint64_t guid) const { - auto it = playerClassRaceCache_.find(guid); - return it != playerClassRaceCache_.end() ? it->second.classId : 0; - } - uint8_t lookupPlayerRace(uint64_t guid) const { - auto it = playerClassRaceCache_.find(guid); - return it != playerClassRaceCache_.end() ? it->second.raceId : 0; - } - // Look up a display name for any guid: checks playerNameCache then entity manager. // Returns empty string if unknown. Used by chat display to resolve names at render time. const std::string& lookupName(uint64_t guid) const { @@ -2687,8 +2662,6 @@ private: AddonChatCallback addonChatCallback_; AddonEventCallback addonEventCallback_; SpellIconPathResolver spellIconPathResolver_; - ItemIconPathResolver itemIconPathResolver_; - SpellDataResolver spellDataResolver_; RandomPropertyNameResolver randomPropertyNameResolver_; EmoteAnimCallback emoteAnimCallback_; @@ -2729,9 +2702,6 @@ private: // ---- Phase 1: Name caches ---- std::unordered_map playerNameCache; - // Class/race cache from SMSG_NAME_QUERY_RESPONSE (guid → {classId, raceId}) - struct PlayerClassRace { uint8_t classId = 0; uint8_t raceId = 0; }; - std::unordered_map playerClassRaceCache_; std::unordered_set pendingNameQueries; std::unordered_map creatureInfoCache; std::unordered_set pendingCreatureQueries; diff --git a/include/ui/game_screen.hpp b/include/ui/game_screen.hpp index bf8ac8b5..5391978f 100644 --- a/include/ui/game_screen.hpp +++ b/include/ui/game_screen.hpp @@ -62,10 +62,7 @@ private: // Populated by the SpellCastFailedCallback; queried during action bar button rendering. std::unordered_map actionFlashEndTimes_; - // Cached game handler for input callbacks (set each frame in render) - game::GameHandler* cachedGameHandler_ = nullptr; - - // Tab-completion state for slash commands and player names + // Tab-completion state for slash commands std::string chatTabPrefix_; // prefix captured on first Tab press std::vector chatTabMatches_; // matching command list int chatTabMatchIdx_ = -1; // active match index (-1 = inactive) diff --git a/src/addons/addon_manager.cpp b/src/addons/addon_manager.cpp index ca91e92d..e826097f 100644 --- a/src/addons/addon_manager.cpp +++ b/src/addons/addon_manager.cpp @@ -68,11 +68,6 @@ std::string AddonManager::getSavedVariablesPath(const TocFile& addon) const { return addon.basePath + "/" + addon.addonName + ".lua.saved"; } -std::string AddonManager::getSavedVariablesPerCharacterPath(const TocFile& addon) const { - if (characterName_.empty()) return ""; - return addon.basePath + "/" + addon.addonName + "." + characterName_ + ".lua.saved"; -} - bool AddonManager::loadAddon(const TocFile& addon) { // Load SavedVariables before addon code (so globals are available at load time) auto savedVars = addon.getSavedVariables(); @@ -81,15 +76,6 @@ bool AddonManager::loadAddon(const TocFile& addon) { luaEngine_.loadSavedVariables(svPath); LOG_DEBUG("AddonManager: loaded saved variables for '", addon.addonName, "'"); } - // Load per-character SavedVariables - auto savedVarsPC = addon.getSavedVariablesPerCharacter(); - if (!savedVarsPC.empty()) { - std::string svpcPath = getSavedVariablesPerCharacterPath(addon); - if (!svpcPath.empty()) { - luaEngine_.loadSavedVariables(svpcPath); - LOG_DEBUG("AddonManager: loaded per-character saved variables for '", addon.addonName, "'"); - } - } bool success = true; for (const auto& filename : addon.files) { @@ -134,13 +120,6 @@ void AddonManager::saveAllSavedVariables() { std::string svPath = getSavedVariablesPath(addon); luaEngine_.saveSavedVariables(svPath, savedVars); } - auto savedVarsPC = addon.getSavedVariablesPerCharacter(); - if (!savedVarsPC.empty()) { - std::string svpcPath = getSavedVariablesPerCharacterPath(addon); - if (!svpcPath.empty()) { - luaEngine_.saveSavedVariables(svpcPath, savedVarsPC); - } - } } } diff --git a/src/addons/lua_engine.cpp b/src/addons/lua_engine.cpp index 12e7f8fb..e75d5359 100644 --- a/src/addons/lua_engine.cpp +++ b/src/addons/lua_engine.cpp @@ -2,10 +2,7 @@ #include "addons/toc_parser.hpp" #include "game/game_handler.hpp" #include "game/entity.hpp" -#include "game/update_field_table.hpp" #include "core/logger.hpp" -#include "core/application.hpp" -#include #include #include #include @@ -63,34 +60,11 @@ static int lua_wow_message(lua_State* L) { } // Helper: resolve WoW unit IDs to GUID -// Read UNIT_FIELD_TARGET_LO/HI from an entity's update fields to get what it's targeting -static uint64_t getEntityTargetGuid(game::GameHandler* gh, uint64_t guid) { - if (guid == 0) return 0; - // If asking for the player's target, use direct accessor - if (guid == gh->getPlayerGuid()) return gh->getTargetGuid(); - auto entity = gh->getEntityManager().getEntity(guid); - if (!entity) return 0; - const auto& fields = entity->getFields(); - auto loIt = fields.find(game::fieldIndex(game::UF::UNIT_FIELD_TARGET_LO)); - if (loIt == fields.end()) return 0; - uint64_t targetGuid = loIt->second; - auto hiIt = fields.find(game::fieldIndex(game::UF::UNIT_FIELD_TARGET_HI)); - if (hiIt != fields.end()) - targetGuid |= (static_cast(hiIt->second) << 32); - return targetGuid; -} - static uint64_t resolveUnitGuid(game::GameHandler* gh, const std::string& uid) { if (uid == "player") return gh->getPlayerGuid(); if (uid == "target") return gh->getTargetGuid(); if (uid == "focus") return gh->getFocusGuid(); - if (uid == "mouseover") return gh->getMouseoverGuid(); if (uid == "pet") return gh->getPetGuid(); - // Compound unit IDs: targettarget, focustarget, pettarget, mouseovertarget - if (uid == "targettarget") return getEntityTargetGuid(gh, gh->getTargetGuid()); - if (uid == "focustarget") return getEntityTargetGuid(gh, gh->getFocusGuid()); - if (uid == "pettarget") return getEntityTargetGuid(gh, gh->getPetGuid()); - if (uid == "mouseovertarget") return getEntityTargetGuid(gh, gh->getMouseoverGuid()); // party1-party4, raid1-raid40 if (uid.rfind("party", 0) == 0 && uid.size() > 5) { int idx = 0; @@ -117,7 +91,7 @@ static uint64_t resolveUnitGuid(game::GameHandler* gh, const std::string& uid) { return 0; } -// Helper: resolve unit IDs (player, target, focus, mouseover, pet, targettarget, focustarget, etc.) to entity +// Helper: resolve "player", "target", "focus", "pet", "partyN", "raidN" unit IDs to entity static game::Unit* resolveUnit(lua_State* L, const char* unitId) { auto* gh = getGameHandler(L); if (!gh || !unitId) return nullptr; @@ -133,135 +107,56 @@ static game::Unit* resolveUnit(lua_State* L, const char* unitId) { // --- WoW Unit API --- -// Helper: find GroupMember data for a GUID (for party members out of entity range) -static const game::GroupMember* findPartyMember(game::GameHandler* gh, uint64_t guid) { - if (!gh || guid == 0) return nullptr; - for (const auto& m : gh->getPartyData().members) { - if (m.guid == guid && m.hasPartyStats) return &m; - } - return nullptr; -} - static int lua_UnitName(lua_State* L) { const char* uid = luaL_optstring(L, 1, "player"); auto* unit = resolveUnit(L, uid); if (unit && !unit->getName().empty()) { lua_pushstring(L, unit->getName().c_str()); } else { - // Fallback: party member name for out-of-range members - auto* gh = getGameHandler(L); - std::string uidStr(uid); - for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); - uint64_t guid = gh ? resolveUnitGuid(gh, uidStr) : 0; - const auto* pm = findPartyMember(gh, guid); - if (pm && !pm->name.empty()) { - lua_pushstring(L, pm->name.c_str()); - } else if (gh && guid != 0) { - // Try player name cache - const std::string& cached = gh->lookupName(guid); - lua_pushstring(L, cached.empty() ? "Unknown" : cached.c_str()); - } else { - lua_pushstring(L, "Unknown"); - } + lua_pushstring(L, "Unknown"); } return 1; } - static int lua_UnitHealth(lua_State* L) { const char* uid = luaL_optstring(L, 1, "player"); auto* unit = resolveUnit(L, uid); - if (unit) { - lua_pushnumber(L, unit->getHealth()); - } else { - // Fallback: party member stats for out-of-range members - auto* gh = getGameHandler(L); - std::string uidStr(uid); - for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); - uint64_t guid = gh ? resolveUnitGuid(gh, uidStr) : 0; - const auto* pm = findPartyMember(gh, guid); - lua_pushnumber(L, pm ? pm->curHealth : 0); - } + lua_pushnumber(L, unit ? unit->getHealth() : 0); return 1; } static int lua_UnitHealthMax(lua_State* L) { const char* uid = luaL_optstring(L, 1, "player"); auto* unit = resolveUnit(L, uid); - if (unit) { - lua_pushnumber(L, unit->getMaxHealth()); - } else { - auto* gh = getGameHandler(L); - std::string uidStr(uid); - for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); - uint64_t guid = gh ? resolveUnitGuid(gh, uidStr) : 0; - const auto* pm = findPartyMember(gh, guid); - lua_pushnumber(L, pm ? pm->maxHealth : 0); - } + lua_pushnumber(L, unit ? unit->getMaxHealth() : 0); return 1; } static int lua_UnitPower(lua_State* L) { const char* uid = luaL_optstring(L, 1, "player"); auto* unit = resolveUnit(L, uid); - if (unit) { - lua_pushnumber(L, unit->getPower()); - } else { - auto* gh = getGameHandler(L); - std::string uidStr(uid); - for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); - uint64_t guid = gh ? resolveUnitGuid(gh, uidStr) : 0; - const auto* pm = findPartyMember(gh, guid); - lua_pushnumber(L, pm ? pm->curPower : 0); - } + lua_pushnumber(L, unit ? unit->getPower() : 0); return 1; } static int lua_UnitPowerMax(lua_State* L) { const char* uid = luaL_optstring(L, 1, "player"); auto* unit = resolveUnit(L, uid); - if (unit) { - lua_pushnumber(L, unit->getMaxPower()); - } else { - auto* gh = getGameHandler(L); - std::string uidStr(uid); - for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); - uint64_t guid = gh ? resolveUnitGuid(gh, uidStr) : 0; - const auto* pm = findPartyMember(gh, guid); - lua_pushnumber(L, pm ? pm->maxPower : 0); - } + lua_pushnumber(L, unit ? unit->getMaxPower() : 0); return 1; } static int lua_UnitLevel(lua_State* L) { const char* uid = luaL_optstring(L, 1, "player"); auto* unit = resolveUnit(L, uid); - if (unit) { - lua_pushnumber(L, unit->getLevel()); - } else { - auto* gh = getGameHandler(L); - std::string uidStr(uid); - for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); - uint64_t guid = gh ? resolveUnitGuid(gh, uidStr) : 0; - const auto* pm = findPartyMember(gh, guid); - lua_pushnumber(L, pm ? pm->level : 0); - } + lua_pushnumber(L, unit ? unit->getLevel() : 0); return 1; } static int lua_UnitExists(lua_State* L) { const char* uid = luaL_optstring(L, 1, "player"); auto* unit = resolveUnit(L, uid); - if (unit) { - lua_pushboolean(L, 1); - } else { - // Party members in other zones don't have entities but still "exist" - auto* gh = getGameHandler(L); - std::string uidStr(uid); - for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); - uint64_t guid = gh ? resolveUnitGuid(gh, uidStr) : 0; - lua_pushboolean(L, guid != 0 && findPartyMember(gh, guid) != nullptr); - } + lua_pushboolean(L, unit != nullptr); return 1; } @@ -280,27 +175,11 @@ static int lua_UnitClass(lua_State* L) { static const char* kClasses[] = {"", "Warrior","Paladin","Hunter","Rogue","Priest", "Death Knight","Shaman","Mage","Warlock","","Druid"}; uint8_t classId = 0; + // For player, use character data; for others, use UNIT_FIELD_BYTES_0 std::string uidStr(uid); for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); - if (uidStr == "player") { - classId = gh->getPlayerClass(); - } else { - // Read class from UNIT_FIELD_BYTES_0 (class is byte 1) - uint64_t guid = resolveUnitGuid(gh, uidStr); - if (guid != 0) { - auto entity = gh->getEntityManager().getEntity(guid); - if (entity) { - uint32_t bytes0 = entity->getField( - game::fieldIndex(game::UF::UNIT_FIELD_BYTES_0)); - classId = static_cast((bytes0 >> 8) & 0xFF); - } - } - // Fallback: check name query class/race cache - if (classId == 0 && guid != 0) { - classId = gh->lookupPlayerClass(guid); - } - } - const char* name = (classId > 0 && classId < 12) ? kClasses[classId] : "Unknown"; + if (uidStr == "player") classId = gh->getPlayerClass(); + const char* name = (classId < 12) ? kClasses[classId] : "Unknown"; lua_pushstring(L, name); lua_pushstring(L, name); // WoW returns localized + English lua_pushnumber(L, classId); @@ -312,129 +191,6 @@ static int lua_UnitClass(lua_State* L) { return 3; } -// UnitIsGhost(unit) — true if unit is in ghost form -static int lua_UnitIsGhost(lua_State* L) { - const char* uid = luaL_optstring(L, 1, "player"); - auto* gh = getGameHandler(L); - if (!gh) { lua_pushboolean(L, 0); return 1; } - std::string uidStr(uid); - for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); - if (uidStr == "player") { - lua_pushboolean(L, gh->isPlayerGhost()); - } else { - // Check UNIT_FIELD_FLAGS for UNIT_FLAG_GHOST (0x00000100) — best approximation - uint64_t guid = resolveUnitGuid(gh, uidStr); - bool ghost = false; - if (guid != 0) { - auto entity = gh->getEntityManager().getEntity(guid); - if (entity) { - uint32_t flags = entity->getField(game::fieldIndex(game::UF::UNIT_FIELD_FLAGS)); - ghost = (flags & 0x00000100) != 0; // PLAYER_FLAGS_GHOST - } - } - lua_pushboolean(L, ghost); - } - return 1; -} - -// UnitIsDeadOrGhost(unit) -static int lua_UnitIsDeadOrGhost(lua_State* L) { - const char* uid = luaL_optstring(L, 1, "player"); - auto* unit = resolveUnit(L, uid); - auto* gh = getGameHandler(L); - bool dead = (unit && unit->getHealth() == 0); - if (!dead && gh) { - std::string uidStr(uid); - for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); - if (uidStr == "player") dead = gh->isPlayerGhost() || gh->isPlayerDead(); - } - lua_pushboolean(L, dead); - return 1; -} - -// UnitIsAFK(unit), UnitIsDND(unit) -static int lua_UnitIsAFK(lua_State* L) { - const char* uid = luaL_optstring(L, 1, "player"); - auto* gh = getGameHandler(L); - if (!gh) { lua_pushboolean(L, 0); return 1; } - std::string uidStr(uid); - for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); - uint64_t guid = resolveUnitGuid(gh, uidStr); - if (guid != 0) { - auto entity = gh->getEntityManager().getEntity(guid); - if (entity) { - // PLAYER_FLAGS at UNIT_FIELD_FLAGS: PLAYER_FLAGS_AFK = 0x01 - uint32_t playerFlags = entity->getField(game::fieldIndex(game::UF::UNIT_FIELD_FLAGS)); - lua_pushboolean(L, (playerFlags & 0x01) != 0); - return 1; - } - } - lua_pushboolean(L, 0); - return 1; -} - -static int lua_UnitIsDND(lua_State* L) { - const char* uid = luaL_optstring(L, 1, "player"); - auto* gh = getGameHandler(L); - if (!gh) { lua_pushboolean(L, 0); return 1; } - std::string uidStr(uid); - for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); - uint64_t guid = resolveUnitGuid(gh, uidStr); - if (guid != 0) { - auto entity = gh->getEntityManager().getEntity(guid); - if (entity) { - uint32_t playerFlags = entity->getField(game::fieldIndex(game::UF::UNIT_FIELD_FLAGS)); - lua_pushboolean(L, (playerFlags & 0x02) != 0); // PLAYER_FLAGS_DND - return 1; - } - } - lua_pushboolean(L, 0); - return 1; -} - -// UnitPlayerControlled(unit) — true for players and player-controlled pets -static int lua_UnitPlayerControlled(lua_State* L) { - const char* uid = luaL_optstring(L, 1, "player"); - auto* gh = getGameHandler(L); - if (!gh) { lua_pushboolean(L, 0); return 1; } - std::string uidStr(uid); - for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); - uint64_t guid = resolveUnitGuid(gh, uidStr); - if (guid == 0) { lua_pushboolean(L, 0); return 1; } - auto entity = gh->getEntityManager().getEntity(guid); - if (!entity) { lua_pushboolean(L, 0); return 1; } - // Players are always player-controlled; pets check UNIT_FLAG_PLAYER_CONTROLLED (0x01000000) - if (entity->getType() == game::ObjectType::PLAYER) { - lua_pushboolean(L, 1); - } else { - uint32_t flags = entity->getField(game::fieldIndex(game::UF::UNIT_FIELD_FLAGS)); - lua_pushboolean(L, (flags & 0x01000000) != 0); - } - return 1; -} - -// UnitSex(unit) → 1=unknown, 2=male, 3=female -static int lua_UnitSex(lua_State* L) { - const char* uid = luaL_optstring(L, 1, "player"); - auto* gh = getGameHandler(L); - if (!gh) { lua_pushnumber(L, 1); return 1; } - std::string uidStr(uid); - for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); - uint64_t guid = resolveUnitGuid(gh, uidStr); - if (guid != 0) { - auto entity = gh->getEntityManager().getEntity(guid); - if (entity) { - // Gender is byte 2 of UNIT_FIELD_BYTES_0 (0=male, 1=female) - uint32_t bytes0 = entity->getField(game::fieldIndex(game::UF::UNIT_FIELD_BYTES_0)); - uint8_t gender = static_cast((bytes0 >> 16) & 0xFF); - lua_pushnumber(L, gender == 0 ? 2 : (gender == 1 ? 3 : 1)); // WoW: 2=male, 3=female - return 1; - } - } - lua_pushnumber(L, 1); // unknown - return 1; -} - // --- Player/Game API --- static int lua_GetMoney(lua_State* L) { @@ -470,33 +226,18 @@ static int lua_GetPlayerMapPosition(lua_State* L) { static int lua_UnitRace(lua_State* L) { auto* gh = getGameHandler(L); - if (!gh) { lua_pushstring(L, "Unknown"); lua_pushstring(L, "Unknown"); lua_pushnumber(L, 0); return 3; } + if (!gh) { lua_pushstring(L, "Unknown"); return 1; } std::string uid(luaL_optstring(L, 1, "player")); for (char& c : uid) c = static_cast(std::tolower(static_cast(c))); - static const char* kRaces[] = {"","Human","Orc","Dwarf","Night Elf","Undead", - "Tauren","Gnome","Troll","","Blood Elf","Draenei"}; - uint8_t raceId = 0; if (uid == "player") { - raceId = gh->getPlayerRace(); - } else { - // Read race from UNIT_FIELD_BYTES_0 (race is byte 0) - uint64_t guid = resolveUnitGuid(gh, uid); - if (guid != 0) { - auto entity = gh->getEntityManager().getEntity(guid); - if (entity) { - uint32_t bytes0 = entity->getField( - game::fieldIndex(game::UF::UNIT_FIELD_BYTES_0)); - raceId = static_cast(bytes0 & 0xFF); - } - // Fallback: name query class/race cache - if (raceId == 0) raceId = gh->lookupPlayerRace(guid); - } + uint8_t race = gh->getPlayerRace(); + static const char* kRaces[] = {"","Human","Orc","Dwarf","Night Elf","Undead", + "Tauren","Gnome","Troll","","Blood Elf","Draenei"}; + lua_pushstring(L, (race < 12) ? kRaces[race] : "Unknown"); + return 1; } - const char* name = (raceId > 0 && raceId < 12) ? kRaces[raceId] : "Unknown"; - lua_pushstring(L, name); // 1: localized race - lua_pushstring(L, name); // 2: English race - lua_pushnumber(L, raceId); // 3: raceId (WoW returns 3 values) - return 3; + lua_pushstring(L, "Unknown"); + return 1; } static int lua_UnitPowerType(lua_State* L) { @@ -600,37 +341,6 @@ static int lua_GetAddOnInfo(lua_State* L) { return 5; } -// GetAddOnMetadata(addonNameOrIndex, key) → value -static int lua_GetAddOnMetadata(lua_State* L) { - lua_getfield(L, LUA_REGISTRYINDEX, "wowee_addon_info"); - if (!lua_istable(L, -1)) { lua_pop(L, 1); lua_pushnil(L); return 1; } - - int idx = 0; - if (lua_isnumber(L, 1)) { - idx = static_cast(lua_tonumber(L, 1)); - } else if (lua_isstring(L, 1)) { - const char* name = lua_tostring(L, 1); - int count = static_cast(lua_objlen(L, -1)); - for (int i = 1; i <= count; i++) { - lua_rawgeti(L, -1, i); - lua_getfield(L, -1, "name"); - const char* aName = lua_tostring(L, -1); - lua_pop(L, 1); - if (aName && strcmp(aName, name) == 0) { idx = i; lua_pop(L, 1); break; } - lua_pop(L, 1); - } - } - if (idx < 1) { lua_pop(L, 1); lua_pushnil(L); return 1; } - - const char* key = luaL_checkstring(L, 2); - lua_rawgeti(L, -1, idx); - if (!lua_istable(L, -1)) { lua_pop(L, 2); lua_pushnil(L); return 1; } - lua_getfield(L, -1, "metadata"); - if (!lua_istable(L, -1)) { lua_pop(L, 3); lua_pushnil(L); return 1; } - lua_getfield(L, -1, key); - return 1; -} - // UnitBuff(unitId, index) / UnitDebuff(unitId, index) // Returns: name, rank, icon, count, debuffType, duration, expirationTime, caster, isStealable, shouldConsolidate, spellId static int lua_UnitAura(lua_State* L, bool wantBuff) { @@ -799,65 +509,6 @@ static int lua_CastSpellByName(lua_State* L) { return 0; } -// SendAddonMessage(prefix, text, chatType, target) — send addon message -static int lua_SendAddonMessage(lua_State* L) { - auto* gh = getGameHandler(L); - if (!gh) return 0; - const char* prefix = luaL_checkstring(L, 1); - const char* text = luaL_checkstring(L, 2); - const char* chatType = luaL_optstring(L, 3, "PARTY"); - const char* target = luaL_optstring(L, 4, ""); - - // Build addon message: prefix + TAB + text, send via the appropriate channel - std::string typeStr(chatType); - for (char& c : typeStr) c = static_cast(std::toupper(static_cast(c))); - - game::ChatType ct = game::ChatType::PARTY; - if (typeStr == "PARTY") ct = game::ChatType::PARTY; - else if (typeStr == "RAID") ct = game::ChatType::RAID; - else if (typeStr == "GUILD") ct = game::ChatType::GUILD; - else if (typeStr == "OFFICER") ct = game::ChatType::OFFICER; - else if (typeStr == "BATTLEGROUND") ct = game::ChatType::BATTLEGROUND; - else if (typeStr == "WHISPER") ct = game::ChatType::WHISPER; - - // Encode as prefix\ttext (WoW addon message format) - std::string encoded = std::string(prefix) + "\t" + text; - std::string targetStr(target && *target ? target : ""); - gh->sendChatMessage(ct, encoded, targetStr); - return 0; -} - -// RegisterAddonMessagePrefix(prefix) — register prefix for receiving addon messages -static int lua_RegisterAddonMessagePrefix(lua_State* L) { - const char* prefix = luaL_checkstring(L, 1); - // Store in a global Lua table for filtering - lua_getglobal(L, "__WoweeAddonPrefixes"); - if (lua_isnil(L, -1)) { - lua_pop(L, 1); - lua_newtable(L); - lua_pushvalue(L, -1); - lua_setglobal(L, "__WoweeAddonPrefixes"); - } - lua_pushboolean(L, 1); - lua_setfield(L, -2, prefix); - lua_pop(L, 1); - lua_pushboolean(L, 1); // success - return 1; -} - -// IsAddonMessagePrefixRegistered(prefix) → boolean -static int lua_IsAddonMessagePrefixRegistered(lua_State* L) { - const char* prefix = luaL_checkstring(L, 1); - lua_getglobal(L, "__WoweeAddonPrefixes"); - if (lua_istable(L, -1)) { - lua_getfield(L, -1, prefix); - lua_pushboolean(L, lua_toboolean(L, -1)); - return 1; - } - lua_pushboolean(L, 0); - return 1; -} - static int lua_IsSpellKnown(lua_State* L) { auto* gh = getGameHandler(L); uint32_t spellId = static_cast(luaL_checknumber(L, 1)); @@ -905,112 +556,6 @@ static int lua_HasTarget(lua_State* L) { return 1; } -// TargetUnit(unitId) — set current target -static int lua_TargetUnit(lua_State* L) { - auto* gh = getGameHandler(L); - if (!gh) return 0; - const char* uid = luaL_checkstring(L, 1); - std::string uidStr(uid); - for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); - uint64_t guid = resolveUnitGuid(gh, uidStr); - if (guid != 0) gh->setTarget(guid); - return 0; -} - -// ClearTarget() — clear current target -static int lua_ClearTarget(lua_State* L) { - auto* gh = getGameHandler(L); - if (gh) gh->clearTarget(); - return 0; -} - -// FocusUnit(unitId) — set focus target -static int lua_FocusUnit(lua_State* L) { - auto* gh = getGameHandler(L); - if (!gh) return 0; - const char* uid = luaL_optstring(L, 1, nullptr); - if (!uid || !*uid) return 0; - std::string uidStr(uid); - for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); - uint64_t guid = resolveUnitGuid(gh, uidStr); - if (guid != 0) gh->setFocus(guid); - return 0; -} - -// ClearFocus() — clear focus target -static int lua_ClearFocus(lua_State* L) { - auto* gh = getGameHandler(L); - if (gh) gh->clearFocus(); - return 0; -} - -// AssistUnit(unitId) — target whatever the given unit is targeting -static int lua_AssistUnit(lua_State* L) { - auto* gh = getGameHandler(L); - if (!gh) return 0; - const char* uid = luaL_optstring(L, 1, "target"); - std::string uidStr(uid); - for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); - uint64_t guid = resolveUnitGuid(gh, uidStr); - if (guid == 0) return 0; - uint64_t theirTarget = getEntityTargetGuid(gh, guid); - if (theirTarget != 0) gh->setTarget(theirTarget); - return 0; -} - -// TargetLastTarget() — re-target previous target -static int lua_TargetLastTarget(lua_State* L) { - auto* gh = getGameHandler(L); - if (gh) gh->targetLastTarget(); - return 0; -} - -// TargetNearestEnemy() — tab-target nearest enemy -static int lua_TargetNearestEnemy(lua_State* L) { - auto* gh = getGameHandler(L); - if (gh) gh->targetEnemy(false); - return 0; -} - -// TargetNearestFriend() — target nearest friendly unit -static int lua_TargetNearestFriend(lua_State* L) { - auto* gh = getGameHandler(L); - if (gh) gh->targetFriend(false); - return 0; -} - -// GetRaidTargetIndex(unit) → icon index (1-8) or nil -static int lua_GetRaidTargetIndex(lua_State* L) { - auto* gh = getGameHandler(L); - if (!gh) { lua_pushnil(L); return 1; } - const char* uid = luaL_optstring(L, 1, "target"); - std::string uidStr(uid); - for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); - uint64_t guid = resolveUnitGuid(gh, uidStr); - if (guid == 0) { lua_pushnil(L); return 1; } - uint8_t mark = gh->getEntityRaidMark(guid); - if (mark == 0xFF) { lua_pushnil(L); return 1; } - lua_pushnumber(L, mark + 1); // WoW uses 1-indexed (1=Star, 2=Circle, ... 8=Skull) - return 1; -} - -// SetRaidTarget(unit, index) — set raid marker (1-8, or 0 to clear) -static int lua_SetRaidTarget(lua_State* L) { - auto* gh = getGameHandler(L); - if (!gh) return 0; - const char* uid = luaL_optstring(L, 1, "target"); - int index = static_cast(luaL_checknumber(L, 2)); - std::string uidStr(uid); - for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); - uint64_t guid = resolveUnitGuid(gh, uidStr); - if (guid == 0) return 0; - if (index >= 1 && index <= 8) - gh->setRaidMark(guid, static_cast(index - 1)); - else if (index == 0) - gh->setRaidMark(guid, 0xFF); // clear - return 0; -} - // --- GetSpellInfo / GetSpellTexture --- // GetSpellInfo(spellIdOrName) -> name, rank, icon, castTime, minRange, maxRange, spellId static int lua_GetSpellInfo(lua_State* L) { @@ -1053,11 +598,9 @@ static int lua_GetSpellInfo(lua_State* L) { std::string iconPath = gh->getSpellIconPath(spellId); if (!iconPath.empty()) lua_pushstring(L, iconPath.c_str()); else lua_pushnil(L); // 3: icon texture path - // Resolve cast time and range from Spell.dbc → SpellCastTimes.dbc / SpellRange.dbc - auto spellData = gh->getSpellData(spellId); - lua_pushnumber(L, spellData.castTimeMs); // 4: castTime (ms) - lua_pushnumber(L, spellData.minRange); // 5: minRange (yards) - lua_pushnumber(L, spellData.maxRange); // 6: maxRange (yards) + 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; } @@ -1123,14 +666,7 @@ static int lua_GetItemInfo(lua_State* L) { lua_pushstring(L, ""); // 7: subclass lua_pushnumber(L, info->maxStack > 0 ? info->maxStack : 1); // 8: maxStack lua_pushstring(L, ""); // 9: equipSlot - // 10: texture (icon path from ItemDisplayInfo.dbc) - if (info->displayInfoId != 0) { - std::string iconPath = gh->getItemIconPath(info->displayInfoId); - if (!iconPath.empty()) lua_pushstring(L, iconPath.c_str()); - else lua_pushnil(L); - } else { - lua_pushnil(L); - } + lua_pushnil(L); // 10: texture (icon path — no ItemDisplayInfo icon resolver yet) lua_pushnumber(L, info->sellPrice); // 11: vendorPrice return 11; } @@ -1553,437 +1089,6 @@ static int lua_IsQuestComplete(lua_State* L) { return 1; } -// --- Skill Line API --- - -// GetNumSkillLines() → count -static int lua_GetNumSkillLines(lua_State* L) { - auto* gh = getGameHandler(L); - if (!gh) { lua_pushnumber(L, 0); return 1; } - lua_pushnumber(L, gh->getPlayerSkills().size()); - return 1; -} - -// GetSkillLineInfo(index) → skillName, isHeader, isExpanded, skillRank, numTempPoints, skillModifier, skillMaxRank, isAbandonable, stepCost, rankCost, minLevel, skillCostType -static int lua_GetSkillLineInfo(lua_State* L) { - auto* gh = getGameHandler(L); - int index = static_cast(luaL_checknumber(L, 1)); - if (!gh || index < 1) { - lua_pushnil(L); - return 1; - } - const auto& skills = gh->getPlayerSkills(); - if (index > static_cast(skills.size())) { - lua_pushnil(L); - return 1; - } - // Skills are in a map — iterate to the Nth entry - auto it = skills.begin(); - std::advance(it, index - 1); - const auto& skill = it->second; - std::string name = gh->getSkillName(skill.skillId); - if (name.empty()) name = "Skill " + std::to_string(skill.skillId); - - lua_pushstring(L, name.c_str()); // 1: skillName - lua_pushboolean(L, 0); // 2: isHeader (false — flat list) - lua_pushboolean(L, 1); // 3: isExpanded - lua_pushnumber(L, skill.effectiveValue()); // 4: skillRank - lua_pushnumber(L, skill.bonusTemp); // 5: numTempPoints - lua_pushnumber(L, skill.bonusPerm); // 6: skillModifier - lua_pushnumber(L, skill.maxValue); // 7: skillMaxRank - lua_pushboolean(L, 0); // 8: isAbandonable - lua_pushnumber(L, 0); // 9: stepCost - lua_pushnumber(L, 0); // 10: rankCost - lua_pushnumber(L, 0); // 11: minLevel - lua_pushnumber(L, 0); // 12: skillCostType - return 12; -} - -// --- Friends/Ignore API --- - -// GetNumFriends() → count -static int lua_GetNumFriends(lua_State* L) { - auto* gh = getGameHandler(L); - if (!gh) { lua_pushnumber(L, 0); return 1; } - int count = 0; - for (const auto& c : gh->getContacts()) - if (c.isFriend()) count++; - lua_pushnumber(L, count); - return 1; -} - -// GetFriendInfo(index) → name, level, class, area, connected, status, note -static int lua_GetFriendInfo(lua_State* L) { - auto* gh = getGameHandler(L); - int index = static_cast(luaL_checknumber(L, 1)); - if (!gh || index < 1) { - lua_pushnil(L); return 1; - } - int found = 0; - for (const auto& c : gh->getContacts()) { - if (!c.isFriend()) continue; - if (++found == index) { - lua_pushstring(L, c.name.c_str()); // 1: name - lua_pushnumber(L, c.level); // 2: level - static const char* kClasses[] = {"","Warrior","Paladin","Hunter","Rogue","Priest", - "Death Knight","Shaman","Mage","Warlock","","Druid"}; - lua_pushstring(L, c.classId < 12 ? kClasses[c.classId] : "Unknown"); // 3: class - std::string area; - if (c.areaId != 0) area = gh->getWhoAreaName(c.areaId); - lua_pushstring(L, area.c_str()); // 4: area - lua_pushboolean(L, c.isOnline()); // 5: connected - lua_pushstring(L, c.status == 2 ? "" : (c.status == 3 ? "" : "")); // 6: status - lua_pushstring(L, c.note.c_str()); // 7: note - return 7; - } - } - lua_pushnil(L); - return 1; -} - -// --- Guild API --- - -// IsInGuild() → boolean -static int lua_IsInGuild(lua_State* L) { - auto* gh = getGameHandler(L); - lua_pushboolean(L, gh && gh->isInGuild()); - return 1; -} - -// GetGuildInfo("player") → guildName, guildRankName, guildRankIndex -static int lua_GetGuildInfoFunc(lua_State* L) { - auto* gh = getGameHandler(L); - if (!gh || !gh->isInGuild()) { lua_pushnil(L); return 1; } - lua_pushstring(L, gh->getGuildName().c_str()); - // Get rank name for the player - const auto& roster = gh->getGuildRoster(); - std::string rankName; - uint32_t rankIndex = 0; - for (const auto& m : roster.members) { - if (m.guid == gh->getPlayerGuid()) { - rankIndex = m.rankIndex; - const auto& rankNames = gh->getGuildRankNames(); - if (rankIndex < rankNames.size()) rankName = rankNames[rankIndex]; - break; - } - } - lua_pushstring(L, rankName.c_str()); - lua_pushnumber(L, rankIndex); - return 3; -} - -// GetNumGuildMembers() → totalMembers, onlineMembers -static int lua_GetNumGuildMembers(lua_State* L) { - auto* gh = getGameHandler(L); - if (!gh) { lua_pushnumber(L, 0); lua_pushnumber(L, 0); return 2; } - const auto& roster = gh->getGuildRoster(); - int online = 0; - for (const auto& m : roster.members) - if (m.online) online++; - lua_pushnumber(L, roster.members.size()); - lua_pushnumber(L, online); - return 2; -} - -// GetGuildRosterInfo(index) → name, rank, rankIndex, level, class, zone, note, officerNote, online, status, classId -static int lua_GetGuildRosterInfo(lua_State* L) { - auto* gh = getGameHandler(L); - int index = static_cast(luaL_checknumber(L, 1)); - if (!gh || index < 1) { lua_pushnil(L); return 1; } - const auto& roster = gh->getGuildRoster(); - if (index > static_cast(roster.members.size())) { lua_pushnil(L); return 1; } - const auto& m = roster.members[index - 1]; - - lua_pushstring(L, m.name.c_str()); // 1: name - const auto& rankNames = gh->getGuildRankNames(); - lua_pushstring(L, m.rankIndex < rankNames.size() - ? rankNames[m.rankIndex].c_str() : ""); // 2: rank name - lua_pushnumber(L, m.rankIndex); // 3: rankIndex - lua_pushnumber(L, m.level); // 4: level - static const char* kCls[] = {"","Warrior","Paladin","Hunter","Rogue","Priest", - "Death Knight","Shaman","Mage","Warlock","","Druid"}; - lua_pushstring(L, m.classId < 12 ? kCls[m.classId] : "Unknown"); // 5: class - std::string zone; - if (m.zoneId != 0 && m.online) zone = gh->getWhoAreaName(m.zoneId); - lua_pushstring(L, zone.c_str()); // 6: zone - lua_pushstring(L, m.publicNote.c_str()); // 7: note - lua_pushstring(L, m.officerNote.c_str()); // 8: officerNote - lua_pushboolean(L, m.online); // 9: online - lua_pushnumber(L, 0); // 10: status (0=online, 1=AFK, 2=DND) - lua_pushnumber(L, m.classId); // 11: classId (numeric) - return 11; -} - -// GetGuildRosterMOTD() → motd -static int lua_GetGuildRosterMOTD(lua_State* L) { - auto* gh = getGameHandler(L); - if (!gh) { lua_pushstring(L, ""); return 1; } - lua_pushstring(L, gh->getGuildRoster().motd.c_str()); - return 1; -} - -// GetNumIgnores() → count -static int lua_GetNumIgnores(lua_State* L) { - auto* gh = getGameHandler(L); - if (!gh) { lua_pushnumber(L, 0); return 1; } - int count = 0; - for (const auto& c : gh->getContacts()) - if (c.isIgnored()) count++; - lua_pushnumber(L, count); - return 1; -} - -// GetIgnoreName(index) → name -static int lua_GetIgnoreName(lua_State* L) { - auto* gh = getGameHandler(L); - int index = static_cast(luaL_checknumber(L, 1)); - if (!gh || index < 1) { lua_pushnil(L); return 1; } - int found = 0; - for (const auto& c : gh->getContacts()) { - if (!c.isIgnored()) continue; - if (++found == index) { - lua_pushstring(L, c.name.c_str()); - return 1; - } - } - lua_pushnil(L); - return 1; -} - -// --- Talent API --- - -// GetNumTalentTabs() → count (usually 3) -static int lua_GetNumTalentTabs(lua_State* L) { - auto* gh = getGameHandler(L); - if (!gh) { lua_pushnumber(L, 0); return 1; } - // Count tabs matching the player's class - uint8_t classId = gh->getPlayerClass(); - uint32_t classMask = (classId > 0) ? (1u << (classId - 1)) : 0; - int count = 0; - for (const auto& [tabId, tab] : gh->getAllTalentTabs()) { - if (tab.classMask & classMask) count++; - } - lua_pushnumber(L, count); - return 1; -} - -// GetTalentTabInfo(tabIndex) → name, iconTexture, pointsSpent, background -static int lua_GetTalentTabInfo(lua_State* L) { - auto* gh = getGameHandler(L); - int tabIndex = static_cast(luaL_checknumber(L, 1)); // 1-indexed - if (!gh || tabIndex < 1) { - lua_pushnil(L); return 1; - } - uint8_t classId = gh->getPlayerClass(); - uint32_t classMask = (classId > 0) ? (1u << (classId - 1)) : 0; - // Find the Nth tab for this class (sorted by orderIndex) - std::vector classTabs; - for (const auto& [tabId, tab] : gh->getAllTalentTabs()) { - if (tab.classMask & classMask) classTabs.push_back(&tab); - } - std::sort(classTabs.begin(), classTabs.end(), - [](const auto* a, const auto* b) { return a->orderIndex < b->orderIndex; }); - if (tabIndex > static_cast(classTabs.size())) { - lua_pushnil(L); return 1; - } - const auto* tab = classTabs[tabIndex - 1]; - // Count points spent in this tab - int pointsSpent = 0; - const auto& learned = gh->getLearnedTalents(); - for (const auto& [talentId, rank] : learned) { - const auto* entry = gh->getTalentEntry(talentId); - if (entry && entry->tabId == tab->tabId) pointsSpent += rank; - } - lua_pushstring(L, tab->name.c_str()); // 1: name - lua_pushnil(L); // 2: iconTexture (not resolved) - lua_pushnumber(L, pointsSpent); // 3: pointsSpent - lua_pushstring(L, tab->backgroundFile.c_str()); // 4: background - return 4; -} - -// GetNumTalents(tabIndex) → count -static int lua_GetNumTalents(lua_State* L) { - auto* gh = getGameHandler(L); - int tabIndex = static_cast(luaL_checknumber(L, 1)); - if (!gh || tabIndex < 1) { lua_pushnumber(L, 0); return 1; } - uint8_t classId = gh->getPlayerClass(); - uint32_t classMask = (classId > 0) ? (1u << (classId - 1)) : 0; - std::vector classTabs; - for (const auto& [tabId, tab] : gh->getAllTalentTabs()) { - if (tab.classMask & classMask) classTabs.push_back(&tab); - } - std::sort(classTabs.begin(), classTabs.end(), - [](const auto* a, const auto* b) { return a->orderIndex < b->orderIndex; }); - if (tabIndex > static_cast(classTabs.size())) { - lua_pushnumber(L, 0); return 1; - } - uint32_t targetTabId = classTabs[tabIndex - 1]->tabId; - int count = 0; - for (const auto& [talentId, entry] : gh->getAllTalents()) { - if (entry.tabId == targetTabId) count++; - } - lua_pushnumber(L, count); - return 1; -} - -// GetTalentInfo(tabIndex, talentIndex) → name, iconTexture, tier, column, rank, maxRank, isExceptional, available -static int lua_GetTalentInfo(lua_State* L) { - auto* gh = getGameHandler(L); - int tabIndex = static_cast(luaL_checknumber(L, 1)); - int talentIndex = static_cast(luaL_checknumber(L, 2)); - if (!gh || tabIndex < 1 || talentIndex < 1) { - for (int i = 0; i < 8; i++) lua_pushnil(L); - return 8; - } - uint8_t classId = gh->getPlayerClass(); - uint32_t classMask = (classId > 0) ? (1u << (classId - 1)) : 0; - std::vector classTabs; - for (const auto& [tabId, tab] : gh->getAllTalentTabs()) { - if (tab.classMask & classMask) classTabs.push_back(&tab); - } - std::sort(classTabs.begin(), classTabs.end(), - [](const auto* a, const auto* b) { return a->orderIndex < b->orderIndex; }); - if (tabIndex > static_cast(classTabs.size())) { - for (int i = 0; i < 8; i++) lua_pushnil(L); - return 8; - } - uint32_t targetTabId = classTabs[tabIndex - 1]->tabId; - // Collect talents for this tab, sorted by row then column - std::vector tabTalents; - for (const auto& [talentId, entry] : gh->getAllTalents()) { - if (entry.tabId == targetTabId) tabTalents.push_back(&entry); - } - std::sort(tabTalents.begin(), tabTalents.end(), - [](const auto* a, const auto* b) { - return (a->row != b->row) ? a->row < b->row : a->column < b->column; - }); - if (talentIndex > static_cast(tabTalents.size())) { - for (int i = 0; i < 8; i++) lua_pushnil(L); - return 8; - } - const auto* talent = tabTalents[talentIndex - 1]; - uint8_t rank = gh->getTalentRank(talent->talentId); - // Get spell name for rank 1 spell - std::string name = gh->getSpellName(talent->rankSpells[0]); - if (name.empty()) name = "Talent " + std::to_string(talent->talentId); - - lua_pushstring(L, name.c_str()); // 1: name - lua_pushnil(L); // 2: iconTexture - lua_pushnumber(L, talent->row + 1); // 3: tier (1-indexed) - lua_pushnumber(L, talent->column + 1); // 4: column (1-indexed) - lua_pushnumber(L, rank); // 5: rank - lua_pushnumber(L, talent->maxRank); // 6: maxRank - lua_pushboolean(L, 0); // 7: isExceptional - lua_pushboolean(L, 1); // 8: available - return 8; -} - -// GetActiveTalentGroup() → 1 or 2 -static int lua_GetActiveTalentGroup(lua_State* L) { - auto* gh = getGameHandler(L); - lua_pushnumber(L, gh ? (gh->getActiveTalentSpec() + 1) : 1); - return 1; -} - -// --- Loot API --- - -// GetNumLootItems() → count -static int lua_GetNumLootItems(lua_State* L) { - auto* gh = getGameHandler(L); - if (!gh || !gh->isLootWindowOpen()) { lua_pushnumber(L, 0); return 1; } - lua_pushnumber(L, gh->getCurrentLoot().items.size()); - return 1; -} - -// GetLootSlotInfo(slot) → texture, name, quantity, quality, locked -static int lua_GetLootSlotInfo(lua_State* L) { - auto* gh = getGameHandler(L); - int slot = static_cast(luaL_checknumber(L, 1)); // 1-indexed - if (!gh || !gh->isLootWindowOpen()) { - lua_pushnil(L); return 1; - } - const auto& loot = gh->getCurrentLoot(); - if (slot < 1 || slot > static_cast(loot.items.size())) { - lua_pushnil(L); return 1; - } - const auto& item = loot.items[slot - 1]; - const auto* info = gh->getItemInfo(item.itemId); - - // texture (icon path from ItemDisplayInfo.dbc) - std::string icon; - if (info && info->displayInfoId != 0) { - icon = gh->getItemIconPath(info->displayInfoId); - } - if (!icon.empty()) lua_pushstring(L, icon.c_str()); - else lua_pushnil(L); - - // name - if (info && !info->name.empty()) lua_pushstring(L, info->name.c_str()); - else lua_pushstring(L, ("Item #" + std::to_string(item.itemId)).c_str()); - - lua_pushnumber(L, item.count); // quantity - lua_pushnumber(L, info ? info->quality : 1); // quality - lua_pushboolean(L, 0); // locked (not tracked) - return 5; -} - -// GetLootSlotLink(slot) → itemLink -static int lua_GetLootSlotLink(lua_State* L) { - auto* gh = getGameHandler(L); - int slot = static_cast(luaL_checknumber(L, 1)); - if (!gh || !gh->isLootWindowOpen()) { lua_pushnil(L); return 1; } - const auto& loot = gh->getCurrentLoot(); - if (slot < 1 || slot > static_cast(loot.items.size())) { - lua_pushnil(L); return 1; - } - const auto& item = loot.items[slot - 1]; - const auto* info = gh->getItemInfo(item.itemId); - if (!info || info->name.empty()) { lua_pushnil(L); return 1; } - static const char* kQH[] = {"9d9d9d","ffffff","1eff00","0070dd","a335ee","ff8000","e6cc80","e6cc80"}; - uint32_t qi = info->quality < 8 ? info->quality : 1u; - char link[256]; - snprintf(link, sizeof(link), "|cff%s|Hitem:%u:0:0:0:0:0:0:0|h[%s]|h|r", - kQH[qi], item.itemId, info->name.c_str()); - lua_pushstring(L, link); - return 1; -} - -// LootSlot(slot) — take item from loot -static int lua_LootSlot(lua_State* L) { - auto* gh = getGameHandler(L); - int slot = static_cast(luaL_checknumber(L, 1)); - if (!gh || !gh->isLootWindowOpen()) return 0; - const auto& loot = gh->getCurrentLoot(); - if (slot < 1 || slot > static_cast(loot.items.size())) return 0; - gh->lootItem(loot.items[slot - 1].slotIndex); - return 0; -} - -// CloseLoot() — close loot window -static int lua_CloseLoot(lua_State* L) { - auto* gh = getGameHandler(L); - if (gh) gh->closeLoot(); - return 0; -} - -// GetLootMethod() → "freeforall"|"roundrobin"|"master"|"group"|"needbeforegreed", partyLoot, raidLoot -static int lua_GetLootMethod(lua_State* L) { - auto* gh = getGameHandler(L); - if (!gh) { lua_pushstring(L, "freeforall"); lua_pushnumber(L, 0); lua_pushnumber(L, 0); return 3; } - const auto& pd = gh->getPartyData(); - const char* method = "freeforall"; - switch (pd.lootMethod) { - case 0: method = "freeforall"; break; - case 1: method = "roundrobin"; break; - case 2: method = "master"; break; - case 3: method = "group"; break; - case 4: method = "needbeforegreed"; break; - } - lua_pushstring(L, method); - lua_pushnumber(L, 0); // partyLootMaster (index) - lua_pushnumber(L, 0); // raidLootMaster (index) - return 3; -} - // --- Additional WoW API --- static int lua_UnitAffectingCombat(lua_State* L) { @@ -1995,18 +1100,7 @@ static int lua_UnitAffectingCombat(lua_State* L) { if (uidStr == "player") { lua_pushboolean(L, gh->isInCombat()); } else { - // Check UNIT_FLAG_IN_COMBAT (0x00080000) in UNIT_FIELD_FLAGS - uint64_t guid = resolveUnitGuid(gh, uidStr); - bool inCombat = false; - if (guid != 0) { - auto entity = gh->getEntityManager().getEntity(guid); - if (entity) { - uint32_t flags = entity->getField( - game::fieldIndex(game::UF::UNIT_FIELD_FLAGS)); - inCombat = (flags & 0x00080000) != 0; // UNIT_FLAG_IN_COMBAT - } - } - lua_pushboolean(L, inCombat); + lua_pushboolean(L, 0); } return 1; } @@ -2252,22 +1346,8 @@ static int lua_IsUsableSpell(lua_State* L) { float cd = gh->getSpellCooldown(spellId); bool onCooldown = (cd > 0.1f); - // 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 + lua_pushboolean(L, onCooldown ? 0 : 1); // usable (not on cooldown) + lua_pushboolean(L, 0); // noMana (can't determine without spell cost data) return 2; } @@ -2334,240 +1414,6 @@ static int lua_UnitClassification(lua_State* L) { return 1; } -// GetComboPoints("player"|"vehicle", "target") → number -static int lua_GetComboPoints(lua_State* L) { - auto* gh = getGameHandler(L); - lua_pushnumber(L, gh ? gh->getComboPoints() : 0); - return 1; -} - -// UnitReaction(unit, otherUnit) → 1-8 (hostile to exalted) -// Simplified: hostile=2, neutral=4, friendly=5 -static int lua_UnitReaction(lua_State* L) { - auto* gh = getGameHandler(L); - if (!gh) { lua_pushnil(L); return 1; } - const char* uid1 = luaL_checkstring(L, 1); - const char* uid2 = luaL_checkstring(L, 2); - auto* unit2 = resolveUnit(L, uid2); - if (!unit2) { lua_pushnil(L); return 1; } - // If unit2 is the player, always friendly to self - std::string u1(uid1); - for (char& c : u1) c = static_cast(std::tolower(static_cast(c))); - std::string u2(uid2); - for (char& c : u2) c = static_cast(std::tolower(static_cast(c))); - uint64_t g1 = resolveUnitGuid(gh, u1); - uint64_t g2 = resolveUnitGuid(gh, u2); - if (g1 == g2) { lua_pushnumber(L, 5); return 1; } // same unit = friendly - if (unit2->isHostile()) { - lua_pushnumber(L, 2); // hostile - } else { - lua_pushnumber(L, 5); // friendly - } - return 1; -} - -// UnitIsConnected(unit) → boolean -static int lua_UnitIsConnected(lua_State* L) { - auto* gh = getGameHandler(L); - if (!gh) { lua_pushboolean(L, 0); return 1; } - const char* uid = luaL_optstring(L, 1, "player"); - std::string uidStr(uid); - for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); - uint64_t guid = resolveUnitGuid(gh, uidStr); - if (guid == 0) { lua_pushboolean(L, 0); return 1; } - // Player is always connected - if (guid == gh->getPlayerGuid()) { lua_pushboolean(L, 1); return 1; } - // Check party/raid member online status - const auto& pd = gh->getPartyData(); - for (const auto& m : pd.members) { - if (m.guid == guid) { - lua_pushboolean(L, m.isOnline ? 1 : 0); - return 1; - } - } - // Non-party entities that exist are considered connected - auto entity = gh->getEntityManager().getEntity(guid); - lua_pushboolean(L, entity ? 1 : 0); - return 1; -} - -// HasAction(slot) → boolean (1-indexed slot) -static int lua_HasAction(lua_State* L) { - auto* gh = getGameHandler(L); - if (!gh) { lua_pushboolean(L, 0); return 1; } - int slot = static_cast(luaL_checknumber(L, 1)) - 1; // WoW uses 1-indexed slots - const auto& bar = gh->getActionBar(); - if (slot < 0 || slot >= static_cast(bar.size())) { - lua_pushboolean(L, 0); - return 1; - } - lua_pushboolean(L, !bar[slot].isEmpty()); - return 1; -} - -// GetActionTexture(slot) → texturePath or nil -static int lua_GetActionTexture(lua_State* L) { - auto* gh = getGameHandler(L); - if (!gh) { lua_pushnil(L); return 1; } - int slot = static_cast(luaL_checknumber(L, 1)) - 1; - const auto& bar = gh->getActionBar(); - if (slot < 0 || slot >= static_cast(bar.size()) || bar[slot].isEmpty()) { - lua_pushnil(L); - return 1; - } - const auto& action = bar[slot]; - if (action.type == game::ActionBarSlot::SPELL) { - std::string icon = gh->getSpellIconPath(action.id); - if (!icon.empty()) { - lua_pushstring(L, icon.c_str()); - return 1; - } - } else if (action.type == game::ActionBarSlot::ITEM && action.id != 0) { - const auto* info = gh->getItemInfo(action.id); - if (info && info->displayInfoId != 0) { - std::string icon = gh->getItemIconPath(info->displayInfoId); - if (!icon.empty()) { - lua_pushstring(L, icon.c_str()); - return 1; - } - } - } - lua_pushnil(L); - return 1; -} - -// IsCurrentAction(slot) → boolean -static int lua_IsCurrentAction(lua_State* L) { - // Currently no "active action" tracking; return false - (void)L; - lua_pushboolean(L, 0); - return 1; -} - -// IsUsableAction(slot) → usable, notEnoughMana -static int lua_IsUsableAction(lua_State* L) { - auto* gh = getGameHandler(L); - if (!gh) { lua_pushboolean(L, 0); lua_pushboolean(L, 0); return 2; } - int slot = static_cast(luaL_checknumber(L, 1)) - 1; - const auto& bar = gh->getActionBar(); - if (slot < 0 || slot >= static_cast(bar.size()) || bar[slot].isEmpty()) { - lua_pushboolean(L, 0); - lua_pushboolean(L, 0); - return 2; - } - const auto& action = bar[slot]; - bool usable = action.isReady(); - bool noMana = false; - if (action.type == game::ActionBarSlot::SPELL) { - usable = usable && gh->getKnownSpells().count(action.id); - // Check power cost - if (usable && action.id != 0) { - auto spellData = gh->getSpellData(action.id); - if (spellData.manaCost > 0) { - auto pe = gh->getEntityManager().getEntity(gh->getPlayerGuid()); - if (pe) { - auto* unit = dynamic_cast(pe.get()); - if (unit && unit->getPower() < spellData.manaCost) { - noMana = true; - usable = false; - } - } - } - } - } - lua_pushboolean(L, usable ? 1 : 0); - lua_pushboolean(L, noMana ? 1 : 0); - return 2; -} - -// GetActionCooldown(slot) → start, duration, enable -static int lua_GetActionCooldown(lua_State* L) { - auto* gh = getGameHandler(L); - if (!gh) { lua_pushnumber(L, 0); lua_pushnumber(L, 0); lua_pushnumber(L, 1); return 3; } - int slot = static_cast(luaL_checknumber(L, 1)) - 1; - const auto& bar = gh->getActionBar(); - if (slot < 0 || slot >= static_cast(bar.size()) || bar[slot].isEmpty()) { - lua_pushnumber(L, 0); - lua_pushnumber(L, 0); - lua_pushnumber(L, 1); - return 3; - } - const auto& action = bar[slot]; - if (action.cooldownRemaining > 0.0f) { - // WoW returns GetTime()-based start time; approximate - double now = 0; - lua_getglobal(L, "GetTime"); - if (lua_isfunction(L, -1)) { - lua_call(L, 0, 1); - now = lua_tonumber(L, -1); - lua_pop(L, 1); - } else { - lua_pop(L, 1); - } - double start = now - (action.cooldownTotal - action.cooldownRemaining); - lua_pushnumber(L, start); - lua_pushnumber(L, action.cooldownTotal); - lua_pushnumber(L, 1); - } else { - lua_pushnumber(L, 0); - lua_pushnumber(L, 0); - lua_pushnumber(L, 1); - } - return 3; -} - -// UseAction(slot, checkCursor, onSelf) — activate action bar slot (1-indexed) -static int lua_UseAction(lua_State* L) { - auto* gh = getGameHandler(L); - if (!gh) return 0; - int slot = static_cast(luaL_checknumber(L, 1)) - 1; - const auto& bar = gh->getActionBar(); - if (slot < 0 || slot >= static_cast(bar.size()) || bar[slot].isEmpty()) return 0; - const auto& action = bar[slot]; - if (action.type == game::ActionBarSlot::SPELL && action.isReady()) { - uint64_t target = gh->hasTarget() ? gh->getTargetGuid() : 0; - gh->castSpell(action.id, target); - } else if (action.type == game::ActionBarSlot::ITEM && action.id != 0) { - gh->useItemById(action.id); - } - // Macro execution requires GameScreen context; not available from pure Lua API - return 0; -} - -// CancelUnitBuff(unit, index) — cancel a buff by index (1-indexed) -static int lua_CancelUnitBuff(lua_State* L) { - auto* gh = getGameHandler(L); - if (!gh) return 0; - const char* uid = luaL_optstring(L, 1, "player"); - std::string uidStr(uid); - for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); - if (uidStr != "player") return 0; // Can only cancel own buffs - int index = static_cast(luaL_checknumber(L, 2)); - const auto& auras = gh->getPlayerAuras(); - // Find the Nth buff (non-debuff) - int buffCount = 0; - for (const auto& a : auras) { - if (a.isEmpty()) continue; - if ((a.flags & 0x80) != 0) continue; // skip debuffs - if (++buffCount == index) { - gh->cancelAura(a.spellId); - break; - } - } - return 0; -} - -// CastSpellByID(spellId) — cast spell by numeric ID -static int lua_CastSpellByID(lua_State* L) { - auto* gh = getGameHandler(L); - if (!gh) return 0; - uint32_t spellId = static_cast(luaL_checknumber(L, 1)); - if (spellId == 0) return 0; - uint64_t target = gh->hasTarget() ? gh->getTargetGuid() : 0; - gh->castSpell(spellId, target); - return 0; -} - // --- Frame System --- // Minimal WoW-compatible frame objects with RegisterEvent/SetScript/GetScript. // Frames are Lua tables with a metatable that provides methods. @@ -2698,193 +1544,6 @@ static int lua_Frame_IsShown(lua_State* L) { return 1; } -// Frame method: frame:CreateTexture(name, layer) → texture stub -static int lua_Frame_CreateTexture(lua_State* L) { - lua_newtable(L); - // Add noop methods for common texture operations - luaL_dostring(L, - "return function(t) " - "function t:SetTexture() end " - "function t:SetTexCoord() end " - "function t:SetVertexColor() end " - "function t:SetAllPoints() end " - "function t:SetPoint() end " - "function t:SetSize() end " - "function t:SetWidth() end " - "function t:SetHeight() end " - "function t:Show() end " - "function t:Hide() end " - "function t:SetAlpha() end " - "function t:GetTexture() return '' end " - "function t:SetDesaturated() end " - "function t:SetBlendMode() end " - "function t:SetDrawLayer() end " - "end"); - lua_pushvalue(L, -2); // push the table - lua_call(L, 1, 0); // call the function with the table - return 1; -} - -// Frame method: frame:CreateFontString(name, layer, template) → fontstring stub -static int lua_Frame_CreateFontString(lua_State* L) { - lua_newtable(L); - luaL_dostring(L, - "return function(fs) " - "fs._text = '' " - "function fs:SetText(t) self._text = t or '' end " - "function fs:GetText() return self._text end " - "function fs:SetFont() end " - "function fs:SetFontObject() end " - "function fs:SetTextColor() end " - "function fs:SetJustifyH() end " - "function fs:SetJustifyV() end " - "function fs:SetPoint() end " - "function fs:SetAllPoints() end " - "function fs:Show() end " - "function fs:Hide() end " - "function fs:SetAlpha() end " - "function fs:GetStringWidth() return 0 end " - "function fs:GetStringHeight() return 0 end " - "function fs:SetWordWrap() end " - "function fs:SetNonSpaceWrap() end " - "function fs:SetMaxLines() end " - "function fs:SetShadowOffset() end " - "function fs:SetShadowColor() end " - "function fs:SetWidth() end " - "function fs:SetHeight() end " - "end"); - lua_pushvalue(L, -2); - lua_call(L, 1, 0); - return 1; -} - -// GetFramerate() → fps -static int lua_GetFramerate(lua_State* L) { - lua_pushnumber(L, static_cast(ImGui::GetIO().Framerate)); - return 1; -} - -// GetCursorPosition() → x, y (screen coordinates, origin top-left) -static int lua_GetCursorPosition(lua_State* L) { - const auto& io = ImGui::GetIO(); - lua_pushnumber(L, io.MousePos.x); - lua_pushnumber(L, io.MousePos.y); - return 2; -} - -// GetScreenWidth() → width -static int lua_GetScreenWidth(lua_State* L) { - auto* window = core::Application::getInstance().getWindow(); - lua_pushnumber(L, window ? window->getWidth() : 1920); - return 1; -} - -// GetScreenHeight() → height -static int lua_GetScreenHeight(lua_State* L) { - auto* window = core::Application::getInstance().getWindow(); - lua_pushnumber(L, window ? window->getHeight() : 1080); - return 1; -} - -// Frame methods: SetPoint, SetSize, SetWidth, SetHeight, GetWidth, GetHeight, GetCenter, SetAlpha, GetAlpha -static int lua_Frame_SetPoint(lua_State* L) { - luaL_checktype(L, 1, LUA_TTABLE); - const char* point = luaL_optstring(L, 2, "CENTER"); - // Store point info in frame table - lua_pushstring(L, point); - lua_setfield(L, 1, "__point"); - // Optional x/y offsets (args 4,5 if relativeTo is given, or 3,4 if not) - double xOfs = 0, yOfs = 0; - if (lua_isnumber(L, 4)) { xOfs = lua_tonumber(L, 4); yOfs = lua_tonumber(L, 5); } - else if (lua_isnumber(L, 3)) { xOfs = lua_tonumber(L, 3); yOfs = lua_tonumber(L, 4); } - lua_pushnumber(L, xOfs); - lua_setfield(L, 1, "__xOfs"); - lua_pushnumber(L, yOfs); - lua_setfield(L, 1, "__yOfs"); - return 0; -} - -static int lua_Frame_SetSize(lua_State* L) { - luaL_checktype(L, 1, LUA_TTABLE); - double w = luaL_optnumber(L, 2, 0); - double h = luaL_optnumber(L, 3, 0); - lua_pushnumber(L, w); - lua_setfield(L, 1, "__width"); - lua_pushnumber(L, h); - lua_setfield(L, 1, "__height"); - return 0; -} - -static int lua_Frame_SetWidth(lua_State* L) { - luaL_checktype(L, 1, LUA_TTABLE); - lua_pushnumber(L, luaL_checknumber(L, 2)); - lua_setfield(L, 1, "__width"); - return 0; -} - -static int lua_Frame_SetHeight(lua_State* L) { - luaL_checktype(L, 1, LUA_TTABLE); - lua_pushnumber(L, luaL_checknumber(L, 2)); - lua_setfield(L, 1, "__height"); - return 0; -} - -static int lua_Frame_GetWidth(lua_State* L) { - luaL_checktype(L, 1, LUA_TTABLE); - lua_getfield(L, 1, "__width"); - if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_pushnumber(L, 0); } - return 1; -} - -static int lua_Frame_GetHeight(lua_State* L) { - luaL_checktype(L, 1, LUA_TTABLE); - lua_getfield(L, 1, "__height"); - if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_pushnumber(L, 0); } - return 1; -} - -static int lua_Frame_GetCenter(lua_State* L) { - luaL_checktype(L, 1, LUA_TTABLE); - lua_getfield(L, 1, "__xOfs"); - double x = lua_isnumber(L, -1) ? lua_tonumber(L, -1) : 0; - lua_pop(L, 1); - lua_getfield(L, 1, "__yOfs"); - double y = lua_isnumber(L, -1) ? lua_tonumber(L, -1) : 0; - lua_pop(L, 1); - lua_pushnumber(L, x); - lua_pushnumber(L, y); - return 2; -} - -static int lua_Frame_SetAlpha(lua_State* L) { - luaL_checktype(L, 1, LUA_TTABLE); - lua_pushnumber(L, luaL_checknumber(L, 2)); - lua_setfield(L, 1, "__alpha"); - return 0; -} - -static int lua_Frame_GetAlpha(lua_State* L) { - luaL_checktype(L, 1, LUA_TTABLE); - lua_getfield(L, 1, "__alpha"); - if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_pushnumber(L, 1.0); } - return 1; -} - -static int lua_Frame_SetParent(lua_State* L) { - luaL_checktype(L, 1, LUA_TTABLE); - if (lua_istable(L, 2) || lua_isnil(L, 2)) { - lua_pushvalue(L, 2); - lua_setfield(L, 1, "__parent"); - } - return 0; -} - -static int lua_Frame_GetParent(lua_State* L) { - luaL_checktype(L, 1, LUA_TTABLE); - lua_getfield(L, 1, "__parent"); - return 1; -} - // CreateFrame(frameType, name, parent, template) static int lua_CreateFrame(lua_State* L) { const char* frameType = luaL_optstring(L, 1, "Frame"); @@ -3067,40 +1726,19 @@ void LuaEngine::registerCoreAPI() { {"UnitHealthMax", lua_UnitHealthMax}, {"UnitPower", lua_UnitPower}, {"UnitPowerMax", lua_UnitPowerMax}, - {"UnitMana", lua_UnitPower}, - {"UnitManaMax", lua_UnitPowerMax}, {"UnitLevel", lua_UnitLevel}, {"UnitExists", lua_UnitExists}, {"UnitIsDead", lua_UnitIsDead}, - {"UnitIsGhost", lua_UnitIsGhost}, - {"UnitIsDeadOrGhost", lua_UnitIsDeadOrGhost}, - {"UnitIsAFK", lua_UnitIsAFK}, - {"UnitIsDND", lua_UnitIsDND}, - {"UnitPlayerControlled", lua_UnitPlayerControlled}, - {"UnitSex", lua_UnitSex}, {"UnitClass", lua_UnitClass}, {"GetMoney", lua_GetMoney}, {"IsInGroup", lua_IsInGroup}, {"IsInRaid", lua_IsInRaid}, {"GetPlayerMapPosition", lua_GetPlayerMapPosition}, {"SendChatMessage", lua_SendChatMessage}, - {"SendAddonMessage", lua_SendAddonMessage}, - {"RegisterAddonMessagePrefix", lua_RegisterAddonMessagePrefix}, - {"IsAddonMessagePrefixRegistered", lua_IsAddonMessagePrefixRegistered}, {"CastSpellByName", lua_CastSpellByName}, {"IsSpellKnown", lua_IsSpellKnown}, {"GetSpellCooldown", lua_GetSpellCooldown}, {"HasTarget", lua_HasTarget}, - {"TargetUnit", lua_TargetUnit}, - {"ClearTarget", lua_ClearTarget}, - {"FocusUnit", lua_FocusUnit}, - {"ClearFocus", lua_ClearFocus}, - {"AssistUnit", lua_AssistUnit}, - {"TargetLastTarget", lua_TargetLastTarget}, - {"TargetNearestEnemy", lua_TargetNearestEnemy}, - {"TargetNearestFriend", lua_TargetNearestFriend}, - {"GetRaidTargetIndex", lua_GetRaidTargetIndex}, - {"SetRaidTarget", lua_SetRaidTarget}, {"UnitRace", lua_UnitRace}, {"UnitPowerType", lua_UnitPowerType}, {"GetNumGroupMembers", lua_GetNumGroupMembers}, @@ -3112,7 +1750,6 @@ void LuaEngine::registerCoreAPI() { {"UnitAura", lua_UnitAuraGeneric}, {"GetNumAddOns", lua_GetNumAddOns}, {"GetAddOnInfo", lua_GetAddOnInfo}, - {"GetAddOnMetadata", lua_GetAddOnMetadata}, {"GetSpellInfo", lua_GetSpellInfo}, {"GetSpellTexture", lua_GetSpellTexture}, {"GetItemInfo", lua_GetItemInfo}, @@ -3170,46 +1807,6 @@ void LuaEngine::registerCoreAPI() { {"GetQuestLogTitle", lua_GetQuestLogTitle}, {"GetQuestLogQuestText", lua_GetQuestLogQuestText}, {"IsQuestComplete", lua_IsQuestComplete}, - // Skill line API - {"GetNumSkillLines", lua_GetNumSkillLines}, - {"GetSkillLineInfo", lua_GetSkillLineInfo}, - // Talent API - {"GetNumTalentTabs", lua_GetNumTalentTabs}, - {"GetTalentTabInfo", lua_GetTalentTabInfo}, - {"GetNumTalents", lua_GetNumTalents}, - {"GetTalentInfo", lua_GetTalentInfo}, - {"GetActiveTalentGroup", lua_GetActiveTalentGroup}, - // Friends/ignore API - // Guild API - {"IsInGuild", lua_IsInGuild}, - {"GetGuildInfo", lua_GetGuildInfoFunc}, - {"GetNumGuildMembers", lua_GetNumGuildMembers}, - {"GetGuildRosterInfo", lua_GetGuildRosterInfo}, - {"GetGuildRosterMOTD", lua_GetGuildRosterMOTD}, - {"GetNumFriends", lua_GetNumFriends}, - {"GetFriendInfo", lua_GetFriendInfo}, - {"GetNumIgnores", lua_GetNumIgnores}, - {"GetIgnoreName", lua_GetIgnoreName}, - // Reaction/connection queries - {"UnitReaction", lua_UnitReaction}, - {"UnitIsConnected", lua_UnitIsConnected}, - {"GetComboPoints", lua_GetComboPoints}, - // Action bar API - {"HasAction", lua_HasAction}, - {"GetActionTexture", lua_GetActionTexture}, - {"IsCurrentAction", lua_IsCurrentAction}, - {"IsUsableAction", lua_IsUsableAction}, - {"GetActionCooldown", lua_GetActionCooldown}, - {"UseAction", lua_UseAction}, - {"CancelUnitBuff", lua_CancelUnitBuff}, - {"CastSpellByID", lua_CastSpellByID}, - // Loot API - {"GetNumLootItems", lua_GetNumLootItems}, - {"GetLootSlotInfo", lua_GetLootSlotInfo}, - {"GetLootSlotLink", lua_GetLootSlotLink}, - {"LootSlot", lua_LootSlot}, - {"CloseLoot", lua_CloseLoot}, - {"GetLootMethod", lua_GetLootMethod}, // Utilities {"strsplit", lua_strsplit}, {"strtrim", lua_strtrim}, @@ -3255,19 +1852,6 @@ void LuaEngine::registerCoreAPI() { {"Hide", lua_Frame_Hide}, {"IsShown", lua_Frame_IsShown}, {"IsVisible", lua_Frame_IsShown}, // alias - {"SetPoint", lua_Frame_SetPoint}, - {"SetSize", lua_Frame_SetSize}, - {"SetWidth", lua_Frame_SetWidth}, - {"SetHeight", lua_Frame_SetHeight}, - {"GetWidth", lua_Frame_GetWidth}, - {"GetHeight", lua_Frame_GetHeight}, - {"GetCenter", lua_Frame_GetCenter}, - {"SetAlpha", lua_Frame_SetAlpha}, - {"GetAlpha", lua_Frame_GetAlpha}, - {"SetParent", lua_Frame_SetParent}, - {"GetParent", lua_Frame_GetParent}, - {"CreateTexture", lua_Frame_CreateTexture}, - {"CreateFontString", lua_Frame_CreateFontString}, {nullptr, nullptr} }; for (const luaL_Reg* r = frameMethods; r->name; r++) { @@ -3276,71 +1860,10 @@ void LuaEngine::registerCoreAPI() { } lua_setglobal(L_, "__WoweeFrameMT"); - // Add commonly called no-op frame methods to prevent addon errors - luaL_dostring(L_, - "local mt = __WoweeFrameMT\n" - "function mt:SetFrameLevel(level) self.__frameLevel = level end\n" - "function mt:GetFrameLevel() return self.__frameLevel or 1 end\n" - "function mt:SetFrameStrata(strata) self.__strata = strata end\n" - "function mt:GetFrameStrata() return self.__strata or 'MEDIUM' end\n" - "function mt:EnableMouse(enable) end\n" - "function mt:EnableMouseWheel(enable) end\n" - "function mt:SetMovable(movable) end\n" - "function mt:SetResizable(resizable) end\n" - "function mt:RegisterForDrag(...) end\n" - "function mt:SetClampedToScreen(clamped) end\n" - "function mt:SetBackdrop(backdrop) end\n" - "function mt:SetBackdropColor(...) end\n" - "function mt:SetBackdropBorderColor(...) end\n" - "function mt:ClearAllPoints() end\n" - "function mt:SetID(id) self.__id = id end\n" - "function mt:GetID() return self.__id or 0 end\n" - "function mt:SetScale(scale) self.__scale = scale end\n" - "function mt:GetScale() return self.__scale or 1.0 end\n" - "function mt:GetEffectiveScale() return self.__scale or 1.0 end\n" - "function mt:SetToplevel(top) end\n" - "function mt:Raise() end\n" - "function mt:Lower() end\n" - "function mt:GetLeft() return 0 end\n" - "function mt:GetRight() return 0 end\n" - "function mt:GetTop() return 0 end\n" - "function mt:GetBottom() return 0 end\n" - "function mt:GetNumPoints() return 0 end\n" - "function mt:GetPoint(n) return 'CENTER', nil, 'CENTER', 0, 0 end\n" - "function mt:SetHitRectInsets(...) end\n" - "function mt:RegisterForClicks(...) end\n" - "function mt:SetAttribute(name, value) self['attr_'..name] = value end\n" - "function mt:GetAttribute(name) return self['attr_'..name] end\n" - "function mt:HookScript(scriptType, fn)\n" - " local orig = self.__scripts and self.__scripts[scriptType]\n" - " if orig then\n" - " self:SetScript(scriptType, function(...) orig(...); fn(...) end)\n" - " else\n" - " self:SetScript(scriptType, fn)\n" - " end\n" - "end\n" - "function mt:SetMinResize(...) end\n" - "function mt:SetMaxResize(...) end\n" - "function mt:StartMoving() end\n" - "function mt:StopMovingOrSizing() end\n" - "function mt:IsMouseOver() return false end\n" - "function mt:GetObjectType() return 'Frame' end\n" - ); - // CreateFrame function lua_pushcfunction(L_, lua_CreateFrame); lua_setglobal(L_, "CreateFrame"); - // Cursor/screen/FPS functions - lua_pushcfunction(L_, lua_GetCursorPosition); - lua_setglobal(L_, "GetCursorPosition"); - lua_pushcfunction(L_, lua_GetScreenWidth); - lua_setglobal(L_, "GetScreenWidth"); - lua_pushcfunction(L_, lua_GetScreenHeight); - lua_setglobal(L_, "GetScreenHeight"); - lua_pushcfunction(L_, lua_GetFramerate); - lua_setglobal(L_, "GetFramerate"); - // Frame event dispatch table lua_newtable(L_); lua_setglobal(L_, "__WoweeFrameEvents"); @@ -3424,66 +1947,6 @@ void LuaEngine::registerCoreAPI() { "end\n" ); - // LibStub — universal library version management used by Ace3 and virtually all addon libs. - // This is the standard WoW LibStub implementation that addons embed/expect globally. - luaL_dostring(L_, - "local LibStub = LibStub or {}\n" - "LibStub.libs = LibStub.libs or {}\n" - "LibStub.minors = LibStub.minors or {}\n" - "function LibStub:NewLibrary(major, minor)\n" - " assert(type(major) == 'string', 'LibStub:NewLibrary: bad argument #1 (string expected)')\n" - " minor = assert(tonumber(minor or (type(minor) == 'string' and minor:match('(%d+)'))), 'LibStub:NewLibrary: bad argument #2 (number expected)')\n" - " local oldMinor = self.minors[major]\n" - " if oldMinor and oldMinor >= minor then return nil end\n" - " local lib = self.libs[major] or {}\n" - " self.libs[major] = lib\n" - " self.minors[major] = minor\n" - " return lib, oldMinor\n" - "end\n" - "function LibStub:GetLibrary(major, silent)\n" - " if not self.libs[major] and not silent then\n" - " error('Cannot find a library instance of \"' .. tostring(major) .. '\".')\n" - " end\n" - " return self.libs[major], self.minors[major]\n" - "end\n" - "function LibStub:IterateLibraries() return pairs(self.libs) end\n" - "setmetatable(LibStub, { __call = LibStub.GetLibrary })\n" - "_G['LibStub'] = LibStub\n" - ); - - // CallbackHandler-1.0 — minimal implementation for Ace3-based addons - luaL_dostring(L_, - "if LibStub then\n" - " local CBH = LibStub:NewLibrary('CallbackHandler-1.0', 7)\n" - " if CBH then\n" - " CBH.mixins = { 'RegisterCallback', 'UnregisterCallback', 'UnregisterAllCallbacks', 'Fire' }\n" - " function CBH:New(target, regName, unregName, unregAllName, onUsed)\n" - " local registry = setmetatable({}, { __index = CBH })\n" - " registry.callbacks = {}\n" - " target = target or {}\n" - " target[regName or 'RegisterCallback'] = function(self, event, method, ...)\n" - " if not registry.callbacks[event] then registry.callbacks[event] = {} end\n" - " local handler = type(method) == 'function' and method or self[method]\n" - " registry.callbacks[event][self] = handler\n" - " end\n" - " target[unregName or 'UnregisterCallback'] = function(self, event)\n" - " if registry.callbacks[event] then registry.callbacks[event][self] = nil end\n" - " end\n" - " target[unregAllName or 'UnregisterAllCallbacks'] = function(self)\n" - " for event, handlers in pairs(registry.callbacks) do handlers[self] = nil end\n" - " end\n" - " registry.Fire = function(self, event, ...)\n" - " if not self.callbacks[event] then return end\n" - " for obj, handler in pairs(self.callbacks[event]) do\n" - " handler(obj, event, ...)\n" - " end\n" - " end\n" - " return registry\n" - " end\n" - " end\n" - "end\n" - ); - // Noop stubs for commonly called functions that don't need implementation luaL_dostring(L_, "function SetDesaturation() end\n" @@ -3494,33 +1957,6 @@ void LuaEngine::registerCoreAPI() { "function UIParent_OnEvent() end\n" "UIParent = CreateFrame('Frame', 'UIParent')\n" "WorldFrame = CreateFrame('Frame', 'WorldFrame')\n" - // GameTooltip: global tooltip frame used by virtually all addons - "GameTooltip = CreateFrame('Frame', 'GameTooltip')\n" - "GameTooltip.__lines = {}\n" - "function GameTooltip:SetOwner(owner, anchor) self.__owner = owner; self.__anchor = anchor end\n" - "function GameTooltip:ClearLines() self.__lines = {} end\n" - "function GameTooltip:AddLine(text, r, g, b, wrap) table.insert(self.__lines, {text=text or '',r=r,g=g,b=b}) end\n" - "function GameTooltip:AddDoubleLine(l, r, lr, lg, lb, rr, rg, rb) table.insert(self.__lines, {text=(l or '')..' '..(r or '')}) end\n" - "function GameTooltip:SetText(text, r, g, b) self.__lines = {{text=text or '',r=r,g=g,b=b}} end\n" - "function GameTooltip:GetItem() return nil end\n" - "function GameTooltip:GetSpell() return nil end\n" - "function GameTooltip:GetUnit() return nil end\n" - "function GameTooltip:NumLines() return #self.__lines end\n" - "function GameTooltip:GetText() return self.__lines[1] and self.__lines[1].text or '' end\n" - "function GameTooltip:SetUnitBuff(...) end\n" - "function GameTooltip:SetUnitDebuff(...) end\n" - "function GameTooltip:SetHyperlink(...) end\n" - "function GameTooltip:SetInventoryItem(...) end\n" - "function GameTooltip:SetBagItem(...) end\n" - "function GameTooltip:SetSpellByID(...) end\n" - "function GameTooltip:SetAction(...) end\n" - "function GameTooltip:FadeOut() end\n" - "function GameTooltip:SetFrameStrata(...) end\n" - "function GameTooltip:SetClampedToScreen(...) end\n" - "function GameTooltip:IsOwned(f) return self.__owner == f end\n" - // ShoppingTooltip: used by comparison tooltips - "ShoppingTooltip1 = CreateFrame('Frame', 'ShoppingTooltip1')\n" - "ShoppingTooltip2 = CreateFrame('Frame', 'ShoppingTooltip2')\n" // Error handling stubs (used by many addons) "local _errorHandler = function(err) return err end\n" "function geterrorhandler() return _errorHandler end\n" @@ -3535,13 +1971,16 @@ void LuaEngine::registerCoreAPI() { "function GetCVarBool(name) return _cvars[name] == '1' end\n" "function SetCVar(name, value) _cvars[name] = tostring(value) end\n" // Misc compatibility stubs - // GetScreenWidth, GetScreenHeight, GetNumLootItems are now C functions - // GetFramerate is now a C function + "function GetScreenWidth() return 1920 end\n" + "function GetScreenHeight() return 1080 end\n" + "function GetFramerate() return 60 end\n" "function GetNetStats() return 0, 0, 0, 0 end\n" "function IsLoggedIn() return true end\n" + // IsMounted, IsFlying, IsSwimming, IsResting, IsFalling, IsStealthed + // are now C functions registered in registerCoreAPI() with real game state + "function GetNumLootItems() return 0 end\n" "function StaticPopup_Show() end\n" "function StaticPopup_Hide() end\n" - // CreateTexture/CreateFontString are now C frame methods in the metatable "RAID_CLASS_COLORS = {\n" " WARRIOR={r=0.78,g=0.61,b=0.43}, PALADIN={r=0.96,g=0.55,b=0.73},\n" " HUNTER={r=0.67,g=0.83,b=0.45}, ROGUE={r=1.0,g=0.96,b=0.41},\n" @@ -3564,157 +2003,6 @@ void LuaEngine::registerCoreAPI() { "end\n" "GetCoinText = GetCoinTextureString\n" ); - - // UIDropDownMenu framework — minimal compat for addons using dropdown menus - luaL_dostring(L_, - "UIDROPDOWNMENU_MENU_LEVEL = 1\n" - "UIDROPDOWNMENU_MENU_VALUE = nil\n" - "UIDROPDOWNMENU_OPEN_MENU = nil\n" - "local _ddMenuList = {}\n" - "function UIDropDownMenu_Initialize(frame, initFunc, displayMode, level, menuList)\n" - " if frame then frame.__initFunc = initFunc end\n" - "end\n" - "function UIDropDownMenu_CreateInfo() return {} end\n" - "function UIDropDownMenu_AddButton(info, level) table.insert(_ddMenuList, info) end\n" - "function UIDropDownMenu_SetWidth(frame, width) end\n" - "function UIDropDownMenu_SetButtonWidth(frame, width) end\n" - "function UIDropDownMenu_SetText(frame, text)\n" - " if frame then frame.__text = text end\n" - "end\n" - "function UIDropDownMenu_GetText(frame)\n" - " return frame and frame.__text or ''\n" - "end\n" - "function UIDropDownMenu_SetSelectedID(frame, id) end\n" - "function UIDropDownMenu_SetSelectedValue(frame, value) end\n" - "function UIDropDownMenu_GetSelectedID(frame) return 1 end\n" - "function UIDropDownMenu_GetSelectedValue(frame) return nil end\n" - "function UIDropDownMenu_JustifyText(frame, justify) end\n" - "function UIDropDownMenu_EnableDropDown(frame) end\n" - "function UIDropDownMenu_DisableDropDown(frame) end\n" - "function CloseDropDownMenus() end\n" - "function ToggleDropDownMenu(level, value, frame, anchor, xOfs, yOfs) end\n" - ); - - // UISpecialFrames: frames in this list close on Escape key - luaL_dostring(L_, - "UISpecialFrames = {}\n" - // Font object stubs — addons reference these for CreateFontString templates - "GameFontNormal = {}\n" - "GameFontNormalSmall = {}\n" - "GameFontNormalLarge = {}\n" - "GameFontHighlight = {}\n" - "GameFontHighlightSmall = {}\n" - "GameFontHighlightLarge = {}\n" - "GameFontDisable = {}\n" - "GameFontDisableSmall = {}\n" - "GameFontWhite = {}\n" - "GameFontRed = {}\n" - "GameFontGreen = {}\n" - "NumberFontNormal = {}\n" - "ChatFontNormal = {}\n" - "SystemFont = {}\n" - // InterfaceOptionsFrame: addons register settings panels here - "InterfaceOptionsFrame = CreateFrame('Frame', 'InterfaceOptionsFrame')\n" - "InterfaceOptionsFramePanelContainer = CreateFrame('Frame', 'InterfaceOptionsFramePanelContainer')\n" - "function InterfaceOptions_AddCategory(panel) end\n" - "function InterfaceOptionsFrame_OpenToCategory(panel) end\n" - // Commonly expected global tables - "SLASH_RELOAD1 = '/reload'\n" - "SLASH_RELOADUI1 = '/reloadui'\n" - "GRAY_FONT_COLOR = {r=0.5,g=0.5,b=0.5}\n" - "NORMAL_FONT_COLOR = {r=1.0,g=0.82,b=0.0}\n" - "HIGHLIGHT_FONT_COLOR = {r=1.0,g=1.0,b=1.0}\n" - "GREEN_FONT_COLOR = {r=0.1,g=1.0,b=0.1}\n" - "RED_FONT_COLOR = {r=1.0,g=0.1,b=0.1}\n" - // C_ChatInfo — addon message prefix API used by some addons - "C_ChatInfo = C_ChatInfo or {}\n" - "C_ChatInfo.RegisterAddonMessagePrefix = RegisterAddonMessagePrefix\n" - "C_ChatInfo.IsAddonMessagePrefixRegistered = IsAddonMessagePrefixRegistered\n" - "C_ChatInfo.SendAddonMessage = SendAddonMessage\n" - ); - - // Action bar constants and functions used by action bar addons - luaL_dostring(L_, - "NUM_ACTIONBAR_BUTTONS = 12\n" - "NUM_ACTIONBAR_PAGES = 6\n" - "ACTION_BUTTON_SHOW_GRID_REASON_CVAR = 1\n" - "ACTION_BUTTON_SHOW_GRID_REASON_EVENT = 2\n" - // Action bar page tracking - "local _actionBarPage = 1\n" - "function GetActionBarPage() return _actionBarPage end\n" - "function ChangeActionBarPage(page) _actionBarPage = page end\n" - "function GetBonusBarOffset() return 0 end\n" - // Action type query - "function GetActionText(slot) return nil end\n" - "function GetActionCount(slot) return 0 end\n" - // Binding functions - "function GetBindingKey(action) return nil end\n" - "function GetBindingAction(key) return nil end\n" - "function SetBinding(key, action) end\n" - "function SaveBindings(which) end\n" - "function GetCurrentBindingSet() return 1 end\n" - // Macro functions - "function GetNumMacros() return 0, 0 end\n" - "function GetMacroInfo(id) return nil end\n" - "function GetMacroBody(id) return nil end\n" - "function GetMacroIndexByName(name) return 0 end\n" - // Stance bar - "function GetNumShapeshiftForms() return 0 end\n" - "function GetShapeshiftFormInfo(index) return nil, nil, nil, nil end\n" - // Pet action bar - "NUM_PET_ACTION_SLOTS = 10\n" - "function GetPetActionInfo(slot) return nil end\n" - "function GetPetActionsUsable() return false end\n" - ); - - // WoW table/string utility functions used by many addons - luaL_dostring(L_, - // Table utilities - "function tContains(tbl, item)\n" - " for _, v in pairs(tbl) do if v == item then return true end end\n" - " return false\n" - "end\n" - "function tInvert(tbl)\n" - " local inv = {}\n" - " for k, v in pairs(tbl) do inv[v] = k end\n" - " return inv\n" - "end\n" - "function CopyTable(src)\n" - " if type(src) ~= 'table' then return src end\n" - " local copy = {}\n" - " for k, v in pairs(src) do copy[k] = CopyTable(v) end\n" - " return setmetatable(copy, getmetatable(src))\n" - "end\n" - "function tDeleteItem(tbl, item)\n" - " for i = #tbl, 1, -1 do if tbl[i] == item then table.remove(tbl, i) end end\n" - "end\n" - // String utilities (WoW globals that alias Lua string functions) - "strupper = string.upper\n" - "strlower = string.lower\n" - "strfind = string.find\n" - "strsub = string.sub\n" - "strlen = string.len\n" - "strrep = string.rep\n" - "strbyte = string.byte\n" - "strchar = string.char\n" - "strrev = string.reverse\n" - "gsub = string.gsub\n" - "gmatch = string.gmatch\n" - "strjoin = function(delim, ...)\n" - " return table.concat({...}, delim)\n" - "end\n" - // Math utilities - "function Clamp(val, lo, hi) return math.min(math.max(val, lo), hi) end\n" - "function Round(val) return math.floor(val + 0.5) end\n" - // Bit operations (WoW provides these; Lua 5.1 doesn't have native bit ops) - "bit = bit or {}\n" - "bit.band = bit.band or function(a, b) local r,m=0,1 for i=0,31 do if a%2==1 and b%2==1 then r=r+m end a=math.floor(a/2) b=math.floor(b/2) m=m*2 end return r end\n" - "bit.bor = bit.bor or function(a, b) local r,m=0,1 for i=0,31 do if a%2==1 or b%2==1 then r=r+m end a=math.floor(a/2) b=math.floor(b/2) m=m*2 end return r end\n" - "bit.bxor = bit.bxor or function(a, b) local r,m=0,1 for i=0,31 do if (a%2==1)~=(b%2==1) then r=r+m end a=math.floor(a/2) b=math.floor(b/2) m=m*2 end return r end\n" - "bit.bnot = bit.bnot or function(a) return 4294967295 - a end\n" - "bit.lshift = bit.lshift or function(a, n) return a * (2^n) end\n" - "bit.rshift = bit.rshift or function(a, n) return math.floor(a / (2^n)) end\n" - ); } // ---- Event System ---- @@ -3820,9 +2108,8 @@ void LuaEngine::fireEvent(const std::string& eventName, int nargs = 1 + static_cast(args.size()); if (lua_pcall(L_, nargs, 0, 0) != 0) { const char* err = lua_tostring(L_, -1); - std::string errStr = err ? err : "(unknown)"; - LOG_ERROR("LuaEngine: event '", eventName, "' handler error: ", errStr); - if (luaErrorCallback_) luaErrorCallback_(errStr); + LOG_ERROR("LuaEngine: event '", eventName, "' handler error: ", + err ? err : "(unknown)"); lua_pop(L_, 1); } } @@ -3848,10 +2135,7 @@ void LuaEngine::fireEvent(const std::string& eventName, for (const auto& arg : args) lua_pushstring(L_, arg.c_str()); int nargs = 2 + static_cast(args.size()); if (lua_pcall(L_, nargs, 0, 0) != 0) { - const char* ferr = lua_tostring(L_, -1); - std::string ferrStr = ferr ? ferr : "(unknown)"; - LOG_ERROR("LuaEngine: frame OnEvent error: ", ferrStr); - if (luaErrorCallback_) luaErrorCallback_(ferrStr); + LOG_ERROR("LuaEngine: frame OnEvent error: ", lua_tostring(L_, -1)); lua_pop(L_, 1); } } else { @@ -3891,10 +2175,7 @@ void LuaEngine::dispatchOnUpdate(float elapsed) { lua_pushvalue(L_, -3); // self (frame) lua_pushnumber(L_, static_cast(elapsed)); if (lua_pcall(L_, 2, 0, 0) != 0) { - const char* uerr = lua_tostring(L_, -1); - std::string uerrStr = uerr ? uerr : "(unknown)"; - LOG_ERROR("LuaEngine: OnUpdate error: ", uerrStr); - if (luaErrorCallback_) luaErrorCallback_(uerrStr); + LOG_ERROR("LuaEngine: OnUpdate error: ", lua_tostring(L_, -1)); lua_pop(L_, 1); } } else { @@ -4034,13 +2315,6 @@ void LuaEngine::setAddonList(const std::vector& addons) { auto notesIt = addons[i].directives.find("Notes"); lua_pushstring(L_, notesIt != addons[i].directives.end() ? notesIt->second.c_str() : ""); lua_setfield(L_, -2, "notes"); - // Store all TOC directives for GetAddOnMetadata - lua_newtable(L_); - for (const auto& [key, val] : addons[i].directives) { - lua_pushstring(L_, val.c_str()); - lua_setfield(L_, -2, key.c_str()); - } - lua_setfield(L_, -2, "metadata"); lua_rawseti(L_, -2, static_cast(i + 1)); } lua_setfield(L_, LUA_REGISTRYINDEX, "wowee_addon_info"); @@ -4101,7 +2375,6 @@ bool LuaEngine::executeFile(const std::string& path) { const char* errMsg = lua_tostring(L_, -1); std::string msg = errMsg ? errMsg : "(unknown error)"; LOG_ERROR("LuaEngine: error loading '", path, "': ", msg); - if (luaErrorCallback_) luaErrorCallback_(msg); if (gameHandler_) { game::MessageChatData errChat; errChat.type = game::ChatType::SYSTEM; @@ -4123,7 +2396,6 @@ bool LuaEngine::executeString(const std::string& code) { const char* errMsg = lua_tostring(L_, -1); std::string msg = errMsg ? errMsg : "(unknown error)"; LOG_ERROR("LuaEngine: script error: ", msg); - if (luaErrorCallback_) luaErrorCallback_(msg); if (gameHandler_) { game::MessageChatData errChat; errChat.type = game::ChatType::SYSTEM; diff --git a/src/addons/toc_parser.cpp b/src/addons/toc_parser.cpp index 523a164a..3b5c03ab 100644 --- a/src/addons/toc_parser.cpp +++ b/src/addons/toc_parser.cpp @@ -19,12 +19,17 @@ bool TocFile::isLoadOnDemand() const { return (it != directives.end()) && it->second == "1"; } -static std::vector parseVarList(const std::string& val) { +std::vector TocFile::getSavedVariables() const { std::vector result; + auto it = directives.find("SavedVariables"); + if (it == directives.end()) return result; + // Parse comma-separated variable names + std::string val = it->second; size_t pos = 0; while (pos <= val.size()) { size_t comma = val.find(',', pos); std::string name = (comma != std::string::npos) ? val.substr(pos, comma - pos) : val.substr(pos); + // Trim whitespace size_t start = name.find_first_not_of(" \t"); size_t end = name.find_last_not_of(" \t"); if (start != std::string::npos) @@ -35,16 +40,6 @@ static std::vector parseVarList(const std::string& val) { return result; } -std::vector TocFile::getSavedVariables() const { - auto it = directives.find("SavedVariables"); - return (it != directives.end()) ? parseVarList(it->second) : std::vector{}; -} - -std::vector TocFile::getSavedVariablesPerCharacter() const { - auto it = directives.find("SavedVariablesPerCharacter"); - return (it != directives.end()) ? parseVarList(it->second) : std::vector{}; -} - std::optional parseTocFile(const std::string& tocPath) { std::ifstream f(tocPath); if (!f.is_open()) return std::nullopt; diff --git a/src/core/application.cpp b/src/core/application.cpp index 63b2c2f9..8b4aeeb0 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -335,10 +335,6 @@ bool Application::initialize() { if (addonManager_->initialize(gameHandler.get())) { std::string addonsDir = assetPath + "/interface/AddOns"; addonManager_->scanAddons(addonsDir); - // Wire Lua errors to UI error display - addonManager_->getLuaEngine()->setLuaErrorCallback([gh = gameHandler.get()](const std::string& err) { - if (gh) gh->addUIError(err); - }); // Wire chat messages to addon event dispatch gameHandler->setAddonChatCallback([this](const game::MessageChatData& msg) { if (!addonManager_ || !addonsLoaded_) return; @@ -358,25 +354,6 @@ bool Application::initialize() { case game::ChatType::CHANNEL: eventName = "CHAT_MSG_CHANNEL"; break; case game::ChatType::EMOTE: case game::ChatType::TEXT_EMOTE: eventName = "CHAT_MSG_EMOTE"; break; - case game::ChatType::ACHIEVEMENT: eventName = "CHAT_MSG_ACHIEVEMENT"; break; - case game::ChatType::GUILD_ACHIEVEMENT: eventName = "CHAT_MSG_GUILD_ACHIEVEMENT"; break; - case game::ChatType::WHISPER_INFORM: eventName = "CHAT_MSG_WHISPER_INFORM"; break; - case game::ChatType::RAID_LEADER: eventName = "CHAT_MSG_RAID_LEADER"; break; - case game::ChatType::BATTLEGROUND_LEADER: eventName = "CHAT_MSG_BATTLEGROUND_LEADER"; break; - case game::ChatType::MONSTER_SAY: eventName = "CHAT_MSG_MONSTER_SAY"; break; - case game::ChatType::MONSTER_YELL: eventName = "CHAT_MSG_MONSTER_YELL"; break; - case game::ChatType::MONSTER_EMOTE: eventName = "CHAT_MSG_MONSTER_EMOTE"; break; - case game::ChatType::MONSTER_WHISPER: eventName = "CHAT_MSG_MONSTER_WHISPER"; break; - case game::ChatType::RAID_BOSS_EMOTE: eventName = "CHAT_MSG_RAID_BOSS_EMOTE"; break; - case game::ChatType::RAID_BOSS_WHISPER: eventName = "CHAT_MSG_RAID_BOSS_WHISPER"; break; - case game::ChatType::BG_SYSTEM_NEUTRAL: eventName = "CHAT_MSG_BG_SYSTEM_NEUTRAL"; break; - case game::ChatType::BG_SYSTEM_ALLIANCE: eventName = "CHAT_MSG_BG_SYSTEM_ALLIANCE"; break; - case game::ChatType::BG_SYSTEM_HORDE: eventName = "CHAT_MSG_BG_SYSTEM_HORDE"; break; - case game::ChatType::MONSTER_PARTY: eventName = "CHAT_MSG_MONSTER_PARTY"; break; - case game::ChatType::AFK: eventName = "CHAT_MSG_AFK"; break; - case game::ChatType::DND: eventName = "CHAT_MSG_DND"; break; - case game::ChatType::LOOT: eventName = "CHAT_MSG_LOOT"; break; - case game::ChatType::SKILL: eventName = "CHAT_MSG_SKILL"; break; default: break; } if (eventName) { @@ -436,119 +413,6 @@ bool Application::initialize() { return pit->second; }); } - // Wire item icon path resolver: displayInfoId -> "Interface\\Icons\\INV_..." - { - auto iconNames = std::make_shared>(); - auto loaded = std::make_shared(false); - auto* am = assetManager.get(); - gameHandler->setItemIconPathResolver([iconNames, loaded, am](uint32_t displayInfoId) -> std::string { - if (!am || displayInfoId == 0) return {}; - if (!*loaded) { - *loaded = true; - auto dbc = am->loadDBC("ItemDisplayInfo.dbc"); - const auto* dispL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("ItemDisplayInfo") : nullptr; - if (dbc && dbc->isLoaded()) { - uint32_t iconField = dispL ? (*dispL)["InventoryIcon"] : 5; - for (uint32_t i = 0; i < dbc->getRecordCount(); i++) { - uint32_t id = dbc->getUInt32(i, 0); // field 0 = ID - std::string name = dbc->getString(i, iconField); - if (id > 0 && !name.empty()) (*iconNames)[id] = name; - } - LOG_INFO("Loaded ", iconNames->size(), " item icon names from ItemDisplayInfo.dbc"); - } - } - auto it = iconNames->find(displayInfoId); - if (it == iconNames->end()) return {}; - return "Interface\\Icons\\" + it->second; - }); - } - // Wire spell data resolver: spellId -> {castTimeMs, minRange, maxRange} - { - auto castTimeMap = std::make_shared>(); - 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, spellCostMap, loaded, am](uint32_t spellId) -> game::GameHandler::SpellDataInfo { - if (!am) return {}; - if (!*loaded) { - *loaded = true; - // Load SpellCastTimes.dbc - auto ctDbc = am->loadDBC("SpellCastTimes.dbc"); - if (ctDbc && ctDbc->isLoaded()) { - for (uint32_t i = 0; i < ctDbc->getRecordCount(); ++i) { - uint32_t id = ctDbc->getUInt32(i, 0); - int32_t base = static_cast(ctDbc->getUInt32(i, 1)); - if (id > 0 && base > 0) (*castTimeMap)[id] = static_cast(base); - } - } - // Load SpellRange.dbc - const auto* srL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("SpellRange") : nullptr; - uint32_t minRField = srL ? (*srL)["MinRange"] : 1; - uint32_t maxRField = srL ? (*srL)["MaxRange"] : 4; - auto rDbc = am->loadDBC("SpellRange.dbc"); - if (rDbc && rDbc->isLoaded()) { - for (uint32_t i = 0; i < rDbc->getRecordCount(); ++i) { - uint32_t id = rDbc->getUInt32(i, 0); - float minR = rDbc->getFloat(i, minRField); - float maxR = rDbc->getFloat(i, maxRField); - if (id > 0) (*rangeMap)[id] = {minR, maxR}; - } - } - // Load Spell.dbc: extract castTimeIndex and rangeIndex per spell - auto sDbc = am->loadDBC("Spell.dbc"); - const auto* spL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("Spell") : nullptr; - if (sDbc && sDbc->isLoaded()) { - 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; - uint32_t ct = sDbc->getUInt32(i, ctF); - 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, ", - spellRangeIdx->size(), " range indices"); - } - game::GameHandler::SpellDataInfo info; - auto ciIt = spellCastIdx->find(spellId); - if (ciIt != spellCastIdx->end()) { - auto ctIt = castTimeMap->find(ciIt->second); - if (ctIt != castTimeMap->end()) info.castTimeMs = ctIt->second; - } - auto riIt = spellRangeIdx->find(spellId); - if (riIt != spellRangeIdx->end()) { - auto rIt = rangeMap->find(riIt->second); - if (rIt != rangeMap->end()) { - info.minRange = rIt->second.first; - 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; - }); - } // Wire random property/suffix name resolver for item display { auto propNames = std::make_shared>(); @@ -5318,21 +5182,6 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float // Load addons once per session on first world entry if (addonManager_ && !addonsLoaded_) { - // Set character name for per-character SavedVariables - if (gameHandler) { - const std::string& charName = gameHandler->lookupName(gameHandler->getPlayerGuid()); - if (!charName.empty()) { - addonManager_->setCharacterName(charName); - } else { - // Fallback: find name from character list - for (const auto& c : gameHandler->getCharacters()) { - if (c.guid == gameHandler->getPlayerGuid()) { - addonManager_->setCharacterName(c.name); - break; - } - } - } - } addonManager_->loadAllAddons(); addonsLoaded_ = true; addonManager_->fireEvent("VARIABLES_LOADED"); diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 164e9d52..47f0756f 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -2010,11 +2010,6 @@ void GameHandler::handlePacket(network::Packet& packet) { pendingItemPushNotifs_.push_back({itemId, count}); } } - // Fire bag/inventory events for all item receipts (not just chat-visible ones) - if (addonEventCallback_) { - addonEventCallback_("BAG_UPDATE", {}); - addonEventCallback_("UNIT_INVENTORY_CHANGED", {"player"}); - } LOG_INFO("Item push: itemId=", itemId, " count=", count, " showInChat=", static_cast(showInChat)); } @@ -2165,15 +2160,6 @@ void GameHandler::handlePacket(network::Packet& packet) { if (auto* unit = dynamic_cast(entity.get())) { unit->setHealth(hp); } - if (addonEventCallback_ && guid != 0) { - std::string unitId; - if (guid == playerGuid) unitId = "player"; - else if (guid == targetGuid) unitId = "target"; - else if (guid == focusGuid) unitId = "focus"; - else if (guid == petGuid_) unitId = "pet"; - if (!unitId.empty()) - addonEventCallback_("UNIT_HEALTH", {unitId}); - } break; } case Opcode::SMSG_POWER_UPDATE: { @@ -2191,15 +2177,6 @@ void GameHandler::handlePacket(network::Packet& packet) { if (auto* unit = dynamic_cast(entity.get())) { unit->setPowerByType(powerType, value); } - if (addonEventCallback_ && guid != 0) { - std::string unitId; - if (guid == playerGuid) unitId = "player"; - else if (guid == targetGuid) unitId = "target"; - else if (guid == focusGuid) unitId = "focus"; - else if (guid == petGuid_) unitId = "pet"; - if (!unitId.empty()) - addonEventCallback_("UNIT_POWER", {unitId}); - } break; } @@ -2236,8 +2213,6 @@ void GameHandler::handlePacket(network::Packet& packet) { if (pvpHonorCallback_) { pvpHonorCallback_(honor, victimGuid, rank); } - if (addonEventCallback_) - addonEventCallback_("CHAT_MSG_COMBAT_HONOR_GAIN", {msg}); } break; } @@ -2275,11 +2250,6 @@ void GameHandler::handlePacket(network::Packet& packet) { mirrorTimers_[type].scale = scale; mirrorTimers_[type].paused = (paused != 0); mirrorTimers_[type].active = true; - if (addonEventCallback_) - addonEventCallback_("MIRROR_TIMER_START", { - std::to_string(type), std::to_string(value), - std::to_string(maxV), std::to_string(scale), - paused ? "1" : "0"}); } break; } @@ -2290,8 +2260,6 @@ void GameHandler::handlePacket(network::Packet& packet) { if (type < 3) { mirrorTimers_[type].active = false; mirrorTimers_[type].value = 0; - if (addonEventCallback_) - addonEventCallback_("MIRROR_TIMER_STOP", {std::to_string(type)}); } break; } @@ -2336,10 +2304,8 @@ void GameHandler::handlePacket(network::Packet& packet) { : ("Spell cast failed (error " + std::to_string(castResult) + ")"); addUIError(errMsg); if (spellCastFailedCallback_) spellCastFailedCallback_(castResultSpellId); - if (addonEventCallback_) { + if (addonEventCallback_) addonEventCallback_("UNIT_SPELLCAST_FAILED", {"player", std::to_string(castResultSpellId)}); - addonEventCallback_("UNIT_SPELLCAST_STOP", {"player", std::to_string(castResultSpellId)}); - } MessageChatData msg; msg.type = ChatType::SYSTEM; msg.language = ChatLanguage::UNIVERSAL; @@ -2360,16 +2326,6 @@ void GameHandler::handlePacket(network::Packet& packet) { : UpdateObjectParser::readPackedGuid(packet); if (failOtherGuid != 0 && failOtherGuid != playerGuid) { unitCastStates_.erase(failOtherGuid); - // Fire cast failure events so cast bar addons clear the bar - if (addonEventCallback_) { - std::string unitId; - if (failOtherGuid == targetGuid) unitId = "target"; - else if (failOtherGuid == focusGuid) unitId = "focus"; - if (!unitId.empty()) { - addonEventCallback_("UNIT_SPELLCAST_FAILED", {unitId}); - addonEventCallback_("UNIT_SPELLCAST_STOP", {unitId}); - } - } } packet.setReadPos(packet.getSize()); break; @@ -2446,8 +2402,6 @@ void GameHandler::handlePacket(network::Packet& packet) { pendingLootRoll_.rollStartedAt = std::chrono::steady_clock::now(); LOG_INFO("SMSG_LOOT_START_ROLL: item=", itemId, " (", pendingLootRoll_.itemName, ") slot=", slot, " voteMask=0x", std::hex, (int)voteMask, std::dec); - if (addonEventCallback_) - addonEventCallback_("START_LOOT_ROLL", {std::to_string(slot), std::to_string(countdown)}); break; } @@ -3311,10 +3265,8 @@ void GameHandler::handlePacket(network::Packet& packet) { sendMovement(Opcode::MSG_MOVE_STOP_TURN); sendMovement(Opcode::MSG_MOVE_STOP_SWIM); addSystemChatMessage("Movement disabled by server."); - if (addonEventCallback_) addonEventCallback_("PLAYER_CONTROL_LOST", {}); } else if (changed && allowMovement) { addSystemChatMessage("Movement re-enabled."); - if (addonEventCallback_) addonEventCallback_("PLAYER_CONTROL_GAINED", {}); } } break; @@ -3466,11 +3418,8 @@ void GameHandler::handlePacket(network::Packet& packet) { if (failGuid == playerGuid || failGuid == 0) unitId = "player"; else if (failGuid == targetGuid) unitId = "target"; else if (failGuid == focusGuid) unitId = "focus"; - else if (failGuid == petGuid_) unitId = "pet"; - if (!unitId.empty()) { + if (!unitId.empty()) addonEventCallback_("UNIT_SPELLCAST_INTERRUPTED", {unitId}); - addonEventCallback_("UNIT_SPELLCAST_STOP", {unitId}); - } } if (failGuid == playerGuid || failGuid == 0) { // Player's own cast failed — clear gather-node loot target so the @@ -3762,10 +3711,6 @@ void GameHandler::handlePacket(network::Packet& packet) { addUIError("Your party has been disbanded."); addSystemChatMessage("Your party has been disbanded."); LOG_INFO("SMSG_GROUP_DESTROYED: party cleared"); - if (addonEventCallback_) { - addonEventCallback_("GROUP_ROSTER_UPDATE", {}); - addonEventCallback_("PARTY_MEMBERS_CHANGED", {}); - } break; case Opcode::SMSG_GROUP_CANCEL: // Group invite was cancelled before being accepted. @@ -3809,8 +3754,6 @@ void GameHandler::handlePacket(network::Packet& packet) { ? "Ready check initiated!" : readyCheckInitiator_ + " initiated a ready check!"); LOG_INFO("MSG_RAID_READY_CHECK: initiator=", readyCheckInitiator_); - if (addonEventCallback_) - addonEventCallback_("READY_CHECK", {readyCheckInitiator_}); break; } case Opcode::MSG_RAID_READY_CHECK_CONFIRM: { @@ -3839,11 +3782,6 @@ void GameHandler::handlePacket(network::Packet& packet) { std::snprintf(rbuf, sizeof(rbuf), "%s is %s.", rname.c_str(), isReady ? "Ready" : "Not Ready"); addSystemChatMessage(rbuf); } - if (addonEventCallback_) { - char guidBuf[32]; - snprintf(guidBuf, sizeof(guidBuf), "0x%016llX", (unsigned long long)respGuid); - addonEventCallback_("READY_CHECK_CONFIRM", {guidBuf, isReady ? "1" : "0"}); - } break; } case Opcode::MSG_RAID_READY_CHECK_FINISHED: { @@ -3856,8 +3794,6 @@ void GameHandler::handlePacket(network::Packet& packet) { readyCheckReadyCount_ = 0; readyCheckNotReadyCount_ = 0; readyCheckResults_.clear(); - if (addonEventCallback_) - addonEventCallback_("READY_CHECK_FINISHED", {}); break; } case Opcode::SMSG_RAID_INSTANCE_INFO: @@ -4072,8 +4008,6 @@ void GameHandler::handlePacket(network::Packet& packet) { resurrectCasterName_ = (nit != playerNameCache.end()) ? nit->second : ""; } resurrectRequestPending_ = true; - if (addonEventCallback_) - addonEventCallback_("RESURRECT_REQUEST", {resurrectCasterName_}); } break; } @@ -4802,7 +4736,6 @@ void GameHandler::handlePacket(network::Packet& packet) { recentLootMoneyAnnounceCooldowns_[notifyGuid] = 1.5f; } } - if (addonEventCallback_) addonEventCallback_("PLAYER_MONEY", {}); } break; } @@ -4824,10 +4757,6 @@ void GameHandler::handlePacket(network::Packet& packet) { if (auto* sfx = renderer->getUiSoundManager()) sfx->playDropOnGround(); } - if (addonEventCallback_) { - addonEventCallback_("BAG_UPDATE", {}); - addonEventCallback_("PLAYER_MONEY", {}); - } } else { bool removedPending = false; auto it = pendingSellToBuyback_.find(itemGuid); @@ -5062,8 +4991,6 @@ void GameHandler::handlePacket(network::Packet& packet) { } } LOG_DEBUG("MSG_RAID_TARGET_UPDATE: type=", static_cast(rtuType)); - if (addonEventCallback_) - addonEventCallback_("RAID_TARGET_UPDATE", {}); break; } case Opcode::SMSG_BUY_ITEM: { @@ -5093,10 +5020,6 @@ void GameHandler::handlePacket(network::Packet& packet) { } pendingBuyItemId_ = 0; pendingBuyItemSlot_ = 0; - if (addonEventCallback_) { - addonEventCallback_("MERCHANT_UPDATE", {}); - addonEventCallback_("BAG_UPDATE", {}); - } } break; } @@ -5479,10 +5402,6 @@ void GameHandler::handlePacket(network::Packet& packet) { if (questProgressCallback_) { questProgressCallback_(quest.title, creatureName, count, reqCount); } - if (addonEventCallback_) { - addonEventCallback_("QUEST_WATCH_UPDATE", {std::to_string(questId)}); - addonEventCallback_("QUEST_LOG_UPDATE", {}); - } LOG_INFO("Updated kill count for quest ", questId, ": ", count, "/", reqCount); @@ -5560,10 +5479,6 @@ void GameHandler::handlePacket(network::Packet& packet) { } } - if (addonEventCallback_ && updatedAny) { - addonEventCallback_("QUEST_WATCH_UPDATE", {}); - addonEventCallback_("QUEST_LOG_UPDATE", {}); - } LOG_INFO("Quest item update: itemId=", itemId, " count=", count, " trackedQuestsUpdated=", updatedAny); } @@ -5627,8 +5542,6 @@ void GameHandler::handlePacket(network::Packet& packet) { isResting_ = nowResting; addSystemChatMessage(isResting_ ? "You are now resting." : "You are no longer resting."); - if (addonEventCallback_) - addonEventCallback_("PLAYER_UPDATE_RESTING", {}); } break; } @@ -5795,10 +5708,6 @@ void GameHandler::handlePacket(network::Packet& packet) { if (!leaderName.empty()) addSystemChatMessage(leaderName + " is now the group leader."); LOG_INFO("SMSG_GROUP_SET_LEADER: ", leaderName); - if (addonEventCallback_) { - addonEventCallback_("PARTY_LEADER_CHANGED", {}); - addonEventCallback_("GROUP_ROSTER_UPDATE", {}); - } } break; } @@ -6163,8 +6072,6 @@ void GameHandler::handlePacket(network::Packet& packet) { talentWipePending_ = true; LOG_INFO("MSG_TALENT_WIPE_CONFIRM: npc=0x", std::hex, talentWipeNpcGuid_, std::dec, " cost=", talentWipeCost_); - if (addonEventCallback_) - addonEventCallback_("CONFIRM_TALENT_WIPE", {std::to_string(talentWipeCost_)}); break; } @@ -6504,8 +6411,6 @@ void GameHandler::handlePacket(network::Packet& packet) { isResting_ = (restTrigger > 0); addSystemChatMessage(isResting_ ? "You are now resting." : "You are no longer resting."); - if (addonEventCallback_) - addonEventCallback_("PLAYER_UPDATE_RESTING", {}); } break; } @@ -7493,7 +7398,6 @@ void GameHandler::handlePacket(network::Packet& packet) { if (chanCaster == playerGuid) unitId = "player"; else if (chanCaster == targetGuid) unitId = "target"; else if (chanCaster == focusGuid) unitId = "focus"; - else if (chanCaster == petGuid_) unitId = "pet"; if (!unitId.empty()) addonEventCallback_("UNIT_SPELLCAST_CHANNEL_START", {unitId, std::to_string(chanSpellId)}); } @@ -7530,7 +7434,6 @@ void GameHandler::handlePacket(network::Packet& packet) { if (chanCaster2 == playerGuid) unitId = "player"; else if (chanCaster2 == targetGuid) unitId = "target"; else if (chanCaster2 == focusGuid) unitId = "focus"; - else if (chanCaster2 == petGuid_) unitId = "pet"; if (!unitId.empty()) addonEventCallback_("UNIT_SPELLCAST_CHANNEL_STOP", {unitId}); } @@ -7776,10 +7679,6 @@ void GameHandler::handlePacket(network::Packet& packet) { LOG_DEBUG("SMSG_REAL_GROUP_UPDATE groupType=", static_cast(newGroupType), " memberFlags=0x", std::hex, newMemberFlags, std::dec, " leaderGuid=", newLeaderGuid); - if (addonEventCallback_) { - addonEventCallback_("PARTY_LEADER_CHANGED", {}); - addonEventCallback_("GROUP_ROSTER_UPDATE", {}); - } break; } @@ -8005,7 +7904,6 @@ void GameHandler::handlePacket(network::Packet& packet) { const std::string& sname = getSpellName(spellId); addSystemChatMessage("Your pet has learned " + (sname.empty() ? "a new ability." : sname + ".")); LOG_DEBUG("SMSG_PET_LEARNED_SPELL: spellId=", spellId); - if (addonEventCallback_) addonEventCallback_("PET_BAR_UPDATE", {}); } packet.setReadPos(packet.getSize()); break; @@ -8125,11 +8023,6 @@ void GameHandler::handlePacket(network::Packet& packet) { LOG_INFO("SMSG_INSPECT (Classic): ", playerName, " has gear in ", std::count_if(items.begin(), items.end(), [](uint32_t e) { return e != 0; }), "/19 slots"); - if (addonEventCallback_) { - char guidBuf[32]; - snprintf(guidBuf, sizeof(guidBuf), "0x%016llX", (unsigned long long)guid); - addonEventCallback_("INSPECT_READY", {guidBuf}); - } break; } @@ -11198,13 +11091,6 @@ void GameHandler::sendMovement(Opcode opcode) { } } - // Track movement state transition for PLAYER_STARTED/STOPPED_MOVING events - const uint32_t kMoveMask = static_cast(MovementFlags::FORWARD) | - static_cast(MovementFlags::BACKWARD) | - static_cast(MovementFlags::STRAFE_LEFT) | - static_cast(MovementFlags::STRAFE_RIGHT); - const bool wasMoving = (movementInfo.flags & kMoveMask) != 0; - // Cancel any timed (non-channeled) cast the moment the player starts moving. // Channeled spells end via MSG_CHANNEL_UPDATE / SMSG_CHANNEL_NOTIFY from the server. // Turning (MSG_MOVE_START_TURN_*) is allowed while casting. @@ -11309,15 +11195,6 @@ void GameHandler::sendMovement(Opcode opcode) { break; } - // Fire PLAYER_STARTED/STOPPED_MOVING on movement state transitions - { - const bool isMoving = (movementInfo.flags & kMoveMask) != 0; - if (isMoving && !wasMoving && addonEventCallback_) - addonEventCallback_("PLAYER_STARTED_MOVING", {}); - else if (!isMoving && wasMoving && addonEventCallback_) - addonEventCallback_("PLAYER_STOPPED_MOVING", {}); - } - if (opcode == Opcode::MSG_MOVE_SET_FACING) { lastFacingSendTimeMs_ = movementInfo.time; lastFacingSentOrientation_ = movementInfo.orientation; @@ -13657,7 +13534,6 @@ std::shared_ptr GameHandler::getTarget() const { void GameHandler::setFocus(uint64_t guid) { focusGuid = guid; - if (addonEventCallback_) addonEventCallback_("PLAYER_FOCUS_CHANGED", {}); if (guid != 0) { auto entity = entityManager.getEntity(guid); if (entity) { @@ -13683,14 +13559,6 @@ void GameHandler::clearFocus() { LOG_INFO("Focus cleared"); } focusGuid = 0; - if (addonEventCallback_) addonEventCallback_("PLAYER_FOCUS_CHANGED", {}); -} - -void GameHandler::setMouseoverGuid(uint64_t guid) { - if (mouseoverGuid_ != guid) { - mouseoverGuid_ = guid; - if (addonEventCallback_) addonEventCallback_("UPDATE_MOUSEOVER_UNIT", {}); - } } std::shared_ptr GameHandler::getFocus() const { @@ -14364,7 +14232,6 @@ void GameHandler::handleDuelRequested(network::Packet& packet) { } LOG_INFO("SMSG_DUEL_REQUESTED: challenger=0x", std::hex, duelChallengerGuid_, " flag=0x", duelFlagGuid_, std::dec, " name=", duelChallengerName_); - if (addonEventCallback_) addonEventCallback_("DUEL_REQUESTED", {duelChallengerName_}); } void GameHandler::handleDuelComplete(network::Packet& packet) { @@ -14377,7 +14244,6 @@ void GameHandler::handleDuelComplete(network::Packet& packet) { addSystemChatMessage("The duel was cancelled."); } LOG_INFO("SMSG_DUEL_COMPLETE: started=", static_cast(started)); - if (addonEventCallback_) addonEventCallback_("DUEL_FINISHED", {}); } void GameHandler::handleDuelWinner(network::Packet& packet) { @@ -14916,10 +14782,6 @@ void GameHandler::handleNameQueryResponse(network::Packet& packet) { if (data.isValid()) { playerNameCache[data.guid] = data.name; - // Cache class/race from name query for UnitClass/UnitRace fallback - if (data.classId != 0 || data.race != 0) { - playerClassRaceCache_[data.guid] = {data.classId, data.race}; - } // Update entity name auto entity = entityManager.getEntity(data.guid); if (entity && entity->getType() == ObjectType::PLAYER) { @@ -14946,16 +14808,6 @@ void GameHandler::handleNameQueryResponse(network::Packet& packet) { if (friendGuids_.count(data.guid)) { friendsCache[data.name] = data.guid; } - - // Fire UNIT_NAME_UPDATE so nameplate/unit frame addons know the name is available - if (addonEventCallback_) { - std::string unitId; - if (data.guid == targetGuid) unitId = "target"; - else if (data.guid == focusGuid) unitId = "focus"; - else if (data.guid == playerGuid) unitId = "player"; - if (!unitId.empty()) - addonEventCallback_("UNIT_NAME_UPDATE", {unitId}); - } } } @@ -15331,11 +15183,6 @@ void GameHandler::handleInspectResults(network::Packet& packet) { LOG_INFO("Inspect results for ", playerName, ": ", totalTalents, " talents, ", unspentTalents, " unspent, ", (int)talentGroupCount, " specs"); - if (addonEventCallback_) { - char guidBuf[32]; - snprintf(guidBuf, sizeof(guidBuf), "0x%016llX", (unsigned long long)guid); - addonEventCallback_("INSPECT_READY", {guidBuf}); - } } uint64_t GameHandler::resolveOnlineItemGuid(uint32_t itemId) const { @@ -16167,8 +16014,6 @@ void GameHandler::stopAutoAttack() { socket->send(packet); } LOG_INFO("Stopping auto-attack"); - if (addonEventCallback_) - addonEventCallback_("PLAYER_LEAVE_COMBAT", {}); } void GameHandler::addCombatText(CombatTextEntry::Type type, int32_t amount, uint32_t spellId, bool isPlayerSource, uint8_t powerType, @@ -16290,8 +16135,6 @@ void GameHandler::handleAttackStart(network::Packet& packet) { autoAttacking = true; autoAttackRetryPending_ = false; autoAttackTarget = data.victimGuid; - if (addonEventCallback_) - addonEventCallback_("PLAYER_ENTER_COMBAT", {}); } else if (data.victimGuid == playerGuid && data.attackerGuid != 0) { hostileAttackers_.insert(data.attackerGuid); autoTargetAttacker(data.attackerGuid); @@ -16850,8 +16693,6 @@ void GameHandler::handleBattlefieldStatus(network::Packet& packet) { LOG_INFO("Battlefield status: unknown (", statusId, ") for ", bgName); break; } - if (addonEventCallback_) - addonEventCallback_("UPDATE_BATTLEFIELD_STATUS", {std::to_string(statusId)}); } void GameHandler::handleBattlefieldList(network::Packet& packet) { @@ -18472,27 +18313,6 @@ void GameHandler::handleMonsterMove(network::Packet& packet) { creatureMoveCallback_(data.guid, posCanonical.x, posCanonical.y, posCanonical.z, 0); } - } else if (data.moveType == 4) { - // FacingAngle without movement — rotate NPC in place - float orientation = core::coords::serverToCanonicalYaw(data.facingAngle); - glm::vec3 posCanonical = core::coords::serverToCanonical( - glm::vec3(data.x, data.y, data.z)); - entity->setPosition(posCanonical.x, posCanonical.y, posCanonical.z, orientation); - if (creatureMoveCallback_) { - creatureMoveCallback_(data.guid, - posCanonical.x, posCanonical.y, posCanonical.z, 0); - } - } else if (data.moveType == 3 && data.facingTarget != 0) { - // FacingTarget without movement — rotate NPC to face a target - auto target = entityManager.getEntity(data.facingTarget); - if (target) { - float dx = target->getX() - entity->getX(); - float dy = target->getY() - entity->getY(); - if (std::abs(dx) > 0.01f || std::abs(dy) > 0.01f) { - float orientation = std::atan2(-dy, dx); - entity->setOrientation(orientation); - } - } } } @@ -18899,13 +18719,6 @@ void GameHandler::castSpell(uint32_t spellId, uint64_t targetGuid) { socket->send(packet); LOG_INFO("Casting spell: ", spellId, " on 0x", std::hex, target, std::dec); - // Fire UNIT_SPELLCAST_SENT for cast bar addons (fires on client intent, before server confirms) - if (addonEventCallback_) { - std::string targetName; - if (target != 0) targetName = lookupName(target); - addonEventCallback_("UNIT_SPELLCAST_SENT", {"player", targetName, std::to_string(spellId)}); - } - // Optimistically start GCD immediately on cast, but do not restart it while // already active (prevents timeout animation reset on repeated key presses). if (!isGCDActive()) { @@ -18934,8 +18747,6 @@ void GameHandler::cancelCast() { craftQueueRemaining_ = 0; queuedSpellId_ = 0; queuedSpellTarget_ = 0; - if (addonEventCallback_) - addonEventCallback_("UNIT_SPELLCAST_STOP", {"player"}); } void GameHandler::startCraftQueue(uint32_t spellId, int count) { @@ -18978,7 +18789,6 @@ void GameHandler::handlePetSpells(network::Packet& packet) { petAutocastSpells_.clear(); memset(petActionSlots_, 0, sizeof(petActionSlots_)); LOG_INFO("SMSG_PET_SPELLS: pet cleared"); - if (addonEventCallback_) addonEventCallback_("UNIT_PET", {"player"}); return; } @@ -18988,7 +18798,6 @@ void GameHandler::handlePetSpells(network::Packet& packet) { petAutocastSpells_.clear(); memset(petActionSlots_, 0, sizeof(petActionSlots_)); LOG_INFO("SMSG_PET_SPELLS: pet cleared (guid=0)"); - if (addonEventCallback_) addonEventCallback_("UNIT_PET", {"player"}); return; } @@ -19030,10 +18839,6 @@ done: LOG_INFO("SMSG_PET_SPELLS: petGuid=0x", std::hex, petGuid_, std::dec, " react=", (int)petReact_, " command=", (int)petCommand_, " spells=", petSpellList_.size()); - if (addonEventCallback_) { - addonEventCallback_("UNIT_PET", {"player"}); - addonEventCallback_("PET_BAR_UPDATE", {}); - } } void GameHandler::sendPetAction(uint32_t action, uint64_t targetGuid) { @@ -19362,7 +19167,6 @@ void GameHandler::handleSpellStart(network::Packet& packet) { if (data.casterUnit == playerGuid) unitId = "player"; else if (data.casterUnit == targetGuid) unitId = "target"; else if (data.casterUnit == focusGuid) unitId = "focus"; - else if (data.casterUnit == petGuid_) unitId = "pet"; if (!unitId.empty()) addonEventCallback_("UNIT_SPELLCAST_START", {unitId, std::to_string(data.spellId)}); } @@ -19442,10 +19246,6 @@ void GameHandler::handleSpellGo(network::Packet& packet) { spellCastAnimCallback_(playerGuid, false, false); } - // Fire UNIT_SPELLCAST_STOP — cast bar should disappear - if (addonEventCallback_) - addonEventCallback_("UNIT_SPELLCAST_STOP", {"player", std::to_string(data.spellId)}); - // Spell queue: fire the next queued spell now that casting has ended if (queuedSpellId_ != 0) { uint32_t nextSpell = queuedSpellId_; @@ -19512,7 +19312,6 @@ void GameHandler::handleSpellGo(network::Packet& packet) { if (data.casterUnit == playerGuid) unitId = "player"; else if (data.casterUnit == targetGuid) unitId = "target"; else if (data.casterUnit == focusGuid) unitId = "focus"; - else if (data.casterUnit == petGuid_) unitId = "pet"; if (!unitId.empty()) addonEventCallback_("UNIT_SPELLCAST_SUCCEEDED", {unitId, std::to_string(data.spellId)}); } @@ -19683,7 +19482,6 @@ void GameHandler::handleLearnedSpell(network::Packet& packet) { LOG_INFO("Learned spell: ", spellId, alreadyKnown ? " (already known, skipping chat)" : ""); // Check if this spell corresponds to a talent rank - bool isTalentSpell = false; for (const auto& [talentId, talent] : talentCache_) { for (int rank = 0; rank < 5; ++rank) { if (talent.rankSpells[rank] == spellId) { @@ -19692,15 +19490,9 @@ void GameHandler::handleLearnedSpell(network::Packet& packet) { learnedTalents_[activeTalentSpec_][talentId] = newRank; LOG_INFO("Talent learned: id=", talentId, " rank=", (int)newRank, " (spell ", spellId, ") in spec ", (int)activeTalentSpec_); - isTalentSpell = true; - if (addonEventCallback_) { - addonEventCallback_("CHARACTER_POINTS_CHANGED", {}); - addonEventCallback_("PLAYER_TALENT_UPDATE", {}); - } - break; + return; } } - if (isTalentSpell) break; } // Fire LEARNED_SPELL_IN_TAB / SPELLS_CHANGED for Lua addons @@ -19709,8 +19501,6 @@ void GameHandler::handleLearnedSpell(network::Packet& packet) { addonEventCallback_("SPELLS_CHANGED", {}); } - if (isTalentSpell) return; // talent spells don't show chat message - // Show chat message for non-talent spells, but only if not already announced by // SMSG_TRAINER_BUY_SUCCEEDED (which pre-inserts into knownSpells). if (!alreadyKnown) { @@ -19888,13 +19678,6 @@ void GameHandler::handleTalentsInfo(network::Packet& packet) { " groups=", (int)talentGroupCount, " active=", (int)activeTalentGroup, " learned=", learnedTalents_[activeTalentGroup].size()); - // Fire talent-related events for addons - if (addonEventCallback_) { - addonEventCallback_("CHARACTER_POINTS_CHANGED", {}); - addonEventCallback_("ACTIVE_TALENT_GROUP_CHANGED", {}); - addonEventCallback_("PLAYER_TALENT_UPDATE", {}); - } - if (!talentsInitialized_) { talentsInitialized_ = true; if (unspentTalents > 0) { @@ -20044,8 +19827,6 @@ void GameHandler::handleGroupInvite(network::Packet& packet) { if (auto* sfx = renderer->getUiSoundManager()) sfx->playTargetSelect(); } - if (addonEventCallback_) - addonEventCallback_("PARTY_INVITE_REQUEST", {data.inviterName}); } void GameHandler::handleGroupDecline(network::Packet& packet) { @@ -20340,40 +20121,6 @@ void GameHandler::handlePartyMemberStats(network::Packet& packet, bool isFull) { LOG_DEBUG("Party member stats for ", member->name, ": HP=", member->curHealth, "/", member->maxHealth, " Level=", member->level); - - // Fire addon events for party/raid member health/power/aura changes - if (addonEventCallback_) { - // Resolve unit ID for this member (party1..4 or raid1..40) - std::string unitId; - if (partyData.groupType == 1) { - // Raid: find 1-based index - for (size_t i = 0; i < partyData.members.size(); ++i) { - if (partyData.members[i].guid == memberGuid) { - unitId = "raid" + std::to_string(i + 1); - break; - } - } - } else { - // Party: find 1-based index excluding self - int found = 0; - for (const auto& m : partyData.members) { - if (m.guid == playerGuid) continue; - ++found; - if (m.guid == memberGuid) { - unitId = "party" + std::to_string(found); - break; - } - } - } - if (!unitId.empty()) { - if (updateFlags & (0x0002 | 0x0004)) // CUR_HP or MAX_HP - addonEventCallback_("UNIT_HEALTH", {unitId}); - if (updateFlags & (0x0010 | 0x0020)) // CUR_POWER or MAX_POWER - addonEventCallback_("UNIT_POWER", {unitId}); - if (updateFlags & 0x0200) // AURAS - addonEventCallback_("UNIT_AURA", {unitId}); - } - } } // ============================================================ @@ -20688,7 +20435,6 @@ void GameHandler::handleGuildRoster(network::Packet& packet) { guildRoster_ = std::move(data); hasGuildRoster_ = true; LOG_INFO("Guild roster received: ", guildRoster_.members.size(), " members"); - if (addonEventCallback_) addonEventCallback_("GUILD_ROSTER_UPDATE", {}); } void GameHandler::handleGuildQueryResponse(network::Packet& packet) { @@ -20714,10 +20460,8 @@ void GameHandler::handleGuildQueryResponse(network::Packet& packet) { guildRankNames_.push_back(data.rankNames[i]); } LOG_INFO("Guild name set to: ", guildName_); - if (wasUnknown && !guildName_.empty()) { + if (wasUnknown && !guildName_.empty()) addSystemChatMessage("Guild: <" + guildName_ + ">"); - if (addonEventCallback_) addonEventCallback_("PLAYER_GUILD_UPDATE", {}); - } } else { LOG_INFO("Cached guild name: id=", data.guildId, " name=", data.guildName); } @@ -20767,7 +20511,6 @@ void GameHandler::handleGuildEvent(network::Packet& packet) { guildRankNames_.clear(); guildRoster_ = GuildRosterData{}; hasGuildRoster_ = false; - if (addonEventCallback_) addonEventCallback_("PLAYER_GUILD_UPDATE", {}); break; case GuildEvent::SIGNED_ON: if (data.numStrings >= 1) @@ -20790,28 +20533,6 @@ void GameHandler::handleGuildEvent(network::Packet& packet) { addLocalChatMessage(chatMsg); } - // Fire addon events for guild state changes - if (addonEventCallback_) { - switch (data.eventType) { - case GuildEvent::MOTD: - addonEventCallback_("GUILD_MOTD", {data.numStrings >= 1 ? data.strings[0] : ""}); - break; - case GuildEvent::SIGNED_ON: - case GuildEvent::SIGNED_OFF: - case GuildEvent::PROMOTION: - case GuildEvent::DEMOTION: - case GuildEvent::JOINED: - case GuildEvent::LEFT: - case GuildEvent::REMOVED: - case GuildEvent::LEADER_CHANGED: - case GuildEvent::DISBANDED: - addonEventCallback_("GUILD_ROSTER_UPDATE", {}); - break; - default: - break; - } - } - // Auto-refresh roster after membership/rank changes switch (data.eventType) { case GuildEvent::PROMOTION: @@ -20836,8 +20557,6 @@ void GameHandler::handleGuildInvite(network::Packet& packet) { pendingGuildInviteGuildName_ = data.guildName; LOG_INFO("Guild invite from: ", data.inviterName, " to guild: ", data.guildName); addSystemChatMessage(data.inviterName + " has invited you to join " + data.guildName + "."); - if (addonEventCallback_) - addonEventCallback_("GUILD_INVITE_REQUEST", {data.inviterName, data.guildName}); } void GameHandler::handleGuildCommandResult(network::Packet& packet) { @@ -20932,7 +20651,6 @@ void GameHandler::lootItem(uint8_t slotIndex) { void GameHandler::closeLoot() { if (!lootWindowOpen) return; lootWindowOpen = false; - if (addonEventCallback_) addonEventCallback_("LOOT_CLOSED", {}); masterLootCandidates_.clear(); if (currentLoot.lootGuid != 0 && targetGuid == currentLoot.lootGuid) { clearTarget(); @@ -21383,7 +21101,6 @@ void GameHandler::handleQuestDetails(network::Packet& packet) { // Delay opening the window slightly to allow item queries to complete questDetailsOpenTime = std::chrono::steady_clock::now() + std::chrono::milliseconds(100); gossipWindowOpen = false; - if (addonEventCallback_) addonEventCallback_("QUEST_DETAIL", {}); } bool GameHandler::hasQuestInLog(uint32_t questId) const { @@ -21829,7 +21546,6 @@ void GameHandler::handleQuestOfferReward(network::Packet& packet) { gossipWindowOpen = false; questDetailsOpen = false; questDetailsOpenTime = std::chrono::steady_clock::time_point{}; - if (addonEventCallback_) addonEventCallback_("QUEST_COMPLETE", {}); // Query item names for reward items for (const auto& item : data.choiceRewards) @@ -21888,7 +21604,6 @@ void GameHandler::closeQuestOfferReward() { void GameHandler::closeGossip() { gossipWindowOpen = false; - if (addonEventCallback_) addonEventCallback_("GOSSIP_CLOSED", {}); currentGossip = GossipMessageData{}; } @@ -22397,7 +22112,6 @@ void GameHandler::handleLootResponse(network::Packet& packet) { return; } lootWindowOpen = true; - if (addonEventCallback_) addonEventCallback_("LOOT_OPENED", {}); lastInteractedGoGuid_ = 0; // loot opened — no need to re-send in handleSpellGo pendingGameObjectLootOpens_.erase( std::remove_if(pendingGameObjectLootOpens_.begin(), pendingGameObjectLootOpens_.end(), @@ -22442,7 +22156,6 @@ void GameHandler::handleLootReleaseResponse(network::Packet& packet) { (void)packet; localLootState_.erase(currentLoot.lootGuid); lootWindowOpen = false; - if (addonEventCallback_) addonEventCallback_("LOOT_CLOSED", {}); currentLoot = LootResponseData{}; } @@ -22465,8 +22178,6 @@ void GameHandler::handleLootRemoved(network::Packet& packet) { sfx->playLootItem(); } currentLoot.items.erase(it); - if (addonEventCallback_) - addonEventCallback_("LOOT_SLOT_CLEARED", {std::to_string(slotIndex + 1)}); break; } } @@ -22478,7 +22189,6 @@ void GameHandler::handleGossipMessage(network::Packet& packet) { if (!ok) return; if (questDetailsOpen) return; // Don't reopen gossip while viewing quest gossipWindowOpen = true; - if (addonEventCallback_) addonEventCallback_("GOSSIP_SHOW", {}); vendorWindowOpen = false; // Close vendor if gossip opens // Update known quest-log entries based on gossip quests. @@ -22592,7 +22302,6 @@ void GameHandler::handleQuestgiverQuestList(network::Packet& packet) { currentGossip = std::move(data); gossipWindowOpen = true; - if (addonEventCallback_) addonEventCallback_("GOSSIP_SHOW", {}); vendorWindowOpen = false; bool hasAvailableQuest = false; @@ -22643,7 +22352,6 @@ void GameHandler::handleGossipComplete(network::Packet& packet) { } gossipWindowOpen = false; - if (addonEventCallback_) addonEventCallback_("GOSSIP_CLOSED", {}); currentGossip = GossipMessageData{}; } @@ -22772,7 +22480,6 @@ void GameHandler::handleTrainerList(network::Packet& packet) { if (!TrainerListParser::parse(packet, currentTrainerList_, isClassic)) return; trainerWindowOpen_ = true; gossipWindowOpen = false; - if (addonEventCallback_) addonEventCallback_("TRAINER_SHOW", {}); LOG_INFO("Trainer list: ", currentTrainerList_.spells.size(), " spells"); LOG_DEBUG("Known spells count: ", knownSpells.size()); @@ -22830,7 +22537,6 @@ void GameHandler::trainSpell(uint32_t spellId) { void GameHandler::closeTrainer() { trainerWindowOpen_ = false; - if (addonEventCallback_) addonEventCallback_("TRAINER_CLOSED", {}); currentTrainerList_ = TrainerListData{}; trainerTabs_.clear(); } @@ -24386,7 +24092,6 @@ void GameHandler::handleFriendList(network::Packet& packet) { entry.classId = classId; contacts_.push_back(std::move(entry)); } - if (addonEventCallback_) addonEventCallback_("FRIENDLIST_UPDATE", {}); } void GameHandler::handleContactList(network::Packet& packet) { @@ -24450,11 +24155,6 @@ void GameHandler::handleContactList(network::Packet& packet) { } LOG_INFO("SMSG_CONTACT_LIST: mask=", lastContactListMask_, " count=", lastContactListCount_); - if (addonEventCallback_) { - addonEventCallback_("FRIENDLIST_UPDATE", {}); - if (lastContactListMask_ & 0x2) // ignore list - addonEventCallback_("IGNORELIST_UPDATE", {}); - } } void GameHandler::handleFriendStatus(network::Packet& packet) { @@ -24538,7 +24238,6 @@ void GameHandler::handleFriendStatus(network::Packet& packet) { } LOG_INFO("Friend status update: ", playerName, " status=", (int)data.status); - if (addonEventCallback_) addonEventCallback_("FRIENDLIST_UPDATE", {}); } void GameHandler::handleRandomRoll(network::Packet& packet) { @@ -25318,7 +25017,6 @@ void GameHandler::handleMailListResult(network::Packet& packet) { selectedMailIndex_ = -1; showMailCompose_ = false; } - if (addonEventCallback_) addonEventCallback_("MAIL_INBOX_UPDATE", {}); } void GameHandler::handleSendMailResult(network::Packet& packet) { @@ -25393,7 +25091,6 @@ void GameHandler::handleReceivedMail(network::Packet& packet) { LOG_INFO("SMSG_RECEIVED_MAIL: New mail arrived!"); hasNewMail_ = true; addSystemChatMessage("New mail has arrived."); - if (addonEventCallback_) addonEventCallback_("UPDATE_PENDING_MAIL", {}); // If mailbox is open, refresh if (mailboxOpen_) { refreshMailList(); @@ -25887,8 +25584,6 @@ void GameHandler::handleSummonRequest(network::Packet& packet) { addSystemChatMessage(msg); LOG_INFO("SMSG_SUMMON_REQUEST: summoner=", summonerName_, " zoneId=", zoneId, " timeout=", summonTimeoutSec_, "s"); - if (addonEventCallback_) - addonEventCallback_("CONFIRM_SUMMON", {}); } void GameHandler::acceptSummon() { @@ -25947,7 +25642,6 @@ void GameHandler::handleTradeStatus(network::Packet& packet) { } tradeStatus_ = TradeStatus::PendingIncoming; addSystemChatMessage(tradePeerName_ + " wants to trade with you."); - if (addonEventCallback_) addonEventCallback_("TRADE_REQUEST", {}); break; } case 2: // OPEN_WINDOW @@ -25957,27 +25651,22 @@ void GameHandler::handleTradeStatus(network::Packet& packet) { peerTradeGold_ = 0; tradeStatus_ = TradeStatus::Open; addSystemChatMessage("Trade window opened."); - if (addonEventCallback_) addonEventCallback_("TRADE_SHOW", {}); break; case 3: // CANCELLED case 12: // CLOSE_WINDOW resetTradeState(); addSystemChatMessage("Trade cancelled."); - if (addonEventCallback_) addonEventCallback_("TRADE_CLOSED", {}); break; case 9: // REJECTED — other player clicked Decline resetTradeState(); addSystemChatMessage("Trade declined."); - if (addonEventCallback_) addonEventCallback_("TRADE_CLOSED", {}); break; case 4: // ACCEPTED (partner accepted) tradeStatus_ = TradeStatus::Accepted; addSystemChatMessage("Trade accepted. Awaiting other player..."); - if (addonEventCallback_) addonEventCallback_("TRADE_ACCEPT_UPDATE", {}); break; case 8: // COMPLETE addSystemChatMessage("Trade complete!"); - if (addonEventCallback_) addonEventCallback_("TRADE_CLOSED", {}); resetTradeState(); break; case 7: // BACK_TO_TRADE (unaccepted after a change) @@ -26435,8 +26124,6 @@ void GameHandler::handleAchievementEarned(network::Packet& packet) { LOG_INFO("SMSG_ACHIEVEMENT_EARNED: guid=0x", std::hex, guid, std::dec, " achievementId=", achievementId, " self=", isSelf, achName.empty() ? "" : " name=", achName); - if (addonEventCallback_) - addonEventCallback_("ACHIEVEMENT_EARNED", {std::to_string(achievementId)}); } // --------------------------------------------------------------------------- diff --git a/src/rendering/m2_renderer.cpp b/src/rendering/m2_renderer.cpp index f711f542..365bf7c3 100644 --- a/src/rendering/m2_renderer.cpp +++ b/src/rendering/m2_renderer.cpp @@ -1579,26 +1579,12 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) { // since we don't have the full combo table — dual-UV effects are rare edge cases. bgpu.textureUnit = 0; - // Start at full opacity; hide only if texture failed to load. + // Batch is hidden only when its named texture failed to load (avoids white shell artifacts). + // Do NOT bake transparency/color animation tracks here — they animate over time and + // baking the first keyframe value causes legitimate meshes to become invisible. + // Keep terrain clutter visible even when source texture paths are malformed. bgpu.batchOpacity = (texFailed && !groundDetailModel) ? 0.0f : 1.0f; - // Apply at-rest transparency and color alpha from the M2 animation tracks. - // These provide per-batch opacity for ghosts, ethereal effects, fading doodads, etc. - // Skip zero values: some animated tracks start at 0 and animate up, and baking - // that first keyframe would make the entire batch permanently invisible. - if (bgpu.batchOpacity > 0.0f) { - float animAlpha = 1.0f; - if (batch.colorIndex < model.colorAlphas.size()) { - float ca = model.colorAlphas[batch.colorIndex]; - if (ca > 0.001f) animAlpha *= ca; - } - if (batch.transparencyIndex < model.textureWeights.size()) { - float tw = model.textureWeights[batch.transparencyIndex]; - if (tw > 0.001f) animAlpha *= tw; - } - bgpu.batchOpacity *= animAlpha; - } - // Compute batch center and radius for glow sprite positioning if ((bgpu.blendMode >= 3 || bgpu.colorKeyBlack) && batch.indexCount > 0) { glm::vec3 sum(0.0f); diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 4daf51ff..18d0a6ec 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -268,7 +268,6 @@ static std::string evaluateMacroConditionals(const std::string& rawArg, static std::string getMacroShowtooltipArg(const std::string& macroText); void GameScreen::render(game::GameHandler& gameHandler) { - cachedGameHandler_ = &gameHandler; // Set up chat bubble callback (once) if (!chatBubbleCallbackSet_) { gameHandler.setChatBubbleCallback([this](uint64_t guid, const std::string& msg, bool isYell) { @@ -2675,107 +2674,6 @@ void GameScreen::renderChatWindow(game::GameHandler& gameHandler) { data->DeleteChars(0, data->BufTextLen); data->InsertChars(0, newBuf.c_str()); } - } else if (data->BufTextLen > 0) { - // Player name tab-completion for commands like /w, /whisper, /invite, /trade, /duel - // Also works for plain text (completes nearby player names) - std::string fullBuf(data->Buf, data->BufTextLen); - size_t spacePos = fullBuf.find(' '); - bool isNameCommand = false; - std::string namePrefix; - size_t replaceStart = 0; - - if (fullBuf[0] == '/' && spacePos != std::string::npos) { - std::string cmd = fullBuf.substr(0, spacePos); - for (char& c : cmd) c = static_cast(std::tolower(static_cast(c))); - // Commands that take a player name as the first argument after the command - if (cmd == "/w" || cmd == "/whisper" || cmd == "/invite" || - cmd == "/trade" || cmd == "/duel" || cmd == "/follow" || - cmd == "/inspect" || cmd == "/friend" || cmd == "/removefriend" || - cmd == "/ignore" || cmd == "/unignore" || cmd == "/who" || - cmd == "/t" || cmd == "/target" || cmd == "/kick" || - cmd == "/uninvite" || cmd == "/ginvite" || cmd == "/gkick") { - // Extract the partial name after the space - namePrefix = fullBuf.substr(spacePos + 1); - // Only complete the first word after the command - size_t nameSpace = namePrefix.find(' '); - if (nameSpace == std::string::npos) { - isNameCommand = true; - replaceStart = spacePos + 1; - } - } - } - - if (isNameCommand && !namePrefix.empty()) { - std::string lowerPrefix = namePrefix; - for (char& c : lowerPrefix) c = static_cast(std::tolower(static_cast(c))); - - if (self->chatTabMatchIdx_ < 0 || self->chatTabPrefix_ != lowerPrefix) { - self->chatTabPrefix_ = lowerPrefix; - self->chatTabMatches_.clear(); - // Search player name cache and nearby entities - auto* gh = self->cachedGameHandler_; - // Party/raid members - for (const auto& m : gh->getPartyData().members) { - if (m.name.empty()) continue; - std::string lname = m.name; - for (char& c : lname) c = static_cast(std::tolower(static_cast(c))); - if (lname.compare(0, lowerPrefix.size(), lowerPrefix) == 0) - self->chatTabMatches_.push_back(m.name); - } - // Friends - for (const auto& c : gh->getContacts()) { - if (!c.isFriend() || c.name.empty()) continue; - std::string lname = c.name; - for (char& cc : lname) cc = static_cast(std::tolower(static_cast(cc))); - if (lname.compare(0, lowerPrefix.size(), lowerPrefix) == 0) { - // Avoid duplicates from party - bool dup = false; - for (const auto& em : self->chatTabMatches_) - if (em == c.name) { dup = true; break; } - if (!dup) self->chatTabMatches_.push_back(c.name); - } - } - // Nearby visible players - for (const auto& [guid, entity] : gh->getEntityManager().getEntities()) { - if (!entity || entity->getType() != game::ObjectType::PLAYER) continue; - auto player = std::static_pointer_cast(entity); - if (player->getName().empty()) continue; - std::string lname = player->getName(); - for (char& cc : lname) cc = static_cast(std::tolower(static_cast(cc))); - if (lname.compare(0, lowerPrefix.size(), lowerPrefix) == 0) { - bool dup = false; - for (const auto& em : self->chatTabMatches_) - if (em == player->getName()) { dup = true; break; } - if (!dup) self->chatTabMatches_.push_back(player->getName()); - } - } - // Last whisper sender - if (!gh->getLastWhisperSender().empty()) { - std::string lname = gh->getLastWhisperSender(); - for (char& cc : lname) cc = static_cast(std::tolower(static_cast(cc))); - if (lname.compare(0, lowerPrefix.size(), lowerPrefix) == 0) { - bool dup = false; - for (const auto& em : self->chatTabMatches_) - if (em == gh->getLastWhisperSender()) { dup = true; break; } - if (!dup) self->chatTabMatches_.insert(self->chatTabMatches_.begin(), gh->getLastWhisperSender()); - } - } - self->chatTabMatchIdx_ = 0; - } else { - ++self->chatTabMatchIdx_; - if (self->chatTabMatchIdx_ >= static_cast(self->chatTabMatches_.size())) - self->chatTabMatchIdx_ = 0; - } - - if (!self->chatTabMatches_.empty()) { - std::string match = self->chatTabMatches_[self->chatTabMatchIdx_]; - std::string prefix = fullBuf.substr(0, replaceStart); - std::string newBuf = prefix + match; - if (self->chatTabMatches_.size() == 1) newBuf += ' '; - data->DeleteChars(0, data->BufTextLen); - data->InsertChars(0, newBuf.c_str()); - } - } } return 0; } @@ -2889,18 +2787,6 @@ void GameScreen::processTargetInput(game::GameHandler& gameHandler) { gameHandler.closeBank(); } else if (gameHandler.isTrainerWindowOpen()) { gameHandler.closeTrainer(); - } else if (gameHandler.isMailboxOpen()) { - gameHandler.closeMailbox(); - } else if (gameHandler.isAuctionHouseOpen()) { - gameHandler.closeAuctionHouse(); - } else if (gameHandler.isQuestDetailsOpen()) { - gameHandler.declineQuest(); - } else if (gameHandler.isQuestOfferRewardOpen()) { - gameHandler.closeQuestOfferReward(); - } else if (gameHandler.isQuestRequestItemsOpen()) { - gameHandler.closeQuestRequestItems(); - } else if (gameHandler.isTradeOpen()) { - gameHandler.cancelTrade(); } else if (showWhoWindow_) { showWhoWindow_ = false; } else if (showCombatLog_) {