Kelsidavis-WoWee/src/addons/lua_system_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

761 lines
30 KiB
C++

// lua_system_api.cpp — System, time, sound, locale, map, addons, instances, and utilities Lua API bindings.
// Extracted from lua_engine.cpp as part of §5.1 (Tame LuaEngine).
#include "addons/lua_api_helpers.hpp"
#include "audio/audio_coordinator.hpp"
#include "audio/ui_sound_manager.hpp"
#include "core/window.hpp"
#include "game/expansion_profile.hpp"
namespace wowee::addons {
static int lua_PlaySound(lua_State* L) {
auto* svc = getLuaServices(L);
auto* ac = svc ? svc->audioCoordinator : nullptr;
if (!ac) return 0;
auto* sfx = ac->getUiSoundManager();
if (!sfx) return 0;
// Accept numeric sound ID or string name
std::string sound;
if (lua_isnumber(L, 1)) {
uint32_t id = static_cast<uint32_t>(lua_tonumber(L, 1));
// Map common WoW sound IDs to named sounds
switch (id) {
case 856: case 1115: sfx->playButtonClick(); return 0; // igMainMenuOption
case 840: sfx->playQuestActivate(); return 0; // igQuestListOpen
case 841: sfx->playQuestComplete(); return 0; // igQuestListComplete
case 862: sfx->playBagOpen(); return 0; // igBackPackOpen
case 863: sfx->playBagClose(); return 0; // igBackPackClose
case 867: sfx->playError(); return 0; // igPlayerInvite
case 888: sfx->playLevelUp(); return 0; // LEVELUPSOUND
default: return 0;
}
} else {
const char* name = luaL_optstring(L, 1, "");
sound = name;
for (char& c : sound) c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
if (sound == "IGMAINMENUOPTION" || sound == "IGMAINMENUOPTIONCHECKBOXON")
sfx->playButtonClick();
else if (sound == "IGQUESTLISTOPEN") sfx->playQuestActivate();
else if (sound == "IGQUESTLISTCOMPLETE") sfx->playQuestComplete();
else if (sound == "IGBACKPACKOPEN") sfx->playBagOpen();
else if (sound == "IGBACKPACKCLOSE") sfx->playBagClose();
else if (sound == "LEVELUPSOUND") sfx->playLevelUp();
else if (sound == "IGPLAYERINVITEACCEPTED") sfx->playButtonClick();
else if (sound == "TALENTSCREENOPEN") sfx->playCharacterSheetOpen();
else if (sound == "TALENTSCREENCLOSE") sfx->playCharacterSheetClose();
}
return 0;
}
// PlaySoundFile(path) — stub (file-based sounds not loaded from Lua)
static int lua_PlaySoundFile(lua_State* L) { (void)L; return 0; }
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;
}
// GetPlayerFacing() → radians (0 = north, increasing counter-clockwise)
static int lua_GetPlayerFacing(lua_State* L) {
auto* gh = getGameHandler(L);
if (gh) {
float facing = gh->getMovementInfo().orientation;
// Normalize to [0, 2π)
while (facing < 0) facing += 6.2831853f;
while (facing >= 6.2831853f) facing -= 6.2831853f;
lua_pushnumber(L, facing);
} else {
lua_pushnumber(L, 0);
}
return 1;
}
// GetCVar(name) → value string (stub for most, real for a few)
static int lua_GetCVar(lua_State* L) {
const char* name = luaL_checkstring(L, 1);
std::string n(name);
// Return sensible defaults for commonly queried CVars
if (n == "uiScale") lua_pushstring(L, "1");
else if (n == "useUIScale") lua_pushstring(L, "1");
else if (n == "screenWidth" || n == "gxResolution") {
auto* svc = getLuaServices(L);
auto* win = svc ? svc->window : nullptr;
lua_pushstring(L, std::to_string(win ? win->getWidth() : 1920).c_str());
} else if (n == "screenHeight" || n == "gxFullscreenResolution") {
auto* svc = getLuaServices(L);
auto* win = svc ? svc->window : nullptr;
lua_pushstring(L, std::to_string(win ? win->getHeight() : 1080).c_str());
} else if (n == "nameplateShowFriends") lua_pushstring(L, "1");
else if (n == "nameplateShowEnemies") lua_pushstring(L, "1");
else if (n == "Sound_EnableSFX") lua_pushstring(L, "1");
else if (n == "Sound_EnableMusic") lua_pushstring(L, "1");
else if (n == "chatBubbles") lua_pushstring(L, "1");
else if (n == "autoLootDefault") lua_pushstring(L, "1");
else lua_pushstring(L, "0");
return 1;
}
// SetCVar(name, value) — no-op stub
static int lua_SetCVar(lua_State* L) {
(void)L;
return 0;
}
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);
return luaReturnNil(L);
}
int idx = 0;
if (lua_isnumber(L, 1)) {
idx = static_cast<int>(lua_tonumber(L, 1));
} else if (lua_isstring(L, 1)) {
// Search by name
const char* name = lua_tostring(L, 1);
int count = static_cast<int>(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<int>(lua_tonumber(L, 1));
} else if (lua_isstring(L, 1)) {
const char* name = lua_tostring(L, 1);
int count = static_cast<int>(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_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);
}
// --- World Map Navigation API ---
// Map ID → continent mapping
static int mapIdToContinent(uint32_t mapId) {
switch (mapId) {
case 0: return 2; // Eastern Kingdoms
case 1: return 1; // Kalimdor
case 530: return 3; // Outland
case 571: return 4; // Northrend
default: return 0; // Instance or unknown
}
}
// Internal tracked map state (which continent/zone the map UI is viewing)
static int s_mapContinent = 0;
static int s_mapZone = 0;
// SetMapToCurrentZone() — sets map view to the player's current zone
static int lua_SetMapToCurrentZone(lua_State* L) {
auto* gh = getGameHandler(L);
if (gh) {
s_mapContinent = mapIdToContinent(gh->getCurrentMapId());
s_mapZone = static_cast<int>(gh->getWorldStateZoneId());
}
return 0;
}
// GetCurrentMapContinent() → continentId (1=Kalimdor, 2=EK, 3=Outland, 4=Northrend)
static int lua_GetCurrentMapContinent(lua_State* L) {
if (s_mapContinent == 0) {
auto* gh = getGameHandler(L);
if (gh) s_mapContinent = mapIdToContinent(gh->getCurrentMapId());
}
lua_pushnumber(L, s_mapContinent);
return 1;
}
// GetCurrentMapZone() → zoneId
static int lua_GetCurrentMapZone(lua_State* L) {
if (s_mapZone == 0) {
auto* gh = getGameHandler(L);
if (gh) s_mapZone = static_cast<int>(gh->getWorldStateZoneId());
}
lua_pushnumber(L, s_mapZone);
return 1;
}
// SetMapZoom(continent [, zone]) — sets map view to continent/zone
static int lua_SetMapZoom(lua_State* L) {
s_mapContinent = static_cast<int>(luaL_checknumber(L, 1));
s_mapZone = static_cast<int>(luaL_optnumber(L, 2, 0));
return 0;
}
// GetMapContinents() → "Kalimdor", "Eastern Kingdoms", ...
static int lua_GetMapContinents(lua_State* L) {
lua_pushstring(L, "Kalimdor");
lua_pushstring(L, "Eastern Kingdoms");
lua_pushstring(L, "Outland");
lua_pushstring(L, "Northrend");
return 4;
}
// GetMapZones(continent) → zone names for that continent
// Returns a basic list; addons mainly need this to not error
static int lua_GetMapZones(lua_State* L) {
int cont = static_cast<int>(luaL_checknumber(L, 1));
// Return a minimal representative set per continent
switch (cont) {
case 1: // Kalimdor
lua_pushstring(L, "Durotar"); lua_pushstring(L, "Mulgore");
lua_pushstring(L, "The Barrens"); lua_pushstring(L, "Teldrassil");
return 4;
case 2: // Eastern Kingdoms
lua_pushstring(L, "Elwynn Forest"); lua_pushstring(L, "Westfall");
lua_pushstring(L, "Dun Morogh"); lua_pushstring(L, "Tirisfal Glades");
return 4;
case 3: // Outland
lua_pushstring(L, "Hellfire Peninsula"); lua_pushstring(L, "Zangarmarsh");
return 2;
case 4: // Northrend
lua_pushstring(L, "Borean Tundra"); lua_pushstring(L, "Howling Fjord");
return 2;
default:
return 0;
}
}
// GetNumMapLandmarks() → 0 (no landmark data exposed yet)
static int lua_GetNumMapLandmarks(lua_State* L) {
lua_pushnumber(L, 0);
return 1;
}
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<int>(gt) % 24;
int mins = static_cast<int>((gt - static_cast<int>(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<double>(std::time(nullptr)));
return 1;
}
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 constexpr 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;
}
static int lua_GetInstanceDifficulty(lua_State* L) {
auto* gh = getGameHandler(L);
lua_pushnumber(L, gh ? (gh->getInstanceDifficulty() + 1) : 1);
return 1;
}
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<int>(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<double>(time(nullptr)));
return 1;
}
// GetTime() — returns elapsed seconds since engine start (shared epoch)
static int lua_wow_gettime(lua_State* L) {
lua_pushnumber(L, luaGetTimeNow());
return 1;
}
void registerSystemLuaAPI(lua_State* L) {
static const struct { const char* name; lua_CFunction func; } api[] = {
{"PlaySound", lua_PlaySound},
{"PlaySoundFile", lua_PlaySoundFile},
{"GetPlayerMapPosition", lua_GetPlayerMapPosition},
{"GetPlayerFacing", lua_GetPlayerFacing},
{"GetCVar", lua_GetCVar},
{"SetCVar", lua_SetCVar},
{"GetLocale", lua_GetLocale},
{"GetBuildInfo", lua_GetBuildInfo},
{"GetCurrentMapAreaID", lua_GetCurrentMapAreaID},
{"SetMapToCurrentZone", lua_SetMapToCurrentZone},
{"GetCurrentMapContinent", lua_GetCurrentMapContinent},
{"GetCurrentMapZone", lua_GetCurrentMapZone},
{"SetMapZoom", lua_SetMapZoom},
{"GetMapContinents", lua_GetMapContinents},
{"GetMapZones", lua_GetMapZones},
{"GetNumMapLandmarks", lua_GetNumMapLandmarks},
{"GetZoneText", lua_GetZoneText},
{"GetRealZoneText", lua_GetZoneText},
{"GetSubZoneText", lua_GetSubZoneText},
{"GetMinimapZoneText", lua_GetMinimapZoneText},
{"GetGameTime", lua_GetGameTime},
{"GetServerTime", lua_GetServerTime},
{"GetNumAddOns", lua_GetNumAddOns},
{"GetAddOnInfo", lua_GetAddOnInfo},
{"GetAddOnMetadata", lua_GetAddOnMetadata},
{"IsInInstance", lua_IsInInstance},
{"GetInstanceInfo", lua_GetInstanceInfo},
{"GetInstanceDifficulty", lua_GetInstanceDifficulty},
{"strsplit", lua_strsplit},
{"strtrim", lua_strtrim},
{"wipe", lua_wipe},
{"date", lua_wow_date},
{"time", lua_wow_time},
{"GetTime", lua_wow_gettime},
{"IsConnectedToServer", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
lua_pushboolean(L, gh && gh->isConnected() ? 1 : 0);
return 1;
}},
{"GetRealmName", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
if (gh) {
const auto* ac = gh->getActiveCharacter();
lua_pushstring(L, ac ? "WoWee" : "Unknown");
} else lua_pushstring(L, "Unknown");
return 1;
}},
{"GetNormalizedRealmName", [](lua_State* L) -> int {
lua_pushstring(L, "WoWee");
return 1;
}},
{"ShowHelm", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
if (gh) gh->toggleHelm(); // Toggles helm visibility
return 0;
}},
{"ShowCloak", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
if (gh) gh->toggleCloak();
return 0;
}},
{"TogglePVP", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
if (gh) gh->togglePvp();
return 0;
}},
{"Minimap_Ping", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
float x = static_cast<float>(luaL_optnumber(L, 1, 0));
float y = static_cast<float>(luaL_optnumber(L, 2, 0));
if (gh) gh->sendMinimapPing(x, y);
return 0;
}},
{"RequestTimePlayed", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
if (gh) gh->requestPlayedTime();
return 0;
}},
{"Logout", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
if (gh) gh->requestLogout();
return 0;
}},
{"CancelLogout", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
if (gh) gh->cancelLogout();
return 0;
}},
{"NumTaxiNodes", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
lua_pushnumber(L, gh ? gh->getTaxiNodes().size() : 0);
return 1;
}},
{"TaxiNodeName", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
int index = static_cast<int>(luaL_checknumber(L, 1));
if (!gh) { lua_pushstring(L, ""); return 1; }
int i = 0;
for (const auto& [id, node] : gh->getTaxiNodes()) {
if (++i == index) {
lua_pushstring(L, node.name.c_str());
return 1;
}
}
lua_pushstring(L, "");
return 1;
}},
{"TaxiNodeGetType", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
int index = static_cast<int>(luaL_checknumber(L, 1));
if (!gh) { return luaReturnZero(L); }
int i = 0;
for (const auto& [id, node] : gh->getTaxiNodes()) {
if (++i == index) {
bool known = gh->isKnownTaxiNode(id);
lua_pushnumber(L, known ? 1 : 0); // 0=none, 1=reachable, 2=current
return 1;
}
}
lua_pushnumber(L, 0);
return 1;
}},
{"TakeTaxiNode", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
int index = static_cast<int>(luaL_checknumber(L, 1));
if (!gh) return 0;
int i = 0;
for (const auto& [id, node] : gh->getTaxiNodes()) {
if (++i == index) {
gh->activateTaxi(id);
break;
}
}
return 0;
}},
{"GetNetStats", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
uint32_t ms = gh ? gh->getLatencyMs() : 0;
lua_pushnumber(L, 0); // bandwidthIn
lua_pushnumber(L, 0); // bandwidthOut
lua_pushnumber(L, ms); // latencyHome
lua_pushnumber(L, ms); // latencyWorld
return 4;
}},
{"GetCurrentTitle", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
lua_pushnumber(L, gh ? gh->getChosenTitleBit() : -1);
return 1;
}},
{"GetTitleName", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
int bit = static_cast<int>(luaL_checknumber(L, 1));
if (!gh || bit < 0) { return luaReturnNil(L); }
std::string title = gh->getFormattedTitle(static_cast<uint32_t>(bit));
if (title.empty()) { return luaReturnNil(L); }
lua_pushstring(L, title.c_str());
return 1;
}},
{"SetCurrentTitle", [](lua_State* L) -> int {
(void)L; // Title changes require CMSG_SET_TITLE which we don't expose yet
return 0;
}},
{"GetInspectSpecialization", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
const auto* ir = gh ? gh->getInspectResult() : nullptr;
lua_pushnumber(L, ir ? ir->activeTalentGroup : 0);
return 1;
}},
{"NotifyInspect", [](lua_State* L) -> int {
(void)L; // Inspect is auto-triggered by the C++ side when targeting a player
return 0;
}},
{"ClearInspectPlayer", [](lua_State* L) -> int {
(void)L;
return 0;
}},
{"GetHonorCurrency", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
lua_pushnumber(L, gh ? gh->getHonorPoints() : 0);
return 1;
}},
{"GetArenaCurrency", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
lua_pushnumber(L, gh ? gh->getArenaPoints() : 0);
return 1;
}},
{"GetTimePlayed", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
if (!gh) { lua_pushnumber(L, 0); lua_pushnumber(L, 0); return 2; }
lua_pushnumber(L, gh->getTotalTimePlayed());
lua_pushnumber(L, gh->getLevelTimePlayed());
return 2;
}},
{"GetBindLocation", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
if (!gh) { lua_pushstring(L, "Unknown"); return 1; }
lua_pushstring(L, gh->getWhoAreaName(gh->getHomeBindZoneId()).c_str());
return 1;
}},
{"GetNumSavedInstances", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
lua_pushnumber(L, gh ? gh->getInstanceLockouts().size() : 0);
return 1;
}},
{"GetSavedInstanceInfo", [](lua_State* L) -> int {
// GetSavedInstanceInfo(index) → name, id, reset, difficulty, locked, extended, instanceIDMostSig, isRaid, maxPlayers
auto* gh = getGameHandler(L);
int index = static_cast<int>(luaL_checknumber(L, 1));
if (!gh || index < 1) { return luaReturnNil(L); }
const auto& lockouts = gh->getInstanceLockouts();
if (index > static_cast<int>(lockouts.size())) { return luaReturnNil(L); }
const auto& l = lockouts[index - 1];
lua_pushstring(L, ("Instance " + std::to_string(l.mapId)).c_str()); // name (would need MapDBC for real names)
lua_pushnumber(L, l.mapId); // id
lua_pushnumber(L, static_cast<double>(l.resetTime - static_cast<uint64_t>(time(nullptr)))); // reset (seconds until)
lua_pushnumber(L, l.difficulty); // difficulty
lua_pushboolean(L, l.locked ? 1 : 0); // locked
lua_pushboolean(L, l.extended ? 1 : 0); // extended
lua_pushnumber(L, 0); // instanceIDMostSig
lua_pushboolean(L, l.difficulty >= 2 ? 1 : 0); // isRaid (25-man = raid)
lua_pushnumber(L, l.difficulty >= 2 ? 25 : (l.difficulty >= 1 ? 10 : 5)); // maxPlayers
return 9;
}},
{"CalendarGetDate", [](lua_State* L) -> int {
// CalendarGetDate() → weekday, month, day, year
time_t now = time(nullptr);
struct tm* t = localtime(&now);
lua_pushnumber(L, t->tm_wday + 1); // weekday (1=Sun)
lua_pushnumber(L, t->tm_mon + 1); // month (1-12)
lua_pushnumber(L, t->tm_mday); // day
lua_pushnumber(L, t->tm_year + 1900); // year
return 4;
}},
{"CalendarGetNumPendingInvites", [](lua_State* L) -> int {
return luaReturnZero(L);
}},
{"CalendarGetNumDayEvents", [](lua_State* L) -> int {
return luaReturnZero(L);
}},
{"GetDifficultyInfo", [](lua_State* L) -> int {
// GetDifficultyInfo(id) → name, groupType, isHeroic, maxPlayers
int diff = static_cast<int>(luaL_checknumber(L, 1));
struct DiffInfo { const char* name; const char* group; int heroic; int maxPlayers; };
static const DiffInfo infos[] = {
{"5 Player", "party", 0, 5}, // 0: Normal 5-man
{"5 Player (Heroic)", "party", 1, 5}, // 1: Heroic 5-man
{"10 Player", "raid", 0, 10}, // 2: 10-man Normal
{"25 Player", "raid", 0, 25}, // 3: 25-man Normal
{"10 Player (Heroic)", "raid", 1, 10}, // 4: 10-man Heroic
{"25 Player (Heroic)", "raid", 1, 25}, // 5: 25-man Heroic
};
if (diff >= 0 && diff < 6) {
lua_pushstring(L, infos[diff].name);
lua_pushstring(L, infos[diff].group);
lua_pushboolean(L, infos[diff].heroic);
lua_pushnumber(L, infos[diff].maxPlayers);
} else {
lua_pushstring(L, "Unknown");
lua_pushstring(L, "party");
lua_pushboolean(L, 0);
lua_pushnumber(L, 5);
}
return 4;
}},
{"GetWeatherInfo", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
if (!gh) { lua_pushnumber(L, 0); lua_pushnumber(L, 0); return 2; }
lua_pushnumber(L, gh->getWeatherType());
lua_pushnumber(L, gh->getWeatherIntensity());
return 2;
}},
{"GetMaxPlayerLevel", [](lua_State* L) -> int {
auto* svc = getLuaServices(L);
auto* reg = svc ? svc->expansionRegistry : nullptr;
auto* prof = reg ? reg->getActive() : nullptr;
if (prof && prof->id == "wotlk") lua_pushnumber(L, 80);
else if (prof && prof->id == "tbc") lua_pushnumber(L, 70);
else lua_pushnumber(L, 60);
return 1;
}},
{"GetAccountExpansionLevel", [](lua_State* L) -> int {
auto* svc = getLuaServices(L);
auto* reg = svc ? svc->expansionRegistry : nullptr;
auto* prof = reg ? reg->getActive() : nullptr;
if (prof && prof->id == "wotlk") lua_pushnumber(L, 3);
else if (prof && prof->id == "tbc") lua_pushnumber(L, 2);
else lua_pushnumber(L, 1);
return 1;
}},
};
for (const auto& [name, func] : api) {
lua_pushcfunction(L, func);
lua_setglobal(L, name);
}
}
} // namespace wowee::addons