From 2c81677cfa500bdc7f70f19229674d450226e4f3 Mon Sep 17 00:00:00 2001 From: avitasia Date: Tue, 2 Dec 2025 13:27:59 -0800 Subject: [PATCH] add unit field access as well as spell modifiers --- README.md | 199 ++++++++++++++++++++++++++--- UNIT_FIELDS.md | 266 +++++++++++++++++++++++++++++++++++++++ nampower/CMakeLists.txt | 2 + nampower/game.cpp | 2 +- nampower/main.cpp | 23 +++- nampower/main.hpp | 1 + nampower/offsets.hpp | 3 +- nampower/scripts.cpp | 194 ++++++++++++++++++++++++++++ nampower/scripts.hpp | 6 + nampower/unit_fields.cpp | 110 ++++++++++++++++ nampower/unit_fields.hpp | 26 ++++ 11 files changed, 806 insertions(+), 26 deletions(-) create mode 100644 UNIT_FIELDS.md create mode 100644 nampower/unit_fields.cpp create mode 100644 nampower/unit_fields.hpp diff --git a/README.md b/README.md index cc2d0b9..a64f340 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/UNIT_FIELDS.md b/UNIT_FIELDS.md new file mode 100644 index 0000000..a55fc16 --- /dev/null +++ b/UNIT_FIELDS.md @@ -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 diff --git a/nampower/CMakeLists.txt b/nampower/CMakeLists.txt index 6b3b5ad..95736bb 100644 --- a/nampower/CMakeLists.txt +++ b/nampower/CMakeLists.txt @@ -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}) diff --git a/nampower/game.cpp b/nampower/game.cpp index 2867108..ba1af54 100644 --- a/nampower/game.cpp +++ b/nampower/game.cpp @@ -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(Offsets::Spell_C_GetSpellModifiers); + auto const getModifiers = reinterpret_cast(Offsets::Spell_C_ApplySpellModifiers); auto modificationPercentage = 0; getModifiers(spellRec, &modificationPercentage, spellMod); diff --git a/nampower/main.cpp b/nampower/main.cpp index 732e976..8fd5913 100644 --- a/nampower/main.cpp +++ b/nampower/main.cpp @@ -93,6 +93,7 @@ namespace Nampower { std::unique_ptr> gCancelSpellDetour; std::unique_ptr> gSignalEventDetour; std::unique_ptr> gSpellFailedDetour; + std::unique_ptr> gSpell_C_GetSpellModifiersDetour; std::unique_ptr gCastbarPatch; std::unique_ptr> gIEndSceneDetour; std::unique_ptr> 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(); + 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(process, Offsets::GetSpellSlotFromLua, &GetSpellSlotFromLuaHook); - // gCGPlayer_C_OnAttackIconPressedDetour = createHook(process, - // Offsets::CGPlayer_C_OnAttackIconPressed, - // &CGPlayer_C_OnAttackIconPressedHook); } void SpellVisualsInitializeHook(hadesmem::PatchDetourBase *detour) { @@ -1176,7 +1181,8 @@ namespace Nampower { RegisterLuaFunction(getSpellSlotTypeIdForName, reinterpret_cast(Script_GetSpellSlotTypeIdForName)); char channelStopCastingNextTick[] = "ChannelStopCastingNextTick"; - RegisterLuaFunction(channelStopCastingNextTick, reinterpret_cast(Script_ChannelStopCastingNextTick)); + RegisterLuaFunction(channelStopCastingNextTick, + reinterpret_cast(Script_ChannelStopCastingNextTick)); char getNampowerVersion[] = "GetNampowerVersion"; RegisterLuaFunction(getNampowerVersion, reinterpret_cast(Script_GetNampowerVersion)); @@ -1195,6 +1201,15 @@ namespace Nampower { char getSpellRecField[] = "GetSpellRecField"; RegisterLuaFunction(getSpellRecField, reinterpret_cast(Script_GetSpellRecField)); + + char getUnitData[] = "GetUnitData"; + RegisterLuaFunction(getUnitData, reinterpret_cast(Script_GetUnitData)); + + char getUnitField[] = "GetUnitField"; + RegisterLuaFunction(getUnitField, reinterpret_cast(Script_GetUnitField)); + + char getSpellModifiers[] = "GetSpellModifiers"; + RegisterLuaFunction(getSpellModifiers, reinterpret_cast(Script_GetSpellModifiers)); } std::once_flag loadFlag; diff --git a/nampower/main.hpp b/nampower/main.hpp index 294f02c..a15522a 100644 --- a/nampower/main.hpp +++ b/nampower/main.hpp @@ -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); diff --git a/nampower/offsets.hpp b/nampower/offsets.hpp index d565e09..63f1801 100644 --- a/nampower/offsets.hpp +++ b/nampower/offsets.hpp @@ -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, diff --git a/nampower/scripts.cpp b/nampower/scripts.cpp index 2422937..0d1519e 100644 --- a/nampower/scripts.cpp +++ b/nampower/scripts.cpp @@ -6,6 +6,7 @@ #include "offsets.hpp" #include "items.hpp" #include "dbc_fields.hpp" +#include "unit_fields.hpp" #include "helper.hpp" #include @@ -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(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(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(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(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(unitFields) + unitFieldsFields[i].offset; + + switch (unitFieldsFields[i].type) { + case FieldType::UINT32: + lua_pushnumber(luaState, *reinterpret_cast(fieldPtr)); + return 1; + case FieldType::UINT64: + lua_pushnumber(luaState, static_cast(*reinterpret_cast(fieldPtr))); + return 1; + case FieldType::FLOAT: + lua_pushnumber(luaState, *reinterpret_cast(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(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(fieldPtr)[j]); + break; + case FieldType::FLOAT: + lua_pushnumber(luaState, reinterpret_cast(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(lua_tonumber(luaState, 1)); + int modifierType = static_cast(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(Offsets::Spell_C_GetSpellModifierValues); + + int flatModificationValue = 0; + uint32_t percentModificationValue = 0; + + auto ret = getModifierValues(const_cast(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(); diff --git a/nampower/scripts.hpp b/nampower/scripts.hpp index ccbd19f..3dc361f 100644 --- a/nampower/scripts.hpp +++ b/nampower/scripts.hpp @@ -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); diff --git a/nampower/unit_fields.cpp b/nampower/unit_fields.cpp new file mode 100644 index 0000000..2c5ed93 --- /dev/null +++ b/nampower/unit_fields.cpp @@ -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 unitFieldsFieldMap; + std::unordered_map 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; + } +} diff --git a/nampower/unit_fields.hpp b/nampower/unit_fields.hpp new file mode 100644 index 0000000..86a2df1 --- /dev/null +++ b/nampower/unit_fields.hpp @@ -0,0 +1,26 @@ +// +// Created by pmacc on 1/8/2025. +// + +#pragma once + +#include "main.hpp" +#include "dbc_fields.hpp" +#include +#include +#include + +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 unitFieldsFieldMap; + extern std::unordered_map unitFieldsArrayFieldMap; + + // Initialize unit fields maps (call once before using field lookups) + void InitializeUnitFieldMaps(); +}