switch to table references to reduce memory usage

This commit is contained in:
avitasia
2025-12-17 09:27:25 -08:00
parent a0751ccf19
commit 163b4db361
11 changed files with 574 additions and 219 deletions
+125 -19
View File
@@ -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
+9 -1
View File
@@ -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);
+50
View File
@@ -141,4 +141,54 @@ namespace Nampower {
lua_settable(luaState, -3);
}
}
// Version that uses reusable table references for each array field
template<typename T>
inline void PushArrayFieldsToLuaWithRefs(uintptr_t* luaState, const T* obj, const ArrayFieldDescriptor* fields,
size_t fieldCount, std::unordered_map<std::string, int>& refMap) {
for (size_t i = 0; i < fieldCount; ++i) {
const auto& field = fields[i];
lua_pushstring(luaState, const_cast<char*>(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<const char*>(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<const int32_t*>(fieldPtr)[j]);
break;
case FieldType::UINT32:
lua_pushnumber(luaState, reinterpret_cast<const uint32_t*>(fieldPtr)[j]);
break;
case FieldType::FLOAT:
lua_pushnumber(luaState, reinterpret_cast<const float*>(fieldPtr)[j]);
break;
case FieldType::UINT64:
lua_pushnumber(luaState, static_cast<double>(reinterpret_cast<const uint64_t*>(fieldPtr)[j]));
break;
case FieldType::UINT8:
lua_pushnumber(luaState, reinterpret_cast<const uint8_t*>(fieldPtr)[j]);
break;
case FieldType::STRING: {
const char* str = reinterpret_cast<const char* const*>(fieldPtr)[j];
lua_pushstring(luaState, str ? const_cast<char*>(str) : const_cast<char*>(""));
break;
}
}
lua_settable(luaState, -3);
}
lua_settable(luaState, -3);
}
}
}
+3
View File
@@ -26,6 +26,9 @@ namespace Nampower {
lua_pushnilT lua_pushnil = reinterpret_cast<lua_pushnilT>(Offsets::lua_pushnil);
lua_newtableT lua_newtable = reinterpret_cast<lua_newtableT>(Offsets::lua_newtable);
lua_settableT lua_settable = reinterpret_cast<lua_settableT>(Offsets::lua_settable);
luaL_refT luaL_ref = reinterpret_cast<luaL_refT>(Offsets::luaL_ref);
lua_rawgetiT lua_rawgeti = reinterpret_cast<lua_rawgetiT>(Offsets::lua_rawgeti);
luaL_unrefT luaL_unref = reinterpret_cast<luaL_unrefT>(Offsets::luaL_unref);
uint32_t GetSpellSlotAndTypeForName(const char *spellName, uint32_t *spellType) {
+8
View File
@@ -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);
+40 -7
View File
@@ -14,6 +14,13 @@ 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;
// 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<GetContainerGuidT>(Offsets::GetContainerGuid);
auto const getBagItem = reinterpret_cast<CGBag_C_GetItemAtSlotT>(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<double>(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<double>(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<double>(bagIndex));
lua_newtable(luaState);
lua_rawgeti(luaState, LUA_REGISTRYINDEX, bagTableRef);
auto bagSize = *bagPtr;
for (uint32_t slot = 0; slot < bagSize; slot++) {
+170 -167
View File
@@ -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<hadesmem::PatchDetour<SpellVisualsInitializeT >> gSpellVisualsInitDetour;
std::unique_ptr<hadesmem::PatchDetour<LoadScriptFunctionsT >> gLoadScriptFunctionsDetour;
std::unique_ptr<hadesmem::PatchDetour<FrameScript_CreateEventsT >> gCreateEventsDetour;
std::unique_ptr<hadesmem::PatchDetour<FramescriptSetEventCountT >> gSetEventCountDetour;
std::unique_ptr<hadesmem::PatchDetour<SpellVisualsInitializeT> > gSpellVisualsInitDetour;
std::unique_ptr<hadesmem::PatchDetour<LoadScriptFunctionsT> > gLoadScriptFunctionsDetour;
std::unique_ptr<hadesmem::PatchDetour<FrameScript_CreateEventsT> > gCreateEventsDetour;
std::unique_ptr<hadesmem::PatchDetour<FramescriptSetEventCountT> > gSetEventCountDetour;
std::unique_ptr<hadesmem::PatchDetour<SetCVarT>> gSetCVarDetour;
std::unique_ptr<hadesmem::PatchDetour<CGSpellBook_CastSpellT>> gCGSpellBook_CastSpellDetour;
std::unique_ptr<hadesmem::PatchDetour<CastSpellT>> gCastDetour;
std::unique_ptr<hadesmem::PatchDetour<SendCastT>> gSendCastDetour;
std::unique_ptr<hadesmem::PatchDetour<CancelSpellT>> gCancelSpellDetour;
std::unique_ptr<hadesmem::PatchDetour<SignalEventT>> gSignalEventDetour;
std::unique_ptr<hadesmem::PatchDetour<Spell_C_SpellFailedT>> gSpellFailedDetour;
std::unique_ptr<hadesmem::PatchDetour<Spell_C_GetSpellModifiersT>> gSpell_C_GetSpellModifiersDetour;
std::unique_ptr<hadesmem::PatchDetour<Spell_C_GetSpellRadiusT>> gSpell_C_GetSpellRadiusDetour;
std::unique_ptr<hadesmem::PatchDetour<SetCVarT> > gSetCVarDetour;
std::unique_ptr<hadesmem::PatchDetour<CGSpellBook_CastSpellT> > gCGSpellBook_CastSpellDetour;
std::unique_ptr<hadesmem::PatchDetour<CastSpellT> > gCastDetour;
std::unique_ptr<hadesmem::PatchDetour<SendCastT> > gSendCastDetour;
std::unique_ptr<hadesmem::PatchDetour<CancelSpellT> > gCancelSpellDetour;
std::unique_ptr<hadesmem::PatchDetour<SignalEventT> > gSignalEventDetour;
std::unique_ptr<hadesmem::PatchDetour<Spell_C_SpellFailedT> > gSpellFailedDetour;
std::unique_ptr<hadesmem::PatchDetour<Spell_C_GetSpellModifiersT> > gSpell_C_GetSpellModifiersDetour;
std::unique_ptr<hadesmem::PatchDetour<Spell_C_GetSpellRadiusT> > gSpell_C_GetSpellRadiusDetour;
std::unique_ptr<hadesmem::PatchRaw> gCastbarPatch;
std::unique_ptr<hadesmem::PatchDetour<ISceneEndT>> gIEndSceneDetour;
std::unique_ptr<hadesmem::PatchDetour<Spell_C_GetAutoRepeatingSpellT>> gSpell_C_GetAutoRepeatingSpellDetour;
std::unique_ptr<hadesmem::PatchDetour<Spell_C_CooldownEventTriggeredT >> gSpell_C_CooldownEventTriggeredDetour;
std::unique_ptr<hadesmem::PatchDetour<SpellGoT>> gSpellGoDetour;
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT>> gSpellTargetUnitDetour;
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT>> gSpellStopCastingDetour;
std::unique_ptr<hadesmem::PatchDetour<OnSpriteRightClickT>> gOnSpriteRightClickDetour;
std::unique_ptr<hadesmem::PatchDetour<Spell_C_HandleSpriteClickT>> gSpell_C_HandleSpriteClickDetour;
std::unique_ptr<hadesmem::PatchDetour<Spell_C_HandleTerrainClickT>> gSpell_C_HandleTerrainClickDetour;
std::unique_ptr<hadesmem::PatchDetour<CGWorldFrame_OnLayerTrackTerrainT>> gCGWorldFrame_OnLayerTrackTerrainDetour;
std::unique_ptr<hadesmem::PatchDetour<Spell_C_TargetSpellT>> gSpell_C_TargetSpellDetour;
std::unique_ptr<hadesmem::PatchDetour<ISceneEndT> > gIEndSceneDetour;
std::unique_ptr<hadesmem::PatchDetour<Spell_C_GetAutoRepeatingSpellT> > gSpell_C_GetAutoRepeatingSpellDetour;
std::unique_ptr<hadesmem::PatchDetour<Spell_C_CooldownEventTriggeredT> > gSpell_C_CooldownEventTriggeredDetour;
std::unique_ptr<hadesmem::PatchDetour<SpellGoT> > gSpellGoDetour;
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT> > gSpellTargetUnitDetour;
std::unique_ptr<hadesmem::PatchDetour<LuaScriptT> > gSpellStopCastingDetour;
std::unique_ptr<hadesmem::PatchDetour<OnSpriteRightClickT> > gOnSpriteRightClickDetour;
std::unique_ptr<hadesmem::PatchDetour<Spell_C_HandleSpriteClickT> > gSpell_C_HandleSpriteClickDetour;
std::unique_ptr<hadesmem::PatchDetour<Spell_C_HandleTerrainClickT> > gSpell_C_HandleTerrainClickDetour;
std::unique_ptr<hadesmem::PatchDetour<CGWorldFrame_OnLayerTrackTerrainT> > gCGWorldFrame_OnLayerTrackTerrainDetour;
std::unique_ptr<hadesmem::PatchDetour<Spell_C_TargetSpellT> > gSpell_C_TargetSpellDetour;
std::unique_ptr<hadesmem::PatchDetour<PacketHandlerT>> gSpellCooldownDetour;
std::unique_ptr<hadesmem::PatchDetour<PacketHandlerT>> gSpellDelayedDetour;
std::unique_ptr<hadesmem::PatchDetour<PacketHandlerT>> gCastResultHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<PacketHandlerT>> gSpellFailedHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<PacketHandlerT>> gSpellChannelStartHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<PacketHandlerT>> gSpellChannelUpdateHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<PacketHandlerT>> gPlaySpellVisualHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<PacketHandlerT> > gSpellCooldownDetour;
std::unique_ptr<hadesmem::PatchDetour<PacketHandlerT> > gSpellDelayedDetour;
std::unique_ptr<hadesmem::PatchDetour<PacketHandlerT> > gCastResultHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<PacketHandlerT> > gSpellFailedHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<PacketHandlerT> > gSpellChannelStartHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<PacketHandlerT> > gSpellChannelUpdateHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<PacketHandlerT> > gPlaySpellVisualHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<FastCallPacketHandlerT>> gSpellStartHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<FastCallPacketHandlerT>> gPeriodicAuraLogHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<FastCallPacketHandlerT>> gSpellNonMeleeDmgLogHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<FastCallPacketHandlerT> > gSpellStartHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<FastCallPacketHandlerT> > gPeriodicAuraLogHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<FastCallPacketHandlerT> > gSpellNonMeleeDmgLogHandlerDetour;
std::unique_ptr<hadesmem::PatchDetour<CGPlayer_C_OnAttackIconPressedT>> gCGPlayer_C_OnAttackIconPressedDetour;
std::unique_ptr<hadesmem::PatchDetour<CGActionBar_UseActionT>> gCGActionBar_UseActionDetour;
std::unique_ptr<hadesmem::PatchDetour<CGUnit_C_OnAuraRemovedT>> gCGUnit_C_OnAuraRemovedDetour;
std::unique_ptr<hadesmem::PatchDetour<CGUnit_C_OnAuraAddedT>> gCGUnit_C_OnAuraAddedDetour;
std::unique_ptr<hadesmem::PatchDetour<CGUnit_C_OnAuraAddedStackT>> gCGUnit_C_OnAuraAddedStackDetour;
std::unique_ptr<hadesmem::PatchDetour<UnitCombatLogUnitDeadT>> gUnitCombatLogUnitDeadDetour;
std::unique_ptr<hadesmem::PatchDetour<CGPlayer_C_OnAttackIconPressedT> > gCGPlayer_C_OnAttackIconPressedDetour;
std::unique_ptr<hadesmem::PatchDetour<CGActionBar_UseActionT> > gCGActionBar_UseActionDetour;
std::unique_ptr<hadesmem::PatchDetour<CGUnit_C_OnAuraRemovedT> > gCGUnit_C_OnAuraRemovedDetour;
std::unique_ptr<hadesmem::PatchDetour<CGUnit_C_OnAuraAddedT> > gCGUnit_C_OnAuraAddedDetour;
std::unique_ptr<hadesmem::PatchDetour<CGUnit_C_OnAuraAddedStackT> > gCGUnit_C_OnAuraAddedStackDetour;
std::unique_ptr<hadesmem::PatchDetour<UnitCombatLogUnitDeadT> > gUnitCombatLogUnitDeadDetour;
std::unique_ptr<hadesmem::PatchDetour<InvalidFunctionPtrCheckT>> gInvalidFunctionPtrCheckDetour;
std::unique_ptr<hadesmem::PatchDetour<InvalidFunctionPtrCheckT> > gInvalidFunctionPtrCheckDetour;
std::unique_ptr<hadesmem::PatchDetour<GetSpellSlotFromLuaT>> gGetSpellSlotFromLuaDetour;
std::unique_ptr<hadesmem::PatchDetour<GetSpellSlotFromLuaT> > gGetSpellSlotFromLuaDetour;
uint32_t GetTime() {
return static_cast<uint32_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
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<FrameScript_RegisterFunctionT>(Offsets::FrameScript_RegisterFunction);
auto const registerFunction = reinterpret_cast<FrameScript_RegisterFunctionT>(
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>(game::TYPEMASK_PLAYER | game::TYPEMASK_UNIT), objectGUID);
static_cast<game::TypeMask>(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<uint32_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
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<typename FuncT, typename HookT>
std::unique_ptr<hadesmem::PatchDetour<FuncT>>
std::unique_ptr<hadesmem::PatchDetour<FuncT> >
createHook(const hadesmem::Process &process, Offsets offset, HookT hookFunc) {
auto const originalFunc = hadesmem::detail::AliasCast<FuncT>(offset);
auto detour = std::make_unique<hadesmem::PatchDetour<FuncT>>(process, originalFunc, hookFunc);
auto detour = std::make_unique<hadesmem::PatchDetour<FuncT> >(process, originalFunc, hookFunc);
detour->Apply();
return detour;
}
@@ -1116,8 +1111,10 @@ namespace Nampower {
const hadesmem::Process process(::GetCurrentProcessId());
gSetCVarDetour = createHook<SetCVarT>(process, Offsets::Script_SetCVar, &Script_SetCVarHook);
gCGSpellBook_CastSpellDetour = createHook<CGSpellBook_CastSpellT>(process, Offsets::CGSpellBook_CastSpell, &CGSpellBook_CastSpellHook);
gCGActionBar_UseActionDetour = createHook<CGActionBar_UseActionT>(process, Offsets::CGActionBar_UseAction, &CGActionBar_UseActionHook);
gCGSpellBook_CastSpellDetour = createHook<CGSpellBook_CastSpellT>(
process, Offsets::CGSpellBook_CastSpell, &CGSpellBook_CastSpellHook);
gCGActionBar_UseActionDetour = createHook<CGActionBar_UseActionT>(
process, Offsets::CGActionBar_UseAction, &CGActionBar_UseActionHook);
gCastDetour = createHook<CastSpellT>(process, Offsets::Spell_C_CastSpell, &Spell_C_CastSpellHook);
gSendCastDetour = createHook<SendCastT>(process, Offsets::SendCast, &SendCastHook);
gCancelSpellDetour = createHook<CancelSpellT>(process, Offsets::CancelSpell, &CancelSpellHook);
@@ -1144,10 +1141,12 @@ namespace Nampower {
&Script_SpellStopCastingHook);
gSpell_C_TargetSpellDetour = createHook<Spell_C_TargetSpellT>(process, Offsets::Spell_C_TargetSpell,
&Spell_C_TargetSpellHook);
gSpell_C_HandleTerrainClickDetour = createHook<Spell_C_HandleTerrainClickT>(process, Offsets::Spell_C_HandleTerrainClick,
&Spell_C_HandleTerrainClickHook);
gCGWorldFrame_OnLayerTrackTerrainDetour = createHook<CGWorldFrame_OnLayerTrackTerrainT>(process, Offsets::CGWorldFrame_OnLayerTrackTerrain,
&CGWorldFrame_OnLayerTrackTerrainHook);
gSpell_C_HandleTerrainClickDetour = createHook<Spell_C_HandleTerrainClickT>(
process, Offsets::Spell_C_HandleTerrainClick,
&Spell_C_HandleTerrainClickHook);
gCGWorldFrame_OnLayerTrackTerrainDetour = createHook<CGWorldFrame_OnLayerTrackTerrainT>(
process, Offsets::CGWorldFrame_OnLayerTrackTerrain,
&CGWorldFrame_OnLayerTrackTerrainHook);
gOnSpriteRightClickDetour = createHook<OnSpriteRightClickT>(process, Offsets::OnSpriteRightClick,
OnSpriteRightClickHook);
gIEndSceneDetour = createHook<ISceneEndT>(process, Offsets::ISceneEndPtr, &ISceneEndHook);
@@ -1156,13 +1155,14 @@ namespace Nampower {
gGetSpellSlotFromLuaDetour = createHook<GetSpellSlotFromLuaT>(process, Offsets::GetSpellSlotFromLua,
&GetSpellSlotFromLuaHook);
gCGUnit_C_OnAuraRemovedDetour = createHook<CGUnit_C_OnAuraRemovedT>(process, Offsets::CGUnit_C_OnAuraRemoved,
&CGUnit_C_OnAuraRemovedHook);
&CGUnit_C_OnAuraRemovedHook);
gCGUnit_C_OnAuraAddedDetour = createHook<CGUnit_C_OnAuraAddedT>(process, Offsets::CGUnit_C_OnAuraAdded,
&CGUnit_C_OnAuraAddedHook);
gCGUnit_C_OnAuraAddedStackDetour = createHook<CGUnit_C_OnAuraAddedStackT>(process, Offsets::CGUnit_C_OnAuraAddedStack,
&CGUnit_C_OnAuraAddedStackHook);
&CGUnit_C_OnAuraAddedHook);
gCGUnit_C_OnAuraAddedStackDetour = createHook<CGUnit_C_OnAuraAddedStackT>(
process, Offsets::CGUnit_C_OnAuraAddedStack,
&CGUnit_C_OnAuraAddedStackHook);
gUnitCombatLogUnitDeadDetour = createHook<UnitCombatLogUnitDeadT>(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<char ***>(Offsets::Framescript_EventObject_Data);
void addCustomEvent(uint32_t code, char *name) {
char **FrameScript_EventObject_Data = *reinterpret_cast<char ***>(Offsets::Framescript_EventObject_Data);
auto SStrDupA = reinterpret_cast<char* (__stdcall *)(char*, char*, int)>(Offsets::SStrDupA);
FrameScript_EventObject_Data[code*4] = SStrDupA(name, name, 1308);
auto SStrDupA = reinterpret_cast<char* (__stdcall *)(char *, char *, int)>(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<FramescriptSetEventCountT>();
if (count > 200) {
@@ -1333,7 +1334,6 @@ namespace Nampower {
char getItemIdCooldown[] = "GetItemIdCooldown";
RegisterLuaFunction(getItemIdCooldown, reinterpret_cast<uintptr_t *>(Script_GetItemIdCooldown));
}
std::once_flag loadFlag;
@@ -1344,36 +1344,39 @@ namespace Nampower {
const hadesmem::Process process(::GetCurrentProcessId());
auto const spellVisualsInitOrig = hadesmem::detail::AliasCast<SpellVisualsInitializeT>(
Offsets::SpellVisualsInitialize);
gSpellVisualsInitDetour = std::make_unique<hadesmem::PatchDetour<SpellVisualsInitializeT >>(process,
spellVisualsInitOrig,
&SpellVisualsInitializeHook);
Offsets::SpellVisualsInitialize);
gSpellVisualsInitDetour = std::make_unique<hadesmem::PatchDetour<SpellVisualsInitializeT> >(
process,
spellVisualsInitOrig,
&SpellVisualsInitializeHook);
gSpellVisualsInitDetour->Apply();
auto const loadScriptFunctionsOrig = hadesmem::detail::AliasCast<LoadScriptFunctionsT>(
Offsets::LoadScriptFunctions);
gLoadScriptFunctionsDetour = std::make_unique<hadesmem::PatchDetour<LoadScriptFunctionsT >>(process,
loadScriptFunctionsOrig,
&LoadScriptFunctionsHook);
Offsets::LoadScriptFunctions);
gLoadScriptFunctionsDetour = std::make_unique<hadesmem::PatchDetour<LoadScriptFunctionsT> >(
process,
loadScriptFunctionsOrig,
&LoadScriptFunctionsHook);
gLoadScriptFunctionsDetour->Apply();
auto const createEventsOrig = hadesmem::detail::AliasCast<FrameScript_CreateEventsT>(
Offsets::FrameScript_CreateEvents);
gCreateEventsDetour = std::make_unique<hadesmem::PatchDetour<FrameScript_CreateEventsT >>(process,
createEventsOrig,
&FrameScript_CreateEventsHook);
Offsets::FrameScript_CreateEvents);
gCreateEventsDetour = std::make_unique<hadesmem::PatchDetour<FrameScript_CreateEventsT> >(
process,
createEventsOrig,
&FrameScript_CreateEventsHook);
gCreateEventsDetour->Apply();
auto const setEventCountOrig = hadesmem::detail::AliasCast<FramescriptSetEventCountT>(
Offsets::Framescript_SetEventCount);
gSetEventCountDetour = std::make_unique<hadesmem::PatchDetour<FramescriptSetEventCountT >>(process,
setEventCountOrig,
&Framescript_SetEventCountHook);
Offsets::Framescript_SetEventCount);
gSetEventCountDetour = std::make_unique<hadesmem::PatchDetour<FramescriptSetEventCountT> >(
process,
setEventCountOrig,
&Framescript_SetEventCountHook);
gSetEventCountDetour->Apply();
}
);
}
}
extern "C" __declspec(dllexport) uint32_t Load() {
+4 -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 = 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,
+108 -16
View File
@@ -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<std::string, int> itemStatsArrayFieldRefs;
static std::unordered_map<std::string, int> unitFieldsArrayFieldRefs;
// Maps to store separate references for nested array fields (for main table functions)
static std::unordered_map<std::string, int> itemStatsNestedArrayRefs;
static std::unordered_map<std::string, int> 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<uint32_t>(lua_tonumber(luaState, 1));
// Check for optional copy parameter
bool useCopy = false;
if (lua_isnumber(luaState, 2)) {
useCopy = static_cast<int>(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<char *>("description"),
item->m_description ? item->m_description : const_cast<char *>(""));
// 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<uint32_t>(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<int>(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<const char *>(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<int>(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<int>(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<const char *>(unitFields) + field.offset;
+4 -1
View File
@@ -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,
+53 -7
View File
@@ -10,6 +10,15 @@
#include <cstring>
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<std::string, int> spellRecArrayFieldRefs;
// Map to store separate references for nested array fields (for main table function)
static std::unordered_map<std::string, int> 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<uint32_t>(lua_tonumber(luaState, 1));
// Check for optional copy parameter
bool useCopy = false;
if (lua_isnumber(luaState, 2)) {
useCopy = static_cast<int>(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<char *>(reinterpret_cast<const char *>(spell->Rank[language]))
: const_cast<char *>(""));
// 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<uint32_t>(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<int>(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<const char *>(spell) + field.offset;