working party member info fallback

This commit is contained in:
avitasia
2026-03-19 21:37:33 -07:00
parent 56d0952cdc
commit bbc029beaf
12 changed files with 14066 additions and 13787 deletions
+2
View File
@@ -211,6 +211,8 @@ 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`.
`GetUnitField` also has a fallback path for party/raid units when live `UnitFields` are unavailable. If the unit object is not loaded, it will try cached `party_member_fields` data instead. The overlapping `UnitFields` names available through this fallback are `health`, `maxHealth`, `level`, and `aura` for party/raid members, plus `displayId`, `health`, `maxHealth`, and `aura` for party/raid pets. `GetUnitData` does not use this fallback and still returns `nil` when the unit object is unavailable instead of returning a sparse object.
`CancelPlayerAuraSpellId(spellId, [ignoreMissing])` supports an optional second parameter where `1` skips the aura-slot presence check (thought this might work when buff-capped but doesn't seem to work) and `0`/omitted keeps the default check.
Cooldown detail tables now also expose `itemId`, `itemHasActiveSpell`, and `itemActiveSpellId` alongside the existing per-category timing data.
+2 -8
View File
@@ -744,10 +744,7 @@ Returns a Lua table reference containing all unit fields for the specified unit.
- A Lua table reference containing all unit fields, or nil if the unit cannot be found
- Object-reference GUID fields are returned as hex GUID strings (not numbers) to avoid Lua 64-bit precision issues. This includes fields such as `charm`, `summon`, `charmedBy`, `summonedBy`, `createdBy`, `target`, `persuaded`, and `channelObject`.
**Note — raid members not in your immediate party:** For raid members outside your local 5-man group who are not in your object manager (e.g. not nearby or not visible), only a limited subset of fields are available. All other fields will be zero/default.
- **Player fields:** status flags (online/offline/dead), current and max HP, power type, current and max power, level, zone, auras (up to 32 positive + 16 negative — spell IDs only, no stack count)
- **Pet fields:** GUID, name, model ID, current and max HP, power type, current and max power, auras (same format as player)
- No stats, no buff details beyond spell IDs.
**Note:** `GetUnitData` does not use the `party_member_fields` fallback. If the unit object is not loaded in the object manager, this function returns `nil`.
Full field name lists are in [`UNIT_FIELDS.md`](UNIT_FIELDS.md).
@@ -778,10 +775,7 @@ Fast lookup for a single field on a unit. More efficient than GetUnitData when y
- For array fields (like "aura", "resistances"), returns a Lua table with numeric indices
- Object-reference GUID fields are returned as hex GUID strings (not numbers) to avoid Lua 64-bit precision issues. This includes fields such as `charm`, `summon`, `charmedBy`, `summonedBy`, `createdBy`, `target`, `persuaded`, and `channelObject`.
**Note — raid members not in your immediate party:** For raid members outside your local 5-man group who are not in your object manager (e.g. not nearby or not visible), only a limited subset of fields are available. All other fields will be zero/default.
- **Player fields:** status flags (online/offline/dead), current and max HP, power type, current and max power, level, zone, auras (up to 32 positive + 16 negative — spell IDs only, no stack count)
- **Pet fields:** GUID, name, model ID, current and max HP, power type, current and max power, auras (same format as player)
- No stats, no buff details beyond spell IDs.
**Note:** `GetUnitField` falls back to cached `party_member_fields` data when the unit object is not loaded. The overlapping `UnitFields` names available through this fallback are `health`, `maxHealth`, `level`, and `aura` for party/raid members, plus `displayId`, `health`, `maxHealth`, and `aura` for party/raid pets.
Full field name lists are in [`UNIT_FIELDS.md`](UNIT_FIELDS.md).
+13816 -13751
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -41,6 +41,8 @@ set(SOURCE_FILES
dbc_fields.cpp
unit_fields.hpp
unit_fields.cpp
party_member_fields.hpp
party_member_fields.cpp
auras.hpp
auras.cpp
lua_refs.hpp
+17 -1
View File
@@ -18,7 +18,9 @@ namespace Nampower {
FLOAT,
STRING,
UINT64,
UINT8
UINT8,
UINT16,
INLINE_STRING // inline char array (not a pointer)
};
// Field descriptor for simple fields
@@ -93,11 +95,17 @@ namespace Nampower {
case FieldType::UINT8:
lua_pushnumber(luaState, *reinterpret_cast<const uint8_t*>(fieldPtr));
break;
case FieldType::UINT16:
lua_pushnumber(luaState, *reinterpret_cast<const uint16_t*>(fieldPtr));
break;
case FieldType::STRING: {
const char* str = *reinterpret_cast<const char* const*>(fieldPtr);
lua_pushstring(luaState, str ? const_cast<char*>(str) : const_cast<char*>(""));
break;
}
case FieldType::INLINE_STRING:
lua_pushstring(luaState, const_cast<char*>(fieldPtr));
break;
}
lua_settable(luaState, -3);
}
@@ -131,11 +139,15 @@ namespace Nampower {
case FieldType::UINT8:
lua_pushnumber(luaState, reinterpret_cast<const uint8_t*>(fieldPtr)[j]);
break;
case FieldType::UINT16:
lua_pushnumber(luaState, reinterpret_cast<const uint16_t*>(fieldPtr)[j]);
break;
case FieldType::STRING: {
const char* str = reinterpret_cast<const char* const*>(fieldPtr)[j];
lua_pushstring(luaState, str ? const_cast<char*>(str) : const_cast<char*>(""));
break;
}
default: break;
}
lua_settable(luaState, -3);
}
@@ -175,11 +187,15 @@ namespace Nampower {
case FieldType::UINT8:
lua_pushnumber(luaState, reinterpret_cast<const uint8_t*>(fieldPtr)[j]);
break;
case FieldType::UINT16:
lua_pushnumber(luaState, reinterpret_cast<const uint16_t*>(fieldPtr)[j]);
break;
case FieldType::STRING: {
const char* str = reinterpret_cast<const char* const*>(fieldPtr)[j];
lua_pushstring(luaState, str ? const_cast<char*>(str) : const_cast<char*>(""));
break;
}
default: break;
}
lua_settable(luaState, -3);
}
+12
View File
@@ -65,6 +65,18 @@ namespace game {
return getObjectPtr(guid);
}
CGPartyMemberInfo *GetPartyOrRaidMemberInfo(uint64_t guid) {
auto const getPartyOrRaidMember = reinterpret_cast<CGGameUI_GetPartyOrRaidMemberT>(
Offsets::CGGameUI_GetPartyOrRaidMember);
return getPartyOrRaidMember(&guid);
}
CGPartyMemberPetInfo *GetPartyOrRaidPetInfo(uint64_t guid) {
auto const getPartyOrRaidPet = reinterpret_cast<CGGameUI_GetPartyOrRaidPetT>(
Offsets::CGGameUI_GetPartyOrRaidPet);
return getPartyOrRaidPet(&guid);
}
uintptr_t *ClntObjMgrObjectPtr(TypeMask typeMask, std::uint64_t guid) {
using ClntObjMgrObjectPtrT = uintptr_t* (__fastcall *)(TypeMask typeMask, const char *debugMessage, unsigned __int64 guid, int debugCode);
+39
View File
@@ -2046,6 +2046,45 @@ namespace game {
float powerCostMultiplier[7]; // Size:7
} UnitFields;
// sizeof = 0x148 (328 bytes)
struct CGPartyMemberPetInfo {
/* 0x000 */ uint64_t guid; // flag 0x000800
/* 0x008 */ char name[80]; // flag 0x001000 — null-terminated, max 0x50 chars
/* 0x058 */ uint8_t powerType; // flag 0x010000
/* 0x059 */ uint8_t _pad;
/* 0x05A */ uint16_t displayId; // flag 0x002000
/* 0x05C */ uint16_t health; // flag 0x004000
/* 0x05E */ uint16_t maxHealth; // flag 0x008000
/* 0x060 */ uint16_t power; // flag 0x020000
/* 0x062 */ uint16_t maxPower; // flag 0x040000
/* 0x064 */ uint16_t aura[48]; // flags 0x080000/0x100000 — slots 0-31 positive, 32-47 negative
/* 0x0C4 */ uint16_t _auraPad[2]; // unused padding
};
struct CGPartyMemberInfo
{
/* 0x000 */ uint64_t guid; // flag 0x000000 — member GUID
/* 0x008 */ uint8_t status; // flag 0x000001 — MEMBER_STATUS_* bitmask; client ORs 0x20 if map pos visible
/* 0x009 */ uint8_t powerType; // flag 0x000008
/* 0x00A */ uint16_t health; // flag 0x000002
/* 0x00C */ uint16_t maxHealth; // flag 0x000004
/* 0x00E */ uint16_t power; // flag 0x000010
/* 0x010 */ uint16_t maxPower; // flag 0x000020
/* 0x012 */ uint16_t level; // flag 0x000040
/* 0x014 */ uint16_t zoneId; // flag 0x000080
/* 0x016 */ uint16_t posX; // flag 0x000100
/* 0x018 */ uint16_t posY; // flag 0x000100
/* 0x01A */ uint16_t aura[48]; // flags 0x000200/0x000400 — spell ID per slot, 0 = empty; slots 0-31 positive, 32-47 negative
/* 0x07A */ uint16_t _auraPad[3]; // unused padding
/* 0x080 */ CGPartyMemberPetInfo pet;
};
using CGGameUI_GetPartyOrRaidMemberT = CGPartyMemberInfo *(__fastcall *)(uint64_t *guid);
using CGGameUI_GetPartyOrRaidPetT = CGPartyMemberPetInfo *(__fastcall *)(uint64_t *guid);
CGPartyMemberInfo *GetPartyOrRaidMemberInfo(uint64_t guid);
CGPartyMemberPetInfo *GetPartyOrRaidPetInfo(uint64_t guid);
uintptr_t *GetObjectPtr(std::uint64_t guid);
std::uint32_t GetCastTime(void *unit, uint32_t spellId);
+13 -3
View File
@@ -5,7 +5,7 @@
#include "misc_scripts.hpp"
#include "logging.hpp"
#include "offsets.hpp"
#include "unit.hpp"
#include "party_member_fields.hpp"
#include "items.hpp"
#include "dbc_fields.hpp"
#include "unit_fields.hpp"
@@ -385,7 +385,7 @@ namespace Nampower {
}
// Get unit object pointer
auto unit = GetUnitPtr(guid);
auto unit = game::GetObjectPtr(guid);
if (!unit) {
lua_pushnil(luaState);
return 1;
@@ -470,8 +470,18 @@ namespace Nampower {
}
// Get unit object pointer
auto unit = GetUnitPtr(guid);
auto unit = game::GetObjectPtr(guid);
if (!unit) {
if (auto *info = game::GetPartyOrRaidMemberInfo(guid)) {
if (!GetCGPartyMemberField(luaState, info, fieldName, useCopy))
lua_pushnil(luaState);
return 1;
}
if (auto *info = game::GetPartyOrRaidPetInfo(guid)) {
if (!GetCGPartyMemberPetField(luaState, info, fieldName, useCopy))
lua_pushnil(luaState);
return 1;
}
lua_pushnil(luaState);
return 1;
}
+125
View File
@@ -0,0 +1,125 @@
#include "party_member_fields.hpp"
#include "helper.hpp"
#include "lua_refs.hpp"
namespace Nampower {
std::unordered_map<std::string, size_t> cgPartyMemberFieldMap;
std::unordered_map<std::string, size_t> cgPartyMemberArrayFieldMap;
std::unordered_map<std::string, size_t> cgPartyMemberPetFieldMap;
std::unordered_map<std::string, size_t> cgPartyMemberPetArrayFieldMap;
static std::unordered_map<std::string, int> cgPartyMemberArrayRefs;
static std::unordered_map<std::string, int> cgPartyMemberPetArrayRefs;
static bool cgPartyMemberFieldMapsInitialized = false;
const FieldDescriptor cgPartyMemberFields[] = {
{"guid", offsetof(game::CGPartyMemberInfo, guid), FieldType::UINT64},
{"status", offsetof(game::CGPartyMemberInfo, status), FieldType::UINT8},
{"powerType", offsetof(game::CGPartyMemberInfo, powerType), FieldType::UINT8},
{"health", offsetof(game::CGPartyMemberInfo, health), FieldType::UINT16},
{"maxHealth", offsetof(game::CGPartyMemberInfo, maxHealth), FieldType::UINT16},
{"power", offsetof(game::CGPartyMemberInfo, power), FieldType::UINT16},
{"maxPower", offsetof(game::CGPartyMemberInfo, maxPower), FieldType::UINT16},
{"level", offsetof(game::CGPartyMemberInfo, level), FieldType::UINT16},
{"zoneId", offsetof(game::CGPartyMemberInfo, zoneId), FieldType::UINT16},
{"posX", offsetof(game::CGPartyMemberInfo, posX), FieldType::UINT16},
{"posY", offsetof(game::CGPartyMemberInfo, posY), FieldType::UINT16},
};
const size_t cgPartyMemberFieldsCount = sizeof(cgPartyMemberFields) / sizeof(cgPartyMemberFields[0]);
const ArrayFieldDescriptor cgPartyMemberArrayFields[] = {
{"aura", offsetof(game::CGPartyMemberInfo, aura), FieldType::UINT16, 48},
};
const size_t cgPartyMemberArrayFieldsCount = sizeof(cgPartyMemberArrayFields) / sizeof(cgPartyMemberArrayFields[0]);
const FieldDescriptor cgPartyMemberPetFields[] = {
{"guid", offsetof(game::CGPartyMemberPetInfo, guid), FieldType::UINT64},
{"name", offsetof(game::CGPartyMemberPetInfo, name), FieldType::INLINE_STRING},
{"powerType", offsetof(game::CGPartyMemberPetInfo, powerType), FieldType::UINT8},
{"displayId", offsetof(game::CGPartyMemberPetInfo, displayId), FieldType::UINT16},
{"health", offsetof(game::CGPartyMemberPetInfo, health), FieldType::UINT16},
{"maxHealth", offsetof(game::CGPartyMemberPetInfo, maxHealth), FieldType::UINT16},
{"power", offsetof(game::CGPartyMemberPetInfo, power), FieldType::UINT16},
{"maxPower", offsetof(game::CGPartyMemberPetInfo, maxPower), FieldType::UINT16},
};
const size_t cgPartyMemberPetFieldsCount = sizeof(cgPartyMemberPetFields) / sizeof(cgPartyMemberPetFields[0]);
const ArrayFieldDescriptor cgPartyMemberPetArrayFields[] = {
{"aura", offsetof(game::CGPartyMemberPetInfo, aura), FieldType::UINT16, 48},
};
const size_t cgPartyMemberPetArrayFieldsCount = sizeof(cgPartyMemberPetArrayFields) / sizeof(cgPartyMemberPetArrayFields[0]);
void InitializeCGPartyMemberFieldMaps() {
if (cgPartyMemberFieldMapsInitialized) return;
for (size_t i = 0; i < cgPartyMemberFieldsCount; ++i)
cgPartyMemberFieldMap[cgPartyMemberFields[i].name] = i;
for (size_t i = 0; i < cgPartyMemberArrayFieldsCount; ++i)
cgPartyMemberArrayFieldMap[cgPartyMemberArrayFields[i].name] = i;
for (size_t i = 0; i < cgPartyMemberPetFieldsCount; ++i)
cgPartyMemberPetFieldMap[cgPartyMemberPetFields[i].name] = i;
for (size_t i = 0; i < cgPartyMemberPetArrayFieldsCount; ++i)
cgPartyMemberPetArrayFieldMap[cgPartyMemberPetArrayFields[i].name] = i;
cgPartyMemberFieldMapsInitialized = true;
}
template <typename T>
static uint32_t LookupCGPartyMemberField(uintptr_t *luaState, const T *info,
const char *fieldName, bool useCopy,
const std::unordered_map<std::string, size_t> &fieldMap,
const FieldDescriptor *fields,
const std::unordered_map<std::string, size_t> &arrayFieldMap,
const ArrayFieldDescriptor *arrayFields,
std::unordered_map<std::string, int> &arrayRefs) {
auto simpleIt = fieldMap.find(fieldName);
if (simpleIt != fieldMap.end()) {
const auto &field = fields[simpleIt->second];
const char *ptr = reinterpret_cast<const char *>(info) + field.offset;
switch (field.type) {
case FieldType::UINT64: PushGuidString(luaState, *reinterpret_cast<const uint64_t *>(ptr)); return 1;
case FieldType::UINT16: lua_pushnumber(luaState, *reinterpret_cast<const uint16_t *>(ptr)); return 1;
case FieldType::UINT8: lua_pushnumber(luaState, *reinterpret_cast<const uint8_t *>(ptr)); return 1;
case FieldType::INLINE_STRING: lua_pushstring(luaState, const_cast<char *>(ptr)); return 1;
default: break;
}
}
auto arrayIt = arrayFieldMap.find(fieldName);
if (arrayIt != arrayFieldMap.end()) {
const auto &field = arrayFields[arrayIt->second];
const char *ptr = reinterpret_cast<const char *>(info) + field.offset;
if (useCopy)
lua_newtable(luaState);
else
GetTableRef(luaState, arrayRefs[fieldName]);
for (size_t i = 0; i < field.count; ++i) {
lua_pushnumber(luaState, static_cast<double>(i + 1));
lua_pushnumber(luaState, reinterpret_cast<const uint16_t *>(ptr)[i]);
lua_settable(luaState, -3);
}
return 1;
}
return 0;
}
uint32_t GetCGPartyMemberField(uintptr_t *luaState, const game::CGPartyMemberInfo *info,
const char *fieldName, bool useCopy) {
InitializeCGPartyMemberFieldMaps();
return LookupCGPartyMemberField(luaState, info, fieldName, useCopy,
cgPartyMemberFieldMap, cgPartyMemberFields,
cgPartyMemberArrayFieldMap, cgPartyMemberArrayFields,
cgPartyMemberArrayRefs);
}
uint32_t GetCGPartyMemberPetField(uintptr_t *luaState, const game::CGPartyMemberPetInfo *info,
const char *fieldName, bool useCopy) {
InitializeCGPartyMemberFieldMaps();
return LookupCGPartyMemberField(luaState, info, fieldName, useCopy,
cgPartyMemberPetFieldMap, cgPartyMemberPetFields,
cgPartyMemberPetArrayFieldMap, cgPartyMemberPetArrayFields,
cgPartyMemberPetArrayRefs);
}
}
+34
View File
@@ -0,0 +1,34 @@
#pragma once
#include "main.hpp"
#include "dbc_fields.hpp"
#include "game.hpp"
#include <unordered_map>
#include <string>
#include <cstddef>
namespace Nampower {
// CGPartyMemberInfo member field descriptors (Lua names, offsets into CGPartyMemberInfo)
extern const FieldDescriptor cgPartyMemberFields[];
extern const size_t cgPartyMemberFieldsCount;
extern const ArrayFieldDescriptor cgPartyMemberArrayFields[];
extern const size_t cgPartyMemberArrayFieldsCount;
// CGPartyMemberInfo pet field descriptors (Lua names map to pet sub-fields)
extern const FieldDescriptor cgPartyMemberPetFields[];
extern const size_t cgPartyMemberPetFieldsCount;
extern const ArrayFieldDescriptor cgPartyMemberPetArrayFields[];
extern const size_t cgPartyMemberPetArrayFieldsCount;
// Hash tables for O(1) lookups
extern std::unordered_map<std::string, size_t> cgPartyMemberFieldMap;
extern std::unordered_map<std::string, size_t> cgPartyMemberArrayFieldMap;
extern std::unordered_map<std::string, size_t> cgPartyMemberPetFieldMap;
extern std::unordered_map<std::string, size_t> cgPartyMemberPetArrayFieldMap;
void InitializeCGPartyMemberFieldMaps();
uint32_t GetCGPartyMemberField(uintptr_t *luaState, const game::CGPartyMemberInfo *info,
const char *fieldName, bool useCopy);
uint32_t GetCGPartyMemberPetField(uintptr_t *luaState, const game::CGPartyMemberPetInfo *info,
const char *fieldName, bool useCopy);
}
-17
View File
@@ -9,23 +9,6 @@
#include "logging.hpp"
namespace Nampower {
uintptr_t *GetUnitPtr(uint64_t guid) {
if (auto *unit = game::GetObjectPtr(guid)) {
return unit;
}
auto const getPartyOrRaidMember = reinterpret_cast<CGGameUI_GetPartyOrRaidMemberT>(
Offsets::CGGameUI_GetPartyOrRaidMember);
if (auto *unit = getPartyOrRaidMember(&guid)) {
return unit;
}
auto const getPartyOrRaidPet = reinterpret_cast<CGGameUI_GetPartyOrRaidPetT>(
Offsets::CGGameUI_GetPartyOrRaidPet);
auto *unit = getPartyOrRaidPet(&guid);
return unit;
}
UnitNames GetUnitNames(uint64_t *guid) {
UnitNames names;
+4 -7
View File
@@ -59,12 +59,10 @@ namespace Nampower {
}
};
using CGGameUI_GetPartyMemberT = uint64_t (__fastcall *)(uint32_t index);
using CGGameUI_GetPartyMemberPetT = uint64_t (__fastcall *)(uint32_t index);
using CGGameUI_GetRaidMemberT = uint64_t (__fastcall *)(uint32_t index);
using CGGameUI_GetRaidMemberPetT = uint64_t (__fastcall *)(uint32_t index);
using CGGameUI_GetPartyOrRaidMemberT = uintptr_t *(__fastcall *)(uint64_t *guid);
using CGGameUI_GetPartyOrRaidPetT = uintptr_t *(__fastcall *)(uint64_t *guid);
using CGGameUI_GetPartyMemberT = uint64_t (__fastcall *)(uint32_t index);
using CGGameUI_GetPartyMemberPetT = uint64_t (__fastcall *)(uint32_t index);
using CGGameUI_GetRaidMemberT = uint64_t (__fastcall *)(uint32_t index);
using CGGameUI_GetRaidMemberPetT = uint64_t (__fastcall *)(uint32_t index);
using CGUnit_C_HandleEnvironmentDamageT = void (__fastcall *)(uintptr_t *unit, void *dummy_edx, int dmgType, int damage, int absorb, int resist);
using UnitCombatLogDamageShieldT = void (__fastcall *)(uint64_t *victimGuid, uint64_t *unitGuid, uint32_t damage, uint32_t spellSchool);
@@ -74,7 +72,6 @@ namespace Nampower {
using SignalEventParamGuidT = int (__cdecl *)(uint32_t eventCode, const char *format, const char *guid, int isPlayer, int isTarget, int isMouseover, int isPet, int partyIndex, int raidIndex);
using SignalEventParamUnitCombatGuidT = int (__cdecl *)(uint32_t eventCode, const char *format, const char *guid, const char *action, int damage, int school, int hitInfo, int isPet, int partyIndex, int raidIndex);
uintptr_t *GetUnitPtr(uint64_t guid);
UnitNames GetUnitNames(uint64_t *guid);
void FireUnitGuidEvent(const UnitNames &names, uint32_t guidEventCode);
void TriggerUnitEvents(const UnitNames &names, uint32_t eventCode);