fix luaSlot not handling hidden buffs on BUFF/DEBUFF event, add IsAuraHidden function

This commit is contained in:
avitasia
2026-02-27 11:27:07 -08:00
parent ccea6956cd
commit 18045d602f
10 changed files with 8688 additions and 8516 deletions
+2 -2
View File
@@ -258,11 +258,11 @@ DEBUFF_REMOVED_OTHER
All eight events pass the same parameters:
1. string guid - unit guid like "0xF5300000000000A5"
2. int luaSlot - 1-based Lua slot index for the buff/debuff (skips empty slots to match UnitBuff/UnitDebuff ordering). This is the slot you would pass to UnitBuff/UnitDebuff.
2. int luaSlot - 1-based Lua slot index for the buff/debuff (skips empty/hidden slots to match UnitBuff/UnitDebuff ordering). If the aura that triggered the event itself is hidden, this value is `0`.
3. int spellId
4. int stackCount - current stack count for the aura
5. int auraLevel - caster level for the aura from UnitFields.auraLevels (uint8 per slot, 48 entries)
6. int auraSlot - the raw 0-based aura slot index (0-31 for buffs, 32-47 for debuffs). This is the raw internal slot, not the Lua slot. Consistent with unit data fields, GetPlayerAuraDuration, and BUFF/DEBUFF_UPDATE_DURATION_SELF events.
6. int auraSlot - the raw 0-based aura slot index (0-31 for buffs, 32-47 for debuffs). This is the raw internal slot, not the Lua slot. Consistent with the aura field on units, GetPlayerAuraDuration, and BUFF/DEBUFF_UPDATE_DURATION_SELF events.
7. int state - indicates why the event fired: `0` = newly added, `1` = newly removed, `2` = modified (stack change). When state is `2`, the event type (*_ADDED_* or *_REMOVED_*) reflects whether stacks increased or decreased.
Example:
+2 -3
View File
@@ -143,8 +143,7 @@ Examples:
```
### Unit Token Extensions (`UnitGUID` + all `unitToken`/`target` string params)
`UnitGUID(unitToken)` now uses the same extended unit-token parser used by Nampower's unit-string APIs.
In practice, any Lua function documented as taking a unit string (`unitToken`, `target`, etc.) can use the formats below.
All functions taking a unit string like "player", "target", "raid1", etc now uses the same extended unit-token parser used by Superwow.
Supported formats:
- Standard unit tokens supported by the client parser (`player`, `target`, `pet`, `mouseover`, `party1`, `raid1`, etc.)
@@ -189,7 +188,7 @@ This includes functions for:
- Cast information (GetCastInfo, GetCurrentCastingInfo)
- Cooldown tracking (GetSpellIdCooldown, GetItemIdCooldown), including item metadata on cooldown detail tables
- Inventory helpers (GetTrinketCooldown, GetTrinkets, GetAmmo)
- Aura duration tracking and cancel helpers (GetPlayerAuraDuration, CancelPlayerAuraSlot, CancelPlayerAuraSpellId)
- Aura duration tracking, cancel helpers, and aura visibility checks (GetPlayerAuraDuration, CancelPlayerAuraSlot, CancelPlayerAuraSpellId, IsAuraHidden)
- Spell duration lookup (GetSpellDuration) - returns channel duration for channeling spells and the first aura effect duration for non-channeling spells
- Player movement state queries (PlayerIsMoving, PlayerIsRooted, PlayerIsSwimming)
- Talent helpers (LearnTalentRank)
+59
View File
@@ -34,6 +34,7 @@ For custom events, see [EVENTS.md](EVENTS.md). For installation, configuration,
- [GetPlayerAuraDuration](#getplayerauradurationauraslot)
- [CancelPlayerAuraSlot](#cancelplayerauraslotauraslot)
- [CancelPlayerAuraSpellId](#cancelplayerauraspellidspellid-ignoremissing)
- [IsAuraHidden](#isaurahiddenspellid)
- [LearnTalentRank](#learntalentranktalentpage-talentindex-rank)
- [Spell Casting and Queuing](#spell-casting-and-queuing)
- [QueueSpellByName](#queuespellbynamespellname)
@@ -56,6 +57,7 @@ For custom events, see [EVENTS.md](EVENTS.md). For installation, configuration,
- [PlayerIsRooted](#playerisrooted)
- [PlayerIsSwimming](#playerisswimming)
- [Utility Functions](#utility-functions)
- [UnitGUID](#unitguidunittoken)
- [DisenchantAll](#disenchantallitemidorname-includesoulbound-or-disenchantallquality-includesoulbound)
---
@@ -524,6 +526,25 @@ Cancels a player aura by raw aura slot index.
- `1` if the slot contains an aura and cancel was attempted
- `0` if the slot is invalid/out of range or no aura is present in that slot
#### IsAuraHidden(spellId)
Returns whether a spell's aura would be hidden from Lua aura APIs (e.g. `GetPlayerBuff`).
An aura is considered hidden if it has the `SPELL_ATTR_HIDDEN_CLIENTSIDE` attribute, the `SPELL_ATTR_EX_NO_AURA_ICON` attribute, or is a tracking aura (track creatures, resources, or stealthed).
**Parameters:**
- `spellId` (number): The spell ID to check.
**Returns:**
- `1` if the aura is hidden from Lua
- `0` if the aura is visible to Lua
**Examples:**
```lua
if IsAuraHidden(2458) == 1 then
print("Aura is hidden")
end
```
#### LearnTalentRank(talentPage, talentIndex, rank)
Learns a specific talent rank directly by tab/index.
@@ -1188,6 +1209,44 @@ end
### Utility Functions
#### UnitGUID(unitToken)
Returns the GUID of the unit identified by the given unit token.
This function replaces the vanilla client's `UnitGUID` with an extended version that supports Nampower's additional unit-token formats (see [Unit Token Extensions](README.md#unit-token-extensions-unitguid--all-unittokentarget-string-params)).
**Parameters:**
- `unitToken` (string): A unit token or extended unit token string.
**Returns:**
- `guid` (string): The unit's GUID as a hex string (e.g. `"0xF5300000000000A5"`), or `nil` if the unit cannot be resolved.
**Supported token formats:**
- Standard tokens: `"player"`, `"target"`, `"pet"`, `"mouseover"`, `"party1"`–`"party4"`, `"partypet1"`–`"partypet4"`, `"raid1"`–`"raid40"`, `"raidpet1"`–`"raidpet40"`
- Raid target marks: `"mark1"`–`"mark8"`
- Suffix forms: any token with `"owner"`, `"target"`, or `"pet"` appended (e.g. `"targetowner"`, `"mark1target"`, `"party1pet"`)
- Raw hex GUIDs with optional suffix: `"0x[16 hex digits]"`, `"0x[16 hex digits]target"`, etc.
**Examples:**
```lua
-- Standard tokens
print(UnitGUID("player"))
print(UnitGUID("target"))
print(UnitGUID("party1"))
-- Raid target marks
print(UnitGUID("mark1"))
-- Suffix forms
print(UnitGUID("mark1target")) -- target of the unit marked with mark 1
print(UnitGUID("targetowner")) -- owner of the current target
print(UnitGUID("party1pet")) -- pet of party member 1
-- Raw hex GUID with suffix
print(UnitGUID("0xF5300000000000A5target"))
```
---
#### DisenchantAll(itemIdOrName, [includeSoulbound]) or DisenchantAll(quality, [includeSoulbound])
Automatically disenchants items in your inventory. Can disenchant a specific item by ID/name, or all weapons and armor of a specified quality.
+8480 -8475
View File
File diff suppressed because it is too large Load Diff
+125 -35
View File
@@ -9,8 +9,115 @@
#include "helper.hpp"
namespace Nampower {
constexpr uint32_t SPELL_AURA_TRACK_CREATURES = 44;
constexpr uint32_t SPELL_AURA_TRACK_RESOURCES = 45;
constexpr uint32_t SPELL_AURA_TRACK_STEALTHED = 151;
uint32_t gAuraExpirationTime[MAX_AURA_SLOTS] = {};
static bool IsTrackingAura(const game::SpellRec *spellRec) {
if (!spellRec) {
return false;
}
for (uint32_t auraName: spellRec->EffectApplyAuraName) {
if (auraName == SPELL_AURA_TRACK_CREATURES ||
auraName == SPELL_AURA_TRACK_RESOURCES ||
auraName == SPELL_AURA_TRACK_STEALTHED) {
return true;
}
}
return false;
}
bool IsAuraHiddenForLua(uint32_t spellId) {
if (spellId == 0) {
return true;
}
auto const *spellRec = game::GetSpellInfo(spellId);
if (!spellRec) {
return false;
}
if ((spellRec->Attributes & game::SPELL_ATTR_HIDDEN_CLIENTSIDE) != 0) {
return true;
}
if ((spellRec->AttributesEx & game::SPELL_ATTR_EX_NO_AURA_ICON) != 0) {
return true;
}
if (IsTrackingAura(spellRec)) {
return true;
}
return false;
}
static bool IsAuraVisibleInPackedFlags(const game::UnitFields *unitFields, uint32_t slot, uint8_t *packedOut = nullptr,
uint8_t *nibbleOut = nullptr) {
auto *auraFlags = reinterpret_cast<const uint8_t *>(unitFields->auraFlags);
uint8_t packedSlotFlags = auraFlags[slot / 2];
uint8_t nibble = (slot & 1u) != 0 ? (packedSlotFlags >> 4) : packedSlotFlags;
if (packedOut) {
*packedOut = packedSlotFlags;
}
if (nibbleOut) {
*nibbleOut = nibble;
}
return (nibble & 0x0Eu) != 0; // (flags & (NEGATIVE | PASSIVE | PERMANENT)) != 0
}
static bool CountsAsEmptyForLua(const game::UnitFields *unitFields, uint32_t slot) {
uint32_t spellId = unitFields->aura[slot];
if (spellId == 0) {
return true;
}
if (IsAuraHiddenForLua(spellId)) {
return true;
}
uint8_t packedSlotFlags = 0;
uint8_t nibble = 0;
bool isVisible = IsAuraVisibleInPackedFlags(unitFields, slot, &packedSlotFlags, &nibble);
return !isVisible;
}
static bool IsHiddenAura(const game::UnitFields *unitFields, uint32_t slot, uint32_t spellId, uint32_t state) {
if (spellId != 0 && IsAuraHiddenForLua(spellId)) {
return true;
}
// Removed events run after the aura was cleared, so do not use slot/flags visibility there.
if (state == 1) {
return false;
}
return CountsAsEmptyForLua(unitFields, slot);
}
static int CalculateLuaUnitSlotFromAuraSlot(const game::UnitFields *unitFields, uint32_t slot) {
bool isBuff = slot < 32;
int luaUnitSlot = static_cast<int>(slot) + 1;
if (!isBuff) {
luaUnitSlot -= 32;
}
uint32_t firstSlot = isBuff ? 0 : 32;
int emptyCount = 0;
for (uint32_t i = firstSlot; i < slot; ++i) {
if (CountsAsEmptyForLua(unitFields, i)) {
++emptyCount;
}
}
return luaUnitSlot - emptyCount;
}
// Helper function to trigger buff/debuff events
// state: 0 = newly added, 1 = newly removed, 2 = modified (stack change)
void TriggerAuraEvent(uintptr_t *unit, uint32_t slot, uint32_t spellId, bool wasAdded, uint32_t state) {
@@ -23,26 +130,16 @@ namespace Nampower {
// Trigger the appropriate event based on buff/debuff, add/remove, and self/other
game::Events eventToTrigger;
int luaUnitSlot = slot + 1; // lua uses 1-based indexing for slots. This is the slot for UnitBuff/UnitDebuff
auto *unitFields = *reinterpret_cast<game::UnitFields **>(unit + 68);
auto auras = unitFields->aura;
auto auraLevels = unitFields->auraLevels;
auto auraStacks = unitFields->auraApplications;
int luaUnitSlot = CalculateLuaUnitSlotFromAuraSlot(unitFields, slot);
auto luaStacks = (state == 1) ? auraStacks[slot] : auraStacks[slot] + 1; // for some crazy reason the stack count is 0 indexed but also 0 instead of -1 when removed
auto luaStacks = (state == 1) ? auraStacks[slot] : auraStacks[slot] + 1;
// for some crazy reason the stack count is 0 indexed but also 0 instead of -1 when removed
if (isBuff) {
// count empty buff slots before slot to adjust luaSlot for actual buff/debuff index
int emptyCount = 0;
for (uint32_t i = 0; i < slot; ++i) {
if (auras[i] == 0) {
emptyCount++;
}
}
luaUnitSlot = luaUnitSlot - emptyCount;
if (wasAdded) {
// Buff added
eventToTrigger = isSelf ? game::BUFF_ADDED_SELF : game::BUFF_ADDED_OTHER;
@@ -51,17 +148,6 @@ namespace Nampower {
eventToTrigger = isSelf ? game::BUFF_REMOVED_SELF : game::BUFF_REMOVED_OTHER;
}
} else {
luaUnitSlot = luaUnitSlot - 32; // adjust slot for debuffs
// count empty debuff slots before slot to adjust luaSlot for actual buff/debuff index
int emptyCount = 0;
for (uint32_t i = 32; i < slot; ++i) {
if (auras[i] == 0) {
emptyCount++;
}
}
luaUnitSlot = luaUnitSlot - emptyCount;
if (wasAdded) {
// Debuff added
eventToTrigger = isSelf ? game::DEBUFF_ADDED_SELF : game::DEBUFF_ADDED_OTHER;
@@ -71,20 +157,23 @@ namespace Nampower {
}
}
bool isHidden = IsHiddenAura(unitFields, slot, spellId, state);
static char format[] = "%s%d%d%d%d%d%d";
char *guidStr = ConvertGuidToString(isSelf ? playerGuid : unitGuid);
int eventLuaSlot = isHidden ? 0 : luaUnitSlot;
// Trigger the event with spellId as parameter
((int (__cdecl *)(int, char *, char *, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t)) Offsets::SignalEventParam)(
eventToTrigger,
format,
guidStr,
luaUnitSlot,
spellId,
luaStacks,
auraLevels[slot],
slot,
state);
((int (__cdecl *)(int, char *, char *, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t))
Offsets::SignalEventParam)(
eventToTrigger,
format,
guidStr,
eventLuaSlot,
spellId,
luaStacks,
auraLevels[slot],
slot,
state);
FreeGuidString(guidStr);
}
@@ -109,7 +198,8 @@ namespace Nampower {
TriggerAuraEvent(unit, slot, spellId, true, 0);
}
void CGUnit_C_OnAuraStacksChangedHook(hadesmem::PatchDetourBase *detour, uintptr_t *unit, void *dummy_edx, int slot, uint8_t stackCount) {
void CGUnit_C_OnAuraStacksChangedHook(hadesmem::PatchDetourBase *detour, uintptr_t *unit, void *dummy_edx, int slot,
uint8_t stackCount) {
auto const onAuraStacksChanged = detour->GetTrampolineT<CGUnit_C_OnAuraStacksChangedT>();
// Get spell ID from the aura slot
+2
View File
@@ -18,6 +18,8 @@ namespace Nampower {
using UnitCombatLogUnitDeadT = void (__fastcall *)(uint64_t guid);
using CGBuffBar_UpdateDurationT = void (__fastcall *)(uint8_t auraSlot, int durationMs);
bool IsAuraHiddenForLua(uint32_t spellId);
void CGUnit_C_OnAuraRemovedHook(hadesmem::PatchDetourBase *detour, uintptr_t *unit, void *dummy_edx, uint32_t slot, uint32_t spellId);
void CGUnit_C_OnAuraAddedHook(hadesmem::PatchDetourBase *detour, uintptr_t *unit, void *dummy_edx, uint32_t slot, uint32_t spellId);
void CGUnit_C_OnAuraStacksChangedHook(hadesmem::PatchDetourBase *detour, uintptr_t *unit, void *dummy_edx, int slot, uint8_t stackCount);
+3
View File
@@ -1837,6 +1837,9 @@ namespace Nampower {
char unitGUID[] = "UnitGUID";
RegisterLuaFunction(unitGUID, reinterpret_cast<uintptr_t *>(Script_UnitGUID));
char isAuraHidden[] = "IsAuraHidden";
RegisterLuaFunction(isAuraHidden, reinterpret_cast<uintptr_t *>(Script_IsAuraHidden));
}
void load() {
+1 -1
View File
@@ -25,7 +25,7 @@ namespace Nampower {
constexpr uint32_t DISENCHANT_QUALITY_PURPLE = 0x04; // Epic
constexpr uint32_t MAJOR_VERSION = 2;
constexpr uint32_t MINOR_VERSION = 41;
constexpr uint32_t MINOR_VERSION = 42;
constexpr uint32_t PATCH_VERSION = 0;
constexpr int32_t LUA_REGISTRYINDEX = -10000;
+13
View File
@@ -945,6 +945,19 @@ namespace Nampower {
return 1;
}
uint32_t Script_IsAuraHidden(uintptr_t *luaState) {
luaState = GetLuaStatePtr();
if (!lua_isnumber(luaState, 1)) {
lua_error(luaState, "Usage: IsAuraHidden(spellId)");
return 0;
}
auto const spellId = static_cast<uint32_t>(lua_tonumber(luaState, 1));
lua_pushnumber(luaState, IsAuraHiddenForLua(spellId) ? 1 : 0);
return 1;
}
uint64_t GetGUIDFromNameHook(hadesmem::PatchDetourBase *detour, const char *nameStr) {
auto const original = detour->GetTrampolineT<GetGUIDFromNameT>();
+1
View File
@@ -39,6 +39,7 @@ namespace Nampower {
uint32_t Script_SetMouseoverUnit(uintptr_t *luaState);
uint32_t Script_UnitGUID(uintptr_t *luaState);
uint32_t Script_IsAuraHidden(uintptr_t *luaState);
uint32_t CSimpleFrame_GetNameHook(hadesmem::PatchDetourBase *detour, uintptr_t *luaState);
uint64_t GetGUIDFromNameHook(hadesmem::PatchDetourBase *detour, const char *nameStr);