Kelsidavis-WoWee/src/addons/lua_social_api.cpp
Paul a916270a13 chore(lua): refactor addon Lua engine API + progress docs
- Refactor Lua addon integration:
  - Update CMakeLists.txt for addon build paths
  - Enhance addons API headers and Lua engine interface
  - Add new Lua API addon modules (`lua_api_helpers`, `lua_api_registrations`, `lua_services`, `lua_action_api`, `lua_inventory_api`, `lua_quest_api`, `lua_social_api`, `lua_spell_api`, `lua_system_api`, `lua_unit_api`)
  - Update implementation in addon_manager.cpp, lua_engine.cpp, application.cpp, game_handler.cpp
2026-04-03 07:31:06 +03:00

502 lines
20 KiB
C++

// 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<char>(std::toupper(static_cast<unsigned char>(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<char>(std::toupper(static_cast<unsigned char>(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<int>(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 ? "<AFK>" : (c.status == 3 ? "<DND>" : "")); // 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<int>(luaL_checknumber(L, 1));
if (!gh || index < 1) { return luaReturnNil(L); }
const auto& roster = gh->getGuildRoster();
if (index > static_cast<int>(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<int>(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<char>(std::toupper(static_cast<unsigned char>(c)));
// Map common emote tokens to DBC TextEmote IDs
static const std::unordered_map<std::string, uint32_t> 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<int>(luaL_checknumber(L, 1));
if (!gh || index < 1) { return luaReturnNil(L); }
const auto& results = gh->getWhoResults();
if (index > static_cast<int>(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<int>(luaL_checknumber(L, 1));
if (!gh || index < 1) return 0;
const auto& opts = gh->getCurrentGossip().options;
if (index <= static_cast<int>(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<int>(luaL_optnumber(L, 1, 1));
int mx = static_cast<int>(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<int>(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