add UseItemIdOrName, UseTrinket, GetTrinkets, and AURA_CAST events

This commit is contained in:
avitasia
2025-12-18 13:23:55 -08:00
parent 163b4db361
commit 68512b73fb
15 changed files with 971 additions and 156 deletions
+17 -1
View File
@@ -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)
```
```
+7 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
}
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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) {
+3
View File
@@ -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);
+168
View File
@@ -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";
+17
View File
@@ -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);
}
+34
View File
@@ -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
View File
@@ -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);
+6
View File
@@ -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
View File
@@ -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
+1
View File
@@ -28,6 +28,7 @@ struct UserSettings {
bool quickcastOnDoubleCast;
bool spamProtectionEnabled;
bool enableAuraCastEvents;
uint32_t spellQueueWindowMs;
uint32_t onSwingBufferCooldownMs;