add unit field access as well as spell modifiers

This commit is contained in:
avitasia
2025-12-02 13:27:59 -08:00
parent 2ed95bc439
commit 2c81677cfa
11 changed files with 806 additions and 26 deletions
+179 -20
View File
@@ -119,6 +119,185 @@ Examples:
### 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.
Full field name lists are in [`DBC_FIELDS.md`](DBC_FIELDS.md).
#### GetItemStatsField(itemId, fieldName)
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.
Full field name lists are in [`DBC_FIELDS.md`](DBC_FIELDS.md).
**Examples:**
```lua
-- Get item name
local name = GetItemStatsField(19019, "displayName")
print(name) -- "Thunderfury, Blessed Blade of the Windseeker"
-- Get item level
local ilvl = GetItemStatsField(22589, "itemLevel")
print("Atiesh item level: " .. ilvl) -- 90
-- Get item quality (0=Poor, 1=Common, 2=Uncommon, 3=Rare, 4=Epic, 5=Legendary)
local quality = GetItemStatsField(19019, "quality")
print("Quality: " .. quality) -- 5 (Legendary)
-- Get item delay (weapon speed in milliseconds)
local delay = GetItemStatsField(19019, "delay")
print("Weapon speed: " .. (delay / 1000) .. " seconds") -- 1.9 seconds
```
#### 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.
Full field name lists are in [`DBC_FIELDS.md`](DBC_FIELDS.md).
#### GetSpellRecField(spellId, fieldName)
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.
Full field name lists are in [`DBC_FIELDS.md`](DBC_FIELDS.md).
**Examples:**
```lua
-- Get spell name
local name = GetSpellRecField(116, "name")
print(name) -- "Frostbolt"
-- Get spell rank
local rank = GetSpellRecField(116, "rank")
print(rank) -- "Rank 1"
-- Get spell cast time in milliseconds
local castTime = GetSpellRecField(133, "castTime")
print("Fireball cast time: " .. (castTime / 1000) .. " seconds") -- 3.5 seconds
-- Get spell range (max range in yards * 10, so divide by 10)
local maxRange = GetSpellRecField(116, "rangeMax")
print("Frostbolt max range: " .. (maxRange / 10) .. " yards") -- 30 yards
-- Get spell mana cost
local manaCost = GetSpellRecField(116, "manaCost")
print("Mana cost: " .. manaCost)
-- Get spell school (0=Physical, 1=Holy, 2=Fire, 3=Nature, 4=Frost, 5=Shadow, 6=Arcane)
local school = GetSpellRecField(116, "school")
print("School: " .. school) -- 4 (Frost)
-- Get spell icon ID
local spellIconID = GetSpellRecField(116, "spellIconID")
print("Icon ID: " .. spellIconID)
```
#### GetSpellModifiers(spellId, modifierType)
Returns the current spell modifiers applied to a spell for the player. This includes buffs, talents, and other effects that modify spell behavior.
**Parameters:**
- `spellId` (number): The spell ID to check
- `modifierType` (number): The type of modifier to check (see list below)
**Returns:**
- 1st param (number): Flat modification value (e.g., +50 damage)
- 2nd param (number): Percent modification value (e.g., 10 for +10%)
- 3rd param (number): Return value from the function (whether there was any percent or flat modifier)
**Modifier Types:**
- 0 = DAMAGE
- 1 = DURATION
- 2 = THREAT
- 3 = ATTACK_POWER
- 4 = CHARGES
- 5 = RANGE
- 6 = RADIUS
- 7 = CRITICAL_CHANCE
- 8 = ALL_EFFECTS
- 9 = NOT_LOSE_CASTING_TIME
- 10 = CASTING_TIME
- 11 = COOLDOWN
- 12 = SPEED
- 14 = COST
- 15 = CRIT_DAMAGE_BONUS
- 16 = RESIST_MISS_CHANCE
- 17 = JUMP_TARGETS
- 18 = CHANCE_OF_SUCCESS
- 19 = ACTIVATION_TIME
- 20 = EFFECT_PAST_FIRST
- 21 = CASTING_TIME_OLD
- 22 = DOT
- 23 = HASTE
- 24 = SPELL_BONUS_DAMAGE
- 27 = MULTIPLE_VALUE
- 28 = RESIST_DISPEL_CHANCE
**Example:**
```lua
-- Check damage modifiers on Frostbolt (spell ID 116)
local flatMod, percentMod, ret = GetSpellModifiers(116, 0)
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.
**Parameters:**
- `unitToken` (string): Can be a standard unit token ("player", "target", "pet", "mouseover", etc.) or a GUID string (e.g., "0xF5300000000000A5")
**Returns:**
- A Lua table containing all unit fields, or nil if the unit cannot be found
Full field name lists are in [`UNIT_FIELDS.md`](UNIT_FIELDS.md).
**Example:**
```lua
-- Get all unit data for your current target
local data = GetUnitData("target")
if data then
print("Target health: " .. data.health .. "/" .. data.maxHealth)
print("Target level: " .. data.level)
print("Target display ID: " .. data.displayId)
end
-- Using a GUID
local data = GetUnitData("0xF5300000000000A5")
```
#### GetUnitField(unitToken, fieldName)
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
**Returns:**
- The requested field value; returns nil if the unit is not found; raises a Lua error if the field name is invalid
- For array fields (like "aura", "resistances"), returns a Lua table with numeric indices
Full field name lists are in [`UNIT_FIELDS.md`](UNIT_FIELDS.md).
**Examples:**
```lua
-- Get target's current health
local health = GetUnitField("target", "health")
print("Target health: " .. health)
-- Get player's current mana (power1)
local mana = GetUnitField("player", "power1")
print("Player mana: " .. mana)
-- Get all auras on target (returns a table)
local auras = GetUnitField("target", "aura")
for i, auraId in ipairs(auras) do
print("Aura " .. i .. ": " .. auraId)
end
-- Get all resistances (returns a table)
local resistances = GetUnitField("player", "resistances")
-- resistances[1] = armor, [2] = holy, [3] = fire, [4] = nature, [5] = frost, [6] = shadow, [7] = arcane
```
#### QueueSpellByName(spellName)
Will force queue a spell regardless of the appropriate queue window. If no spell is currently being cast it will be cast immediately.
For example can make a macro with
@@ -228,26 +407,6 @@ Examples:
/run local slot, bookType, spellId=GetSpellSlotTypeIdForName("Frostbolt");print(slot);print(bookType);print(spellId)
```
#### 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.
Full field name lists are in [`DBC_FIELDS.md`](DBC_FIELDS.md).
#### GetItemStatsField(itemId, fieldName)
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.
Full field name lists are in [`DBC_FIELDS.md`](DBC_FIELDS.md).
#### 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.
Full field name lists are in [`DBC_FIELDS.md`](DBC_FIELDS.md).
#### GetSpellRecField(spellId, fieldName)
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.
Full field name lists are in [`DBC_FIELDS.md`](DBC_FIELDS.md).
#### GetNampowerVersion()
Returns the current version of Nampower split into major, minor and patch numbers.
+266
View File
@@ -0,0 +1,266 @@
# Unit Fields Reference
This document lists all available unit fields that can be accessed via `GetUnitData()` and `GetUnitField()` functions.
## Overview
Unit fields provide low-level access to game data for any unit (player, target, pet, NPCs, etc.). These fields are part of the WoW 1.12 client's internal data structures.
## Usage
```lua
-- Get all fields at once
local data = GetUnitData("target")
if data then
print("Health: " .. data.health)
end
-- Get a single field (more efficient)
local health = GetUnitField("target", "health")
```
## Simple Fields
These fields return a single value (number).
### Object References (UINT64)
These fields contain GUIDs (globally unique identifiers) for game objects:
- **charm** - GUID of the unit that has charmed this unit
- **summon** - GUID of the unit's current summon
- **charmedBy** - GUID of the unit that charmed this unit
- **summonedBy** - GUID of the unit that summoned this unit
- **createdBy** - GUID of the unit that created this unit
- **target** - GUID of the unit's current target
- **persuaded** - GUID of the unit being persuaded
- **channelObject** - GUID of the object being channeled (for channeling spells)
### Health and Power (UINT32)
- **health** - Current health points
- **power1** - Current mana (or energy/rage depending on class)
- **power2** - Current rage
- **power3** - Current focus (hunter pets)
- **power4** - Current energy (rogues, druids in cat/bear form)
- **power5** - Current happiness (hunter pets)
- **maxHealth** - Maximum health points
- **maxPower1** - Maximum mana
- **maxPower2** - Maximum rage
- **maxPower3** - Maximum focus
- **maxPower4** - Maximum energy
- **maxPower5** - Maximum happiness
### Basic Unit Info (UINT32)
- **level** - Unit's level
- **factionTemplate** - Unit's faction template ID
- **flags** - Unit flags (combat, dead, mounted, etc.)
- **dynamicFlags** - Dynamic unit flags (lootable, tapped, etc.)
- **auraState** - Aura state flags (reactive abilities like Revenge, Overpower)
- **npcFlags** - NPC flags (vendor, trainer, quest giver, etc.)
- **npcEmoteState** - Current emote state ID
### Display and Appearance (UINT32)
- **displayId** - Current display model ID
- **nativeDisplayId** - Original/native display model ID
- **mountDisplayId** - Mount display model ID (if mounted)
### Combat Stats (UINT32)
- **baseAttackTime** - Base main-hand attack time in milliseconds
- **offhandAttackTime** - Off-hand attack time in milliseconds
- **rangedAttackTime** - Ranged attack time in milliseconds
### Combat Stats (FLOAT)
- **boundingRadius** - Unit's bounding radius for collision
- **combatReach** - Unit's combat reach distance
- **minDamage** - Minimum main-hand damage
- **maxDamage** - Maximum main-hand damage
- **minOffhandDamage** - Minimum off-hand damage
- **maxOffhandDamage** - Maximum off-hand damage
- **minRangedDamage** - Minimum ranged damage
- **maxRangedDamage** - Maximum ranged damage
### Pet Info (UINT32)
- **petNumber** - Pet's number identifier
- **petNameTimestamp** - Timestamp of pet name
- **petExperience** - Pet's current experience
- **petNextLevelExp** - Experience needed for pet's next level
### Spell/Channel Info (UINT32)
- **channelSpell** - Spell ID of currently channeling spell
- **createdBySpell** - Spell ID that created this unit (for summons)
### Casting Speed (FLOAT)
- **modCastSpeed** - Cast speed modifier (1.0 = normal, <1.0 = faster, >1.0 = slower)
### Training (UINT32)
- **trainingPoints** - Available training points (for pets and talents)
### Base Stats (UINT32)
- **stat0** - Strength
- **stat1** - Agility
- **stat2** - Stamina
- **stat3** - Intellect
- **stat4** - Spirit
- **baseMana** - Base mana before modifiers
- **baseHealth** - Base health before modifiers
### Attack Power (UINT32)
- **attackPower** - Total melee attack power
- **attackPowerMods** - Attack power modifiers
- **rangedAttackPower** - Total ranged attack power
- **rangedAttackPowerMods** - Ranged attack power modifiers
### Attack Power Multipliers (FLOAT)
- **attackPowerMultiplier** - Melee attack power multiplier
- **rangedAttackPowerMultiplier** - Ranged attack power multiplier
### Packed Byte Fields (UINT32)
These are special fields that pack multiple values into a single 32-bit integer:
- **bytes0** - Contains race, class, gender, and power type
- Byte 0: Race
- Byte 1: Class
- Byte 2: Gender
- Byte 3: Power type
- **bytes1** - Contains stand state, pet talent points, vis flags, and anim tier
- Byte 0: Stand state
- Byte 1: Pet talent points
- Byte 2: Vis flags
- Byte 3: Anim tier
- **bytes2** - Contains sheath state, pvp flags, pet flags, and shape shift form
- Byte 0: Sheath state
- Byte 1: PvP flags
- Byte 2: Pet flags
- Byte 3: Shape shift form
## Array Fields
These fields return a Lua table with numeric indices (1-based).
### Visual Items (UINT32 arrays)
- **virtualItemDisplay** [3] - Display IDs for virtual items (weapon visuals, etc.)
- **virtualItemInfo** [6] - Additional virtual item information
### Auras/Buffs/Debuffs (UINT32 arrays)
- **aura** [48] - Array of aura (buff/debuff) spell IDs on the unit
- Index 1-48 contains spell IDs (0 = no aura in that slot)
- **auraFlags** [6] - Bit flags for aura properties (positive, negative, cancelable, etc.)
- Each UINT32 contains flags for 8 auras (4 bits per aura)
- **auraLevels** [12] - Aura caster levels
- Each UINT32 contains 4 aura levels (8 bits per level)
- **auraApplications** [12] - Aura stack counts
- Each UINT32 contains 4 aura stacks (8 bits per stack)
### Resistances (UINT32 arrays)
- **resistances** [7] - Resistance values for all schools
- Index 1: Armor (physical resistance)
- Index 2: Holy resistance
- Index 3: Fire resistance
- Index 4: Nature resistance
- Index 5: Frost resistance
- Index 6: Shadow resistance
- Index 7: Arcane resistance
### Spell Power Modifiers (FLOAT arrays)
- **powerCostModifier** [7] - Flat mana cost modifications per school
- Index 1-7: Physical, Holy, Fire, Nature, Frost, Shadow, Arcane
- **powerCostMultiplier** [7] - Percent mana cost modifications per school
- Index 1-7: Physical, Holy, Fire, Nature, Frost, Shadow, Arcane
## Examples
### Health Monitoring
```lua
-- Monitor target health percentage
local health = GetUnitField("target", "health")
local maxHealth = GetUnitField("target", "maxHealth")
local healthPct = (health / maxHealth) * 100
print("Target health: " .. healthPct .. "%")
```
### Checking Auras
```lua
-- Check if target has a specific debuff
local auras = GetUnitField("target", "aura")
local CURSE_OF_AGONY = 980
for i, spellId in ipairs(auras) do
if spellId == CURSE_OF_AGONY then
print("Target has Curse of Agony!")
break
end
end
```
### Reading Resistances
```lua
-- Get all player resistances
local resistances = GetUnitField("player", "resistances")
print("Armor: " .. resistances[1])
print("Holy resistance: " .. resistances[2])
print("Fire resistance: " .. resistances[3])
print("Nature resistance: " .. resistances[4])
print("Frost resistance: " .. resistances[5])
print("Shadow resistance: " .. resistances[6])
print("Arcane resistance: " .. resistances[7])
```
### Checking Combat State
```lua
-- Check if unit is in combat (flags field bit check)
local flags = GetUnitField("target", "flags")
local UNIT_FLAG_IN_COMBAT = 0x00080000
local inCombat = bit.band(flags, UNIT_FLAG_IN_COMBAT) ~= 0
print("In combat: " .. tostring(inCombat))
```
### Monitoring Cast Speed
```lua
-- Check your current cast speed modifier
local modCastSpeed = GetUnitField("player", "modCastSpeed")
-- 1.0 = normal, 0.9 = 10% faster, 1.1 = 10% slower
print("Cast speed modifier: " .. modCastSpeed)
```
### Attack Power Info
```lua
-- Get your attack power
local ap = GetUnitField("player", "attackPower")
local apMods = GetUnitField("player", "attackPowerMods")
local apMult = GetUnitField("player", "attackPowerMultiplier")
print("Base AP: " .. ap)
print("AP Mods: " .. apMods)
print("AP Multiplier: " .. apMult)
```
### Pet Information
```lua
-- Check pet experience
if UnitExists("pet") then
local petXP = GetUnitField("pet", "petExperience")
local petNextXP = GetUnitField("pet", "petNextLevelExp")
local progress = (petXP / petNextXP) * 100
print("Pet XP: " .. progress .. "%")
end
```
## Notes
- GUID fields return numbers but represent 64-bit values. Lua handles these as doubles.
- Array indices are 1-based (Lua convention), not 0-based.
- Returns `nil` if the unit doesn't exist or the field cannot be read.
- Some fields may require specific game state to be meaningful (e.g., pet fields only work when you have a pet).
- Unit flags and other bit fields require bitwise operations to interpret properly.
## Related Functions
- `GetUnitData(unitToken)` - Get all fields at once
- `GetUnitField(unitToken, fieldName)` - Get a single field efficiently
- `GetSpellModifiers(spellId, modifierType)` - Get spell modifiers
- `GetItemStats(itemId)` - Get item data
- `GetSpellRec(spellId)` - Get spell data
+2
View File
@@ -28,6 +28,8 @@ set(SOURCE_FILES
items.cpp
dbc_fields.hpp
dbc_fields.cpp
unit_fields.hpp
unit_fields.cpp
)
add_library(${DLL_NAME} SHARED ${SOURCE_FILES})
+1 -1
View File
@@ -85,7 +85,7 @@ namespace game {
int GetSpellModifier(const SpellRec *spellRec, SpellModOp spellMod) {
using Spell_C_GetSpellModifiersT = void (__fastcall *)(const SpellRec *spellRec, int *returnVal, SpellModOp modOp);
auto const getModifiers = reinterpret_cast<Spell_C_GetSpellModifiersT>(Offsets::Spell_C_GetSpellModifiers);
auto const getModifiers = reinterpret_cast<Spell_C_GetSpellModifiersT>(Offsets::Spell_C_ApplySpellModifiers);
auto modificationPercentage = 0;
getModifiers(spellRec, &modificationPercentage, spellMod);
+19 -4
View File
@@ -93,6 +93,7 @@ namespace Nampower {
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::PatchRaw> gCastbarPatch;
std::unique_ptr<hadesmem::PatchDetour<ISceneEndT>> gIEndSceneDetour;
std::unique_ptr<hadesmem::PatchDetour<Spell_C_GetAutoRepeatingSpellT>> gSpell_C_GetAutoRepeatingSpellDetour;
@@ -539,6 +540,13 @@ namespace Nampower {
// remove original call to allow registering lua functions at any address range
}
void
Spell_C_GetSpellModifiersHook(hadesmem::PatchDetourBase *detour, const game::SpellRec *spellRec, int *returnVal,
game::SpellModOp modOp) {
auto const getSpellModifiers = detour->GetTrampolineT<Spell_C_GetSpellModifiersT>();
getSpellModifiers(spellRec, returnVal, modOp);
}
void updateFromCvar(const char *cvar, const char *value) {
if (strcmp(cvar, "NP_QueueCastTimeSpells") == 0) {
gUserSettings.queueCastTimeSpells = atoi(value) != 0;
@@ -1120,9 +1128,6 @@ namespace Nampower {
gGetSpellSlotFromLuaDetour = createHook<GetSpellSlotFromLuaT>(process, Offsets::GetSpellSlotFromLua,
&GetSpellSlotFromLuaHook);
// gCGPlayer_C_OnAttackIconPressedDetour = createHook<CGPlayer_C_OnAttackIconPressedT>(process,
// Offsets::CGPlayer_C_OnAttackIconPressed,
// &CGPlayer_C_OnAttackIconPressedHook);
}
void SpellVisualsInitializeHook(hadesmem::PatchDetourBase *detour) {
@@ -1176,7 +1181,8 @@ namespace Nampower {
RegisterLuaFunction(getSpellSlotTypeIdForName, reinterpret_cast<uintptr_t *>(Script_GetSpellSlotTypeIdForName));
char channelStopCastingNextTick[] = "ChannelStopCastingNextTick";
RegisterLuaFunction(channelStopCastingNextTick, reinterpret_cast<uintptr_t *>(Script_ChannelStopCastingNextTick));
RegisterLuaFunction(channelStopCastingNextTick,
reinterpret_cast<uintptr_t *>(Script_ChannelStopCastingNextTick));
char getNampowerVersion[] = "GetNampowerVersion";
RegisterLuaFunction(getNampowerVersion, reinterpret_cast<uintptr_t *>(Script_GetNampowerVersion));
@@ -1195,6 +1201,15 @@ namespace Nampower {
char getSpellRecField[] = "GetSpellRecField";
RegisterLuaFunction(getSpellRecField, reinterpret_cast<uintptr_t *>(Script_GetSpellRecField));
char getUnitData[] = "GetUnitData";
RegisterLuaFunction(getUnitData, reinterpret_cast<uintptr_t *>(Script_GetUnitData));
char getUnitField[] = "GetUnitField";
RegisterLuaFunction(getUnitField, reinterpret_cast<uintptr_t *>(Script_GetUnitField));
char getSpellModifiers[] = "GetSpellModifiers";
RegisterLuaFunction(getSpellModifiers, reinterpret_cast<uintptr_t *>(Script_GetSpellModifiers));
}
std::once_flag loadFlag;
+1
View File
@@ -93,6 +93,7 @@ namespace Nampower {
using Spell_C_GetSpellCooldownT = int (__fastcall *)(uint32_t spellId, uint32_t isPetSpell,
uint32_t *duration, uint64_t *startTime, uint32_t *enable);
using Spell_C_IsSpellUsableT = int (__fastcall *)(const game::SpellRec *spellRec, uint32_t *usesManaReturn);
using Spell_C_GetSpellModifiersT = void (__fastcall *)(const game::SpellRec *spellRec, int *returnVal, game::SpellModOp modOp);
using CGPlayer_C_OnAttackIconPressedT = int (__fastcall *)(uintptr_t *this_ptr, void *dummy_edx, uint64_t guid);
+2 -1
View File
@@ -104,7 +104,8 @@ enum class Offsets : std::uint32_t {
Spell_C_GetSpellCooldown = 0X006E2EA0,
Spell_C_IsSpellUsable = 0X006E3D60,
Spell_C_GetDuration = 0X006EA000,
Spell_C_GetSpellModifiers = 0x006e6af0,
Spell_C_ApplySpellModifiers = 0x006e6af0,
Spell_C_GetSpellModifierValues = 0x006e6b30,
CVarLookup = 0x0063DEC0,
RegisterCVar = 0X0063DB90,
+194
View File
@@ -6,6 +6,7 @@
#include "offsets.hpp"
#include "items.hpp"
#include "dbc_fields.hpp"
#include "unit_fields.hpp"
#include "helper.hpp"
#include <cstring>
@@ -697,6 +698,199 @@ namespace Nampower {
return 0;
}
uint32_t Script_GetUnitData(uintptr_t *luaState) {
if (!lua_isstring(luaState, 1)) {
lua_error(luaState, "Usage: GetUnitData(unitToken) - unitToken can be 'player', 'target', 'pet', etc., or a GUID string");
return 0;
}
const char *unitToken = lua_tostring(luaState, 1);
uint64_t guid;
// Check if it's a GUID string (starts with "0x" or "0X")
if (strncmp(unitToken, "0x", 2) == 0 || strncmp(unitToken, "0X", 2) == 0) {
guid = std::stoull(unitToken, nullptr, 16);
} else {
// Get GUID from unit token
auto const getGUIDFromName = reinterpret_cast<GetGUIDFromNameT>(Offsets::GetGUIDFromName);
guid = getGUIDFromName(unitToken);
}
if (guid == 0) {
lua_pushnil(luaState);
return 1;
}
// Get unit object pointer
auto unit = game::GetObjectPtr(guid);
if (!unit) {
lua_pushnil(luaState);
return 1;
}
// Get unit fields (offset 68 from unit pointer)
auto *unitFields = *reinterpret_cast<game::UnitFields **>(unit + 68);
if (!unitFields) {
lua_pushnil(luaState);
return 1;
}
// Create new table
lua_newtable(luaState);
// Push all simple fields using descriptors
PushFieldsToLua(luaState, unitFields, unitFieldsFields, unitFieldsFieldsCount);
// Push all array fields using descriptors
PushArrayFieldsToLua(luaState, unitFields, unitFieldsArrayFields, unitFieldsArrayFieldsCount);
return 1; // Return the table
}
uint32_t Script_GetUnitField(uintptr_t *luaState) {
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");
return 0;
}
InitializeUnitFieldMaps();
const char *unitToken = lua_tostring(luaState, 1);
const char *fieldName = lua_tostring(luaState, 2);
uint64_t guid;
// Check if it's a GUID string (starts with "0x" or "0X")
if (strncmp(unitToken, "0x", 2) == 0 || strncmp(unitToken, "0X", 2) == 0) {
guid = std::stoull(unitToken, nullptr, 16);
} else {
// Get GUID from unit token
auto const getGUIDFromName = reinterpret_cast<GetGUIDFromNameT>(Offsets::GetGUIDFromName);
guid = getGUIDFromName(unitToken);
}
if (guid == 0) {
lua_pushnil(luaState);
return 1;
}
// Get unit object pointer
auto unit = game::GetObjectPtr(guid);
if (!unit) {
lua_pushnil(luaState);
return 1;
}
// Get unit fields (offset 68 from unit pointer)
auto *unitFields = *reinterpret_cast<game::UnitFields **>(unit + 68);
if (!unitFields) {
lua_pushnil(luaState);
return 1;
}
// O(1) hash table lookup for simple fields
auto simpleIt = unitFieldsFieldMap.find(fieldName);
if (simpleIt != unitFieldsFieldMap.end()) {
size_t i = simpleIt->second;
const char *fieldPtr = reinterpret_cast<const char *>(unitFields) + unitFieldsFields[i].offset;
switch (unitFieldsFields[i].type) {
case FieldType::UINT32:
lua_pushnumber(luaState, *reinterpret_cast<const uint32_t *>(fieldPtr));
return 1;
case FieldType::UINT64:
lua_pushnumber(luaState, static_cast<double>(*reinterpret_cast<const uint64_t *>(fieldPtr)));
return 1;
case FieldType::FLOAT:
lua_pushnumber(luaState, *reinterpret_cast<const float *>(fieldPtr));
return 1;
default:
break;
}
}
// O(1) hash table lookup for array fields
auto arrayIt = unitFieldsArrayFieldMap.find(fieldName);
if (arrayIt != unitFieldsArrayFieldMap.end()) {
size_t i = arrayIt->second;
const auto &field = unitFieldsArrayFields[i];
lua_newtable(luaState);
const char *fieldPtr = reinterpret_cast<const char *>(unitFields) + field.offset;
for (size_t j = 0; j < field.count; ++j) {
lua_pushnumber(luaState, j + 1);
switch (field.type) {
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;
default:
lua_pushnumber(luaState, 0);
break;
}
lua_settable(luaState, -3);
}
return 1;
}
// Field not found
lua_error(luaState, "Unknown field name");
return 0;
}
uint32_t Script_GetSpellModifiers(uintptr_t *luaState) {
if (!lua_isnumber(luaState, 1) || !lua_isnumber(luaState, 2)) {
lua_error(luaState, "Usage: GetSpellModifiers(spellId, modifierType) - modifierType: 0=DAMAGE, 1=DURATION, 2=THREAT, 3=ATTACK_POWER, 4=CHARGES, 5=RANGE, 6=RADIUS, 7=CRITICAL_CHANCE, 8=ALL_EFFECTS, 9=NOT_LOSE_CASTING_TIME, 10=CASTING_TIME, 11=COOLDOWN, 12=SPEED, 14=COST, 15=CRIT_DAMAGE_BONUS, 16=RESIST_MISS_CHANCE, 17=JUMP_TARGETS, 18=CHANCE_OF_SUCCESS, 19=ACTIVATION_TIME, 20=EFFECT_PAST_FIRST, 21=CASTING_TIME_OLD, 22=DOT, 23=HASTE, 24=SPELL_BONUS_DAMAGE, 27=MULTIPLE_VALUE, 28=RESIST_DISPEL_CHANCE");
return 0;
}
uint32_t spellId = static_cast<uint32_t>(lua_tonumber(luaState, 1));
int modifierType = static_cast<int>(lua_tonumber(luaState, 2));
// Validate modifier type
if (modifierType < 0 || modifierType >= game::MAX_SPELLMOD) {
lua_error(luaState, "Invalid modifier type. Must be between 0 and 28.");
return 0;
}
// Get spell info
auto spell = game::GetSpellInfo(spellId);
if (!spell) {
lua_error(luaState, "Invalid spell id");
return 0;
}
// Call Spell_C_GetSpellModifierValues
using Spell_C_GetSpellModifierValuesT = uint32_t (__fastcall *)(game::SpellRec *spellRec, int mod, int *flatMod, uint32_t *percentMod);
auto const getModifierValues = reinterpret_cast<Spell_C_GetSpellModifierValuesT>(Offsets::Spell_C_GetSpellModifierValues);
int flatModificationValue = 0;
uint32_t percentModificationValue = 0;
auto ret = getModifierValues(const_cast<game::SpellRec *>(spell), modifierType, &flatModificationValue, &percentModificationValue);
// show flat modifications
lua_pushnumber(luaState, flatModificationValue);
if (flatModificationValue != 0 && percentModificationValue >= 100) {
percentModificationValue -= 100;
} else if (modifierType == 9 && percentModificationValue >= 100) {
percentModificationValue -= 100;
}
// show percent modifications
lua_pushnumber(luaState, percentModificationValue);
// show return
lua_pushnumber(luaState, ret);
return 3;
}
uint32_t GetSpellSlotFromLuaHook(hadesmem::PatchDetourBase *detour, int param_1, uint32_t *slot, uint32_t *type) {
// Get Lua state pointer
uintptr_t *luaState = GetLuaStatePtr();
+6
View File
@@ -42,6 +42,12 @@ namespace Nampower {
uint32_t Script_GetSpellRecField(uintptr_t *luaState);
uint32_t Script_GetUnitData(uintptr_t *luaState);
uint32_t Script_GetUnitField(uintptr_t *luaState);
uint32_t Script_GetSpellModifiers(uintptr_t *luaState);
uint32_t GetSpellSlotFromLuaHook(hadesmem::PatchDetourBase *detour, int param_1, uint32_t *slot, uint32_t *type);
bool RunQueuedScript(int priority);
+110
View File
@@ -0,0 +1,110 @@
//
// Created by pmacc on 1/8/2025.
//
#include "unit_fields.hpp"
#include "game.hpp"
namespace Nampower {
// Hash tables for O(1) field lookups
std::unordered_map<std::string, size_t> unitFieldsFieldMap;
std::unordered_map<std::string, size_t> unitFieldsArrayFieldMap;
static bool unitFieldMapsInitialized = false;
// UnitFields field descriptors
const FieldDescriptor unitFieldsFields[] = {
{"charm", offsetof(game::UnitFields, charm), FieldType::UINT64},
{"summon", offsetof(game::UnitFields, summon), FieldType::UINT64},
{"charmedBy", offsetof(game::UnitFields, charmedBy), FieldType::UINT64},
{"summonedBy", offsetof(game::UnitFields, summonedBy), FieldType::UINT64},
{"createdBy", offsetof(game::UnitFields, createdBy), FieldType::UINT64},
{"target", offsetof(game::UnitFields, target), FieldType::UINT64},
{"persuaded", offsetof(game::UnitFields, persuaded), FieldType::UINT64},
{"channelObject", offsetof(game::UnitFields, channelObject), FieldType::UINT64},
{"health", offsetof(game::UnitFields, health), FieldType::UINT32},
{"power1", offsetof(game::UnitFields, power1), FieldType::UINT32},
{"power2", offsetof(game::UnitFields, power2), FieldType::UINT32},
{"power3", offsetof(game::UnitFields, power3), FieldType::UINT32},
{"power4", offsetof(game::UnitFields, power4), FieldType::UINT32},
{"power5", offsetof(game::UnitFields, power5), FieldType::UINT32},
{"maxHealth", offsetof(game::UnitFields, maxHealth), FieldType::UINT32},
{"maxPower1", offsetof(game::UnitFields, maxPower1), FieldType::UINT32},
{"maxPower2", offsetof(game::UnitFields, maxPower2), FieldType::UINT32},
{"maxPower3", offsetof(game::UnitFields, maxPower3), FieldType::UINT32},
{"maxPower4", offsetof(game::UnitFields, maxPower4), FieldType::UINT32},
{"maxPower5", offsetof(game::UnitFields, maxPower5), FieldType::UINT32},
{"level", offsetof(game::UnitFields, level), FieldType::UINT32},
{"factionTemplate", offsetof(game::UnitFields, factionTemplate), FieldType::UINT32},
{"bytes0", offsetof(game::UnitFields, bytes0), FieldType::UINT32},
{"flags", offsetof(game::UnitFields, flags), FieldType::UINT32},
{"auraState", offsetof(game::UnitFields, auraState), FieldType::UINT32},
{"baseAttackTime", offsetof(game::UnitFields, baseAttackTime), FieldType::UINT32},
{"offhandAttackTime", offsetof(game::UnitFields, offhandAttackTime), FieldType::UINT32},
{"rangedAttackTime", offsetof(game::UnitFields, rangedAttackTime), FieldType::UINT32},
{"boundingRadius", offsetof(game::UnitFields, boundingRadius), FieldType::FLOAT},
{"combatReach", offsetof(game::UnitFields, combatReach), FieldType::FLOAT},
{"displayId", offsetof(game::UnitFields, displayId), FieldType::UINT32},
{"nativeDisplayId", offsetof(game::UnitFields, nativeDisplayId), FieldType::UINT32},
{"mountDisplayId", offsetof(game::UnitFields, mountDisplayId), FieldType::UINT32},
{"minDamage", offsetof(game::UnitFields, minDamage), FieldType::FLOAT},
{"maxDamage", offsetof(game::UnitFields, maxDamage), FieldType::FLOAT},
{"minOffhandDamage", offsetof(game::UnitFields, minOffhandDamage), FieldType::FLOAT},
{"maxOffhandDamage", offsetof(game::UnitFields, maxOffhandDamage), FieldType::FLOAT},
{"bytes1", offsetof(game::UnitFields, bytes1), FieldType::UINT32},
{"petNumber", offsetof(game::UnitFields, petNumber), FieldType::UINT32},
{"petNameTimestamp", offsetof(game::UnitFields, petNameTimestamp), FieldType::UINT32},
{"petExperience", offsetof(game::UnitFields, petExperience), FieldType::UINT32},
{"petNextLevelExp", offsetof(game::UnitFields, petNextLevelExp), FieldType::UINT32},
{"dynamicFlags", offsetof(game::UnitFields, dynamicFlags), FieldType::UINT32},
{"channelSpell", offsetof(game::UnitFields, channelSpell), FieldType::UINT32},
{"modCastSpeed", offsetof(game::UnitFields, modCastSpeed), FieldType::FLOAT},
{"createdBySpell", offsetof(game::UnitFields, createdBySpell), FieldType::UINT32},
{"npcFlags", offsetof(game::UnitFields, npcFlags), FieldType::UINT32},
{"npcEmoteState", offsetof(game::UnitFields, npcEmoteState), FieldType::UINT32},
{"trainingPoints", offsetof(game::UnitFields, trainingPoints), FieldType::UINT32},
{"stat0", offsetof(game::UnitFields, stat0), FieldType::UINT32},
{"stat1", offsetof(game::UnitFields, stat1), FieldType::UINT32},
{"stat2", offsetof(game::UnitFields, stat2), FieldType::UINT32},
{"stat3", offsetof(game::UnitFields, stat3), FieldType::UINT32},
{"stat4", offsetof(game::UnitFields, stat4), FieldType::UINT32},
{"baseMana", offsetof(game::UnitFields, baseMana), FieldType::UINT32},
{"baseHealth", offsetof(game::UnitFields, baseHealth), FieldType::UINT32},
{"bytes2", offsetof(game::UnitFields, bytes2), FieldType::UINT32},
{"attackPower", offsetof(game::UnitFields, attackPower), FieldType::UINT32},
{"attackPowerMods", offsetof(game::UnitFields, attackPowerMods), FieldType::UINT32},
{"attackPowerMultiplier", offsetof(game::UnitFields, attackPowerMultiplier), FieldType::FLOAT},
{"rangedAttackPower", offsetof(game::UnitFields, rangedAttackPower), FieldType::UINT32},
{"rangedAttackPowerMods", offsetof(game::UnitFields, rangedAttackPowerMods), FieldType::UINT32},
{"rangedAttackPowerMultiplier", offsetof(game::UnitFields, rangedAttackPowerMultiplier), FieldType::FLOAT},
{"minRangedDamage", offsetof(game::UnitFields, minRangedDamage), FieldType::FLOAT},
{"maxRangedDamage", offsetof(game::UnitFields, maxRangedDamage), FieldType::FLOAT},
};
const size_t unitFieldsFieldsCount = sizeof(unitFieldsFields) / sizeof(unitFieldsFields[0]);
const ArrayFieldDescriptor unitFieldsArrayFields[] = {
{"virtualItemDisplay", offsetof(game::UnitFields, virtualItemDisplay), FieldType::UINT32, 3},
{"virtualItemInfo", offsetof(game::UnitFields, virtualItemInfo), FieldType::UINT32, 6},
{"aura", offsetof(game::UnitFields, aura), FieldType::UINT32, 48},
{"auraFlags", offsetof(game::UnitFields, auraFlags), FieldType::UINT32, 6},
{"auraLevels", offsetof(game::UnitFields, auraLevels), FieldType::UINT32, 12},
{"auraApplications", offsetof(game::UnitFields, auraApplications), FieldType::UINT32, 12},
{"resistances", offsetof(game::UnitFields, resistances), FieldType::UINT32, 7},
{"powerCostModifier", offsetof(game::UnitFields, powerCostModifier), FieldType::FLOAT, 7},
{"powerCostMultiplier", offsetof(game::UnitFields, powerCostMultiplier), FieldType::FLOAT, 7},
};
const size_t unitFieldsArrayFieldsCount = sizeof(unitFieldsArrayFields) / sizeof(unitFieldsArrayFields[0]);
void InitializeUnitFieldMaps() {
if (unitFieldMapsInitialized) return;
// Build hash maps for O(1) lookups
for (size_t i = 0; i < unitFieldsFieldsCount; ++i) {
unitFieldsFieldMap[unitFieldsFields[i].name] = i;
}
for (size_t i = 0; i < unitFieldsArrayFieldsCount; ++i) {
unitFieldsArrayFieldMap[unitFieldsArrayFields[i].name] = i;
}
unitFieldMapsInitialized = true;
}
}
+26
View File
@@ -0,0 +1,26 @@
//
// Created by pmacc on 1/8/2025.
//
#pragma once
#include "main.hpp"
#include "dbc_fields.hpp"
#include <unordered_map>
#include <string>
#include <cstddef>
namespace Nampower {
// UnitFields field descriptors
extern const FieldDescriptor unitFieldsFields[];
extern const size_t unitFieldsFieldsCount;
extern const ArrayFieldDescriptor unitFieldsArrayFields[];
extern const size_t unitFieldsArrayFieldsCount;
// Hash tables for O(1) field lookups
extern std::unordered_map<std::string, size_t> unitFieldsFieldMap;
extern std::unordered_map<std::string, size_t> unitFieldsArrayFieldMap;
// Initialize unit fields maps (call once before using field lookups)
void InitializeUnitFieldMaps();
}