mirror of
https://github.com/brues-code/nampower.git
synced 2026-09-21 23:26:54 +00:00
fix guids not being string, add LearnTalentRank
This commit is contained in:
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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));
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user