2026-03-20 11:12:07 -07:00
# include "addons/lua_engine.hpp"
2026-03-20 13:07:45 -07:00
# include "addons/toc_parser.hpp"
2026-03-20 11:12:07 -07:00
# include "game/game_handler.hpp"
feat: add core WoW Lua API functions for addon development
Add 13 WoW-compatible Lua API functions that addons can call:
Unit API: UnitName, UnitHealth, UnitHealthMax, UnitPower, UnitPowerMax,
UnitLevel, UnitExists, UnitIsDead, UnitClass (supports "player",
"target", "focus", "pet" unit IDs)
Game API: GetMoney, IsInGroup, IsInRaid, GetPlayerMapPosition
Updated HelloWorld addon to demonstrate querying player state.
2026-03-20 11:17:15 -07:00
# include "game/entity.hpp"
2026-03-21 01:37:44 -07:00
# include "game/update_field_table.hpp"
2026-03-20 11:12:07 -07:00
# include "core/logger.hpp"
feat: add GetCursorPosition, screen size queries, and frame positioning methods
Add global Lua API functions:
- GetCursorPosition() returns mouse x,y screen coordinates
- GetScreenWidth()/GetScreenHeight() return window dimensions
Add frame methods for UI layout:
- SetPoint, SetSize, SetWidth, SetHeight, GetWidth, GetHeight, GetCenter
- SetAlpha, GetAlpha, SetParent, GetParent
These enable UI customization addons to query cursor position, screen
dimensions, and manage frame layout — fundamental for unit frames,
action bars, and tooltip addons.
2026-03-21 01:44:59 -07:00
# include "core/application.hpp"
# include <imgui.h>
2026-03-20 12:07:22 -07:00
# include <cstring>
2026-03-20 12:22:50 -07:00
# include <fstream>
# include <filesystem>
2026-03-20 11:12:07 -07:00
extern " C " {
# include <lua.h>
# include <lauxlib.h>
# include <lualib.h>
}
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 < game : : GameHandler * > ( 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 ) ;
}
2026-03-20 14:15:00 -07:00
// Helper: resolve WoW unit IDs to GUID
2026-03-21 01:37:44 -07:00
// 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 < uint64_t > ( hiIt - > second ) < < 32 ) ;
return targetGuid ;
}
2026-03-20 14:15:00 -07:00
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 ( ) ;
2026-03-21 01:26:37 -07:00
if ( uid = = " mouseover " ) return gh - > getMouseoverGuid ( ) ;
2026-03-20 14:15:00 -07:00
if ( uid = = " pet " ) return gh - > getPetGuid ( ) ;
2026-03-21 01:37:44 -07:00
// 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 ( ) ) ;
2026-03-20 14:15:00 -07:00
// 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 < int > ( pd . members . size ( ) ) )
return pd . members [ idx - 1 ] . guid ;
return 0 ;
}
return 0 ;
}
2026-03-21 01:37:44 -07:00
// Helper: resolve unit IDs (player, target, focus, mouseover, pet, targettarget, focustarget, etc.) to entity
feat: add core WoW Lua API functions for addon development
Add 13 WoW-compatible Lua API functions that addons can call:
Unit API: UnitName, UnitHealth, UnitHealthMax, UnitPower, UnitPowerMax,
UnitLevel, UnitExists, UnitIsDead, UnitClass (supports "player",
"target", "focus", "pet" unit IDs)
Game API: GetMoney, IsInGroup, IsInRaid, GetPlayerMapPosition
Updated HelloWorld addon to demonstrate querying player state.
2026-03-20 11:17:15 -07:00
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 < char > ( std : : tolower ( static_cast < unsigned char > ( c ) ) ) ;
2026-03-20 14:15:00 -07:00
uint64_t guid = resolveUnitGuid ( gh , uid ) ;
feat: add core WoW Lua API functions for addon development
Add 13 WoW-compatible Lua API functions that addons can call:
Unit API: UnitName, UnitHealth, UnitHealthMax, UnitPower, UnitPowerMax,
UnitLevel, UnitExists, UnitIsDead, UnitClass (supports "player",
"target", "focus", "pet" unit IDs)
Game API: GetMoney, IsInGroup, IsInRaid, GetPlayerMapPosition
Updated HelloWorld addon to demonstrate querying player state.
2026-03-20 11:17:15 -07:00
if ( guid = = 0 ) return nullptr ;
auto entity = gh - > getEntityManager ( ) . getEntity ( guid ) ;
if ( ! entity ) return nullptr ;
return dynamic_cast < game : : Unit * > ( entity . get ( ) ) ;
}
// --- WoW Unit API ---
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 {
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 ) ;
lua_pushnumber ( L , unit ? unit - > getHealth ( ) : 0 ) ;
return 1 ;
}
static int lua_UnitHealthMax ( lua_State * L ) {
const char * uid = luaL_optstring ( L , 1 , " player " ) ;
auto * unit = resolveUnit ( L , uid ) ;
lua_pushnumber ( L , unit ? unit - > getMaxHealth ( ) : 0 ) ;
return 1 ;
}
static int lua_UnitPower ( lua_State * L ) {
const char * uid = luaL_optstring ( L , 1 , " player " ) ;
auto * unit = resolveUnit ( L , uid ) ;
lua_pushnumber ( L , unit ? unit - > getPower ( ) : 0 ) ;
return 1 ;
}
static int lua_UnitPowerMax ( lua_State * L ) {
const char * uid = luaL_optstring ( L , 1 , " player " ) ;
auto * unit = resolveUnit ( L , uid ) ;
lua_pushnumber ( L , unit ? unit - > getMaxPower ( ) : 0 ) ;
return 1 ;
}
static int lua_UnitLevel ( lua_State * L ) {
const char * uid = luaL_optstring ( L , 1 , " player " ) ;
auto * unit = resolveUnit ( L , uid ) ;
lua_pushnumber ( L , unit ? unit - > getLevel ( ) : 0 ) ;
return 1 ;
}
static int lua_UnitExists ( lua_State * L ) {
const char * uid = luaL_optstring ( L , 1 , " player " ) ;
auto * unit = resolveUnit ( L , uid ) ;
lua_pushboolean ( L , unit ! = nullptr ) ;
return 1 ;
}
static int lua_UnitIsDead ( lua_State * L ) {
const char * uid = luaL_optstring ( L , 1 , " player " ) ;
auto * unit = resolveUnit ( L , uid ) ;
lua_pushboolean ( L , unit & & unit - > getHealth ( ) = = 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 ;
// For player, use character data; for others, use UNIT_FIELD_BYTES_0
std : : string uidStr ( uid ) ;
for ( char & c : uidStr ) c = static_cast < char > ( std : : tolower ( static_cast < unsigned char > ( c ) ) ) ;
if ( uidStr = = " player " ) classId = gh - > getPlayerClass ( ) ;
const char * name = ( classId < 12 ) ? kClasses [ classId ] : " Unknown " ;
lua_pushstring ( L , name ) ;
lua_pushstring ( L , name ) ; // WoW returns localized + English
lua_pushnumber ( L , classId ) ;
return 3 ;
}
lua_pushstring ( L , " Unknown " ) ;
lua_pushstring ( L , " Unknown " ) ;
lua_pushnumber ( L , 0 ) ;
return 3 ;
}
// --- Player/Game API ---
static int lua_GetMoney ( lua_State * L ) {
auto * gh = getGameHandler ( L ) ;
lua_pushnumber ( L , gh ? static_cast < double > ( 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 ;
}
2026-03-20 12:15:36 -07:00
static int lua_UnitRace ( lua_State * L ) {
auto * gh = getGameHandler ( L ) ;
if ( ! gh ) { lua_pushstring ( L , " Unknown " ) ; return 1 ; }
std : : string uid ( luaL_optstring ( L , 1 , " player " ) ) ;
for ( char & c : uid ) c = static_cast < char > ( std : : tolower ( static_cast < unsigned char > ( c ) ) ) ;
if ( uid = = " player " ) {
uint8_t race = gh - > getPlayerRace ( ) ;
static const char * kRaces [ ] = { " " , " Human " , " Orc " , " Dwarf " , " Night Elf " , " Undead " ,
" Tauren " , " Gnome " , " Troll " , " " , " Blood Elf " , " Draenei " } ;
lua_pushstring ( L , ( race < 12 ) ? kRaces [ race ] : " Unknown " ) ;
return 1 ;
}
lua_pushstring ( L , " Unknown " ) ;
return 1 ;
}
static int lua_UnitPowerType ( lua_State * L ) {
const char * uid = luaL_optstring ( L , 1 , " player " ) ;
auto * unit = resolveUnit ( L , uid ) ;
if ( unit ) {
lua_pushnumber ( L , unit - > getPowerType ( ) ) ;
static const char * kPowerNames [ ] = { " MANA " , " RAGE " , " FOCUS " , " ENERGY " , " HAPPINESS " , " " , " RUNIC_POWER " } ;
uint8_t pt = unit - > getPowerType ( ) ;
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 < char > ( std : : tolower ( static_cast < unsigned char > ( c ) ) ) ;
2026-03-20 14:15:00 -07:00
uint64_t guid = resolveUnitGuid ( gh , uidStr ) ;
2026-03-20 12:15:36 -07:00
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 < char > ( std : : tolower ( static_cast < unsigned char > ( c ) ) ) ;
2026-03-20 14:15:00 -07:00
uint64_t guid = resolveUnitGuid ( gh , uidStr ) ;
2026-03-20 12:15:36 -07:00
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 ;
}
2026-03-20 13:07:45 -07:00
// --- 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 < 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 ;
}
feat: add UnitBuff and UnitDebuff API for Lua addons
Implement the most commonly used buff/debuff query functions:
- UnitBuff(unitId, index) — query the Nth buff on a unit
- UnitDebuff(unitId, index) — query the Nth debuff on a unit
Returns WoW-compatible 11-value tuple: name, rank, icon, count,
debuffType, duration, expirationTime, caster, isStealable,
shouldConsolidate, spellId.
Supports "player" and "target" unit IDs. Essential for buff tracking
addons (WeakAuras-style), healer addons, and combat analysis tools.
Total WoW API: 31 functions.
2026-03-20 12:45:43 -07:00
// 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 < int > ( luaL_optnumber ( L , 2 , 1 ) ) ;
if ( index < 1 ) { lua_pushnil ( L ) ; return 1 ; }
std : : string uidStr ( uid ) ;
for ( char & c : uidStr ) c = static_cast < char > ( std : : tolower ( static_cast < unsigned char > ( c ) ) ) ;
const std : : vector < game : : AuraSlot > * auras = nullptr ;
if ( uidStr = = " player " ) auras = & gh - > getPlayerAuras ( ) ;
else if ( uidStr = = " target " ) auras = & gh - > getTargetAuras ( ) ;
2026-03-20 14:15:00 -07:00
else {
// Try party/raid/focus via GUID lookup in unitAurasCache
uint64_t guid = resolveUnitGuid ( gh , uidStr ) ;
if ( guid ! = 0 ) auras = gh - > getUnitAuras ( guid ) ;
}
feat: add UnitBuff and UnitDebuff API for Lua addons
Implement the most commonly used buff/debuff query functions:
- UnitBuff(unitId, index) — query the Nth buff on a unit
- UnitDebuff(unitId, index) — query the Nth debuff on a unit
Returns WoW-compatible 11-value tuple: name, rank, icon, count,
debuffType, duration, expirationTime, caster, isStealable,
shouldConsolidate, spellId.
Supports "player" and "target" unit IDs. Essential for buff tracking
addons (WeakAuras-style), healer addons, and combat analysis tools.
Total WoW API: 31 functions.
2026-03-20 12:45:43 -07:00
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
2026-03-20 13:58:54 -07:00
std : : string iconPath = gh - > getSpellIconPath ( aura . spellId ) ;
if ( ! iconPath . empty ( ) ) lua_pushstring ( L , iconPath . c_str ( ) ) ;
else lua_pushnil ( L ) ; // icon texture path
feat: add UnitBuff and UnitDebuff API for Lua addons
Implement the most commonly used buff/debuff query functions:
- UnitBuff(unitId, index) — query the Nth buff on a unit
- UnitDebuff(unitId, index) — query the Nth debuff on a unit
Returns WoW-compatible 11-value tuple: name, rank, icon, count,
debuffType, duration, expirationTime, caster, isStealable,
shouldConsolidate, spellId.
Supports "player" and "target" unit IDs. Essential for buff tracking
addons (WeakAuras-style), healer addons, and combat analysis tools.
Total WoW API: 31 functions.
2026-03-20 12:45:43 -07:00
lua_pushnumber ( L , aura . charges ) ; // count
2026-03-20 17:53:01 -07:00
// 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 ;
}
}
feat: add UnitBuff and UnitDebuff API for Lua addons
Implement the most commonly used buff/debuff query functions:
- UnitBuff(unitId, index) — query the Nth buff on a unit
- UnitDebuff(unitId, index) — query the Nth debuff on a unit
Returns WoW-compatible 11-value tuple: name, rank, icon, count,
debuffType, duration, expirationTime, caster, isStealable,
shouldConsolidate, spellId.
Supports "player" and "target" unit IDs. Essential for buff tracking
addons (WeakAuras-style), healer addons, and combat analysis tools.
Total WoW API: 31 functions.
2026-03-20 12:45:43 -07:00
lua_pushnumber ( L , aura . maxDurationMs > 0 ? aura . maxDurationMs / 1000.0 : 0 ) ; // duration
2026-03-20 18:00:57 -07:00
// expirationTime: GetTime() + remaining seconds (so addons can compute countdown)
if ( aura . durationMs > 0 ) {
uint64_t auraNowMs = static_cast < uint64_t > (
std : : chrono : : duration_cast < std : : chrono : : milliseconds > (
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 < double > (
std : : chrono : : steady_clock : : now ( ) - sStart ) . count ( ) ;
lua_pushnumber ( L , nowSec + remMs / 1000.0 ) ;
} else {
lua_pushnumber ( L , 0 ) ; // permanent aura
}
2026-03-20 17:58:53 -07:00
// 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 ) ;
}
feat: add UnitBuff and UnitDebuff API for Lua addons
Implement the most commonly used buff/debuff query functions:
- UnitBuff(unitId, index) — query the Nth buff on a unit
- UnitDebuff(unitId, index) — query the Nth debuff on a unit
Returns WoW-compatible 11-value tuple: name, rank, icon, count,
debuffType, duration, expirationTime, caster, isStealable,
shouldConsolidate, spellId.
Supports "player" and "target" unit IDs. Essential for buff tracking
addons (WeakAuras-style), healer addons, and combat analysis tools.
Total WoW API: 31 functions.
2026-03-20 12:45:43 -07:00
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 ) ; }
2026-03-20 16:42:06 -07:00
// 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 < char > ( std : : toupper ( static_cast < unsigned char > ( c ) ) ) ;
bool wantBuff = ( f . find ( " HARMFUL " ) = = std : : string : : npos ) ;
return lua_UnitAura ( L , wantBuff ) ;
}
feat: add action WoW API functions for Lua addons
Add 5 more essential WoW API functions for addon development:
- SendChatMessage(msg, type, lang, target) — send chat messages
(SAY, YELL, WHISPER, PARTY, GUILD, OFFICER, RAID, BG)
- CastSpellByName(name) — cast highest rank of named spell
- IsSpellKnown(spellId) — check if player knows a spell
- GetSpellCooldown(nameOrId) — get remaining cooldown
- HasTarget() — check if player has a target
Total WoW API surface: 18 functions across Unit, Game, and Action
categories. Addons can now query state, react to events, send
messages, and cast spells.
2026-03-20 11:34:04 -07:00
// --- 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 < 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 ;
}
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 < char > ( std : : tolower ( static_cast < unsigned char > ( 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 < char > ( std : : tolower ( static_cast < unsigned char > ( 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 < char > ( std : : tolower ( static_cast < unsigned char > ( 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 ;
}
feat: add SendAddonMessage and RegisterAddonMessagePrefix for addon comms
Implement the addon messaging API used by virtually every multiplayer
addon (DBM, BigWigs, EPGP, RC Loot Council, WeakAuras, etc.):
- SendAddonMessage(prefix, text, chatType, target) sends an addon
message encoded as "prefix\ttext" via the appropriate chat channel
- RegisterAddonMessagePrefix(prefix) registers a prefix for filtering
incoming addon messages
- IsAddonMessagePrefixRegistered(prefix) checks registration status
- C_ChatInfo table with aliases for the above functions (newer API compat)
Without these functions, all inter-addon communication between players
fails, breaking boss mods, loot distribution, and group coordination.
2026-03-21 03:31:54 -07:00
// 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 ;
}
feat: add action WoW API functions for Lua addons
Add 5 more essential WoW API functions for addon development:
- SendChatMessage(msg, type, lang, target) — send chat messages
(SAY, YELL, WHISPER, PARTY, GUILD, OFFICER, RAID, BG)
- CastSpellByName(name) — cast highest rank of named spell
- IsSpellKnown(spellId) — check if player knows a spell
- GetSpellCooldown(nameOrId) — get remaining cooldown
- HasTarget() — check if player has a target
Total WoW API surface: 18 functions across Unit, Game, and Action
categories. Addons can now query state, react to events, send
messages, and cast spells.
2026-03-20 11:34:04 -07:00
static int lua_IsSpellKnown ( lua_State * L ) {
auto * gh = getGameHandler ( L ) ;
uint32_t spellId = static_cast < uint32_t > ( 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 < uint32_t > ( lua_tonumber ( L , 1 ) ) ;
} else {
const char * name = luaL_checkstring ( L , 1 ) ;
std : : string nameLow ( name ) ;
for ( char & c : nameLow ) c = static_cast < char > ( std : : tolower ( static_cast < unsigned char > ( c ) ) ) ;
for ( uint32_t sid : gh - > getKnownSpells ( ) ) {
std : : string sn = gh - > getSpellName ( sid ) ;
for ( char & c : sn ) c = static_cast < char > ( std : : tolower ( static_cast < unsigned char > ( c ) ) ) ;
if ( sn = = nameLow ) { spellId = sid ; break ; }
}
}
float cd = gh - > getSpellCooldown ( spellId ) ;
2026-03-20 19:03:34 -07:00
// 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 < double > (
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 ;
feat: add action WoW API functions for Lua addons
Add 5 more essential WoW API functions for addon development:
- SendChatMessage(msg, type, lang, target) — send chat messages
(SAY, YELL, WHISPER, PARTY, GUILD, OFFICER, RAID, BG)
- CastSpellByName(name) — cast highest rank of named spell
- IsSpellKnown(spellId) — check if player knows a spell
- GetSpellCooldown(nameOrId) — get remaining cooldown
- HasTarget() — check if player has a target
Total WoW API surface: 18 functions across Unit, Game, and Action
categories. Addons can now query state, react to events, send
messages, and cast spells.
2026-03-20 11:34:04 -07:00
}
static int lua_HasTarget ( lua_State * L ) {
auto * gh = getGameHandler ( L ) ;
lua_pushboolean ( L , gh & & gh - > hasTarget ( ) ) ;
return 1 ;
}
2026-03-21 01:58:03 -07:00
// 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 < char > ( std : : tolower ( static_cast < unsigned char > ( 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 < char > ( std : : tolower ( static_cast < unsigned char > ( 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 < char > ( std : : tolower ( static_cast < unsigned char > ( 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 ;
}
2026-03-20 13:58:54 -07:00
// --- 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 < uint32_t > ( 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 < char > ( std : : tolower ( static_cast < unsigned char > ( c ) ) ) ;
int bestRank = - 1 ;
for ( uint32_t sid : gh - > getKnownSpells ( ) ) {
std : : string sn = gh - > getSpellName ( sid ) ;
for ( char & c : sn ) c = static_cast < char > ( std : : tolower ( static_cast < unsigned char > ( 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 < char > ( std : : tolower ( static_cast < unsigned char > ( 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
lua_pushnumber ( L , 0 ) ; // 4: castTime (ms) — not tracked
lua_pushnumber ( L , 0 ) ; // 5: minRange
lua_pushnumber ( L , 0 ) ; // 6: maxRange
lua_pushnumber ( L , spellId ) ; // 7: spellId
return 7 ;
}
// 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 < uint32_t > ( 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 < char > ( std : : tolower ( static_cast < unsigned char > ( c ) ) ) ;
for ( uint32_t sid : gh - > getKnownSpells ( ) ) {
std : : string sn = gh - > getSpellName ( sid ) ;
for ( char & c : sn ) c = static_cast < char > ( std : : tolower ( static_cast < unsigned char > ( 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 < uint32_t > ( 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 < uint32_t > ( 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
2026-03-21 02:53:07 -07:00
// 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 ) ;
}
2026-03-20 13:58:54 -07:00
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 ;
}
2026-03-20 15:44:25 -07:00
// 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 ) ;
}
2026-03-20 14:57:13 -07:00
// --- 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 ;
}
feat: add container/bag Lua API for bag addon support
Add GetContainerNumSlots(bag), GetContainerItemInfo(bag, slot),
GetContainerItemLink(bag, slot), and GetContainerNumFreeSlots(bag).
Container 0 = backpack (16 slots), containers 1-4 = equipped bags.
Returns item count, quality, and WoW-format item links with quality
colors. Enables bag management addons (Bagnon, OneBag, AdiBags).
2026-03-20 17:14:07 -07:00
// --- 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 < int > ( 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 < int > ( luaL_checknumber ( L , 1 ) ) ;
int slot = static_cast < int > ( 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 < int > ( luaL_checknumber ( L , 1 ) ) ;
int slot = static_cast < int > ( 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 < int > ( 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 ;
}
2026-03-20 17:56:20 -07:00
// --- 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 < int > ( 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 < char > ( std : : tolower ( static_cast < unsigned char > ( c ) ) ) ;
if ( uidStr ! = " player " ) { lua_pushnil ( L ) ; return 1 ; }
const auto & inv = gh - > getInventory ( ) ;
const auto & slot = inv . getEquipSlot ( static_cast < game : : EquipSlot > ( 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 < uint32_t > ( 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 < int > ( 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 < char > ( std : : tolower ( static_cast < unsigned char > ( c ) ) ) ;
if ( uidStr ! = " player " ) { lua_pushnil ( L ) ; return 1 ; }
const auto & inv = gh - > getInventory ( ) ;
const auto & slot = inv . getEquipSlot ( static_cast < game : : EquipSlot > ( 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 < int > ( 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 < char > ( std : : tolower ( static_cast < unsigned char > ( c ) ) ) ;
if ( uidStr ! = " player " ) { lua_pushnil ( L ) ; return 1 ; }
const auto & inv = gh - > getInventory ( ) ;
const auto & slot = inv . getEquipSlot ( static_cast < game : : EquipSlot > ( 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 ;
}
2026-03-20 18:16:12 -07:00
// --- 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 < 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_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 < char > ( std : : tolower ( static_cast < unsigned char > ( 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 < char > ( std : : tolower ( static_cast < unsigned char > ( c ) ) ) ;
if ( u = = " player " ) {
uint32_t nlxp = gh - > getPlayerNextLevelXp ( ) ;
lua_pushnumber ( L , nlxp > 0 ? nlxp : 1 ) ;
} else {
lua_pushnumber ( L , 1 ) ;
}
return 1 ;
}
2026-03-20 18:53:56 -07:00
// 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 ;
}
2026-03-20 17:37:35 -07:00
// --- 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 < int > ( luaL_checknumber ( L , 1 ) ) ;
if ( ! gh | | index < 1 ) { lua_pushnil ( L ) ; return 1 ; }
const auto & ql = gh - > getQuestLog ( ) ;
if ( index > static_cast < int > ( 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 < int > ( luaL_checknumber ( L , 1 ) ) ;
if ( ! gh | | index < 1 ) { lua_pushnil ( L ) ; return 1 ; }
const auto & ql = gh - > getQuestLog ( ) ;
if ( index > static_cast < int > ( 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 < uint32_t > ( 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 ;
}
2026-03-21 02:18:25 -07:00
// --- 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 < int > ( luaL_checknumber ( L , 1 ) ) ;
if ( ! gh | | index < 1 ) {
lua_pushnil ( L ) ;
return 1 ;
}
const auto & skills = gh - > getPlayerSkills ( ) ;
if ( index > static_cast < int > ( 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 ;
}
2026-03-21 03:08:37 -07:00
// --- 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 < int > ( 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 ? " <AFK> " : ( c . status = = 3 ? " <DND> " : " " ) ) ; // 6: status
lua_pushstring ( L , c . note . c_str ( ) ) ; // 7: note
return 7 ;
}
}
lua_pushnil ( L ) ;
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 < int > ( 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 ;
}
feat: add talent tree Lua API for talent inspection addons
Implement 5 talent-related WoW Lua API functions:
- GetNumTalentTabs() returns class-specific talent tree count (usually 3)
- GetTalentTabInfo(tab) returns name, icon, pointsSpent, background
- GetNumTalents(tab) returns talent count in a specific tree
- GetTalentInfo(tab, index) returns full 8-value tuple with name, tier,
column, current rank, max rank, and availability
- GetActiveTalentGroup() returns active spec (1 or 2)
Data sourced from Talent.dbc, TalentTab.dbc, and the server-sent talent
info packet. Enables talent addons and spec display addons.
2026-03-21 02:22:35 -07:00
// --- 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 < int > ( 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 < const game : : GameHandler : : TalentTabEntry * > 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 < int > ( 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 < int > ( 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 < const game : : GameHandler : : TalentTabEntry * > 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 < int > ( 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 < int > ( luaL_checknumber ( L , 1 ) ) ;
int talentIndex = static_cast < int > ( 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 < const game : : GameHandler : : TalentTabEntry * > 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 < int > ( 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 < const game : : GameHandler : : TalentEntry * > 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 < int > ( 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 ;
}
2026-03-21 01:42:03 -07:00
// --- 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 < int > ( 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 < int > ( loot . items . size ( ) ) ) {
lua_pushnil ( L ) ; return 1 ;
}
const auto & item = loot . items [ slot - 1 ] ;
const auto * info = gh - > getItemInfo ( item . itemId ) ;
2026-03-21 02:53:07 -07:00
// texture (icon path from ItemDisplayInfo.dbc)
2026-03-21 01:42:03 -07:00
std : : string icon ;
2026-03-21 02:53:07 -07:00
if ( info & & info - > displayInfoId ! = 0 ) {
icon = gh - > getItemIconPath ( info - > displayInfoId ) ;
2026-03-21 01:42:03 -07:00
}
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 < int > ( luaL_checknumber ( L , 1 ) ) ;
if ( ! gh | | ! gh - > isLootWindowOpen ( ) ) { lua_pushnil ( L ) ; return 1 ; }
const auto & loot = gh - > getCurrentLoot ( ) ;
if ( slot < 1 | | slot > static_cast < int > ( 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 < int > ( luaL_checknumber ( L , 1 ) ) ;
if ( ! gh | | ! gh - > isLootWindowOpen ( ) ) return 0 ;
const auto & loot = gh - > getCurrentLoot ( ) ;
if ( slot < 1 | | slot > static_cast < int > ( 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 ;
}
feat: add 9 more WoW Lua API functions for group and unit queries
Add UnitAffectingCombat, GetNumRaidMembers, GetNumPartyMembers, UnitInParty,
UnitInRaid, UnitIsUnit, UnitIsFriend, UnitIsEnemy, and UnitCreatureType.
These are commonly used by raid/group addons for party composition checks,
combat state queries, and mob type identification. Total API count now 55.
2026-03-20 15:21:38 -07:00
// --- 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 < char > ( std : : tolower ( static_cast < unsigned char > ( c ) ) ) ;
if ( uidStr = = " player " ) {
lua_pushboolean ( L , gh - > isInCombat ( ) ) ;
} else {
lua_pushboolean ( L , 0 ) ;
}
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 < int > ( 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 < char > ( std : : tolower ( static_cast < unsigned char > ( 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 < char > ( std : : tolower ( static_cast < unsigned char > ( 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 < char > ( std : : tolower ( static_cast < unsigned char > ( c ) ) ) ;
for ( char & c : u2 ) c = static_cast < char > ( std : : tolower ( static_cast < unsigned char > ( 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 < char > ( std : : tolower ( static_cast < unsigned char > ( 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 < game : : Unit > ( 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 ;
}
2026-03-20 20:22:15 -07:00
// 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 ;
}
2026-03-20 20:07:45 -07:00
// 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 < uint32_t > ( 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 ;
}
2026-03-20 19:57:13 -07:00
// 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 < uint32_t > ( 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 < char > ( std : : tolower ( static_cast < unsigned char > ( c ) ) ) ;
for ( uint32_t sid : gh - > getKnownSpells ( ) ) {
std : : string sn = gh - > getSpellName ( sid ) ;
for ( char & c : sn ) c = static_cast < char > ( std : : tolower ( static_cast < unsigned char > ( 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 ;
}
2026-03-20 18:42:33 -07:00
// 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 < uint32_t > ( 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 < char > ( std : : tolower ( static_cast < unsigned char > ( c ) ) ) ;
for ( uint32_t sid : gh - > getKnownSpells ( ) ) {
std : : string sn = gh - > getSpellName ( sid ) ;
for ( char & c : sn ) c = static_cast < char > ( std : : tolower ( static_cast < unsigned char > ( 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 ) ;
lua_pushboolean ( L , onCooldown ? 0 : 1 ) ; // usable (not on cooldown)
lua_pushboolean ( L , 0 ) ; // noMana (can't determine without spell cost data)
return 2 ;
}
2026-03-20 18:33:44 -07:00
// 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 ;
}
2026-03-20 18:25:39 -07:00
// 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 < char > ( std : : tolower ( static_cast < unsigned char > ( 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 < game : : Unit > ( 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 ;
}
feat: add action bar, combo points, reaction, and connection Lua API functions
Implement 10 new WoW Lua API functions for addon compatibility:
- GetComboPoints, UnitReaction, UnitIsConnected for unit frames/raid addons
- HasAction, GetActionTexture, IsCurrentAction, IsUsableAction, GetActionCooldown
for action bar addons (Bartender, Dominos, etc.)
- UnitMana/UnitManaMax as Classic-era aliases for UnitPower/UnitPowerMax
2026-03-21 01:31:34 -07:00
// 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 < char > ( std : : tolower ( static_cast < unsigned char > ( c ) ) ) ;
std : : string u2 ( uid2 ) ;
for ( char & c : u2 ) c = static_cast < char > ( std : : tolower ( static_cast < unsigned char > ( 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 < char > ( std : : tolower ( static_cast < unsigned char > ( 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 < int > ( luaL_checknumber ( L , 1 ) ) - 1 ; // WoW uses 1-indexed slots
const auto & bar = gh - > getActionBar ( ) ;
if ( slot < 0 | | slot > = static_cast < int > ( 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 < int > ( luaL_checknumber ( L , 1 ) ) - 1 ;
const auto & bar = gh - > getActionBar ( ) ;
if ( slot < 0 | | slot > = static_cast < int > ( 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 ;
}
2026-03-21 02:53:07 -07:00
} 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 ;
}
}
feat: add action bar, combo points, reaction, and connection Lua API functions
Implement 10 new WoW Lua API functions for addon compatibility:
- GetComboPoints, UnitReaction, UnitIsConnected for unit frames/raid addons
- HasAction, GetActionTexture, IsCurrentAction, IsUsableAction, GetActionCooldown
for action bar addons (Bartender, Dominos, etc.)
- UnitMana/UnitManaMax as Classic-era aliases for UnitPower/UnitPowerMax
2026-03-21 01:31:34 -07:00
}
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 < int > ( luaL_checknumber ( L , 1 ) ) - 1 ;
const auto & bar = gh - > getActionBar ( ) ;
if ( slot < 0 | | slot > = static_cast < int > ( bar . size ( ) ) | | bar [ slot ] . isEmpty ( ) ) {
lua_pushboolean ( L , 0 ) ;
lua_pushboolean ( L , 0 ) ;
return 2 ;
}
const auto & action = bar [ slot ] ;
bool usable = action . isReady ( ) ;
if ( action . type = = game : : ActionBarSlot : : SPELL ) {
usable = usable & & gh - > getKnownSpells ( ) . count ( action . id ) ;
}
lua_pushboolean ( L , usable ? 1 : 0 ) ;
lua_pushboolean ( L , 0 ) ; // notEnoughMana (can't determine without cost data)
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 < int > ( luaL_checknumber ( L , 1 ) ) - 1 ;
const auto & bar = gh - > getActionBar ( ) ;
if ( slot < 0 | | slot > = static_cast < int > ( 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 ;
}
2026-03-21 02:03:51 -07:00
// 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 < int > ( luaL_checknumber ( L , 1 ) ) - 1 ;
const auto & bar = gh - > getActionBar ( ) ;
if ( slot < 0 | | slot > = static_cast < int > ( 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 < char > ( std : : tolower ( static_cast < unsigned char > ( c ) ) ) ;
if ( uidStr ! = " player " ) return 0 ; // Can only cancel own buffs
int index = static_cast < int > ( 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 < uint32_t > ( luaL_checknumber ( L , 1 ) ) ;
if ( spellId = = 0 ) return 0 ;
uint64_t target = gh - > hasTarget ( ) ? gh - > getTargetGuid ( ) : 0 ;
gh - > castSpell ( spellId , target ) ;
return 0 ;
}
feat: add CreateFrame with RegisterEvent/SetScript for WoW addon pattern
Implement the core WoW frame system that nearly all addons use:
- CreateFrame(type, name, parent, template) — creates a frame table
with metatable methods, optionally registered as a global by name
- frame:RegisterEvent(event) — register frame for event dispatch
- frame:UnregisterEvent(event) — unregister
- frame:SetScript(type, handler) — set OnEvent/OnUpdate/etc handlers
- frame:GetScript(type) — retrieve handlers
- frame:Show()/Hide()/IsShown()/IsVisible() — visibility state
- frame:GetName() — return frame name
Event dispatch now fires both global RegisterEvent handlers AND
frame OnEvent scripts, matching WoW's dual dispatch model.
Updated HelloWorld to use standard WoW addon pattern:
local f = CreateFrame("Frame", "MyFrame")
f:RegisterEvent("PLAYER_ENTERING_WORLD")
f:SetScript("OnEvent", function(self, event, ...) end)
2026-03-20 11:46:04 -07:00
// --- 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 < int > ( 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 ) ;
2026-03-20 12:07:22 -07:00
// 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 < int > ( lua_objlen ( L , - 1 ) ) ;
lua_pushvalue ( L , 1 ) ;
lua_rawseti ( L , - 2 , len + 1 ) ;
}
lua_pop ( L , 1 ) ;
}
feat: add CreateFrame with RegisterEvent/SetScript for WoW addon pattern
Implement the core WoW frame system that nearly all addons use:
- CreateFrame(type, name, parent, template) — creates a frame table
with metatable methods, optionally registered as a global by name
- frame:RegisterEvent(event) — register frame for event dispatch
- frame:UnregisterEvent(event) — unregister
- frame:SetScript(type, handler) — set OnEvent/OnUpdate/etc handlers
- frame:GetScript(type) — retrieve handlers
- frame:Show()/Hide()/IsShown()/IsVisible() — visibility state
- frame:GetName() — return frame name
Event dispatch now fires both global RegisterEvent handlers AND
frame OnEvent scripts, matching WoW's dual dispatch model.
Updated HelloWorld to use standard WoW addon pattern:
local f = CreateFrame("Frame", "MyFrame")
f:RegisterEvent("PLAYER_ENTERING_WORLD")
f:SetScript("OnEvent", function(self, event, ...) end)
2026-03-20 11:46:04 -07:00
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 ;
}
2026-03-21 01:52:59 -07:00
// 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 < double > ( ImGui : : GetIO ( ) . Framerate ) ) ;
return 1 ;
}
feat: add GetCursorPosition, screen size queries, and frame positioning methods
Add global Lua API functions:
- GetCursorPosition() returns mouse x,y screen coordinates
- GetScreenWidth()/GetScreenHeight() return window dimensions
Add frame methods for UI layout:
- SetPoint, SetSize, SetWidth, SetHeight, GetWidth, GetHeight, GetCenter
- SetAlpha, GetAlpha, SetParent, GetParent
These enable UI customization addons to query cursor position, screen
dimensions, and manage frame layout — fundamental for unit frames,
action bars, and tooltip addons.
2026-03-21 01:44:59 -07:00
// 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 ;
}
feat: add CreateFrame with RegisterEvent/SetScript for WoW addon pattern
Implement the core WoW frame system that nearly all addons use:
- CreateFrame(type, name, parent, template) — creates a frame table
with metatable methods, optionally registered as a global by name
- frame:RegisterEvent(event) — register frame for event dispatch
- frame:UnregisterEvent(event) — unregister
- frame:SetScript(type, handler) — set OnEvent/OnUpdate/etc handlers
- frame:GetScript(type) — retrieve handlers
- frame:Show()/Hide()/IsShown()/IsVisible() — visibility state
- frame:GetName() — return frame name
Event dispatch now fires both global RegisterEvent handlers AND
frame OnEvent scripts, matching WoW's dual dispatch model.
Updated HelloWorld to use standard WoW addon pattern:
local f = CreateFrame("Frame", "MyFrame")
f:RegisterEvent("PLAYER_ENTERING_WORLD")
f:SetScript("OnEvent", function(self, event, ...) end)
2026-03-20 11:46:04 -07:00
// 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 ;
}
2026-03-20 11:40:58 -07:00
// --- 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 < 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 ;
}
2026-03-20 11:12:07 -07:00
// 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 < double > ( 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 ( ) ;
2026-03-20 11:23:38 -07:00
registerEventAPI ( ) ;
2026-03-20 11:12:07 -07:00
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 " ) ;
feat: add core WoW Lua API functions for addon development
Add 13 WoW-compatible Lua API functions that addons can call:
Unit API: UnitName, UnitHealth, UnitHealthMax, UnitPower, UnitPowerMax,
UnitLevel, UnitExists, UnitIsDead, UnitClass (supports "player",
"target", "focus", "pet" unit IDs)
Game API: GetMoney, IsInGroup, IsInRaid, GetPlayerMapPosition
Updated HelloWorld addon to demonstrate querying player state.
2026-03-20 11:17:15 -07:00
// 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 } ,
feat: add action bar, combo points, reaction, and connection Lua API functions
Implement 10 new WoW Lua API functions for addon compatibility:
- GetComboPoints, UnitReaction, UnitIsConnected for unit frames/raid addons
- HasAction, GetActionTexture, IsCurrentAction, IsUsableAction, GetActionCooldown
for action bar addons (Bartender, Dominos, etc.)
- UnitMana/UnitManaMax as Classic-era aliases for UnitPower/UnitPowerMax
2026-03-21 01:31:34 -07:00
{ " UnitMana " , lua_UnitPower } ,
{ " UnitManaMax " , lua_UnitPowerMax } ,
feat: add core WoW Lua API functions for addon development
Add 13 WoW-compatible Lua API functions that addons can call:
Unit API: UnitName, UnitHealth, UnitHealthMax, UnitPower, UnitPowerMax,
UnitLevel, UnitExists, UnitIsDead, UnitClass (supports "player",
"target", "focus", "pet" unit IDs)
Game API: GetMoney, IsInGroup, IsInRaid, GetPlayerMapPosition
Updated HelloWorld addon to demonstrate querying player state.
2026-03-20 11:17:15 -07:00
{ " UnitLevel " , lua_UnitLevel } ,
{ " UnitExists " , lua_UnitExists } ,
{ " UnitIsDead " , lua_UnitIsDead } ,
{ " UnitClass " , lua_UnitClass } ,
{ " GetMoney " , lua_GetMoney } ,
{ " IsInGroup " , lua_IsInGroup } ,
{ " IsInRaid " , lua_IsInRaid } ,
{ " GetPlayerMapPosition " , lua_GetPlayerMapPosition } ,
feat: add action WoW API functions for Lua addons
Add 5 more essential WoW API functions for addon development:
- SendChatMessage(msg, type, lang, target) — send chat messages
(SAY, YELL, WHISPER, PARTY, GUILD, OFFICER, RAID, BG)
- CastSpellByName(name) — cast highest rank of named spell
- IsSpellKnown(spellId) — check if player knows a spell
- GetSpellCooldown(nameOrId) — get remaining cooldown
- HasTarget() — check if player has a target
Total WoW API surface: 18 functions across Unit, Game, and Action
categories. Addons can now query state, react to events, send
messages, and cast spells.
2026-03-20 11:34:04 -07:00
{ " SendChatMessage " , lua_SendChatMessage } ,
feat: add SendAddonMessage and RegisterAddonMessagePrefix for addon comms
Implement the addon messaging API used by virtually every multiplayer
addon (DBM, BigWigs, EPGP, RC Loot Council, WeakAuras, etc.):
- SendAddonMessage(prefix, text, chatType, target) sends an addon
message encoded as "prefix\ttext" via the appropriate chat channel
- RegisterAddonMessagePrefix(prefix) registers a prefix for filtering
incoming addon messages
- IsAddonMessagePrefixRegistered(prefix) checks registration status
- C_ChatInfo table with aliases for the above functions (newer API compat)
Without these functions, all inter-addon communication between players
fails, breaking boss mods, loot distribution, and group coordination.
2026-03-21 03:31:54 -07:00
{ " SendAddonMessage " , lua_SendAddonMessage } ,
{ " RegisterAddonMessagePrefix " , lua_RegisterAddonMessagePrefix } ,
{ " IsAddonMessagePrefixRegistered " , lua_IsAddonMessagePrefixRegistered } ,
feat: add action WoW API functions for Lua addons
Add 5 more essential WoW API functions for addon development:
- SendChatMessage(msg, type, lang, target) — send chat messages
(SAY, YELL, WHISPER, PARTY, GUILD, OFFICER, RAID, BG)
- CastSpellByName(name) — cast highest rank of named spell
- IsSpellKnown(spellId) — check if player knows a spell
- GetSpellCooldown(nameOrId) — get remaining cooldown
- HasTarget() — check if player has a target
Total WoW API surface: 18 functions across Unit, Game, and Action
categories. Addons can now query state, react to events, send
messages, and cast spells.
2026-03-20 11:34:04 -07:00
{ " CastSpellByName " , lua_CastSpellByName } ,
{ " IsSpellKnown " , lua_IsSpellKnown } ,
{ " GetSpellCooldown " , lua_GetSpellCooldown } ,
{ " HasTarget " , lua_HasTarget } ,
2026-03-21 01:58:03 -07:00
{ " TargetUnit " , lua_TargetUnit } ,
{ " ClearTarget " , lua_ClearTarget } ,
{ " FocusUnit " , lua_FocusUnit } ,
{ " ClearFocus " , lua_ClearFocus } ,
{ " AssistUnit " , lua_AssistUnit } ,
{ " TargetLastTarget " , lua_TargetLastTarget } ,
{ " TargetNearestEnemy " , lua_TargetNearestEnemy } ,
{ " TargetNearestFriend " , lua_TargetNearestFriend } ,
2026-03-20 12:15:36 -07:00
{ " UnitRace " , lua_UnitRace } ,
{ " UnitPowerType " , lua_UnitPowerType } ,
{ " GetNumGroupMembers " , lua_GetNumGroupMembers } ,
{ " UnitGUID " , lua_UnitGUID } ,
{ " UnitIsPlayer " , lua_UnitIsPlayer } ,
{ " InCombatLockdown " , lua_InCombatLockdown } ,
feat: add UnitBuff and UnitDebuff API for Lua addons
Implement the most commonly used buff/debuff query functions:
- UnitBuff(unitId, index) — query the Nth buff on a unit
- UnitDebuff(unitId, index) — query the Nth debuff on a unit
Returns WoW-compatible 11-value tuple: name, rank, icon, count,
debuffType, duration, expirationTime, caster, isStealable,
shouldConsolidate, spellId.
Supports "player" and "target" unit IDs. Essential for buff tracking
addons (WeakAuras-style), healer addons, and combat analysis tools.
Total WoW API: 31 functions.
2026-03-20 12:45:43 -07:00
{ " UnitBuff " , lua_UnitBuff } ,
{ " UnitDebuff " , lua_UnitDebuff } ,
2026-03-20 16:42:06 -07:00
{ " UnitAura " , lua_UnitAuraGeneric } ,
2026-03-20 13:07:45 -07:00
{ " GetNumAddOns " , lua_GetNumAddOns } ,
{ " GetAddOnInfo " , lua_GetAddOnInfo } ,
2026-03-20 13:58:54 -07:00
{ " GetSpellInfo " , lua_GetSpellInfo } ,
{ " GetSpellTexture " , lua_GetSpellTexture } ,
{ " GetItemInfo " , lua_GetItemInfo } ,
{ " GetLocale " , lua_GetLocale } ,
{ " GetBuildInfo " , lua_GetBuildInfo } ,
{ " GetCurrentMapAreaID " , lua_GetCurrentMapAreaID } ,
2026-03-20 15:44:25 -07:00
{ " GetZoneText " , lua_GetZoneText } ,
{ " GetRealZoneText " , lua_GetZoneText } ,
{ " GetSubZoneText " , lua_GetSubZoneText } ,
{ " GetMinimapZoneText " , lua_GetMinimapZoneText } ,
2026-03-20 14:57:13 -07:00
// 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 } ,
feat: add 9 more WoW Lua API functions for group and unit queries
Add UnitAffectingCombat, GetNumRaidMembers, GetNumPartyMembers, UnitInParty,
UnitInRaid, UnitIsUnit, UnitIsFriend, UnitIsEnemy, and UnitCreatureType.
These are commonly used by raid/group addons for party composition checks,
combat state queries, and mob type identification. Total API count now 55.
2026-03-20 15:21:38 -07:00
// 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 } ,
2026-03-20 18:25:39 -07:00
{ " UnitClassification " , lua_UnitClassification } ,
2026-03-20 20:22:15 -07:00
{ " GetPlayerInfoByGUID " , lua_GetPlayerInfoByGUID } ,
2026-03-20 20:07:45 -07:00
{ " GetItemLink " , lua_GetItemLink } ,
2026-03-20 19:57:13 -07:00
{ " GetSpellLink " , lua_GetSpellLink } ,
2026-03-20 18:42:33 -07:00
{ " IsUsableSpell " , lua_IsUsableSpell } ,
2026-03-20 18:33:44 -07:00
{ " IsInInstance " , lua_IsInInstance } ,
{ " GetInstanceInfo " , lua_GetInstanceInfo } ,
{ " GetInstanceDifficulty " , lua_GetInstanceDifficulty } ,
feat: add container/bag Lua API for bag addon support
Add GetContainerNumSlots(bag), GetContainerItemInfo(bag, slot),
GetContainerItemLink(bag, slot), and GetContainerNumFreeSlots(bag).
Container 0 = backpack (16 slots), containers 1-4 = equipped bags.
Returns item count, quality, and WoW-format item links with quality
colors. Enables bag management addons (Bagnon, OneBag, AdiBags).
2026-03-20 17:14:07 -07:00
// Container/bag API
{ " GetContainerNumSlots " , lua_GetContainerNumSlots } ,
{ " GetContainerItemInfo " , lua_GetContainerItemInfo } ,
{ " GetContainerItemLink " , lua_GetContainerItemLink } ,
{ " GetContainerNumFreeSlots " , lua_GetContainerNumFreeSlots } ,
2026-03-20 17:56:20 -07:00
// Equipment slot API
{ " GetInventoryItemLink " , lua_GetInventoryItemLink } ,
{ " GetInventoryItemID " , lua_GetInventoryItemID } ,
{ " GetInventoryItemTexture " , lua_GetInventoryItemTexture } ,
2026-03-20 18:16:12 -07:00
// Time/XP API
{ " GetGameTime " , lua_GetGameTime } ,
{ " GetServerTime " , lua_GetServerTime } ,
{ " UnitXP " , lua_UnitXP } ,
{ " UnitXPMax " , lua_UnitXPMax } ,
2026-03-20 18:53:56 -07:00
{ " GetXPExhaustion " , lua_GetXPExhaustion } ,
{ " GetRestState " , lua_GetRestState } ,
2026-03-20 17:37:35 -07:00
// Quest log API
{ " GetNumQuestLogEntries " , lua_GetNumQuestLogEntries } ,
{ " GetQuestLogTitle " , lua_GetQuestLogTitle } ,
{ " GetQuestLogQuestText " , lua_GetQuestLogQuestText } ,
{ " IsQuestComplete " , lua_IsQuestComplete } ,
2026-03-21 02:18:25 -07:00
// Skill line API
{ " GetNumSkillLines " , lua_GetNumSkillLines } ,
{ " GetSkillLineInfo " , lua_GetSkillLineInfo } ,
feat: add talent tree Lua API for talent inspection addons
Implement 5 talent-related WoW Lua API functions:
- GetNumTalentTabs() returns class-specific talent tree count (usually 3)
- GetTalentTabInfo(tab) returns name, icon, pointsSpent, background
- GetNumTalents(tab) returns talent count in a specific tree
- GetTalentInfo(tab, index) returns full 8-value tuple with name, tier,
column, current rank, max rank, and availability
- GetActiveTalentGroup() returns active spec (1 or 2)
Data sourced from Talent.dbc, TalentTab.dbc, and the server-sent talent
info packet. Enables talent addons and spec display addons.
2026-03-21 02:22:35 -07:00
// Talent API
{ " GetNumTalentTabs " , lua_GetNumTalentTabs } ,
{ " GetTalentTabInfo " , lua_GetTalentTabInfo } ,
{ " GetNumTalents " , lua_GetNumTalents } ,
{ " GetTalentInfo " , lua_GetTalentInfo } ,
{ " GetActiveTalentGroup " , lua_GetActiveTalentGroup } ,
2026-03-21 03:08:37 -07:00
// Friends/ignore API
{ " GetNumFriends " , lua_GetNumFriends } ,
{ " GetFriendInfo " , lua_GetFriendInfo } ,
{ " GetNumIgnores " , lua_GetNumIgnores } ,
{ " GetIgnoreName " , lua_GetIgnoreName } ,
feat: add action bar, combo points, reaction, and connection Lua API functions
Implement 10 new WoW Lua API functions for addon compatibility:
- GetComboPoints, UnitReaction, UnitIsConnected for unit frames/raid addons
- HasAction, GetActionTexture, IsCurrentAction, IsUsableAction, GetActionCooldown
for action bar addons (Bartender, Dominos, etc.)
- UnitMana/UnitManaMax as Classic-era aliases for UnitPower/UnitPowerMax
2026-03-21 01:31:34 -07:00
// 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 } ,
2026-03-21 02:03:51 -07:00
{ " UseAction " , lua_UseAction } ,
{ " CancelUnitBuff " , lua_CancelUnitBuff } ,
{ " CastSpellByID " , lua_CastSpellByID } ,
2026-03-21 01:42:03 -07:00
// Loot API
{ " GetNumLootItems " , lua_GetNumLootItems } ,
{ " GetLootSlotInfo " , lua_GetLootSlotInfo } ,
{ " GetLootSlotLink " , lua_GetLootSlotLink } ,
{ " LootSlot " , lua_LootSlot } ,
{ " CloseLoot " , lua_CloseLoot } ,
{ " GetLootMethod " , lua_GetLootMethod } ,
2026-03-20 11:40:58 -07:00
// Utilities
{ " strsplit " , lua_strsplit } ,
{ " strtrim " , lua_strtrim } ,
{ " wipe " , lua_wipe } ,
{ " date " , lua_wow_date } ,
{ " time " , lua_wow_time } ,
feat: add core WoW Lua API functions for addon development
Add 13 WoW-compatible Lua API functions that addons can call:
Unit API: UnitName, UnitHealth, UnitHealthMax, UnitPower, UnitPowerMax,
UnitLevel, UnitExists, UnitIsDead, UnitClass (supports "player",
"target", "focus", "pet" unit IDs)
Game API: GetMoney, IsInGroup, IsInRaid, GetPlayerMapPosition
Updated HelloWorld addon to demonstrate querying player state.
2026-03-20 11:17:15 -07:00
} ;
for ( const auto & [ name , func ] : unitAPI ) {
lua_pushcfunction ( L_ , func ) ;
lua_setglobal ( L_ , name ) ;
}
2026-03-20 11:40:58 -07:00
// 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 " ) ;
feat: add CreateFrame with RegisterEvent/SetScript for WoW addon pattern
Implement the core WoW frame system that nearly all addons use:
- CreateFrame(type, name, parent, template) — creates a frame table
with metatable methods, optionally registered as a global by name
- frame:RegisterEvent(event) — register frame for event dispatch
- frame:UnregisterEvent(event) — unregister
- frame:SetScript(type, handler) — set OnEvent/OnUpdate/etc handlers
- frame:GetScript(type) — retrieve handlers
- frame:Show()/Hide()/IsShown()/IsVisible() — visibility state
- frame:GetName() — return frame name
Event dispatch now fires both global RegisterEvent handlers AND
frame OnEvent scripts, matching WoW's dual dispatch model.
Updated HelloWorld to use standard WoW addon pattern:
local f = CreateFrame("Frame", "MyFrame")
f:RegisterEvent("PLAYER_ENTERING_WORLD")
f:SetScript("OnEvent", function(self, event, ...) end)
2026-03-20 11:46:04 -07:00
// 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
feat: add GetCursorPosition, screen size queries, and frame positioning methods
Add global Lua API functions:
- GetCursorPosition() returns mouse x,y screen coordinates
- GetScreenWidth()/GetScreenHeight() return window dimensions
Add frame methods for UI layout:
- SetPoint, SetSize, SetWidth, SetHeight, GetWidth, GetHeight, GetCenter
- SetAlpha, GetAlpha, SetParent, GetParent
These enable UI customization addons to query cursor position, screen
dimensions, and manage frame layout — fundamental for unit frames,
action bars, and tooltip addons.
2026-03-21 01:44:59 -07:00
{ " 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 } ,
2026-03-21 01:52:59 -07:00
{ " CreateTexture " , lua_Frame_CreateTexture } ,
{ " CreateFontString " , lua_Frame_CreateFontString } ,
feat: add CreateFrame with RegisterEvent/SetScript for WoW addon pattern
Implement the core WoW frame system that nearly all addons use:
- CreateFrame(type, name, parent, template) — creates a frame table
with metatable methods, optionally registered as a global by name
- frame:RegisterEvent(event) — register frame for event dispatch
- frame:UnregisterEvent(event) — unregister
- frame:SetScript(type, handler) — set OnEvent/OnUpdate/etc handlers
- frame:GetScript(type) — retrieve handlers
- frame:Show()/Hide()/IsShown()/IsVisible() — visibility state
- frame:GetName() — return frame name
Event dispatch now fires both global RegisterEvent handlers AND
frame OnEvent scripts, matching WoW's dual dispatch model.
Updated HelloWorld to use standard WoW addon pattern:
local f = CreateFrame("Frame", "MyFrame")
f:RegisterEvent("PLAYER_ENTERING_WORLD")
f:SetScript("OnEvent", function(self, event, ...) end)
2026-03-20 11:46:04 -07:00
{ 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 " ) ;
feat: add 40+ frame metatable methods to prevent addon nil-reference errors
Add commonly called frame methods as no-ops or with basic state tracking
on the frame metatable, so any CreateFrame result supports them:
Layout: SetFrameLevel/Get, SetFrameStrata/Get, SetScale/Get/GetEffective,
ClearAllPoints, SetID/GetID, GetLeft/Right/Top/Bottom, GetNumPoints,
GetPoint, SetHitRectInsets
Behavior: EnableMouse, EnableMouseWheel, SetMovable, SetResizable,
RegisterForDrag, SetClampedToScreen, SetToplevel, Raise, Lower,
StartMoving, StopMovingOrSizing, RegisterForClicks, IsMouseOver
Visual: SetBackdrop, SetBackdropColor, SetBackdropBorderColor
Scripting: HookScript (chains with existing SetScript handlers),
SetAttribute/GetAttribute, GetObjectType
Sizing: SetMinResize, SetMaxResize
These prevent the most common addon errors when addons call standard
WoW frame methods on CreateFrame results.
2026-03-21 02:39:44 -07:00
// 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 "
) ;
feat: add CreateFrame with RegisterEvent/SetScript for WoW addon pattern
Implement the core WoW frame system that nearly all addons use:
- CreateFrame(type, name, parent, template) — creates a frame table
with metatable methods, optionally registered as a global by name
- frame:RegisterEvent(event) — register frame for event dispatch
- frame:UnregisterEvent(event) — unregister
- frame:SetScript(type, handler) — set OnEvent/OnUpdate/etc handlers
- frame:GetScript(type) — retrieve handlers
- frame:Show()/Hide()/IsShown()/IsVisible() — visibility state
- frame:GetName() — return frame name
Event dispatch now fires both global RegisterEvent handlers AND
frame OnEvent scripts, matching WoW's dual dispatch model.
Updated HelloWorld to use standard WoW addon pattern:
local f = CreateFrame("Frame", "MyFrame")
f:RegisterEvent("PLAYER_ENTERING_WORLD")
f:SetScript("OnEvent", function(self, event, ...) end)
2026-03-20 11:46:04 -07:00
// CreateFrame function
lua_pushcfunction ( L_ , lua_CreateFrame ) ;
lua_setglobal ( L_ , " CreateFrame " ) ;
2026-03-21 01:52:59 -07:00
// Cursor/screen/FPS functions
feat: add GetCursorPosition, screen size queries, and frame positioning methods
Add global Lua API functions:
- GetCursorPosition() returns mouse x,y screen coordinates
- GetScreenWidth()/GetScreenHeight() return window dimensions
Add frame methods for UI layout:
- SetPoint, SetSize, SetWidth, SetHeight, GetWidth, GetHeight, GetCenter
- SetAlpha, GetAlpha, SetParent, GetParent
These enable UI customization addons to query cursor position, screen
dimensions, and manage frame layout — fundamental for unit frames,
action bars, and tooltip addons.
2026-03-21 01:44:59 -07:00
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 " ) ;
2026-03-21 01:52:59 -07:00
lua_pushcfunction ( L_ , lua_GetFramerate ) ;
lua_setglobal ( L_ , " GetFramerate " ) ;
feat: add GetCursorPosition, screen size queries, and frame positioning methods
Add global Lua API functions:
- GetCursorPosition() returns mouse x,y screen coordinates
- GetScreenWidth()/GetScreenHeight() return window dimensions
Add frame methods for UI layout:
- SetPoint, SetSize, SetWidth, SetHeight, GetWidth, GetHeight, GetCenter
- SetAlpha, GetAlpha, SetParent, GetParent
These enable UI customization addons to query cursor position, screen
dimensions, and manage frame layout — fundamental for unit frames,
action bars, and tooltip addons.
2026-03-21 01:44:59 -07:00
feat: add CreateFrame with RegisterEvent/SetScript for WoW addon pattern
Implement the core WoW frame system that nearly all addons use:
- CreateFrame(type, name, parent, template) — creates a frame table
with metatable methods, optionally registered as a global by name
- frame:RegisterEvent(event) — register frame for event dispatch
- frame:UnregisterEvent(event) — unregister
- frame:SetScript(type, handler) — set OnEvent/OnUpdate/etc handlers
- frame:GetScript(type) — retrieve handlers
- frame:Show()/Hide()/IsShown()/IsVisible() — visibility state
- frame:GetName() — return frame name
Event dispatch now fires both global RegisterEvent handlers AND
frame OnEvent scripts, matching WoW's dual dispatch model.
Updated HelloWorld to use standard WoW addon pattern:
local f = CreateFrame("Frame", "MyFrame")
f:RegisterEvent("PLAYER_ENTERING_WORLD")
f:SetScript("OnEvent", function(self, event, ...) end)
2026-03-20 11:46:04 -07:00
// Frame event dispatch table
lua_newtable ( L_ ) ;
lua_setglobal ( L_ , " __WoweeFrameEvents " ) ;
2026-03-20 12:07:22 -07:00
// OnUpdate frame tracking table
lua_newtable ( L_ ) ;
lua_setglobal ( L_ , " __WoweeOnUpdateFrames " ) ;
2026-03-20 12:11:24 -07:00
// 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 "
) ;
2026-03-20 13:18:16 -07:00
// 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 "
) ;
2026-03-20 13:27:27 -07:00
// 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 "
) ;
2026-03-21 02:15:50 -07:00
// 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 "
) ;
2026-03-20 13:27:27 -07:00
// 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 "
feat: add WoW compatibility stubs for broader addon support
Add error handling (geterrorhandler, seterrorhandler, debugstack, securecall,
issecurevariable), CVar system (GetCVar, GetCVarBool, SetCVar), screen/state
queries (GetScreenWidth/Height, GetFramerate, GetNetStats, IsLoggedIn,
IsMounted, IsFlying, etc.), UI stubs (StaticPopup_Show/Hide, StopSound),
and RAID_CLASS_COLORS table. Prevents common addon load errors.
2026-03-20 14:38:50 -07:00
" function StopSound() end \n "
2026-03-20 13:27:27 -07:00
" function UIParent_OnEvent() end \n "
" UIParent = CreateFrame('Frame', 'UIParent') \n "
" WorldFrame = CreateFrame('Frame', 'WorldFrame') \n "
2026-03-21 01:52:59 -07:00
// 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 "
feat: add WoW compatibility stubs for broader addon support
Add error handling (geterrorhandler, seterrorhandler, debugstack, securecall,
issecurevariable), CVar system (GetCVar, GetCVarBool, SetCVar), screen/state
queries (GetScreenWidth/Height, GetFramerate, GetNetStats, IsLoggedIn,
IsMounted, IsFlying, etc.), UI stubs (StaticPopup_Show/Hide, StopSound),
and RAID_CLASS_COLORS table. Prevents common addon load errors.
2026-03-20 14:38:50 -07:00
// 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
2026-03-21 01:52:59 -07:00
// GetScreenWidth, GetScreenHeight, GetNumLootItems are now C functions
// GetFramerate is now a C function
feat: add WoW compatibility stubs for broader addon support
Add error handling (geterrorhandler, seterrorhandler, debugstack, securecall,
issecurevariable), CVar system (GetCVar, GetCVarBool, SetCVar), screen/state
queries (GetScreenWidth/Height, GetFramerate, GetNetStats, IsLoggedIn,
IsMounted, IsFlying, etc.), UI stubs (StaticPopup_Show/Hide, StopSound),
and RAID_CLASS_COLORS table. Prevents common addon load errors.
2026-03-20 14:38:50 -07:00
" 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 "
2026-03-21 01:52:59 -07:00
// CreateTexture/CreateFontString are now C frame methods in the metatable
feat: add WoW compatibility stubs for broader addon support
Add error handling (geterrorhandler, seterrorhandler, debugstack, securecall,
issecurevariable), CVar system (GetCVar, GetCVarBool, SetCVar), screen/state
queries (GetScreenWidth/Height, GetFramerate, GetNetStats, IsLoggedIn,
IsMounted, IsFlying, etc.), UI stubs (StaticPopup_Show/Hide, StopSound),
and RAID_CLASS_COLORS table. Prevents common addon load errors.
2026-03-20 14:38:50 -07:00
" RAID_CLASS_COLORS = { \n "
" WARRIOR={r=0.78,g=0.61,b=0.43}, PALADIN={r=0.96,g=0.55,b=0.73}, \n "
" HUNTER={r=0.67,g=0.83,b=0.45}, ROGUE={r=1.0,g=0.96,b=0.41}, \n "
" PRIEST={r=1.0,g=1.0,b=1.0}, DEATHKNIGHT={r=0.77,g=0.12,b=0.23}, \n "
" SHAMAN={r=0.0,g=0.44,b=0.87}, MAGE={r=0.41,g=0.80,b=0.94}, \n "
" WARLOCK={r=0.58,g=0.51,b=0.79}, DRUID={r=1.0,g=0.49,b=0.04}, \n "
" } \n "
2026-03-20 20:44:59 -07:00
// 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 "
2026-03-20 13:27:27 -07:00
) ;
feat: add UIDropDownMenu framework, font objects, and UI global stubs
Add the UIDropDownMenu compatibility framework used by virtually every
addon with settings or selection menus: UIDropDownMenu_Initialize,
CreateInfo, AddButton, SetWidth, SetText, GetText, SetSelectedID, etc.
Add global font object stubs (GameFontNormal, GameFontHighlight, etc.)
referenced by CreateFontString template arguments.
Add UISpecialFrames table, InterfaceOptionsFrame for addon panels,
InterfaceOptions_AddCategory, and common font color constants
(GRAY_FONT_COLOR, NORMAL_FONT_COLOR, etc.).
These globals prevent nil-reference errors in most popular addons.
2026-03-21 02:36:06 -07:00
// 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 "
feat: add SendAddonMessage and RegisterAddonMessagePrefix for addon comms
Implement the addon messaging API used by virtually every multiplayer
addon (DBM, BigWigs, EPGP, RC Loot Council, WeakAuras, etc.):
- SendAddonMessage(prefix, text, chatType, target) sends an addon
message encoded as "prefix\ttext" via the appropriate chat channel
- RegisterAddonMessagePrefix(prefix) registers a prefix for filtering
incoming addon messages
- IsAddonMessagePrefixRegistered(prefix) checks registration status
- C_ChatInfo table with aliases for the above functions (newer API compat)
Without these functions, all inter-addon communication between players
fails, breaking boss mods, loot distribution, and group coordination.
2026-03-21 03:31:54 -07:00
// 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 "
feat: add UIDropDownMenu framework, font objects, and UI global stubs
Add the UIDropDownMenu compatibility framework used by virtually every
addon with settings or selection menus: UIDropDownMenu_Initialize,
CreateInfo, AddButton, SetWidth, SetText, GetText, SetSelectedID, etc.
Add global font object stubs (GameFontNormal, GameFontHighlight, etc.)
referenced by CreateFontString template arguments.
Add UISpecialFrames table, InterfaceOptionsFrame for addon panels,
InterfaceOptions_AddCategory, and common font color constants
(GRAY_FONT_COLOR, NORMAL_FONT_COLOR, etc.).
These globals prevent nil-reference errors in most popular addons.
2026-03-21 02:36:06 -07:00
) ;
feat: add WoW table/string/math/bit utility functions for addon compat
Add commonly used WoW global utility functions that many addons depend on:
Table: tContains, tInvert, CopyTable, tDeleteItem
String: strupper, strlower, strfind, strsub, strlen, strrep, strbyte,
strchar, strrev, gsub, gmatch, strjoin
Math: Clamp, Round
Bit ops: bit.band, bit.bor, bit.bxor, bit.bnot, bit.lshift, bit.rshift
(pure Lua implementation for Lua 5.1 which lacks native bit ops)
These prevent nil-reference errors and missing-function crashes in
addons that use standard WoW utility globals.
2026-03-21 02:37:56 -07:00
// 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 "
) ;
2026-03-20 11:12:07 -07:00
}
2026-03-20 11:23:38 -07:00
// ---- 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 < int > ( 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 < int > ( 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 < std : : string > & 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 < int > ( 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 < int > ( args . size ( ) ) ;
if ( lua_pcall ( L_ , nargs , 0 , 0 ) ! = 0 ) {
const char * err = lua_tostring ( L_ , - 1 ) ;
LOG_ERROR ( " LuaEngine: event ' " , eventName , " ' handler error: " ,
err ? err : " (unknown) " ) ;
lua_pop ( L_ , 1 ) ;
}
}
lua_pop ( L_ , 2 ) ; // pop handler list + WoweeEvents
feat: add CreateFrame with RegisterEvent/SetScript for WoW addon pattern
Implement the core WoW frame system that nearly all addons use:
- CreateFrame(type, name, parent, template) — creates a frame table
with metatable methods, optionally registered as a global by name
- frame:RegisterEvent(event) — register frame for event dispatch
- frame:UnregisterEvent(event) — unregister
- frame:SetScript(type, handler) — set OnEvent/OnUpdate/etc handlers
- frame:GetScript(type) — retrieve handlers
- frame:Show()/Hide()/IsShown()/IsVisible() — visibility state
- frame:GetName() — return frame name
Event dispatch now fires both global RegisterEvent handlers AND
frame OnEvent scripts, matching WoW's dual dispatch model.
Updated HelloWorld to use standard WoW addon pattern:
local f = CreateFrame("Frame", "MyFrame")
f:RegisterEvent("PLAYER_ENTERING_WORLD")
f:SetScript("OnEvent", function(self, event, ...) end)
2026-03-20 11:46:04 -07:00
// 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 < int > ( 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 < int > ( args . size ( ) ) ;
if ( lua_pcall ( L_ , nargs , 0 , 0 ) ! = 0 ) {
LOG_ERROR ( " LuaEngine: frame OnEvent error: " , lua_tostring ( L_ , - 1 ) ) ;
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
2026-03-20 11:23:38 -07:00
}
2026-03-20 12:07:22 -07:00
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 < int > ( 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 < double > ( elapsed ) ) ;
if ( lua_pcall ( L_ , 2 , 0 , 0 ) ! = 0 ) {
LOG_ERROR ( " LuaEngine: OnUpdate error: " , lua_tostring ( L_ , - 1 ) ) ;
lua_pop ( L_ , 1 ) ;
}
} else {
lua_pop ( L_ , 1 ) ;
}
}
lua_pop ( L_ , 2 ) ; // pop __scripts + frame
}
lua_pop ( L_ , 1 ) ; // pop __WoweeOnUpdateFrames
}
2026-03-20 11:40:58 -07:00
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 < char > ( std : : tolower ( static_cast < unsigned char > ( 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_<NAME>1 through SLASH_<NAME>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 < char > ( std : : tolower ( static_cast < unsigned char > ( 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 ;
}
2026-03-20 12:22:50 -07:00
// ---- 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 < long long > ( 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 ;
}
}
2026-03-20 13:07:45 -07:00
void LuaEngine : : setAddonList ( const std : : vector < TocFile > & addons ) {
if ( ! L_ ) return ;
lua_pushnumber ( L_ , static_cast < double > ( 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 " ) ;
lua_rawseti ( L_ , - 2 , static_cast < int > ( i + 1 ) ) ;
}
lua_setfield ( L_ , LUA_REGISTRYINDEX , " wowee_addon_info " ) ;
}
2026-03-20 12:22:50 -07:00
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 < char > ( f ) ) , std : : istreambuf_iterator < char > ( ) ) ;
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 < std : : string > & 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 ;
}
2026-03-20 11:12:07 -07:00
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 ( 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 ( 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