mirror of
https://github.com/brues-code/nampower.git
synced 2026-09-22 07:26:56 +00:00
a7d2cd4fa6
fix guid in GetCastInfo
945 lines
36 KiB
C++
945 lines
36 KiB
C++
//
|
|
// Created by pmacc on 1/8/2025.
|
|
//
|
|
|
|
#include "misc_scripts.hpp"
|
|
#include "offsets.hpp"
|
|
#include "items.hpp"
|
|
#include "dbc_fields.hpp"
|
|
#include "unit_fields.hpp"
|
|
#include "helper.hpp"
|
|
#include "lua_refs.hpp"
|
|
#include "auras.hpp"
|
|
#include <cstring>
|
|
|
|
namespace Nampower {
|
|
// Lua table field name constants
|
|
namespace LuaFields {
|
|
static char castId[] = "castId";
|
|
static char spellId[] = "spellId";
|
|
static char guid[] = "guid";
|
|
static char castType[] = "castType";
|
|
static char castStartS[] = "castStartS";
|
|
static char castEndS[] = "castEndS";
|
|
static char castRemainingMs[] = "castRemainingMs";
|
|
static char castDurationMs[] = "castDurationMs";
|
|
static char gcdEndS[] = "gcdEndS";
|
|
static char gcdRemainingMs[] = "gcdRemainingMs";
|
|
}
|
|
|
|
bool gScriptQueued;
|
|
int gScriptPriority = 1;
|
|
char *queuedScript;
|
|
|
|
// Reusable table references to reduce memory allocations
|
|
static int castInfoTableRef = LUA_REFNIL;
|
|
static int itemStatsTableRef = LUA_REFNIL;
|
|
static int unitDataTableRef = LUA_REFNIL;
|
|
|
|
// Maps to store separate references for each array field name (for Field functions)
|
|
static std::unordered_map<std::string, int> itemStatsArrayFieldRefs;
|
|
static std::unordered_map<std::string, int> unitFieldsArrayFieldRefs;
|
|
|
|
// Maps to store separate references for nested array fields (for main table functions)
|
|
static std::unordered_map<std::string, int> itemStatsNestedArrayRefs;
|
|
static std::unordered_map<std::string, int> unitFieldsNestedArrayRefs;
|
|
|
|
uint32_t Script_GetCurrentCastingInfo(uintptr_t *luaState) {
|
|
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
|
|
|
|
auto const castingSpellId = reinterpret_cast<uint32_t *>(Offsets::CastingSpellId);
|
|
lua_pushnumber(luaState, *castingSpellId);
|
|
|
|
auto const isCasting = gCastData.castEndMs > GetTime();
|
|
auto const isChanneling = gCastData.channeling;
|
|
|
|
auto const visualSpellId = reinterpret_cast<uint32_t *>(Offsets::VisualSpellId);
|
|
lua_pushnumber(luaState, *visualSpellId);
|
|
|
|
auto const autoRepeatingSpellId = reinterpret_cast<uint32_t *>(Offsets::AutoRepeatingSpellId);
|
|
lua_pushnumber(luaState, *autoRepeatingSpellId);
|
|
|
|
auto playerUnit = game::GetObjectPtr(game::ClntObjMgrGetActivePlayerGuid());
|
|
if (isCasting) {
|
|
lua_pushnumber(luaState, 1);
|
|
} else {
|
|
lua_pushnumber(luaState, 0);
|
|
}
|
|
|
|
if (isChanneling) {
|
|
lua_pushnumber(luaState, 1);
|
|
} else {
|
|
lua_pushnumber(luaState, 0);
|
|
}
|
|
|
|
if (gCastData.pendingOnSwingCast) {
|
|
lua_pushnumber(luaState, 1);
|
|
} else {
|
|
lua_pushnumber(luaState, 0);
|
|
}
|
|
|
|
auto const attackPtr = playerUnit + 0x312; // auto attacking
|
|
if (attackPtr && *reinterpret_cast<uint32_t *>(attackPtr) > 0) {
|
|
lua_pushnumber(luaState, 1);
|
|
} else {
|
|
lua_pushnumber(luaState, 0);
|
|
}
|
|
|
|
return 7;
|
|
}
|
|
|
|
uint32_t Script_GetCastInfo(uintptr_t *luaState) {
|
|
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
|
|
|
|
// Check if there's an active cast or channel
|
|
uint32_t activeSpellId = 0;
|
|
uint32_t castEndTime = 0;
|
|
|
|
CastSpellParams* castParams = nullptr;
|
|
|
|
// Check for active channeling spell first
|
|
if (gCastData.channeling && gCastData.channelSpellId != 0) {
|
|
activeSpellId = gCastData.channelSpellId;
|
|
castEndTime = gCastData.channelEndMs;
|
|
// cast will be finished for channels
|
|
castParams = gCastHistory.findNewestSuccessfulSpellId(activeSpellId);
|
|
}
|
|
// Check for active cast spell
|
|
else if (gCastData.castSpellId != 0) {
|
|
activeSpellId = gCastData.castSpellId;
|
|
// Use the max of castEndMs and gcdEndMs
|
|
castEndTime = (gCastData.castEndMs > gCastData.gcdEndMs) ? gCastData.castEndMs : gCastData.gcdEndMs;
|
|
// cast won't be finished yet
|
|
castParams = gCastHistory.findNewestWaitingForServerSpellId(activeSpellId);
|
|
}
|
|
|
|
// If no active cast or channel, return nil
|
|
if (activeSpellId == 0) {
|
|
lua_pushnil(luaState);
|
|
return 1;
|
|
}
|
|
|
|
if (castParams == nullptr || castParams->castId == 0) {
|
|
lua_pushnil(luaState);
|
|
return 1;
|
|
}
|
|
|
|
// Get or create reusable table
|
|
GetTableRef(luaState, castInfoTableRef);
|
|
|
|
// Get current time and calculate offset to convert to WoW time
|
|
uint32_t currentTime = GetTime();
|
|
uint64_t currentWowTime = GetWowTimeMs();
|
|
int64_t timeOffset = static_cast<int64_t>(currentWowTime) - static_cast<int64_t>(currentTime);
|
|
|
|
// Convert all timestamps to WoW time and convert to seconds
|
|
double castStartTimeWow = (castParams->castStartTimeMs + timeOffset) / 1000.0;
|
|
double castEndTimeWow = (castEndTime + timeOffset) / 1000.0;
|
|
double gcdEndTimeWow = (gCastData.gcdEndMs + timeOffset) / 1000.0;
|
|
|
|
// Add fields to table
|
|
PushTableValue(luaState, LuaFields::castId, castParams->castId);
|
|
PushTableValue(luaState, LuaFields::spellId, castParams->spellId);
|
|
PushTableValue(luaState, LuaFields::guid, ConvertGuidToString(castParams->guid));
|
|
PushTableValue(luaState, LuaFields::castType, static_cast<uint32_t>(castParams->castType));
|
|
PushTableValue(luaState, LuaFields::castStartS, castStartTimeWow);
|
|
PushTableValue(luaState, LuaFields::castEndS, castEndTimeWow);
|
|
|
|
uint32_t timeRemaining = (castEndTime > currentTime) ? (castEndTime - currentTime) : 0;
|
|
PushTableValue(luaState, LuaFields::castRemainingMs, timeRemaining);
|
|
|
|
uint32_t duration = (castEndTime > castParams->castStartTimeMs) ?
|
|
(castEndTime - castParams->castStartTimeMs) : 0;
|
|
PushTableValue(luaState, LuaFields::castDurationMs, duration);
|
|
|
|
// Add GCD info
|
|
PushTableValue(luaState, LuaFields::gcdEndS, gcdEndTimeWow);
|
|
uint32_t gcdRemaining = (gCastData.gcdEndMs > currentTime) ? (gCastData.gcdEndMs - currentTime) : 0;
|
|
PushTableValue(luaState, LuaFields::gcdRemainingMs, gcdRemaining);
|
|
|
|
return 1; // Return the table
|
|
}
|
|
|
|
uint32_t Script_ChannelStopCastingNextTick(uintptr_t *luaState) {
|
|
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
|
|
|
|
if (gCastData.channeling) {
|
|
DEBUG_LOG("ChannelStopCastingNextTick activated, canceling next tick");
|
|
gCastData.cancelChannelNextTick = true;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32_t Script_GetNampowerVersion(uintptr_t *luaState) {
|
|
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
|
|
|
|
lua_pushnumber(luaState, MAJOR_VERSION);
|
|
lua_pushnumber(luaState, MINOR_VERSION);
|
|
lua_pushnumber(luaState, PATCH_VERSION);
|
|
|
|
return 3;
|
|
}
|
|
|
|
uint32_t Script_GetItemLevel(uintptr_t *luaState) {
|
|
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
|
|
|
|
if (lua_isnumber(luaState, 1)) {
|
|
auto const itemId = static_cast<uint32_t>(lua_tonumber(luaState, 1));
|
|
|
|
// Pointer to ItemDBCache
|
|
void *itemDbCache = reinterpret_cast<void *>(Offsets::ItemDBCache);
|
|
|
|
// Parameters for the DBCache<>::GetRecord function
|
|
int **param2 = nullptr;
|
|
int *param3 = nullptr;
|
|
int *param4 = nullptr;
|
|
char param5 = 0;
|
|
|
|
// Call the DBCache<>::GetRecord function
|
|
auto getRecord = reinterpret_cast<uintptr_t *(__thiscall *)(void *, uint32_t, int **, int *, int *, char)>(
|
|
Offsets::DBCacheGetRecord
|
|
);
|
|
uintptr_t *itemObject = getRecord(itemDbCache, itemId, param2, param3, param4, param5);
|
|
if (itemObject == nullptr) {
|
|
lua_error(luaState, "Item not found in DBCache");
|
|
return 0;
|
|
}
|
|
|
|
uint32_t itemLevel = *reinterpret_cast<uint32_t *>(itemObject + 14);
|
|
lua_pushnumber(luaState, itemLevel);
|
|
return 1;
|
|
} else {
|
|
lua_error(luaState, "Usage: GetItemLevel(itemId)");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32_t Script_QueueScript(uintptr_t *luaState) {
|
|
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
|
|
|
|
DEBUG_LOG("Trying to queue script");
|
|
|
|
auto const currentTime = GetTime();
|
|
auto effectiveCastEndMs = EffectiveCastEndMs();
|
|
auto remainingEffectiveCastTime = (effectiveCastEndMs > currentTime) ? effectiveCastEndMs - currentTime : 0;
|
|
auto remainingGcd = (gCastData.gcdEndMs > currentTime) ? gCastData.gcdEndMs - currentTime : 0;
|
|
auto inSpellQueueWindow = InSpellQueueWindow(remainingEffectiveCastTime, remainingGcd, false);
|
|
|
|
if (inSpellQueueWindow) {
|
|
// check if valid string
|
|
if (lua_isstring(luaState, 1)) {
|
|
auto script = lua_tostring(luaState, 1);
|
|
|
|
if (script != nullptr && strlen(script) > 0) {
|
|
// save the script to be run later
|
|
queuedScript = script;
|
|
gScriptQueued = true;
|
|
|
|
// check if priority is set
|
|
if (lua_isnumber(luaState, 2)) {
|
|
gScriptPriority = (int) lua_tonumber(luaState, 2);
|
|
|
|
DEBUG_LOG("Queuing script priority " << gScriptPriority << ": " << script);
|
|
} else {
|
|
DEBUG_LOG("Queuing script: " << script);
|
|
}
|
|
}
|
|
} else {
|
|
DEBUG_LOG("Invalid script");
|
|
lua_error(luaState, "Usage: QueueScript(\"script\", (optional)priority)");
|
|
}
|
|
} else {
|
|
// just call regular runscript
|
|
auto const runScript = reinterpret_cast<LuaScriptT >(Offsets::Script_RunScript);
|
|
return runScript(luaState);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool RunQueuedScript(int priority) {
|
|
if (gScriptQueued && gScriptPriority == priority) {
|
|
auto currentTime = GetTime();
|
|
|
|
auto effectiveCastEndMs = EffectiveCastEndMs();
|
|
// get max of cooldown and gcd
|
|
auto delay = effectiveCastEndMs > gCastData.gcdEndMs ? effectiveCastEndMs : gCastData.gcdEndMs;
|
|
|
|
if (delay <= currentTime) {
|
|
DEBUG_LOG("Running queued script priority " << gScriptPriority << ": " << queuedScript);
|
|
LuaCall(queuedScript);
|
|
gScriptQueued = false;
|
|
gScriptPriority = 1;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
uint32_t Script_GetItemStats(uintptr_t *luaState) {
|
|
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
|
|
|
|
if (!lua_isnumber(luaState, 1)) {
|
|
lua_error(luaState, "Usage: GetItemStats(itemId, [copy])");
|
|
return 0;
|
|
}
|
|
|
|
uint32_t itemId = static_cast<uint32_t>(lua_tonumber(luaState, 1));
|
|
|
|
// Check for optional copy parameter
|
|
bool useCopy = false;
|
|
if (lua_isnumber(luaState, 2)) {
|
|
useCopy = static_cast<int>(lua_tonumber(luaState, 2)) != 0;
|
|
}
|
|
|
|
// Get from cache
|
|
game::ItemStats_C *item = GetItemStats(itemId);
|
|
if (!item) {
|
|
lua_pushnil(luaState);
|
|
return 1;
|
|
}
|
|
|
|
// Create new table or get reusable table based on copy parameter
|
|
if (useCopy) {
|
|
lua_newtable(luaState);
|
|
} else {
|
|
GetTableRef(luaState, itemStatsTableRef);
|
|
}
|
|
|
|
// Push all simple fields using descriptors
|
|
PushFieldsToLua(luaState, item, itemStatsFields, itemStatsFieldsCount);
|
|
|
|
// Push string fields manually (require language)
|
|
auto const language = *reinterpret_cast<uint32_t *>(Offsets::Language);
|
|
PushTableValue(luaState, const_cast<char *>("displayName"),
|
|
item->m_displayName[language] ? item->m_displayName[language] : const_cast<char *>(""));
|
|
PushTableValue(luaState, const_cast<char *>("description"),
|
|
item->m_description ? item->m_description : const_cast<char *>(""));
|
|
|
|
// Push all array fields using descriptors with or without references based on copy parameter
|
|
if (useCopy) {
|
|
PushArrayFieldsToLua(luaState, item, itemStatsArrayFields, itemStatsArrayFieldsCount);
|
|
} else {
|
|
PushArrayFieldsToLuaWithRefs(luaState, item, itemStatsArrayFields, itemStatsArrayFieldsCount, itemStatsNestedArrayRefs);
|
|
}
|
|
|
|
return 1; // Return the table
|
|
}
|
|
|
|
uint32_t Script_GetItemStatsField(uintptr_t *luaState) {
|
|
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
|
|
|
|
if (!lua_isnumber(luaState, 1) || !lua_isstring(luaState, 2)) {
|
|
lua_error(luaState, "Usage: GetItemStatsField(itemId, fieldName, [copy])");
|
|
return 0;
|
|
}
|
|
|
|
InitializeFieldMaps();
|
|
|
|
uint32_t itemId = static_cast<uint32_t>(lua_tonumber(luaState, 1));
|
|
const char *fieldName = lua_tostring(luaState, 2);
|
|
|
|
// Check for optional copy parameter
|
|
bool useCopy = false;
|
|
if (lua_isnumber(luaState, 3)) {
|
|
useCopy = static_cast<int>(lua_tonumber(luaState, 3)) != 0;
|
|
}
|
|
|
|
// Get from cache
|
|
game::ItemStats_C *item = GetItemStats(itemId);
|
|
if (!item) {
|
|
lua_pushnil(luaState);
|
|
return 1;
|
|
}
|
|
|
|
// O(1) hash table lookup for simple fields
|
|
auto simpleIt = itemStatsFieldMap.find(fieldName);
|
|
if (simpleIt != itemStatsFieldMap.end()) {
|
|
size_t i = simpleIt->second;
|
|
const char *fieldPtr = reinterpret_cast<const char *>(item) + itemStatsFields[i].offset;
|
|
|
|
switch (itemStatsFields[i].type) {
|
|
case FieldType::INT32:
|
|
lua_pushnumber(luaState, *reinterpret_cast<const int32_t *>(fieldPtr));
|
|
return 1;
|
|
case FieldType::UINT32:
|
|
lua_pushnumber(luaState, *reinterpret_cast<const uint32_t *>(fieldPtr));
|
|
return 1;
|
|
case FieldType::UINT8:
|
|
lua_pushnumber(luaState, *reinterpret_cast<const uint8_t *>(fieldPtr));
|
|
return 1;
|
|
case FieldType::FLOAT:
|
|
lua_pushnumber(luaState, *reinterpret_cast<const float *>(fieldPtr));
|
|
return 1;
|
|
case FieldType::STRING: {
|
|
const char *str = *reinterpret_cast<const char *const *>(fieldPtr);
|
|
lua_pushstring(luaState, str ? const_cast<char *>(str) : const_cast<char *>(""));
|
|
return 1;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// O(1) hash table lookup for array fields
|
|
auto arrayIt = itemStatsArrayFieldMap.find(fieldName);
|
|
if (arrayIt != itemStatsArrayFieldMap.end()) {
|
|
size_t i = arrayIt->second;
|
|
const auto &field = itemStatsArrayFields[i];
|
|
|
|
// Create new table or get reusable table based on copy parameter
|
|
if (useCopy) {
|
|
lua_newtable(luaState);
|
|
} else {
|
|
// Get or create reusable table for this specific field name
|
|
GetTableRef(luaState, itemStatsArrayFieldRefs[fieldName]);
|
|
}
|
|
|
|
const char *fieldPtr = reinterpret_cast<const char *>(item) + field.offset;
|
|
|
|
for (size_t j = 0; j < field.count; ++j) {
|
|
lua_pushnumber(luaState, j + 1);
|
|
|
|
switch (field.type) {
|
|
case FieldType::INT32:
|
|
lua_pushnumber(luaState, reinterpret_cast<const int32_t *>(fieldPtr)[j]);
|
|
break;
|
|
case FieldType::UINT32:
|
|
lua_pushnumber(luaState, reinterpret_cast<const uint32_t *>(fieldPtr)[j]);
|
|
break;
|
|
case FieldType::UINT8:
|
|
lua_pushnumber(luaState, reinterpret_cast<const uint8_t *>(fieldPtr)[j]);
|
|
break;
|
|
case FieldType::FLOAT:
|
|
lua_pushnumber(luaState, reinterpret_cast<const float *>(fieldPtr)[j]);
|
|
break;
|
|
default:
|
|
lua_pushnumber(luaState, 0);
|
|
break;
|
|
}
|
|
lua_settable(luaState, -3);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// Check for special string fields
|
|
if (strcmp(fieldName, "displayName") == 0) {
|
|
auto const language = *reinterpret_cast<uint32_t *>(Offsets::Language);
|
|
lua_pushstring(luaState,
|
|
item->m_displayName[language] ? item->m_displayName[language] : const_cast<char *>(""));
|
|
return 1;
|
|
}
|
|
if (strcmp(fieldName, "description") == 0) {
|
|
lua_pushstring(luaState, item->m_description ? item->m_description : const_cast<char *>(""));
|
|
return 1;
|
|
}
|
|
|
|
// Field not found
|
|
lua_error(luaState, "Unknown field name");
|
|
return 0;
|
|
}
|
|
|
|
// won't do anything unless you uncomment line in process queues
|
|
uint32_t Script_StartItemExport(uintptr_t *luaState) {
|
|
StartItemExport();
|
|
return 0;
|
|
}
|
|
|
|
uint32_t Script_GetUnitData(uintptr_t *luaState) {
|
|
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
|
|
|
|
if (!lua_isstring(luaState, 1)) {
|
|
lua_error(luaState, "Usage: GetUnitData(unitToken, [copy]) - unitToken can be 'player', 'target', 'pet', etc., or a GUID string");
|
|
return 0;
|
|
}
|
|
|
|
const char *unitToken = lua_tostring(luaState, 1);
|
|
|
|
// Check for optional copy parameter
|
|
bool useCopy = false;
|
|
if (lua_isnumber(luaState, 2)) {
|
|
useCopy = static_cast<int>(lua_tonumber(luaState, 2)) != 0;
|
|
}
|
|
|
|
uint64_t guid = GetUnitGuidFromString(unitToken);
|
|
|
|
if (guid == 0) {
|
|
lua_pushnil(luaState);
|
|
return 1;
|
|
}
|
|
|
|
// Get unit object pointer
|
|
auto unit = game::GetObjectPtr(guid);
|
|
if (!unit) {
|
|
lua_pushnil(luaState);
|
|
return 1;
|
|
}
|
|
|
|
// Get unit fields (offset 68 from unit pointer)
|
|
auto *unitFields = *reinterpret_cast<game::UnitFields **>(unit + 68);
|
|
if (!unitFields) {
|
|
lua_pushnil(luaState);
|
|
return 1;
|
|
}
|
|
|
|
// Create new table or get reusable table based on copy parameter
|
|
if (useCopy) {
|
|
lua_newtable(luaState);
|
|
} else {
|
|
GetTableRef(luaState, unitDataTableRef);
|
|
}
|
|
|
|
// Push all simple fields using descriptors
|
|
PushFieldsToLua(luaState, unitFields, unitFieldsFields, unitFieldsFieldsCount);
|
|
|
|
// Push all array fields using descriptors with or without references based on copy parameter
|
|
if (useCopy) {
|
|
PushArrayFieldsToLua(luaState, unitFields, unitFieldsArrayFields, unitFieldsArrayFieldsCount);
|
|
} else {
|
|
PushArrayFieldsToLuaWithRefs(luaState, unitFields, unitFieldsArrayFields, unitFieldsArrayFieldsCount, unitFieldsNestedArrayRefs);
|
|
}
|
|
|
|
return 1; // Return the table
|
|
}
|
|
|
|
uint32_t Script_GetUnitField(uintptr_t *luaState) {
|
|
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
|
|
|
|
if (!lua_isstring(luaState, 1) || !lua_isstring(luaState, 2)) {
|
|
lua_error(luaState, "Usage: GetUnitField(unitToken, fieldName, [copy]) - unitToken can be 'player', 'target', 'pet', etc., or a GUID string");
|
|
return 0;
|
|
}
|
|
|
|
InitializeUnitFieldMaps();
|
|
|
|
const char *unitToken = lua_tostring(luaState, 1);
|
|
const char *fieldName = lua_tostring(luaState, 2);
|
|
|
|
// Check for optional copy parameter
|
|
bool useCopy = false;
|
|
if (lua_isnumber(luaState, 3)) {
|
|
useCopy = static_cast<int>(lua_tonumber(luaState, 3)) != 0;
|
|
}
|
|
|
|
uint64_t guid = GetUnitGuidFromString(unitToken);
|
|
|
|
if (guid == 0) {
|
|
lua_pushnil(luaState);
|
|
return 1;
|
|
}
|
|
|
|
// Get unit object pointer
|
|
auto unit = game::GetObjectPtr(guid);
|
|
if (!unit) {
|
|
lua_pushnil(luaState);
|
|
return 1;
|
|
}
|
|
|
|
// Get unit fields (offset 68 from unit pointer)
|
|
auto *unitFields = *reinterpret_cast<game::UnitFields **>(unit + 68);
|
|
if (!unitFields) {
|
|
lua_pushnil(luaState);
|
|
return 1;
|
|
}
|
|
|
|
// O(1) hash table lookup for simple fields
|
|
auto simpleIt = unitFieldsFieldMap.find(fieldName);
|
|
if (simpleIt != unitFieldsFieldMap.end()) {
|
|
size_t i = simpleIt->second;
|
|
const char *fieldPtr = reinterpret_cast<const char *>(unitFields) + unitFieldsFields[i].offset;
|
|
|
|
switch (unitFieldsFields[i].type) {
|
|
case FieldType::UINT32:
|
|
lua_pushnumber(luaState, *reinterpret_cast<const uint32_t *>(fieldPtr));
|
|
return 1;
|
|
case FieldType::UINT8:
|
|
lua_pushnumber(luaState, *reinterpret_cast<const uint8_t *>(fieldPtr));
|
|
return 1;
|
|
case FieldType::UINT64:
|
|
lua_pushnumber(luaState, static_cast<double>(*reinterpret_cast<const uint64_t *>(fieldPtr)));
|
|
return 1;
|
|
case FieldType::FLOAT:
|
|
lua_pushnumber(luaState, *reinterpret_cast<const float *>(fieldPtr));
|
|
return 1;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// O(1) hash table lookup for array fields
|
|
auto arrayIt = unitFieldsArrayFieldMap.find(fieldName);
|
|
if (arrayIt != unitFieldsArrayFieldMap.end()) {
|
|
size_t i = arrayIt->second;
|
|
const auto &field = unitFieldsArrayFields[i];
|
|
|
|
// Create new table or get reusable table based on copy parameter
|
|
if (useCopy) {
|
|
lua_newtable(luaState);
|
|
} else {
|
|
// Get or create reusable table for this specific field name
|
|
GetTableRef(luaState, unitFieldsArrayFieldRefs[fieldName]);
|
|
}
|
|
|
|
const char *fieldPtr = reinterpret_cast<const char *>(unitFields) + field.offset;
|
|
|
|
for (size_t j = 0; j < field.count; ++j) {
|
|
lua_pushnumber(luaState, j + 1);
|
|
|
|
switch (field.type) {
|
|
case FieldType::UINT32:
|
|
lua_pushnumber(luaState, reinterpret_cast<const uint32_t *>(fieldPtr)[j]);
|
|
break;
|
|
case FieldType::UINT8:
|
|
lua_pushnumber(luaState, reinterpret_cast<const uint8_t *>(fieldPtr)[j]);
|
|
break;
|
|
case FieldType::FLOAT:
|
|
lua_pushnumber(luaState, reinterpret_cast<const float *>(fieldPtr)[j]);
|
|
break;
|
|
default:
|
|
lua_pushnumber(luaState, 0);
|
|
break;
|
|
}
|
|
lua_settable(luaState, -3);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// Field not found
|
|
lua_error(luaState, "Unknown field name");
|
|
return 0;
|
|
}
|
|
|
|
bool TryDisenchant() {
|
|
DEBUG_LOG("Trying disenchant with quality filter " << gDisenchantQuality << " itemId " << gDisenchantItemId);
|
|
if (gDisenchantQuality == 0 && gDisenchantItemId == 0) {
|
|
ResetDisenchantState();
|
|
return false;
|
|
}
|
|
|
|
// Find the item in player inventory
|
|
PlayerItemSearchResult itemSearchResult;
|
|
if (gDisenchantItemId != 0) {
|
|
// Item-specific disenchanting
|
|
itemSearchResult = FindPlayerItem(gDisenchantItemId, nullptr);
|
|
} else {
|
|
// Quality-based disenchanting (weapons and armor only)
|
|
itemSearchResult = FindPlayerDisenchantItem(gDisenchantQuality, gDisenchantIncludeSoulbound);
|
|
}
|
|
|
|
if (!itemSearchResult.found()) {
|
|
// Item not found, stop disenchanting
|
|
DEBUG_LOG("No disenchantable item found, stopping disenchant");
|
|
LuaCall("DEFAULT_CHAT_FRAME:AddMessage(\"No more items to disenchant.\")");
|
|
ResetDisenchantState();
|
|
return false;
|
|
}
|
|
|
|
// Get the item GUID
|
|
auto item = itemSearchResult.item;
|
|
if (!item || !item->object.m_obj) {
|
|
// Invalid item, stop disenchanting
|
|
DEBUG_LOG("Invalid item, stopping disenchant");
|
|
ResetDisenchantState();
|
|
return false;
|
|
}
|
|
uint64_t itemGuid = item->object.m_obj->m_guid;
|
|
|
|
// Get player unit for casting
|
|
auto playerGuid = game::ClntObjMgrGetActivePlayerGuid();
|
|
auto playerUnit = game::GetObjectPtr(playerGuid);
|
|
|
|
if (!playerUnit) {
|
|
// No player unit, stop disenchanting
|
|
DEBUG_LOG("No player unit, stopping disenchant");
|
|
ResetDisenchantState();
|
|
return false;
|
|
}
|
|
|
|
// Display which item is being disenchanted
|
|
char luaCode[256];
|
|
snprintf(luaCode, sizeof(luaCode),
|
|
"local itemLink = GetContainerItemLink(%d, %d); "
|
|
"if itemLink then DEFAULT_CHAT_FRAME:AddMessage(\"Disenchanting \" .. itemLink .. \" move during cast to cancel.\") end",
|
|
itemSearchResult.bagIndex, itemSearchResult.slot + 1); // Lua slots are 1-indexed
|
|
LuaCall(luaCode);
|
|
|
|
// Set timer for next disenchant attempt (5 seconds to allow time for inventory to update)
|
|
auto currentTime = GetTime();
|
|
gNextDisenchantTimeMs = currentTime + 5000;
|
|
|
|
// Cast Disenchant spell (13262) on the item
|
|
auto const castSpell = reinterpret_cast<Spell_C_CastSpellT>(Offsets::Spell_C_CastSpell);
|
|
bool success = castSpell(playerUnit, 13262, nullptr, itemGuid);
|
|
|
|
if (!success) {
|
|
// Cast failed, stop disenchanting
|
|
DEBUG_LOG("Cast failed, stopping disenchant");
|
|
LuaCall("DEFAULT_CHAT_FRAME:AddMessage(\"Disenchant interrupted or failed.\")");
|
|
ResetDisenchantState();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
uint32_t Script_DisenchantAll(uintptr_t *luaState) {
|
|
luaState = GetLuaStatePtr();
|
|
|
|
if (!lua_isnumber(luaState, 1) && !lua_isstring(luaState, 1)) {
|
|
lua_error(luaState, "Usage: DisenchantAll(itemIdOrName, [includeSoulbound]) or DisenchantAll(quality, [includeSoulbound])\n"
|
|
"quality: \"greens\", \"blues\", \"purples\", or combinations like \"greens|blues\" or \"greens|blues|purples\"\n"
|
|
"itemIdOrName: item ID (number) or item name (string)\n"
|
|
"includeSoulbound: optional number - pass 1 to include soulbound items (defaults to 0)");
|
|
return 0;
|
|
}
|
|
|
|
// Check if player has Disenchant spell (13262)
|
|
constexpr uint32_t DISENCHANT_SPELL_ID = 13262;
|
|
bool hasDisenchant = false;
|
|
for (uint32_t slot = 0; slot < 1024; slot++) {
|
|
uint32_t spellId = *reinterpret_cast<uint32_t *>(static_cast<uint32_t>(Offsets::CGSpellBook_mKnownSpells) + slot * 4);
|
|
if (spellId == DISENCHANT_SPELL_ID) {
|
|
hasDisenchant = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!hasDisenchant) {
|
|
lua_error(luaState, "You do not have the Disenchant spell");
|
|
return 0;
|
|
}
|
|
|
|
// Reset all modes
|
|
ResetDisenchantState();
|
|
|
|
// Check number first (lua_isstring will also match numbers)
|
|
if (lua_isnumber(luaState, 1)) {
|
|
// It's an item ID
|
|
gDisenchantItemId = static_cast<uint32_t>(lua_tonumber(luaState, 1));
|
|
} else if (lua_isstring(luaState, 1)) {
|
|
// It's a string - check if it's quality-based or item name
|
|
const char *param = lua_tostring(luaState, 1);
|
|
|
|
// Parse quality combinations (e.g., "greens", "blues|purples", "greens|blues|purples")
|
|
bool isQualityString = false;
|
|
uint32_t qualityBitmask = 0;
|
|
|
|
// Make a mutable copy of the string for tokenizing
|
|
std::string paramStr(param);
|
|
size_t start = 0;
|
|
size_t end = 0;
|
|
|
|
while (end != std::string::npos) {
|
|
end = paramStr.find('|', start);
|
|
std::string token = paramStr.substr(start, (end == std::string::npos) ? std::string::npos : end - start);
|
|
|
|
if (_stricmp(token.c_str(), "greens") == 0) {
|
|
qualityBitmask |= DISENCHANT_QUALITY_GREEN;
|
|
isQualityString = true;
|
|
} else if (_stricmp(token.c_str(), "blues") == 0) {
|
|
qualityBitmask |= DISENCHANT_QUALITY_BLUE;
|
|
isQualityString = true;
|
|
} else if (_stricmp(token.c_str(), "purples") == 0) {
|
|
qualityBitmask |= DISENCHANT_QUALITY_PURPLE;
|
|
isQualityString = true;
|
|
}
|
|
|
|
start = (end == std::string::npos) ? std::string::npos : end + 1;
|
|
}
|
|
|
|
if (isQualityString) {
|
|
gDisenchantQuality = qualityBitmask;
|
|
} else {
|
|
// It's an item name
|
|
uint32_t cachedItemId = GetItemIdFromCache(param);
|
|
if (cachedItemId != 0) {
|
|
gDisenchantItemId = cachedItemId;
|
|
} else {
|
|
// Try to find the item to get its ID
|
|
auto itemSearchResult = FindPlayerItem(0, param);
|
|
if (itemSearchResult.found()) {
|
|
gDisenchantItemId = game::GetItemId(itemSearchResult.item);
|
|
// Cache the item name for future lookups
|
|
CacheItemNameToId(param, gDisenchantItemId);
|
|
} else {
|
|
// Item not found
|
|
lua_pushnumber(luaState, 0);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse optional includeSoulbound parameter (defaults to false)
|
|
// Any non-zero integer enables it
|
|
if (lua_isnumber(luaState, 2)) {
|
|
gDisenchantIncludeSoulbound = (lua_tonumber(luaState, 2) != 0);
|
|
}
|
|
|
|
// Try the first disenchant
|
|
bool success = TryDisenchant();
|
|
|
|
lua_pushnumber(luaState, success ? 1 : 0);
|
|
return 1;
|
|
}
|
|
|
|
uint32_t Script_GetItemIconTexture(uintptr_t *luaState) {
|
|
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
|
|
|
|
if (!lua_isnumber(luaState, 1)) {
|
|
lua_error(luaState, "Usage: GetItemIconTexture(displayInfoId)");
|
|
return 0;
|
|
}
|
|
|
|
uint32_t displayInfoId = static_cast<uint32_t>(lua_tonumber(luaState, 1));
|
|
|
|
auto const getInventoryArt = reinterpret_cast<GetInventoryArtT>(Offsets::CGItem_C_GetInventoryArt);
|
|
char *texturePath = getInventoryArt(displayInfoId);
|
|
|
|
if (texturePath && texturePath[0] != '\0' && !strstr(texturePath, "QuestionMark")) {
|
|
lua_pushstring(luaState, texturePath);
|
|
} else {
|
|
lua_pushnil(luaState);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
uint32_t Script_GetSpellIconTexture(uintptr_t *luaState) {
|
|
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
|
|
|
|
if (!lua_isnumber(luaState, 1)) {
|
|
lua_error(luaState, "Usage: GetSpellIconTexture(spellIconId)");
|
|
return 0;
|
|
}
|
|
|
|
int32_t iconId = static_cast<int32_t>(lua_tonumber(luaState, 1));
|
|
|
|
int32_t maxIconId = *reinterpret_cast<int32_t *>(Offsets::SpellIconDBMaxId);
|
|
uintptr_t *iconArray = *reinterpret_cast<uintptr_t **>(Offsets::SpellIconDBArray);
|
|
|
|
if (iconId < 0 || iconId > maxIconId) {
|
|
lua_pushnil(luaState);
|
|
return 1;
|
|
}
|
|
|
|
uintptr_t iconEntry = reinterpret_cast<uintptr_t *>(iconArray)[iconId];
|
|
if (iconEntry == 0) {
|
|
lua_pushnil(luaState);
|
|
return 1;
|
|
}
|
|
|
|
char *texturePath = *reinterpret_cast<char **>(iconEntry + 4);
|
|
if (texturePath && texturePath[0] != '\0' && !strstr(texturePath, "QuestionMark")) {
|
|
lua_pushstring(luaState, texturePath);
|
|
} else {
|
|
lua_pushnil(luaState);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
uint32_t Script_GetPlayerAuraDuration(uintptr_t *luaState) {
|
|
luaState = GetLuaStatePtr();
|
|
|
|
if (!lua_isnumber(luaState, 1)) {
|
|
lua_error(luaState, "Usage: GetPlayerAuraDuration(auraSlot)");
|
|
return 0;
|
|
}
|
|
|
|
auto auraSlot = static_cast<uint32_t>(lua_tonumber(luaState, 1));
|
|
if (auraSlot >= MAX_AURA_SLOTS) {
|
|
lua_pushnil(luaState);
|
|
return 1;
|
|
}
|
|
|
|
// Get spellId from player unit fields
|
|
auto playerGuid = game::ClntObjMgrGetActivePlayerGuid();
|
|
auto *playerUnit = game::GetObjectPtr(playerGuid);
|
|
if (!playerUnit) {
|
|
lua_pushnil(luaState);
|
|
return 1;
|
|
}
|
|
|
|
auto *unitFields = *reinterpret_cast<game::UnitFields **>(playerUnit + 68);
|
|
uint32_t spellId = unitFields->aura[auraSlot];
|
|
|
|
uint32_t expirationTime = gAuraExpirationTime[auraSlot];
|
|
auto now = static_cast<uint32_t>(GetWowTimeMs() & 0xFFFFFFFF);
|
|
int remainingDurationMs = expirationTime > now ? static_cast<int>(expirationTime - now) : 0;
|
|
|
|
lua_pushnumber(luaState, spellId);
|
|
lua_pushnumber(luaState, remainingDurationMs);
|
|
lua_pushnumber(luaState, expirationTime);
|
|
return 3;
|
|
}
|
|
|
|
uint32_t CSimpleFrame_GetNameHook(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) {
|
|
luaState = GetLuaStatePtr();
|
|
|
|
// Type ID lazy initialization (mirrors original at 0x00CF0C3C / 0x00CEEF6C)
|
|
static auto typeIdCounter = reinterpret_cast<uint32_t *>(0x00CEEF6C);
|
|
static auto nameplateTypeId = reinterpret_cast<uint32_t *>(0x00CF0C3C);
|
|
|
|
if (*nameplateTypeId == 0) {
|
|
*typeIdCounter = *typeIdCounter + 1;
|
|
*nameplateTypeId = *typeIdCounter;
|
|
}
|
|
uint32_t typeId = *nameplateTypeId;
|
|
|
|
uintptr_t *frameObj = nullptr;
|
|
|
|
int luaArgType = lua_type(luaState, 1);
|
|
if (luaArgType == LUA_TTABLE) {
|
|
lua_rawgeti(luaState, 1, 0);
|
|
|
|
frameObj = reinterpret_cast<uintptr_t *>(lua_touserdata(luaState, -1));
|
|
lua_settop(luaState, -2);
|
|
|
|
if (frameObj == nullptr) {
|
|
lua_error(luaState, "Attempt to find 'this' in non-frame table");
|
|
} else {
|
|
// Type check via vtable[4] (offset 0x10)
|
|
using IsTypeT = char (__thiscall *)(uintptr_t *thisPtr, uint32_t typeId);
|
|
auto vtable = reinterpret_cast<uintptr_t *>(*frameObj);
|
|
auto isType = reinterpret_cast<IsTypeT>(vtable[4]);
|
|
if (isType(frameObj, typeId) == 0) {
|
|
lua_error(luaState, "Wrong object type for member function");
|
|
}
|
|
}
|
|
} else {
|
|
lua_error(luaState, "Attempt to find 'this' in non-table value");
|
|
}
|
|
|
|
// Check if second arg is passed (e.g. nameplate:GetName(1) to get GUID)
|
|
if (lua_isnumber(luaState, 2) && static_cast<int>(lua_tonumber(luaState, 2)) != 0) {
|
|
// Return GUID string from frame object at offset 0x4E8/0x4EC
|
|
uint32_t guidLow = frameObj[0x13a];
|
|
uint32_t guidHigh = frameObj[0x13b];
|
|
uint64_t guid = (static_cast<uint64_t>(guidHigh) << 32) | guidLow;
|
|
|
|
char guidStr[21] = {0};
|
|
std::snprintf(guidStr, sizeof(guidStr), "0x%016llX", static_cast<unsigned long long>(guid));
|
|
lua_pushstring(luaState, guidStr);
|
|
} else {
|
|
// GetName via vtable[1] (offset 0x4)
|
|
using GetNameT = char *(__thiscall *)(uintptr_t *thisPtr);
|
|
auto vtable = reinterpret_cast<uintptr_t *>(*frameObj);
|
|
auto getName = reinterpret_cast<GetNameT>(vtable[1]);
|
|
char *name = getName(frameObj);
|
|
|
|
if (name != nullptr && name[0] != '\0') {
|
|
lua_pushstring(luaState, name);
|
|
} else {
|
|
lua_pushnil(luaState);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
}
|