fix guids not being string, add LearnTalentRank

This commit is contained in:
avitasia
2026-02-12 16:10:07 -08:00
parent c094fdd5e8
commit f4a4ce5bb4
9 changed files with 12308 additions and 12067 deletions
+7
View File
@@ -153,8 +153,15 @@ This includes functions for:
- Cooldown tracking (GetSpellIdCooldown, GetItemIdCooldown), including item metadata on cooldown detail tables - Cooldown tracking (GetSpellIdCooldown, GetItemIdCooldown), including item metadata on cooldown detail tables
- Inventory helpers (GetTrinketCooldown, GetTrinkets, GetAmmo) - Inventory helpers (GetTrinketCooldown, GetTrinkets, GetAmmo)
- Aura duration tracking and cancel helpers (GetPlayerAuraDuration, CancelPlayerAuraSlot, CancelPlayerAuraSpellId) - Aura duration tracking and cancel helpers (GetPlayerAuraDuration, CancelPlayerAuraSlot, CancelPlayerAuraSpellId)
- Talent helpers (LearnTalentRank)
- Spell lookups and utilities - Spell lookups and utilities
`LearnTalentRank(talentPage, talentIndex, rank)` learns a specific talent rank directly by tab/index.
Valid ranges are: `talentPage` = `1-3`, `talentIndex` = `1-32`, `rank` = `1-5`.
For unit data APIs, object-reference GUID fields are returned as strings (hex format) rather than numbers to avoid Lua 64-bit precision issues.
This applies to both `GetUnitData` and `GetUnitField` for fields such as: `charm`, `summon`, `charmedBy`, `summonedBy`, `createdBy`, `target`, `persuaded`, and `channelObject`.
`CancelPlayerAuraSpellId(spellId, [ignoreMissing])` supports an optional second parameter where `1` skips the aura-slot presence check (useful for buff-capped cases) and `0`/omitted keeps the default check. `CancelPlayerAuraSpellId(spellId, [ignoreMissing])` supports an optional second parameter where `1` skips the aura-slot presence check (useful for buff-capped cases) and `0`/omitted keeps the default check.
Cooldown detail tables now also expose `itemId`, `itemHasActiveSpell`, and `itemActiveSpellId` alongside the existing per-category timing data. Cooldown detail tables now also expose `itemId`, `itemHasActiveSpell`, and `itemActiveSpellId` alongside the existing per-category timing data.
+1 -1
View File
@@ -23,7 +23,7 @@ local health = GetUnitField("target", "health")
These fields return a single value (number). These fields return a single value (number).
### Object References (UINT64) ### Object References (UINT64 0x... string in lua)
These fields contain GUIDs (globally unique identifiers) for game objects: These fields contain GUIDs (globally unique identifiers) for game objects:
- **charm** - GUID of the unit that has charmed this unit - **charm** - GUID of the unit that has charmed this unit
+12086 -12061
View File
File diff suppressed because it is too large Load Diff
+57
View File
@@ -292,6 +292,63 @@ namespace Nampower {
return guidStr; return guidStr;
} }
void PushGuidString(uintptr_t *luaState, uint64_t guid) {
char guidStr[21] = {0};
std::snprintf(guidStr, sizeof(guidStr), "0x%016llX", static_cast<unsigned long long>(guid));
lua_pushstring(luaState, guidStr);
}
bool IsReadableMemory(const void *ptr, size_t size) {
if (!ptr || size == 0) {
return false;
}
auto current = reinterpret_cast<uintptr_t>(ptr);
auto remaining = size;
while (remaining > 0) {
MEMORY_BASIC_INFORMATION mbi = {};
if (VirtualQuery(reinterpret_cast<LPCVOID>(current), &mbi, sizeof(mbi)) == 0) {
return false;
}
if (mbi.State != MEM_COMMIT) {
return false;
}
if ((mbi.Protect & (PAGE_NOACCESS | PAGE_GUARD)) != 0) {
return false;
}
auto regionEnd = reinterpret_cast<uintptr_t>(mbi.BaseAddress) + mbi.RegionSize;
if (regionEnd <= current) {
return false;
}
auto chunk = static_cast<size_t>(regionEnd - current);
if (chunk > remaining) {
chunk = remaining;
}
remaining -= chunk;
current += chunk;
}
return true;
}
bool SafeReadMemory(const void *address, void *out, size_t size) {
if (!out || size == 0) {
return false;
}
if (!IsReadableMemory(address, size)) {
return false;
}
std::memcpy(out, address, size);
return true;
}
uint64_t GetUnitGuidFromString(const char *unitToken) { uint64_t GetUnitGuidFromString(const char *unitToken) {
if (!unitToken) { if (!unitToken) {
return 0; return 0;
+9 -1
View File
@@ -71,6 +71,14 @@ namespace Nampower {
bool IsSpellOnCooldown(uint32_t spellId); bool IsSpellOnCooldown(uint32_t spellId);
char *ConvertGuidToString(uint64_t guid); char *ConvertGuidToString(uint64_t guid);
void PushGuidString(uintptr_t *luaState, uint64_t guid);
bool IsReadableMemory(const void *ptr, size_t size);
bool SafeReadMemory(const void *address, void *out, size_t size);
template <typename T>
inline bool SafeRead(const void *address, T &out) {
return SafeReadMemory(address, &out, sizeof(T));
}
uint64_t GetUnitGuidFromLuaParam(uintptr_t *luaState, int paramIndex); uint64_t GetUnitGuidFromLuaParam(uintptr_t *luaState, int paramIndex);
@@ -116,4 +124,4 @@ namespace Nampower {
lua_pushstring(luaState, const_cast<char *>(value)); lua_pushstring(luaState, const_cast<char *>(value));
lua_settable(luaState, -3); lua_settable(luaState, -3);
} }
} }
+3
View File
@@ -1698,6 +1698,9 @@ namespace Nampower {
char getSpellPower[] = "GetSpellPower"; char getSpellPower[] = "GetSpellPower";
RegisterLuaFunction(getSpellPower, reinterpret_cast<uintptr_t *>(Script_GetSpellPower)); RegisterLuaFunction(getSpellPower, reinterpret_cast<uintptr_t *>(Script_GetSpellPower));
char learnTalentRank[] = "LearnTalentRank";
RegisterLuaFunction(learnTalentRank, reinterpret_cast<uintptr_t *>(Script_LearnTalentRank));
} }
void load() { void load() {
+1 -1
View File
@@ -25,7 +25,7 @@ namespace Nampower {
constexpr uint32_t DISENCHANT_QUALITY_PURPLE = 0x04; // Epic constexpr uint32_t DISENCHANT_QUALITY_PURPLE = 0x04; // Epic
constexpr uint32_t MAJOR_VERSION = 2; constexpr uint32_t MAJOR_VERSION = 2;
constexpr uint32_t MINOR_VERSION = 34; constexpr uint32_t MINOR_VERSION = 35;
constexpr uint32_t PATCH_VERSION = 0; constexpr uint32_t PATCH_VERSION = 0;
constexpr int32_t LUA_REGISTRYINDEX = -10000; constexpr int32_t LUA_REGISTRYINDEX = -10000;
+143 -3
View File
@@ -259,6 +259,121 @@ namespace Nampower {
return 0; return 0;
} }
uint32_t Script_LearnTalentRank(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
if (!lua_isnumber(luaState, 1) || !lua_isnumber(luaState, 2) || !lua_isnumber(luaState, 3)) {
lua_error(luaState, "Usage: LearnTalentRank(talentPage, talentIndex, rank)");
return 0;
}
constexpr uintptr_t CGTalentInfo_m_talentTabs = 0x00BDCD24;
constexpr uintptr_t CGTalentInfo_m_talents = 0x00BDCD28;
constexpr uint32_t CMSG_LEARN_TALENT = 0x0251;
auto talentPage = static_cast<int32_t>(lua_tonumber(luaState, 1));
auto talentIndex = static_cast<int32_t>(lua_tonumber(luaState, 2));
auto requestedRank = static_cast<int32_t>(lua_tonumber(luaState, 3));
if (talentPage < 1 || talentPage > 3) {
lua_error(luaState, "LearnTalentRank: talentPage must be in range 1-3");
return 0;
}
if (talentIndex < 1 || talentIndex > 32) {
lua_error(luaState, "LearnTalentRank: talentIndex must be in range 1-32");
return 0;
}
if (requestedRank < 1 || requestedRank > 5) {
lua_error(luaState, "LearnTalentRank: rank must be in range 1-5");
return 0;
}
auto const playerGuid = game::ClntObjMgrGetActivePlayerGuid();
auto *playerUnit = game::ClntObjMgrObjectPtr(game::TYPEMASK_PLAYER, playerGuid);
if (!playerUnit) {
lua_error(luaState, "LearnTalentRank: active player not available");
return 0;
}
auto talentPage0Index = talentPage - 1;
auto talentIndex0 = talentIndex - 1;
int32_t talentTabCount = 0;
if (!SafeRead(reinterpret_cast<void *>(CGTalentInfo_m_talentTabs), talentTabCount)) {
lua_error(luaState, "LearnTalentRank: unable to read talent tab count");
return 0;
}
if (talentPage0Index < 0 || talentPage0Index >= talentTabCount || talentIndex0 < 0) {
lua_error(luaState, "LearnTalentRank: talent page/index out of bounds");
return 0;
}
uintptr_t talentsBase = 0;
if (!SafeRead(reinterpret_cast<void *>(CGTalentInfo_m_talents), talentsBase)) {
lua_error(luaState, "LearnTalentRank: unable to read talents base pointer");
return 0;
}
if (talentsBase == 0) {
lua_error(luaState, "LearnTalentRank: talents base pointer is null");
return 0;
}
auto talentTabSlotAddr = talentsBase + static_cast<uintptr_t>(talentPage0Index) * sizeof(uint32_t);
uintptr_t talentTabPtr = 0;
if (!SafeRead(reinterpret_cast<void *>(talentTabSlotAddr), talentTabPtr)) {
lua_error(luaState, "LearnTalentRank: unable to read talent tab pointer");
return 0;
}
if (talentTabPtr == 0) {
lua_error(luaState, "LearnTalentRank: talent tab pointer is null");
return 0;
}
uint32_t talentCountInTab = 0;
uintptr_t talentListBase = 0;
if (!SafeRead(reinterpret_cast<void *>(talentTabPtr + 0x8), talentCountInTab) ||
!SafeRead(reinterpret_cast<void *>(talentTabPtr + 0xC), talentListBase)) {
lua_error(luaState, "LearnTalentRank: unable to read talent tab metadata");
return 0;
}
if (talentCountInTab == 0 || talentCountInTab >= 512 || talentListBase == 0) {
lua_error(luaState, "LearnTalentRank: invalid talent tab metadata");
return 0;
}
if (talentIndex0 >= static_cast<int32_t>(talentCountInTab)) {
lua_error(luaState, "LearnTalentRank: talent index not present in selected tab");
return 0;
}
auto talentEntryPtr = talentListBase + static_cast<uintptr_t>(talentIndex0) * 0x54;
int32_t talentId = 0;
if (!SafeRead(reinterpret_cast<void *>(talentEntryPtr), talentId)) {
lua_error(luaState, "LearnTalentRank: unable to read talent entry");
return 0;
}
if (talentId <= 0 || talentId >= 200000) {
lua_error(luaState, "LearnTalentRank: resolved invalid talentId");
return 0;
}
auto requestedRank0Index = requestedRank - 1;
auto dataStore = CDataStore();
dataStore.Put<uint32_t>(CMSG_LEARN_TALENT);
dataStore.Put<uint32_t>(static_cast<uint32_t>(talentId));
dataStore.Put<uint32_t>(static_cast<uint32_t>(requestedRank0Index));
dataStore.Finalize();
auto const clientServicesSend = reinterpret_cast<ClientServices_SendT>(Offsets::ClientServices_Send);
clientServicesSend(&dataStore);
lua_pushnumber(luaState, 1);
return 1;
}
bool RunQueuedScript(int priority) { bool RunQueuedScript(int priority) {
if (gScriptQueued && gScriptPriority == priority) { if (gScriptQueued && gScriptPriority == priority) {
auto currentTime = GetTime(); auto currentTime = GetTime();
@@ -492,8 +607,33 @@ namespace Nampower {
GetTableRef(luaState, unitDataTableRef); GetTableRef(luaState, unitDataTableRef);
} }
// Push all simple fields using descriptors // Push all simple fields using descriptors.
PushFieldsToLua(luaState, unitFields, unitFieldsFields, unitFieldsFieldsCount); // UnitFields UINT64 members are GUID/object references and must be Lua strings.
for (size_t i = 0; i < unitFieldsFieldsCount; ++i) {
const auto &field = unitFieldsFields[i];
lua_pushstring(luaState, const_cast<char *>(field.name));
const char *fieldPtr = reinterpret_cast<const char *>(unitFields) + field.offset;
switch (field.type) {
case FieldType::UINT32:
lua_pushnumber(luaState, *reinterpret_cast<const uint32_t *>(fieldPtr));
break;
case FieldType::UINT8:
lua_pushnumber(luaState, *reinterpret_cast<const uint8_t *>(fieldPtr));
break;
case FieldType::FLOAT:
lua_pushnumber(luaState, *reinterpret_cast<const float *>(fieldPtr));
break;
case FieldType::UINT64:
PushGuidString(luaState, *reinterpret_cast<const uint64_t *>(fieldPtr));
break;
default:
lua_pushnil(luaState);
break;
}
lua_settable(luaState, -3);
}
// Push all array fields using descriptors with or without references based on copy parameter // Push all array fields using descriptors with or without references based on copy parameter
if (useCopy) { if (useCopy) {
@@ -559,7 +699,7 @@ namespace Nampower {
lua_pushnumber(luaState, *reinterpret_cast<const uint8_t *>(fieldPtr)); lua_pushnumber(luaState, *reinterpret_cast<const uint8_t *>(fieldPtr));
return 1; return 1;
case FieldType::UINT64: case FieldType::UINT64:
lua_pushnumber(luaState, static_cast<double>(*reinterpret_cast<const uint64_t *>(fieldPtr))); PushGuidString(luaState, *reinterpret_cast<const uint64_t *>(fieldPtr));
return 1; return 1;
case FieldType::FLOAT: case FieldType::FLOAT:
lua_pushnumber(luaState, *reinterpret_cast<const float *>(fieldPtr)); lua_pushnumber(luaState, *reinterpret_cast<const float *>(fieldPtr));
+1
View File
@@ -37,6 +37,7 @@ namespace Nampower {
uint32_t Script_GetItemIconTexture(uintptr_t *luaState); uint32_t Script_GetItemIconTexture(uintptr_t *luaState);
uint32_t Script_GetSpellIconTexture(uintptr_t *luaState); uint32_t Script_GetSpellIconTexture(uintptr_t *luaState);
uint32_t Script_LearnTalentRank(uintptr_t *luaState);
bool RunQueuedScript(int priority); bool RunQueuedScript(int priority);