Files
nampower/nampower/item_scripts.cpp
T

645 lines
23 KiB
C++

#include "item_scripts.hpp"
#include "helper.hpp"
#include "items.hpp"
#include "logging.hpp"
#include "offsets.hpp"
#include <cctype>
#include <cstring>
#include <string>
#include <unordered_map>
namespace Nampower {
// Local cache for item name lookups
static std::unordered_map<std::string, uint32_t> itemNameToIdCache;
// Reusable table references to reduce memory allocations
static int basicItemInfoTableRef = LUA_REFNIL;
static int itemInfoTableRef = LUA_REFNIL;
static int equippedItemsTableRef = LUA_REFNIL;
static int bagItemsTableRef = LUA_REFNIL;
static int bagTableRef = LUA_REFNIL;
// String keys used when pushing item data to Lua
static char itemIdKey[] = "itemId";
static char permanentEnchantIdKey[] = "permanentEnchantId";
static char tempEnchantIdKey[] = "tempEnchantId";
static char stackCountKey[] = "stackCount";
static char durationKey[] = "duration";
static char flagsKey[] = "flags";
static char durabilityKey[] = "durability";
static char maxDurabilityKey[] = "maxDurability";
static char tempEnchantmentTimeLeftMsKey[] = "tempEnchantmentTimeLeftMs";
static char tempEnchantmentChargesKey[] = "tempEnchantmentCharges";
std::string ToLowerCase(const char *str) {
std::string result;
if (!str) return result;
while (*str) {
result += static_cast<char>(tolower(*str));
++str;
}
return result;
}
uint32_t GetItemIdFromCache(const char *itemName) {
if (!itemName) return 0;
std::string lowerName = ToLowerCase(itemName);
auto it = itemNameToIdCache.find(lowerName);
if (it != itemNameToIdCache.end()) {
return it->second;
}
return 0;
}
void CacheItemNameToId(const char *itemName, uint32_t itemId) {
if (!itemName || itemId == 0) return;
std::string lowerName = ToLowerCase(itemName);
itemNameToIdCache[lowerName] = itemId;
}
bool DoesItemMatch(uint32_t itemId, uint32_t searchItemId, const char *searchItemName) {
if (searchItemId != 0) {
return itemId == searchItemId;
}
if (searchItemName) {
auto itemStats = GetItemStats(itemId);
if (itemStats && itemStats->m_displayName[0]) {
bool matches = _stricmp(itemStats->m_displayName[0], searchItemName) == 0;
if (matches) {
CacheItemNameToId(searchItemName, itemId);
}
return matches;
}
}
return false;
}
void PushItemFoundResult(uintptr_t *luaState, int32_t bagIndex, uint32_t slot) {
uint32_t adjustedSlot = slot;
if (bagIndex == 0) {
adjustedSlot = slot - 0x17; // subtract 23
} else if (bagIndex == -1) {
adjustedSlot = slot - 0x27; // subtract 39
} else if (bagIndex == -2) {
adjustedSlot = slot - 0x51; // subtract 81
}
lua_pushnumber(luaState, static_cast<double>(bagIndex));
lua_pushnumber(luaState, static_cast<double>(adjustedSlot + 1)); // lua is 1 indexed
}
uintptr_t *GetBagPtrFromContainer(uintptr_t *containerPtr) {
auto vftable = game::GetObjectVFTable(containerPtr);
using GetBagPtrT = uintptr_t * (__thiscall *)(uintptr_t *);
auto getBagPtrFunc = reinterpret_cast<GetBagPtrT>(vftable[4]);
return getBagPtrFunc(containerPtr);
}
void CreateBasicItemInfoTable(uintptr_t *luaState, game::CGItem *cgItem) {
if (!cgItem) {
lua_pushnil(luaState);
return;
}
// Get or create reusable table
if (basicItemInfoTableRef == LUA_REFNIL) {
lua_newtable(luaState);
basicItemInfoTableRef = luaL_ref(luaState, LUA_REGISTRYINDEX);
}
lua_rawgeti(luaState, LUA_REGISTRYINDEX, basicItemInfoTableRef);
PushTableValue(luaState, itemIdKey, cgItem->itemId);
PushTableValue(luaState, permanentEnchantIdKey, cgItem->permanentEnchantId);
PushTableValue(luaState, tempEnchantIdKey, cgItem->tempEnchantId);
}
void CreateItemInfoTable(uintptr_t *luaState, game::CGItem_C *item) {
if (!item || !item->itemFields) {
lua_pushnil(luaState);
return;
}
auto itemId = game::GetItemId(item);
auto itemFields = item->itemFields;
// Get or create reusable table
if (itemInfoTableRef == LUA_REFNIL) {
lua_newtable(luaState);
itemInfoTableRef = luaL_ref(luaState, LUA_REGISTRYINDEX);
}
lua_rawgeti(luaState, LUA_REGISTRYINDEX, itemInfoTableRef);
PushTableValue(luaState, itemIdKey, itemId);
PushTableValue(luaState, stackCountKey, itemFields->stackCount);
PushTableValue(luaState, durationKey, itemFields->duration);
PushTableValue(luaState, flagsKey, itemFields->flags);
PushTableValue(luaState, permanentEnchantIdKey, itemFields->permEnchantmentSlot.id);
PushTableValue(luaState, tempEnchantIdKey, itemFields->tempEnchantmentSlot.id);
PushTableValue(luaState, tempEnchantmentTimeLeftMsKey, itemFields->tempEnchantmentSlot.duration);
PushTableValue(luaState, tempEnchantmentChargesKey, itemFields->tempEnchantmentSlot.charges);
PushTableValue(luaState, durabilityKey, itemFields->durability);
PushTableValue(luaState, maxDurabilityKey, itemFields->maxDurability);
}
void PushBagCGItemToTable(uintptr_t *luaState, int32_t bagIndex, uint32_t slot, game::CGItem_C *item) {
if (!item) {
DEBUG_LOG("missing item at slot " << slot);
return;
}
if (!item->itemFields) {
DEBUG_LOG("missing itemfields at slot " << slot << " itemId " << game::GetItemId(item));
return;
}
uint32_t adjustedSlot = slot;
if (bagIndex == 0) {
adjustedSlot = slot - 0x17; // subtract 23
} else if (bagIndex == -1) {
adjustedSlot = slot - 0x27; // subtract 39
} else if (bagIndex == -2) {
adjustedSlot = slot - 0x51; // subtract 81
}
lua_pushnumber(luaState, static_cast<double>(adjustedSlot + 1)); // lua is 1 indexed
CreateItemInfoTable(luaState, item);
lua_settable(luaState, -3);
}
uint32_t Script_FindPlayerItemSlot(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
auto playerGuid = game::ClntObjMgrGetActivePlayerGuid();
auto playerUnit = game::GetObjectPtr(playerGuid);
if (!playerUnit) {
lua_error(luaState, "Unable to get player unit");
return 0;
}
uint32_t searchItemId = 0;
const char *searchItemName = nullptr;
if (lua_isnumber(luaState, 1)) {
searchItemId = static_cast<uint32_t>(lua_tonumber(luaState, 1));
} else if (lua_isstring(luaState, 1)) {
searchItemName = lua_tostring(luaState, 1);
uint32_t cachedItemId = GetItemIdFromCache(searchItemName);
if (cachedItemId != 0) {
searchItemId = cachedItemId;
searchItemName = nullptr;
}
} else {
lua_error(luaState, "Usage: FindPlayerItemSlot(itemName) or FindPlayerItemSlot(itemId)");
return 0;
}
auto const getBagItem = reinterpret_cast<CGBag_C_GetItemAtSlotT>(Offsets::CGBag_C_GetItemAtSlot);
auto const getContainerGuid = reinterpret_cast<GetContainerGuidT>(Offsets::GetContainerGuid);
auto inventory = game::GetPlayerInventoryPtr(playerUnit);
for (uint32_t slot = 0; slot <= 18; slot++) {
auto item = getBagItem(inventory, slot);
if (item && DoesItemMatch(game::GetItemId(item), searchItemId, searchItemName)) {
lua_pushnil(luaState);
lua_pushnumber(luaState, static_cast<double>(slot+1)); // lua is 1 indexed
return 2;
}
}
for (uint32_t slot = 23; slot <= 38; slot++) {
auto item = getBagItem(inventory, slot);
if (item && DoesItemMatch(game::GetItemId(item), searchItemId, searchItemName)) {
PushItemFoundResult(luaState, 0, slot);
return 2;
}
}
for (int32_t bagIndex = 1; bagIndex <= 4; bagIndex++) {
uint64_t containerGuid = getContainerGuid(bagIndex - 1); // bagIndex 1-4 maps to container 0-3
if (containerGuid == 0) continue;
auto containerPtr = game::ClntObjMgrObjectPtr(game::TYPEMASK_CONTAINER, containerGuid);
if (!containerPtr) continue;
auto bagPtr = GetBagPtrFromContainer(containerPtr);
if (!bagPtr) continue;
auto bagSize = *bagPtr;
for (uint32_t slot = 0; slot < bagSize; slot++) {
auto item = getBagItem(bagPtr, slot);
if (item && DoesItemMatch(game::GetItemId(item), searchItemId, searchItemName)) {
PushItemFoundResult(luaState, bagIndex, slot);
return 2;
}
}
}
uint64_t bankGuid = *reinterpret_cast<uint64_t *>(Offsets::BankGuid);
if (bankGuid > 0) {
for (uint32_t slot = 39; slot <= 62; slot++) {
auto item = getBagItem(inventory, slot);
if (item && DoesItemMatch(game::GetItemId(item), searchItemId, searchItemName)) {
PushItemFoundResult(luaState, -1, slot);
return 2;
}
}
for (int32_t bagIndex = 5; bagIndex <= 9; bagIndex++) {
uint64_t containerGuid = getContainerGuid(bagIndex - 1); // bagIndex 5-9 maps to container 4-8
if (containerGuid == 0) continue;
auto containerPtr = game::ClntObjMgrObjectPtr(game::TYPEMASK_CONTAINER, containerGuid);
if (!containerPtr) continue;
auto bagPtr = GetBagPtrFromContainer(containerPtr);
if (!bagPtr) continue;
auto bagSize = *bagPtr;
for (uint32_t slot = 0; slot < bagSize; slot++) {
auto item = getBagItem(bagPtr, slot);
if (item && DoesItemMatch(game::GetItemId(item), searchItemId, searchItemName)) {
PushItemFoundResult(luaState, bagIndex, slot);
return 2;
}
}
}
}
for (uint32_t slot = 81; slot <= 96; slot++) {
auto item = getBagItem(inventory, slot);
if (item && DoesItemMatch(game::GetItemId(item), searchItemId, searchItemName)) {
PushItemFoundResult(luaState, -2, slot);
return 2;
}
}
lua_pushnil(luaState);
lua_pushnil(luaState);
return 2;
}
uint32_t Script_GetEquippedItems(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
uint64_t guid;
if (lua_gettop(luaState) >= 1) {
guid = GetUnitGuidFromLuaParam(luaState, 1);
if (guid == 0) {
lua_error(luaState, "Usage: GetEquippedItems() or GetEquippedItems(unitStr) or GetEquippedItems(guid)");
return 0;
}
} else {
guid = game::ClntObjMgrGetActivePlayerGuid();
}
auto unit = game::GetObjectPtr(guid);
if (!unit) {
lua_error(luaState, "Unable to get unit");
return 0;
}
auto const canInspectUnit = reinterpret_cast<CanInspectUnitT>(Offsets::CanInspectUnit);
if (!canInspectUnit(unit)) {
lua_error(luaState, "Cannot inspect unit");
return 0;
}
// Get or create reusable table
if (equippedItemsTableRef == LUA_REFNIL) {
lua_newtable(luaState);
equippedItemsTableRef = luaL_ref(luaState, LUA_REGISTRYINDEX);
}
lua_rawgeti(luaState, LUA_REGISTRYINDEX, equippedItemsTableRef);
auto playerGuid = game::ClntObjMgrGetActivePlayerGuid();
bool isPlayer = (guid == playerGuid);
if (isPlayer) {
auto const getBagItem = reinterpret_cast<CGBag_C_GetItemAtSlotT>(Offsets::CGBag_C_GetItemAtSlot);
auto inventory = game::GetPlayerInventoryPtr(unit);
for (uint32_t slot = 0; slot <= 18; slot++) {
auto item = getBagItem(inventory, slot);
if (item) {
PushBagCGItemToTable(luaState, -999, slot, item);
}
}
} else {
auto const getEquippedItem = reinterpret_cast<CGUnit_C_GetEquippedItemAtSlotT>(
Offsets::CGUnit_C_GetEquippedItemAtSlot);
for (uint32_t slot = 0; slot <= 18; slot++) {
auto cgItem = getEquippedItem(unit, slot);
if (cgItem) {
lua_pushnumber(luaState, static_cast<double>(slot+1)); // lua is 1 indexed
CreateBasicItemInfoTable(luaState, cgItem);
lua_settable(luaState, -3);
}
}
}
return 1;
}
uint32_t Script_GetEquippedItem(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
if (lua_gettop(luaState) < 2) {
lua_error(luaState, "Usage: GetEquippedItem(unitStr, slot) or GetEquippedItem(guid, slot)");
return 0;
}
uint64_t guid;
if (lua_gettop(luaState) >= 1) {
guid = GetUnitGuidFromLuaParam(luaState, 1);
if (guid == 0) {
lua_error(luaState, "Usage: GetEquippedItem(unitStr, slot) or GetEquippedItem(guid, slot)");
return 0;
}
} else {
guid = game::ClntObjMgrGetActivePlayerGuid();
}
if (!lua_isnumber(luaState, 2)) {
lua_error(luaState, "Slot must be a number between 1 and 19");
return 0;
}
auto luaSlot = static_cast<uint32_t>(lua_tonumber(luaState, 2));
if (luaSlot < 1 || luaSlot > 19) {
lua_error(luaState, "Slot must be between 1 and 19");
return 0;
}
auto unit = game::GetObjectPtr(guid);
if (!unit) {
lua_error(luaState, "Unable to get unit");
return 0;
}
auto const canInspectUnit = reinterpret_cast<CanInspectUnitT>(Offsets::CanInspectUnit);
if (!canInspectUnit(unit)) {
lua_error(luaState, "Cannot inspect unit");
return 0;
}
auto playerGuid = game::ClntObjMgrGetActivePlayerGuid();
bool isPlayer = (guid == playerGuid);
if (isPlayer) {
auto const getBagItem = reinterpret_cast<CGBag_C_GetItemAtSlotT>(Offsets::CGBag_C_GetItemAtSlot);
auto inventory = game::GetPlayerInventoryPtr(unit);
auto item = getBagItem(inventory, luaSlot-1); // luaSlot is 1-indexed
CreateItemInfoTable(luaState, item);
} else {
auto const getEquippedItem = reinterpret_cast<CGUnit_C_GetEquippedItemAtSlotT>(
Offsets::CGUnit_C_GetEquippedItemAtSlot);
auto cgItem = getEquippedItem(unit, luaSlot - 1); // luaSlot is 1-indexed
CreateBasicItemInfoTable(luaState, cgItem);
}
return 1;
}
uint32_t ConvertLuaSlot(int32_t bagIndex, uint32_t luaSlot) {
uint32_t relativeSlot = luaSlot - 1;
if (bagIndex == 0) {
return relativeSlot + 0x17; // add 23 -> absolute slots 23-38
} else if (bagIndex == -1) {
return relativeSlot + 0x27; // add 39 -> absolute slots 39-62
} else if (bagIndex == -2) {
return relativeSlot + 0x51; // add 81 -> absolute slots 81-96
} else {
return relativeSlot;
}
}
uint32_t Script_GetBagItem(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
if (lua_gettop(luaState) < 2) {
lua_error(luaState, "Usage: GetBagItem(bagIndex, slot)");
return 0;
}
if (!lua_isnumber(luaState, 1)) {
lua_error(luaState, "Bag index must be a number");
return 0;
}
auto bagIndex = static_cast<int32_t>(lua_tonumber(luaState, 1));
if (!lua_isnumber(luaState, 2)) {
lua_error(luaState, "Slot must be a number");
return 0;
}
auto luaSlot = static_cast<uint32_t>(lua_tonumber(luaState, 2));
auto slot = ConvertLuaSlot(bagIndex, luaSlot);
auto const getBagItem = reinterpret_cast<CGBag_C_GetItemAtSlotT>(Offsets::CGBag_C_GetItemAtSlot);
auto const getContainerGuid = reinterpret_cast<GetContainerGuidT>(Offsets::GetContainerGuid);
auto playerGuid = game::ClntObjMgrGetActivePlayerGuid();
auto player = game::GetObjectPtr(playerGuid);
auto inventory = game::GetPlayerInventoryPtr(player);
game::CGItem_C *item = nullptr;
if (bagIndex == 0) {
if (slot < 23 || slot > 38) {
lua_error(luaState, "Slot must be between 1 and 16 for bag 1");
return 0;
}
item = getBagItem(inventory, slot);
} else if (bagIndex >= 1 && bagIndex <= 4) {
uint64_t containerGuid = getContainerGuid(bagIndex-1); // bagIndex 1-4 maps to container 0-3
if (containerGuid == 0) {
lua_pushnil(luaState);
return 1;
}
auto containerPtr = game::ClntObjMgrObjectPtr(game::TYPEMASK_CONTAINER, containerGuid);
if (!containerPtr) {
lua_pushnil(luaState);
return 1;
}
auto bagPtr = GetBagPtrFromContainer(containerPtr);
if (!bagPtr) {
lua_pushnil(luaState);
return 1;
}
auto bagSize = *bagPtr;
if (slot >= bagSize) {
lua_error(luaState, "Slot exceeds bag size");
return 0;
}
item = getBagItem(bagPtr, slot);
} else if (bagIndex == -1) {
uint64_t bankGuid = *reinterpret_cast<uint64_t *>(Offsets::BankGuid);
if (bankGuid == 0 && slot >= 39 && slot <= 62) {
lua_error(luaState, "Bank is not open");
return 0;
}
if ((slot >= 39 && slot <= 62) || (slot >= 69 && slot <= 80)) {
item = getBagItem(inventory, slot);
} else {
lua_error(luaState, "For bag -1, slot must be 1-24 (bank) or 31-42 (buyback) (Lua 1-indexed)");
return 0;
}
} else if (bagIndex >= 4 && bagIndex <= 8) {
uint64_t bankGuid = *reinterpret_cast<uint64_t *>(Offsets::BankGuid);
if (bankGuid == 0) {
lua_error(luaState, "Bank is not open");
return 0;
}
uint64_t containerGuid = getContainerGuid(bagIndex);
if (containerGuid == 0) {
lua_pushnil(luaState);
return 1;
}
auto containerPtr = game::ClntObjMgrObjectPtr(game::TYPEMASK_CONTAINER, containerGuid);
if (!containerPtr) {
lua_pushnil(luaState);
return 1;
}
auto bagPtr = GetBagPtrFromContainer(containerPtr);
if (!bagPtr) {
lua_pushnil(luaState);
return 1;
}
auto bagSize = *bagPtr;
if (slot >= bagSize) {
lua_error(luaState, "Slot exceeds bag size");
return 0;
}
item = getBagItem(bagPtr, slot);
} else if (bagIndex == -2) {
if (slot < 81 || slot > 96) {
lua_error(luaState, "For bag -2 (keyring), slot must be 1-16 (Lua 1-indexed)");
return 0;
}
item = getBagItem(inventory, slot);
} else {
lua_error(luaState, "Invalid bag index. Valid values: 0, 1-4, -1, 5-9 (bank), -2 (keyring)");
return 0;
}
CreateItemInfoTable(luaState, item);
return 1;
}
uint32_t Script_GetBagItems(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
auto const getContainerGuid = reinterpret_cast<GetContainerGuidT>(Offsets::GetContainerGuid);
auto const getBagItem = reinterpret_cast<CGBag_C_GetItemAtSlotT>(Offsets::CGBag_C_GetItemAtSlot);
// Get or create reusable table
if (bagItemsTableRef == LUA_REFNIL) {
lua_newtable(luaState);
bagItemsTableRef = luaL_ref(luaState, LUA_REGISTRYINDEX);
}
lua_rawgeti(luaState, LUA_REGISTRYINDEX, bagItemsTableRef);
auto playerGuid = game::ClntObjMgrGetActivePlayerGuid();
auto player = game::GetObjectPtr(playerGuid);
auto inventory = game::GetPlayerInventoryPtr(player);
lua_pushnumber(luaState, static_cast<double>(0));
// Get or create reusable bag table
if (bagTableRef == LUA_REFNIL) {
lua_newtable(luaState);
bagTableRef = luaL_ref(luaState, LUA_REGISTRYINDEX);
}
lua_rawgeti(luaState, LUA_REGISTRYINDEX, bagTableRef);
for (uint32_t slot = 23; slot <= 38; slot++) {
auto item = getBagItem(inventory, slot);
if (item) {
PushBagCGItemToTable(luaState, 0, slot, item);
}
}
lua_settable(luaState, -3);
for (int32_t bagIndex = 1; bagIndex <= 4; bagIndex++) {
uint64_t containerGuid = getContainerGuid(bagIndex - 1); // bagIndex 1-4 maps to container 0-3
if (containerGuid == 0) continue;
auto containerPtr = game::ClntObjMgrObjectPtr(game::TYPEMASK_CONTAINER, containerGuid);
if (!containerPtr) continue;
auto bagPtr = GetBagPtrFromContainer(containerPtr);
if (!bagPtr) continue;
lua_pushnumber(luaState, static_cast<double>(bagIndex));
lua_rawgeti(luaState, LUA_REGISTRYINDEX, bagTableRef);
auto bagSize = *bagPtr;
for (uint32_t slot = 0; slot < bagSize; slot++) {
auto item = getBagItem(bagPtr, slot);
if (item) {
PushBagCGItemToTable(luaState, bagIndex, slot, item);
}
}
lua_settable(luaState, -3);
}
uint64_t bankGuid = *reinterpret_cast<uint64_t *>(Offsets::BankGuid);
if (bankGuid > 0) {
for (int32_t bagIndex = 5; bagIndex <= 9; bagIndex++) {
uint64_t containerGuid = getContainerGuid(bagIndex - 1); // bagIndex 5-9 maps to container 4-8
if (containerGuid == 0) continue;
auto containerPtr = game::ClntObjMgrObjectPtr(game::TYPEMASK_CONTAINER, containerGuid);
if (!containerPtr) continue;
auto bagPtr = GetBagPtrFromContainer(containerPtr);
if (!bagPtr) continue;
lua_pushnumber(luaState, static_cast<double>(bagIndex));
lua_rawgeti(luaState, LUA_REGISTRYINDEX, bagTableRef);
auto bagSize = *bagPtr;
for (uint32_t slot = 0; slot < bagSize; slot++) {
auto item = getBagItem(bagPtr, slot);
if (item) {
PushBagCGItemToTable(luaState, bagIndex, slot, item);
}
}
lua_settable(luaState, -3);
}
}
return 1;
}
}