diff --git a/SCRIPTS.md b/SCRIPTS.md index 2992364..4353334 100644 --- a/SCRIPTS.md +++ b/SCRIPTS.md @@ -5,6 +5,7 @@ This document describes all custom Lua functions and events added by Nampower. For installation, configuration, and general usage information, see the main [README.md](README.md). ## Table of Contents +- [Performance Optimization - Table References](#performance-optimization---table-references) - [Custom Lua Functions](#custom-lua-functions) - [Spell/Item/Unit Information](#spellitemunit-information) - [Spell Casting and Queuing](#spell-casting-and-queuing) @@ -12,18 +13,117 @@ For installation, configuration, and general usage information, see the main [RE - [Cooldown Information](#cooldown-information) --- +## Performance Optimization - Table References + +Nampower functions that return tables use **reusable table references** to reduce memory allocations and improve performance. This means the same table object is reused across multiple function calls, with its contents updated each time. + +### Functions Using Reusable Table References + +The following functions use reusable table references: + +- **`GetCastInfo()`** - Returns cast information table +- **`GetEquippedItems([unitToken])`** - Returns equipped items table +- **`GetBagItems()`** - Returns bag items table +- **`GetBagItem(bagIndex, slot)`** - Returns item info table +- **`GetEquippedItem(unitToken, slot)`** - Returns item info table +- **`GetSpellIdCooldown(spellId)`** - Returns cooldown detail table +- **`GetItemIdCooldown(itemId)`** - Returns cooldown detail table +- +- **`GetItemStats(itemId, [copy])`** - Returns item stats table +- **`GetUnitData(unitToken, [copy])`** - Returns unit data table +- **`GetSpellRec(spellId, [copy])`** - Returns spell record table +- **`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 + +**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. + +**Note:** Functions like `GetItemStats`, `GetUnitData`, and `GetSpellRec` also use reusable references for their nested array fields (e.g., `bonusStat`, `auras`, `EffectImplicitTargetA`). Each nested array field name has its own dedicated reference that is reused across calls. + +```lua +-- ✓ SAFE - Extract values immediately +local castInfo = GetCastInfo() +if castInfo then + local spellId = castInfo.spellId + local castEnd = castInfo.castEndS + -- Use spellId and castEnd later +end + +-- ✓ SAFE - Extract nested array values immediately +local itemStats = GetItemStats(19019) +if itemStats then + local bonusStats = {} + for i = 1, #itemStats.bonusStat do + bonusStats[i] = itemStats.bonusStat[i] + end + -- Now bonusStats is a safe independent copy +end + +-- ✗ UNSAFE - Storing table references from the same function +local cast1 = GetCastInfo() -- Gets table reference +-- ... later ... +local cast2 = GetCastInfo() -- Gets SAME table reference with new data +-- cast1 and cast2 both point to the same table with cast2's data! + +-- ✗ UNSAFE - Storing nested array references +local item1 = GetItemStats(19019) +local item1BonusStats = item1.bonusStat -- Stores reference to nested array +local item2 = GetItemStats(22589) +-- item1BonusStats was overwritten! The "bonusStat" nested array reference is reused + +-- ✓ SAFE - Using copy parameter for nested arrays +local item1 = GetItemStats(19019, 1) -- Pass 1 to get independent copy +local item1BonusStats = item1.bonusStat -- Safe to store, it's an independent copy +local item2 = GetItemStats(22589, 1) -- Another independent copy +-- Both item1BonusStats and item2.bonusStat are independent tables +``` + +**Important for array field functions:** Each field name gets its own dedicated table reference, but the table is still reused across calls with the same field name. **Always extract values immediately - never store the table reference itself.** Alternatively, pass `1` as the `copy` parameter to get an independent table copy: + +```lua +-- ✓ SAFE - Extract array values immediately +local bonusStats = {} +local tempTable = GetItemStatsField(itemId, "bonusStat") +for i = 1, #tempTable do + bonusStats[i] = tempTable[i] +end +-- Now bonusStats is a safe independent copy + +-- ✓ EASIER - Use copy parameter to get independent table +local bonusStats = GetItemStatsField(itemId, "bonusStat", 1) +-- Safe to store, no manual copying needed! + +-- ✗ UNSAFE - Storing table references (even with different field names) +local bonusStats = GetItemStatsField(itemId, "bonusStat") +local bonusAmounts = GetItemStatsField(itemId, "bonusAmount") +-- Later... +local newBonusStats = GetItemStatsField(otherItemId, "bonusStat") +-- bonusStats was overwritten! The "bonusStat" reference is reused across calls + +-- ✗ ALSO UNSAFE - Same field name, multiple calls +local item1Stats = GetItemStatsField(19019, "bonusStat") +local item2Stats = GetItemStatsField(22589, "bonusStat") +-- item1Stats was immediately overwritten by the second call! +``` + +--- + ### Custom Lua Functions ### Spell/Item/Unit information -#### GetItemStats(itemId) -Returns a Lua table containing all fields for the item's `ItemStats` record (including localized `displayName` and `description`). Returns nil if the item cannot be found or loaded. +#### GetItemStats(itemId, [copy]) +Returns a Lua table reference containing all fields for the item's `ItemStats` record (including localized `displayName` and `description`). Returns nil if the item cannot be found or loaded. + +**Optional parameter:** Pass `1` for `copy` to get an independent table copy instead of a reusable reference. Full field name lists are in [`DBC_FIELDS.md`](DBC_FIELDS.md). -#### GetItemStatsField(itemId, fieldName) +#### GetItemStatsField(itemId, fieldName, [copy]) Fast lookup for a single field on an item. Returns the requested field value; returns nil if the item is not found; raises a Lua error if the field name is invalid. +**Optional parameter:** Pass `1` for `copy` to get an independent table copy (for array fields only). + Full field name lists are in [`DBC_FIELDS.md`](DBC_FIELDS.md). **Examples:** @@ -102,13 +202,13 @@ end ``` #### GetEquippedItems(unitToken) -Returns a table containing all equipped items for the specified unit. +Returns a table reference containing all equipped items for the specified unit. **Parameters:** - `unitToken` (string): Can be a standard unit token ("player", "target", "pet", etc.) or a GUID string **Returns:** -- A Lua table with equipment slot indices as keys (0-18) and item info tables as values +- A Lua table reference with equipment slot indices as keys (0-18) and item info tables as values - Returns nil if the unit cannot be found or inspected For the player, item info includes: @@ -162,7 +262,7 @@ Returns item info for a specific equipment slot on the specified unit. - 15 = Back, 16 = Main Hand, 17 = Off Hand, 18 = Ranged, 19 = Tabard **Returns:** -- A Lua table containing the item info (same fields as GetEquippedItems) +- A Lua table reference containing the item info (same fields as GetEquippedItems) - Returns nil if the slot is empty, unit cannot be found, or unit cannot be inspected **Examples:** @@ -184,10 +284,10 @@ end ``` #### GetBagItems() -Returns a nested table containing all items in all bags (including bank if open). +Returns a nested table reference containing all items in all bags (including bank if open). **Returns:** -- A Lua table with bag indices as keys and bag contents as values +- A Lua table reference with bag indices as keys and bag contents as values - Each bag contains **1-indexed** slot numbers as keys and item info tables as values - Bag indices: - 0 = Inventory pack (16 slots) @@ -243,7 +343,7 @@ Returns item info for a specific slot in a specific bag. - `slot` (number): **1-indexed** slot number within the bag **Returns:** -- A Lua table containing the item info (same fields as GetBagItems) +- A Lua table reference containing the item info (same fields as GetBagItems) - Returns nil if the slot is empty or invalid **Examples:** @@ -270,14 +370,18 @@ if bankItem then end ``` -#### GetSpellRec(spellId) -Returns a Lua table containing all fields for the spell's `SpellRec` record (including localized `name` and `rank`). Returns nil if the spell cannot be found. +#### GetSpellRec(spellId, [copy]) +Returns a Lua table reference containing all fields for the spell's `SpellRec` record (including localized `name` and `rank`). Returns nil if the spell cannot be found. + +**Optional parameter:** Pass `1` for `copy` to get an independent table copy instead of a reusable reference. Full field name lists are in [`DBC_FIELDS.md`](DBC_FIELDS.md). -#### GetSpellRecField(spellId, fieldName) +#### GetSpellRecField(spellId, fieldName, [copy]) Fast lookup for a single field on a spell. Returns the requested field value; returns nil if the spell is not found; raises a Lua error if the field name is invalid. +**Optional parameter:** Pass `1` for `copy` to get an independent table copy (for array fields only). + Full field name lists are in [`DBC_FIELDS.md`](DBC_FIELDS.md). **Examples:** @@ -359,14 +463,15 @@ print("Flat damage bonus: " .. flatMod) print("Percent damage bonus: " .. percentMod .. "%") ``` -#### GetUnitData(unitToken) -Returns a Lua table containing all unit fields for the specified unit. This provides access to low-level unit data like health, mana, stats, auras, resistances, and more. +#### GetUnitData(unitToken, [copy]) +Returns a Lua table reference containing all unit fields for the specified unit. This provides access to low-level unit data like health, mana, stats, auras, resistances, and more. **Parameters:** - `unitToken` (string): Can be a standard unit token ("player", "target", "pet", "mouseover", etc.) or a GUID string (e.g., "0xF5300000000000A5") +- `copy` (number, optional): Pass `1` to get an independent table copy instead of a reusable reference **Returns:** -- A Lua table containing all unit fields, or nil if the unit cannot be found +- A Lua table reference containing all unit fields, or nil if the unit cannot be found Full field name lists are in [`UNIT_FIELDS.md`](UNIT_FIELDS.md). @@ -384,12 +489,13 @@ end local data = GetUnitData("0xF5300000000000A5") ``` -#### GetUnitField(unitToken, fieldName) +#### GetUnitField(unitToken, fieldName, [copy]) Fast lookup for a single field on a unit. More efficient than GetUnitData when you only need one specific field. **Parameters:** - `unitToken` (string): Can be a standard unit token ("player", "target", "pet", "mouseover", etc.) or a GUID string - `fieldName` (string): The name of the field to retrieve +- `copy` (number, optional): Pass `1` to get an independent table copy (for array fields only) **Returns:** - The requested field value; returns nil if the unit is not found; raises a Lua error if the field name is invalid @@ -497,7 +603,7 @@ Returns detailed information about the currently active cast or channel. Returns GetCurrentCastingInfo was made very early on and doesn't provide enough information for many use cases, but still has some uses and is available for backwards compatibility. **Returns:** -A Lua table with the following fields, or nil if no cast is active: +A Lua table reference with the following fields, or nil if no cast is active: - `castId` (number): Unique identifier for this cast - `spellId` (number): The spell ID being cast @@ -552,7 +658,7 @@ Returns detailed cooldown information for a spell from the spell history. This p - `spellId` (number): The spell ID to check **Returns:** -A Lua table with the following fields: +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 @@ -610,7 +716,7 @@ Returns detailed cooldown information for an item from the spell history. Works - `itemId` (number): The item ID to check **Returns:** -A Lua table with the same structure as GetSpellIdCooldown (see above). +A Lua table reference with the same structure as GetSpellIdCooldown (see above). **Notes:** - Returns the longest cooldown among all spells associated with the item diff --git a/nampower/cooldown_scripts.cpp b/nampower/cooldown_scripts.cpp index f0c2a1f..ec03028 100644 --- a/nampower/cooldown_scripts.cpp +++ b/nampower/cooldown_scripts.cpp @@ -6,6 +6,9 @@ #include "offsets.hpp" namespace Nampower { + // Reusable table reference to reduce memory allocations + static int cooldownDetailTableRef = LUA_REFNIL; + struct CooldownDetail { bool isOnCooldown = false; uint32_t cooldownRemainingMs = 0; @@ -156,7 +159,12 @@ namespace Nampower { static char gcdCategoryRemainingMsKey[] = "gcdCategoryRemainingMs"; static char isOnGcdCategoryCooldownKey[] = "isOnGcdCategoryCooldown"; - lua_newtable(luaState); + // Get or create reusable table + if (cooldownDetailTableRef == LUA_REFNIL) { + lua_newtable(luaState); + cooldownDetailTableRef = luaL_ref(luaState, LUA_REGISTRYINDEX); + } + lua_rawgeti(luaState, LUA_REGISTRYINDEX, cooldownDetailTableRef); // Overall cooldown status PushTableInt(luaState, isOnCooldownKey, detail.isOnCooldown ? 1 : 0); diff --git a/nampower/dbc_fields.hpp b/nampower/dbc_fields.hpp index 56708f4..35d66ce 100644 --- a/nampower/dbc_fields.hpp +++ b/nampower/dbc_fields.hpp @@ -141,4 +141,54 @@ namespace Nampower { lua_settable(luaState, -3); } } + + // Version that uses reusable table references for each array field + template + inline void PushArrayFieldsToLuaWithRefs(uintptr_t* luaState, const T* obj, const ArrayFieldDescriptor* fields, + size_t fieldCount, std::unordered_map& refMap) { + for (size_t i = 0; i < fieldCount; ++i) { + const auto& field = fields[i]; + lua_pushstring(luaState, const_cast(field.name)); + + // Get or create reusable table for this specific field name + auto refIt = refMap.find(field.name); + if (refIt == refMap.end()) { + lua_newtable(luaState); + int ref = luaL_ref(luaState, LUA_REGISTRYINDEX); + refMap[field.name] = ref; + } + lua_rawgeti(luaState, LUA_REGISTRYINDEX, refMap[field.name]); + + const char* fieldPtr = reinterpret_cast(obj) + field.offset; + + for (size_t j = 0; j < field.count; ++j) { + lua_pushnumber(luaState, j + 1); // Lua is 1-indexed + + switch (field.type) { + case FieldType::INT32: + lua_pushnumber(luaState, reinterpret_cast(fieldPtr)[j]); + break; + case FieldType::UINT32: + lua_pushnumber(luaState, reinterpret_cast(fieldPtr)[j]); + break; + case FieldType::FLOAT: + lua_pushnumber(luaState, reinterpret_cast(fieldPtr)[j]); + break; + case FieldType::UINT64: + lua_pushnumber(luaState, static_cast(reinterpret_cast(fieldPtr)[j])); + break; + case FieldType::UINT8: + lua_pushnumber(luaState, reinterpret_cast(fieldPtr)[j]); + break; + case FieldType::STRING: { + const char* str = reinterpret_cast(fieldPtr)[j]; + lua_pushstring(luaState, str ? const_cast(str) : const_cast("")); + break; + } + } + lua_settable(luaState, -3); + } + lua_settable(luaState, -3); + } + } } diff --git a/nampower/helper.cpp b/nampower/helper.cpp index 0b7e0a1..007e195 100644 --- a/nampower/helper.cpp +++ b/nampower/helper.cpp @@ -26,6 +26,9 @@ namespace Nampower { lua_pushnilT lua_pushnil = reinterpret_cast(Offsets::lua_pushnil); lua_newtableT lua_newtable = reinterpret_cast(Offsets::lua_newtable); lua_settableT lua_settable = reinterpret_cast(Offsets::lua_settable); + luaL_refT luaL_ref = reinterpret_cast(Offsets::luaL_ref); + lua_rawgetiT lua_rawgeti = reinterpret_cast(Offsets::lua_rawgeti); + luaL_unrefT luaL_unref = reinterpret_cast(Offsets::luaL_unref); uint32_t GetSpellSlotAndTypeForName(const char *spellName, uint32_t *spellType) { diff --git a/nampower/helper.hpp b/nampower/helper.hpp index 9a809a8..0b469ae 100644 --- a/nampower/helper.hpp +++ b/nampower/helper.hpp @@ -21,6 +21,14 @@ namespace Nampower { extern lua_pushnilT lua_pushnil; extern lua_newtableT lua_newtable; extern lua_settableT lua_settable; + extern luaL_refT luaL_ref; + extern lua_rawgetiT lua_rawgeti; + extern luaL_unrefT luaL_unref; + + // Lua 5.0 constants + #ifndef LUA_REFNIL + #define LUA_REFNIL (-2) + #endif uint32_t GetSpellSlotAndTypeForName(const char *spellName, uint32_t *spellType); diff --git a/nampower/item_scripts.cpp b/nampower/item_scripts.cpp index 016a0cd..1c8f440 100644 --- a/nampower/item_scripts.cpp +++ b/nampower/item_scripts.cpp @@ -14,6 +14,13 @@ namespace Nampower { // Local cache for item name lookups static std::unordered_map 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; + // String keys used when pushing item data to Lua static char itemIdKey[] = "itemId"; static char permanentEnchantIdKey[] = "permanentEnchantId"; @@ -101,7 +108,12 @@ namespace Nampower { return; } - lua_newtable(luaState); + // Get or create reusable table + if (basicItemInfoTableRef == LUA_REFNIL) { + lua_newtable(luaState); + basicItemInfoTableRef = luaL_ref(luaState, LUA_REGISTRYINDEX); + } + lua_rawgeti(luaState, LUA_REGISTRYINDEX, basicItemInfoTableRef); PushTableValue(luaState, itemIdKey, cgItem->itemId); PushTableValue(luaState, permanentEnchantIdKey, cgItem->permanentEnchantId); @@ -117,7 +129,12 @@ namespace Nampower { auto itemId = game::GetItemId(item); auto itemFields = item->itemFields; - lua_newtable(luaState); + // Get or create reusable table + if (itemInfoTableRef == LUA_REFNIL) { + lua_newtable(luaState); + itemInfoTableRef = luaL_ref(luaState, LUA_REGISTRYINDEX); + } + lua_rawgeti(luaState, LUA_REGISTRYINDEX, itemInfoTableRef); PushTableValue(luaState, itemIdKey, itemId); PushTableValue(luaState, stackCountKey, itemFields->stackCount); @@ -298,7 +315,12 @@ namespace Nampower { return 0; } - lua_newtable(luaState); + // Get or create reusable table + if (equippedItemsTableRef == LUA_REFNIL) { + lua_newtable(luaState); + equippedItemsTableRef = luaL_ref(luaState, LUA_REGISTRYINDEX); + } + lua_rawgeti(luaState, LUA_REGISTRYINDEX, equippedItemsTableRef); auto playerGuid = game::ClntObjMgrGetActivePlayerGuid(); bool isPlayer = (guid == playerGuid); @@ -536,14 +558,25 @@ namespace Nampower { auto const getContainerGuid = reinterpret_cast(Offsets::GetContainerGuid); auto const getBagItem = reinterpret_cast(Offsets::CGBag_C_GetItemAtSlot); - lua_newtable(luaState); + // Get or create reusable table + if (bagItemsTableRef == LUA_REFNIL) { + lua_newtable(luaState); + bagItemsTableRef = luaL_ref(luaState, LUA_REGISTRYINDEX); + } + lua_rawgeti(luaState, LUA_REGISTRYINDEX, bagItemsTableRef); auto playerGuid = game::ClntObjMgrGetActivePlayerGuid(); auto player = game::GetObjectPtr(playerGuid); auto inventory = game::GetPlayerInventoryPtr(player); lua_pushnumber(luaState, static_cast(0)); - lua_newtable(luaState); + + // Get or create reusable bag table + if (bagTableRef == LUA_REFNIL) { + lua_newtable(luaState); + bagTableRef = luaL_ref(luaState, LUA_REGISTRYINDEX); + } + lua_rawgeti(luaState, LUA_REGISTRYINDEX, bagTableRef); for (uint32_t slot = 23; slot <= 38; slot++) { auto item = getBagItem(inventory, slot); @@ -565,7 +598,7 @@ namespace Nampower { if (!bagPtr) continue; lua_pushnumber(luaState, static_cast(bagIndex)); - lua_newtable(luaState); + lua_rawgeti(luaState, LUA_REGISTRYINDEX, bagTableRef); auto bagSize = *bagPtr; for (uint32_t slot = 0; slot < bagSize; slot++) { @@ -591,7 +624,7 @@ namespace Nampower { if (!bagPtr) continue; lua_pushnumber(luaState, static_cast(bagIndex)); - lua_newtable(luaState); + lua_rawgeti(luaState, LUA_REGISTRYINDEX, bagTableRef); auto bagSize = *bagPtr; for (uint32_t slot = 0; slot < bagSize; slot++) { diff --git a/nampower/main.cpp b/nampower/main.cpp index e41b683..4816548 100644 --- a/nampower/main.cpp +++ b/nampower/main.cpp @@ -61,7 +61,7 @@ namespace Nampower { uint32_t gLastBufferIncreaseTimeMs; uint32_t gLastBufferDecreaseTimeMs; - uint32_t gBufferTimeMs; // adjusts dynamically depending on errors + uint32_t gBufferTimeMs; // adjusts dynamically depending on errors bool gForceQueueCast; bool gNoQueueCast; @@ -89,59 +89,59 @@ namespace Nampower { CastQueue gCastHistory = CastQueue(30); - std::unique_ptr> gSpellVisualsInitDetour; - std::unique_ptr> gLoadScriptFunctionsDetour; - std::unique_ptr> gCreateEventsDetour; - std::unique_ptr> gSetEventCountDetour; + std::unique_ptr > gSpellVisualsInitDetour; + std::unique_ptr > gLoadScriptFunctionsDetour; + std::unique_ptr > gCreateEventsDetour; + std::unique_ptr > gSetEventCountDetour; - std::unique_ptr> gSetCVarDetour; - std::unique_ptr> gCGSpellBook_CastSpellDetour; - std::unique_ptr> gCastDetour; - std::unique_ptr> gSendCastDetour; - std::unique_ptr> gCancelSpellDetour; - std::unique_ptr> gSignalEventDetour; - std::unique_ptr> gSpellFailedDetour; - std::unique_ptr> gSpell_C_GetSpellModifiersDetour; - std::unique_ptr> gSpell_C_GetSpellRadiusDetour; + std::unique_ptr > gSetCVarDetour; + std::unique_ptr > gCGSpellBook_CastSpellDetour; + std::unique_ptr > gCastDetour; + std::unique_ptr > gSendCastDetour; + std::unique_ptr > gCancelSpellDetour; + std::unique_ptr > gSignalEventDetour; + std::unique_ptr > gSpellFailedDetour; + std::unique_ptr > gSpell_C_GetSpellModifiersDetour; + std::unique_ptr > gSpell_C_GetSpellRadiusDetour; std::unique_ptr gCastbarPatch; - std::unique_ptr> gIEndSceneDetour; - std::unique_ptr> gSpell_C_GetAutoRepeatingSpellDetour; - std::unique_ptr> gSpell_C_CooldownEventTriggeredDetour; - std::unique_ptr> gSpellGoDetour; - std::unique_ptr> gSpellTargetUnitDetour; - std::unique_ptr> gSpellStopCastingDetour; - std::unique_ptr> gOnSpriteRightClickDetour; - std::unique_ptr> gSpell_C_HandleSpriteClickDetour; - std::unique_ptr> gSpell_C_HandleTerrainClickDetour; - std::unique_ptr> gCGWorldFrame_OnLayerTrackTerrainDetour; - std::unique_ptr> gSpell_C_TargetSpellDetour; + std::unique_ptr > gIEndSceneDetour; + std::unique_ptr > gSpell_C_GetAutoRepeatingSpellDetour; + std::unique_ptr > gSpell_C_CooldownEventTriggeredDetour; + std::unique_ptr > gSpellGoDetour; + std::unique_ptr > gSpellTargetUnitDetour; + std::unique_ptr > gSpellStopCastingDetour; + std::unique_ptr > gOnSpriteRightClickDetour; + std::unique_ptr > gSpell_C_HandleSpriteClickDetour; + std::unique_ptr > gSpell_C_HandleTerrainClickDetour; + std::unique_ptr > gCGWorldFrame_OnLayerTrackTerrainDetour; + std::unique_ptr > gSpell_C_TargetSpellDetour; - std::unique_ptr> gSpellCooldownDetour; - std::unique_ptr> gSpellDelayedDetour; - std::unique_ptr> gCastResultHandlerDetour; - std::unique_ptr> gSpellFailedHandlerDetour; - std::unique_ptr> gSpellChannelStartHandlerDetour; - std::unique_ptr> gSpellChannelUpdateHandlerDetour; - std::unique_ptr> gPlaySpellVisualHandlerDetour; + std::unique_ptr > gSpellCooldownDetour; + std::unique_ptr > gSpellDelayedDetour; + std::unique_ptr > gCastResultHandlerDetour; + std::unique_ptr > gSpellFailedHandlerDetour; + std::unique_ptr > gSpellChannelStartHandlerDetour; + std::unique_ptr > gSpellChannelUpdateHandlerDetour; + std::unique_ptr > gPlaySpellVisualHandlerDetour; - std::unique_ptr> gSpellStartHandlerDetour; - std::unique_ptr> gPeriodicAuraLogHandlerDetour; - std::unique_ptr> gSpellNonMeleeDmgLogHandlerDetour; + std::unique_ptr > gSpellStartHandlerDetour; + std::unique_ptr > gPeriodicAuraLogHandlerDetour; + std::unique_ptr > gSpellNonMeleeDmgLogHandlerDetour; - std::unique_ptr> gCGPlayer_C_OnAttackIconPressedDetour; - std::unique_ptr> gCGActionBar_UseActionDetour; - std::unique_ptr> gCGUnit_C_OnAuraRemovedDetour; - std::unique_ptr> gCGUnit_C_OnAuraAddedDetour; - std::unique_ptr> gCGUnit_C_OnAuraAddedStackDetour; - std::unique_ptr> gUnitCombatLogUnitDeadDetour; + std::unique_ptr > gCGPlayer_C_OnAttackIconPressedDetour; + std::unique_ptr > gCGActionBar_UseActionDetour; + std::unique_ptr > gCGUnit_C_OnAuraRemovedDetour; + std::unique_ptr > gCGUnit_C_OnAuraAddedDetour; + std::unique_ptr > gCGUnit_C_OnAuraAddedStackDetour; + std::unique_ptr > gUnitCombatLogUnitDeadDetour; - std::unique_ptr> gInvalidFunctionPtrCheckDetour; + std::unique_ptr > gInvalidFunctionPtrCheckDetour; - std::unique_ptr> gGetSpellSlotFromLuaDetour; + std::unique_ptr > gGetSpellSlotFromLuaDetour; uint32_t GetTime() { return static_cast(std::chrono::duration_cast( - std::chrono::high_resolution_clock::now().time_since_epoch()).count()) - gStartTime; + std::chrono::high_resolution_clock::now().time_since_epoch()).count()) - gStartTime; } std::string GetHumanReadableTime() { @@ -205,7 +205,8 @@ namespace Nampower { void RegisterLuaFunction(char *name, uintptr_t *func) { DEBUG_LOG("Registering " << name << " to " << func); - auto const registerFunction = reinterpret_cast(Offsets::FrameScript_RegisterFunction); + auto const registerFunction = reinterpret_cast( + Offsets::FrameScript_RegisterFunction); registerFunction(name, func); } @@ -262,8 +263,10 @@ namespace Nampower { return true; } - auto const remainingChannelTime = (gCastData.channelEndMs > currentTime) ? gCastData.channelEndMs - - currentTime : 0; + auto const remainingChannelTime = (gCastData.channelEndMs > currentTime) + ? gCastData.channelEndMs - + currentTime + : 0; return remainingChannelTime < gUserSettings.channelQueueWindowMs; } } else if (spellIsTargeting) { @@ -291,8 +294,10 @@ namespace Nampower { if (gCastData.channeling && gUserSettings.queueChannelingSpells) { if (gUserSettings.interruptChannelsOutsideQueueWindow) { auto currentTime = GetTime(); - auto const remainingChannelTime = (gCastData.channelEndMs > currentTime) ? gCastData.channelEndMs - - currentTime : 0; + auto const remainingChannelTime = (gCastData.channelEndMs > currentTime) + ? gCastData.channelEndMs - + currentTime + : 0; if (remainingChannelTime < gUserSettings.channelQueueWindowMs) { return gCastData.channelEndMs; } @@ -314,7 +319,6 @@ namespace Nampower { gCastData.channelTickTimeMs = 0; gCastData.channelNumTicks = 0; - } void ResetCastFlags() { @@ -355,8 +359,9 @@ namespace Nampower { auto const currentTime = GetTime(); auto const elapsed = currentTime - gLastCastData.channelStartTimeMs; - auto remainingChannelTime = (gCastData.channelEndMs > currentTime) ? gCastData.channelEndMs - currentTime - : 0; + auto remainingChannelTime = (gCastData.channelEndMs > currentTime) + ? gCastData.channelEndMs - currentTime + : 0; auto const currentLatency = GetLatencyMs(); uint32_t latencyReduction = 0; @@ -373,9 +378,9 @@ namespace Nampower { if (remainingChannelTime <= 0) { DEBUG_LOG("Ending channel [" << elapsed << " elapsed > " - << gCastData.channelDuration << " original duration " - << " latency reduction " << latencyReduction << "]" - << " triggering queued spells"); + << gCastData.channelDuration << " original duration " + << " latency reduction " << latencyReduction << "]" + << " triggering queued spells"); ResetChannelingFlags(); } else if (gCastData.cancelChannelNextTick && @@ -412,8 +417,8 @@ namespace Nampower { if (remainingTickTime <= 0) { DEBUG_LOG("Ending channel due to cancelChannelNextTick. " - << "Remaining tick time: " << nextTickTimeMs - currentTime - << " latency reduction: " << latencyReduction); + << "Remaining tick time: " << nextTickTimeMs - currentTime + << " latency reduction: " << latencyReduction); ResetChannelingFlags(); } } @@ -475,7 +480,7 @@ namespace Nampower { return true; } else { DEBUG_LOG("Ignoring queued cast of " << game::GetSpellName(gLastNormalCastParams.spellId) - << " due to max time since last cast"); + << " due to max time since last cast"); TriggerSpellQueuedEvent(NORMAL_QUEUE_POPPED, gLastNormalCastParams.spellId); gCastData.normalSpellQueued = false; gCastData.targetingSpellQueued = false; @@ -520,9 +525,8 @@ namespace Nampower { // if we have a target that is not the right click target, ignore the right click if (gUserSettings.preventRightClickTargetChange && currentTargetGuid && currentTargetGuid != objectGUID) { - auto unitOrPlayer = game::ClntObjMgrObjectPtr( - static_cast(game::TYPEMASK_PLAYER | game::TYPEMASK_UNIT), objectGUID); + static_cast(game::TYPEMASK_PLAYER | game::TYPEMASK_UNIT), objectGUID); // only prevent right click if guid is a unit/player if (unitOrPlayer) { @@ -596,12 +600,10 @@ namespace Nampower { } else if (strcmp(cvar, "NP_QueueSpellsOnCooldown") == 0) { gUserSettings.queueSpellsOnCooldown = atoi(value) != 0; DEBUG_LOG("Set NP_QueueSpellsOnCooldown to " << gUserSettings.queueSpellsOnCooldown); - } else if (strcmp(cvar, "NP_InterruptChannelsOutsideQueueWindow") == 0) { gUserSettings.interruptChannelsOutsideQueueWindow = atoi(value) != 0; DEBUG_LOG("Set NP_InterruptChannelsOutsideQueueWindow to " - << gUserSettings.interruptChannelsOutsideQueueWindow); - + << gUserSettings.interruptChannelsOutsideQueueWindow); } else if ((strcmp(cvar, "NP_RetryServerRejectedSpells") == 0)) { gUserSettings.retryServerRejectedSpells = atoi(value) != 0; DEBUG_LOG("Set NP_RetryServerRejectedSpells to " << gUserSettings.retryServerRejectedSpells); @@ -614,27 +616,21 @@ namespace Nampower { } else if (strcmp(cvar, "NP_OptimizeBufferUsingPacketTimings") == 0) { gUserSettings.optimizeBufferUsingPacketTimings = atoi(value) != 0; DEBUG_LOG("Set NP_OptimizeBufferUsingPacketTimings to " << gUserSettings.optimizeBufferUsingPacketTimings); - } else if (strcmp(cvar, "NP_PreventRightClickTargetChange") == 0) { gUserSettings.preventRightClickTargetChange = atoi(value) != 0; DEBUG_LOG("Set NP_PreventRightClickTargetChange to " << gUserSettings.preventRightClickTargetChange); - } else if (strcmp(cvar, "NP_PreventRightClickPvPAttack") == 0) { gUserSettings.preventRightClickPvPAttack = atoi(value) != 0; DEBUG_LOG("Set NP_PreventRightClickPvPAttack to " << gUserSettings.preventRightClickPvPAttack); - } else if (strcmp(cvar, "NP_DoubleCastToEndChannelEarly") == 0) { gUserSettings.doubleCastToEndChannelEarly = atoi(value) != 0; DEBUG_LOG("Set NP_DoubleCastToEndChannelEarly to " << gUserSettings.doubleCastToEndChannelEarly); - } else if (strcmp(cvar, "NP_QuickcastOnDoubleCast") == 0) { gUserSettings.quickcastOnDoubleCast = atoi(value) != 0; DEBUG_LOG("Set NP_QuickcastOnDoubleCast to " << gUserSettings.quickcastOnDoubleCast); - } 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_MinBufferTimeMs") == 0) { gUserSettings.minBufferTimeMs = atoi(value); DEBUG_LOG("Set NP_MinBufferTimeMs and current buffer to " << gUserSettings.minBufferTimeMs); @@ -645,7 +641,6 @@ namespace Nampower { } else if (strcmp(cvar, "NP_MaxBufferIncreaseMs") == 0) { gUserSettings.maxBufferIncreaseMs = atoi(value); DEBUG_LOG("Set NP_MaxBufferIncreaseMs to " << gUserSettings.maxBufferIncreaseMs); - } else if (strcmp(cvar, "NP_SpellQueueWindowMs") == 0) { gUserSettings.spellQueueWindowMs = atoi(value); DEBUG_LOG("Set NP_SpellQueueWindowMs to " << gUserSettings.spellQueueWindowMs); @@ -661,17 +656,15 @@ namespace Nampower { } else if (strcmp(cvar, "NP_CooldownQueueWindowMs") == 0) { gUserSettings.cooldownQueueWindowMs = atoi(value); DEBUG_LOG("Set NP_CooldownQueueWindowMs to " << gUserSettings.cooldownQueueWindowMs); - } else if (strcmp(cvar, "NP_ChannelLatencyReductionPercentage") == 0) { gUserSettings.channelLatencyReductionPercentage = atoi(value); DEBUG_LOG( - "Set NP_ChannelLatencyReductionPercentage to " << gUserSettings.channelLatencyReductionPercentage); - + "Set NP_ChannelLatencyReductionPercentage to " << gUserSettings.channelLatencyReductionPercentage); } else if (strcmp(cvar, "NP_NameplateDistance") == 0) { auto distance = std::stof(value); SetNameplateDistance(distance); DEBUG_LOG( - "Set NP_NameplateDistance to " << distance); + "Set NP_NameplateDistance to " << distance); } } @@ -742,7 +735,7 @@ namespace Nampower { void loadConfig() { gStartTime = static_cast(std::chrono::duration_cast( - std::chrono::high_resolution_clock::now().time_since_epoch()).count()); + std::chrono::high_resolution_clock::now().time_since_epoch()).count()); // remove/rename previous logs safeRemove("nampower_debug.log.3"); @@ -802,263 +795,265 @@ namespace Nampower { char NP_QueueCastTimeSpells[] = "NP_QueueCastTimeSpells"; CVarRegister(NP_QueueCastTimeSpells, // name nullptr, // help - 0, // unk1 + 0, // unk1 gUserSettings.queueCastTimeSpells ? defaultTrue : defaultFalse, // default value address nullptr, // callback 5, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_QueueInstantSpells[] = "NP_QueueInstantSpells"; CVarRegister(NP_QueueInstantSpells, // name nullptr, // help - 0, // unk1 + 0, // unk1 gUserSettings.queueInstantSpells ? defaultTrue : defaultFalse, // default value address nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_QueueChannelingSpells[] = "NP_QueueChannelingSpells"; CVarRegister(NP_QueueChannelingSpells, // name nullptr, // help - 0, // unk1 + 0, // unk1 gUserSettings.queueChannelingSpells ? defaultTrue : defaultFalse, // default value address nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_QueueTargetingSpells[] = "NP_QueueTargetingSpells"; CVarRegister(NP_QueueTargetingSpells, // name nullptr, // help - 0, // unk1 + 0, // unk1 gUserSettings.queueTargetingSpells ? defaultTrue : defaultFalse, // default value address nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_QueueOnSwingSpells[] = "NP_QueueOnSwingSpells"; CVarRegister(NP_QueueOnSwingSpells, // name nullptr, // help - 0, // unk1 + 0, // unk1 gUserSettings.queueOnSwingSpells ? defaultTrue : defaultFalse, // default value address nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_QueueSpellsOnCooldown[] = "NP_QueueSpellsOnCooldown"; CVarRegister(NP_QueueSpellsOnCooldown, // name nullptr, // help - 0, // unk1 + 0, // unk1 gUserSettings.queueSpellsOnCooldown ? defaultTrue : defaultFalse, // default value address nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_InterruptChannelsOutsideQueueWindow[] = "NP_InterruptChannelsOutsideQueueWindow"; CVarRegister(NP_InterruptChannelsOutsideQueueWindow, // name nullptr, // help - 0, // unk1 - gUserSettings.interruptChannelsOutsideQueueWindow ? defaultTrue - : defaultFalse, // default value address + 0, // unk1 + gUserSettings.interruptChannelsOutsideQueueWindow + ? defaultTrue + : defaultFalse, // default value address nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_RetryServerRejectedSpells[] = "NP_RetryServerRejectedSpells"; CVarRegister(NP_RetryServerRejectedSpells, // name nullptr, // help - 0, // unk1 + 0, // unk1 gUserSettings.retryServerRejectedSpells ? defaultTrue : defaultFalse, // default value address nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_QuickcastTargetingSpells[] = "NP_QuickcastTargetingSpells"; CVarRegister(NP_QuickcastTargetingSpells, // name nullptr, // help - 0, // unk1 + 0, // unk1 gUserSettings.quickcastTargetingSpells ? defaultTrue : defaultFalse, // default value address nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_MinBufferTimeMs[] = "NP_MinBufferTimeMs"; CVarRegister(NP_MinBufferTimeMs, // name nullptr, // help - 0, // unk1 + 0, // unk1 std::to_string(gUserSettings.minBufferTimeMs).c_str(), // default value address nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_NonGcdBufferTimeMs[] = "NP_NonGcdBufferTimeMs"; CVarRegister(NP_NonGcdBufferTimeMs, // name nullptr, // help - 0, // unk1 + 0, // unk1 std::to_string(gUserSettings.nonGcdBufferTimeMs).c_str(), // default value address nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_MaxBufferIncreaseMs[] = "NP_MaxBufferIncreaseMs"; CVarRegister(NP_MaxBufferIncreaseMs, // name nullptr, // help - 0, // unk1 + 0, // unk1 std::to_string(gUserSettings.maxBufferIncreaseMs).c_str(), // default value address nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_SpellQueueWindowMs[] = "NP_SpellQueueWindowMs"; CVarRegister(NP_SpellQueueWindowMs, // name nullptr, // help - 0, // unk1 + 0, // unk1 std::to_string(gUserSettings.spellQueueWindowMs).c_str(), // default value address nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_ChannelQueueWindowMs[] = "NP_ChannelQueueWindowMs"; CVarRegister(NP_ChannelQueueWindowMs, // name nullptr, // help - 0, // unk1 + 0, // unk1 std::to_string(gUserSettings.channelQueueWindowMs).c_str(), // default value address nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_TargetingQueueWindowMs[] = "NP_TargetingQueueWindowMs"; CVarRegister(NP_TargetingQueueWindowMs, // name nullptr, // help - 0, // unk1 + 0, // unk1 std::to_string(gUserSettings.targetingQueueWindowMs).c_str(), // default value address nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_CooldownQueueWindowMs[] = "NP_CooldownQueueWindowMs"; CVarRegister(NP_CooldownQueueWindowMs, // name nullptr, // help - 0, // unk1 + 0, // unk1 std::to_string(gUserSettings.cooldownQueueWindowMs).c_str(), // default value address nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_OnSwingBufferCooldownMs[] = "NP_OnSwingBufferCooldownMs"; CVarRegister(NP_OnSwingBufferCooldownMs, // name nullptr, // help - 0, // unk1 + 0, // unk1 std::to_string(gUserSettings.onSwingBufferCooldownMs).c_str(), // default value address nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_ReplaceMatchingNonGcdCategory[] = "NP_ReplaceMatchingNonGcdCategory"; CVarRegister(NP_ReplaceMatchingNonGcdCategory, // name nullptr, // help - 0, // unk1 + 0, // unk1 gUserSettings.replaceMatchingNonGcdCategory ? defaultTrue : defaultFalse, // default value address nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_OptimizeBufferUsingPacketTimings[] = "NP_OptimizeBufferUsingPacketTimings"; CVarRegister(NP_OptimizeBufferUsingPacketTimings, // name nullptr, // help - 0, // unk1 - gUserSettings.optimizeBufferUsingPacketTimings ? defaultTrue - : defaultFalse, // default value address + 0, // unk1 + gUserSettings.optimizeBufferUsingPacketTimings + ? defaultTrue + : defaultFalse, // default value address nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_PreventRightClickTargetChange[] = "NP_PreventRightClickTargetChange"; CVarRegister(NP_PreventRightClickTargetChange, // name nullptr, // help - 0, // unk1 + 0, // unk1 gUserSettings.preventRightClickTargetChange ? defaultTrue : defaultFalse, // default value address nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_PreventRightClickPvPAttack[] = "NP_PreventRightClickPvPAttack"; CVarRegister(NP_PreventRightClickPvPAttack, // name nullptr, // help - 0, // unk1 + 0, // unk1 gUserSettings.preventRightClickPvPAttack ? defaultTrue : defaultFalse, // default value address nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_DoubleCastToEndChannelEarly[] = "NP_DoubleCastToEndChannelEarly"; CVarRegister(NP_DoubleCastToEndChannelEarly, // name nullptr, // help - 0, // unk1 + 0, // unk1 gUserSettings.doubleCastToEndChannelEarly ? defaultTrue : defaultFalse, // default value address nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_QuickcastOnDoubleCast[] = "NP_QuickcastOnDoubleCast"; CVarRegister(NP_QuickcastOnDoubleCast, // name nullptr, // help - 0, // unk1 + 0, // unk1 gUserSettings.quickcastOnDoubleCast ? defaultTrue : defaultFalse, // default value address nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_SpamProtectionEnabled[] = "NP_SpamProtectionEnabled"; CVarRegister(NP_SpamProtectionEnabled, // name nullptr, // help - 0, // unk1 + 0, // unk1 gUserSettings.spamProtectionEnabled ? defaultTrue : defaultFalse, // default value address nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_ChannelLatencyReductionPercentage[] = "NP_ChannelLatencyReductionPercentage"; CVarRegister(NP_ChannelLatencyReductionPercentage, // name nullptr, // help - 0, // unk1 + 0, // unk1 std::to_string(gUserSettings.channelLatencyReductionPercentage).c_str(), // default value address nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 char NP_NameplateDistance[] = "NP_NameplateDistance"; CVarRegister(NP_NameplateDistance, // name nullptr, // help - 0, // unk1 + 0, // unk1 std::to_string(GetNameplateDistance()).c_str(), // use the game's DAT value as the default nullptr, // callback 1, // category - 0, // unk2 + 0, // unk2 0); // unk3 // update from cvars @@ -1104,10 +1099,10 @@ namespace Nampower { // Template function to simplify hook initialization with specific storage template - std::unique_ptr> + std::unique_ptr > createHook(const hadesmem::Process &process, Offsets offset, HookT hookFunc) { auto const originalFunc = hadesmem::detail::AliasCast(offset); - auto detour = std::make_unique>(process, originalFunc, hookFunc); + auto detour = std::make_unique >(process, originalFunc, hookFunc); detour->Apply(); return detour; } @@ -1116,8 +1111,10 @@ namespace Nampower { const hadesmem::Process process(::GetCurrentProcessId()); gSetCVarDetour = createHook(process, Offsets::Script_SetCVar, &Script_SetCVarHook); - gCGSpellBook_CastSpellDetour = createHook(process, Offsets::CGSpellBook_CastSpell, &CGSpellBook_CastSpellHook); - gCGActionBar_UseActionDetour = createHook(process, Offsets::CGActionBar_UseAction, &CGActionBar_UseActionHook); + gCGSpellBook_CastSpellDetour = createHook( + process, Offsets::CGSpellBook_CastSpell, &CGSpellBook_CastSpellHook); + gCGActionBar_UseActionDetour = createHook( + process, Offsets::CGActionBar_UseAction, &CGActionBar_UseActionHook); gCastDetour = createHook(process, Offsets::Spell_C_CastSpell, &Spell_C_CastSpellHook); gSendCastDetour = createHook(process, Offsets::SendCast, &SendCastHook); gCancelSpellDetour = createHook(process, Offsets::CancelSpell, &CancelSpellHook); @@ -1144,10 +1141,12 @@ namespace Nampower { &Script_SpellStopCastingHook); gSpell_C_TargetSpellDetour = createHook(process, Offsets::Spell_C_TargetSpell, &Spell_C_TargetSpellHook); - gSpell_C_HandleTerrainClickDetour = createHook(process, Offsets::Spell_C_HandleTerrainClick, - &Spell_C_HandleTerrainClickHook); - gCGWorldFrame_OnLayerTrackTerrainDetour = createHook(process, Offsets::CGWorldFrame_OnLayerTrackTerrain, - &CGWorldFrame_OnLayerTrackTerrainHook); + gSpell_C_HandleTerrainClickDetour = createHook( + process, Offsets::Spell_C_HandleTerrainClick, + &Spell_C_HandleTerrainClickHook); + gCGWorldFrame_OnLayerTrackTerrainDetour = createHook( + process, Offsets::CGWorldFrame_OnLayerTrackTerrain, + &CGWorldFrame_OnLayerTrackTerrainHook); gOnSpriteRightClickDetour = createHook(process, Offsets::OnSpriteRightClick, OnSpriteRightClickHook); gIEndSceneDetour = createHook(process, Offsets::ISceneEndPtr, &ISceneEndHook); @@ -1156,13 +1155,14 @@ namespace Nampower { gGetSpellSlotFromLuaDetour = createHook(process, Offsets::GetSpellSlotFromLua, &GetSpellSlotFromLuaHook); gCGUnit_C_OnAuraRemovedDetour = createHook(process, Offsets::CGUnit_C_OnAuraRemoved, - &CGUnit_C_OnAuraRemovedHook); + &CGUnit_C_OnAuraRemovedHook); gCGUnit_C_OnAuraAddedDetour = createHook(process, Offsets::CGUnit_C_OnAuraAdded, - &CGUnit_C_OnAuraAddedHook); - gCGUnit_C_OnAuraAddedStackDetour = createHook(process, Offsets::CGUnit_C_OnAuraAddedStack, - &CGUnit_C_OnAuraAddedStackHook); + &CGUnit_C_OnAuraAddedHook); + gCGUnit_C_OnAuraAddedStackDetour = createHook( + process, Offsets::CGUnit_C_OnAuraAddedStack, + &CGUnit_C_OnAuraAddedStackHook); gUnitCombatLogUnitDeadDetour = createHook(process, Offsets::UnitCombatLogUnitDead, - &UnitCombatLogUnitDeadHook); + &UnitCombatLogUnitDeadHook); } void SpellVisualsInitializeHook(hadesmem::PatchDetourBase *detour) { @@ -1172,11 +1172,11 @@ namespace Nampower { initHooks(); } - void addCustomEvent(uint32_t code, char* name) { - char ** FrameScript_EventObject_Data = *reinterpret_cast(Offsets::Framescript_EventObject_Data); + void addCustomEvent(uint32_t code, char *name) { + char **FrameScript_EventObject_Data = *reinterpret_cast(Offsets::Framescript_EventObject_Data); - auto SStrDupA = reinterpret_cast(Offsets::SStrDupA); - FrameScript_EventObject_Data[code*4] = SStrDupA(name, name, 1308); + auto SStrDupA = reinterpret_cast(Offsets::SStrDupA); + FrameScript_EventObject_Data[code * 4] = SStrDupA(name, name, 1308); DEBUG_LOG("Added custom event code:" << code << " " << name); } @@ -1232,7 +1232,8 @@ namespace Nampower { } } - void Framescript_SetEventCountHook(hadesmem::PatchDetourBase *detour, void *thisPtr, void *dummy_edx, uint32_t count) { + void Framescript_SetEventCountHook(hadesmem::PatchDetourBase *detour, void *thisPtr, void *dummy_edx, + uint32_t count) { auto const setEventCount = detour->GetTrampolineT(); if (count > 200) { @@ -1333,7 +1334,6 @@ namespace Nampower { char getItemIdCooldown[] = "GetItemIdCooldown"; RegisterLuaFunction(getItemIdCooldown, reinterpret_cast(Script_GetItemIdCooldown)); - } std::once_flag loadFlag; @@ -1344,36 +1344,39 @@ namespace Nampower { const hadesmem::Process process(::GetCurrentProcessId()); auto const spellVisualsInitOrig = hadesmem::detail::AliasCast( - Offsets::SpellVisualsInitialize); - gSpellVisualsInitDetour = std::make_unique>(process, - spellVisualsInitOrig, - &SpellVisualsInitializeHook); + Offsets::SpellVisualsInitialize); + gSpellVisualsInitDetour = std::make_unique >( + process, + spellVisualsInitOrig, + &SpellVisualsInitializeHook); gSpellVisualsInitDetour->Apply(); auto const loadScriptFunctionsOrig = hadesmem::detail::AliasCast( - Offsets::LoadScriptFunctions); - gLoadScriptFunctionsDetour = std::make_unique>(process, - loadScriptFunctionsOrig, - &LoadScriptFunctionsHook); + Offsets::LoadScriptFunctions); + gLoadScriptFunctionsDetour = std::make_unique >( + process, + loadScriptFunctionsOrig, + &LoadScriptFunctionsHook); gLoadScriptFunctionsDetour->Apply(); auto const createEventsOrig = hadesmem::detail::AliasCast( - Offsets::FrameScript_CreateEvents); - gCreateEventsDetour = std::make_unique>(process, - createEventsOrig, - &FrameScript_CreateEventsHook); + Offsets::FrameScript_CreateEvents); + gCreateEventsDetour = std::make_unique >( + process, + createEventsOrig, + &FrameScript_CreateEventsHook); gCreateEventsDetour->Apply(); auto const setEventCountOrig = hadesmem::detail::AliasCast( - Offsets::Framescript_SetEventCount); - gSetEventCountDetour = std::make_unique>(process, - setEventCountOrig, - &Framescript_SetEventCountHook); + Offsets::Framescript_SetEventCount); + gSetEventCountDetour = std::make_unique >( + process, + setEventCountOrig, + &Framescript_SetEventCountHook); gSetEventCountDetour->Apply(); } ); } - } extern "C" __declspec(dllexport) uint32_t Load() { diff --git a/nampower/main.hpp b/nampower/main.hpp index 9d7a004..f27fa06 100644 --- a/nampower/main.hpp +++ b/nampower/main.hpp @@ -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 = 17; + constexpr uint32_t MINOR_VERSION = 18; constexpr uint32_t PATCH_VERSION = 0; constexpr int32_t LUA_REGISTRYINDEX = -10000; @@ -137,6 +137,9 @@ namespace Nampower { using lua_settopT = void (__fastcall *)(uintptr_t *, int); using lua_newtableT = void (__fastcall *)(uintptr_t *); using lua_settableT = void (__fastcall *)(uintptr_t *, int); + using luaL_refT = int (__fastcall *)(uintptr_t *, int); + using lua_rawgetiT = void (__fastcall *)(uintptr_t *, int, int); + using luaL_unrefT = void (__fastcall *)(uintptr_t *, int, int); using Spell_C_CooldownEventTriggeredT = void (__fastcall *)(uint32_t spellId, uint64_t *targetGUID, diff --git a/nampower/misc_scripts.cpp b/nampower/misc_scripts.cpp index 14200e6..ca17866 100644 --- a/nampower/misc_scripts.cpp +++ b/nampower/misc_scripts.cpp @@ -29,6 +29,19 @@ namespace Nampower { int gScriptPriority = 1; char *queuedScript; + // Reusable table references to reduce memory allocations + static int castInfoTableRef = LUA_REFNIL; + static int itemStatsTableRef = LUA_REFNIL; + static int unitDataTableRef = LUA_REFNIL; + + // Maps to store separate references for each array field name (for Field functions) + static std::unordered_map itemStatsArrayFieldRefs; + static std::unordered_map unitFieldsArrayFieldRefs; + + // Maps to store separate references for nested array fields (for main table functions) + static std::unordered_map itemStatsNestedArrayRefs; + static std::unordered_map unitFieldsNestedArrayRefs; + uint32_t Script_GetCurrentCastingInfo(uintptr_t *luaState) { luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why @@ -109,8 +122,12 @@ namespace Nampower { return 1; } - // Create new table - lua_newtable(luaState); + // Get or create reusable table + if (castInfoTableRef == LUA_REFNIL) { + lua_newtable(luaState); + castInfoTableRef = luaL_ref(luaState, LUA_REGISTRYINDEX); + } + lua_rawgeti(luaState, LUA_REGISTRYINDEX, castInfoTableRef); // Get current time and calculate offset to convert to WoW time uint32_t currentTime = GetTime(); @@ -268,12 +285,18 @@ namespace Nampower { luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why if (!lua_isnumber(luaState, 1)) { - lua_error(luaState, "Usage: GetItemStats(itemId)"); + lua_error(luaState, "Usage: GetItemStats(itemId, [copy])"); return 0; } uint32_t itemId = static_cast(lua_tonumber(luaState, 1)); + // Check for optional copy parameter + bool useCopy = false; + if (lua_isnumber(luaState, 2)) { + useCopy = static_cast(lua_tonumber(luaState, 2)) != 0; + } + // Get from cache game::ItemStats_C *item = GetItemStats(itemId); if (!item) { @@ -281,8 +304,16 @@ namespace Nampower { return 1; } - // Create new table - lua_newtable(luaState); + // Create new table or get reusable table based on copy parameter + if (useCopy) { + lua_newtable(luaState); + } else { + if (itemStatsTableRef == LUA_REFNIL) { + lua_newtable(luaState); + itemStatsTableRef = luaL_ref(luaState, LUA_REGISTRYINDEX); + } + lua_rawgeti(luaState, LUA_REGISTRYINDEX, itemStatsTableRef); + } // Push all simple fields using descriptors PushFieldsToLua(luaState, item, itemStatsFields, itemStatsFieldsCount); @@ -294,8 +325,12 @@ namespace Nampower { PushTableValue(luaState, const_cast("description"), item->m_description ? item->m_description : const_cast("")); - // Push all array fields using descriptors - PushArrayFieldsToLua(luaState, item, itemStatsArrayFields, itemStatsArrayFieldsCount); + // Push all array fields using descriptors with or without references based on copy parameter + if (useCopy) { + PushArrayFieldsToLua(luaState, item, itemStatsArrayFields, itemStatsArrayFieldsCount); + } else { + PushArrayFieldsToLuaWithRefs(luaState, item, itemStatsArrayFields, itemStatsArrayFieldsCount, itemStatsNestedArrayRefs); + } return 1; // Return the table } @@ -304,7 +339,7 @@ namespace Nampower { luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why if (!lua_isnumber(luaState, 1) || !lua_isstring(luaState, 2)) { - lua_error(luaState, "Usage: GetItemStatsField(itemId, fieldName)"); + lua_error(luaState, "Usage: GetItemStatsField(itemId, fieldName, [copy])"); return 0; } @@ -313,6 +348,12 @@ namespace Nampower { uint32_t itemId = static_cast(lua_tonumber(luaState, 1)); const char *fieldName = lua_tostring(luaState, 2); + // Check for optional copy parameter + bool useCopy = false; + if (lua_isnumber(luaState, 3)) { + useCopy = static_cast(lua_tonumber(luaState, 3)) != 0; + } + // Get from cache game::ItemStats_C *item = GetItemStats(itemId); if (!item) { @@ -354,7 +395,20 @@ namespace Nampower { if (arrayIt != itemStatsArrayFieldMap.end()) { size_t i = arrayIt->second; const auto &field = itemStatsArrayFields[i]; - lua_newtable(luaState); + + // Create new table or get reusable table based on copy parameter + if (useCopy) { + lua_newtable(luaState); + } else { + // Get or create reusable table for this specific field name + auto refIt = itemStatsArrayFieldRefs.find(fieldName); + if (refIt == itemStatsArrayFieldRefs.end()) { + lua_newtable(luaState); + int ref = luaL_ref(luaState, LUA_REGISTRYINDEX); + itemStatsArrayFieldRefs[fieldName] = ref; + } + lua_rawgeti(luaState, LUA_REGISTRYINDEX, itemStatsArrayFieldRefs[fieldName]); + } const char *fieldPtr = reinterpret_cast(item) + field.offset; @@ -404,11 +458,18 @@ namespace Nampower { luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why if (!lua_isstring(luaState, 1)) { - lua_error(luaState, "Usage: GetUnitData(unitToken) - unitToken can be 'player', 'target', 'pet', etc., or a GUID string"); + lua_error(luaState, "Usage: GetUnitData(unitToken, [copy]) - unitToken can be 'player', 'target', 'pet', etc., or a GUID string"); return 0; } const char *unitToken = lua_tostring(luaState, 1); + + // Check for optional copy parameter + bool useCopy = false; + if (lua_isnumber(luaState, 2)) { + useCopy = static_cast(lua_tonumber(luaState, 2)) != 0; + } + uint64_t guid = GetUnitGuidFromString(unitToken); if (guid == 0) { @@ -430,14 +491,26 @@ namespace Nampower { return 1; } - // Create new table - lua_newtable(luaState); + // Create new table or get reusable table based on copy parameter + if (useCopy) { + lua_newtable(luaState); + } else { + if (unitDataTableRef == LUA_REFNIL) { + lua_newtable(luaState); + unitDataTableRef = luaL_ref(luaState, LUA_REGISTRYINDEX); + } + lua_rawgeti(luaState, LUA_REGISTRYINDEX, unitDataTableRef); + } // Push all simple fields using descriptors PushFieldsToLua(luaState, unitFields, unitFieldsFields, unitFieldsFieldsCount); - // Push all array fields using descriptors - PushArrayFieldsToLua(luaState, unitFields, unitFieldsArrayFields, unitFieldsArrayFieldsCount); + // Push all array fields using descriptors with or without references based on copy parameter + if (useCopy) { + PushArrayFieldsToLua(luaState, unitFields, unitFieldsArrayFields, unitFieldsArrayFieldsCount); + } else { + PushArrayFieldsToLuaWithRefs(luaState, unitFields, unitFieldsArrayFields, unitFieldsArrayFieldsCount, unitFieldsNestedArrayRefs); + } return 1; // Return the table } @@ -446,7 +519,7 @@ namespace Nampower { luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why if (!lua_isstring(luaState, 1) || !lua_isstring(luaState, 2)) { - lua_error(luaState, "Usage: GetUnitField(unitToken, fieldName) - unitToken can be 'player', 'target', 'pet', etc., or a GUID string"); + lua_error(luaState, "Usage: GetUnitField(unitToken, fieldName, [copy]) - unitToken can be 'player', 'target', 'pet', etc., or a GUID string"); return 0; } @@ -455,6 +528,12 @@ namespace Nampower { const char *unitToken = lua_tostring(luaState, 1); const char *fieldName = lua_tostring(luaState, 2); + // Check for optional copy parameter + bool useCopy = false; + if (lua_isnumber(luaState, 3)) { + useCopy = static_cast(lua_tonumber(luaState, 3)) != 0; + } + uint64_t guid = GetUnitGuidFromString(unitToken); if (guid == 0) { @@ -505,7 +584,20 @@ namespace Nampower { if (arrayIt != unitFieldsArrayFieldMap.end()) { size_t i = arrayIt->second; const auto &field = unitFieldsArrayFields[i]; - lua_newtable(luaState); + + // Create new table or get reusable table based on copy parameter + if (useCopy) { + lua_newtable(luaState); + } else { + // Get or create reusable table for this specific field name + auto refIt = unitFieldsArrayFieldRefs.find(fieldName); + if (refIt == unitFieldsArrayFieldRefs.end()) { + lua_newtable(luaState); + int ref = luaL_ref(luaState, LUA_REGISTRYINDEX); + unitFieldsArrayFieldRefs[fieldName] = ref; + } + lua_rawgeti(luaState, LUA_REGISTRYINDEX, unitFieldsArrayFieldRefs[fieldName]); + } const char *fieldPtr = reinterpret_cast(unitFields) + field.offset; diff --git a/nampower/offsets.hpp b/nampower/offsets.hpp index c43f1cf..8bfe993 100644 --- a/nampower/offsets.hpp +++ b/nampower/offsets.hpp @@ -160,8 +160,11 @@ enum class Offsets : std::uint32_t { lua_newtable = 0x006F3C90, lua_settable = 0x006F3E20, lua_pushboolean = 0x006F39F0, - lua_rawseti = 0x006F3EA0, + lua_rawseti = 0x006f3f60, + lua_rawgeti = 0x006f3bc0, lua_gettop = 0x006F3070, + luaL_ref = 0x006F5310, + luaL_unref = 0x006F5400, CGInputControlGetActive = 0XBE1148, diff --git a/nampower/spell_scripts.cpp b/nampower/spell_scripts.cpp index ba8385a..4454755 100644 --- a/nampower/spell_scripts.cpp +++ b/nampower/spell_scripts.cpp @@ -10,6 +10,15 @@ #include namespace Nampower { + // Reusable table references to reduce memory allocations + static int spellRecTableRef = LUA_REFNIL; + + // Map to store separate references for each array field name (for Field function) + static std::unordered_map spellRecArrayFieldRefs; + + // Map to store separate references for nested array fields (for main table function) + static std::unordered_map spellRecNestedArrayRefs; + uint32_t Script_CastSpellByNameNoQueue(uintptr_t *luaState) { luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why @@ -265,12 +274,18 @@ namespace Nampower { luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why if (!lua_isnumber(luaState, 1)) { - lua_error(luaState, "Usage: GetSpellRec(spellId)"); + lua_error(luaState, "Usage: GetSpellRec(spellId, [copy])"); return 0; } uint32_t spellId = static_cast(lua_tonumber(luaState, 1)); + // Check for optional copy parameter + bool useCopy = false; + if (lua_isnumber(luaState, 2)) { + useCopy = static_cast(lua_tonumber(luaState, 2)) != 0; + } + // Get spell info auto spell = game::GetSpellInfo(spellId); if (!spell) { @@ -278,8 +293,16 @@ namespace Nampower { return 1; } - // Create new table - lua_newtable(luaState); + // Create new table or get reusable table based on copy parameter + if (useCopy) { + lua_newtable(luaState); + } else { + if (spellRecTableRef == LUA_REFNIL) { + lua_newtable(luaState); + spellRecTableRef = luaL_ref(luaState, LUA_REGISTRYINDEX); + } + lua_rawgeti(luaState, LUA_REGISTRYINDEX, spellRecTableRef); + } // Push all simple fields using descriptors PushFieldsToLua(luaState, spell, spellRecFields, spellRecFieldsCount); @@ -294,8 +317,12 @@ namespace Nampower { ? const_cast(reinterpret_cast(spell->Rank[language])) : const_cast("")); - // Push all array fields using descriptors - PushArrayFieldsToLua(luaState, spell, spellRecArrayFields, spellRecArrayFieldsCount); + // Push all array fields using descriptors with or without references based on copy parameter + if (useCopy) { + PushArrayFieldsToLua(luaState, spell, spellRecArrayFields, spellRecArrayFieldsCount); + } else { + PushArrayFieldsToLuaWithRefs(luaState, spell, spellRecArrayFields, spellRecArrayFieldsCount, spellRecNestedArrayRefs); + } return 1; // Return the table } @@ -304,7 +331,7 @@ namespace Nampower { luaState = GetLuaStatePtr(); // pcall leads to corrupted lua state pointer on added scripts, not sure why if (!lua_isnumber(luaState, 1) || !lua_isstring(luaState, 2)) { - lua_error(luaState, "Usage: GetSpellRecField(spellId, fieldName)"); + lua_error(luaState, "Usage: GetSpellRecField(spellId, fieldName, [copy])"); return 0; } @@ -313,6 +340,12 @@ namespace Nampower { uint32_t spellId = static_cast(lua_tonumber(luaState, 1)); const char *fieldName = lua_tostring(luaState, 2); + // Check for optional copy parameter + bool useCopy = false; + if (lua_isnumber(luaState, 3)) { + useCopy = static_cast(lua_tonumber(luaState, 3)) != 0; + } + // Get spell info auto spell = game::GetSpellInfo(spellId); if (!spell) { @@ -355,7 +388,20 @@ namespace Nampower { if (arrayIt != spellRecArrayFieldMap.end()) { size_t i = arrayIt->second; const auto &field = spellRecArrayFields[i]; - lua_newtable(luaState); + + // Create new table or get reusable table based on copy parameter + if (useCopy) { + lua_newtable(luaState); + } else { + // Get or create reusable table for this specific field name + auto refIt = spellRecArrayFieldRefs.find(fieldName); + if (refIt == spellRecArrayFieldRefs.end()) { + lua_newtable(luaState); + int ref = luaL_ref(luaState, LUA_REGISTRYINDEX); + spellRecArrayFieldRefs[fieldName] = ref; + } + lua_rawgeti(luaState, LUA_REGISTRYINDEX, spellRecArrayFieldRefs[fieldName]); + } const char *fieldPtr = reinterpret_cast(spell) + field.offset;