mirror of
https://github.com/brues-code/nampower.git
synced 2026-09-21 23:26:54 +00:00
add UseItemIdOrName, UseTrinket, GetTrinkets, and AURA_CAST events
This commit is contained in:
@@ -145,6 +145,22 @@ for _, eventName in ipairs({"BUFF_ADDED_SELF", "BUFF_REMOVED_SELF", "DEBUFF_ADDE
|
||||
end
|
||||
```
|
||||
|
||||
#### AURA_CAST_ON_SELF and AURA_CAST_ON_OTHER
|
||||
Fire when a spell cast applies an aura. "Self" covers casts that land on the active player (including cases where the active player is the caster with no explicit target); "Other" covers all other targets.
|
||||
|
||||
These events are gated behind the `NP_EnableAuraCastEvents` CVar (default 0). Set it to `1` to enable.
|
||||
|
||||
Parameters:
|
||||
1. int spellId
|
||||
2. string casterGuid - caster guid like "0xF5300000000000A5"
|
||||
3. string targetGuid - target guid like "0xF5300000000000A5"
|
||||
4. int effect - aura-applying effect id (event fires once for each qualifying effect in the spell)
|
||||
5. int effectAuraName - corresponding entry from EffectApplyAuraName
|
||||
6. int effectAmplitude - EffectAmplitude entry for the selected aura effect
|
||||
7. int effectMiscValue - EffectMiscValue entry for the selected aura effect
|
||||
8. int durationMs - spell duration in milliseconds (after modifiers)
|
||||
9. int auraCapStatus - bitfield: 1 = buff bar full, 2 = debuff bar full (3 means both)
|
||||
|
||||
#### UNIT_DIED
|
||||
Fires when a unit death is recorded in the combat log.
|
||||
|
||||
@@ -156,4 +172,4 @@ Example:
|
||||
frame:RegisterEvent("UNIT_DIED", function(guid)
|
||||
DEFAULT_CHAT_FRAME:AddMessage("Unit died: " .. guid)
|
||||
end)
|
||||
```
|
||||
```
|
||||
|
||||
@@ -125,9 +125,14 @@ This includes functions for:
|
||||
- Spell/item/unit information (GetItemStats, GetSpellRec, GetUnitData, etc.)
|
||||
- Spell casting and queuing (QueueSpellByName, QueueScript, etc.)
|
||||
- Cast information (GetCastInfo, GetCurrentCastingInfo)
|
||||
- Cooldown tracking (GetSpellIdCooldown, GetItemIdCooldown)
|
||||
- Cooldown tracking (GetSpellIdCooldown, GetItemIdCooldown), including item metadata on cooldown detail tables
|
||||
- Inventory helpers (GetTrinketCooldown, GetTrinkets)
|
||||
- Spell lookups and utilities
|
||||
|
||||
Cooldown detail tables now also expose `itemId`, `itemHasActiveSpell`, and `itemActiveSpellId` alongside the existing per-category timing data.
|
||||
|
||||
Use `GetTrinkets([copy])` to enumerate equipped trinkets and bagged trinkets (backpack/bags 1-4) with `itemId`, `trinketName`, `bagIndex` (nil when equipped), and 1-based `slotIndex`. It reuses cached tables by default; pass `1` (or any truthy value) to force a fresh copy.
|
||||
|
||||
## Custom Events
|
||||
|
||||
For complete documentation of all custom events added by Nampower, see **[EVENTS.md](EVENTS.md)**.
|
||||
@@ -137,6 +142,7 @@ Available events:
|
||||
- SPELL_CAST_EVENT - Fires when you cast a spell with additional information
|
||||
- SPELL_DAMAGE_EVENT_SELF and SPELL_DAMAGE_EVENT_OTHER - Combat damage events
|
||||
- Buff/Debuff Events (BUFF_ADDED_SELF, BUFF_REMOVED_SELF, etc.)
|
||||
- AURA_CAST_ON_SELF and AURA_CAST_ON_OTHER - Aura application events (fires once per aura effect; "Self" fires when the aura lands on the active player, including self-cast with no explicit target; includes aura metadata + amplitude/misc + aura cap bitfield for buff/debuff slots); set `NP_EnableAuraCastEvents=1` to enable
|
||||
- UNIT_DIED - Fires when a unit dies
|
||||
|
||||
## Bug Reporting
|
||||
|
||||
+100
-1
@@ -35,6 +35,7 @@ The following functions use reusable table references:
|
||||
- **`GetItemStatsField(itemId, fieldName, [copy])`** - Returns individual item field value
|
||||
- **`GetUnitField(unitToken, fieldName, [copy])`** - Returns individual unit field value
|
||||
- **`GetSpellRecField(spellId, fieldName, [copy])`** - Returns individual spell field value
|
||||
- **`GetTrinkets([copy])`** - Returns trinket list table
|
||||
|
||||
**Important:** When using these functions without the `copy` parameter, **immediately copy or extract** any values you need to store for later use. Do not store references to the returned tables themselves. Alternatively, pass `1` as the `copy` parameter to get an independent table that is safe to store.
|
||||
|
||||
@@ -201,6 +202,55 @@ if slot then
|
||||
end
|
||||
```
|
||||
|
||||
#### UseItemIdOrName(itemIdOrName, [target])
|
||||
Uses the first matching item found in the player's inventory (including equipped items) by item ID or name.
|
||||
|
||||
**Parameters:**
|
||||
- `itemIdOrName` (number|string): Item ID or item name (case-insensitive)
|
||||
- `target` (optional, string|number): Unit token (e.g. `"target"`, `"player"`) or GUID
|
||||
- If omitted, uses `LockedTargetGuid` if set; otherwise falls back to the active player GUID.
|
||||
|
||||
**Returns:**
|
||||
- `1` if the item was found and `CGItem_C::Use(...)` returned non-zero
|
||||
- `0` if the item was not found or use failed
|
||||
|
||||
**Examples:**
|
||||
```lua
|
||||
-- Use Hearthstone
|
||||
UseItemIdOrName("Hearthstone")
|
||||
|
||||
-- Use a healing potion on yourself (if the item requires a target)
|
||||
UseItemIdOrName(13446, "player")
|
||||
```
|
||||
|
||||
#### UseTrinket(slot|itemIdOrName, [target])
|
||||
Uses a trinket from the equipped trinket slots (13 and 14 only).
|
||||
|
||||
**Parameters:**
|
||||
- `slot|itemIdOrName` (number|string):
|
||||
- `1` or `13` => use first trinket slot
|
||||
- `2` or `14` => use second trinket slot
|
||||
- Any other number => treat as item ID to find in trinket slots
|
||||
- String => item name (case-insensitive) to find in trinket slots
|
||||
- `target` (optional, string|number): Unit token or GUID. If omitted, uses `LockedTargetGuid` if set; otherwise falls back to active player GUID.
|
||||
|
||||
**Returns:**
|
||||
- `1` if the trinket was found and `CGItem_C::Use(...)` returned non-zero
|
||||
- `0` if the trinket was found but use returned zero
|
||||
- `-1` if no matching trinket was found in slots 13/14
|
||||
|
||||
**Examples:**
|
||||
```lua
|
||||
-- Use first trinket slot
|
||||
UseTrinket(1)
|
||||
-- Use second trinket slot on current target
|
||||
UseTrinket(2, "target")
|
||||
-- Use by item id if present in either trinket slot
|
||||
UseTrinket(18406)
|
||||
-- Use by name
|
||||
UseTrinket("Royal Seal of Eldre'Thalas")
|
||||
```
|
||||
|
||||
#### GetEquippedItems(unitToken)
|
||||
Returns a table reference containing all equipped items for the specified unit.
|
||||
|
||||
@@ -662,6 +712,9 @@ A Lua table reference with the following fields:
|
||||
|
||||
- `isOnCooldown` (number): 1 if any cooldown is active, 0 otherwise
|
||||
- `cooldownRemainingMs` (number): Maximum remaining time across all cooldown types in milliseconds
|
||||
- `itemId` (number): Item ID tied to the cooldown (0 if none)
|
||||
- `itemHasActiveSpell` (number): 1 if the item has an on-use spell, 0 otherwise
|
||||
- `itemActiveSpellId` (number): Spell ID of the active item spell (0 if none)
|
||||
|
||||
**Individual Spell Cooldown:**
|
||||
- `individualStartS` (number): When the individual spell cooldown started (seconds, WoW time)
|
||||
@@ -734,6 +787,52 @@ else
|
||||
end
|
||||
```
|
||||
|
||||
#### GetTrinketCooldown(slot|itemIdOrName)
|
||||
Returns cooldown information for the equipped trinket(s) in slots 13 or 14. Accepts slot shortcuts or item identifiers.
|
||||
|
||||
**Parameters:**
|
||||
- `slot|itemIdOrName` (number|string):
|
||||
- `1` or `13` => first trinket slot
|
||||
- `2` or `14` => second trinket slot
|
||||
- Any other number => treat as item ID to match against trinket slots
|
||||
- String => item name (case-insensitive) to match against trinket slots
|
||||
|
||||
**Returns:**
|
||||
- If no matching trinket is equipped in slots 13/14: returns `-1`
|
||||
- Otherwise: a cooldown detail table with the same structure as `GetSpellIdCooldown` / `GetItemIdCooldown`
|
||||
|
||||
**Example:**
|
||||
```lua
|
||||
-- Get cooldown for first trinket slot
|
||||
local cd = GetTrinketCooldown(1)
|
||||
if cd ~= -1 and cd.isOnCooldown == 0 then
|
||||
print("Trinket ready")
|
||||
end
|
||||
|
||||
-- Check by name
|
||||
local cd = GetTrinketCooldown("Royal Seal of Eldre'Thalas")
|
||||
if cd ~= -1 then
|
||||
print("Remaining: " .. cd.cooldownRemainingMs .. "ms")
|
||||
end
|
||||
```
|
||||
|
||||
#### GetTrinkets([copy])
|
||||
Returns a table of trinkets from equipped trinket slots and carried bags.
|
||||
|
||||
**Parameters:**
|
||||
- `[copy]` (number|boolean, optional): Pass `1` (or any truthy value) to force creation of a fresh Lua table. By default the function reuses an internal table and entry tables for performance.
|
||||
|
||||
**Returns:**
|
||||
A Lua table where each entry contains:
|
||||
- `itemId` (number)
|
||||
- `trinketName` (string, `"Unknown"` if no name available)
|
||||
- `bagIndex` (number|nil): `nil` when equipped; `0` for backpack; `1-4` for equipped bags
|
||||
- `slotIndex` (number): Lua 1-based slot within the container (or 1/2 for equipped trinket slots)
|
||||
|
||||
**Notes:**
|
||||
- Scans only equipped trinket slots and bags 0-4 (backpack + equipped bags). Does not scan bank or keyring.
|
||||
- Reuses cached Lua tables unless `copyTable` is truthy; prefer copies if you will mutate the returned tables.
|
||||
|
||||
#### GetSpellIdForName(spellName)
|
||||
Returns:
|
||||
|
||||
@@ -791,4 +890,4 @@ should print 90 for atiesh
|
||||
```
|
||||
|
||||
#### ChannelStopCastingNextTick()
|
||||
Will stop channeling early on the next tick if you have queue channeling spells enabled and try to cast a spell before the next tick (didn't know how to cancel channels without casting another spell). Uses your ChannelLatencyReductionPercentage to determine when to stop the channel.
|
||||
Will stop channeling early on the next tick if you have queue channeling spells enabled and try to cast a spell before the next tick (didn't know how to cancel channels without casting another spell). Uses your ChannelLatencyReductionPercentage to determine when to stop the channel.
|
||||
|
||||
+137
-27
@@ -5,6 +5,8 @@
|
||||
#include "items.hpp"
|
||||
#include "offsets.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace Nampower {
|
||||
// Reusable table reference to reduce memory allocations
|
||||
static int cooldownDetailTableRef = LUA_REFNIL;
|
||||
@@ -13,6 +15,10 @@ namespace Nampower {
|
||||
bool isOnCooldown = false;
|
||||
uint32_t cooldownRemainingMs = 0;
|
||||
|
||||
bool itemHasActiveSpell = false;
|
||||
uint32_t itemId = 0;
|
||||
uint32_t itemActiveSpellId = 0;
|
||||
|
||||
uint32_t individualStartMs = 0;
|
||||
uint32_t individualDurationMs = 0;
|
||||
uint32_t individualRemainingMs = 0;
|
||||
@@ -31,13 +37,6 @@ namespace Nampower {
|
||||
bool isOnGcdCategoryCooldown = false;
|
||||
};
|
||||
|
||||
|
||||
inline void PushTableInt(uintptr_t *luaState, char *key, int value) {
|
||||
lua_pushstring(luaState, key);
|
||||
lua_pushnumber(luaState, value);
|
||||
lua_settable(luaState, -3);
|
||||
}
|
||||
|
||||
game::SpellHistoryEntry *GetSpellHistoryHead() {
|
||||
auto const spellHistoryAddr = static_cast<uintptr_t>(Offsets::SpellHistories);
|
||||
|
||||
@@ -67,16 +66,25 @@ namespace Nampower {
|
||||
return detail;
|
||||
}
|
||||
|
||||
detail.itemId = itemId;
|
||||
|
||||
// Set base durations from spell record so we know what they are even if not on cooldown
|
||||
detail.individualDurationMs = spellRec->RecoveryTime;
|
||||
detail.categoryDurationMs = spellRec->CategoryRecoveryTime;
|
||||
detail.gcdCategoryDurationMs = spellRec->StartRecoveryTime;
|
||||
|
||||
auto const category = itemCategoryOverride ? itemCategoryOverride : spellRec->Category;
|
||||
auto const startRecoveryCategory = spellRec->StartRecoveryCategory;
|
||||
|
||||
detail.categoryId = category;
|
||||
detail.gcdCategoryId = startRecoveryCategory;
|
||||
|
||||
auto const now = static_cast<uint32_t>(GetWowTimeMs() & 0xFFFFFFFF); // get bottom 32 bits of time in ms
|
||||
|
||||
int entryCount = 0;
|
||||
for (auto entry = GetSpellHistoryHead();
|
||||
entry && ((reinterpret_cast<uintptr_t>(entry) & 0x1) == 0);
|
||||
entry = reinterpret_cast<game::SpellHistoryEntry *>(entry->field4_0x4)) {
|
||||
|
||||
// Individual spell cooldown
|
||||
if (entry->spellID == spellId && entry->itemID == itemId &&
|
||||
(entry->recoveryTime != 0 || entry->onHold)) {
|
||||
@@ -107,7 +115,8 @@ namespace Nampower {
|
||||
}
|
||||
|
||||
// GCD category cooldown (startRecoveryCategory)
|
||||
if (entry->startRecoveryCategory == startRecoveryCategory && entry->startRecoveryTime != 0 && startRecoveryCategory != 0) {
|
||||
if (entry->startRecoveryCategory == startRecoveryCategory && entry->startRecoveryTime != 0 &&
|
||||
startRecoveryCategory != 0) {
|
||||
auto start = entry->onHold ? now : entry->recoveryStart;
|
||||
auto end = entry->onHold
|
||||
? uint64_t(now) + entry->startRecoveryTime
|
||||
@@ -124,7 +133,8 @@ namespace Nampower {
|
||||
}
|
||||
|
||||
// Calculate overall cooldown status
|
||||
detail.isOnCooldown = detail.isOnIndividualCooldown || detail.isOnCategoryCooldown || detail.isOnGcdCategoryCooldown;
|
||||
detail.isOnCooldown = detail.isOnIndividualCooldown || detail.isOnCategoryCooldown || detail.
|
||||
isOnGcdCategoryCooldown;
|
||||
|
||||
// Find the maximum remaining cooldown
|
||||
detail.cooldownRemainingMs = detail.individualRemainingMs;
|
||||
@@ -135,12 +145,34 @@ namespace Nampower {
|
||||
detail.cooldownRemainingMs = detail.gcdCategoryRemainingMs;
|
||||
}
|
||||
|
||||
if (itemId > 0) {
|
||||
// get info on active spell for item
|
||||
auto *itemStats = GetItemStats(itemId);
|
||||
if (itemStats) {
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
if (itemStats->m_spellCooldown[i] > 0 || itemStats->m_spellCategoryCooldown[i] > 0) {
|
||||
detail.itemHasActiveSpell = true;
|
||||
detail.itemActiveSpellId = itemStats->m_spellID[i];
|
||||
detail.individualDurationMs = itemStats->m_spellCooldown[i];
|
||||
detail.categoryDurationMs = itemStats->m_spellCategoryCooldown[i];
|
||||
return detail;
|
||||
}
|
||||
}
|
||||
// didn't have active spell, clear any spell gcd info for passive effects
|
||||
detail.gcdCategoryDurationMs = 0;
|
||||
detail.gcdCategoryId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return detail;
|
||||
}
|
||||
|
||||
void PushCooldownDetailTable(uintptr_t *luaState, const CooldownDetail &detail) {
|
||||
static char isOnCooldownKey[] = "isOnCooldown";
|
||||
static char cooldownRemainingMsKey[] = "cooldownRemainingMs";
|
||||
static char itemIdKey[] = "itemId";
|
||||
static char itemHasActiveSpellKey[] = "itemHasActiveSpell";
|
||||
static char itemActiveSpellIdKey[] = "itemActiveSpellId";
|
||||
|
||||
static char individualStartSKey[] = "individualStartS";
|
||||
static char individualDurationMsKey[] = "individualDurationMs";
|
||||
@@ -167,35 +199,38 @@ namespace Nampower {
|
||||
lua_rawgeti(luaState, LUA_REGISTRYINDEX, cooldownDetailTableRef);
|
||||
|
||||
// Overall cooldown status
|
||||
PushTableInt(luaState, isOnCooldownKey, detail.isOnCooldown ? 1 : 0);
|
||||
PushTableValue(luaState, isOnCooldownKey, detail.isOnCooldown ? 1 : 0);
|
||||
PushTableValue(luaState, cooldownRemainingMsKey, detail.cooldownRemainingMs);
|
||||
PushTableValue(luaState, itemIdKey, detail.itemId);
|
||||
PushTableValue(luaState, itemHasActiveSpellKey, detail.itemHasActiveSpell ? 1 : 0);
|
||||
PushTableValue(luaState, itemActiveSpellIdKey, detail.itemActiveSpellId);
|
||||
|
||||
// Individual cooldown
|
||||
PushTableValue(luaState, individualStartSKey, detail.individualStartMs / 1000.0);
|
||||
PushTableValue(luaState, individualDurationMsKey, detail.individualDurationMs);
|
||||
PushTableValue(luaState, individualRemainingMsKey, detail.individualRemainingMs);
|
||||
PushTableInt(luaState, isOnIndividualCooldownKey, detail.isOnIndividualCooldown ? 1 : 0);
|
||||
PushTableValue(luaState, isOnIndividualCooldownKey, detail.isOnIndividualCooldown ? 1 : 0);
|
||||
|
||||
// Category cooldown
|
||||
PushTableValue(luaState, categoryIdKey, detail.categoryId);
|
||||
PushTableValue(luaState, categoryStartSKey, detail.categoryStartMs / 1000.0);
|
||||
PushTableValue(luaState, categoryDurationMsKey, detail.categoryDurationMs);
|
||||
PushTableValue(luaState, categoryRemainingMsKey, detail.categoryRemainingMs);
|
||||
PushTableInt(luaState, isOnCategoryCooldownKey, detail.isOnCategoryCooldown ? 1 : 0);
|
||||
PushTableValue(luaState, isOnCategoryCooldownKey, detail.isOnCategoryCooldown ? 1 : 0);
|
||||
|
||||
// GCD category cooldown
|
||||
PushTableValue(luaState, gcdCategoryIdKey, detail.gcdCategoryId);
|
||||
PushTableValue(luaState, gcdCategoryStartSKey, detail.gcdCategoryStartMs / 1000.0);
|
||||
PushTableValue(luaState, gcdCategoryDurationMsKey, detail.gcdCategoryDurationMs);
|
||||
PushTableValue(luaState, gcdCategoryRemainingMsKey, detail.gcdCategoryRemainingMs);
|
||||
PushTableInt(luaState, isOnGcdCategoryCooldownKey, detail.isOnGcdCategoryCooldown ? 1 : 0);
|
||||
PushTableValue(luaState, isOnGcdCategoryCooldownKey, detail.isOnGcdCategoryCooldown ? 1 : 0);
|
||||
}
|
||||
|
||||
CooldownDetail GetItemCooldownDetail(uint32_t itemId) {
|
||||
auto *itemStats = GetItemStats(itemId);
|
||||
|
||||
CooldownDetail best{};
|
||||
uint32_t bestRemaining = 0;
|
||||
CooldownDetail activeCooldownDetail{};
|
||||
uint32_t longestCooldownMs = 0;
|
||||
|
||||
if (itemStats) {
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
@@ -206,22 +241,17 @@ namespace Nampower {
|
||||
|
||||
auto const categoryOverride = static_cast<uint32_t>(itemStats->m_spellCategory[i]);
|
||||
auto const detail = GetCooldownFromSpellHistory(spellId, itemId, categoryOverride);
|
||||
if (!detail.isOnCooldown) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (detail.cooldownRemainingMs >= bestRemaining) {
|
||||
best = detail;
|
||||
bestRemaining = detail.cooldownRemainingMs;
|
||||
if (detail.cooldownRemainingMs >= longestCooldownMs) {
|
||||
activeCooldownDetail = detail;
|
||||
longestCooldownMs = detail.cooldownRemainingMs;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
activeCooldownDetail = GetCooldownFromSpellHistory(0, itemId, 0);
|
||||
}
|
||||
|
||||
if (!best.isOnCooldown) {
|
||||
best = GetCooldownFromSpellHistory(0, itemId, 0);
|
||||
}
|
||||
|
||||
return best;
|
||||
return activeCooldownDetail;
|
||||
}
|
||||
|
||||
uint32_t Script_GetSpellIdCooldown(uintptr_t *luaState) {
|
||||
@@ -266,4 +296,84 @@ namespace Nampower {
|
||||
PushCooldownDetailTable(luaState, cooldown);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool TrinketMatches(uint32_t itemId, uint32_t searchItemId, const char *searchItemName) {
|
||||
if (searchItemId != 0) {
|
||||
return itemId == searchItemId;
|
||||
}
|
||||
|
||||
if (searchItemName) {
|
||||
auto itemStats = GetItemStats(itemId);
|
||||
if (itemStats && itemStats->m_displayName[0]) {
|
||||
return _stricmp(itemStats->m_displayName[0], searchItemName) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t Script_GetTrinketCooldown(uintptr_t *luaState) {
|
||||
luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why
|
||||
|
||||
if (!lua_isnumber(luaState, 1) && !lua_isstring(luaState, 1)) {
|
||||
lua_error(luaState, "Usage: GetTrinketCooldown(slot|itemIdOrName)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t trinketInvSlot = 0;
|
||||
uint32_t searchItemId = 0;
|
||||
const char *searchItemName = nullptr;
|
||||
|
||||
if (lua_isnumber(luaState, 1)) {
|
||||
auto param = static_cast<uint32_t>(lua_tonumber(luaState, 1));
|
||||
if (param == 1 || param == 13) {
|
||||
trinketInvSlot = 12;
|
||||
} else if (param == 2 || param == 14) {
|
||||
trinketInvSlot = 13;
|
||||
} else {
|
||||
searchItemId = param;
|
||||
}
|
||||
} else {
|
||||
searchItemName = lua_tostring(luaState, 1);
|
||||
}
|
||||
|
||||
auto const getBagItem = reinterpret_cast<CGBag_C_GetItemAtSlotT>(Offsets::CGBag_C_GetItemAtSlot);
|
||||
auto playerGuid = game::ClntObjMgrGetActivePlayerGuid();
|
||||
auto playerUnit = game::GetObjectPtr(playerGuid);
|
||||
if (!playerUnit) {
|
||||
lua_pushnumber(luaState, -1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto inventory = game::GetPlayerInventoryPtr(playerUnit);
|
||||
|
||||
game::CGItem_C *item = nullptr;
|
||||
|
||||
if (trinketInvSlot == 12 || trinketInvSlot == 13) {
|
||||
item = getBagItem(inventory, trinketInvSlot);
|
||||
} else {
|
||||
for (uint32_t invSlot: {12u, 13u}) {
|
||||
auto candidate = getBagItem(inventory, invSlot);
|
||||
if (candidate && TrinketMatches(game::GetItemId(candidate), searchItemId, searchItemName)) {
|
||||
item = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if looking for trinket by name and not equipped, search bags to try to find item id
|
||||
if (!item && searchItemName != nullptr) {
|
||||
// look for item name in bags
|
||||
auto result = FindPlayerItem(0, searchItemName);
|
||||
if (result.found()) {
|
||||
searchItemId = game::GetItemId(result.item);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t itemId = (item) ? game::GetItemId(item) : searchItemId;
|
||||
auto const cooldown = GetItemCooldownDetail(itemId);
|
||||
|
||||
PushCooldownDetailTable(luaState, cooldown);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,4 +9,5 @@
|
||||
namespace Nampower {
|
||||
uint32_t Script_GetSpellIdCooldown(uintptr_t *luaState);
|
||||
uint32_t Script_GetItemIdCooldown(uintptr_t *luaState);
|
||||
uint32_t Script_GetTrinketCooldown(uintptr_t *luaState);
|
||||
}
|
||||
|
||||
+97
-2
@@ -34,6 +34,98 @@
|
||||
|
||||
namespace game {
|
||||
#pragma pack(push, 1)
|
||||
enum ItemClass
|
||||
{
|
||||
ITEM_CLASS_CONSUMABLE = 0,
|
||||
ITEM_CLASS_CONTAINER = 1,
|
||||
ITEM_CLASS_WEAPON = 2,
|
||||
ITEM_CLASS_GEM = 3,
|
||||
ITEM_CLASS_ARMOR = 4,
|
||||
ITEM_CLASS_REAGENT = 5,
|
||||
ITEM_CLASS_PROJECTILE = 6,
|
||||
ITEM_CLASS_TRADE_GOODS = 7,
|
||||
ITEM_CLASS_GENERIC = 8,
|
||||
ITEM_CLASS_RECIPE = 9,
|
||||
ITEM_CLASS_MONEY = 10,
|
||||
ITEM_CLASS_QUIVER = 11,
|
||||
ITEM_CLASS_QUEST = 12,
|
||||
ITEM_CLASS_KEY = 13,
|
||||
ITEM_CLASS_PERMANENT = 14,
|
||||
ITEM_CLASS_JUNK = 15
|
||||
};
|
||||
|
||||
enum ItemSubclassWeapon
|
||||
{
|
||||
ITEM_SUBCLASS_WEAPON_AXE = 0,
|
||||
ITEM_SUBCLASS_WEAPON_AXE2 = 1,
|
||||
ITEM_SUBCLASS_WEAPON_BOW = 2,
|
||||
ITEM_SUBCLASS_WEAPON_GUN = 3,
|
||||
ITEM_SUBCLASS_WEAPON_MACE = 4,
|
||||
ITEM_SUBCLASS_WEAPON_MACE2 = 5,
|
||||
ITEM_SUBCLASS_WEAPON_POLEARM = 6,
|
||||
ITEM_SUBCLASS_WEAPON_SWORD = 7,
|
||||
ITEM_SUBCLASS_WEAPON_SWORD2 = 8,
|
||||
ITEM_SUBCLASS_WEAPON_obsolete = 9,
|
||||
ITEM_SUBCLASS_WEAPON_STAFF = 10,
|
||||
ITEM_SUBCLASS_WEAPON_EXOTIC = 11,
|
||||
ITEM_SUBCLASS_WEAPON_EXOTIC2 = 12,
|
||||
ITEM_SUBCLASS_WEAPON_FIST = 13,
|
||||
ITEM_SUBCLASS_WEAPON_MISC = 14,
|
||||
ITEM_SUBCLASS_WEAPON_DAGGER = 15,
|
||||
ITEM_SUBCLASS_WEAPON_THROWN = 16,
|
||||
ITEM_SUBCLASS_WEAPON_SPEAR = 17,
|
||||
ITEM_SUBCLASS_WEAPON_CROSSBOW = 18,
|
||||
ITEM_SUBCLASS_WEAPON_WAND = 19,
|
||||
ITEM_SUBCLASS_WEAPON_FISHING_POLE = 20
|
||||
};
|
||||
|
||||
enum ItemSubclassArmor
|
||||
{
|
||||
ITEM_SUBCLASS_ARMOR_MISC = 0,
|
||||
ITEM_SUBCLASS_ARMOR_CLOTH = 1,
|
||||
ITEM_SUBCLASS_ARMOR_LEATHER = 2,
|
||||
ITEM_SUBCLASS_ARMOR_MAIL = 3,
|
||||
ITEM_SUBCLASS_ARMOR_PLATE = 4,
|
||||
ITEM_SUBCLASS_ARMOR_BUCKLER = 5,
|
||||
ITEM_SUBCLASS_ARMOR_SHIELD = 6,
|
||||
ITEM_SUBCLASS_ARMOR_LIBRAM = 7,
|
||||
ITEM_SUBCLASS_ARMOR_IDOL = 8,
|
||||
ITEM_SUBCLASS_ARMOR_TOTEM = 9
|
||||
};
|
||||
|
||||
enum InventoryType
|
||||
{
|
||||
INVTYPE_NON_EQUIP = 0,
|
||||
INVTYPE_HEAD = 1,
|
||||
INVTYPE_NECK = 2,
|
||||
INVTYPE_SHOULDERS = 3,
|
||||
INVTYPE_BODY = 4,
|
||||
INVTYPE_CHEST = 5,
|
||||
INVTYPE_WAIST = 6,
|
||||
INVTYPE_LEGS = 7,
|
||||
INVTYPE_FEET = 8,
|
||||
INVTYPE_WRISTS = 9,
|
||||
INVTYPE_HANDS = 10,
|
||||
INVTYPE_FINGER = 11,
|
||||
INVTYPE_TRINKET = 12,
|
||||
INVTYPE_WEAPON = 13,
|
||||
INVTYPE_SHIELD = 14,
|
||||
INVTYPE_RANGED = 15,
|
||||
INVTYPE_CLOAK = 16,
|
||||
INVTYPE_2HWEAPON = 17,
|
||||
INVTYPE_BAG = 18,
|
||||
INVTYPE_TABARD = 19,
|
||||
INVTYPE_ROBE = 20,
|
||||
INVTYPE_WEAPONMAINHAND = 21,
|
||||
INVTYPE_WEAPONOFFHAND = 22,
|
||||
INVTYPE_HOLDABLE = 23,
|
||||
INVTYPE_AMMO = 24,
|
||||
INVTYPE_THROWN = 25,
|
||||
INVTYPE_RANGEDRIGHT = 26,
|
||||
INVTYPE_QUIVER = 27,
|
||||
INVTYPE_RELIC = 28
|
||||
};
|
||||
|
||||
struct SpellRec {
|
||||
unsigned int Id;
|
||||
unsigned int School;
|
||||
@@ -345,7 +437,7 @@ namespace game {
|
||||
};
|
||||
|
||||
struct __declspec(align(4)) ItemStats_C {
|
||||
int m_class;
|
||||
ItemClass m_class;
|
||||
int m_subclass;
|
||||
char *m_displayName[4];
|
||||
int m_displayInfoID;
|
||||
@@ -353,7 +445,7 @@ namespace game {
|
||||
ItemStatsFlags m_flags;
|
||||
int m_buyPrice;
|
||||
int m_sellPrice;
|
||||
int m_inventoryType;
|
||||
InventoryType m_inventoryType;
|
||||
int m_allowableClass;
|
||||
int m_allowableRace;
|
||||
int m_itemLevel;
|
||||
@@ -1335,6 +1427,9 @@ namespace game {
|
||||
BUFF_REMOVED_OTHER = 558,
|
||||
|
||||
UNIT_DIED = 559,
|
||||
|
||||
AURA_CAST_ON_SELF = 560,
|
||||
AURA_CAST_ON_OTHER = 561,
|
||||
};
|
||||
|
||||
enum TypeMask {
|
||||
|
||||
+262
-121
@@ -5,21 +5,21 @@
|
||||
#include "logging.hpp"
|
||||
#include "offsets.hpp"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <array>
|
||||
|
||||
namespace Nampower {
|
||||
// Local cache for item name lookups
|
||||
static std::unordered_map<std::string, uint32_t> itemNameToIdCache;
|
||||
|
||||
// Reusable table references to reduce memory allocations
|
||||
static int basicItemInfoTableRef = LUA_REFNIL;
|
||||
static int itemInfoTableRef = LUA_REFNIL;
|
||||
static int equippedItemsTableRef = LUA_REFNIL;
|
||||
static int bagItemsTableRef = LUA_REFNIL;
|
||||
static int bagTableRef = LUA_REFNIL;
|
||||
static int trinketsTableRef = LUA_REFNIL;
|
||||
static constexpr uint32_t MAX_TRINKET_ENTRY_TABLES = 100;
|
||||
static std::array<int, 100> trinketEntryTableRefs{};
|
||||
static bool trinketEntryRefsInitialized = false;
|
||||
static uint32_t lastTrinketCount = 0;
|
||||
|
||||
// String keys used when pushing item data to Lua
|
||||
static char itemIdKey[] = "itemId";
|
||||
@@ -32,62 +32,17 @@ namespace Nampower {
|
||||
static char maxDurabilityKey[] = "maxDurability";
|
||||
static char tempEnchantmentTimeLeftMsKey[] = "tempEnchantmentTimeLeftMs";
|
||||
static char tempEnchantmentChargesKey[] = "tempEnchantmentCharges";
|
||||
|
||||
std::string ToLowerCase(const char *str) {
|
||||
std::string result;
|
||||
if (!str) return result;
|
||||
|
||||
while (*str) {
|
||||
result += static_cast<char>(tolower(*str));
|
||||
++str;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t GetItemIdFromCache(const char *itemName) {
|
||||
if (!itemName) return 0;
|
||||
|
||||
std::string lowerName = ToLowerCase(itemName);
|
||||
auto it = itemNameToIdCache.find(lowerName);
|
||||
if (it != itemNameToIdCache.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CacheItemNameToId(const char *itemName, uint32_t itemId) {
|
||||
if (!itemName || itemId == 0) return;
|
||||
|
||||
std::string lowerName = ToLowerCase(itemName);
|
||||
itemNameToIdCache[lowerName] = itemId;
|
||||
}
|
||||
|
||||
bool DoesItemMatch(uint32_t itemId, uint32_t searchItemId, const char *searchItemName) {
|
||||
if (searchItemId != 0) {
|
||||
return itemId == searchItemId;
|
||||
}
|
||||
|
||||
if (searchItemName) {
|
||||
auto itemStats = GetItemStats(itemId);
|
||||
if (itemStats && itemStats->m_displayName[0]) {
|
||||
bool matches = _stricmp(itemStats->m_displayName[0], searchItemName) == 0;
|
||||
if (matches) {
|
||||
CacheItemNameToId(searchItemName, itemId);
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
static char bagIndexKey[] = "bagIndex";
|
||||
static char slotIndexKey[] = "slotIndex";
|
||||
static char trinketNameKey[] = "trinketName";
|
||||
|
||||
void PushItemFoundResult(uintptr_t *luaState, int32_t bagIndex, uint32_t slot) {
|
||||
uint32_t adjustedSlot = slot;
|
||||
if (bagIndex == 0) {
|
||||
adjustedSlot = slot - 0x17; // subtract 23
|
||||
} else if (bagIndex == -1) {
|
||||
} else if (bagIndex == BANK_BAG_INDEX) {
|
||||
adjustedSlot = slot - 0x27; // subtract 39
|
||||
} else if (bagIndex == -2) {
|
||||
} else if (bagIndex == KEYRING_BAG_INDEX) {
|
||||
adjustedSlot = slot - 0x51; // subtract 81
|
||||
}
|
||||
|
||||
@@ -95,13 +50,6 @@ namespace Nampower {
|
||||
lua_pushnumber(luaState, static_cast<double>(adjustedSlot + 1)); // lua is 1 indexed
|
||||
}
|
||||
|
||||
uintptr_t *GetBagPtrFromContainer(uintptr_t *containerPtr) {
|
||||
auto vftable = game::GetObjectVFTable(containerPtr);
|
||||
using GetBagPtrT = uintptr_t * (__thiscall *)(uintptr_t *);
|
||||
auto getBagPtrFunc = reinterpret_cast<GetBagPtrT>(vftable[4]);
|
||||
return getBagPtrFunc(containerPtr);
|
||||
}
|
||||
|
||||
void CreateBasicItemInfoTable(uintptr_t *luaState, game::CGItem *cgItem) {
|
||||
if (!cgItem) {
|
||||
lua_pushnil(luaState);
|
||||
@@ -200,29 +148,263 @@ namespace Nampower {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto result = FindPlayerItem(searchItemId, searchItemName);
|
||||
if (!result.found()) {
|
||||
lua_pushnil(luaState);
|
||||
lua_pushnil(luaState);
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (result.bagIndex == EQUIPPED_BAG_INDEX) {
|
||||
lua_pushnil(luaState);
|
||||
lua_pushnumber(luaState, static_cast<double>(result.slot + 1));
|
||||
return 2;
|
||||
}
|
||||
|
||||
PushItemFoundResult(luaState, result.bagIndex, result.slot);
|
||||
return 2;
|
||||
}
|
||||
|
||||
uint32_t Script_UseItemIdOrName(uintptr_t *luaState) {
|
||||
luaState = GetLuaStatePtr();
|
||||
|
||||
if (!lua_isnumber(luaState, 1) && !lua_isstring(luaState, 1)) {
|
||||
lua_error(luaState, "Usage: UseItemIdOrName(itemIdOrName, [target])");
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t searchItemId = 0;
|
||||
const char *searchItemName = nullptr;
|
||||
|
||||
if (lua_isnumber(luaState, 1)) {
|
||||
searchItemId = static_cast<uint32_t>(lua_tonumber(luaState, 1));
|
||||
} else {
|
||||
searchItemName = lua_tostring(luaState, 1);
|
||||
uint32_t cachedItemId = GetItemIdFromCache(searchItemName);
|
||||
if (cachedItemId != 0) {
|
||||
searchItemId = cachedItemId;
|
||||
searchItemName = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t targetGuid = 0;
|
||||
if (lua_gettop(luaState) >= 2) {
|
||||
if (!lua_isnumber(luaState, 2) && !lua_isstring(luaState, 2)) {
|
||||
lua_error(luaState, "Usage: UseItemIdOrName(itemIdOrName, [target])");
|
||||
return 0;
|
||||
}
|
||||
|
||||
targetGuid = GetUnitGuidFromLuaParam(luaState, 2);
|
||||
if (targetGuid == 0) {
|
||||
lua_error(luaState, "Unable to determine target guid");
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
targetGuid = *reinterpret_cast<uint64_t *>(Offsets::LockedTargetGuid);
|
||||
if (targetGuid == 0) {
|
||||
targetGuid = game::ClntObjMgrGetActivePlayerGuid();
|
||||
}
|
||||
}
|
||||
|
||||
auto itemSearchResult = FindPlayerItem(searchItemId, searchItemName);
|
||||
if (!itemSearchResult.found()) {
|
||||
lua_pushnumber(luaState, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
using CGItem_C_UseT = uint32_t(__thiscall *)(game::CGItem_C *this_ptr, uint64_t *targetGuid, int useBindConfirm);
|
||||
auto const useItem = reinterpret_cast<CGItem_C_UseT>(Offsets::CGItem_C_Use);
|
||||
|
||||
auto const result = useItem(itemSearchResult.item, &targetGuid, 0);
|
||||
lua_pushnumber(luaState, result);
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint32_t Script_UseTrinket(uintptr_t *luaState) {
|
||||
luaState = GetLuaStatePtr();
|
||||
|
||||
if (!lua_isnumber(luaState, 1) && !lua_isstring(luaState, 1)) {
|
||||
lua_error(luaState, "Usage: UseTrinket(slot|itemIdOrName, [target])");
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t trinketInvSlot = 0;
|
||||
uint32_t searchItemId = 0;
|
||||
const char *searchItemName = nullptr;
|
||||
|
||||
if (lua_isnumber(luaState, 1)) {
|
||||
auto param = static_cast<uint32_t>(lua_tonumber(luaState, 1));
|
||||
if (param == 1 || param == 13) {
|
||||
trinketInvSlot = 12;
|
||||
} else if (param == 2 || param == 14) {
|
||||
trinketInvSlot = 13;
|
||||
} else {
|
||||
searchItemId = param;
|
||||
}
|
||||
} else {
|
||||
searchItemName = lua_tostring(luaState, 1);
|
||||
uint32_t cachedItemId = GetItemIdFromCache(searchItemName);
|
||||
if (cachedItemId != 0) {
|
||||
searchItemId = cachedItemId;
|
||||
searchItemName = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t targetGuid = 0;
|
||||
if (lua_gettop(luaState) >= 2) {
|
||||
if (!lua_isnumber(luaState, 2) && !lua_isstring(luaState, 2)) {
|
||||
lua_error(luaState, "Usage: UseTrinket(slot|itemIdOrName, [target])");
|
||||
return 0;
|
||||
}
|
||||
|
||||
targetGuid = GetUnitGuidFromLuaParam(luaState, 2);
|
||||
if (targetGuid == 0) {
|
||||
lua_error(luaState, "Unable to determine target guid");
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
targetGuid = *reinterpret_cast<uint64_t *>(Offsets::LockedTargetGuid);
|
||||
if (targetGuid == 0) {
|
||||
targetGuid = game::ClntObjMgrGetActivePlayerGuid();
|
||||
}
|
||||
}
|
||||
|
||||
auto const getBagItem = reinterpret_cast<CGBag_C_GetItemAtSlotT>(Offsets::CGBag_C_GetItemAtSlot);
|
||||
auto const getContainerGuid = reinterpret_cast<GetContainerGuidT>(Offsets::GetContainerGuid);
|
||||
auto playerGuid = game::ClntObjMgrGetActivePlayerGuid();
|
||||
auto playerUnit = game::GetObjectPtr(playerGuid);
|
||||
if (!playerUnit) {
|
||||
lua_pushnumber(luaState, -1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto inventory = game::GetPlayerInventoryPtr(playerUnit);
|
||||
|
||||
for (uint32_t slot = 0; slot <= 18; slot++) {
|
||||
auto item = getBagItem(inventory, slot);
|
||||
if (item && DoesItemMatch(game::GetItemId(item), searchItemId, searchItemName)) {
|
||||
game::CGItem_C *item = nullptr;
|
||||
|
||||
if (trinketInvSlot == 12 || trinketInvSlot == 13) {
|
||||
item = getBagItem(inventory, trinketInvSlot);
|
||||
} else {
|
||||
for (uint32_t slot : {12u, 13u}) {
|
||||
auto candidate = getBagItem(inventory, slot);
|
||||
if (candidate && DoesItemMatch(game::GetItemId(candidate), searchItemId, searchItemName)) {
|
||||
item = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!item) {
|
||||
lua_pushnumber(luaState, -1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
using CGItem_C_UseT = uint32_t(__thiscall *)(game::CGItem_C *this_ptr, uint64_t *targetGuid, int useBindConfirm);
|
||||
auto const useItem = reinterpret_cast<CGItem_C_UseT>(Offsets::CGItem_C_Use);
|
||||
|
||||
auto const result = useItem(item, &targetGuid, 0);
|
||||
lua_pushnumber(luaState, result);
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint32_t Script_GetTrinkets(uintptr_t *luaState) {
|
||||
luaState = GetLuaStatePtr();
|
||||
|
||||
auto const getBagItem = reinterpret_cast<CGBag_C_GetItemAtSlotT>(Offsets::CGBag_C_GetItemAtSlot);
|
||||
auto const getContainerGuid = reinterpret_cast<GetContainerGuidT>(Offsets::GetContainerGuid);
|
||||
|
||||
auto playerGuid = game::ClntObjMgrGetActivePlayerGuid();
|
||||
auto playerUnit = game::GetObjectPtr(playerGuid);
|
||||
|
||||
bool copyTable = false;
|
||||
if (lua_isnumber(luaState, 1)) {
|
||||
copyTable = static_cast<int>(lua_tonumber(luaState, 1)) != 0;
|
||||
}
|
||||
|
||||
if (!trinketEntryRefsInitialized) {
|
||||
trinketEntryTableRefs.fill(LUA_REFNIL);
|
||||
trinketEntryRefsInitialized = true;
|
||||
}
|
||||
|
||||
if (!copyTable && trinketsTableRef == LUA_REFNIL) {
|
||||
lua_newtable(luaState);
|
||||
trinketsTableRef = luaL_ref(luaState, LUA_REGISTRYINDEX);
|
||||
}
|
||||
|
||||
if (copyTable) {
|
||||
lua_newtable(luaState);
|
||||
} else {
|
||||
lua_rawgeti(luaState, LUA_REGISTRYINDEX, trinketsTableRef);
|
||||
for (uint32_t i = 1; i <= lastTrinketCount; ++i) {
|
||||
lua_pushnumber(luaState, static_cast<double>(i));
|
||||
lua_pushnil(luaState);
|
||||
lua_pushnumber(luaState, static_cast<double>(slot+1)); // lua is 1 indexed
|
||||
return 2;
|
||||
lua_settable(luaState, -3);
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t slot = 23; slot <= 38; slot++) {
|
||||
if (!playerUnit) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto inventory = game::GetPlayerInventoryPtr(playerUnit);
|
||||
uint32_t luaIndex = 1;
|
||||
|
||||
auto pushTrinket = [&](int32_t bagIndex, uint32_t slot, game::CGItem_C *item) {
|
||||
if (!item) return;
|
||||
|
||||
auto itemId = game::GetItemId(item);
|
||||
auto itemStats = GetItemStats(itemId);
|
||||
if (!itemStats || itemStats->m_inventoryType != game::INVTYPE_TRINKET) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t luaSlot = slot + 1;
|
||||
if (bagIndex == 0) {
|
||||
luaSlot = slot - 0x17 + 1; // backpack absolute slots 23-38
|
||||
} else if (bagIndex == BANK_BAG_INDEX) {
|
||||
luaSlot = slot - 0x27 + 1; // bank absolute slots 39-62
|
||||
} else if (bagIndex == KEYRING_BAG_INDEX) {
|
||||
luaSlot = slot - 0x51 + 1; // keyring absolute slots 81-96
|
||||
}
|
||||
|
||||
lua_pushnumber(luaState, static_cast<double>(luaIndex++));
|
||||
if (copyTable || luaIndex - 2 >= MAX_TRINKET_ENTRY_TABLES) {
|
||||
lua_newtable(luaState);
|
||||
} else {
|
||||
auto &entryRef = trinketEntryTableRefs[luaIndex - 2];
|
||||
if (entryRef == LUA_REFNIL) {
|
||||
lua_newtable(luaState);
|
||||
entryRef = luaL_ref(luaState, LUA_REGISTRYINDEX);
|
||||
}
|
||||
lua_rawgeti(luaState, LUA_REGISTRYINDEX, entryRef);
|
||||
}
|
||||
PushTableValue(luaState, itemIdKey, itemId);
|
||||
if (bagIndex == EQUIPPED_BAG_INDEX) {
|
||||
lua_pushstring(luaState, bagIndexKey);
|
||||
lua_pushnil(luaState);
|
||||
lua_settable(luaState, -3);
|
||||
} else {
|
||||
PushTableValue(luaState, bagIndexKey, bagIndex);
|
||||
}
|
||||
PushTableValue(luaState, slotIndexKey, luaSlot);
|
||||
const char *trinketName = itemStats->m_displayName[0] ? itemStats->m_displayName[0] : "Unknown";
|
||||
PushTableValue(luaState, trinketNameKey, trinketName);
|
||||
lua_settable(luaState, -3);
|
||||
};
|
||||
|
||||
// Equipped trinket slots (0-based slots 12 and 13)
|
||||
for (uint32_t slot : {12u, 13u}) {
|
||||
auto item = getBagItem(inventory, slot);
|
||||
if (item && DoesItemMatch(game::GetItemId(item), searchItemId, searchItemName)) {
|
||||
PushItemFoundResult(luaState, 0, slot);
|
||||
return 2;
|
||||
}
|
||||
pushTrinket(EQUIPPED_BAG_INDEX, slot, item);
|
||||
}
|
||||
|
||||
for (int32_t bagIndex = 1; bagIndex <= 4; bagIndex++) {
|
||||
// Backpack (bagIndex 0, absolute slots 23-38)
|
||||
for (uint32_t slot = 23; slot <= 38; ++slot) {
|
||||
auto item = getBagItem(inventory, slot);
|
||||
pushTrinket(0, slot, item);
|
||||
}
|
||||
|
||||
// Equipped bags 1-4
|
||||
for (int32_t bagIndex = 1; bagIndex <= 4; ++bagIndex) {
|
||||
uint64_t containerGuid = getContainerGuid(bagIndex - 1); // bagIndex 1-4 maps to container 0-3
|
||||
if (containerGuid == 0) continue;
|
||||
|
||||
@@ -233,58 +415,17 @@ namespace Nampower {
|
||||
if (!bagPtr) continue;
|
||||
|
||||
auto bagSize = *bagPtr;
|
||||
for (uint32_t slot = 0; slot < bagSize; slot++) {
|
||||
for (uint32_t slot = 0; slot < bagSize; ++slot) {
|
||||
auto item = getBagItem(bagPtr, slot);
|
||||
if (item && DoesItemMatch(game::GetItemId(item), searchItemId, searchItemName)) {
|
||||
PushItemFoundResult(luaState, bagIndex, slot);
|
||||
return 2;
|
||||
}
|
||||
pushTrinket(bagIndex, slot, item);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t bankGuid = *reinterpret_cast<uint64_t *>(Offsets::BankGuid);
|
||||
|
||||
if (bankGuid > 0) {
|
||||
for (uint32_t slot = 39; slot <= 62; slot++) {
|
||||
auto item = getBagItem(inventory, slot);
|
||||
if (item && DoesItemMatch(game::GetItemId(item), searchItemId, searchItemName)) {
|
||||
PushItemFoundResult(luaState, -1, slot);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
for (int32_t bagIndex = 5; bagIndex <= 9; bagIndex++) {
|
||||
uint64_t containerGuid = getContainerGuid(bagIndex - 1); // bagIndex 5-9 maps to container 4-8
|
||||
if (containerGuid == 0) continue;
|
||||
|
||||
auto containerPtr = game::ClntObjMgrObjectPtr(game::TYPEMASK_CONTAINER, containerGuid);
|
||||
if (!containerPtr) continue;
|
||||
|
||||
auto bagPtr = GetBagPtrFromContainer(containerPtr);
|
||||
if (!bagPtr) continue;
|
||||
|
||||
auto bagSize = *bagPtr;
|
||||
for (uint32_t slot = 0; slot < bagSize; slot++) {
|
||||
auto item = getBagItem(bagPtr, slot);
|
||||
if (item && DoesItemMatch(game::GetItemId(item), searchItemId, searchItemName)) {
|
||||
PushItemFoundResult(luaState, bagIndex, slot);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!copyTable) {
|
||||
lastTrinketCount = luaIndex - 1;
|
||||
}
|
||||
|
||||
for (uint32_t slot = 81; slot <= 96; slot++) {
|
||||
auto item = getBagItem(inventory, slot);
|
||||
if (item && DoesItemMatch(game::GetItemId(item), searchItemId, searchItemName)) {
|
||||
PushItemFoundResult(luaState, -2, slot);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
lua_pushnil(luaState);
|
||||
lua_pushnil(luaState);
|
||||
return 2;
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint32_t Script_GetEquippedItems(uintptr_t *luaState) {
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
|
||||
namespace Nampower {
|
||||
uint32_t Script_FindPlayerItemSlot(uintptr_t *luaState);
|
||||
uint32_t Script_UseItemIdOrName(uintptr_t *luaState);
|
||||
uint32_t Script_UseTrinket(uintptr_t *luaState);
|
||||
uint32_t Script_GetTrinkets(uintptr_t *luaState);
|
||||
uint32_t Script_GetEquippedItems(uintptr_t *luaState);
|
||||
uint32_t Script_GetEquippedItem(uintptr_t *luaState);
|
||||
uint32_t Script_GetBagItem(uintptr_t *luaState);
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include "logging.hpp"
|
||||
#include "helper.hpp"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@@ -19,6 +21,9 @@ namespace Nampower {
|
||||
// Global dictionary to store itemId -> ItemStats_C mappings
|
||||
static std::unordered_map<uint32_t, game::ItemStats_C *> itemStatsCache;
|
||||
|
||||
// Local cache for item name lookups
|
||||
static std::unordered_map<std::string, uint32_t> itemNameToIdCache;
|
||||
|
||||
// Track pending async loads (can have multiple at once)
|
||||
static std::unordered_set<uint32_t> pendingItemIds;
|
||||
|
||||
@@ -31,6 +36,169 @@ namespace Nampower {
|
||||
auto const getRow = reinterpret_cast<DBCache_ItemCacheDBGetRowT>(Offsets::DBCache_ItemCacheDBGetRow);
|
||||
auto const itemCache = reinterpret_cast<void *>(Offsets::ItemDBCache);
|
||||
|
||||
std::string ToLowerCase(const char *str) {
|
||||
std::string result;
|
||||
if (!str) return result;
|
||||
|
||||
while (*str) {
|
||||
result += static_cast<char>(tolower(*str));
|
||||
++str;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t GetItemIdFromCache(const char *itemName) {
|
||||
if (!itemName) return 0;
|
||||
|
||||
std::string lowerName = ToLowerCase(itemName);
|
||||
auto it = itemNameToIdCache.find(lowerName);
|
||||
if (it != itemNameToIdCache.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CacheItemNameToId(const char *itemName, uint32_t itemId) {
|
||||
if (!itemName || itemId == 0) return;
|
||||
|
||||
std::string lowerName = ToLowerCase(itemName);
|
||||
itemNameToIdCache[lowerName] = itemId;
|
||||
}
|
||||
|
||||
bool DoesItemMatch(uint32_t itemId, uint32_t searchItemId, const char *searchItemName) {
|
||||
if (searchItemId != 0) {
|
||||
return itemId == searchItemId;
|
||||
}
|
||||
|
||||
if (searchItemName) {
|
||||
auto itemStats = GetItemStats(itemId);
|
||||
if (itemStats && itemStats->m_displayName[0]) {
|
||||
bool matches = _stricmp(itemStats->m_displayName[0], searchItemName) == 0;
|
||||
if (matches) {
|
||||
CacheItemNameToId(searchItemName, itemId);
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uintptr_t *GetBagPtrFromContainer(uintptr_t *containerPtr) {
|
||||
auto vftable = game::GetObjectVFTable(containerPtr);
|
||||
using GetBagPtrT = uintptr_t * (__thiscall *)(uintptr_t *);
|
||||
auto getBagPtrFunc = reinterpret_cast<GetBagPtrT>(vftable[4]);
|
||||
return getBagPtrFunc(containerPtr);
|
||||
}
|
||||
|
||||
PlayerItemSearchResult FindPlayerItem(uint32_t searchItemId, const char *searchItemName) {
|
||||
PlayerItemSearchResult result{};
|
||||
|
||||
auto const getBagItem = reinterpret_cast<CGBag_C_GetItemAtSlotT>(Offsets::CGBag_C_GetItemAtSlot);
|
||||
auto const getContainerGuid = reinterpret_cast<GetContainerGuidT>(Offsets::GetContainerGuid);
|
||||
|
||||
auto playerGuid = game::ClntObjMgrGetActivePlayerGuid();
|
||||
auto playerUnit = game::GetObjectPtr(playerGuid);
|
||||
if (!playerUnit) {
|
||||
return result;
|
||||
}
|
||||
|
||||
auto inventory = game::GetPlayerInventoryPtr(playerUnit);
|
||||
|
||||
auto matchesItem = [&](game::CGItem_C *item) {
|
||||
return item && DoesItemMatch(game::GetItemId(item), searchItemId, searchItemName);
|
||||
};
|
||||
|
||||
for (uint32_t slot = 0; slot <= 18; slot++) {
|
||||
auto item = getBagItem(inventory, slot);
|
||||
if (matchesItem(item)) {
|
||||
result.item = item;
|
||||
result.bagIndex = EQUIPPED_BAG_INDEX;
|
||||
result.slot = slot;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t slot = 23; slot <= 38; slot++) {
|
||||
auto item = getBagItem(inventory, slot);
|
||||
if (matchesItem(item)) {
|
||||
result.item = item;
|
||||
result.bagIndex = 0;
|
||||
result.slot = slot;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
for (int32_t bagIndex = 1; bagIndex <= 4; bagIndex++) {
|
||||
uint64_t containerGuid = getContainerGuid(bagIndex - 1);
|
||||
if (containerGuid == 0) continue;
|
||||
|
||||
auto containerPtr = game::ClntObjMgrObjectPtr(game::TYPEMASK_CONTAINER, containerGuid);
|
||||
if (!containerPtr) continue;
|
||||
|
||||
auto bagPtr = GetBagPtrFromContainer(containerPtr);
|
||||
if (!bagPtr) continue;
|
||||
|
||||
auto bagSize = *bagPtr;
|
||||
for (uint32_t slot = 0; slot < bagSize; slot++) {
|
||||
auto item = getBagItem(bagPtr, slot);
|
||||
if (matchesItem(item)) {
|
||||
result.item = item;
|
||||
result.bagIndex = bagIndex;
|
||||
result.slot = slot;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t bankGuid = *reinterpret_cast<uint64_t *>(Offsets::BankGuid);
|
||||
if (bankGuid > 0) {
|
||||
for (uint32_t slot = 39; slot <= 62; slot++) {
|
||||
auto item = getBagItem(inventory, slot);
|
||||
if (matchesItem(item)) {
|
||||
result.item = item;
|
||||
result.bagIndex = BANK_BAG_INDEX;
|
||||
result.slot = slot;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
for (int32_t bagIndex = 5; bagIndex <= 9; bagIndex++) {
|
||||
uint64_t containerGuid = getContainerGuid(bagIndex - 1); // bagIndex 5-9 maps to container 4-8
|
||||
if (containerGuid == 0) continue;
|
||||
|
||||
auto containerPtr = game::ClntObjMgrObjectPtr(game::TYPEMASK_CONTAINER, containerGuid);
|
||||
if (!containerPtr) continue;
|
||||
|
||||
auto bagPtr = GetBagPtrFromContainer(containerPtr);
|
||||
if (!bagPtr) continue;
|
||||
|
||||
auto bagSize = *bagPtr;
|
||||
for (uint32_t slot = 0; slot < bagSize; slot++) {
|
||||
auto item = getBagItem(bagPtr, slot);
|
||||
if (matchesItem(item)) {
|
||||
result.item = item;
|
||||
result.bagIndex = bagIndex;
|
||||
result.slot = slot;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t slot = 81; slot <= 96; slot++) {
|
||||
auto item = getBagItem(inventory, slot);
|
||||
if (matchesItem(item)) {
|
||||
result.item = item;
|
||||
result.bagIndex = KEYRING_BAG_INDEX;
|
||||
result.slot = slot;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string escapeJsonString(const char *str) {
|
||||
if (!str) return "null";
|
||||
|
||||
|
||||
@@ -11,9 +11,26 @@ namespace Nampower {
|
||||
|
||||
using DBCache_ItemCacheDBGetRowT = uint32_t * (__thiscall *)(void *this_ptr, uint32_t itemId, uint64_t *guid, TooltipItemStatsCallbackT callback, uintptr_t *userData, bool requestIfMissing);
|
||||
|
||||
constexpr int32_t EQUIPPED_BAG_INDEX = -3;
|
||||
constexpr int32_t BANK_BAG_INDEX = -1;
|
||||
constexpr int32_t KEYRING_BAG_INDEX = -2;
|
||||
|
||||
struct PlayerItemSearchResult {
|
||||
game::CGItem_C *item = nullptr;
|
||||
int32_t bagIndex = 0; // -3 equipped, -1 bank main slots, -2 keyring, 0 backpack, 1-9 bag indices
|
||||
uint32_t slot = 0;
|
||||
|
||||
bool found() const { return item != nullptr; }
|
||||
};
|
||||
|
||||
void ExportAllItems();
|
||||
bool LoadItem(uint32_t itemId); // Returns true if item needs async load (caller should wait)
|
||||
bool ProcessItemExport(); // Process one item export per frame, returns true if still exporting
|
||||
game::ItemStats_C* GetItemStats(uint32_t itemId);
|
||||
uint32_t GetItemIdFromCache(const char *itemName);
|
||||
void CacheItemNameToId(const char *itemName, uint32_t itemId);
|
||||
bool DoesItemMatch(uint32_t itemId, uint32_t searchItemId, const char *searchItemName);
|
||||
PlayerItemSearchResult FindPlayerItem(uint32_t searchItemId, const char *searchItemName);
|
||||
uintptr_t *GetBagPtrFromContainer(uintptr_t *containerPtr);
|
||||
|
||||
}
|
||||
|
||||
@@ -631,6 +631,9 @@ namespace Nampower {
|
||||
} else if (strcmp(cvar, "NP_SpamProtectionEnabled") == 0) {
|
||||
gUserSettings.spamProtectionEnabled = atoi(value) != 0;
|
||||
DEBUG_LOG("Set NP_SpamProtectionEnabled to " << gUserSettings.spamProtectionEnabled);
|
||||
} else if (strcmp(cvar, "NP_EnableAuraCastEvents") == 0) {
|
||||
gUserSettings.enableAuraCastEvents = atoi(value) != 0;
|
||||
DEBUG_LOG("Set NP_EnableAuraCastEvents to " << gUserSettings.enableAuraCastEvents);
|
||||
} else if (strcmp(cvar, "NP_MinBufferTimeMs") == 0) {
|
||||
gUserSettings.minBufferTimeMs = atoi(value);
|
||||
DEBUG_LOG("Set NP_MinBufferTimeMs and current buffer to " << gUserSettings.minBufferTimeMs);
|
||||
@@ -771,6 +774,7 @@ namespace Nampower {
|
||||
gUserSettings.quickcastOnDoubleCast = false;
|
||||
|
||||
gUserSettings.spamProtectionEnabled = true;
|
||||
gUserSettings.enableAuraCastEvents = false;
|
||||
|
||||
gUserSettings.minBufferTimeMs = 55; // time in ms to buffer cast to minimize server failure
|
||||
gUserSettings.nonGcdBufferTimeMs = 100; // time in ms to buffer non-GCD spells to minimize server failure
|
||||
@@ -954,6 +958,16 @@ namespace Nampower {
|
||||
0, // unk2
|
||||
0); // unk3
|
||||
|
||||
char NP_EnableAuraCastEvents[] = "NP_EnableAuraCastEvents";
|
||||
CVarRegister(NP_EnableAuraCastEvents, // name
|
||||
nullptr, // help
|
||||
0, // unk1
|
||||
gUserSettings.enableAuraCastEvents ? defaultTrue : defaultFalse, // default value address
|
||||
nullptr, // callback
|
||||
1, // category
|
||||
0, // unk2
|
||||
0); // unk3
|
||||
|
||||
char NP_OnSwingBufferCooldownMs[] = "NP_OnSwingBufferCooldownMs";
|
||||
CVarRegister(NP_OnSwingBufferCooldownMs, // name
|
||||
nullptr, // help
|
||||
@@ -1079,6 +1093,7 @@ namespace Nampower {
|
||||
loadUserVar("NP_DoubleCastToEndChannelEarly");
|
||||
|
||||
loadUserVar("NP_SpamProtectionEnabled");
|
||||
loadUserVar("NP_EnableAuraCastEvents");
|
||||
|
||||
loadUserVar("NP_MinBufferTimeMs");
|
||||
loadUserVar("NP_NonGcdBufferTimeMs");
|
||||
@@ -1221,6 +1236,12 @@ namespace Nampower {
|
||||
|
||||
char UNIT_DIED[] = "UNIT_DIED";
|
||||
addCustomEvent(game::UNIT_DIED, UNIT_DIED);
|
||||
|
||||
char AURA_CAST_ON_SELF[] = "AURA_CAST_ON_SELF";
|
||||
addCustomEvent(game::AURA_CAST_ON_SELF, AURA_CAST_ON_SELF);
|
||||
|
||||
char AURA_CAST_ON_OTHER[] = "AURA_CAST_ON_OTHER";
|
||||
addCustomEvent(game::AURA_CAST_ON_OTHER, AURA_CAST_ON_OTHER);
|
||||
}
|
||||
|
||||
void FrameScript_CreateEventsHook(hadesmem::PatchDetourBase *detour, int param_1, uint32_t maxEventId) {
|
||||
@@ -1334,6 +1355,19 @@ namespace Nampower {
|
||||
|
||||
char getItemIdCooldown[] = "GetItemIdCooldown";
|
||||
RegisterLuaFunction(getItemIdCooldown, reinterpret_cast<uintptr_t *>(Script_GetItemIdCooldown));
|
||||
|
||||
char getTrinketCooldown[] = "GetTrinketCooldown";
|
||||
RegisterLuaFunction(getTrinketCooldown, reinterpret_cast<uintptr_t *>(Script_GetTrinketCooldown));
|
||||
|
||||
// 2.19 additions
|
||||
char useItemIdOrName[] = "UseItemIdOrName";
|
||||
RegisterLuaFunction(useItemIdOrName, reinterpret_cast<uintptr_t *>(Script_UseItemIdOrName));
|
||||
|
||||
char useTrinket[] = "UseTrinket";
|
||||
RegisterLuaFunction(useTrinket, reinterpret_cast<uintptr_t *>(Script_UseTrinket));
|
||||
|
||||
char getTrinkets[] = "GetTrinkets";
|
||||
RegisterLuaFunction(getTrinkets, reinterpret_cast<uintptr_t *>(Script_GetTrinkets));
|
||||
}
|
||||
|
||||
std::once_flag loadFlag;
|
||||
|
||||
+2
-1
@@ -30,7 +30,7 @@ namespace Nampower {
|
||||
constexpr uint32_t BUFFER_DECREASE_FREQUENCY = 10000; // time in ms between changes to lower buffer
|
||||
|
||||
constexpr uint32_t MAJOR_VERSION = 2;
|
||||
constexpr uint32_t MINOR_VERSION = 18;
|
||||
constexpr uint32_t MINOR_VERSION = 19;
|
||||
constexpr uint32_t PATCH_VERSION = 0;
|
||||
|
||||
constexpr int32_t LUA_REGISTRYINDEX = -10000;
|
||||
@@ -102,6 +102,7 @@ namespace Nampower {
|
||||
|
||||
using CGPlayer_C_OnAttackIconPressedT = int (__fastcall *)(uintptr_t *this_ptr, void *dummy_edx, uint64_t guid);
|
||||
using CGActionBar_UseActionT = void (__fastcall *)(uint32_t param_1, int param_2, int param_3);
|
||||
using CGCharacterInfo_UseItemT = void (__fastcall *)(uintptr_t *this_ptr, void *dummy_edx, uint32_t itemSlot, uint64_t *targetGuid);
|
||||
|
||||
using GetSpellSlotAndTypeT = uint32_t (__fastcall *)(const char *, uint32_t *);
|
||||
using GetSpellSlotFromLuaT = uint32_t (__fastcall *)(int param_1, uint32_t *slot, uint32_t *type);
|
||||
|
||||
@@ -140,6 +140,8 @@ enum class Offsets : std::uint32_t {
|
||||
Script_SetCVar = 0x00488C10,
|
||||
Script_RunScript = 0x0048B980,
|
||||
Script_SpellStopCasting = 0x006E6E80,
|
||||
Script_UseInventoryItem = 0x004c8de0,
|
||||
Script_UseContainerItem = 0x004fa0e0,
|
||||
|
||||
SStrDupA = 0X0064A620,
|
||||
|
||||
@@ -197,6 +199,10 @@ enum class Offsets : std::uint32_t {
|
||||
|
||||
CGPlayer_C_OnAttackIconPressed = 0X006131A0,
|
||||
|
||||
CGCharacterInfo_UseItem = 0x004c7970,
|
||||
|
||||
CGItem_C_Use = 0x005D8D00,
|
||||
|
||||
DBCache_ItemCacheDBGetRow = 0x0055BA30,
|
||||
|
||||
InvalidFunctionPtrCheck = 0x0042A320,
|
||||
|
||||
+119
-2
@@ -855,6 +855,117 @@ namespace Nampower {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool doesSpellApplyAura(const game::SpellRec *spell) {
|
||||
for (unsigned int i : spell->Effect) {
|
||||
switch (i) {
|
||||
case game::SPELL_EFFECT_APPLY_AURA:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_PARTY:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_RAID:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_FRIEND:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_ENEMY:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_PET:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void TriggerAuraCastEvent(const game::SpellRec *spell,
|
||||
uint64_t casterGuid,
|
||||
uint64_t targetGuid,
|
||||
uint64_t activePlayerGuid,
|
||||
bool castByActivePlayer) {
|
||||
auto eventToTrigger = game::AURA_CAST_ON_OTHER;
|
||||
if (targetGuid == activePlayerGuid) {
|
||||
eventToTrigger = game::AURA_CAST_ON_SELF;
|
||||
}
|
||||
|
||||
// ignore modifiers if not cast by active player
|
||||
auto duration = game::GetSpellDuration(spell, !castByActivePlayer);
|
||||
|
||||
auto *targetUnit = game::ClntObjMgrObjectPtr(
|
||||
static_cast<game::TypeMask>(game::TYPEMASK_PLAYER | game::TYPEMASK_UNIT), targetGuid);
|
||||
|
||||
bool targetIsBuffCapped = false;
|
||||
bool targetIsDebuffCapped = false;
|
||||
|
||||
if (targetUnit) {
|
||||
auto *unitFields = *reinterpret_cast<game::UnitFields **>(targetUnit + 68);
|
||||
if (unitFields) {
|
||||
targetIsBuffCapped = true;
|
||||
for (int i = 31; i >= 0; --i) {
|
||||
if (unitFields->aura[i] == 0) {
|
||||
targetIsBuffCapped = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
targetIsDebuffCapped = true;
|
||||
for (int i = 47; i >= 32; --i) {
|
||||
if (unitFields->aura[i] == 0) {
|
||||
targetIsDebuffCapped = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char *casterGuidStr = ConvertGuidToString(casterGuid);
|
||||
char *targetGuidStr = ConvertGuidToString(targetGuid);
|
||||
char format[] = "%d%s%s%d%d%d%d%d%d";
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
auto const effectType = spell->Effect[i];
|
||||
switch (effectType) {
|
||||
case game::SPELL_EFFECT_APPLY_AURA:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_PARTY:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_RAID:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_FRIEND:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_ENEMY:
|
||||
case game::SPELL_EFFECT_APPLY_AREA_AURA_PET: {
|
||||
auto auraName = spell->EffectApplyAuraName[i];
|
||||
auto effectAmplitude = spell->EffectAmplitude[i];
|
||||
auto effectMiscValue = spell->EffectMiscValue[i];
|
||||
|
||||
auto auraCapStatus = static_cast<uint32_t>(targetIsBuffCapped) |
|
||||
(static_cast<uint32_t>(targetIsDebuffCapped) << 1);
|
||||
|
||||
((int (__cdecl *)(int eventCode,
|
||||
char *fmt,
|
||||
uint32_t spellIdParam,
|
||||
char *casterGuidStrParam,
|
||||
char *targetGuidStrParam,
|
||||
uint32_t effectParam,
|
||||
uint32_t auraNameParam,
|
||||
uint32_t effectAmplitudeParam,
|
||||
uint32_t effectMiscValueParam,
|
||||
uint32_t durationParam,
|
||||
uint32_t auraCapStatusParam)) Offsets::SignalEventParam)(
|
||||
eventToTrigger,
|
||||
format,
|
||||
spell->Id,
|
||||
casterGuidStr,
|
||||
targetGuidStr,
|
||||
effectType,
|
||||
auraName,
|
||||
effectAmplitude,
|
||||
effectMiscValue,
|
||||
duration,
|
||||
auraCapStatus);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
delete[] casterGuidStr;
|
||||
delete[] targetGuidStr;
|
||||
}
|
||||
|
||||
void
|
||||
SpellGoHook(hadesmem::PatchDetourBase *detour, uint64_t *casterGUID, uint64_t *targetGUID,
|
||||
uint32_t spellId,
|
||||
@@ -862,14 +973,15 @@ namespace Nampower {
|
||||
auto const spellGo = detour->GetTrampolineT<SpellGoT>();
|
||||
spellGo(casterGUID, targetGUID, spellId, spellData);
|
||||
|
||||
auto const castByActivePlayer = game::ClntObjMgrGetActivePlayerGuid() == *casterGUID;
|
||||
auto const activePlayerGuid = game::ClntObjMgrGetActivePlayerGuid();
|
||||
auto const castByActivePlayer = activePlayerGuid == *casterGUID;
|
||||
auto const spell = game::GetSpellInfo(spellId);
|
||||
|
||||
if (castByActivePlayer) {
|
||||
auto const currentTime = GetTime();
|
||||
// only care about our own casts
|
||||
if (!gCastData.channeling) {
|
||||
// check if spell is on swing
|
||||
auto const spell = game::GetSpellInfo(spellId);
|
||||
if (spell->Attributes & game::SPELL_ATTR_ON_NEXT_SWING_1) {
|
||||
gLastCastData.onSwingStartTimeMs = currentTime;
|
||||
|
||||
@@ -890,6 +1002,11 @@ namespace Nampower {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gUserSettings.enableAuraCastEvents && doesSpellApplyAura(spell)) {
|
||||
auto targetGuidVal = targetGUID ? *targetGUID : *casterGUID;
|
||||
TriggerAuraCastEvent(spell, *casterGUID, targetGuidVal, activePlayerGuid, castByActivePlayer);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -28,6 +28,7 @@ struct UserSettings {
|
||||
bool quickcastOnDoubleCast;
|
||||
|
||||
bool spamProtectionEnabled;
|
||||
bool enableAuraCastEvents;
|
||||
|
||||
uint32_t spellQueueWindowMs;
|
||||
uint32_t onSwingBufferCooldownMs;
|
||||
|
||||
Reference in New Issue
Block a user