// lua_social_api.cpp — Chat, guild, friends, ignore, gossip, party management, and emotes Lua API bindings. // Extracted from lua_engine.cpp as part of §5.1 (Tame LuaEngine). #include "addons/lua_api_helpers.hpp" namespace wowee::addons { 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; } // 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_GetNumFriends(lua_State* L) { auto* gh = getGameHandler(L); if (!gh) { return luaReturnZero(L); } 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) { return luaReturnNil(L); } 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 lua_pushstring(L, c.classId < 12 ? kLuaClasses[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()) { return luaReturnNil(L); } 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) { return luaReturnNil(L); } const auto& roster = gh->getGuildRoster(); if (index > static_cast(roster.members.size())) { return luaReturnNil(L); } 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 lua_pushstring(L, m.classId < 12 ? kLuaClasses[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) { return luaReturnZero(L); } 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) { return luaReturnNil(L); } 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) void registerSocialLuaAPI(lua_State* L) { static const struct { const char* name; lua_CFunction func; } api[] = { {"SendChatMessage", lua_SendChatMessage}, {"SendAddonMessage", lua_SendAddonMessage}, {"RegisterAddonMessagePrefix", lua_RegisterAddonMessagePrefix}, {"IsAddonMessagePrefixRegistered", lua_IsAddonMessagePrefixRegistered}, {"IsInGuild", lua_IsInGuild}, {"GetGuildInfo", lua_GetGuildInfoFunc}, {"GetNumGuildMembers", lua_GetNumGuildMembers}, {"GuildRoster", [](lua_State* L) -> int { auto* gh = getGameHandler(L); if (gh) gh->requestGuildRoster(); return 0; }}, {"SortGuildRoster", [](lua_State* L) -> int { (void)L; // Sorting is client-side display only return 0; }}, {"GetGuildRosterInfo", lua_GetGuildRosterInfo}, {"GetGuildRosterMOTD", lua_GetGuildRosterMOTD}, {"GetNumFriends", lua_GetNumFriends}, {"GetFriendInfo", lua_GetFriendInfo}, {"GetNumIgnores", lua_GetNumIgnores}, {"GetIgnoreName", lua_GetIgnoreName}, {"GuildInvite", [](lua_State* L) -> int { auto* gh = getGameHandler(L); if (gh) gh->inviteToGuild(luaL_checkstring(L, 1)); return 0; }}, {"GuildUninvite", [](lua_State* L) -> int { auto* gh = getGameHandler(L); if (gh) gh->kickGuildMember(luaL_checkstring(L, 1)); return 0; }}, {"GuildPromote", [](lua_State* L) -> int { auto* gh = getGameHandler(L); if (gh) gh->promoteGuildMember(luaL_checkstring(L, 1)); return 0; }}, {"GuildDemote", [](lua_State* L) -> int { auto* gh = getGameHandler(L); if (gh) gh->demoteGuildMember(luaL_checkstring(L, 1)); return 0; }}, {"GuildLeave", [](lua_State* L) -> int { auto* gh = getGameHandler(L); if (gh) gh->leaveGuild(); return 0; }}, {"GuildSetPublicNote", [](lua_State* L) -> int { auto* gh = getGameHandler(L); if (gh) gh->setGuildPublicNote(luaL_checkstring(L, 1), luaL_checkstring(L, 2)); return 0; }}, {"DoEmote", [](lua_State* L) -> int { auto* gh = getGameHandler(L); const char* token = luaL_checkstring(L, 1); if (!gh) return 0; std::string t(token); for (char& c : t) c = static_cast(std::toupper(static_cast(c))); // Map common emote tokens to DBC TextEmote IDs static const std::unordered_map emoteMap = { {"WAVE", 67}, {"BOW", 2}, {"DANCE", 10}, {"CHEER", 5}, {"CHICKEN", 6}, {"CRY", 8}, {"EAT", 14}, {"DRINK", 13}, {"FLEX", 16}, {"KISS", 22}, {"LAUGH", 23}, {"POINT", 30}, {"ROAR", 34}, {"RUDE", 36}, {"SALUTE", 37}, {"SHY", 40}, {"SILLY", 41}, {"SIT", 42}, {"SLEEP", 43}, {"SPIT", 44}, {"THANK", 52}, {"CLAP", 7}, {"KNEEL", 21}, {"LAY", 24}, {"NO", 28}, {"YES", 70}, {"BEG", 1}, {"ANGRY", 64}, {"FAREWELL", 15}, {"HELLO", 18}, {"WELCOME", 68}, }; auto it = emoteMap.find(t); uint64_t target = gh->hasTarget() ? gh->getTargetGuid() : 0; if (it != emoteMap.end()) { gh->sendTextEmote(it->second, target); } return 0; }}, {"AddFriend", [](lua_State* L) -> int { auto* gh = getGameHandler(L); const char* name = luaL_checkstring(L, 1); const char* note = luaL_optstring(L, 2, ""); if (gh) gh->addFriend(name, note); return 0; }}, {"RemoveFriend", [](lua_State* L) -> int { auto* gh = getGameHandler(L); const char* name = luaL_checkstring(L, 1); if (gh) gh->removeFriend(name); return 0; }}, {"AddIgnore", [](lua_State* L) -> int { auto* gh = getGameHandler(L); const char* name = luaL_checkstring(L, 1); if (gh) gh->addIgnore(name); return 0; }}, {"DelIgnore", [](lua_State* L) -> int { auto* gh = getGameHandler(L); const char* name = luaL_checkstring(L, 1); if (gh) gh->removeIgnore(name); return 0; }}, {"ShowFriends", [](lua_State* L) -> int { (void)L; // Friends panel is shown via ImGui, not Lua return 0; }}, {"GetNumWhoResults", [](lua_State* L) -> int { auto* gh = getGameHandler(L); if (!gh) { lua_pushnumber(L, 0); lua_pushnumber(L, 0); return 2; } lua_pushnumber(L, gh->getWhoResults().size()); lua_pushnumber(L, gh->getWhoOnlineCount()); return 2; }}, {"GetWhoInfo", [](lua_State* L) -> int { // GetWhoInfo(index) → name, guild, level, race, class, zone, classFileName auto* gh = getGameHandler(L); int index = static_cast(luaL_checknumber(L, 1)); if (!gh || index < 1) { return luaReturnNil(L); } const auto& results = gh->getWhoResults(); if (index > static_cast(results.size())) { return luaReturnNil(L); } const auto& w = results[index - 1]; const char* raceName = (w.raceId < 12) ? kLuaRaces[w.raceId] : "Unknown"; const char* className = (w.classId < 12) ? kLuaClasses[w.classId] : "Unknown"; static constexpr const char* kClassFiles[] = {"","WARRIOR","PALADIN","HUNTER","ROGUE","PRIEST","DEATHKNIGHT","SHAMAN","MAGE","WARLOCK","","DRUID"}; const char* classFile = (w.classId < 12) ? kClassFiles[w.classId] : "WARRIOR"; lua_pushstring(L, w.name.c_str()); lua_pushstring(L, w.guildName.c_str()); lua_pushnumber(L, w.level); lua_pushstring(L, raceName); lua_pushstring(L, className); lua_pushstring(L, ""); // zone name (would need area lookup) lua_pushstring(L, classFile); return 7; }}, {"SendWho", [](lua_State* L) -> int { auto* gh = getGameHandler(L); const char* query = luaL_optstring(L, 1, ""); if (gh) gh->queryWho(query); return 0; }}, {"SetWhoToUI", [](lua_State* L) -> int { (void)L; return 0; // Stub }}, {"GetNumGossipOptions", [](lua_State* L) -> int { auto* gh = getGameHandler(L); lua_pushnumber(L, gh ? gh->getCurrentGossip().options.size() : 0); return 1; }}, {"GetGossipOptions", [](lua_State* L) -> int { // Returns pairs of (text, type) for each option auto* gh = getGameHandler(L); if (!gh) return 0; const auto& opts = gh->getCurrentGossip().options; int n = 0; static constexpr const char* kIcons[] = {"gossip","vendor","taxi","trainer","spiritguide","innkeeper","banker","petition","tabard","battlemaster","auctioneer"}; for (const auto& o : opts) { lua_pushstring(L, o.text.c_str()); lua_pushstring(L, o.icon < 11 ? kIcons[o.icon] : "gossip"); n += 2; } return n; }}, {"SelectGossipOption", [](lua_State* L) -> int { auto* gh = getGameHandler(L); int index = static_cast(luaL_checknumber(L, 1)); if (!gh || index < 1) return 0; const auto& opts = gh->getCurrentGossip().options; if (index <= static_cast(opts.size())) gh->selectGossipOption(opts[index - 1].id); return 0; }}, {"GetNumGossipAvailableQuests", [](lua_State* L) -> int { auto* gh = getGameHandler(L); if (!gh) { return luaReturnZero(L); } int count = 0; for (const auto& q : gh->getCurrentGossip().quests) if (q.questIcon != 4) ++count; // 4 = active/in-progress lua_pushnumber(L, count); return 1; }}, {"GetNumGossipActiveQuests", [](lua_State* L) -> int { auto* gh = getGameHandler(L); if (!gh) { return luaReturnZero(L); } int count = 0; for (const auto& q : gh->getCurrentGossip().quests) if (q.questIcon == 4) ++count; lua_pushnumber(L, count); return 1; }}, {"CloseGossip", [](lua_State* L) -> int { auto* gh = getGameHandler(L); if (gh) gh->closeGossip(); return 0; }}, {"InviteUnit", [](lua_State* L) -> int { auto* gh = getGameHandler(L); if (gh) gh->inviteToGroup(luaL_checkstring(L, 1)); return 0; }}, {"UninviteUnit", [](lua_State* L) -> int { auto* gh = getGameHandler(L); if (gh) gh->uninvitePlayer(luaL_checkstring(L, 1)); return 0; }}, {"LeaveParty", [](lua_State* L) -> int { auto* gh = getGameHandler(L); if (gh) gh->leaveGroup(); return 0; }}, {"FollowUnit", [](lua_State* L) -> int { (void)L; // Follow requires movement system integration return 0; }}, {"RandomRoll", [](lua_State* L) -> int { auto* gh = getGameHandler(L); int mn = static_cast(luaL_optnumber(L, 1, 1)); int mx = static_cast(luaL_optnumber(L, 2, 100)); if (gh) gh->randomRoll(mn, mx); return 0; }}, {"JoinChannelByName", [](lua_State* L) -> int { auto* gh = getGameHandler(L); const char* name = luaL_checkstring(L, 1); const char* pw = luaL_optstring(L, 2, ""); if (gh) gh->joinChannel(name, pw); return 0; }}, {"LeaveChannelByName", [](lua_State* L) -> int { auto* gh = getGameHandler(L); const char* name = luaL_checkstring(L, 1); if (gh) gh->leaveChannel(name); return 0; }}, {"GetChannelName", [](lua_State* L) -> int { auto* gh = getGameHandler(L); int index = static_cast(luaL_checknumber(L, 1)); if (!gh || index < 1) { return luaReturnNil(L); } std::string name = gh->getChannelByIndex(index - 1); if (!name.empty()) { lua_pushstring(L, name.c_str()); lua_pushstring(L, ""); // header lua_pushboolean(L, 0); // collapsed lua_pushnumber(L, index); // channelNumber lua_pushnumber(L, 0); // count lua_pushboolean(L, 1); // active lua_pushstring(L, "CHANNEL_CATEGORY_CUSTOM"); // category return 7; } lua_pushnil(L); return 1; }}, }; for (const auto& [name, func] : api) { lua_pushcfunction(L, func); lua_setglobal(L, name); } } } // namespace wowee::addons