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

892 lines
39 KiB
C++

// lua_inventory_api.cpp — Items, containers, merchant, loot, equipment, trading, auction, and mail Lua API bindings.
// Extracted from lua_engine.cpp as part of §5.1 (Tame LuaEngine).
#include "addons/lua_api_helpers.hpp"
namespace wowee::addons {
static int lua_GetMoney(lua_State* L) {
auto* gh = getGameHandler(L);
lua_pushnumber(L, gh ? static_cast<double>(gh->getMoneyCopper()) : 0.0);
return 1;
}
// --- Merchant/Vendor API ---
static int lua_GetMerchantNumItems(lua_State* L) {
auto* gh = getGameHandler(L);
if (!gh) { return luaReturnZero(L); }
lua_pushnumber(L, gh->getVendorItems().items.size());
return 1;
}
// GetMerchantItemInfo(index) → name, texture, price, stackCount, numAvailable, isUsable
static int lua_GetMerchantItemInfo(lua_State* L) {
auto* gh = getGameHandler(L);
int index = static_cast<int>(luaL_checknumber(L, 1));
if (!gh || index < 1) { return luaReturnNil(L); }
const auto& items = gh->getVendorItems().items;
if (index > static_cast<int>(items.size())) { return luaReturnNil(L); }
const auto& vi = items[index - 1];
const auto* info = gh->getItemInfo(vi.itemId);
std::string name = info ? info->name : ("Item #" + std::to_string(vi.itemId));
lua_pushstring(L, name.c_str()); // name
// texture
std::string iconPath;
if (info && info->displayInfoId != 0)
iconPath = gh->getItemIconPath(info->displayInfoId);
if (!iconPath.empty()) lua_pushstring(L, iconPath.c_str());
else lua_pushnil(L);
lua_pushnumber(L, vi.buyPrice); // price (copper)
lua_pushnumber(L, vi.stackCount > 0 ? vi.stackCount : 1); // stackCount
lua_pushnumber(L, vi.maxCount == -1 ? -1 : vi.maxCount); // numAvailable (-1=unlimited)
lua_pushboolean(L, 1); // isUsable
return 6;
}
// GetMerchantItemLink(index) → item link
static int lua_GetMerchantItemLink(lua_State* L) {
auto* gh = getGameHandler(L);
int index = static_cast<int>(luaL_checknumber(L, 1));
if (!gh || index < 1) { return luaReturnNil(L); }
const auto& items = gh->getVendorItems().items;
if (index > static_cast<int>(items.size())) { return luaReturnNil(L); }
const auto& vi = items[index - 1];
const auto* info = gh->getItemInfo(vi.itemId);
if (!info) { return luaReturnNil(L); }
const char* ch = (info->quality < 8) ? kQualHexAlpha[info->quality] : "ffffffff";
char link[256];
snprintf(link, sizeof(link), "|c%s|Hitem:%u:0:0:0:0:0:0:0|h[%s]|h|r", ch, vi.itemId, info->name.c_str());
lua_pushstring(L, link);
return 1;
}
static int lua_CanMerchantRepair(lua_State* L) {
auto* gh = getGameHandler(L);
lua_pushboolean(L, gh && gh->getVendorItems().canRepair ? 1 : 0);
return 1;
}
// UnitStat(unit, statIndex) → base, effective, posBuff, negBuff
static int lua_GetItemInfo(lua_State* L) {
auto* gh = getGameHandler(L);
if (!gh) { return luaReturnNil(L); }
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) { return luaReturnNil(L); }
const auto* info = gh->getItemInfo(itemId);
if (!info) { return luaReturnNil(L); }
lua_pushstring(L, info->name.c_str()); // 1: name
// Build item link with quality-colored text
const char* colorHex = (info->quality < 8) ? kQualHexAlpha[info->quality] : "ffffffff";
char link[256];
snprintf(link, sizeof(link), "|c%s|Hitem:%u:0:0:0:0:0:0:0|h[%s]|h|r",
colorHex, 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
// 6: class (type string) — map itemClass to display name
{
static constexpr const char* kItemClasses[] = {
"Consumable", "Bag", "Weapon", "Gem", "Armor", "Reagent", "Projectile",
"Trade Goods", "Generic", "Recipe", "Money", "Quiver", "Quest", "Key",
"Permanent", "Miscellaneous", "Glyph"
};
if (info->itemClass < 17)
lua_pushstring(L, kItemClasses[info->itemClass]);
else
lua_pushstring(L, "Miscellaneous");
}
// 7: subclass — use subclassName from ItemDef if available, else generic
lua_pushstring(L, info->subclassName.empty() ? "" : info->subclassName.c_str());
lua_pushnumber(L, info->maxStack > 0 ? info->maxStack : 1); // 8: maxStack
// 9: equipSlot — WoW inventoryType to INVTYPE string
{
static constexpr const char* kInvTypes[] = {
"", "INVTYPE_HEAD", "INVTYPE_NECK", "INVTYPE_SHOULDER",
"INVTYPE_BODY", "INVTYPE_CHEST", "INVTYPE_WAIST", "INVTYPE_LEGS",
"INVTYPE_FEET", "INVTYPE_WRIST", "INVTYPE_HAND", "INVTYPE_FINGER",
"INVTYPE_TRINKET", "INVTYPE_WEAPON", "INVTYPE_SHIELD",
"INVTYPE_RANGED", "INVTYPE_CLOAK", "INVTYPE_2HWEAPON",
"INVTYPE_BAG", "INVTYPE_TABARD", "INVTYPE_ROBE",
"INVTYPE_WEAPONMAINHAND", "INVTYPE_WEAPONOFFHAND", "INVTYPE_HOLDABLE",
"INVTYPE_AMMO", "INVTYPE_THROWN", "INVTYPE_RANGEDRIGHT",
"INVTYPE_QUIVER", "INVTYPE_RELIC"
};
uint32_t invType = info->inventoryType;
lua_pushstring(L, invType < 29 ? kInvTypes[invType] : "");
}
// 10: texture (icon path from ItemDisplayInfo.dbc)
if (info->displayInfoId != 0) {
std::string iconPath = gh->getItemIconPath(info->displayInfoId);
if (!iconPath.empty()) lua_pushstring(L, iconPath.c_str());
else lua_pushnil(L);
} else {
lua_pushnil(L);
}
lua_pushnumber(L, info->sellPrice); // 11: vendorPrice
return 11;
}
// GetItemQualityColor(quality) → r, g, b, hex
// Quality: 0=Poor(gray), 1=Common(white), 2=Uncommon(green), 3=Rare(blue),
// 4=Epic(purple), 5=Legendary(orange), 6=Artifact(gold), 7=Heirloom(gold)
static int lua_GetItemQualityColor(lua_State* L) {
int q = static_cast<int>(luaL_checknumber(L, 1));
struct QC { float r, g, b; const char* hex; };
static const QC colors[] = {
{0.62f, 0.62f, 0.62f, "ff9d9d9d"}, // 0 Poor
{1.00f, 1.00f, 1.00f, "ffffffff"}, // 1 Common
{0.12f, 1.00f, 0.00f, "ff1eff00"}, // 2 Uncommon
{0.00f, 0.44f, 0.87f, "ff0070dd"}, // 3 Rare
{0.64f, 0.21f, 0.93f, "ffa335ee"}, // 4 Epic
{1.00f, 0.50f, 0.00f, "ffff8000"}, // 5 Legendary
{0.90f, 0.80f, 0.50f, "ffe6cc80"}, // 6 Artifact
{0.00f, 0.80f, 1.00f, "ff00ccff"}, // 7 Heirloom
};
if (q < 0 || q > 7) q = 1;
lua_pushnumber(L, colors[q].r);
lua_pushnumber(L, colors[q].g);
lua_pushnumber(L, colors[q].b);
lua_pushstring(L, colors[q].hex);
return 4;
}
// GetItemCount(itemId [, includeBank]) → count
static int lua_GetItemCount(lua_State* L) {
auto* gh = getGameHandler(L);
if (!gh) { return luaReturnZero(L); }
uint32_t itemId = static_cast<uint32_t>(luaL_checknumber(L, 1));
const auto& inv = gh->getInventory();
uint32_t count = 0;
// Backpack
for (int i = 0; i < inv.getBackpackSize(); ++i) {
const auto& s = inv.getBackpackSlot(i);
if (!s.empty() && s.item.itemId == itemId)
count += (s.item.stackCount > 0 ? s.item.stackCount : 1);
}
// Bags 1-4
for (int b = 0; b < game::Inventory::NUM_BAG_SLOTS; ++b) {
int sz = inv.getBagSize(b);
for (int i = 0; i < sz; ++i) {
const auto& s = inv.getBagSlot(b, i);
if (!s.empty() && s.item.itemId == itemId)
count += (s.item.stackCount > 0 ? s.item.stackCount : 1);
}
}
lua_pushnumber(L, count);
return 1;
}
// UseContainerItem(bag, slot) — use/equip an item from a bag
static int lua_UseContainerItem(lua_State* L) {
auto* gh = getGameHandler(L);
if (!gh) return 0;
int bag = static_cast<int>(luaL_checknumber(L, 1));
int slot = static_cast<int>(luaL_checknumber(L, 2));
const auto& inv = gh->getInventory();
const game::ItemSlot* itemSlot = nullptr;
if (bag == 0 && slot >= 1 && slot <= inv.getBackpackSize())
itemSlot = &inv.getBackpackSlot(slot - 1);
else if (bag >= 1 && bag <= 4) {
int sz = inv.getBagSize(bag - 1);
if (slot >= 1 && slot <= sz)
itemSlot = &inv.getBagSlot(bag - 1, slot - 1);
}
if (itemSlot && !itemSlot->empty())
gh->useItemById(itemSlot->item.itemId);
return 0;
}
// _GetItemTooltipData(itemId) → table with armor, bind, stats, damage, description
// Returns a Lua table with detailed item info for tooltip building
static int lua_GetItemTooltipData(lua_State* L) {
auto* gh = getGameHandler(L);
uint32_t itemId = static_cast<uint32_t>(luaL_checknumber(L, 1));
if (!gh || itemId == 0) { return luaReturnNil(L); }
const auto* info = gh->getItemInfo(itemId);
if (!info) { return luaReturnNil(L); }
lua_newtable(L);
// Unique / Heroic flags
if (info->maxCount == 1) { lua_pushboolean(L, 1); lua_setfield(L, -2, "isUnique"); }
if (info->itemFlags & 0x8) { lua_pushboolean(L, 1); lua_setfield(L, -2, "isHeroic"); }
if (info->itemFlags & 0x1000000) { lua_pushboolean(L, 1); lua_setfield(L, -2, "isUniqueEquipped"); }
// Bind type
lua_pushnumber(L, info->bindType);
lua_setfield(L, -2, "bindType");
// Armor
lua_pushnumber(L, info->armor);
lua_setfield(L, -2, "armor");
// Damage
lua_pushnumber(L, info->damageMin);
lua_setfield(L, -2, "damageMin");
lua_pushnumber(L, info->damageMax);
lua_setfield(L, -2, "damageMax");
lua_pushnumber(L, info->delayMs);
lua_setfield(L, -2, "speed");
// Primary stats
if (info->stamina != 0) { lua_pushnumber(L, info->stamina); lua_setfield(L, -2, "stamina"); }
if (info->strength != 0) { lua_pushnumber(L, info->strength); lua_setfield(L, -2, "strength"); }
if (info->agility != 0) { lua_pushnumber(L, info->agility); lua_setfield(L, -2, "agility"); }
if (info->intellect != 0) { lua_pushnumber(L, info->intellect); lua_setfield(L, -2, "intellect"); }
if (info->spirit != 0) { lua_pushnumber(L, info->spirit); lua_setfield(L, -2, "spirit"); }
// Description
if (!info->description.empty()) {
lua_pushstring(L, info->description.c_str());
lua_setfield(L, -2, "description");
}
// Required level
lua_pushnumber(L, info->requiredLevel);
lua_setfield(L, -2, "requiredLevel");
// Extra stats (hit, crit, haste, AP, SP, etc.) as array of {type, value} pairs
if (!info->extraStats.empty()) {
lua_newtable(L);
for (size_t i = 0; i < info->extraStats.size(); ++i) {
lua_newtable(L);
lua_pushnumber(L, info->extraStats[i].statType);
lua_setfield(L, -2, "type");
lua_pushnumber(L, info->extraStats[i].statValue);
lua_setfield(L, -2, "value");
lua_rawseti(L, -2, static_cast<int>(i) + 1);
}
lua_setfield(L, -2, "extraStats");
}
// Resistances
if (info->fireRes != 0) { lua_pushnumber(L, info->fireRes); lua_setfield(L, -2, "fireRes"); }
if (info->natureRes != 0) { lua_pushnumber(L, info->natureRes); lua_setfield(L, -2, "natureRes"); }
if (info->frostRes != 0) { lua_pushnumber(L, info->frostRes); lua_setfield(L, -2, "frostRes"); }
if (info->shadowRes != 0) { lua_pushnumber(L, info->shadowRes); lua_setfield(L, -2, "shadowRes"); }
if (info->arcaneRes != 0) { lua_pushnumber(L, info->arcaneRes); lua_setfield(L, -2, "arcaneRes"); }
// Item spell effects (Use: / Equip: / Chance on Hit:)
{
lua_newtable(L);
int spellCount = 0;
for (int i = 0; i < 5; ++i) {
if (info->spells[i].spellId == 0) continue;
++spellCount;
lua_newtable(L);
lua_pushnumber(L, info->spells[i].spellId);
lua_setfield(L, -2, "spellId");
lua_pushnumber(L, info->spells[i].spellTrigger);
lua_setfield(L, -2, "trigger");
// Get spell name for display
const std::string& sName = gh->getSpellName(info->spells[i].spellId);
if (!sName.empty()) { lua_pushstring(L, sName.c_str()); lua_setfield(L, -2, "name"); }
// Get description
const std::string& sDesc = gh->getSpellDescription(info->spells[i].spellId);
if (!sDesc.empty()) { lua_pushstring(L, sDesc.c_str()); lua_setfield(L, -2, "description"); }
lua_rawseti(L, -2, spellCount);
}
if (spellCount > 0) lua_setfield(L, -2, "itemSpells");
else lua_pop(L, 1);
}
// Gem sockets (WotLK/TBC)
int numSockets = 0;
for (int i = 0; i < 3; ++i) {
if (info->socketColor[i] != 0) ++numSockets;
}
if (numSockets > 0) {
lua_newtable(L);
for (int i = 0; i < 3; ++i) {
if (info->socketColor[i] != 0) {
lua_newtable(L);
lua_pushnumber(L, info->socketColor[i]);
lua_setfield(L, -2, "color");
lua_rawseti(L, -2, i + 1);
}
}
lua_setfield(L, -2, "sockets");
}
// Item set
if (info->itemSetId != 0) {
lua_pushnumber(L, info->itemSetId);
lua_setfield(L, -2, "itemSetId");
}
// Quest-starting item
if (info->startQuestId != 0) {
lua_pushboolean(L, 1);
lua_setfield(L, -2, "startsQuest");
}
return 1;
}
// --- Locale/Build/Realm info ---
static int lua_GetContainerNumSlots(lua_State* L) {
auto* gh = getGameHandler(L);
int container = static_cast<int>(luaL_checknumber(L, 1));
if (!gh) { return luaReturnZero(L); }
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) { return luaReturnNil(L); }
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()) { return luaReturnNil(L); }
// 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;
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",
kQualHexNoAlpha[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) { return luaReturnNil(L); }
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()) { return luaReturnNil(L); }
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];
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",
kQualHexNoAlpha[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;
}
// --- 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
// GetInventorySlotInfo("slotName") → slotId, textureName, checkRelic
// Maps WoW slot names (e.g. "HeadSlot", "HEADSLOT") to inventory slot IDs
static int lua_GetInventorySlotInfo(lua_State* L) {
const char* name = luaL_checkstring(L, 1);
std::string slot(name);
// Normalize: uppercase, strip trailing "SLOT" if present
for (char& c : slot) c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
if (slot.size() > 4 && slot.substr(slot.size() - 4) == "SLOT")
slot = slot.substr(0, slot.size() - 4);
// WoW inventory slots are 1-indexed
struct SlotMap { const char* name; int id; const char* texture; };
static const SlotMap mapping[] = {
{"HEAD", 1, "Interface\\PaperDoll\\UI-PaperDoll-Slot-Head"},
{"NECK", 2, "Interface\\PaperDoll\\UI-PaperDoll-Slot-Neck"},
{"SHOULDER", 3, "Interface\\PaperDoll\\UI-PaperDoll-Slot-Shoulder"},
{"SHIRT", 4, "Interface\\PaperDoll\\UI-PaperDoll-Slot-Shirt"},
{"CHEST", 5, "Interface\\PaperDoll\\UI-PaperDoll-Slot-Chest"},
{"WAIST", 6, "Interface\\PaperDoll\\UI-PaperDoll-Slot-Waist"},
{"LEGS", 7, "Interface\\PaperDoll\\UI-PaperDoll-Slot-Legs"},
{"FEET", 8, "Interface\\PaperDoll\\UI-PaperDoll-Slot-Feet"},
{"WRIST", 9, "Interface\\PaperDoll\\UI-PaperDoll-Slot-Wrists"},
{"HANDS", 10, "Interface\\PaperDoll\\UI-PaperDoll-Slot-Hands"},
{"FINGER0", 11, "Interface\\PaperDoll\\UI-PaperDoll-Slot-Finger"},
{"FINGER1", 12, "Interface\\PaperDoll\\UI-PaperDoll-Slot-Finger"},
{"TRINKET0", 13, "Interface\\PaperDoll\\UI-PaperDoll-Slot-Trinket"},
{"TRINKET1", 14, "Interface\\PaperDoll\\UI-PaperDoll-Slot-Trinket"},
{"BACK", 15, "Interface\\PaperDoll\\UI-PaperDoll-Slot-Chest"},
{"MAINHAND", 16, "Interface\\PaperDoll\\UI-PaperDoll-Slot-MainHand"},
{"SECONDARYHAND",17, "Interface\\PaperDoll\\UI-PaperDoll-Slot-SecondaryHand"},
{"RANGED", 18, "Interface\\PaperDoll\\UI-PaperDoll-Slot-Ranged"},
{"TABARD", 19, "Interface\\PaperDoll\\UI-PaperDoll-Slot-Tabard"},
};
for (const auto& m : mapping) {
if (slot == m.name) {
lua_pushnumber(L, m.id);
lua_pushstring(L, m.texture);
lua_pushboolean(L, m.id == 18 ? 1 : 0); // checkRelic: only ranged slot
return 3;
}
}
luaL_error(L, "Unknown inventory slot: %s", name);
return 0;
}
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) { return luaReturnNil(L); }
std::string uidStr(uid);
toLowerInPlace(uidStr);
if (uidStr != "player") { return luaReturnNil(L); }
const auto& inv = gh->getInventory();
const auto& slot = inv.getEquipSlot(static_cast<game::EquipSlot>(slotId - 1));
if (slot.empty()) { return luaReturnNil(L); }
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);
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",
kQualHexNoAlpha[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) { return luaReturnNil(L); }
std::string uidStr(uid);
toLowerInPlace(uidStr);
if (uidStr != "player") { return luaReturnNil(L); }
const auto& inv = gh->getInventory();
const auto& slot = inv.getEquipSlot(static_cast<game::EquipSlot>(slotId - 1));
if (slot.empty()) { return luaReturnNil(L); }
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) { return luaReturnNil(L); }
std::string uidStr(uid);
toLowerInPlace(uidStr);
if (uidStr != "player") { return luaReturnNil(L); }
const auto& inv = gh->getInventory();
const auto& slot = inv.getEquipSlot(static_cast<game::EquipSlot>(slotId - 1));
if (slot.empty()) { return luaReturnNil(L); }
lua_pushnil(L);
return 1;
}
static int lua_GetNumLootItems(lua_State* L) {
auto* gh = getGameHandler(L);
if (!gh || !gh->isLootWindowOpen()) { return luaReturnZero(L); }
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()) {
return luaReturnNil(L);
}
const auto& loot = gh->getCurrentLoot();
if (slot < 1 || slot > static_cast<int>(loot.items.size())) {
return luaReturnNil(L);
}
const auto& item = loot.items[slot - 1];
const auto* info = gh->getItemInfo(item.itemId);
// texture (icon path from ItemDisplayInfo.dbc)
std::string icon;
if (info && info->displayInfoId != 0) {
icon = gh->getItemIconPath(info->displayInfoId);
}
if (!icon.empty()) lua_pushstring(L, icon.c_str());
else lua_pushnil(L);
// name
if (info && !info->name.empty()) lua_pushstring(L, info->name.c_str());
else lua_pushstring(L, ("Item #" + std::to_string(item.itemId)).c_str());
lua_pushnumber(L, item.count); // quantity
lua_pushnumber(L, info ? info->quality : 1); // quality
lua_pushboolean(L, 0); // locked (not tracked)
return 5;
}
// GetLootSlotLink(slot) → itemLink
static int lua_GetLootSlotLink(lua_State* L) {
auto* gh = getGameHandler(L);
int slot = static_cast<int>(luaL_checknumber(L, 1));
if (!gh || !gh->isLootWindowOpen()) { return luaReturnNil(L); }
const auto& loot = gh->getCurrentLoot();
if (slot < 1 || slot > static_cast<int>(loot.items.size())) {
return luaReturnNil(L);
}
const auto& item = loot.items[slot - 1];
const auto* info = gh->getItemInfo(item.itemId);
if (!info || info->name.empty()) { return luaReturnNil(L); }
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",
kQualHexNoAlpha[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;
}
// --- Additional WoW API ---
static int lua_GetItemLink(lua_State* L) {
auto* gh = getGameHandler(L);
if (!gh) { return luaReturnNil(L); }
uint32_t itemId = static_cast<uint32_t>(luaL_checknumber(L, 1));
if (itemId == 0) { return luaReturnNil(L); }
const auto* info = gh->getItemInfo(itemId);
if (!info || info->name.empty()) { return luaReturnNil(L); }
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",
kQualHexNoAlpha[qi], itemId, info->name.c_str());
lua_pushstring(L, link);
return 1;
}
// GetSpellLink(spellIdOrName) → "|cFFxxxxxx|Hspell:ID|h[Name]|h|r"
void registerInventoryLuaAPI(lua_State* L) {
static const struct { const char* name; lua_CFunction func; } api[] = {
{"GetMoney", lua_GetMoney},
{"GetMerchantNumItems", lua_GetMerchantNumItems},
{"GetMerchantItemInfo", lua_GetMerchantItemInfo},
{"GetMerchantItemLink", lua_GetMerchantItemLink},
{"CanMerchantRepair", lua_CanMerchantRepair},
{"GetItemInfo", lua_GetItemInfo},
{"GetItemQualityColor", lua_GetItemQualityColor},
{"_GetItemTooltipData", lua_GetItemTooltipData},
{"GetItemCount", lua_GetItemCount},
{"UseContainerItem", lua_UseContainerItem},
{"GetContainerNumSlots", lua_GetContainerNumSlots},
{"GetContainerItemInfo", lua_GetContainerItemInfo},
{"GetContainerItemLink", lua_GetContainerItemLink},
{"GetContainerNumFreeSlots", lua_GetContainerNumFreeSlots},
{"GetInventorySlotInfo", lua_GetInventorySlotInfo},
{"GetInventoryItemLink", lua_GetInventoryItemLink},
{"GetInventoryItemID", lua_GetInventoryItemID},
{"GetInventoryItemTexture", lua_GetInventoryItemTexture},
{"GetItemLink", lua_GetItemLink},
{"GetNumLootItems", lua_GetNumLootItems},
{"GetLootSlotInfo", lua_GetLootSlotInfo},
{"GetLootSlotLink", lua_GetLootSlotLink},
{"LootSlot", lua_LootSlot},
{"CloseLoot", lua_CloseLoot},
{"GetLootMethod", lua_GetLootMethod},
{"BuyMerchantItem", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
int index = static_cast<int>(luaL_checknumber(L, 1));
int count = static_cast<int>(luaL_optnumber(L, 2, 1));
if (!gh || index < 1) return 0;
const auto& items = gh->getVendorItems().items;
if (index > static_cast<int>(items.size())) return 0;
const auto& vi = items[index - 1];
gh->buyItem(gh->getVendorGuid(), vi.itemId, vi.slot, count);
return 0;
}},
{"SellContainerItem", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
int bag = static_cast<int>(luaL_checknumber(L, 1));
int slot = static_cast<int>(luaL_checknumber(L, 2));
if (!gh) return 0;
if (bag == 0) gh->sellItemBySlot(slot - 1);
else if (bag >= 1 && bag <= 4) gh->sellItemInBag(bag - 1, slot - 1);
return 0;
}},
{"RepairAllItems", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
if (gh && gh->getVendorItems().canRepair) {
bool useGuildBank = lua_toboolean(L, 1) != 0;
gh->repairAll(gh->getVendorGuid(), useGuildBank);
}
return 0;
}},
{"UnequipItemSlot", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
int slot = static_cast<int>(luaL_checknumber(L, 1));
if (gh && slot >= 1 && slot <= 19)
gh->unequipToBackpack(static_cast<game::EquipSlot>(slot - 1));
return 0;
}},
{"AcceptTrade", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
if (gh) gh->acceptTrade();
return 0;
}},
{"CancelTrade", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
if (gh && gh->isTradeOpen()) gh->cancelTrade();
return 0;
}},
{"InitiateTrade", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
const char* uid = luaL_checkstring(L, 1);
if (gh) {
uint64_t guid = resolveUnitGuid(gh, std::string(uid));
if (guid != 0) gh->initiateTrade(guid);
}
return 0;
}},
{"GetNumAuctionItems", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
const char* listType = luaL_optstring(L, 1, "list");
if (!gh) { lua_pushnumber(L, 0); lua_pushnumber(L, 0); return 2; }
std::string t(listType);
const game::AuctionListResult* r = nullptr;
if (t == "list" || t == "browse") r = &gh->getAuctionBrowseResults();
else if (t == "owner") r = &gh->getAuctionOwnerResults();
else if (t == "bidder") r = &gh->getAuctionBidderResults();
lua_pushnumber(L, r ? r->auctions.size() : 0);
lua_pushnumber(L, r ? r->totalCount : 0);
return 2;
}},
{"GetAuctionItemInfo", [](lua_State* L) -> int {
// GetAuctionItemInfo(type, index) → name, texture, count, quality, canUse, level, levelColHeader, minBid, minIncrement, buyoutPrice, bidAmount, highBidder, bidderFullName, owner, ownerFullName, saleStatus, itemId
auto* gh = getGameHandler(L);
const char* listType = luaL_checkstring(L, 1);
int index = static_cast<int>(luaL_checknumber(L, 2));
if (!gh || index < 1) { return luaReturnNil(L); }
std::string t(listType);
const game::AuctionListResult* r = nullptr;
if (t == "list") r = &gh->getAuctionBrowseResults();
else if (t == "owner") r = &gh->getAuctionOwnerResults();
else if (t == "bidder") r = &gh->getAuctionBidderResults();
if (!r || index > static_cast<int>(r->auctions.size())) { return luaReturnNil(L); }
const auto& a = r->auctions[index - 1];
const auto* info = gh->getItemInfo(a.itemEntry);
std::string name = info ? info->name : "Item #" + std::to_string(a.itemEntry);
std::string icon = (info && info->displayInfoId != 0) ? gh->getItemIconPath(info->displayInfoId) : "";
uint32_t quality = info ? info->quality : 1;
lua_pushstring(L, name.c_str()); // name
lua_pushstring(L, icon.empty() ? "Interface\\Icons\\INV_Misc_QuestionMark" : icon.c_str()); // texture
lua_pushnumber(L, a.stackCount); // count
lua_pushnumber(L, quality); // quality
lua_pushboolean(L, 1); // canUse
lua_pushnumber(L, info ? info->requiredLevel : 0); // level
lua_pushstring(L, ""); // levelColHeader
lua_pushnumber(L, a.startBid); // minBid
lua_pushnumber(L, a.minBidIncrement); // minIncrement
lua_pushnumber(L, a.buyoutPrice); // buyoutPrice
lua_pushnumber(L, a.currentBid); // bidAmount
lua_pushboolean(L, a.bidderGuid != 0 ? 1 : 0); // highBidder
lua_pushstring(L, ""); // bidderFullName
lua_pushstring(L, ""); // owner
lua_pushstring(L, ""); // ownerFullName
lua_pushnumber(L, 0); // saleStatus
lua_pushnumber(L, a.itemEntry); // itemId
return 17;
}},
{"GetAuctionItemTimeLeft", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
const char* listType = luaL_checkstring(L, 1);
int index = static_cast<int>(luaL_checknumber(L, 2));
if (!gh || index < 1) { lua_pushnumber(L, 4); return 1; }
std::string t(listType);
const game::AuctionListResult* r = nullptr;
if (t == "list") r = &gh->getAuctionBrowseResults();
else if (t == "owner") r = &gh->getAuctionOwnerResults();
else if (t == "bidder") r = &gh->getAuctionBidderResults();
if (!r || index > static_cast<int>(r->auctions.size())) { lua_pushnumber(L, 4); return 1; }
// Return 1=short(<30m), 2=medium(<2h), 3=long(<12h), 4=very long(>12h)
uint32_t ms = r->auctions[index - 1].timeLeftMs;
int cat = (ms < 1800000) ? 1 : (ms < 7200000) ? 2 : (ms < 43200000) ? 3 : 4;
lua_pushnumber(L, cat);
return 1;
}},
{"GetAuctionItemLink", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
const char* listType = luaL_checkstring(L, 1);
int index = static_cast<int>(luaL_checknumber(L, 2));
if (!gh || index < 1) { return luaReturnNil(L); }
std::string t(listType);
const game::AuctionListResult* r = nullptr;
if (t == "list") r = &gh->getAuctionBrowseResults();
else if (t == "owner") r = &gh->getAuctionOwnerResults();
else if (t == "bidder") r = &gh->getAuctionBidderResults();
if (!r || index > static_cast<int>(r->auctions.size())) { return luaReturnNil(L); }
uint32_t itemId = r->auctions[index - 1].itemEntry;
const auto* info = gh->getItemInfo(itemId);
if (!info) { return luaReturnNil(L); }
const char* ch = (info->quality < 8) ? kQualHexAlpha[info->quality] : "ffffffff";
char link[256];
snprintf(link, sizeof(link), "|c%s|Hitem:%u:0:0:0:0:0:0:0|h[%s]|h|r", ch, itemId, info->name.c_str());
lua_pushstring(L, link);
return 1;
}},
{"GetInboxNumItems", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
lua_pushnumber(L, gh ? gh->getMailInbox().size() : 0);
return 1;
}},
{"GetInboxHeaderInfo", [](lua_State* L) -> int {
// GetInboxHeaderInfo(index) → packageIcon, stationeryIcon, sender, subject, money, COD, daysLeft, hasItem, wasRead, wasReturned, textCreated, canReply, isGM
auto* gh = getGameHandler(L);
int index = static_cast<int>(luaL_checknumber(L, 1));
if (!gh || index < 1) { return luaReturnNil(L); }
const auto& inbox = gh->getMailInbox();
if (index > static_cast<int>(inbox.size())) { return luaReturnNil(L); }
const auto& mail = inbox[index - 1];
lua_pushstring(L, "Interface\\Icons\\INV_Letter_15"); // packageIcon
lua_pushstring(L, "Interface\\Icons\\INV_Letter_15"); // stationeryIcon
lua_pushstring(L, mail.senderName.c_str()); // sender
lua_pushstring(L, mail.subject.c_str()); // subject
lua_pushnumber(L, mail.money); // money (copper)
lua_pushnumber(L, mail.cod); // COD
lua_pushnumber(L, mail.expirationTime / 86400.0f); // daysLeft
lua_pushboolean(L, mail.attachments.empty() ? 0 : 1); // hasItem
lua_pushboolean(L, mail.read ? 1 : 0); // wasRead
lua_pushboolean(L, 0); // wasReturned
lua_pushboolean(L, !mail.body.empty() ? 1 : 0); // textCreated
lua_pushboolean(L, mail.messageType == 0 ? 1 : 0); // canReply (player mail only)
lua_pushboolean(L, 0); // isGM
return 13;
}},
{"GetInboxText", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
int index = static_cast<int>(luaL_checknumber(L, 1));
if (!gh || index < 1) { return luaReturnNil(L); }
const auto& inbox = gh->getMailInbox();
if (index > static_cast<int>(inbox.size())) { return luaReturnNil(L); }
lua_pushstring(L, inbox[index - 1].body.c_str());
return 1;
}},
{"HasNewMail", [](lua_State* L) -> int {
auto* gh = getGameHandler(L);
if (!gh) { return luaReturnFalse(L); }
bool hasNew = false;
for (const auto& m : gh->getMailInbox()) {
if (!m.read) { hasNew = true; break; }
}
lua_pushboolean(L, hasNew ? 1 : 0);
return 1;
}},
};
for (const auto& [name, func] : api) {
lua_pushcfunction(L, func);
lua_setglobal(L, name);
}
}
} // namespace wowee::addons