mirror of
https://github.com/brues-code/nampower.git
synced 2026-09-28 17:56:02 +00:00
fix luaSlot not handling hidden buffs on BUFF/DEBUFF event, add IsAuraHidden function
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+125
-35
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user