#include "addons/lua_engine.hpp" #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 extern "C" { #include #include #include } namespace wowee::addons { // Retrieve GameHandler pointer stored in Lua registry static game::GameHandler* getGameHandler(lua_State* L) { lua_getfield(L, LUA_REGISTRYINDEX, "wowee_game_handler"); auto* gh = static_cast(lua_touserdata(L, -1)); lua_pop(L, 1); return gh; } // WoW-compatible print() — outputs to chat window instead of stdout static int lua_wow_print(lua_State* L) { int nargs = lua_gettop(L); std::string result; for (int i = 1; i <= nargs; i++) { if (i > 1) result += '\t'; // Lua 5.1: use lua_tostring (luaL_tolstring is 5.3+) if (lua_isstring(L, i) || lua_isnumber(L, i)) { const char* s = lua_tostring(L, i); if (s) result += s; } else if (lua_isboolean(L, i)) { result += lua_toboolean(L, i) ? "true" : "false"; } else if (lua_isnil(L, i)) { result += "nil"; } else { result += lua_typename(L, lua_type(L, i)); } } auto* gh = getGameHandler(L); if (gh) { game::MessageChatData msg; msg.type = game::ChatType::SYSTEM; msg.language = game::ChatLanguage::UNIVERSAL; msg.message = result; gh->addLocalChatMessage(msg); } LOG_INFO("[Lua] ", result); return 0; } // WoW-compatible message() — same as print for now static int lua_wow_message(lua_State* L) { return lua_wow_print(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; try { idx = std::stoi(uid.substr(5)); } catch (...) { return 0; } if (idx < 1 || idx > 4) return 0; const auto& pd = gh->getPartyData(); // party members exclude self; index 1-based int found = 0; for (const auto& m : pd.members) { if (m.guid == gh->getPlayerGuid()) continue; if (++found == idx) return m.guid; } return 0; } if (uid.rfind("raid", 0) == 0 && uid.size() > 4 && uid[4] != 'p') { int idx = 0; try { idx = std::stoi(uid.substr(4)); } catch (...) { return 0; } if (idx < 1 || idx > 40) return 0; const auto& pd = gh->getPartyData(); if (idx <= static_cast(pd.members.size())) return pd.members[idx - 1].guid; return 0; } return 0; } // Helper: resolve unit IDs (player, target, focus, mouseover, pet, targettarget, focustarget, etc.) to entity static game::Unit* resolveUnit(lua_State* L, const char* unitId) { auto* gh = getGameHandler(L); if (!gh || !unitId) return nullptr; std::string uid(unitId); for (char& c : uid) c = static_cast(std::tolower(static_cast(c))); uint64_t guid = resolveUnitGuid(gh, uid); if (guid == 0) return nullptr; auto entity = gh->getEntityManager().getEntity(guid); if (!entity) return nullptr; return dynamic_cast(entity.get()); } // --- 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"); } } 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); } 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); } 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); } 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); } 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); } 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); } return 1; } static int lua_UnitIsDead(lua_State* L) { const char* uid = luaL_optstring(L, 1, "player"); auto* unit = resolveUnit(L, uid); if (unit) { lua_pushboolean(L, unit->getHealth() == 0); } 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_pushboolean(L, pm ? (pm->curHealth == 0 && pm->maxHealth > 0) : 0); } return 1; } static int lua_UnitClass(lua_State* L) { const char* uid = luaL_optstring(L, 1, "player"); auto* gh = getGameHandler(L); auto* unit = resolveUnit(L, uid); if (unit && gh) { static const char* kClasses[] = {"", "Warrior","Paladin","Hunter","Rogue","Priest", "Death Knight","Shaman","Mage","Warlock","","Druid"}; uint8_t classId = 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"; lua_pushstring(L, name); lua_pushstring(L, name); // WoW returns localized + English lua_pushnumber(L, classId); return 3; } lua_pushstring(L, "Unknown"); lua_pushstring(L, "Unknown"); lua_pushnumber(L, 0); 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; } // UnitIsTapped(unit) — true if mob is tapped (tagged by any player) static int lua_UnitIsTapped(lua_State* L) { const char* uid = luaL_optstring(L, 1, "target"); auto* unit = resolveUnit(L, uid); if (!unit) { lua_pushboolean(L, 0); return 1; } lua_pushboolean(L, (unit->getDynamicFlags() & 0x0004) != 0); // UNIT_DYNFLAG_TAPPED_BY_PLAYER return 1; } // UnitIsTappedByPlayer(unit) — true if tapped by the local player (can loot) static int lua_UnitIsTappedByPlayer(lua_State* L) { const char* uid = luaL_optstring(L, 1, "target"); auto* unit = resolveUnit(L, uid); if (!unit) { lua_pushboolean(L, 0); return 1; } uint32_t df = unit->getDynamicFlags(); // Tapped by player: has TAPPED flag but also LOOTABLE or TAPPED_BY_ALL bool tapped = (df & 0x0004) != 0; bool lootable = (df & 0x0001) != 0; bool sharedTag = (df & 0x0008) != 0; lua_pushboolean(L, tapped && (lootable || sharedTag)); return 1; } // UnitIsTappedByAllThreatList(unit) — true if shared-tag mob static int lua_UnitIsTappedByAllThreatList(lua_State* L) { const char* uid = luaL_optstring(L, 1, "target"); auto* unit = resolveUnit(L, uid); if (!unit) { lua_pushboolean(L, 0); return 1; } lua_pushboolean(L, (unit->getDynamicFlags() & 0x0008) != 0); return 1; } // UnitThreatSituation(unit, mobUnit) → 0=not tanking, 1=not tanking but threat, 2=insecurely tanking, 3=securely tanking static int lua_UnitThreatSituation(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { lua_pushnumber(L, 0); return 1; } const char* uid = luaL_optstring(L, 1, "player"); const char* mobUid = luaL_optstring(L, 2, nullptr); std::string uidStr(uid); for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); uint64_t playerUnitGuid = resolveUnitGuid(gh, uidStr); if (playerUnitGuid == 0) { lua_pushnumber(L, 0); return 1; } // If no mob specified, check general combat threat against current target uint64_t mobGuid = 0; if (mobUid && *mobUid) { std::string mStr(mobUid); for (char& c : mStr) c = static_cast(std::tolower(static_cast(c))); mobGuid = resolveUnitGuid(gh, mStr); } // Approximate threat: check if the mob is targeting this unit if (mobGuid != 0) { auto mobEntity = gh->getEntityManager().getEntity(mobGuid); if (mobEntity) { const auto& fields = mobEntity->getFields(); auto loIt = fields.find(game::fieldIndex(game::UF::UNIT_FIELD_TARGET_LO)); if (loIt != fields.end()) { uint64_t mobTarget = loIt->second; auto hiIt = fields.find(game::fieldIndex(game::UF::UNIT_FIELD_TARGET_HI)); if (hiIt != fields.end()) mobTarget |= (static_cast(hiIt->second) << 32); if (mobTarget == playerUnitGuid) { lua_pushnumber(L, 3); // securely tanking return 1; } } } } // Check if player is in combat (basic threat indicator) if (playerUnitGuid == gh->getPlayerGuid() && gh->isInCombat()) { lua_pushnumber(L, 1); // in combat but not tanking return 1; } lua_pushnumber(L, 0); return 1; } // UnitDetailedThreatSituation(unit, mobUnit) → isTanking, status, threatPct, rawThreatPct, threatValue static int lua_UnitDetailedThreatSituation(lua_State* L) { // Use UnitThreatSituation logic for the basics auto* gh = getGameHandler(L); if (!gh) { lua_pushboolean(L, 0); lua_pushnumber(L, 0); lua_pushnumber(L, 0); lua_pushnumber(L, 0); lua_pushnumber(L, 0); return 5; } const char* uid = luaL_optstring(L, 1, "player"); const char* mobUid = luaL_optstring(L, 2, nullptr); std::string uidStr(uid); for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); uint64_t unitGuid = resolveUnitGuid(gh, uidStr); bool isTanking = false; int status = 0; if (unitGuid != 0 && mobUid && *mobUid) { std::string mStr(mobUid); for (char& c : mStr) c = static_cast(std::tolower(static_cast(c))); uint64_t mobGuid = resolveUnitGuid(gh, mStr); if (mobGuid != 0) { auto mobEnt = gh->getEntityManager().getEntity(mobGuid); if (mobEnt) { const auto& f = mobEnt->getFields(); auto lo = f.find(game::fieldIndex(game::UF::UNIT_FIELD_TARGET_LO)); if (lo != f.end()) { uint64_t mt = lo->second; auto hi = f.find(game::fieldIndex(game::UF::UNIT_FIELD_TARGET_HI)); if (hi != f.end()) mt |= (static_cast(hi->second) << 32); if (mt == unitGuid) { isTanking = true; status = 3; } } } } } lua_pushboolean(L, isTanking); lua_pushnumber(L, status); lua_pushnumber(L, isTanking ? 100.0 : 0.0); // threatPct lua_pushnumber(L, isTanking ? 100.0 : 0.0); // rawThreatPct lua_pushnumber(L, 0); // threatValue (not available without server threat data) return 5; } // UnitDistanceSquared(unit) → distSq, canCalculate static int lua_UnitDistanceSquared(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { lua_pushnumber(L, 0); lua_pushboolean(L, 0); return 2; } 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 || guid == gh->getPlayerGuid()) { lua_pushnumber(L, 0); lua_pushboolean(L, 0); return 2; } auto targetEnt = gh->getEntityManager().getEntity(guid); auto playerEnt = gh->getEntityManager().getEntity(gh->getPlayerGuid()); if (!targetEnt || !playerEnt) { lua_pushnumber(L, 0); lua_pushboolean(L, 0); return 2; } float dx = playerEnt->getX() - targetEnt->getX(); float dy = playerEnt->getY() - targetEnt->getY(); float dz = playerEnt->getZ() - targetEnt->getZ(); lua_pushnumber(L, dx*dx + dy*dy + dz*dz); lua_pushboolean(L, 1); return 2; } // CheckInteractDistance(unit, distIndex) → boolean // distIndex: 1=inspect(28yd), 2=trade(11yd), 3=duel(10yd), 4=follow(28yd) static int lua_CheckInteractDistance(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { lua_pushboolean(L, 0); return 1; } const char* uid = luaL_checkstring(L, 1); int distIdx = static_cast(luaL_optnumber(L, 2, 4)); 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 targetEnt = gh->getEntityManager().getEntity(guid); auto playerEnt = gh->getEntityManager().getEntity(gh->getPlayerGuid()); if (!targetEnt || !playerEnt) { lua_pushboolean(L, 0); return 1; } float dx = playerEnt->getX() - targetEnt->getX(); float dy = playerEnt->getY() - targetEnt->getY(); float dz = playerEnt->getZ() - targetEnt->getZ(); float dist = std::sqrt(dx*dx + dy*dy + dz*dz); float maxDist = 28.0f; // default: follow/inspect range switch (distIdx) { case 1: maxDist = 28.0f; break; // inspect case 2: maxDist = 11.11f; break; // trade case 3: maxDist = 9.9f; break; // duel case 4: maxDist = 28.0f; break; // follow } lua_pushboolean(L, dist <= maxDist); return 1; } // IsSpellInRange(spellName, unit) → 0 or 1 (nil if can't determine) static int lua_IsSpellInRange(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { lua_pushnil(L); return 1; } const char* spellNameOrId = luaL_checkstring(L, 1); const char* uid = luaL_optstring(L, 2, "target"); // Resolve spell ID uint32_t spellId = 0; if (spellNameOrId[0] >= '0' && spellNameOrId[0] <= '9') { spellId = static_cast(strtoul(spellNameOrId, nullptr, 10)); } else { std::string nameLow(spellNameOrId); for (char& c : nameLow) c = static_cast(std::tolower(static_cast(c))); for (uint32_t sid : gh->getKnownSpells()) { std::string sn = gh->getSpellName(sid); for (char& c : sn) c = static_cast(std::tolower(static_cast(c))); if (sn == nameLow) { spellId = sid; break; } } } if (spellId == 0) { lua_pushnil(L); return 1; } // Get spell max range from DBC auto data = gh->getSpellData(spellId); if (data.maxRange <= 0.0f) { lua_pushnil(L); return 1; } // Resolve target position 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; } auto targetEnt = gh->getEntityManager().getEntity(guid); auto playerEnt = gh->getEntityManager().getEntity(gh->getPlayerGuid()); if (!targetEnt || !playerEnt) { lua_pushnil(L); return 1; } float dx = playerEnt->getX() - targetEnt->getX(); float dy = playerEnt->getY() - targetEnt->getY(); float dz = playerEnt->getZ() - targetEnt->getZ(); float dist = std::sqrt(dx*dx + dy*dy + dz*dz); lua_pushnumber(L, dist <= data.maxRange ? 1 : 0); return 1; } // UnitIsVisible(unit) → boolean (entity exists in the client's entity manager) static int lua_UnitIsVisible(lua_State* L) { const char* uid = luaL_optstring(L, 1, "target"); auto* unit = resolveUnit(L, uid); lua_pushboolean(L, unit != nullptr); return 1; } // UnitGroupRolesAssigned(unit) → "TANK", "HEALER", "DAMAGER", or "NONE" static int lua_UnitGroupRolesAssigned(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { lua_pushstring(L, "NONE"); 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_pushstring(L, "NONE"); return 1; } const auto& pd = gh->getPartyData(); for (const auto& m : pd.members) { if (m.guid == guid) { // WotLK roles bitmask: 0x02=Tank, 0x04=Healer, 0x08=DPS if (m.roles & 0x02) { lua_pushstring(L, "TANK"); return 1; } if (m.roles & 0x04) { lua_pushstring(L, "HEALER"); return 1; } if (m.roles & 0x08) { lua_pushstring(L, "DAMAGER"); return 1; } break; } } lua_pushstring(L, "NONE"); return 1; } // UnitCanAttack(unit, otherUnit) → boolean static int lua_UnitCanAttack(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { lua_pushboolean(L, 0); return 1; } const char* uid1 = luaL_checkstring(L, 1); const char* uid2 = luaL_checkstring(L, 2); std::string u1(uid1), u2(uid2); for (char& c : u1) c = static_cast(std::tolower(static_cast(c))); 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 == 0 || g2 == 0 || g1 == g2) { lua_pushboolean(L, 0); return 1; } // Check if unit2 is hostile to unit1 auto* unit2 = resolveUnit(L, uid2); if (unit2 && unit2->isHostile()) { lua_pushboolean(L, 1); } else { lua_pushboolean(L, 0); } return 1; } // UnitCanCooperate(unit, otherUnit) → boolean static int lua_UnitCanCooperate(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { lua_pushboolean(L, 0); return 1; } (void)luaL_checkstring(L, 1); // unit1 (unused — cooperation is based on unit2's hostility) const char* uid2 = luaL_checkstring(L, 2); auto* unit2 = resolveUnit(L, uid2); if (!unit2) { lua_pushboolean(L, 0); return 1; } lua_pushboolean(L, !unit2->isHostile()); return 1; } // UnitCreatureFamily(unit) → familyName or nil static int lua_UnitCreatureFamily(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; } auto entity = gh->getEntityManager().getEntity(guid); if (!entity || entity->getType() == game::ObjectType::PLAYER) { lua_pushnil(L); return 1; } auto unit = std::dynamic_pointer_cast(entity); if (!unit) { lua_pushnil(L); return 1; } uint32_t family = gh->getCreatureFamily(unit->getEntry()); if (family == 0) { lua_pushnil(L); return 1; } static const char* kFamilies[] = { "", "Wolf", "Cat", "Spider", "Bear", "Boar", "Crocolisk", "Carrion Bird", "Crab", "Gorilla", "Raptor", "", "Tallstrider", "", "", "Felhunter", "Voidwalker", "Succubus", "", "Doomguard", "Scorpid", "Turtle", "", "Imp", "Bat", "Hyena", "Bird of Prey", "Wind Serpent", "", "Dragonhawk", "Ravager", "Warp Stalker", "Sporebat", "Nether Ray", "Serpent", "Moth", "Chimaera", "Devilsaur", "Ghoul", "Silithid", "Worm", "Rhino", "Wasp", "Core Hound", "Spirit Beast" }; lua_pushstring(L, (family < sizeof(kFamilies)/sizeof(kFamilies[0]) && kFamilies[family][0]) ? kFamilies[family] : "Beast"); return 1; } // UnitOnTaxi(unit) → boolean (true if on a flight path) static int lua_UnitOnTaxi(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->isOnTaxiFlight()); } else { lua_pushboolean(L, 0); // Can't determine for other units } 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) { auto* gh = getGameHandler(L); lua_pushnumber(L, gh ? static_cast(gh->getMoneyCopper()) : 0.0); return 1; } static int lua_IsInGroup(lua_State* L) { auto* gh = getGameHandler(L); lua_pushboolean(L, gh && gh->isInGroup()); return 1; } static int lua_IsInRaid(lua_State* L) { auto* gh = getGameHandler(L); lua_pushboolean(L, gh && gh->isInGroup() && gh->getPartyData().groupType == 1); return 1; } static int lua_GetPlayerMapPosition(lua_State* L) { auto* gh = getGameHandler(L); if (gh) { const auto& mi = gh->getMovementInfo(); lua_pushnumber(L, mi.x); lua_pushnumber(L, mi.y); return 2; } lua_pushnumber(L, 0); lua_pushnumber(L, 0); return 2; } 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; } 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); } } 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; } static int lua_UnitPowerType(lua_State* L) { const char* uid = luaL_optstring(L, 1, "player"); auto* unit = resolveUnit(L, uid); static const char* kPowerNames[] = {"MANA","RAGE","FOCUS","ENERGY","HAPPINESS","","RUNIC_POWER"}; if (unit) { uint8_t pt = unit->getPowerType(); lua_pushnumber(L, pt); lua_pushstring(L, (pt < 7) ? kPowerNames[pt] : "MANA"); return 2; } // 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); if (pm) { uint8_t pt = pm->powerType; lua_pushnumber(L, pt); lua_pushstring(L, (pt < 7) ? kPowerNames[pt] : "MANA"); return 2; } lua_pushnumber(L, 0); lua_pushstring(L, "MANA"); return 2; } static int lua_GetNumGroupMembers(lua_State* L) { auto* gh = getGameHandler(L); lua_pushnumber(L, gh ? gh->getPartyData().memberCount : 0); return 1; } static int lua_UnitGUID(lua_State* L) { const char* uid = luaL_optstring(L, 1, "player"); auto* gh = getGameHandler(L); if (!gh) { lua_pushnil(L); 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_pushnil(L); return 1; } char buf[32]; snprintf(buf, sizeof(buf), "0x%016llX", (unsigned long long)guid); lua_pushstring(L, buf); return 1; } static int lua_UnitIsPlayer(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); auto entity = guid ? gh->getEntityManager().getEntity(guid) : nullptr; lua_pushboolean(L, entity && entity->getType() == game::ObjectType::PLAYER); return 1; } static int lua_InCombatLockdown(lua_State* L) { auto* gh = getGameHandler(L); lua_pushboolean(L, gh && gh->isInCombat()); return 1; } // --- Addon Info API --- // These need the AddonManager pointer stored in registry static int lua_GetNumAddOns(lua_State* L) { lua_getfield(L, LUA_REGISTRYINDEX, "wowee_addon_count"); return 1; } static int lua_GetAddOnInfo(lua_State* L) { // Accept index (1-based) or addon name 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)) { // Search by name 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; } lua_rawgeti(L, -1, idx); if (!lua_istable(L, -1)) { lua_pop(L, 2); lua_pushnil(L); return 1; } lua_getfield(L, -1, "name"); lua_getfield(L, -2, "title"); lua_getfield(L, -3, "notes"); lua_pushboolean(L, 1); // loadable (always true for now) lua_pushstring(L, "INSECURE"); // security lua_pop(L, 1); // pop addon info entry (keep others) // Return: name, title, notes, loadable, reason, security 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) { auto* gh = getGameHandler(L); if (!gh) { lua_pushnil(L); return 1; } const char* uid = luaL_optstring(L, 1, "player"); int index = static_cast(luaL_optnumber(L, 2, 1)); if (index < 1) { lua_pushnil(L); return 1; } std::string uidStr(uid); for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); const std::vector* auras = nullptr; if (uidStr == "player") auras = &gh->getPlayerAuras(); else if (uidStr == "target") auras = &gh->getTargetAuras(); else { // Try party/raid/focus via GUID lookup in unitAurasCache uint64_t guid = resolveUnitGuid(gh, uidStr); if (guid != 0) auras = gh->getUnitAuras(guid); } if (!auras) { lua_pushnil(L); return 1; } // Filter to buffs or debuffs and find the Nth one int found = 0; for (const auto& aura : *auras) { if (aura.isEmpty() || aura.spellId == 0) continue; bool isDebuff = (aura.flags & 0x80) != 0; if (wantBuff ? isDebuff : !isDebuff) continue; found++; if (found == index) { // Return: name, rank, icon, count, debuffType, duration, expirationTime, ...spellId std::string name = gh->getSpellName(aura.spellId); lua_pushstring(L, name.empty() ? "Unknown" : name.c_str()); // name lua_pushstring(L, ""); // rank std::string iconPath = gh->getSpellIconPath(aura.spellId); if (!iconPath.empty()) lua_pushstring(L, iconPath.c_str()); else lua_pushnil(L); // icon texture path lua_pushnumber(L, aura.charges); // count // debuffType: resolve from Spell.dbc dispel type { uint8_t dt = gh->getSpellDispelType(aura.spellId); switch (dt) { case 1: lua_pushstring(L, "Magic"); break; case 2: lua_pushstring(L, "Curse"); break; case 3: lua_pushstring(L, "Disease"); break; case 4: lua_pushstring(L, "Poison"); break; default: lua_pushnil(L); break; } } lua_pushnumber(L, aura.maxDurationMs > 0 ? aura.maxDurationMs / 1000.0 : 0); // duration // expirationTime: GetTime() + remaining seconds (so addons can compute countdown) if (aura.durationMs > 0) { uint64_t auraNowMs = static_cast( std::chrono::duration_cast( std::chrono::steady_clock::now().time_since_epoch()).count()); int32_t remMs = aura.getRemainingMs(auraNowMs); // GetTime epoch = steady_clock relative to engine start static auto sStart = std::chrono::steady_clock::now(); double nowSec = std::chrono::duration( std::chrono::steady_clock::now() - sStart).count(); lua_pushnumber(L, nowSec + remMs / 1000.0); } else { lua_pushnumber(L, 0); // permanent aura } // caster: return unit ID string if caster is known if (aura.casterGuid != 0) { if (aura.casterGuid == gh->getPlayerGuid()) lua_pushstring(L, "player"); else if (aura.casterGuid == gh->getTargetGuid()) lua_pushstring(L, "target"); else if (aura.casterGuid == gh->getFocusGuid()) lua_pushstring(L, "focus"); else if (aura.casterGuid == gh->getPetGuid()) lua_pushstring(L, "pet"); else { char cBuf[32]; snprintf(cBuf, sizeof(cBuf), "0x%016llX", (unsigned long long)aura.casterGuid); lua_pushstring(L, cBuf); } } else { lua_pushnil(L); } lua_pushboolean(L, 0); // isStealable lua_pushboolean(L, 0); // shouldConsolidate lua_pushnumber(L, aura.spellId); // spellId return 11; } } lua_pushnil(L); return 1; } static int lua_UnitBuff(lua_State* L) { return lua_UnitAura(L, true); } static int lua_UnitDebuff(lua_State* L) { return lua_UnitAura(L, false); } // UnitAura(unit, index, filter) — generic aura query with filter string // filter: "HELPFUL" = buffs, "HARMFUL" = debuffs, "PLAYER" = cast by player, // "HELPFUL|PLAYER" = buffs cast by player, etc. static int lua_UnitAuraGeneric(lua_State* L) { const char* filter = luaL_optstring(L, 3, "HELPFUL"); std::string f(filter ? filter : "HELPFUL"); for (char& c : f) c = static_cast(std::toupper(static_cast(c))); bool wantBuff = (f.find("HARMFUL") == std::string::npos); return lua_UnitAura(L, wantBuff); } // --- Action API --- static int lua_SendChatMessage(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) return 0; const char* msg = luaL_checkstring(L, 1); const char* chatType = luaL_optstring(L, 2, "SAY"); // language arg (3) ignored — server determines language const char* target = luaL_optstring(L, 4, ""); std::string typeStr(chatType); for (char& c : typeStr) c = static_cast(std::toupper(static_cast(c))); game::ChatType ct = game::ChatType::SAY; if (typeStr == "SAY") ct = game::ChatType::SAY; else if (typeStr == "YELL") ct = game::ChatType::YELL; else if (typeStr == "PARTY") ct = game::ChatType::PARTY; else if (typeStr == "GUILD") ct = game::ChatType::GUILD; else if (typeStr == "OFFICER") ct = game::ChatType::OFFICER; else if (typeStr == "RAID") ct = game::ChatType::RAID; else if (typeStr == "WHISPER") ct = game::ChatType::WHISPER; else if (typeStr == "BATTLEGROUND") ct = game::ChatType::BATTLEGROUND; std::string targetStr(target && *target ? target : ""); gh->sendChatMessage(ct, msg, targetStr); return 0; } static int lua_CastSpellByName(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) return 0; const char* name = luaL_checkstring(L, 1); if (!name || !*name) return 0; // Find highest rank of spell by name (same logic as /cast) std::string nameLow(name); for (char& c : nameLow) c = static_cast(std::tolower(static_cast(c))); uint32_t bestId = 0; int bestRank = -1; for (uint32_t sid : gh->getKnownSpells()) { std::string sn = gh->getSpellName(sid); for (char& c : sn) c = static_cast(std::tolower(static_cast(c))); if (sn != nameLow) continue; int rank = 0; const std::string& rk = gh->getSpellRank(sid); if (!rk.empty()) { std::string rkl = rk; for (char& c : rkl) c = static_cast(std::tolower(static_cast(c))); if (rkl.rfind("rank ", 0) == 0) { try { rank = std::stoi(rkl.substr(5)); } catch (...) {} } } if (rank > bestRank) { bestRank = rank; bestId = sid; } } if (bestId != 0) { uint64_t target = gh->hasTarget() ? gh->getTargetGuid() : 0; gh->castSpell(bestId, target); } 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)); lua_pushboolean(L, gh && gh->getKnownSpells().count(spellId)); return 1; } static int lua_GetSpellCooldown(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { lua_pushnumber(L, 0); lua_pushnumber(L, 0); return 2; } // Accept spell name or ID uint32_t spellId = 0; if (lua_isnumber(L, 1)) { spellId = static_cast(lua_tonumber(L, 1)); } else { const char* name = luaL_checkstring(L, 1); std::string nameLow(name); for (char& c : nameLow) c = static_cast(std::tolower(static_cast(c))); for (uint32_t sid : gh->getKnownSpells()) { std::string sn = gh->getSpellName(sid); for (char& c : sn) c = static_cast(std::tolower(static_cast(c))); if (sn == nameLow) { spellId = sid; break; } } } float cd = gh->getSpellCooldown(spellId); // WoW returns (start, duration, enabled) where remaining = start + duration - GetTime() // Compute start = GetTime() - elapsed, duration = total cooldown static auto sStart = std::chrono::steady_clock::now(); double nowSec = std::chrono::duration( std::chrono::steady_clock::now() - sStart).count(); if (cd > 0.01f) { lua_pushnumber(L, nowSec); // start (approximate — we don't track exact start) lua_pushnumber(L, cd); // duration (remaining, used as total for simplicity) } else { lua_pushnumber(L, 0); // not on cooldown lua_pushnumber(L, 0); } lua_pushnumber(L, 1); // enabled (always 1 — spell is usable) return 3; } static int lua_HasTarget(lua_State* L) { auto* gh = getGameHandler(L); lua_pushboolean(L, gh && gh->hasTarget()); 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; } // GetSpellPowerCost(spellId) → {{ type=powerType, cost=manaCost, name=powerName }} static int lua_GetSpellPowerCost(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { lua_newtable(L); return 1; } uint32_t spellId = static_cast(luaL_checknumber(L, 1)); auto data = gh->getSpellData(spellId); lua_newtable(L); // outer table (array of cost entries) if (data.manaCost > 0) { lua_newtable(L); // cost entry lua_pushnumber(L, data.powerType); lua_setfield(L, -2, "type"); lua_pushnumber(L, data.manaCost); lua_setfield(L, -2, "cost"); static const char* kPowerNames[] = {"MANA","RAGE","FOCUS","ENERGY","HAPPINESS","","RUNIC_POWER"}; lua_pushstring(L, data.powerType < 7 ? kPowerNames[data.powerType] : "MANA"); lua_setfield(L, -2, "name"); lua_rawseti(L, -2, 1); // outer[1] = entry } return 1; } // --- GetSpellInfo / GetSpellTexture --- // GetSpellInfo(spellIdOrName) -> name, rank, icon, castTime, minRange, maxRange, spellId static int lua_GetSpellInfo(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { lua_pushnil(L); return 1; } uint32_t spellId = 0; if (lua_isnumber(L, 1)) { spellId = static_cast(lua_tonumber(L, 1)); } else if (lua_isstring(L, 1)) { const char* name = lua_tostring(L, 1); if (!name || !*name) { lua_pushnil(L); return 1; } std::string nameLow(name); for (char& c : nameLow) c = static_cast(std::tolower(static_cast(c))); int bestRank = -1; for (uint32_t sid : gh->getKnownSpells()) { std::string sn = gh->getSpellName(sid); for (char& c : sn) c = static_cast(std::tolower(static_cast(c))); if (sn != nameLow) continue; int rank = 0; const std::string& rk = gh->getSpellRank(sid); if (!rk.empty()) { std::string rkl = rk; for (char& c : rkl) c = static_cast(std::tolower(static_cast(c))); if (rkl.rfind("rank ", 0) == 0) { try { rank = std::stoi(rkl.substr(5)); } catch (...) {} } } if (rank > bestRank) { bestRank = rank; spellId = sid; } } } if (spellId == 0) { lua_pushnil(L); return 1; } std::string name = gh->getSpellName(spellId); if (name.empty()) { lua_pushnil(L); return 1; } lua_pushstring(L, name.c_str()); // 1: name const std::string& rank = gh->getSpellRank(spellId); lua_pushstring(L, rank.c_str()); // 2: rank std::string iconPath = gh->getSpellIconPath(spellId); if (!iconPath.empty()) lua_pushstring(L, iconPath.c_str()); else lua_pushnil(L); // 3: icon texture path // 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, spellId); // 7: spellId return 7; } // GetSpellTexture(spellIdOrName) -> icon texture path string static int lua_GetSpellTexture(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { lua_pushnil(L); return 1; } uint32_t spellId = 0; if (lua_isnumber(L, 1)) { spellId = static_cast(lua_tonumber(L, 1)); } else if (lua_isstring(L, 1)) { const char* name = lua_tostring(L, 1); if (!name || !*name) { lua_pushnil(L); return 1; } std::string nameLow(name); for (char& c : nameLow) c = static_cast(std::tolower(static_cast(c))); for (uint32_t sid : gh->getKnownSpells()) { std::string sn = gh->getSpellName(sid); for (char& c : sn) c = static_cast(std::tolower(static_cast(c))); if (sn == nameLow) { spellId = sid; break; } } } if (spellId == 0) { lua_pushnil(L); return 1; } std::string iconPath = gh->getSpellIconPath(spellId); if (!iconPath.empty()) lua_pushstring(L, iconPath.c_str()); else lua_pushnil(L); return 1; } // GetItemInfo(itemId) -> name, link, quality, iLevel, reqLevel, class, subclass, maxStack, equipSlot, texture, vendorPrice static int lua_GetItemInfo(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { lua_pushnil(L); return 1; } uint32_t itemId = 0; if (lua_isnumber(L, 1)) { itemId = static_cast(lua_tonumber(L, 1)); } else if (lua_isstring(L, 1)) { // Try to parse "item:12345" link format const char* s = lua_tostring(L, 1); std::string str(s ? s : ""); auto pos = str.find("item:"); if (pos != std::string::npos) { try { itemId = static_cast(std::stoul(str.substr(pos + 5))); } catch (...) {} } } if (itemId == 0) { lua_pushnil(L); return 1; } const auto* info = gh->getItemInfo(itemId); if (!info) { lua_pushnil(L); return 1; } lua_pushstring(L, info->name.c_str()); // 1: name // Build item link string: |cFFFFFFFF|Hitem:ID:0:0:0:0:0:0:0|h[Name]|h|r char link[256]; snprintf(link, sizeof(link), "|cFFFFFFFF|Hitem:%u:0:0:0:0:0:0:0|h[%s]|h|r", itemId, info->name.c_str()); lua_pushstring(L, link); // 2: link lua_pushnumber(L, info->quality); // 3: quality lua_pushnumber(L, info->itemLevel); // 4: iLevel lua_pushnumber(L, info->requiredLevel); // 5: requiredLevel lua_pushstring(L, ""); // 6: class (type string) lua_pushstring(L, ""); // 7: subclass lua_pushnumber(L, info->maxStack > 0 ? info->maxStack : 1); // 8: maxStack lua_pushstring(L, ""); // 9: equipSlot // 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_pushnumber(L, info->sellPrice); // 11: vendorPrice return 11; } // --- Locale/Build/Realm info --- static int lua_GetLocale(lua_State* L) { lua_pushstring(L, "enUS"); return 1; } static int lua_GetBuildInfo(lua_State* L) { // Return WotLK defaults; expansion-specific version detection would need // access to the expansion registry which isn't available here. lua_pushstring(L, "3.3.5a"); // 1: version lua_pushnumber(L, 12340); // 2: buildNumber lua_pushstring(L, "Jan 1 2025");// 3: date lua_pushnumber(L, 30300); // 4: tocVersion return 4; } static int lua_GetCurrentMapAreaID(lua_State* L) { auto* gh = getGameHandler(L); lua_pushnumber(L, gh ? gh->getCurrentMapId() : 0); return 1; } // GetZoneText() / GetRealZoneText() → current zone name static int lua_GetZoneText(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { lua_pushstring(L, ""); return 1; } uint32_t zoneId = gh->getWorldStateZoneId(); if (zoneId != 0) { std::string name = gh->getWhoAreaName(zoneId); if (!name.empty()) { lua_pushstring(L, name.c_str()); return 1; } } lua_pushstring(L, ""); return 1; } // GetSubZoneText() → subzone name (same as zone for now — server doesn't always send subzone) static int lua_GetSubZoneText(lua_State* L) { return lua_GetZoneText(L); // Best-effort: zone and subzone often overlap } // GetMinimapZoneText() → zone name displayed near minimap static int lua_GetMinimapZoneText(lua_State* L) { return lua_GetZoneText(L); } // --- Player State API --- // These replace the hardcoded "return false" Lua stubs with real game state. static int lua_IsMounted(lua_State* L) { auto* gh = getGameHandler(L); lua_pushboolean(L, gh && gh->isMounted()); return 1; } static int lua_IsFlying(lua_State* L) { auto* gh = getGameHandler(L); lua_pushboolean(L, gh && gh->isPlayerFlying()); return 1; } static int lua_IsSwimming(lua_State* L) { auto* gh = getGameHandler(L); lua_pushboolean(L, gh && gh->isSwimming()); return 1; } static int lua_IsResting(lua_State* L) { auto* gh = getGameHandler(L); lua_pushboolean(L, gh && gh->isPlayerResting()); return 1; } static int lua_IsFalling(lua_State* L) { auto* gh = getGameHandler(L); // Check FALLING movement flag if (!gh) { lua_pushboolean(L, 0); return 1; } const auto& mi = gh->getMovementInfo(); lua_pushboolean(L, (mi.flags & 0x2000) != 0); // MOVEFLAG_FALLING = 0x2000 return 1; } static int lua_IsStealthed(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { lua_pushboolean(L, 0); return 1; } // Check for stealth auras (aura flags bit 0x40 = is harmful, stealth is a buff) // WoW detects stealth via unit flags: UNIT_FLAG_IMMUNE (0x02) or specific aura IDs // Simplified: check player auras for known stealth spell IDs bool stealthed = false; for (const auto& a : gh->getPlayerAuras()) { if (a.isEmpty() || a.spellId == 0) continue; // Common stealth IDs: 1784 (Stealth), 5215 (Prowl), 66 (Invisibility) if (a.spellId == 1784 || a.spellId == 5215 || a.spellId == 66 || a.spellId == 1785 || a.spellId == 1786 || a.spellId == 1787 || a.spellId == 11305 || a.spellId == 11306) { stealthed = true; break; } } lua_pushboolean(L, stealthed); return 1; } static int lua_GetUnitSpeed(lua_State* L) { auto* gh = getGameHandler(L); const char* uid = luaL_optstring(L, 1, "player"); if (!gh || std::string(uid) != "player") { lua_pushnumber(L, 0); return 1; } lua_pushnumber(L, gh->getServerRunSpeed()); return 1; } // --- Container/Bag API --- // WoW bags: container 0 = backpack (16 slots), containers 1-4 = equipped bags static int lua_GetContainerNumSlots(lua_State* L) { auto* gh = getGameHandler(L); int container = static_cast(luaL_checknumber(L, 1)); if (!gh) { lua_pushnumber(L, 0); return 1; } const auto& inv = gh->getInventory(); if (container == 0) { lua_pushnumber(L, inv.getBackpackSize()); } else if (container >= 1 && container <= 4) { lua_pushnumber(L, inv.getBagSize(container - 1)); } else { lua_pushnumber(L, 0); } return 1; } // GetContainerItemInfo(container, slot) → texture, count, locked, quality, readable, lootable, link static int lua_GetContainerItemInfo(lua_State* L) { auto* gh = getGameHandler(L); int container = static_cast(luaL_checknumber(L, 1)); int slot = static_cast(luaL_checknumber(L, 2)); if (!gh) { lua_pushnil(L); return 1; } const auto& inv = gh->getInventory(); const game::ItemSlot* itemSlot = nullptr; if (container == 0 && slot >= 1 && slot <= inv.getBackpackSize()) { itemSlot = &inv.getBackpackSlot(slot - 1); // WoW uses 1-based } else if (container >= 1 && container <= 4) { int bagIdx = container - 1; int bagSize = inv.getBagSize(bagIdx); if (slot >= 1 && slot <= bagSize) itemSlot = &inv.getBagSlot(bagIdx, slot - 1); } if (!itemSlot || itemSlot->empty()) { lua_pushnil(L); return 1; } // Get item info for quality/icon const auto* info = gh->getItemInfo(itemSlot->item.itemId); lua_pushnil(L); // texture (icon path — would need ItemDisplayInfo icon resolver) lua_pushnumber(L, itemSlot->item.stackCount); // count lua_pushboolean(L, 0); // locked lua_pushnumber(L, info ? info->quality : 0); // quality lua_pushboolean(L, 0); // readable lua_pushboolean(L, 0); // lootable // Build item link with quality color std::string name = info ? info->name : ("Item #" + std::to_string(itemSlot->item.itemId)); uint32_t q = info ? info->quality : 0; static const char* kQH[] = {"9d9d9d","ffffff","1eff00","0070dd","a335ee","ff8000","e6cc80","e6cc80"}; uint32_t qi = q < 8 ? q : 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], itemSlot->item.itemId, name.c_str()); lua_pushstring(L, link); // link return 7; } // GetContainerItemLink(container, slot) → item link string static int lua_GetContainerItemLink(lua_State* L) { auto* gh = getGameHandler(L); int container = static_cast(luaL_checknumber(L, 1)); int slot = static_cast(luaL_checknumber(L, 2)); if (!gh) { lua_pushnil(L); return 1; } const auto& inv = gh->getInventory(); const game::ItemSlot* itemSlot = nullptr; if (container == 0 && slot >= 1 && slot <= inv.getBackpackSize()) { itemSlot = &inv.getBackpackSlot(slot - 1); } else if (container >= 1 && container <= 4) { int bagIdx = container - 1; int bagSize = inv.getBagSize(bagIdx); if (slot >= 1 && slot <= bagSize) itemSlot = &inv.getBagSlot(bagIdx, slot - 1); } if (!itemSlot || itemSlot->empty()) { lua_pushnil(L); return 1; } const auto* info = gh->getItemInfo(itemSlot->item.itemId); std::string name = info ? info->name : ("Item #" + std::to_string(itemSlot->item.itemId)); uint32_t q = info ? info->quality : 0; char link[256]; static const char* kQH[] = {"9d9d9d","ffffff","1eff00","0070dd","a335ee","ff8000","e6cc80","e6cc80"}; uint32_t qi = q < 8 ? q : 1u; snprintf(link, sizeof(link), "|cff%s|Hitem:%u:0:0:0:0:0:0:0|h[%s]|h|r", kQH[qi], itemSlot->item.itemId, name.c_str()); lua_pushstring(L, link); return 1; } // GetContainerNumFreeSlots(container) → numFreeSlots, bagType static int lua_GetContainerNumFreeSlots(lua_State* L) { auto* gh = getGameHandler(L); int container = static_cast(luaL_checknumber(L, 1)); if (!gh) { lua_pushnumber(L, 0); lua_pushnumber(L, 0); return 2; } const auto& inv = gh->getInventory(); int freeSlots = 0; int totalSlots = 0; if (container == 0) { totalSlots = inv.getBackpackSize(); for (int i = 0; i < totalSlots; ++i) if (inv.getBackpackSlot(i).empty()) ++freeSlots; } else if (container >= 1 && container <= 4) { totalSlots = inv.getBagSize(container - 1); for (int i = 0; i < totalSlots; ++i) if (inv.getBagSlot(container - 1, i).empty()) ++freeSlots; } lua_pushnumber(L, freeSlots); lua_pushnumber(L, 0); // bagType (0 = normal) return 2; } // --- Equipment Slot API --- // WoW inventory slot IDs: 1=Head,2=Neck,3=Shoulders,4=Shirt,5=Chest, // 6=Waist,7=Legs,8=Feet,9=Wrists,10=Hands,11=Ring1,12=Ring2, // 13=Trinket1,14=Trinket2,15=Back,16=MainHand,17=OffHand,18=Ranged,19=Tabard static int lua_GetInventoryItemLink(lua_State* L) { auto* gh = getGameHandler(L); const char* uid = luaL_optstring(L, 1, "player"); int slotId = static_cast(luaL_checknumber(L, 2)); if (!gh || slotId < 1 || slotId > 19) { lua_pushnil(L); return 1; } std::string uidStr(uid); for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); if (uidStr != "player") { lua_pushnil(L); return 1; } const auto& inv = gh->getInventory(); const auto& slot = inv.getEquipSlot(static_cast(slotId - 1)); if (slot.empty()) { lua_pushnil(L); return 1; } const auto* info = gh->getItemInfo(slot.item.itemId); std::string name = info ? info->name : slot.item.name; uint32_t q = info ? info->quality : static_cast(slot.item.quality); static const char* kQH[] = {"9d9d9d","ffffff","1eff00","0070dd","a335ee","ff8000","e6cc80","e6cc80"}; uint32_t qi = q < 8 ? q : 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], slot.item.itemId, name.c_str()); lua_pushstring(L, link); return 1; } static int lua_GetInventoryItemID(lua_State* L) { auto* gh = getGameHandler(L); const char* uid = luaL_optstring(L, 1, "player"); int slotId = static_cast(luaL_checknumber(L, 2)); if (!gh || slotId < 1 || slotId > 19) { lua_pushnil(L); return 1; } std::string uidStr(uid); for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); if (uidStr != "player") { lua_pushnil(L); return 1; } const auto& inv = gh->getInventory(); const auto& slot = inv.getEquipSlot(static_cast(slotId - 1)); if (slot.empty()) { lua_pushnil(L); return 1; } lua_pushnumber(L, slot.item.itemId); return 1; } static int lua_GetInventoryItemTexture(lua_State* L) { auto* gh = getGameHandler(L); const char* uid = luaL_optstring(L, 1, "player"); int slotId = static_cast(luaL_checknumber(L, 2)); if (!gh || slotId < 1 || slotId > 19) { lua_pushnil(L); return 1; } std::string uidStr(uid); for (char& c : uidStr) c = static_cast(std::tolower(static_cast(c))); if (uidStr != "player") { lua_pushnil(L); return 1; } const auto& inv = gh->getInventory(); const auto& slot = inv.getEquipSlot(static_cast(slotId - 1)); if (slot.empty()) { lua_pushnil(L); return 1; } // Return spell icon path for the item's on-use spell, or nil lua_pushnil(L); return 1; } // --- Time & XP API --- static int lua_GetGameTime(lua_State* L) { // Returns server game time as hours, minutes auto* gh = getGameHandler(L); if (gh) { float gt = gh->getGameTime(); int hours = static_cast(gt) % 24; int mins = static_cast((gt - static_cast(gt)) * 60.0f); lua_pushnumber(L, hours); lua_pushnumber(L, mins); } else { lua_pushnumber(L, 12); lua_pushnumber(L, 0); } return 2; } static int lua_GetServerTime(lua_State* L) { lua_pushnumber(L, static_cast(std::time(nullptr))); return 1; } static int lua_UnitXP(lua_State* L) { const char* uid = luaL_optstring(L, 1, "player"); auto* gh = getGameHandler(L); if (!gh) { lua_pushnumber(L, 0); return 1; } std::string u(uid); for (char& c : u) c = static_cast(std::tolower(static_cast(c))); if (u == "player") lua_pushnumber(L, gh->getPlayerXp()); else lua_pushnumber(L, 0); return 1; } static int lua_UnitXPMax(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 u(uid); for (char& c : u) c = static_cast(std::tolower(static_cast(c))); if (u == "player") { uint32_t nlxp = gh->getPlayerNextLevelXp(); lua_pushnumber(L, nlxp > 0 ? nlxp : 1); } else { lua_pushnumber(L, 1); } return 1; } // GetXPExhaustion() → rested XP pool remaining (nil if none) static int lua_GetXPExhaustion(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { lua_pushnil(L); return 1; } uint32_t rested = gh->getPlayerRestedXp(); if (rested > 0) lua_pushnumber(L, rested); else lua_pushnil(L); return 1; } // GetRestState() → 1 = normal, 2 = rested static int lua_GetRestState(lua_State* L) { auto* gh = getGameHandler(L); lua_pushnumber(L, (gh && gh->isPlayerResting()) ? 2 : 1); return 1; } // --- Quest Log API --- static int lua_GetNumQuestLogEntries(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { lua_pushnumber(L, 0); lua_pushnumber(L, 0); return 2; } const auto& ql = gh->getQuestLog(); lua_pushnumber(L, ql.size()); // numEntries lua_pushnumber(L, 0); // numQuests (headers not tracked) return 2; } // GetQuestLogTitle(index) → title, level, suggestedGroup, isHeader, isCollapsed, isComplete, frequency, questID static int lua_GetQuestLogTitle(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& ql = gh->getQuestLog(); if (index > static_cast(ql.size())) { lua_pushnil(L); return 1; } const auto& q = ql[index - 1]; // 1-based lua_pushstring(L, q.title.c_str()); // title lua_pushnumber(L, 0); // level (not tracked) lua_pushnumber(L, 0); // suggestedGroup lua_pushboolean(L, 0); // isHeader lua_pushboolean(L, 0); // isCollapsed lua_pushboolean(L, q.complete); // isComplete lua_pushnumber(L, 0); // frequency lua_pushnumber(L, q.questId); // questID return 8; } // GetQuestLogQuestText(index) → description, objectives static int lua_GetQuestLogQuestText(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& ql = gh->getQuestLog(); if (index > static_cast(ql.size())) { lua_pushnil(L); return 1; } const auto& q = ql[index - 1]; lua_pushstring(L, ""); // description (not stored) lua_pushstring(L, q.objectives.c_str()); // objectives return 2; } // IsQuestComplete(questID) → boolean static int lua_IsQuestComplete(lua_State* L) { auto* gh = getGameHandler(L); uint32_t questId = static_cast(luaL_checknumber(L, 1)); if (!gh) { lua_pushboolean(L, 0); return 1; } for (const auto& q : gh->getQuestLog()) { if (q.questId == questId) { lua_pushboolean(L, q.complete); return 1; } } lua_pushboolean(L, 0); 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) { 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->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); } return 1; } static int lua_GetNumRaidMembers(lua_State* L) { auto* gh = getGameHandler(L); if (!gh || !gh->isInGroup()) { lua_pushnumber(L, 0); return 1; } const auto& pd = gh->getPartyData(); lua_pushnumber(L, (pd.groupType == 1) ? pd.memberCount : 0); return 1; } static int lua_GetNumPartyMembers(lua_State* L) { auto* gh = getGameHandler(L); if (!gh || !gh->isInGroup()) { lua_pushnumber(L, 0); return 1; } const auto& pd = gh->getPartyData(); // In party (not raid), count excludes self int count = (pd.groupType == 0) ? static_cast(pd.memberCount) : 0; // memberCount includes self on some servers, subtract 1 if needed if (count > 0) count = std::max(0, count - 1); lua_pushnumber(L, count); return 1; } static int lua_UnitInParty(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->isInGroup()); } else { uint64_t guid = resolveUnitGuid(gh, uidStr); if (guid == 0) { lua_pushboolean(L, 0); return 1; } const auto& pd = gh->getPartyData(); bool found = false; for (const auto& m : pd.members) { if (m.guid == guid) { found = true; break; } } lua_pushboolean(L, found); } return 1; } static int lua_UnitInRaid(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))); const auto& pd = gh->getPartyData(); if (pd.groupType != 1) { lua_pushboolean(L, 0); return 1; } if (uidStr == "player") { lua_pushboolean(L, 1); return 1; } uint64_t guid = resolveUnitGuid(gh, uidStr); bool found = false; for (const auto& m : pd.members) { if (m.guid == guid) { found = true; break; } } lua_pushboolean(L, found); return 1; } static int lua_UnitIsUnit(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { lua_pushboolean(L, 0); return 1; } const char* uid1 = luaL_checkstring(L, 1); const char* uid2 = luaL_checkstring(L, 2); std::string u1(uid1), u2(uid2); for (char& c : u1) c = static_cast(std::tolower(static_cast(c))); for (char& c : u2) c = static_cast(std::tolower(static_cast(c))); uint64_t g1 = resolveUnitGuid(gh, u1); uint64_t g2 = resolveUnitGuid(gh, u2); lua_pushboolean(L, g1 != 0 && g1 == g2); return 1; } static int lua_UnitIsFriend(lua_State* L) { const char* uid = luaL_optstring(L, 1, "player"); auto* unit = resolveUnit(L, uid); lua_pushboolean(L, unit && !unit->isHostile()); return 1; } static int lua_UnitIsEnemy(lua_State* L) { const char* uid = luaL_optstring(L, 1, "player"); auto* unit = resolveUnit(L, uid); lua_pushboolean(L, unit && unit->isHostile()); return 1; } static int lua_UnitCreatureType(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { lua_pushstring(L, "Unknown"); 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_pushstring(L, "Unknown"); return 1; } auto entity = gh->getEntityManager().getEntity(guid); if (!entity) { lua_pushstring(L, "Unknown"); return 1; } // Player units are always "Humanoid" if (entity->getType() == game::ObjectType::PLAYER) { lua_pushstring(L, "Humanoid"); return 1; } auto unit = std::dynamic_pointer_cast(entity); if (!unit) { lua_pushstring(L, "Unknown"); return 1; } uint32_t ct = gh->getCreatureType(unit->getEntry()); static const char* kTypes[] = { "Unknown", "Beast", "Dragonkin", "Demon", "Elemental", "Giant", "Undead", "Humanoid", "Critter", "Mechanical", "Not specified", "Totem", "Non-combat Pet", "Gas Cloud" }; lua_pushstring(L, (ct < 14) ? kTypes[ct] : "Unknown"); return 1; } // GetPlayerInfoByGUID(guid) → localizedClass, englishClass, localizedRace, englishRace, sex, name, realm static int lua_GetPlayerInfoByGUID(lua_State* L) { auto* gh = getGameHandler(L); const char* guidStr = luaL_checkstring(L, 1); if (!gh || !guidStr) { for (int i = 0; i < 7; i++) lua_pushnil(L); return 7; } // Parse hex GUID string "0x0000000000000001" uint64_t guid = 0; if (guidStr[0] == '0' && (guidStr[1] == 'x' || guidStr[1] == 'X')) guid = strtoull(guidStr + 2, nullptr, 16); else guid = strtoull(guidStr, nullptr, 16); if (guid == 0) { for (int i = 0; i < 7; i++) lua_pushnil(L); return 7; } // Look up entity name std::string name = gh->lookupName(guid); if (name.empty() && guid == gh->getPlayerGuid()) { const auto& chars = gh->getCharacters(); for (const auto& c : chars) if (c.guid == guid) { name = c.name; break; } } // For player GUID, return class/race if it's the local player const char* className = "Unknown"; const char* raceName = "Unknown"; if (guid == gh->getPlayerGuid()) { static const char* kClasses[] = {"","Warrior","Paladin","Hunter","Rogue","Priest", "Death Knight","Shaman","Mage","Warlock","","Druid"}; static const char* kRaces[] = {"","Human","Orc","Dwarf","Night Elf","Undead", "Tauren","Gnome","Troll","","Blood Elf","Draenei"}; uint8_t cid = gh->getPlayerClass(); uint8_t rid = gh->getPlayerRace(); if (cid < 12) className = kClasses[cid]; if (rid < 12) raceName = kRaces[rid]; } lua_pushstring(L, className); // 1: localizedClass lua_pushstring(L, className); // 2: englishClass lua_pushstring(L, raceName); // 3: localizedRace lua_pushstring(L, raceName); // 4: englishRace lua_pushnumber(L, 0); // 5: sex (0=unknown) lua_pushstring(L, name.c_str()); // 6: name lua_pushstring(L, ""); // 7: realm return 7; } // GetItemLink(itemId) → "|cFFxxxxxx|Hitem:ID:...|h[Name]|h|r" static int lua_GetItemLink(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { lua_pushnil(L); return 1; } uint32_t itemId = static_cast(luaL_checknumber(L, 1)); if (itemId == 0) { lua_pushnil(L); return 1; } const auto* info = gh->getItemInfo(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], itemId, info->name.c_str()); lua_pushstring(L, link); return 1; } // GetSpellLink(spellIdOrName) → "|cFFxxxxxx|Hspell:ID|h[Name]|h|r" static int lua_GetSpellLink(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { lua_pushnil(L); return 1; } uint32_t spellId = 0; if (lua_isnumber(L, 1)) { spellId = static_cast(lua_tonumber(L, 1)); } else if (lua_isstring(L, 1)) { const char* name = lua_tostring(L, 1); if (!name || !*name) { lua_pushnil(L); return 1; } std::string nameLow(name); for (char& c : nameLow) c = static_cast(std::tolower(static_cast(c))); for (uint32_t sid : gh->getKnownSpells()) { std::string sn = gh->getSpellName(sid); for (char& c : sn) c = static_cast(std::tolower(static_cast(c))); if (sn == nameLow) { spellId = sid; break; } } } if (spellId == 0) { lua_pushnil(L); return 1; } std::string name = gh->getSpellName(spellId); if (name.empty()) { lua_pushnil(L); return 1; } char link[256]; snprintf(link, sizeof(link), "|cff71d5ff|Hspell:%u|h[%s]|h|r", spellId, name.c_str()); lua_pushstring(L, link); return 1; } // IsUsableSpell(spellIdOrName) → usable, noMana static int lua_IsUsableSpell(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { lua_pushboolean(L, 0); lua_pushboolean(L, 0); return 2; } uint32_t spellId = 0; if (lua_isnumber(L, 1)) { spellId = static_cast(lua_tonumber(L, 1)); } else if (lua_isstring(L, 1)) { const char* name = lua_tostring(L, 1); if (!name || !*name) { lua_pushboolean(L, 0); lua_pushboolean(L, 0); return 2; } std::string nameLow(name); for (char& c : nameLow) c = static_cast(std::tolower(static_cast(c))); for (uint32_t sid : gh->getKnownSpells()) { std::string sn = gh->getSpellName(sid); for (char& c : sn) c = static_cast(std::tolower(static_cast(c))); if (sn == nameLow) { spellId = sid; break; } } } if (spellId == 0 || !gh->getKnownSpells().count(spellId)) { lua_pushboolean(L, 0); lua_pushboolean(L, 0); return 2; } // Check if on cooldown 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 return 2; } // IsInInstance() → isInstance, instanceType static int lua_IsInInstance(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { lua_pushboolean(L, 0); lua_pushstring(L, "none"); return 2; } bool inInstance = gh->isInInstance(); lua_pushboolean(L, inInstance); lua_pushstring(L, inInstance ? "party" : "none"); // simplified: "none", "party", "raid", "pvp", "arena" return 2; } // GetInstanceInfo() → name, type, difficultyIndex, difficultyName, maxPlayers, ... static int lua_GetInstanceInfo(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { lua_pushstring(L, ""); lua_pushstring(L, "none"); lua_pushnumber(L, 0); lua_pushstring(L, "Normal"); lua_pushnumber(L, 0); return 5; } std::string mapName = gh->getMapName(gh->getCurrentMapId()); lua_pushstring(L, mapName.c_str()); // 1: name lua_pushstring(L, gh->isInInstance() ? "party" : "none"); // 2: instanceType lua_pushnumber(L, gh->getInstanceDifficulty()); // 3: difficultyIndex static const char* kDiff[] = {"Normal", "Heroic", "25 Normal", "25 Heroic"}; uint32_t diff = gh->getInstanceDifficulty(); lua_pushstring(L, (diff < 4) ? kDiff[diff] : "Normal"); // 4: difficultyName lua_pushnumber(L, 5); // 5: maxPlayers (default 5-man) return 5; } // GetInstanceDifficulty() → difficulty (1=normal, 2=heroic, 3=25normal, 4=25heroic) static int lua_GetInstanceDifficulty(lua_State* L) { auto* gh = getGameHandler(L); lua_pushnumber(L, gh ? (gh->getInstanceDifficulty() + 1) : 1); // WoW returns 1-based return 1; } // UnitClassification(unit) → "normal", "elite", "rareelite", "worldboss", "rare" static int lua_UnitClassification(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { lua_pushstring(L, "normal"); 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_pushstring(L, "normal"); return 1; } auto entity = gh->getEntityManager().getEntity(guid); if (!entity || entity->getType() == game::ObjectType::PLAYER) { lua_pushstring(L, "normal"); return 1; } auto unit = std::dynamic_pointer_cast(entity); if (!unit) { lua_pushstring(L, "normal"); return 1; } int rank = gh->getCreatureRank(unit->getEntry()); switch (rank) { case 1: lua_pushstring(L, "elite"); break; case 2: lua_pushstring(L, "rareelite"); break; case 3: lua_pushstring(L, "worldboss"); break; case 4: lua_pushstring(L, "rare"); break; default: lua_pushstring(L, "normal"); break; } 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. // Frame method: frame:RegisterEvent("EVENT") static int lua_Frame_RegisterEvent(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); // self const char* eventName = luaL_checkstring(L, 2); // Get frame's registered events table (create if needed) lua_getfield(L, 1, "__events"); if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_newtable(L); lua_pushvalue(L, -1); lua_setfield(L, 1, "__events"); } lua_pushboolean(L, 1); lua_setfield(L, -2, eventName); lua_pop(L, 1); // Also register in global __WoweeFrameEvents for dispatch lua_getglobal(L, "__WoweeFrameEvents"); if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_newtable(L); lua_pushvalue(L, -1); lua_setglobal(L, "__WoweeFrameEvents"); } lua_getfield(L, -1, eventName); if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_newtable(L); lua_pushvalue(L, -1); lua_setfield(L, -3, eventName); } // Append frame reference int len = static_cast(lua_objlen(L, -1)); lua_pushvalue(L, 1); // push frame lua_rawseti(L, -2, len + 1); lua_pop(L, 2); // pop list + __WoweeFrameEvents return 0; } // Frame method: frame:UnregisterEvent("EVENT") static int lua_Frame_UnregisterEvent(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); const char* eventName = luaL_checkstring(L, 2); // Remove from frame's own events lua_getfield(L, 1, "__events"); if (lua_istable(L, -1)) { lua_pushnil(L); lua_setfield(L, -2, eventName); } lua_pop(L, 1); return 0; } // Frame method: frame:SetScript("handler", func) static int lua_Frame_SetScript(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); const char* scriptType = luaL_checkstring(L, 2); // arg 3 can be function or nil lua_getfield(L, 1, "__scripts"); if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_newtable(L); lua_pushvalue(L, -1); lua_setfield(L, 1, "__scripts"); } lua_pushvalue(L, 3); lua_setfield(L, -2, scriptType); lua_pop(L, 1); // Track frames with OnUpdate in __WoweeOnUpdateFrames if (strcmp(scriptType, "OnUpdate") == 0) { lua_getglobal(L, "__WoweeOnUpdateFrames"); if (!lua_istable(L, -1)) { lua_pop(L, 1); return 0; } if (lua_isfunction(L, 3)) { // Add frame to the list int len = static_cast(lua_objlen(L, -1)); lua_pushvalue(L, 1); lua_rawseti(L, -2, len + 1); } lua_pop(L, 1); } return 0; } // Frame method: frame:GetScript("handler") static int lua_Frame_GetScript(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); const char* scriptType = luaL_checkstring(L, 2); lua_getfield(L, 1, "__scripts"); if (lua_istable(L, -1)) { lua_getfield(L, -1, scriptType); } else { lua_pushnil(L); } return 1; } // Frame method: frame:GetName() static int lua_Frame_GetName(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); lua_getfield(L, 1, "__name"); return 1; } // Frame method: frame:Show() / frame:Hide() / frame:IsShown() / frame:IsVisible() static int lua_Frame_Show(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); lua_pushboolean(L, 1); lua_setfield(L, 1, "__visible"); return 0; } static int lua_Frame_Hide(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); lua_pushboolean(L, 0); lua_setfield(L, 1, "__visible"); return 0; } static int lua_Frame_IsShown(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); lua_getfield(L, 1, "__visible"); lua_pushboolean(L, lua_toboolean(L, -1)); 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"); const char* name = luaL_optstring(L, 2, nullptr); (void)frameType; // All frame types use the same table structure for now // Create the frame table lua_newtable(L); // Set frame name if (name && *name) { lua_pushstring(L, name); lua_setfield(L, -2, "__name"); // Also set as a global so other addons can find it by name lua_pushvalue(L, -1); lua_setglobal(L, name); } // Set initial visibility lua_pushboolean(L, 1); lua_setfield(L, -2, "__visible"); // Apply frame metatable with methods lua_getglobal(L, "__WoweeFrameMT"); lua_setmetatable(L, -2); return 1; } // --- WoW Utility Functions --- // strsplit(delimiter, str) — WoW's string split static int lua_strsplit(lua_State* L) { const char* delim = luaL_checkstring(L, 1); const char* str = luaL_checkstring(L, 2); if (!delim[0]) { lua_pushstring(L, str); return 1; } int count = 0; std::string s(str); size_t pos = 0; while (pos <= s.size()) { size_t found = s.find(delim[0], pos); if (found == std::string::npos) { lua_pushstring(L, s.substr(pos).c_str()); count++; break; } lua_pushstring(L, s.substr(pos, found - pos).c_str()); count++; pos = found + 1; } return count; } // strtrim(str) — remove leading/trailing whitespace static int lua_strtrim(lua_State* L) { const char* str = luaL_checkstring(L, 1); std::string s(str); size_t start = s.find_first_not_of(" \t\r\n"); size_t end = s.find_last_not_of(" \t\r\n"); lua_pushstring(L, (start == std::string::npos) ? "" : s.substr(start, end - start + 1).c_str()); return 1; } // wipe(table) — clear all entries from a table static int lua_wipe(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); // Remove all integer keys int len = static_cast(lua_objlen(L, 1)); for (int i = len; i >= 1; i--) { lua_pushnil(L); lua_rawseti(L, 1, i); } // Remove all string keys lua_pushnil(L); while (lua_next(L, 1) != 0) { lua_pop(L, 1); // pop value lua_pushvalue(L, -1); // copy key lua_pushnil(L); lua_rawset(L, 1); // table[key] = nil } lua_pushvalue(L, 1); return 1; } // date(format) — safe date function (os.date was removed) static int lua_wow_date(lua_State* L) { const char* fmt = luaL_optstring(L, 1, "%c"); time_t now = time(nullptr); struct tm* tm = localtime(&now); char buf[256]; strftime(buf, sizeof(buf), fmt, tm); lua_pushstring(L, buf); return 1; } // time() — current unix timestamp static int lua_wow_time(lua_State* L) { lua_pushnumber(L, static_cast(time(nullptr))); return 1; } // Stub for GetTime() — returns elapsed seconds static int lua_wow_gettime(lua_State* L) { static auto start = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now(); double elapsed = std::chrono::duration(now - start).count(); lua_pushnumber(L, elapsed); return 1; } LuaEngine::LuaEngine() = default; LuaEngine::~LuaEngine() { shutdown(); } bool LuaEngine::initialize() { if (L_) return true; L_ = luaL_newstate(); if (!L_) { LOG_ERROR("LuaEngine: failed to create Lua state"); return false; } // Open safe standard libraries (no io, os, debug, package) luaopen_base(L_); luaopen_table(L_); luaopen_string(L_); luaopen_math(L_); // Remove unsafe globals from base library const char* unsafeGlobals[] = { "dofile", "loadfile", "load", "collectgarbage", "newproxy", nullptr }; for (const char** g = unsafeGlobals; *g; ++g) { lua_pushnil(L_); lua_setglobal(L_, *g); } registerCoreAPI(); registerEventAPI(); LOG_INFO("LuaEngine: initialized (Lua 5.1)"); return true; } void LuaEngine::shutdown() { if (L_) { lua_close(L_); L_ = nullptr; LOG_INFO("LuaEngine: shut down"); } } void LuaEngine::setGameHandler(game::GameHandler* handler) { gameHandler_ = handler; if (L_) { lua_pushlightuserdata(L_, handler); lua_setfield(L_, LUA_REGISTRYINDEX, "wowee_game_handler"); } } void LuaEngine::registerCoreAPI() { // Override print() to go to chat lua_pushcfunction(L_, lua_wow_print); lua_setglobal(L_, "print"); // WoW API stubs lua_pushcfunction(L_, lua_wow_message); lua_setglobal(L_, "message"); lua_pushcfunction(L_, lua_wow_gettime); lua_setglobal(L_, "GetTime"); // Unit API static const struct { const char* name; lua_CFunction func; } unitAPI[] = { {"UnitName", lua_UnitName}, {"UnitHealth", lua_UnitHealth}, {"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}, {"UnitIsTapped", lua_UnitIsTapped}, {"UnitIsTappedByPlayer", lua_UnitIsTappedByPlayer}, {"UnitIsTappedByAllThreatList", lua_UnitIsTappedByAllThreatList}, {"UnitIsVisible", lua_UnitIsVisible}, {"UnitGroupRolesAssigned", lua_UnitGroupRolesAssigned}, {"UnitCanAttack", lua_UnitCanAttack}, {"UnitCanCooperate", lua_UnitCanCooperate}, {"UnitCreatureFamily", lua_UnitCreatureFamily}, {"UnitOnTaxi", lua_UnitOnTaxi}, {"UnitThreatSituation", lua_UnitThreatSituation}, {"UnitDetailedThreatSituation", lua_UnitDetailedThreatSituation}, {"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}, {"GetSpellPowerCost", lua_GetSpellPowerCost}, {"IsSpellInRange", lua_IsSpellInRange}, {"UnitDistanceSquared", lua_UnitDistanceSquared}, {"CheckInteractDistance", lua_CheckInteractDistance}, {"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}, {"UnitGUID", lua_UnitGUID}, {"UnitIsPlayer", lua_UnitIsPlayer}, {"InCombatLockdown", lua_InCombatLockdown}, {"UnitBuff", lua_UnitBuff}, {"UnitDebuff", lua_UnitDebuff}, {"UnitAura", lua_UnitAuraGeneric}, {"GetNumAddOns", lua_GetNumAddOns}, {"GetAddOnInfo", lua_GetAddOnInfo}, {"GetAddOnMetadata", lua_GetAddOnMetadata}, {"GetSpellInfo", lua_GetSpellInfo}, {"GetSpellTexture", lua_GetSpellTexture}, {"GetItemInfo", lua_GetItemInfo}, {"GetLocale", lua_GetLocale}, {"GetBuildInfo", lua_GetBuildInfo}, {"GetCurrentMapAreaID", lua_GetCurrentMapAreaID}, {"GetZoneText", lua_GetZoneText}, {"GetRealZoneText", lua_GetZoneText}, {"GetSubZoneText", lua_GetSubZoneText}, {"GetMinimapZoneText", lua_GetMinimapZoneText}, // Player state (replaces hardcoded stubs) {"IsMounted", lua_IsMounted}, {"IsFlying", lua_IsFlying}, {"IsSwimming", lua_IsSwimming}, {"IsResting", lua_IsResting}, {"IsFalling", lua_IsFalling}, {"IsStealthed", lua_IsStealthed}, {"GetUnitSpeed", lua_GetUnitSpeed}, // Combat/group queries {"UnitAffectingCombat", lua_UnitAffectingCombat}, {"GetNumRaidMembers", lua_GetNumRaidMembers}, {"GetNumPartyMembers", lua_GetNumPartyMembers}, {"UnitInParty", lua_UnitInParty}, {"UnitInRaid", lua_UnitInRaid}, {"UnitIsUnit", lua_UnitIsUnit}, {"UnitIsFriend", lua_UnitIsFriend}, {"UnitIsEnemy", lua_UnitIsEnemy}, {"UnitCreatureType", lua_UnitCreatureType}, {"UnitClassification", lua_UnitClassification}, {"GetPlayerInfoByGUID", lua_GetPlayerInfoByGUID}, {"GetItemLink", lua_GetItemLink}, {"GetSpellLink", lua_GetSpellLink}, {"IsUsableSpell", lua_IsUsableSpell}, {"IsInInstance", lua_IsInInstance}, {"GetInstanceInfo", lua_GetInstanceInfo}, {"GetInstanceDifficulty", lua_GetInstanceDifficulty}, // Container/bag API {"GetContainerNumSlots", lua_GetContainerNumSlots}, {"GetContainerItemInfo", lua_GetContainerItemInfo}, {"GetContainerItemLink", lua_GetContainerItemLink}, {"GetContainerNumFreeSlots", lua_GetContainerNumFreeSlots}, // Equipment slot API {"GetInventoryItemLink", lua_GetInventoryItemLink}, {"GetInventoryItemID", lua_GetInventoryItemID}, {"GetInventoryItemTexture", lua_GetInventoryItemTexture}, // Time/XP API {"GetGameTime", lua_GetGameTime}, {"GetServerTime", lua_GetServerTime}, {"UnitXP", lua_UnitXP}, {"UnitXPMax", lua_UnitXPMax}, {"GetXPExhaustion", lua_GetXPExhaustion}, {"GetRestState", lua_GetRestState}, // Quest log API {"GetNumQuestLogEntries", lua_GetNumQuestLogEntries}, {"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}, {"wipe", lua_wipe}, {"date", lua_wow_date}, {"time", lua_wow_time}, }; for (const auto& [name, func] : unitAPI) { lua_pushcfunction(L_, func); lua_setglobal(L_, name); } // WoW aliases lua_getglobal(L_, "string"); lua_getfield(L_, -1, "format"); lua_setglobal(L_, "format"); lua_pop(L_, 1); // pop string table // tinsert/tremove aliases lua_getglobal(L_, "table"); lua_getfield(L_, -1, "insert"); lua_setglobal(L_, "tinsert"); lua_getfield(L_, -1, "remove"); lua_setglobal(L_, "tremove"); lua_pop(L_, 1); // pop table // SlashCmdList table — addons register slash commands here lua_newtable(L_); lua_setglobal(L_, "SlashCmdList"); // Frame metatable with methods lua_newtable(L_); // metatable lua_pushvalue(L_, -1); lua_setfield(L_, -2, "__index"); // metatable.__index = metatable static const struct luaL_Reg frameMethods[] = { {"RegisterEvent", lua_Frame_RegisterEvent}, {"UnregisterEvent", lua_Frame_UnregisterEvent}, {"SetScript", lua_Frame_SetScript}, {"GetScript", lua_Frame_GetScript}, {"GetName", lua_Frame_GetName}, {"Show", lua_Frame_Show}, {"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++) { lua_pushcfunction(L_, r->func); lua_setfield(L_, -2, r->name); } 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"); // OnUpdate frame tracking table lua_newtable(L_); lua_setglobal(L_, "__WoweeOnUpdateFrames"); // C_Timer implementation via Lua (uses OnUpdate internally) luaL_dostring(L_, "C_Timer = {}\n" "local timers = {}\n" "local timerFrame = CreateFrame('Frame', '__WoweeTimerFrame')\n" "timerFrame:SetScript('OnUpdate', function(self, elapsed)\n" " local i = 1\n" " while i <= #timers do\n" " timers[i].remaining = timers[i].remaining - elapsed\n" " if timers[i].remaining <= 0 then\n" " local cb = timers[i].callback\n" " table.remove(timers, i)\n" " cb()\n" " else\n" " i = i + 1\n" " end\n" " end\n" " if #timers == 0 then self:Hide() end\n" "end)\n" "timerFrame:Hide()\n" "function C_Timer.After(seconds, callback)\n" " tinsert(timers, {remaining = seconds, callback = callback})\n" " timerFrame:Show()\n" "end\n" "function C_Timer.NewTicker(seconds, callback, iterations)\n" " local count = 0\n" " local maxIter = iterations or -1\n" " local ticker = {cancelled = false}\n" " local function tick()\n" " if ticker.cancelled then return end\n" " count = count + 1\n" " callback(ticker)\n" " if maxIter > 0 and count >= maxIter then return end\n" " C_Timer.After(seconds, tick)\n" " end\n" " C_Timer.After(seconds, tick)\n" " function ticker:Cancel() self.cancelled = true end\n" " return ticker\n" "end\n" ); // DEFAULT_CHAT_FRAME with AddMessage method (used by many addons) luaL_dostring(L_, "DEFAULT_CHAT_FRAME = {}\n" "function DEFAULT_CHAT_FRAME:AddMessage(text, r, g, b)\n" " if r and g and b then\n" " local hex = format('|cff%02x%02x%02x', " " math.floor(r*255), math.floor(g*255), math.floor(b*255))\n" " print(hex .. tostring(text) .. '|r')\n" " else\n" " print(tostring(text))\n" " end\n" "end\n" "ChatFrame1 = DEFAULT_CHAT_FRAME\n" ); // hooksecurefunc — hook a function to run additional code after it luaL_dostring(L_, "function hooksecurefunc(tblOrName, nameOrFunc, funcOrNil)\n" " local tbl, name, hook\n" " if type(tblOrName) == 'table' then\n" " tbl, name, hook = tblOrName, nameOrFunc, funcOrNil\n" " else\n" " tbl, name, hook = _G, tblOrName, nameOrFunc\n" " end\n" " local orig = tbl[name]\n" " if type(orig) ~= 'function' then return end\n" " tbl[name] = function(...)\n" " local r = {orig(...)}\n" " hook(...)\n" " return unpack(r)\n" " end\n" "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" "function SetPortraitTexture() end\n" "function PlaySound() end\n" "function PlaySoundFile() end\n" "function StopSound() end\n" "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" "function seterrorhandler(fn) if type(fn)=='function' then _errorHandler=fn end end\n" "function debugstack(start, count1, count2) return '' end\n" "function securecall(fn, ...) if type(fn)=='function' then return fn(...) end end\n" "function issecurevariable(...) return false end\n" "function issecure() return false end\n" // CVar stubs (many addons check settings) "local _cvars = {}\n" "function GetCVar(name) return _cvars[name] or '0' end\n" "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 GetNetStats() return 0, 0, 0, 0 end\n" "function IsLoggedIn() return true end\n" "function StaticPopup_Show() end\n" "function StaticPopup_Hide() end\n" // CreateTexture/CreateFontString are now C frame methods in the metatable "do\n" " local function cc(r,g,b)\n" " local t = {r=r, g=g, b=b}\n" " t.colorStr = string.format('%02x%02x%02x', math.floor(r*255), math.floor(g*255), math.floor(b*255))\n" " function t:GenerateHexColor() return '|cff' .. self.colorStr end\n" " function t:GenerateHexColorMarkup() return '|cff' .. self.colorStr end\n" " return t\n" " end\n" " RAID_CLASS_COLORS = {\n" " WARRIOR=cc(0.78,0.61,0.43), PALADIN=cc(0.96,0.55,0.73),\n" " HUNTER=cc(0.67,0.83,0.45), ROGUE=cc(1.0,0.96,0.41),\n" " PRIEST=cc(1.0,1.0,1.0), DEATHKNIGHT=cc(0.77,0.12,0.23),\n" " SHAMAN=cc(0.0,0.44,0.87), MAGE=cc(0.41,0.80,0.94),\n" " WARLOCK=cc(0.58,0.51,0.79), DRUID=cc(1.0,0.49,0.04),\n" " }\n" "end\n" // Money formatting utility "function GetCoinTextureString(copper)\n" " if not copper or copper == 0 then return '0c' end\n" " copper = math.floor(copper)\n" " local g = math.floor(copper / 10000)\n" " local s = math.floor(math.fmod(copper, 10000) / 100)\n" " local c = math.fmod(copper, 100)\n" " local r = ''\n" " if g > 0 then r = r .. g .. 'g ' end\n" " if s > 0 then r = r .. s .. 's ' end\n" " if c > 0 or r == '' then r = r .. c .. 'c' end\n" " return r\n" "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" // Common WoW constants used by many addons "MAX_TALENT_TABS = 3\n" "MAX_NUM_TALENTS = 100\n" "BOOKTYPE_SPELL = 0\n" "BOOKTYPE_PET = 1\n" "MAX_PARTY_MEMBERS = 4\n" "MAX_RAID_MEMBERS = 40\n" "MAX_ARENA_TEAMS = 3\n" "INVSLOT_FIRST_EQUIPPED = 1\n" "INVSLOT_LAST_EQUIPPED = 19\n" "NUM_BAG_SLOTS = 4\n" "NUM_BANKBAGSLOTS = 7\n" "CONTAINER_BAG_OFFSET = 0\n" "MAX_SKILLLINE_TABS = 8\n" "TRADE_ENCHANT_SLOT = 7\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 ---- // Lua-side: WoweeEvents table holds { ["EVENT_NAME"] = { handler1, handler2, ... } } // RegisterEvent("EVENT", handler) adds a handler function // UnregisterEvent("EVENT", handler) removes it static int lua_RegisterEvent(lua_State* L) { const char* eventName = luaL_checkstring(L, 1); luaL_checktype(L, 2, LUA_TFUNCTION); // Get or create the WoweeEvents table lua_getglobal(L, "__WoweeEvents"); if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_newtable(L); lua_pushvalue(L, -1); lua_setglobal(L, "__WoweeEvents"); } // Get or create the handler list for this event lua_getfield(L, -1, eventName); if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_newtable(L); lua_pushvalue(L, -1); lua_setfield(L, -3, eventName); } // Append the handler function to the list int len = static_cast(lua_objlen(L, -1)); lua_pushvalue(L, 2); // push the handler function lua_rawseti(L, -2, len + 1); lua_pop(L, 2); // pop handler list + WoweeEvents return 0; } static int lua_UnregisterEvent(lua_State* L) { const char* eventName = luaL_checkstring(L, 1); luaL_checktype(L, 2, LUA_TFUNCTION); lua_getglobal(L, "__WoweeEvents"); if (lua_isnil(L, -1)) { lua_pop(L, 1); return 0; } lua_getfield(L, -1, eventName); if (lua_isnil(L, -1)) { lua_pop(L, 2); return 0; } // Remove matching handler from the list int len = static_cast(lua_objlen(L, -1)); for (int i = 1; i <= len; i++) { lua_rawgeti(L, -1, i); if (lua_rawequal(L, -1, 2)) { lua_pop(L, 1); // Shift remaining elements down for (int j = i; j < len; j++) { lua_rawgeti(L, -1, j + 1); lua_rawseti(L, -2, j); } lua_pushnil(L); lua_rawseti(L, -2, len); break; } lua_pop(L, 1); } lua_pop(L, 2); return 0; } void LuaEngine::registerEventAPI() { lua_pushcfunction(L_, lua_RegisterEvent); lua_setglobal(L_, "RegisterEvent"); lua_pushcfunction(L_, lua_UnregisterEvent); lua_setglobal(L_, "UnregisterEvent"); // Create the events table lua_newtable(L_); lua_setglobal(L_, "__WoweeEvents"); } void LuaEngine::fireEvent(const std::string& eventName, const std::vector& args) { if (!L_) return; lua_getglobal(L_, "__WoweeEvents"); if (lua_isnil(L_, -1)) { lua_pop(L_, 1); return; } lua_getfield(L_, -1, eventName.c_str()); if (lua_isnil(L_, -1)) { lua_pop(L_, 2); return; } int handlerCount = static_cast(lua_objlen(L_, -1)); for (int i = 1; i <= handlerCount; i++) { lua_rawgeti(L_, -1, i); if (!lua_isfunction(L_, -1)) { lua_pop(L_, 1); continue; } // Push arguments: event name first, then extra args lua_pushstring(L_, eventName.c_str()); for (const auto& arg : args) { lua_pushstring(L_, arg.c_str()); } 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); lua_pop(L_, 1); } } lua_pop(L_, 2); // pop handler list + WoweeEvents // Also dispatch to frames that registered for this event via frame:RegisterEvent() lua_getglobal(L_, "__WoweeFrameEvents"); if (lua_istable(L_, -1)) { lua_getfield(L_, -1, eventName.c_str()); if (lua_istable(L_, -1)) { int frameCount = static_cast(lua_objlen(L_, -1)); for (int i = 1; i <= frameCount; i++) { lua_rawgeti(L_, -1, i); if (!lua_istable(L_, -1)) { lua_pop(L_, 1); continue; } // Get the frame's OnEvent script lua_getfield(L_, -1, "__scripts"); if (lua_istable(L_, -1)) { lua_getfield(L_, -1, "OnEvent"); if (lua_isfunction(L_, -1)) { lua_pushvalue(L_, -3); // self (frame) lua_pushstring(L_, eventName.c_str()); 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); lua_pop(L_, 1); } } else { lua_pop(L_, 1); // pop non-function } } lua_pop(L_, 2); // pop __scripts + frame } } lua_pop(L_, 1); // pop event frame list } lua_pop(L_, 1); // pop __WoweeFrameEvents } void LuaEngine::dispatchOnUpdate(float elapsed) { if (!L_) return; lua_getglobal(L_, "__WoweeOnUpdateFrames"); if (!lua_istable(L_, -1)) { lua_pop(L_, 1); return; } int count = static_cast(lua_objlen(L_, -1)); for (int i = 1; i <= count; i++) { lua_rawgeti(L_, -1, i); if (!lua_istable(L_, -1)) { lua_pop(L_, 1); continue; } // Check if frame is visible lua_getfield(L_, -1, "__visible"); bool visible = lua_toboolean(L_, -1); lua_pop(L_, 1); if (!visible) { lua_pop(L_, 1); continue; } // Get OnUpdate script lua_getfield(L_, -1, "__scripts"); if (lua_istable(L_, -1)) { lua_getfield(L_, -1, "OnUpdate"); if (lua_isfunction(L_, -1)) { 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); lua_pop(L_, 1); } } else { lua_pop(L_, 1); } } lua_pop(L_, 2); // pop __scripts + frame } lua_pop(L_, 1); // pop __WoweeOnUpdateFrames } bool LuaEngine::dispatchSlashCommand(const std::string& command, const std::string& args) { if (!L_) return false; // Check each SlashCmdList entry: for key NAME, check SLASH_NAME1, SLASH_NAME2, etc. lua_getglobal(L_, "SlashCmdList"); if (!lua_istable(L_, -1)) { lua_pop(L_, 1); return false; } std::string cmdLower = command; for (char& c : cmdLower) c = static_cast(std::tolower(static_cast(c))); lua_pushnil(L_); while (lua_next(L_, -2) != 0) { // Stack: SlashCmdList, key, handler if (!lua_isfunction(L_, -1) || !lua_isstring(L_, -2)) { lua_pop(L_, 1); continue; } const char* name = lua_tostring(L_, -2); // Check SLASH_1 through SLASH_9 for (int i = 1; i <= 9; i++) { std::string globalName = "SLASH_" + std::string(name) + std::to_string(i); lua_getglobal(L_, globalName.c_str()); if (lua_isstring(L_, -1)) { std::string slashStr = lua_tostring(L_, -1); for (char& c : slashStr) c = static_cast(std::tolower(static_cast(c))); if (slashStr == cmdLower) { lua_pop(L_, 1); // pop global // Call the handler with args lua_pushvalue(L_, -1); // copy handler lua_pushstring(L_, args.c_str()); if (lua_pcall(L_, 1, 0, 0) != 0) { LOG_ERROR("LuaEngine: SlashCmdList['", name, "'] error: ", lua_tostring(L_, -1)); lua_pop(L_, 1); } lua_pop(L_, 3); // pop handler, key, SlashCmdList return true; } } lua_pop(L_, 1); // pop global } lua_pop(L_, 1); // pop handler, keep key for next iteration } lua_pop(L_, 1); // pop SlashCmdList return false; } // ---- SavedVariables serialization ---- static void serializeLuaValue(lua_State* L, int idx, std::string& out, int indent); static void serializeLuaTable(lua_State* L, int idx, std::string& out, int indent) { out += "{\n"; std::string pad(indent + 2, ' '); lua_pushnil(L); while (lua_next(L, idx) != 0) { out += pad; // Key if (lua_type(L, -2) == LUA_TSTRING) { const char* k = lua_tostring(L, -2); out += "[\""; for (const char* p = k; *p; ++p) { if (*p == '"' || *p == '\\') out += '\\'; out += *p; } out += "\"] = "; } else if (lua_type(L, -2) == LUA_TNUMBER) { out += "[" + std::to_string(static_cast(lua_tonumber(L, -2))) + "] = "; } else { lua_pop(L, 1); continue; } // Value serializeLuaValue(L, lua_gettop(L), out, indent + 2); out += ",\n"; lua_pop(L, 1); } out += std::string(indent, ' ') + "}"; } static void serializeLuaValue(lua_State* L, int idx, std::string& out, int indent) { switch (lua_type(L, idx)) { case LUA_TNIL: out += "nil"; break; case LUA_TBOOLEAN: out += lua_toboolean(L, idx) ? "true" : "false"; break; case LUA_TNUMBER: { double v = lua_tonumber(L, idx); char buf[64]; snprintf(buf, sizeof(buf), "%.17g", v); out += buf; break; } case LUA_TSTRING: { const char* s = lua_tostring(L, idx); out += "\""; for (const char* p = s; *p; ++p) { if (*p == '"' || *p == '\\') out += '\\'; else if (*p == '\n') { out += "\\n"; continue; } else if (*p == '\r') continue; out += *p; } out += "\""; break; } case LUA_TTABLE: serializeLuaTable(L, idx, out, indent); break; default: out += "nil"; // Functions, userdata, etc. can't be serialized break; } } void LuaEngine::setAddonList(const std::vector& addons) { if (!L_) return; lua_pushnumber(L_, static_cast(addons.size())); lua_setfield(L_, LUA_REGISTRYINDEX, "wowee_addon_count"); lua_newtable(L_); for (size_t i = 0; i < addons.size(); i++) { lua_newtable(L_); lua_pushstring(L_, addons[i].addonName.c_str()); lua_setfield(L_, -2, "name"); lua_pushstring(L_, addons[i].getTitle().c_str()); lua_setfield(L_, -2, "title"); 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"); } bool LuaEngine::loadSavedVariables(const std::string& path) { if (!L_) return false; std::ifstream f(path); if (!f.is_open()) return false; // No saved data yet — not an error std::string content((std::istreambuf_iterator(f)), std::istreambuf_iterator()); if (content.empty()) return true; int err = luaL_dostring(L_, content.c_str()); if (err != 0) { LOG_WARNING("LuaEngine: error loading saved variables from '", path, "': ", lua_tostring(L_, -1)); lua_pop(L_, 1); return false; } return true; } bool LuaEngine::saveSavedVariables(const std::string& path, const std::vector& varNames) { if (!L_ || varNames.empty()) return false; std::string output; for (const auto& name : varNames) { lua_getglobal(L_, name.c_str()); if (!lua_isnil(L_, -1)) { output += name + " = "; serializeLuaValue(L_, lua_gettop(L_), output, 0); output += "\n"; } lua_pop(L_, 1); } if (output.empty()) return true; // Ensure directory exists size_t lastSlash = path.find_last_of("/\\"); if (lastSlash != std::string::npos) { std::error_code ec; std::filesystem::create_directories(path.substr(0, lastSlash), ec); } std::ofstream f(path); if (!f.is_open()) { LOG_WARNING("LuaEngine: cannot write saved variables to '", path, "'"); return false; } f << output; LOG_INFO("LuaEngine: saved variables to '", path, "' (", output.size(), " bytes)"); return true; } bool LuaEngine::executeFile(const std::string& path) { if (!L_) return false; int err = luaL_dofile(L_, path.c_str()); if (err != 0) { 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; errChat.language = game::ChatLanguage::UNIVERSAL; errChat.message = "|cffff4040[Lua Error] " + msg + "|r"; gameHandler_->addLocalChatMessage(errChat); } lua_pop(L_, 1); return false; } return true; } bool LuaEngine::executeString(const std::string& code) { if (!L_) return false; int err = luaL_dostring(L_, code.c_str()); if (err != 0) { 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; errChat.language = game::ChatLanguage::UNIVERSAL; errChat.message = "|cffff4040[Lua Error] " + msg + "|r"; gameHandler_->addLocalChatMessage(errChat); } lua_pop(L_, 1); return false; } return true; } } // namespace wowee::addons