From c812f19a058055faf5510e370605b207d34970e1 Mon Sep 17 00:00:00 2001 From: avitasia Date: Tue, 4 Nov 2025 17:10:48 -0800 Subject: [PATCH 1/6] refactors --- nampower/main.cpp | 34 ++++++++++++++++++++++++++++++++++ nampower/main.hpp | 13 ++++++++++++- nampower/offsets.hpp | 3 +++ nampower/spellcast.cpp | 33 ++++++++++----------------------- nampower/spellevents.cpp | 10 +++++++++- nampower/spellevents.hpp | 3 +++ nampower/types.h | 2 +- 7 files changed, 72 insertions(+), 26 deletions(-) diff --git a/nampower/main.cpp b/nampower/main.cpp index 58176bf..21acb81 100644 --- a/nampower/main.cpp +++ b/nampower/main.cpp @@ -128,6 +128,8 @@ namespace Nampower { std::unique_ptr> gPeriodicAuraLogHandlerDetour; std::unique_ptr> gSpellNonMeleeDmgLogHandlerDetour; + std::unique_ptr> gCGPlayer_C_OnAttackIconPressedDetour; + uint32_t GetTime() { return static_cast(std::chrono::duration_cast( std::chrono::high_resolution_clock::now().time_since_epoch()).count()) - gStartTime; @@ -163,6 +165,34 @@ namespace Nampower { return p_GetContext(); } + void SetTarget(uint64_t target) { + auto setTargetFn = reinterpret_cast(Offsets::ScriptTargetUnit); + setTargetFn(&target); + } + + void SetSelectionTarget(uint64_t target) { + auto dataStore = CDataStore(); + uint32_t opcode = 317; // CMSG_SET_SELECTION + dataStore.Put(opcode); + dataStore.Put(target); + + dataStore.Finalize(); + + auto const clientServicesSend = reinterpret_cast(Offsets::ClientServices_Send); + clientServicesSend(&dataStore); + } + + void SetAttackTarget(uint64_t target) { + auto dataStore = CDataStore(); + uint32_t opcode = 0x141; // CMSG_ATTACKSWING + dataStore.Put(opcode); + dataStore.Put(target); + + dataStore.Finalize(); + + auto const clientServicesSend = reinterpret_cast(Offsets::ClientServices_Send); + clientServicesSend(&dataStore); + } void RegisterLuaFunction(char *name, uintptr_t *func) { DEBUG_LOG("Registering " << name << " to " << func); @@ -1109,6 +1139,10 @@ namespace Nampower { Script_GetNampowerVersion); gGetItemLevelDetour = createHook(process, Offsets::Script_GetItemLevel, Script_GetItemLevel); gIEndSceneDetour = createHook(process, Offsets::ISceneEndPtr, &ISceneEndHook); + + // gCGPlayer_C_OnAttackIconPressedDetour = createHook(process, + // Offsets::CGPlayer_C_OnAttackIconPressed, + // &CGPlayer_C_OnAttackIconPressedHook); } void SpellVisualsInitializeHook(hadesmem::PatchDetourBase *detour) { diff --git a/nampower/main.hpp b/nampower/main.hpp index 2827492..a1f2425 100644 --- a/nampower/main.hpp +++ b/nampower/main.hpp @@ -31,7 +31,7 @@ namespace Nampower { constexpr uint32_t MAJOR_VERSION = 2; constexpr uint32_t MINOR_VERSION = 11; - constexpr uint32_t PATCH_VERSION = 1; + constexpr uint32_t PATCH_VERSION = 2; constexpr int32_t LUA_REGISTRYINDEX = -10000; constexpr int32_t LUA_GLOBALSINDEX = -10001; @@ -94,6 +94,8 @@ namespace Nampower { uint32_t *duration, uint64_t *startTime, uint32_t *enable); using Spell_C_IsSpellUsableT = int (__fastcall *)(const game::SpellRec *spellRec, uint32_t *usesManaReturn); + using CGPlayer_C_OnAttackIconPressedT = int (__fastcall *)(uintptr_t *this_ptr, void *dummy_edx, uint64_t guid); + using GetSpellSlotAndTypeT = int (__fastcall *)(const char *, uint32_t *); using GetTimeMsT = uint64_t (__stdcall *)(); @@ -137,6 +139,8 @@ namespace Nampower { using GetBuffByIndexT = uintptr_t *(__fastcall *)(int index); + using TargetUnitT = void (__fastcall *)(uint64_t *guid); + using CVarLookupT = uintptr_t *(__fastcall *)(const char *); using SetCVarT = int (__fastcall *)(uintptr_t *luaPtr); using CVarRegisterT = int *(__fastcall *)(char *name, char *help, int unk1, const char *defaultValuePtr, @@ -170,4 +174,11 @@ namespace Nampower { bool processQueues(); uint32_t EffectiveCastEndMs(); + + void SetTarget(uint64_t target); + + void SetSelectionTarget(uint64_t target); + + void SetAttackTarget(uint64_t target); + } \ No newline at end of file diff --git a/nampower/offsets.hpp b/nampower/offsets.hpp index 11e330a..063cf97 100644 --- a/nampower/offsets.hpp +++ b/nampower/offsets.hpp @@ -76,6 +76,7 @@ enum class Offsets : std::uint32_t { LockedTargetGuid = 0x00B4E2D8, OnSpriteRightClick = 0x00492820, CGGameUI_Target = 0X00493540, + ScriptTargetUnit = 0X00489A40, SpellVisualsHandleCastStart = 0X006EC220, PlaySpellVisualHandler = 0X006E98D0, @@ -174,4 +175,6 @@ enum class Offsets : std::uint32_t { CGUnit_C_ClearCastingSpell = 0x0060d040, CGUnit_C_ClearSpellEffect = 0x00614150, + + CGPlayer_C_OnAttackIconPressed = 0X006131A0, }; diff --git a/nampower/spellcast.cpp b/nampower/spellcast.cpp index f38a8d4..1242d15 100644 --- a/nampower/spellcast.cpp +++ b/nampower/spellcast.cpp @@ -108,8 +108,6 @@ namespace Nampower { } gLastCastData.startTimeMs = currentTime; - - // if queued cast, simulate spellcast start } void CastQueuedNonGcdSpell() { @@ -120,7 +118,7 @@ namespace Nampower { gCastData.castingQueuedSpell = true; gCastData.numRetries = nonGcdCastParams.numRetries; - Spell_C_CastSpellHook(castSpellDetour, nonGcdCastParams.playerUnit, nonGcdCastParams.spellId, + Spell_C_CastSpellHook(castSpellDetour, nonGcdCastParams.casterUnit, nonGcdCastParams.spellId, nonGcdCastParams.item, nonGcdCastParams.guid); gLastCastData.wasQueued = true; } else { @@ -140,7 +138,7 @@ namespace Nampower { DEBUG_LOG("Triggering queued cast of " << game::GetSpellName(gLastNormalCastParams.spellId)); gCastData.castingQueuedSpell = true; gCastData.numRetries = gLastNormalCastParams.numRetries; - Spell_C_CastSpellHook(castSpellDetour, gLastNormalCastParams.playerUnit, gLastNormalCastParams.spellId, + Spell_C_CastSpellHook(castSpellDetour, gLastNormalCastParams.casterUnit, gLastNormalCastParams.spellId, gLastNormalCastParams.item, gLastNormalCastParams.guid); gLastCastData.wasQueued = true; } else { @@ -163,7 +161,7 @@ namespace Nampower { } void SaveCastParams(CastSpellParams *params, - uint32_t *playerUnit, + uint32_t *casterUnit, uint32_t spellId, uintptr_t *item, std::uint64_t guid, @@ -172,7 +170,7 @@ namespace Nampower { uint32_t castStartTimeMs, CastType castType, uint32_t numRetries) { - params->playerUnit = playerUnit; + params->casterUnit = casterUnit; params->spellId = spellId; params->item = item; params->guid = guid; @@ -218,18 +216,6 @@ namespace Nampower { } } - void setSelectionTarget(uint64_t target) { - auto dataStore = CDataStore(); - uint32_t opcode = 317; // CMSG_SET_SELECTION - dataStore.Put(opcode); - dataStore.Put(target); - - dataStore.Finalize(); - - auto const clientServicesSend = reinterpret_cast(Offsets::ClientServices_Send); - clientServicesSend(&dataStore); - } - bool Spell_C_CastSpellHook(hadesmem::PatchDetourBase *detour, uint32_t *casterUnit, uint32_t spellId, uintptr_t *item, std::uint64_t guid) { @@ -257,14 +243,14 @@ namespace Nampower { auto const isSpecialSpell = SpellIsAttackTradeskillOrEnchant(spell); auto const currentTargetGuid = game::GetCurrentTargetGuid(); - if(spellIsChanneling) { + if (spellIsChanneling) { auto casterGuid = game::UnitGetGuid(casterUnit); if (casterGuid == game::ClntObjMgrGetActivePlayerGuid()) { // check that locked target guid matches our unit target guid auto unitTargetGuid = game::UnitGetTargetGuid(casterUnit); if (unitTargetGuid != currentTargetGuid) { DEBUG_LOG("Updating selection target to " << currentTargetGuid << " from " << unitTargetGuid); - setSelectionTarget(currentTargetGuid); + SetSelectionTarget(currentTargetGuid); } } } @@ -299,7 +285,8 @@ namespace Nampower { itemId = game::GetItemId((game::CGItem_C *) item); } - DEBUG_LOG("Attempt cast " << spellName << " item " << item << " on guid " << guid << " target " << currentTargetGuid + DEBUG_LOG("Attempt cast " << spellName << " item " << item << " on guid " << guid << " target " + << currentTargetGuid << ", time since last cast " << currentTime - gLastCastData.startTimeMs); // clear cooldown queue if we are casting a spell @@ -522,7 +509,7 @@ namespace Nampower { // otherwise can break cooldown in the client and cause unnecessary errors if (castTime == 0 || spell->Attributes & game::SPELL_ATTR_DISABLED_WHILE_ACTIVE) { // check if spam protection is enabled - if(gUserSettings.spamProtectionEnabled) { + if (gUserSettings.spamProtectionEnabled) { auto castParams = gCastHistory.findNewestWaitingForServerSpellId(spellId); if (castParams && currentTime - castParams->castStartTimeMs < 500) { @@ -643,7 +630,7 @@ namespace Nampower { TriggerSpellQueuedEvent(ON_SWING_QUEUE_POPPED, gLastOnSwingCastParams.spellId); - Spell_C_CastSpellHook(castSpellDetour, gLastOnSwingCastParams.playerUnit, + Spell_C_CastSpellHook(castSpellDetour, gLastOnSwingCastParams.casterUnit, gLastOnSwingCastParams.spellId, gLastOnSwingCastParams.item, gLastOnSwingCastParams.guid); gCastData.onSwingQueued = false; diff --git a/nampower/spellevents.cpp b/nampower/spellevents.cpp index bafb6ad..300dba0 100644 --- a/nampower/spellevents.cpp +++ b/nampower/spellevents.cpp @@ -277,7 +277,6 @@ namespace Nampower { return spellDelayed(opCode, packet); } - int CastResultHandlerHook(hadesmem::PatchDetourBase *detour, uint32_t *opCode, CDataStore *packet) { auto const castResultHandler = detour->GetTrampolineT(); @@ -705,4 +704,13 @@ namespace Nampower { return 1; } + + int CGPlayer_C_OnAttackIconPressedHook(hadesmem::PatchDetourBase *detour, uintptr_t *this_ptr, void *dummy_edx, + uint64_t guid) { + auto const onAttackIconPressed = detour->GetTrampolineT(); + + DEBUG_LOG("Attack icon pressed for guid " << guid); + + return onAttackIconPressed(this_ptr, dummy_edx, 0); + } } \ No newline at end of file diff --git a/nampower/spellevents.hpp b/nampower/spellevents.hpp index 44fed0c..00898b0 100644 --- a/nampower/spellevents.hpp +++ b/nampower/spellevents.hpp @@ -35,4 +35,7 @@ namespace Nampower { CDataStore *packet); int PlaySpellVisualHandlerHook(hadesmem::PatchDetourBase *detour, uint32_t *opCode, CDataStore *packet); + + int CGPlayer_C_OnAttackIconPressedHook(hadesmem::PatchDetourBase *detour, uintptr_t *this_ptr, void *dummy_edx, + uint64_t guid); } \ No newline at end of file diff --git a/nampower/types.h b/nampower/types.h index 14052d3..237b992 100644 --- a/nampower/types.h +++ b/nampower/types.h @@ -71,7 +71,7 @@ struct CastSpellParams { /* Original cast spell function arguments */ uint64_t castId; - uint32_t *playerUnit; + uint32_t *casterUnit; uint32_t spellId; uintptr_t *item; uint64_t guid; From 2f07d5af6e7ccac375729216ef8a31517a7e9a55 Mon Sep 17 00:00:00 2001 From: avitasia Date: Mon, 1 Dec 2025 19:36:45 -0800 Subject: [PATCH 2/6] add more item/spell dbc functions, improve flexibility of spell lua functions --- README.md | 33 ++++ nampower/CMakeLists.txt | 4 + nampower/dbc_fields.cpp | 189 ++++++++++++++++++ nampower/dbc_fields.hpp | 137 +++++++++++++ nampower/game.hpp | 5 +- nampower/helper.cpp | 149 +++++++++++++- nampower/helper.hpp | 5 + nampower/items.cpp | 293 ++++++++++++++++++++++++++++ nampower/items.hpp | 17 ++ nampower/main.cpp | 88 ++++----- nampower/main.hpp | 11 +- nampower/offsets.hpp | 24 +-- nampower/scripts.cpp | 405 +++++++++++++++++++++++++++++++++++---- nampower/scripts.hpp | 34 ++-- nampower/spellevents.cpp | 9 - nampower/spellevents.hpp | 3 - 16 files changed, 1270 insertions(+), 136 deletions(-) create mode 100644 nampower/dbc_fields.cpp create mode 100644 nampower/dbc_fields.hpp create mode 100644 nampower/items.cpp create mode 100644 nampower/items.hpp diff --git a/README.md b/README.md index b9a17f2..d8257b6 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,25 @@ SET NP_TargetingQueueWindowMs "1000" - `NP_NameplateDistance` - The distance in yards to display nameplates. Defaults to whatever was set by the game or vanilla tweaks. +### Existing Lua Changes + +#### Improved flexibility on spellbook Lua functions +These built-in Lua spell APIs now accept any of the following as their first argument: 1) spell slot (original behavior), 2) spell name, or 3) `spellId:number`. + +Name and spellId lookups are cached internally and validated against current spellbook contents before reuse so you don't have to worry about performance implications or issues after respec'ing. + +See examples below for differences in how BOOKTYPE works. + +Functions: `GetSpellTexture`, `GetSpellName`, `GetSpellCooldown`, `GetSpellAutocast`, `ToggleSpellAutocast`, `PickupSpell`, `CastSpell`, `IsCurrentCast`, `IsSpellPassive`. + +Examples: +``` +/run print(GetSpellTexture(1, "spell")) -- booktype required +/run print(GetSpellTexture("spellId:25978")) -- defaults to BOOKTYPE_SPELL +/run print(GetSpellTexture("spellId:6268", "pet")) -- "pet" needed for pet spells +/run print(GetSpellTexture("Fireball")) -- name search +``` + ### Custom Lua Functions #### QueueSpellByName(spellName) @@ -209,6 +228,20 @@ 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_C` record (including localized `displayName` and `description`). Returns 0 if the item cannot be found or loaded. + +#### GetItemStatsField(itemId, fieldName) +Fast lookup for a single field on an item. Returns the requested field value or raises a Lua error if the item or field is not found. + +#### GetSpellRec(spellId) +Returns a Lua table containing all fields for the spell’s `SpellRec` record (including localized `name` and `rank`). Returns 0 if the spell cannot be found. + +#### GetSpellRecField(spellId, fieldName) +Fast lookup for a single field on a spell. Returns the requested field value or raises a Lua error if the spell or field is not found. + +Struct references (see `nampower/game.hpp` for full details): + #### GetNampowerVersion() Returns the current version of Nampower split into major, minor and patch numbers. diff --git a/nampower/CMakeLists.txt b/nampower/CMakeLists.txt index fb468e1..6b3b5ad 100644 --- a/nampower/CMakeLists.txt +++ b/nampower/CMakeLists.txt @@ -24,6 +24,10 @@ set(SOURCE_FILES spellevents.cpp scripts.hpp scripts.cpp + items.hpp + items.cpp + dbc_fields.hpp + dbc_fields.cpp ) add_library(${DLL_NAME} SHARED ${SOURCE_FILES}) diff --git a/nampower/dbc_fields.cpp b/nampower/dbc_fields.cpp new file mode 100644 index 0000000..c25561c --- /dev/null +++ b/nampower/dbc_fields.cpp @@ -0,0 +1,189 @@ +// +// Created by pmacc on 1/8/2025. +// + +#include "dbc_fields.hpp" +#include "offsets.hpp" + +namespace Nampower { + // Hash tables for O(1) field lookups + std::unordered_map itemStatsFieldMap; + std::unordered_map itemStatsArrayFieldMap; + std::unordered_map spellRecFieldMap; + std::unordered_map spellRecArrayFieldMap; + static bool fieldMapsInitialized = false; + + // ItemStats_C field descriptors + const FieldDescriptor itemStatsFields[] = { + {"class", offsetof(game::ItemStats_C, m_class), FieldType::INT32}, + {"subclass", offsetof(game::ItemStats_C, m_subclass), FieldType::INT32}, + {"displayInfoID", offsetof(game::ItemStats_C, m_displayInfoID), FieldType::INT32}, + {"quality", offsetof(game::ItemStats_C, m_quality), FieldType::INT32}, + {"flags", offsetof(game::ItemStats_C, m_flags), FieldType::INT32}, + {"buyPrice", offsetof(game::ItemStats_C, m_buyPrice), FieldType::INT32}, + {"sellPrice", offsetof(game::ItemStats_C, m_sellPrice), FieldType::INT32}, + {"inventoryType", offsetof(game::ItemStats_C, m_inventoryType), FieldType::INT32}, + {"allowableClass", offsetof(game::ItemStats_C, m_allowableClass), FieldType::INT32}, + {"allowableRace", offsetof(game::ItemStats_C, m_allowableRace), FieldType::INT32}, + {"itemLevel", offsetof(game::ItemStats_C, m_itemLevel), FieldType::INT32}, + {"requiredLevel", offsetof(game::ItemStats_C, m_requiredLevel), FieldType::INT32}, + {"requiredSkill", offsetof(game::ItemStats_C, m_requiredSkill), FieldType::INT32}, + {"requiredSkillRank", offsetof(game::ItemStats_C, m_requiredSkillRank), FieldType::INT32}, + {"requiredSpell", offsetof(game::ItemStats_C, m_requiredSpell), FieldType::INT32}, + {"requiredHonorRank", offsetof(game::ItemStats_C, m_requiredHonorRank), FieldType::INT32}, + {"requiredCityRank", offsetof(game::ItemStats_C, m_requiredCityRank), FieldType::INT32}, + {"requiredRep", offsetof(game::ItemStats_C, m_requiredRep), FieldType::INT32}, + {"requiredRepRank", offsetof(game::ItemStats_C, m_requiredRepRank), FieldType::INT32}, + {"maxCount", offsetof(game::ItemStats_C, m_maxCount), FieldType::INT32}, + {"stackable", offsetof(game::ItemStats_C, m_stackable), FieldType::INT32}, + {"containerSlots", offsetof(game::ItemStats_C, m_containerSlots), FieldType::INT32}, + {"delay", offsetof(game::ItemStats_C, m_delay), FieldType::INT32}, + {"ammoType", offsetof(game::ItemStats_C, m_ammoType), FieldType::INT32}, + {"rangedModRange", offsetof(game::ItemStats_C, m_rangedModRange), FieldType::INT32}, + {"bonding", offsetof(game::ItemStats_C, m_bonding), FieldType::INT32}, + {"pageText", offsetof(game::ItemStats_C, m_pageText), FieldType::INT32}, + {"languageID", offsetof(game::ItemStats_C, m_languageID), FieldType::INT32}, + {"pageMaterial", offsetof(game::ItemStats_C, m_pageMaterial), FieldType::INT32}, + {"startQuestID", offsetof(game::ItemStats_C, m_startQuestID), FieldType::INT32}, + {"lockID", offsetof(game::ItemStats_C, m_lockID), FieldType::INT32}, + {"material", offsetof(game::ItemStats_C, m_material), FieldType::INT32}, + {"sheatheType", offsetof(game::ItemStats_C, m_sheatheType), FieldType::INT32}, + {"randomProperty", offsetof(game::ItemStats_C, m_randomProperty), FieldType::INT32}, + {"block", offsetof(game::ItemStats_C, m_block), FieldType::INT32}, + {"itemSet", offsetof(game::ItemStats_C, m_itemSet), FieldType::INT32}, + {"maxDurability", offsetof(game::ItemStats_C, m_maxDurability), FieldType::INT32}, + {"area", offsetof(game::ItemStats_C, m_area), FieldType::INT32}, + {"map", offsetof(game::ItemStats_C, m_map), FieldType::INT32}, + {"duration", offsetof(game::ItemStats_C, m_duration), FieldType::INT32}, + {"bagFamily", offsetof(game::ItemStats_C, m_bagFamily), FieldType::INT32}, + }; + const size_t itemStatsFieldsCount = sizeof(itemStatsFields) / sizeof(itemStatsFields[0]); + + const ArrayFieldDescriptor itemStatsArrayFields[] = { + {"bonusStat", offsetof(game::ItemStats_C, m_bonusStat), FieldType::INT32, 10}, + {"bonusAmount", offsetof(game::ItemStats_C, m_bonusAmount), FieldType::INT32, 10}, + {"minDamage", offsetof(game::ItemStats_C, m_minDamage), FieldType::FLOAT, 5}, + {"maxDamage", offsetof(game::ItemStats_C, m_maxDamage), FieldType::FLOAT, 5}, + {"damageType", offsetof(game::ItemStats_C, m_damageType), FieldType::INT32, 5}, + {"resistances", offsetof(game::ItemStats_C, m_resistances), FieldType::INT32, 7}, + {"spellID", offsetof(game::ItemStats_C, m_spellID), FieldType::INT32, 5}, + {"spellTrigger", offsetof(game::ItemStats_C, m_spellTrigger), FieldType::INT32, 5}, + {"spellCharges", offsetof(game::ItemStats_C, m_spellCharges), FieldType::INT32, 5}, + {"spellCooldown", offsetof(game::ItemStats_C, m_spellCooldown), FieldType::INT32, 5}, + {"spellCategory", offsetof(game::ItemStats_C, m_spellCategory), FieldType::INT32, 5}, + {"spellCategoryCooldown", offsetof(game::ItemStats_C, m_spellCategoryCooldown), FieldType::INT32, 5}, + }; + const size_t itemStatsArrayFieldsCount = sizeof(itemStatsArrayFields) / sizeof(itemStatsArrayFields[0]); + + // SpellRec field descriptors + const FieldDescriptor spellRecFields[] = { + {"id", offsetof(game::SpellRec, Id), FieldType::UINT32}, + {"school", offsetof(game::SpellRec, School), FieldType::UINT32}, + {"category", offsetof(game::SpellRec, Category), FieldType::UINT32}, + {"castUI", offsetof(game::SpellRec, castUI), FieldType::UINT32}, + {"dispel", offsetof(game::SpellRec, Dispel), FieldType::UINT32}, + {"mechanic", offsetof(game::SpellRec, Mechanic), FieldType::UINT32}, + {"attributes", offsetof(game::SpellRec, Attributes), FieldType::UINT32}, + {"attributesEx", offsetof(game::SpellRec, AttributesEx), FieldType::UINT32}, + {"attributesEx2", offsetof(game::SpellRec, AttributesEx2), FieldType::UINT32}, + {"attributesEx3", offsetof(game::SpellRec, AttributesEx3), FieldType::UINT32}, + {"attributesEx4", offsetof(game::SpellRec, AttributesEx4), FieldType::UINT32}, + {"stances", offsetof(game::SpellRec, Stances), FieldType::UINT32}, + {"stancesNot", offsetof(game::SpellRec, StancesNot), FieldType::UINT32}, + {"targets", offsetof(game::SpellRec, Targets), FieldType::UINT32}, + {"targetCreatureType", offsetof(game::SpellRec, TargetCreatureType), FieldType::UINT32}, + {"requiresSpellFocus", offsetof(game::SpellRec, RequiresSpellFocus), FieldType::UINT32}, + {"casterAuraState", offsetof(game::SpellRec, CasterAuraState), FieldType::UINT32}, + {"targetAuraState", offsetof(game::SpellRec, TargetAuraState), FieldType::UINT32}, + {"castingTimeIndex", offsetof(game::SpellRec, CastingTimeIndex), FieldType::UINT32}, + {"recoveryTime", offsetof(game::SpellRec, RecoveryTime), FieldType::UINT32}, + {"categoryRecoveryTime", offsetof(game::SpellRec, CategoryRecoveryTime), FieldType::UINT32}, + {"interruptFlags", offsetof(game::SpellRec, InterruptFlags), FieldType::UINT32}, + {"auraInterruptFlags", offsetof(game::SpellRec, AuraInterruptFlags), FieldType::UINT32}, + {"channelInterruptFlags", offsetof(game::SpellRec, ChannelInterruptFlags), FieldType::UINT32}, + {"procFlags", offsetof(game::SpellRec, procFlags), FieldType::UINT32}, + {"procChance", offsetof(game::SpellRec, procChance), FieldType::UINT32}, + {"procCharges", offsetof(game::SpellRec, procCharges), FieldType::UINT32}, + {"maxLevel", offsetof(game::SpellRec, maxLevel), FieldType::UINT32}, + {"baseLevel", offsetof(game::SpellRec, baseLevel), FieldType::UINT32}, + {"spellLevel", offsetof(game::SpellRec, spellLevel), FieldType::UINT32}, + {"durationIndex", offsetof(game::SpellRec, DurationIndex), FieldType::UINT32}, + {"powerType", offsetof(game::SpellRec, powerType), FieldType::UINT32}, + {"manaCost", offsetof(game::SpellRec, manaCost), FieldType::UINT32}, + {"manaCostPerlevel", offsetof(game::SpellRec, manaCostPerlevel), FieldType::UINT32}, + {"manaPerSecond", offsetof(game::SpellRec, manaPerSecond), FieldType::UINT32}, + {"manaPerSecondPerLevel", offsetof(game::SpellRec, manaPerSecondPerLevel), FieldType::UINT32}, + {"rangeIndex", offsetof(game::SpellRec, rangeIndex), FieldType::UINT32}, + {"speed", offsetof(game::SpellRec, speed), FieldType::FLOAT}, + {"modalNextSpell", offsetof(game::SpellRec, modalNextSpell), FieldType::UINT32}, + {"stackAmount", offsetof(game::SpellRec, StackAmount), FieldType::UINT32}, + {"equippedItemClass", offsetof(game::SpellRec, EquippedItemClass), FieldType::INT32}, + {"equippedItemSubClassMask", offsetof(game::SpellRec, EquippedItemSubClassMask), FieldType::INT32}, + {"equippedItemInventoryTypeMask", offsetof(game::SpellRec, EquippedItemInventoryTypeMask), FieldType::INT32}, + {"spellVisual", offsetof(game::SpellRec, SpellVisual), FieldType::UINT32}, + {"spellVisual2", offsetof(game::SpellRec, SpellVisual2), FieldType::UINT32}, + {"spellIconID", offsetof(game::SpellRec, SpellIconID), FieldType::UINT32}, + {"activeIconID", offsetof(game::SpellRec, activeIconID), FieldType::UINT32}, + {"spellPriority", offsetof(game::SpellRec, spellPriority), FieldType::UINT32}, + {"manaCostPercentage", offsetof(game::SpellRec, ManaCostPercentage), FieldType::UINT32}, + {"startRecoveryCategory", offsetof(game::SpellRec, StartRecoveryCategory), FieldType::UINT32}, + {"startRecoveryTime", offsetof(game::SpellRec, StartRecoveryTime), FieldType::UINT32}, + {"maxTargetLevel", offsetof(game::SpellRec, MaxTargetLevel), FieldType::UINT32}, + {"spellFamilyName", offsetof(game::SpellRec, SpellFamilyName), FieldType::UINT32}, + {"spellFamilyFlags", offsetof(game::SpellRec, SpellFamilyFlags), FieldType::UINT64}, + {"maxAffectedTargets", offsetof(game::SpellRec, MaxAffectedTargets), FieldType::UINT32}, + {"dmgClass", offsetof(game::SpellRec, DmgClass), FieldType::UINT32}, + {"preventionType", offsetof(game::SpellRec, PreventionType), FieldType::UINT32}, + {"stanceBarOrder", offsetof(game::SpellRec, StanceBarOrder), FieldType::UINT32}, + {"minFactionId", offsetof(game::SpellRec, MinFactionId), FieldType::UINT32}, + {"minReputation", offsetof(game::SpellRec, MinReputation), FieldType::UINT32}, + {"requiredAuraVision", offsetof(game::SpellRec, RequiredAuraVision), FieldType::UINT32}, + }; + const size_t spellRecFieldsCount = sizeof(spellRecFields) / sizeof(spellRecFields[0]); + + const ArrayFieldDescriptor spellRecArrayFields[] = { + {"totem", offsetof(game::SpellRec, Totem), FieldType::UINT32, 2}, + {"reagent", offsetof(game::SpellRec, Reagent), FieldType::INT32, 8}, + {"reagentCount", offsetof(game::SpellRec, ReagentCount), FieldType::UINT32, 8}, + {"effect", offsetof(game::SpellRec, Effect), FieldType::UINT32, 3}, + {"effectDieSides", offsetof(game::SpellRec, EffectDieSides), FieldType::INT32, 3}, + {"effectBaseDice", offsetof(game::SpellRec, EffectBaseDice), FieldType::UINT32, 3}, + {"effectDicePerLevel", offsetof(game::SpellRec, EffectDicePerLevel), FieldType::FLOAT, 3}, + {"effectRealPointsPerLevel", offsetof(game::SpellRec, EffectRealPointsPerLevel), FieldType::FLOAT, 3}, + {"effectBasePoints", offsetof(game::SpellRec, EffectBasePoints), FieldType::INT32, 3}, + {"effectMechanic", offsetof(game::SpellRec, EffectMechanic), FieldType::UINT32, 3}, + {"effectImplicitTargetA", offsetof(game::SpellRec, EffectImplicitTargetA), FieldType::UINT32, 3}, + {"effectImplicitTargetB", offsetof(game::SpellRec, EffectImplicitTargetB), FieldType::UINT32, 3}, + {"effectRadiusIndex", offsetof(game::SpellRec, EffectRadiusIndex), FieldType::UINT32, 3}, + {"effectApplyAuraName", offsetof(game::SpellRec, EffectApplyAuraName), FieldType::UINT32, 3}, + {"effectAmplitude", offsetof(game::SpellRec, EffectAmplitude), FieldType::UINT32, 3}, + {"effectMultipleValue", offsetof(game::SpellRec, EffectMultipleValue), FieldType::FLOAT, 3}, + {"effectChainTarget", offsetof(game::SpellRec, EffectChainTarget), FieldType::UINT32, 3}, + {"effectItemType", offsetof(game::SpellRec, EffectItemType), FieldType::UINT32, 3}, + {"effectMiscValue", offsetof(game::SpellRec, EffectMiscValue), FieldType::INT32, 3}, + {"effectTriggerSpell", offsetof(game::SpellRec, EffectTriggerSpell), FieldType::UINT32, 3}, + {"effectPointsPerComboPoint", offsetof(game::SpellRec, EffectPointsPerComboPoint), FieldType::FLOAT, 3}, + {"dmgMultiplier", offsetof(game::SpellRec, DmgMultiplier), FieldType::FLOAT, 3}, + }; + const size_t spellRecArrayFieldsCount = sizeof(spellRecArrayFields) / sizeof(spellRecArrayFields[0]); + + void InitializeFieldMaps() { + if (fieldMapsInitialized) return; + + // Build hash maps for O(1) lookups + for (size_t i = 0; i < itemStatsFieldsCount; ++i) { + itemStatsFieldMap[itemStatsFields[i].name] = i; + } + for (size_t i = 0; i < itemStatsArrayFieldsCount; ++i) { + itemStatsArrayFieldMap[itemStatsArrayFields[i].name] = i; + } + for (size_t i = 0; i < spellRecFieldsCount; ++i) { + spellRecFieldMap[spellRecFields[i].name] = i; + } + for (size_t i = 0; i < spellRecArrayFieldsCount; ++i) { + spellRecArrayFieldMap[spellRecArrayFields[i].name] = i; + } + + fieldMapsInitialized = true; + } +} diff --git a/nampower/dbc_fields.hpp b/nampower/dbc_fields.hpp new file mode 100644 index 0000000..675da27 --- /dev/null +++ b/nampower/dbc_fields.hpp @@ -0,0 +1,137 @@ +// +// Created by pmacc on 1/8/2025. +// + +#pragma once + +#include "main.hpp" +#include +#include +#include + +namespace Nampower { + // Field type enumeration + enum class FieldType { + INT32, + UINT32, + FLOAT, + STRING, + UINT64 + }; + + // Field descriptor for simple fields + struct FieldDescriptor { + const char* name; + size_t offset; + FieldType type; + }; + + // Field descriptor for array fields + struct ArrayFieldDescriptor { + const char* name; + size_t offset; + FieldType type; + size_t count; + }; + + // ItemStats_C field descriptors + extern const FieldDescriptor itemStatsFields[]; + extern const size_t itemStatsFieldsCount; + extern const ArrayFieldDescriptor itemStatsArrayFields[]; + extern const size_t itemStatsArrayFieldsCount; + + // SpellRec field descriptors + extern const FieldDescriptor spellRecFields[]; + extern const size_t spellRecFieldsCount; + extern const ArrayFieldDescriptor spellRecArrayFields[]; + extern const size_t spellRecArrayFieldsCount; + + // Hash tables for O(1) field lookups + extern std::unordered_map itemStatsFieldMap; + extern std::unordered_map itemStatsArrayFieldMap; + extern std::unordered_map spellRecFieldMap; + extern std::unordered_map spellRecArrayFieldMap; + + // Initialize field maps (call once before using field lookups) + void InitializeFieldMaps(); + + // Forward declarations for Lua functions (defined in offsets.hpp/scripts.cpp) + using lua_pushnumberT = void (__fastcall *)(uintptr_t *, double); + using lua_pushstringT = void (__fastcall *)(uintptr_t *, char *); + using lua_newtableT = void (__fastcall *)(uintptr_t *); + using lua_settableT = void (__fastcall *)(uintptr_t *, int); + + extern lua_pushnumberT lua_pushnumber; + extern lua_pushstringT lua_pushstring; + extern lua_newtableT lua_newtable; + extern lua_settableT lua_settable; + + // Helper template functions to push fields to Lua (inline for performance) + template + inline void PushFieldsToLua(uintptr_t* luaState, const T* obj, const FieldDescriptor* fields, size_t fieldCount) { + for (size_t i = 0; i < fieldCount; ++i) { + const auto& field = fields[i]; + lua_pushstring(luaState, const_cast(field.name)); + + const char* fieldPtr = reinterpret_cast(obj) + field.offset; + + switch (field.type) { + case FieldType::INT32: + lua_pushnumber(luaState, *reinterpret_cast(fieldPtr)); + break; + case FieldType::UINT32: + lua_pushnumber(luaState, *reinterpret_cast(fieldPtr)); + break; + case FieldType::FLOAT: + lua_pushnumber(luaState, *reinterpret_cast(fieldPtr)); + break; + case FieldType::UINT64: + lua_pushnumber(luaState, static_cast(*reinterpret_cast(fieldPtr))); + break; + case FieldType::STRING: { + const char* str = *reinterpret_cast(fieldPtr); + lua_pushstring(luaState, str ? const_cast(str) : const_cast("")); + break; + } + } + lua_settable(luaState, -3); + } + } + + template + inline void PushArrayFieldsToLua(uintptr_t* luaState, const T* obj, const ArrayFieldDescriptor* fields, size_t fieldCount) { + for (size_t i = 0; i < fieldCount; ++i) { + const auto& field = fields[i]; + lua_pushstring(luaState, const_cast(field.name)); + lua_newtable(luaState); + + const char* fieldPtr = reinterpret_cast(obj) + field.offset; + + for (size_t j = 0; j < field.count; ++j) { + lua_pushnumber(luaState, j + 1); // Lua is 1-indexed + + switch (field.type) { + case FieldType::INT32: + lua_pushnumber(luaState, reinterpret_cast(fieldPtr)[j]); + break; + case FieldType::UINT32: + lua_pushnumber(luaState, reinterpret_cast(fieldPtr)[j]); + break; + case FieldType::FLOAT: + lua_pushnumber(luaState, reinterpret_cast(fieldPtr)[j]); + break; + case FieldType::UINT64: + lua_pushnumber(luaState, static_cast(reinterpret_cast(fieldPtr)[j])); + break; + case FieldType::STRING: { + const char* str = reinterpret_cast(fieldPtr)[j]; + lua_pushstring(luaState, str ? const_cast(str) : const_cast("")); + break; + } + } + lua_settable(luaState, -3); + } + lua_settable(luaState, -3); + } + } +} diff --git a/nampower/game.hpp b/nampower/game.hpp index a46500c..42a5c08 100644 --- a/nampower/game.hpp +++ b/nampower/game.hpp @@ -30,6 +30,7 @@ #pragma once #include +#include namespace game { #pragma pack(push, 1) @@ -209,7 +210,7 @@ namespace game { int m_containerSlots; int m_bonusStat[10]; int m_bonusAmount[10]; - int m_minDamage[5]; + float m_minDamage[5]; float m_maxDamage[5]; int m_damageType[5]; int m_resistances[7]; @@ -229,7 +230,7 @@ namespace game { int m_pageMaterial; int m_startQuestID; int m_lockID; - char *m_material; + int m_material; int m_sheatheType; int m_randomProperty; int m_block; diff --git a/nampower/helper.cpp b/nampower/helper.cpp index 12dd307..973bab9 100644 --- a/nampower/helper.cpp +++ b/nampower/helper.cpp @@ -5,8 +5,155 @@ #include "helper.hpp" #include "offsets.hpp" #include "main.hpp" +#include "dbc_fields.hpp" +#include +#include +#include +#include +#include namespace Nampower { + + uint32_t GetSpellSlotAndTypeForName(const char *spellName, uint32_t *spellType) { + struct CachedEntry { + uint32_t slot; + uint32_t type; + std::string cachedNameLower; + }; + static std::unordered_map spellSlotCache; + + auto const normalize = [](const char *name) -> std::string { + std::string out = name ? name : ""; + std::transform(out.begin(), out.end(), out.begin(), [](unsigned char c) { return char(std::tolower(c)); }); + return out; + }; + + const std::string key = normalize(spellName); + + auto const spellNameMatchesAtSlot = [&normalize](const char *requestedLower, uint32_t slot, uint32_t type) -> bool { + if (!requestedLower) { + return false; + } + + uint32_t spellId = 0; + if (type == 0) { + spellId = *reinterpret_cast(uint32_t(Offsets::CGSpellBook_mKnownSpells) + slot * 4); + } else if (type == 1) { + spellId = *reinterpret_cast(uint32_t(Offsets::CGSpellBook_mKnownPetSpells) + slot * 4); + } else { + return false; + } + + if (spellId == 0) { + return false; + } + + auto const spell = game::GetSpellInfo(spellId); + if (!spell) { + return false; + } + + auto const language = *reinterpret_cast(Offsets::Language); + auto const actualName = reinterpret_cast(spell->SpellName[language]); + if (!actualName) { + return false; + } + + auto const actualLower = normalize(actualName); + return _stricmp(actualLower.c_str(), requestedLower) == 0; + }; + + if (!key.empty()) { + auto cacheIt = spellSlotCache.find(key); + if (cacheIt != spellSlotCache.end()) { + auto const &entry = cacheIt->second; + if (entry.slot < 1024 && spellNameMatchesAtSlot(key.c_str(), entry.slot, entry.type)) { + if (spellType) { + *spellType = entry.type; + } + return entry.slot; + } + } + } + + auto const getSpellSlotAndType = reinterpret_cast(Offsets::GetSpellSlotAndType); + auto const slot = getSpellSlotAndType(spellName, spellType); + + if (slot < 1024 && spellType && !key.empty()) { + spellSlotCache[key] = {slot, *spellType, key}; + } + + return slot; + } + + uint32_t GetSpellIdFromSpellName(const char *spellName) { + uint32_t bookType; + uint32_t spellSlot = GetSpellSlotAndTypeForName(spellName, &bookType); + + uint32_t spellId = 0; + if (spellSlot < 1024) { + if (bookType == 0) { + spellId = *reinterpret_cast(uint32_t(Offsets::CGSpellBook_mKnownSpells) + + spellSlot * 4); + } else { + spellId = *reinterpret_cast(uint32_t(Offsets::CGSpellBook_mKnownPetSpells) + + spellSlot * 4); + } + } + + return spellId; + } + + uint32_t ConvertSpellIdToSpellSlot(uint32_t spellId, uint32_t bookType) { + static std::unordered_map spellIdToSlotCache; + auto const cacheKey = (uint64_t(spellId) << 1) | (bookType & 0x1); + + auto const slotHasSpellId = [bookType](uint32_t slot, uint32_t expectedId) -> bool { + if (slot >= 1024) { + return false; + } + uint32_t slotSpellId = 0; + if (bookType == 0) { + slotSpellId = *reinterpret_cast(uint32_t(Offsets::CGSpellBook_mKnownSpells) + slot * 4); + } else { + slotSpellId = *reinterpret_cast(uint32_t(Offsets::CGSpellBook_mKnownPetSpells) + slot * 4); + } + return slotSpellId == expectedId; + }; + + auto cacheIt = spellIdToSlotCache.find(cacheKey); + if (cacheIt != spellIdToSlotCache.end()) { + if (slotHasSpellId(cacheIt->second, spellId)) { + return cacheIt->second; + } + } + + // Search through player spellbook + if (bookType == 0) { + for (uint32_t spellSlot = 0; spellSlot < 1024; spellSlot++) { + uint32_t slotSpellId = *reinterpret_cast(uint32_t(Offsets::CGSpellBook_mKnownSpells) + + spellSlot * 4); + if (slotSpellId == spellId) { + spellIdToSlotCache[cacheKey] = spellSlot; + return spellSlot; + } + } + } else { + // Search through pet spellbook + for (uint32_t spellSlot = 0; spellSlot < 1024; spellSlot++) { + uint32_t slotSpellId = *reinterpret_cast(uint32_t(Offsets::CGSpellBook_mKnownPetSpells) + + spellSlot * 4); + if (slotSpellId == spellId) { + spellIdToSlotCache[cacheKey] = spellSlot; + return spellSlot; + } + } + } + + return 0; + } + + bool SpellIsOnGcd(const game::SpellRec *spell) { if (spell->Id == 51714) { // power overwhelming gcd removed but client not updated @@ -122,4 +269,4 @@ namespace Nampower { void SetNameplateDistance(float distance) { *reinterpret_cast(Offsets::NameplateDistance) = distance * distance; } -} \ No newline at end of file +} diff --git a/nampower/helper.hpp b/nampower/helper.hpp index 91eee88..2dd6268 100644 --- a/nampower/helper.hpp +++ b/nampower/helper.hpp @@ -7,6 +7,11 @@ #include "game.hpp" namespace Nampower { + uint32_t GetSpellSlotAndTypeForName(const char *spellName, uint32_t *spellType); + + uint32_t GetSpellIdFromSpellName(const char *spellName); + + uint32_t ConvertSpellIdToSpellSlot(uint32_t spellId, uint32_t bookType); bool SpellIsOnGcd(const game::SpellRec *spell); diff --git a/nampower/items.cpp b/nampower/items.cpp new file mode 100644 index 0000000..532ac02 --- /dev/null +++ b/nampower/items.cpp @@ -0,0 +1,293 @@ +// +// Created by pmacc on 9/21/2024. +// + +#include "items.hpp" +#include "offsets.hpp" +#include "logging.hpp" + +#include +#include +#include +#include + +namespace Nampower { + // Global dictionary to store itemId -> ItemStats_C mappings + static std::unordered_map itemStatsCache; + + auto const getRow = reinterpret_cast(Offsets::DBCache_ItemCacheDBGetRow); + auto const itemCache = reinterpret_cast(Offsets::ItemDBCache); + + // Callback function for DBCache + void __stdcall ItemStatsCallback(uintptr_t *tooltip, char param_2) { + if (tooltip != nullptr) { + auto itemId = *reinterpret_cast(tooltip + 0xE6); + + int ptr; + auto itemStats = getRow(itemCache, nullptr, itemId, &ptr, &ItemStatsCallback, nullptr, 0); + if (itemStats) { + DEBUG_LOG("Queried itemId " << itemId << " -> " << itemStats); + // Store in cache + itemStatsCache[itemId] = reinterpret_cast(itemStats); + } + } + } + + void LoadItem(uint32_t itemId) { + // Check if already in cache + auto it = itemStatsCache.find(itemId); + if (it != itemStatsCache.end()) { + return; + } + + if (!itemCache) { + return; + } + + int ptr; + auto itemStats = getRow(itemCache, nullptr, itemId, &ptr, &ItemStatsCallback, nullptr, 0); + if (itemStats) { + // Store in cache + itemStatsCache[itemId] = reinterpret_cast(itemStats); + } else { + // wait for .005 sec + Sleep(5); + } + } + + game::ItemStats_C* GetItemStats(uint32_t itemId) { + auto it = itemStatsCache.find(itemId); + if (it != itemStatsCache.end()) { + return it->second; + } + return nullptr; + } + + std::string escapeJsonString(const char *str) { + if (!str) return "null"; + + std::string result = "\""; + for (const char *p = str; *p; ++p) { + switch (*p) { + case '"': + result += "\\\""; + break; + case '\\': + result += "\\\\"; + break; + case '\b': + result += "\\b"; + break; + case '\f': + result += "\\f"; + break; + case '\n': + result += "\\n"; + break; + case '\r': + result += "\\r"; + break; + case '\t': + result += "\\t"; + break; + default: + if (static_cast(*p) < 0x20) { + char buf[8]; + snprintf(buf, sizeof(buf), "\\u%04x", static_cast(*p)); + result += buf; + } else { + result += *p; + } + } + } + result += "\""; + return result; + } + + // Register a function if you want to use this + // Don't want everyone scraping all items. + void ExportAllItems() { + DEBUG_LOG("Starting getAllItems - fetching items 1 to 100000"); + + // Loop through all item IDs + for (uint32_t itemId = 1; itemId <= 100000; ++itemId) { + LoadItem(itemId); + + // Log progress every 1000 items + if (itemId % 1000 == 0) { + DEBUG_LOG("Progress: " << itemId << " / 100000 items processed"); + } + } + + DEBUG_LOG("Finished fetching items, found " << itemStatsCache.size() << " valid items"); + DEBUG_LOG("Writing items to items.json"); + + // Write all cached items to JSON file (overwrite mode) + try { + std::ofstream jsonFile("items.json", std::ios::trunc); + if (!jsonFile.is_open()) { + DEBUG_LOG("Failed to open items.json for writing"); + return; + } + + jsonFile << "[\n"; + + bool first = true; + for (const auto &pair: itemStatsCache) { + if (!first) { + jsonFile << ",\n"; + } + first = false; + + uint32_t itemId = pair.first; + game::ItemStats_C *item = pair.second; + + if (!item) continue; + + std::ostringstream json; + json << " {\n"; + json << " \"id\": " << itemId << ",\n"; + json << " \"class\": " << item->m_class << ",\n"; + json << " \"subclass\": " << item->m_subclass << ",\n"; + json << " \"displayName\": " << escapeJsonString(item->m_displayName[0]) << ",\n"; + json << " \"displayInfoID\": " << item->m_displayInfoID << ",\n"; + json << " \"quality\": " << item->m_quality << ",\n"; + json << " \"flags\": " << static_cast(item->m_flags) << ",\n"; + json << " \"buyPrice\": " << item->m_buyPrice << ",\n"; + json << " \"sellPrice\": " << item->m_sellPrice << ",\n"; + json << " \"inventoryType\": " << item->m_inventoryType << ",\n"; + json << " \"allowableClass\": " << item->m_allowableClass << ",\n"; + json << " \"allowableRace\": " << item->m_allowableRace << ",\n"; + json << " \"itemLevel\": " << item->m_itemLevel << ",\n"; + json << " \"requiredLevel\": " << item->m_requiredLevel << ",\n"; + json << " \"requiredSkill\": " << item->m_requiredSkill << ",\n"; + json << " \"requiredSkillRank\": " << item->m_requiredSkillRank << ",\n"; + json << " \"requiredSpell\": " << item->m_requiredSpell << ",\n"; + json << " \"requiredHonorRank\": " << item->m_requiredHonorRank << ",\n"; + json << " \"requiredCityRank\": " << item->m_requiredCityRank << ",\n"; + json << " \"requiredRep\": " << item->m_requiredRep << ",\n"; + json << " \"requiredRepRank\": " << item->m_requiredRepRank << ",\n"; + json << " \"maxCount\": " << item->m_maxCount << ",\n"; + json << " \"stackable\": " << item->m_stackable << ",\n"; + json << " \"containerSlots\": " << item->m_containerSlots << ",\n"; + + // Arrays + json << " \"bonusStat\": ["; + for (int i = 0; i < 10; ++i) { + json << item->m_bonusStat[i]; + if (i < 9) json << ", "; + } + json << "],\n"; + + json << " \"bonusAmount\": ["; + for (int i = 0; i < 10; ++i) { + json << item->m_bonusAmount[i]; + if (i < 9) json << ", "; + } + json << "],\n"; + + json << " \"minDamage\": ["; + for (int i = 0; i < 5; ++i) { + json << item->m_minDamage[i]; + if (i < 4) json << ", "; + } + json << "],\n"; + + json << " \"maxDamage\": ["; + for (int i = 0; i < 5; ++i) { + json << item->m_maxDamage[i]; + if (i < 4) json << ", "; + } + json << "],\n"; + + json << " \"damageType\": ["; + for (int i = 0; i < 5; ++i) { + json << item->m_damageType[i]; + if (i < 4) json << ", "; + } + json << "],\n"; + + json << " \"resistances\": ["; + for (int i = 0; i < 7; ++i) { + json << item->m_resistances[i]; + if (i < 6) json << ", "; + } + json << "],\n"; + + json << " \"delay\": " << item->m_delay << ",\n"; + json << " \"ammoType\": " << item->m_ammoType << ",\n"; + json << " \"rangedModRange\": " << item->m_rangedModRange << ",\n"; + + json << " \"spellID\": ["; + for (int i = 0; i < 5; ++i) { + json << item->m_spellID[i]; + if (i < 4) json << ", "; + } + json << "],\n"; + + json << " \"spellTrigger\": ["; + for (int i = 0; i < 5; ++i) { + json << item->m_spellTrigger[i]; + if (i < 4) json << ", "; + } + json << "],\n"; + + json << " \"spellCharges\": ["; + for (int i = 0; i < 5; ++i) { + json << item->m_spellCharges[i]; + if (i < 4) json << ", "; + } + json << "],\n"; + + json << " \"spellCooldown\": ["; + for (int i = 0; i < 5; ++i) { + json << item->m_spellCooldown[i]; + if (i < 4) json << ", "; + } + json << "],\n"; + + json << " \"spellCategory\": ["; + for (int i = 0; i < 5; ++i) { + json << item->m_spellCategory[i]; + if (i < 4) json << ", "; + } + json << "],\n"; + + json << " \"spellCategoryCooldown\": ["; + for (int i = 0; i < 5; ++i) { + json << item->m_spellCategoryCooldown[i]; + if (i < 4) json << ", "; + } + json << "],\n"; + + json << " \"bonding\": " << item->m_bonding << ",\n"; + json << " \"description\": " << escapeJsonString(item->m_description) << ",\n"; + json << " \"pageText\": " << item->m_pageText << ",\n"; + json << " \"languageID\": " << item->m_languageID << ",\n"; + json << " \"pageMaterial\": " << item->m_pageMaterial << ",\n"; + json << " \"startQuestID\": " << item->m_startQuestID << ",\n"; + json << " \"lockID\": " << item->m_lockID << ",\n"; + json << " \"material\": " << item->m_material << ",\n"; + json << " \"sheatheType\": " << item->m_sheatheType << ",\n"; + json << " \"randomProperty\": " << item->m_randomProperty << ",\n"; + json << " \"block\": " << item->m_block << ",\n"; + json << " \"itemSet\": " << item->m_itemSet << ",\n"; + json << " \"maxDurability\": " << item->m_maxDurability << ",\n"; + json << " \"area\": " << item->m_area << ",\n"; + json << " \"map\": " << item->m_map << ",\n"; + json << " \"duration\": " << item->m_duration << ",\n"; + json << " \"bagFamily\": " << item->m_bagFamily << "\n"; + json << " }"; + + jsonFile << json.str(); + } + + jsonFile << "\n]\n"; + jsonFile.close(); + + DEBUG_LOG("Successfully wrote " << itemStatsCache.size() << " items to items.json"); + } catch (const std::exception &e) { + DEBUG_LOG("Exception writing to items.json: " << e.what()); + } + } +} diff --git a/nampower/items.hpp b/nampower/items.hpp new file mode 100644 index 0000000..bfb1432 --- /dev/null +++ b/nampower/items.hpp @@ -0,0 +1,17 @@ +// +// Created by pmacc on 9/21/2024. +// + +#pragma once + +#include "main.hpp" + +namespace Nampower { + using TooltipItemStatsCallbackT = void (__stdcall *)(uintptr_t *tooltip, char param_2); + + using DBCache_ItemCacheDBGetRowT = uint32_t * (__fastcall *)(void *this_ptr, void *dummy_edx, uint32_t itemId, int *param_2, TooltipItemStatsCallbackT callback, int *param_4, char param_5); + + void ExportAllItems(); + void LoadItem(uint32_t itemId); + game::ItemStats_C* GetItemStats(uint32_t itemId); +} diff --git a/nampower/main.cpp b/nampower/main.cpp index 21acb81..732e976 100644 --- a/nampower/main.cpp +++ b/nampower/main.cpp @@ -36,6 +36,7 @@ #include "scripts.hpp" #include "spellchannel.hpp" #include "helper.hpp" +#include "items.hpp" #include #include @@ -99,23 +100,10 @@ namespace Nampower { std::unique_ptr> gSpellGoDetour; std::unique_ptr> gSpellTargetUnitDetour; std::unique_ptr> gSpellStopCastingDetour; - std::unique_ptr> gCastSpellByNameNoQueueDetour; - std::unique_ptr> gQueueSpellByNameDetour; - std::unique_ptr> qQueueScriptDetour; - std::unique_ptr> gIsSpellInRangeDetour; - std::unique_ptr> gIsSpellUsableDetour; - std::unique_ptr> gGetCurrentCastingInfoDetour; - std::unique_ptr> gGetSpellIdForNameDetour; - std::unique_ptr> gGetSpellNameAndRankForIdDetour; - std::unique_ptr> gGetSpellSlotAndTypeForNameDetour; - std::unique_ptr> gChannelStopCastingNextTickDetour; std::unique_ptr> gOnSpriteRightClickDetour; std::unique_ptr> gSpell_C_HandleSpriteClickDetour; std::unique_ptr> gSpell_C_TargetSpellDetour; - std::unique_ptr> gGetNampowerVersionDetour; - std::unique_ptr> gGetItemLevelDetour; - std::unique_ptr> gSpellCooldownDetour; std::unique_ptr> gSpellDelayedDetour; std::unique_ptr> gCastResultHandlerDetour; @@ -130,6 +118,10 @@ namespace Nampower { std::unique_ptr> gCGPlayer_C_OnAttackIconPressedDetour; + std::unique_ptr> gInvalidFunctionPtrCheckDetour; + + std::unique_ptr> gGetSpellSlotFromLuaDetour; + uint32_t GetTime() { return static_cast(std::chrono::duration_cast( std::chrono::high_resolution_clock::now().time_since_epoch()).count()) - gStartTime; @@ -543,6 +535,10 @@ namespace Nampower { return iSceneEnd(ptr); } + void InvalidFunctionPtrCheckHook(hadesmem::PatchDetourBase *detour, uint32_t param_1) { + // remove original call to allow registering lua functions at any address range + } + void updateFromCvar(const char *cvar, const char *value) { if (strcmp(cvar, "NP_QueueCastTimeSpells") == 0) { gUserSettings.queueCastTimeSpells = atoi(value) != 0; @@ -1116,29 +1112,13 @@ namespace Nampower { &Script_SpellStopCastingHook); gSpell_C_TargetSpellDetour = createHook(process, Offsets::Spell_C_TargetSpell, &Spell_C_TargetSpellHook); - gCastSpellByNameNoQueueDetour = createHook(process, Offsets::Script_CastSpellByNameNoQueue, - Script_CastSpellByNameNoQueue); - gQueueSpellByNameDetour = createHook(process, Offsets::Script_QueueSpellByName, - Script_QueueSpellByName); - qQueueScriptDetour = createHook(process, Offsets::Script_QueueScript, Script_QueueScript); - gIsSpellInRangeDetour = createHook(process, Offsets::Script_IsSpellInRange, Script_IsSpellInRange); - gIsSpellUsableDetour = createHook(process, Offsets::Script_IsSpellUsable, Script_IsSpellUsable); - gGetCurrentCastingInfoDetour = createHook(process, Offsets::Script_GetCurrentCastingInfo, - Script_GetCurrentCastingInfo); - gGetSpellIdForNameDetour = createHook(process, Offsets::Script_GetSpellIdForName, - Script_GetSpellIdForName); - gGetSpellNameAndRankForIdDetour = createHook(process, Offsets::Script_GetSpellNameAndRankForId, - Script_GetSpellNameAndRankForId); - gGetSpellSlotAndTypeForNameDetour = createHook(process, Offsets::Script_GetSpellSlotTypeIdForName, - Script_GetSpellSlotTypeIdForName); gOnSpriteRightClickDetour = createHook(process, Offsets::OnSpriteRightClick, OnSpriteRightClickHook); - gChannelStopCastingNextTickDetour = createHook(process, Offsets::Script_ChannelStopCastingNextTick, - Script_ChannelStopCastingNextTick); - gGetNampowerVersionDetour = createHook(process, Offsets::Script_GetNampowerVersion, - Script_GetNampowerVersion); - gGetItemLevelDetour = createHook(process, Offsets::Script_GetItemLevel, Script_GetItemLevel); gIEndSceneDetour = createHook(process, Offsets::ISceneEndPtr, &ISceneEndHook); + gInvalidFunctionPtrCheckDetour = createHook(process, Offsets::InvalidFunctionPtrCheck, + &InvalidFunctionPtrCheckHook); + gGetSpellSlotFromLuaDetour = createHook(process, Offsets::GetSpellSlotFromLua, + &GetSpellSlotFromLuaHook); // gCGPlayer_C_OnAttackIconPressedDetour = createHook(process, // Offsets::CGPlayer_C_OnAttackIconPressed, @@ -1169,46 +1149,52 @@ namespace Nampower { // register our own lua functions DEBUG_LOG("Registering Custom Lua functions"); char queueSpellByName[] = "QueueSpellByName"; - RegisterLuaFunction(queueSpellByName, reinterpret_cast(Offsets::Script_QueueSpellByName)); + RegisterLuaFunction(queueSpellByName, reinterpret_cast(Script_QueueSpellByName)); char castSpellByNameNoQueue[] = "CastSpellByNameNoQueue"; - RegisterLuaFunction(castSpellByNameNoQueue, - reinterpret_cast(Offsets::Script_CastSpellByNameNoQueue)); + RegisterLuaFunction(castSpellByNameNoQueue, reinterpret_cast(Script_CastSpellByNameNoQueue)); char queueScript[] = "QueueScript"; - RegisterLuaFunction(queueScript, reinterpret_cast(Offsets::Script_QueueScript)); + RegisterLuaFunction(queueScript, reinterpret_cast(Script_QueueScript)); char isSpellInRange[] = "IsSpellInRange"; - RegisterLuaFunction(isSpellInRange, reinterpret_cast(Offsets::Script_IsSpellInRange)); + RegisterLuaFunction(isSpellInRange, reinterpret_cast(Script_IsSpellInRange)); char isSpellUsable[] = "IsSpellUsable"; - RegisterLuaFunction(isSpellUsable, reinterpret_cast(Offsets::Script_IsSpellUsable)); + RegisterLuaFunction(isSpellUsable, reinterpret_cast(Script_IsSpellUsable)); char getCurrentCastingInfo[] = "GetCurrentCastingInfo"; - RegisterLuaFunction(getCurrentCastingInfo, - reinterpret_cast(Offsets::Script_GetCurrentCastingInfo)); + RegisterLuaFunction(getCurrentCastingInfo, reinterpret_cast(Script_GetCurrentCastingInfo)); char getSpellIdForName[] = "GetSpellIdForName"; - RegisterLuaFunction(getSpellIdForName, - reinterpret_cast(Offsets::Script_GetSpellIdForName)); + RegisterLuaFunction(getSpellIdForName, reinterpret_cast(Script_GetSpellIdForName)); char getSpellNameAndRankForId[] = "GetSpellNameAndRankForId"; - RegisterLuaFunction(getSpellNameAndRankForId, - reinterpret_cast(Offsets::Script_GetSpellNameAndRankForId)); + RegisterLuaFunction(getSpellNameAndRankForId, reinterpret_cast(Script_GetSpellNameAndRankForId)); char getSpellSlotTypeIdForName[] = "GetSpellSlotTypeIdForName"; - RegisterLuaFunction(getSpellSlotTypeIdForName, - reinterpret_cast(Offsets::Script_GetSpellSlotTypeIdForName)); + RegisterLuaFunction(getSpellSlotTypeIdForName, reinterpret_cast(Script_GetSpellSlotTypeIdForName)); char channelStopCastingNextTick[] = "ChannelStopCastingNextTick"; - RegisterLuaFunction(channelStopCastingNextTick, - reinterpret_cast(Offsets::Script_ChannelStopCastingNextTick)); + RegisterLuaFunction(channelStopCastingNextTick, reinterpret_cast(Script_ChannelStopCastingNextTick)); char getNampowerVersion[] = "GetNampowerVersion"; - RegisterLuaFunction(getNampowerVersion, reinterpret_cast(Offsets::Script_GetNampowerVersion)); + RegisterLuaFunction(getNampowerVersion, reinterpret_cast(Script_GetNampowerVersion)); char getItemILevel[] = "GetItemLevel"; - RegisterLuaFunction(getItemILevel, reinterpret_cast(Offsets::Script_GetItemLevel)); + RegisterLuaFunction(getItemILevel, reinterpret_cast(Script_GetItemLevel)); + + char getItemStats[] = "GetItemStats"; + RegisterLuaFunction(getItemStats, reinterpret_cast(Script_GetItemStats)); + + char getSpellRec[] = "GetSpellRec"; + RegisterLuaFunction(getSpellRec, reinterpret_cast(Script_GetSpellRec)); + + char getItemStatsField[] = "GetItemStatsField"; + RegisterLuaFunction(getItemStatsField, reinterpret_cast(Script_GetItemStatsField)); + + char getSpellRecField[] = "GetSpellRecField"; + RegisterLuaFunction(getSpellRecField, reinterpret_cast(Script_GetSpellRecField)); } std::once_flag loadFlag; diff --git a/nampower/main.hpp b/nampower/main.hpp index a1f2425..294f02c 100644 --- a/nampower/main.hpp +++ b/nampower/main.hpp @@ -30,8 +30,8 @@ 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 = 11; - constexpr uint32_t PATCH_VERSION = 2; + constexpr uint32_t MINOR_VERSION = 12; + constexpr uint32_t PATCH_VERSION = 0; constexpr int32_t LUA_REGISTRYINDEX = -10000; constexpr int32_t LUA_GLOBALSINDEX = -10001; @@ -96,7 +96,8 @@ namespace Nampower { using CGPlayer_C_OnAttackIconPressedT = int (__fastcall *)(uintptr_t *this_ptr, void *dummy_edx, uint64_t guid); - using GetSpellSlotAndTypeT = int (__fastcall *)(const char *, uint32_t *); + using GetSpellSlotAndTypeT = uint32_t (__fastcall *)(const char *, uint32_t *); + using GetSpellSlotFromLuaT = uint32_t (__fastcall *)(int param_1, uint32_t *slot, uint32_t *type); using GetTimeMsT = uint64_t (__stdcall *)(); using GetClientConnectionT = uintptr_t *(__stdcall *)(); @@ -124,6 +125,8 @@ namespace Nampower { using lua_pushnilT = void (__fastcall *)(uintptr_t *); using lua_errorT = void (__cdecl *)(uintptr_t *, const char *); using lua_settopT = void (__fastcall *)(uintptr_t *, int); + using lua_newtableT = void (__fastcall *)(uintptr_t *); + using lua_settableT = void (__fastcall *)(uintptr_t *, int); using Spell_C_CooldownEventTriggeredT = void (__fastcall *)(uint32_t spellId, uint64_t *targetGUID, @@ -147,6 +150,8 @@ namespace Nampower { void *callbackPtr, int category, char unk2, int unk3); + using InvalidFunctionPtrCheckT = void (__fastcall *)(uint32_t param_1); + void RegisterLuaFunction(char *, uintptr_t *func); void LuaCall(const char *code); diff --git a/nampower/offsets.hpp b/nampower/offsets.hpp index 063cf97..d565e09 100644 --- a/nampower/offsets.hpp +++ b/nampower/offsets.hpp @@ -49,6 +49,7 @@ enum class Offsets : std::uint32_t { AutoRepeatingSpellId = 0xCEAC30, VisualSpellId = 0X00CEAC58, GetSpellSlotAndType = 0X004B3950, + GetSpellSlotFromLua = 0X004B3EC0, OsGetAsyncTimeMs = 0X0042B790, ChannelTargetGuid = 0xC4D980, NameplateDistance = 0xC4D988, // float containing the distance squared ready to be pythagorean'd @@ -124,20 +125,6 @@ enum class Offsets : std::uint32_t { Script_RunScript = 0x0048B980, Script_SpellStopCasting = 0x006E6E80, - // Added script functions - Script_QueueSpellByName = 0x004B4B38, // unused address at the end of Script_CastSpellByName. Need to be < 0x7FEDAC to avoid Invalid Function Pointer - Script_CastSpellByNameNoQueue = 0X004B4B64, - Script_QueueScript = 0X0048B968, // unused address at the end of Script_StopCinematic - Script_IsSpellInRange = 0x004E76D8, - Script_IsSpellUsable = 0X004E77A4, - Script_GetCurrentCastingInfo = 0X004E77F8, - Script_GetSpellIdForName = 0X004E7828, - Script_GetSpellNameAndRankForId = 0X004E7844, - Script_GetSpellSlotTypeIdForName = 0X004E784A, - Script_ChannelStopCastingNextTick = 0X004E7858, - Script_GetNampowerVersion = 0X004E7874, - Script_GetItemLevel = 0X004E787A, - lua_state_ptr = 0x7040D0, lua_isstring = 0x006F3510, @@ -152,6 +139,11 @@ enum class Offsets : std::uint32_t { lua_pcall = 0x006F41A0, lua_error = 0X006F4940, lua_settop = 0X006F3080, + lua_newtable = 0x006F3C90, + lua_settable = 0x006F3E20, + lua_pushboolean = 0x006F39F0, + lua_rawseti = 0x006F3EA0, + lua_gettop = 0x006F3070, CGInputControlGetActive = 0XBE1148, @@ -177,4 +169,8 @@ enum class Offsets : std::uint32_t { CGUnit_C_ClearSpellEffect = 0x00614150, CGPlayer_C_OnAttackIconPressed = 0X006131A0, + + DBCache_ItemCacheDBGetRow = 0x0055BA30, + + InvalidFunctionPtrCheck = 0x0042A320, }; diff --git a/nampower/scripts.cpp b/nampower/scripts.cpp index d1989ab..56b1be8 100644 --- a/nampower/scripts.cpp +++ b/nampower/scripts.cpp @@ -4,6 +4,10 @@ #include "scripts.hpp" #include "offsets.hpp" +#include "items.hpp" +#include "dbc_fields.hpp" +#include "helper.hpp" +#include namespace Nampower { auto const lua_error = reinterpret_cast(Offsets::lua_error); @@ -14,38 +18,17 @@ namespace Nampower { auto const lua_tostring = reinterpret_cast(Offsets::lua_tostring); auto const lua_tonumber = reinterpret_cast(Offsets::lua_tonumber); - auto const lua_pushnumber = reinterpret_cast(Offsets::lua_pushnumber); - auto const lua_pushstring = reinterpret_cast(Offsets::lua_pushstring); + // Export Lua functions for dbc_fields.hpp templates + lua_pushnumberT lua_pushnumber = reinterpret_cast(Offsets::lua_pushnumber); + lua_pushstringT lua_pushstring = reinterpret_cast(Offsets::lua_pushstring); + lua_newtableT lua_newtable = reinterpret_cast(Offsets::lua_newtable); + lua_settableT lua_settable = reinterpret_cast(Offsets::lua_settable); bool gScriptQueued; int gScriptPriority = 1; char *queuedScript; - uint32_t GetSpellSlotAndTypeForName(const char *spellName, uint32_t *spellType) { - auto const getSpellSlotAndType = reinterpret_cast(Offsets::GetSpellSlotAndType); - return getSpellSlotAndType(spellName, spellType); - } - - - uint32_t GetSpellIdFromSpellName(const char *spellName) { - uint32_t bookType; - uint32_t spellSlot = GetSpellSlotAndTypeForName(spellName, &bookType); - - uint32_t spellId = 0; - if (spellSlot < 1024) { - if (bookType == 0) { - spellId = *reinterpret_cast(uint32_t(Offsets::CGSpellBook_mKnownSpells) + - spellSlot * 4); - } else { - spellId = *reinterpret_cast(uint32_t(Offsets::CGSpellBook_mKnownPetSpells) + - spellSlot * 4); - } - } - - return spellId; - } - - uint32_t Script_CastSpellByNameNoQueue(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) { + uint32_t Script_CastSpellByNameNoQueue(uintptr_t *luaState) { DEBUG_LOG("Casting next spell without queuing"); // turn on forceQueue and then call regular CastSpellByName gNoQueueCast = true; @@ -56,7 +39,7 @@ namespace Nampower { return result; } - uint32_t Script_QueueSpellByName(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) { + uint32_t Script_QueueSpellByName(uintptr_t *luaState) { DEBUG_LOG("Force queuing next cast spell"); // turn on forceQueue and then call regular CastSpellByName gForceQueueCast = true; @@ -78,7 +61,7 @@ namespace Nampower { return spellStopCasting(luaState); } - uint32_t Script_IsSpellInRange(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) { + uint32_t Script_IsSpellInRange(uintptr_t *luaState) { auto param1IsString = lua_isstring(luaState, 1); auto param1IsNumber = lua_isnumber(luaState, 1); if (param1IsString || param1IsNumber) { @@ -150,7 +133,7 @@ namespace Nampower { return 0; } - uint32_t Script_IsSpellUsable(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) { + uint32_t Script_IsSpellUsable(uintptr_t *luaState) { auto param1IsString = lua_isstring(luaState, 1); auto param1IsNumber = lua_isnumber(luaState, 1); if (param1IsString || param1IsNumber) { @@ -204,7 +187,7 @@ namespace Nampower { return 0; } - uint32_t Script_GetCurrentCastingInfo(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) { + uint32_t Script_GetCurrentCastingInfo(uintptr_t *luaState) { auto const castingSpellId = reinterpret_cast(Offsets::CastingSpellId); lua_pushnumber(luaState, *castingSpellId); @@ -246,7 +229,7 @@ namespace Nampower { return 7; } - uint32_t Script_GetSpellIdForName(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) { + uint32_t Script_GetSpellIdForName(uintptr_t *luaState) { if (lua_isstring(luaState, 1)) { auto const spellName = lua_tostring(luaState, 1); auto const spellId = GetSpellIdFromSpellName(spellName); @@ -259,7 +242,7 @@ namespace Nampower { return 0; } - uint32_t Script_GetSpellNameAndRankForId(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) { + uint32_t Script_GetSpellNameAndRankForId(uintptr_t *luaState) { if (lua_isnumber(luaState, 1)) { auto const spellId = uint32_t(lua_tonumber(luaState, 1)); auto const spell = game::GetSpellInfo(spellId); @@ -271,7 +254,7 @@ namespace Nampower { return 2; } else { DEBUG_LOG("Spell not found for id: " << spellId); - lua_error(luaState, "Spell not found"); + lua_error(luaState, "Spell not found"); } } else { lua_error(luaState, "Usage: GetSpellNameAndRankForId(spellId)"); @@ -280,7 +263,7 @@ namespace Nampower { return 0; } - uint32_t Script_GetSpellSlotTypeIdForName(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) { + uint32_t Script_GetSpellSlotTypeIdForName(uintptr_t *luaState) { if (lua_isstring(luaState, 1)) { auto const spellName = lua_tostring(luaState, 1); uint32_t bookType; @@ -330,7 +313,7 @@ namespace Nampower { return 0; } - uint32_t Script_ChannelStopCastingNextTick(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) { + uint32_t Script_ChannelStopCastingNextTick(uintptr_t *luaState) { if (gCastData.channeling) { DEBUG_LOG("ChannelStopCastingNextTick activated, canceling next tick"); gCastData.cancelChannelNextTick = true; @@ -339,7 +322,7 @@ namespace Nampower { return 0; } - uint32_t Script_GetNampowerVersion(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) { + uint32_t Script_GetNampowerVersion(uintptr_t *luaState) { lua_pushnumber(luaState, MAJOR_VERSION); lua_pushnumber(luaState, MINOR_VERSION); lua_pushnumber(luaState, PATCH_VERSION); @@ -347,7 +330,7 @@ namespace Nampower { return 3; } - uint32_t Script_GetItemLevel(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) { + uint32_t Script_GetItemLevel(uintptr_t *luaState) { if (lua_isnumber(luaState, 1)) { auto const itemId = static_cast(lua_tonumber(luaState, 1)); @@ -380,7 +363,7 @@ namespace Nampower { return 0; } - bool Script_QueueScript(hadesmem::PatchDetourBase *detour, uintptr_t *luaState) { + uint32_t Script_QueueScript(uintptr_t *luaState) { DEBUG_LOG("Trying to queue script"); auto const currentTime = GetTime(); @@ -418,7 +401,7 @@ namespace Nampower { return runScript(luaState); } - return false; + return 0; } bool RunQueuedScript(int priority) { @@ -440,4 +423,344 @@ namespace Nampower { return false; } -} \ No newline at end of file + + uint32_t Script_GetItemStats(uintptr_t *luaState) { + if (!lua_isnumber(luaState, 1)) { + lua_error(luaState, "Usage: GetItemStats(itemId)"); + return 0; + } + + uint32_t itemId = static_cast(lua_tonumber(luaState, 1)); + + // Load item if not cached + LoadItem(itemId); + + // Get from cache + game::ItemStats_C *item = GetItemStats(itemId); + if (!item) { + lua_error(luaState, "Item not found"); + return 0; + } + + // Create new table + lua_newtable(luaState); + + // Push all simple fields using descriptors + PushFieldsToLua(luaState, item, itemStatsFields, itemStatsFieldsCount); + + // Push string fields manually (require language) + auto const language = *reinterpret_cast(Offsets::Language); + lua_pushstring(luaState, const_cast("displayName")); + lua_pushstring(luaState, + item->m_displayName[language] ? item->m_displayName[language] : const_cast("")); + lua_settable(luaState, -3); + + lua_pushstring(luaState, const_cast("description")); + lua_pushstring(luaState, item->m_description ? item->m_description : const_cast("")); + lua_settable(luaState, -3); + + // Push all array fields using descriptors + PushArrayFieldsToLua(luaState, item, itemStatsArrayFields, itemStatsArrayFieldsCount); + + return 1; // Return the table + } + + uint32_t Script_GetSpellRec(uintptr_t *luaState) { + if (!lua_isnumber(luaState, 1)) { + lua_error(luaState, "Usage: GetSpellRec(spellId)"); + return 0; + } + + uint32_t spellId = static_cast(lua_tonumber(luaState, 1)); + + // Get spell info + auto spell = game::GetSpellInfo(spellId); + if (!spell) { + lua_error(luaState, "Spell not found"); + return 0; + } + + // Create new table + lua_newtable(luaState); + + // Push all simple fields using descriptors + PushFieldsToLua(luaState, spell, spellRecFields, spellRecFieldsCount); + + // Push string fields manually (require language) + auto const language = *reinterpret_cast(Offsets::Language); + lua_pushstring(luaState, const_cast("name")); + lua_pushstring(luaState, spell->SpellName[language] ? const_cast(spell->SpellName[language]) + : const_cast("")); + lua_settable(luaState, -3); + + lua_pushstring(luaState, const_cast("rank")); + lua_pushstring(luaState, reinterpret_cast(spell->Rank[language]) + ? const_cast(reinterpret_cast(spell->Rank[language])) + : const_cast("")); + lua_settable(luaState, -3); + + // Push all array fields using descriptors + PushArrayFieldsToLua(luaState, spell, spellRecArrayFields, spellRecArrayFieldsCount); + + return 1; // Return the table + } + + uint32_t Script_GetItemStatsField(uintptr_t *luaState) { + if (!lua_isnumber(luaState, 1) || !lua_isstring(luaState, 2)) { + lua_error(luaState, "Usage: GetItemStatsField(itemId, fieldName)"); + return 0; + } + + InitializeFieldMaps(); + + uint32_t itemId = static_cast(lua_tonumber(luaState, 1)); + const char *fieldName = lua_tostring(luaState, 2); + + // Load item if not cached + LoadItem(itemId); + + // Get from cache + game::ItemStats_C *item = GetItemStats(itemId); + if (!item) { + lua_pushnumber(luaState, 0); // Return nil/false to indicate not found + return 1; + } + + // O(1) hash table lookup for simple fields + auto simpleIt = itemStatsFieldMap.find(fieldName); + if (simpleIt != itemStatsFieldMap.end()) { + size_t i = simpleIt->second; + const char *fieldPtr = reinterpret_cast(item) + itemStatsFields[i].offset; + + switch (itemStatsFields[i].type) { + case FieldType::INT32: + lua_pushnumber(luaState, *reinterpret_cast(fieldPtr)); + return 1; + case FieldType::UINT32: + lua_pushnumber(luaState, *reinterpret_cast(fieldPtr)); + return 1; + case FieldType::FLOAT: + lua_pushnumber(luaState, *reinterpret_cast(fieldPtr)); + return 1; + case FieldType::STRING: { + const char *str = *reinterpret_cast(fieldPtr); + lua_pushstring(luaState, str ? const_cast(str) : const_cast("")); + return 1; + } + default: + break; + } + } + + // O(1) hash table lookup for array fields + auto arrayIt = itemStatsArrayFieldMap.find(fieldName); + if (arrayIt != itemStatsArrayFieldMap.end()) { + size_t i = arrayIt->second; + const auto &field = itemStatsArrayFields[i]; + lua_newtable(luaState); + + const char *fieldPtr = reinterpret_cast(item) + field.offset; + + for (size_t j = 0; j < field.count; ++j) { + lua_pushnumber(luaState, j + 1); + + switch (field.type) { + case FieldType::INT32: + lua_pushnumber(luaState, reinterpret_cast(fieldPtr)[j]); + break; + case FieldType::UINT32: + lua_pushnumber(luaState, reinterpret_cast(fieldPtr)[j]); + break; + case FieldType::FLOAT: + lua_pushnumber(luaState, reinterpret_cast(fieldPtr)[j]); + break; + default: + lua_pushnumber(luaState, 0); + break; + } + lua_settable(luaState, -3); + } + return 1; + } + + // Check for special string fields + if (strcmp(fieldName, "displayName") == 0) { + auto const language = *reinterpret_cast(Offsets::Language); + lua_pushstring(luaState, + item->m_displayName[language] ? item->m_displayName[language] : const_cast("")); + return 1; + } + if (strcmp(fieldName, "description") == 0) { + lua_pushstring(luaState, item->m_description ? item->m_description : const_cast("")); + return 1; + } + + // Field not found + lua_error(luaState, "Unknown field name"); + return 0; + } + + uint32_t Script_GetSpellRecField(uintptr_t *luaState) { + if (!lua_isnumber(luaState, 1) || !lua_isstring(luaState, 2)) { + lua_error(luaState, "Usage: GetSpellRecField(spellId, fieldName)"); + return 0; + } + + InitializeFieldMaps(); + + uint32_t spellId = static_cast(lua_tonumber(luaState, 1)); + const char *fieldName = lua_tostring(luaState, 2); + + // Get spell info + auto spell = game::GetSpellInfo(spellId); + if (!spell) { + lua_pushnumber(luaState, 0); // Return nil/false to indicate not found + return 1; + } + + // O(1) hash table lookup for simple fields + auto simpleIt = spellRecFieldMap.find(fieldName); + if (simpleIt != spellRecFieldMap.end()) { + size_t i = simpleIt->second; + const char *fieldPtr = reinterpret_cast(spell) + spellRecFields[i].offset; + + switch (spellRecFields[i].type) { + case FieldType::INT32: + lua_pushnumber(luaState, *reinterpret_cast(fieldPtr)); + return 1; + case FieldType::UINT32: + lua_pushnumber(luaState, *reinterpret_cast(fieldPtr)); + return 1; + case FieldType::FLOAT: + lua_pushnumber(luaState, *reinterpret_cast(fieldPtr)); + return 1; + case FieldType::UINT64: + lua_pushnumber(luaState, static_cast(*reinterpret_cast(fieldPtr))); + return 1; + case FieldType::STRING: { + const char *str = *reinterpret_cast(fieldPtr); + lua_pushstring(luaState, str ? const_cast(str) : const_cast("")); + return 1; + } + } + } + + // O(1) hash table lookup for array fields + auto arrayIt = spellRecArrayFieldMap.find(fieldName); + if (arrayIt != spellRecArrayFieldMap.end()) { + size_t i = arrayIt->second; + const auto &field = spellRecArrayFields[i]; + lua_newtable(luaState); + + const char *fieldPtr = reinterpret_cast(spell) + field.offset; + + for (size_t j = 0; j < field.count; ++j) { + lua_pushnumber(luaState, j + 1); + + switch (field.type) { + case FieldType::INT32: + lua_pushnumber(luaState, reinterpret_cast(fieldPtr)[j]); + break; + case FieldType::UINT32: + lua_pushnumber(luaState, reinterpret_cast(fieldPtr)[j]); + break; + case FieldType::FLOAT: + lua_pushnumber(luaState, reinterpret_cast(fieldPtr)[j]); + break; + default: + lua_pushnumber(luaState, 0); + break; + } + lua_settable(luaState, -3); + } + return 1; + } + + // Check for special string fields + if (strcmp(fieldName, "name") == 0) { + auto const language = *reinterpret_cast(Offsets::Language); + lua_pushstring(luaState, spell->SpellName[language] ? const_cast(spell->SpellName[language]) + : const_cast("")); + return 1; + } + if (strcmp(fieldName, "rank") == 0) { + auto const language = *reinterpret_cast(Offsets::Language); + lua_pushstring(luaState, reinterpret_cast(spell->Rank[language]) + ? const_cast(reinterpret_cast(spell->Rank[language])) + : const_cast("")); + return 1; + } + + // Field not found + lua_error(luaState, "Unknown field name"); + return 0; + } + + uint32_t GetSpellSlotFromLuaHook(hadesmem::PatchDetourBase *detour, int param_1, uint32_t *slot, uint32_t *type) { + // Get Lua state pointer + uintptr_t *luaState = GetLuaStatePtr(); + + // Check if first parameter is not a number + if (lua_isnumber(luaState, 1) == 0) { + const char *spellStr = lua_tostring(luaState, 1); + + // Check if string starts with "spellId:" (case-insensitive) + if (_strnicmp(spellStr, "spellId:", 8) == 0) { + // Extract spell ID from string + uint32_t spellId = atoi(spellStr + 8); + + if (spellId > 0) { + // Check if second parameter has "pet" + uint32_t bookType = 0; + if (lua_isstring(luaState, 2)) { + const char *bookTypeStr = lua_tostring(luaState, 2); + if (_strcmpi(bookTypeStr, "pet") == 0) { + bookType = 1; + } + } + + uint32_t spellSlot = ConvertSpellIdToSpellSlot(spellId, bookType); + if(!spellSlot){ + lua_error(luaState, "Unable convert spell id to spell slot."); + return 0; + } + + if (slot) { + *slot = spellSlot; + } + if (type) { + *type = bookType; // Use the requested bookType + } + + return 1; + } else { + lua_error(luaState, "Unable parse spell id. Format is spellId:123."); + return 0; + } + } else { + uint32_t bookType; + auto spellSlot = GetSpellSlotAndTypeForName(spellStr, &bookType); + // returns large number if spell not found + if (spellSlot == 0 || spellSlot > 100000) { + lua_error(luaState, "Unable to find spell slot for spell name."); + return 0; + } else { + if (slot) { + *slot = spellSlot; + } + if (type) { + *type = bookType; + } + } + + return 1; + } + } else { + auto const getSpellSlotFromLua = detour->GetTrampolineT(); + // Fall back to original function + return getSpellSlotFromLua(param_1, slot, type); + } + + return 0; + } +} diff --git a/nampower/scripts.hpp b/nampower/scripts.hpp index ab18b8e..ccbd19f 100644 --- a/nampower/scripts.hpp +++ b/nampower/scripts.hpp @@ -8,31 +8,41 @@ #include "main.hpp" namespace Nampower { - uint32_t Script_CastSpellByNameNoQueue(hadesmem::PatchDetourBase *detour, uintptr_t *luaState); + uint32_t Script_CastSpellByNameNoQueue(uintptr_t *luaState); - uint32_t Script_QueueSpellByName(hadesmem::PatchDetourBase *detour, uintptr_t *luaState); + uint32_t Script_QueueSpellByName(uintptr_t *luaState); - uint32_t Script_IsSpellInRange(hadesmem::PatchDetourBase *detour, uintptr_t *luaState); + uint32_t Script_IsSpellInRange(uintptr_t *luaState); - uint32_t Script_IsSpellUsable(hadesmem::PatchDetourBase *detour, uintptr_t *luaState); + uint32_t Script_IsSpellUsable(uintptr_t *luaState); uint32_t Script_SpellStopCastingHook(hadesmem::PatchDetourBase *detour, uintptr_t *luaState); - uint32_t Script_GetCurrentCastingInfo(hadesmem::PatchDetourBase *detour, uintptr_t *luaState); + uint32_t Script_GetCurrentCastingInfo(uintptr_t *luaState); - uint32_t Script_GetSpellIdForName(hadesmem::PatchDetourBase *detour, uintptr_t *luaState); + uint32_t Script_GetSpellIdForName(uintptr_t *luaState); - uint32_t Script_GetSpellNameAndRankForId(hadesmem::PatchDetourBase *detour, uintptr_t *luaState); + uint32_t Script_GetSpellNameAndRankForId(uintptr_t *luaState); - uint32_t Script_GetSpellSlotTypeIdForName(hadesmem::PatchDetourBase *detour, uintptr_t *luaState); + uint32_t Script_GetSpellSlotTypeIdForName(uintptr_t *luaState); - uint32_t Script_ChannelStopCastingNextTick(hadesmem::PatchDetourBase *detour, uintptr_t *luaState); + uint32_t Script_ChannelStopCastingNextTick(uintptr_t *luaState); - uint32_t Script_GetNampowerVersion(hadesmem::PatchDetourBase *detour, uintptr_t *luaState); + uint32_t Script_GetNampowerVersion(uintptr_t *luaState); - uint32_t Script_GetItemLevel(hadesmem::PatchDetourBase *detour, uintptr_t *luaState); + uint32_t Script_GetItemLevel(uintptr_t *luaState); - bool Script_QueueScript(hadesmem::PatchDetourBase *detour, uintptr_t *luaState); + uint32_t Script_QueueScript(uintptr_t *luaState); + + uint32_t Script_GetItemStats(uintptr_t *luaState); + + uint32_t Script_GetSpellRec(uintptr_t *luaState); + + uint32_t Script_GetItemStatsField(uintptr_t *luaState); + + uint32_t Script_GetSpellRecField(uintptr_t *luaState); + + uint32_t GetSpellSlotFromLuaHook(hadesmem::PatchDetourBase *detour, int param_1, uint32_t *slot, uint32_t *type); bool RunQueuedScript(int priority); } \ No newline at end of file diff --git a/nampower/spellevents.cpp b/nampower/spellevents.cpp index 300dba0..e105a39 100644 --- a/nampower/spellevents.cpp +++ b/nampower/spellevents.cpp @@ -704,13 +704,4 @@ namespace Nampower { return 1; } - - int CGPlayer_C_OnAttackIconPressedHook(hadesmem::PatchDetourBase *detour, uintptr_t *this_ptr, void *dummy_edx, - uint64_t guid) { - auto const onAttackIconPressed = detour->GetTrampolineT(); - - DEBUG_LOG("Attack icon pressed for guid " << guid); - - return onAttackIconPressed(this_ptr, dummy_edx, 0); - } } \ No newline at end of file diff --git a/nampower/spellevents.hpp b/nampower/spellevents.hpp index 00898b0..44fed0c 100644 --- a/nampower/spellevents.hpp +++ b/nampower/spellevents.hpp @@ -35,7 +35,4 @@ namespace Nampower { CDataStore *packet); int PlaySpellVisualHandlerHook(hadesmem::PatchDetourBase *detour, uint32_t *opCode, CDataStore *packet); - - int CGPlayer_C_OnAttackIconPressedHook(hadesmem::PatchDetourBase *detour, uintptr_t *this_ptr, void *dummy_edx, - uint64_t guid); } \ No newline at end of file From 48dc086677cffa21656bc95f00324da9133cfdb0 Mon Sep 17 00:00:00 2001 From: avitasia Date: Tue, 2 Dec 2025 03:46:48 +0000 Subject: [PATCH 3/6] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d8257b6..fc42a69 100644 --- a/README.md +++ b/README.md @@ -240,7 +240,7 @@ Returns a Lua table containing all fields for the spell’s `SpellRec` record (i #### GetSpellRecField(spellId, fieldName) Fast lookup for a single field on a spell. Returns the requested field value or raises a Lua error if the spell or field is not found. -Struct references (see `nampower/game.hpp` for full details): +Struct references (see `nampower/dbc_fields.cpp` for full details): #### GetNampowerVersion() Returns the current version of Nampower split into major, minor and patch numbers. From 2ed95bc439bd0def5c2783e78200cf9fe9bc30c3 Mon Sep 17 00:00:00 2001 From: avitasia Date: Mon, 1 Dec 2025 21:29:56 -0800 Subject: [PATCH 4/6] improve errors and readme --- DBC_FIELDS.md | 144 +++++++++++++++++++++++++++++++++++++++++++ README.md | 16 +++-- nampower/scripts.cpp | 13 ++-- 3 files changed, 162 insertions(+), 11 deletions(-) create mode 100644 DBC_FIELDS.md diff --git a/DBC_FIELDS.md b/DBC_FIELDS.md new file mode 100644 index 0000000..414a1ad --- /dev/null +++ b/DBC_FIELDS.md @@ -0,0 +1,144 @@ +# DBC Field Names + +Related structs live in `nampower/game.hpp` (notably `SpellRec` and `ItemStats_C`). + +## ItemStats_C Fields (alphabetical) +- `allowableClass` +- `allowableRace` +- `ammoType` +- `area` +- `bagFamily` +- `block` +- `bonding` +- `buyPrice` +- `class` +- `containerSlots` +- `delay` +- `displayInfoID` +- `duration` +- `flags` +- `inventoryType` +- `itemLevel` +- `itemSet` +- `languageID` +- `lockID` +- `map` +- `material` +- `maxCount` +- `maxDurability` +- `pageMaterial` +- `pageText` +- `quality` +- `randomProperty` +- `rangedModRange` +- `requiredCityRank` +- `requiredHonorRank` +- `requiredLevel` +- `requiredRep` +- `requiredRepRank` +- `requiredSkill` +- `requiredSkillRank` +- `requiredSpell` +- `sellPrice` +- `sheatheType` +- `stackable` +- `startQuestID` +- `subclass` + +### ItemStats_C Array Fields (alphabetical) +- `bonusAmount` +- `bonusStat` +- `damageType` +- `maxDamage` +- `minDamage` +- `resistances` +- `spellCategory` +- `spellCategoryCooldown` +- `spellCharges` +- `spellCooldown` +- `spellID` +- `spellTrigger` + +## SpellRec Fields (alphabetical) +- `activeIconID` +- `attributes` +- `attributesEx` +- `attributesEx2` +- `attributesEx3` +- `attributesEx4` +- `auraInterruptFlags` +- `baseLevel` +- `castUI` +- `category` +- `categoryRecoveryTime` +- `casterAuraState` +- `castingTimeIndex` +- `channelInterruptFlags` +- `dispel` +- `dmgClass` +- `durationIndex` +- `equippedItemClass` +- `equippedItemInventoryTypeMask` +- `equippedItemSubClassMask` +- `id` +- `interruptFlags` +- `manaCost` +- `manaCostPercentage` +- `manaCostPerlevel` +- `manaPerSecond` +- `manaPerSecondPerLevel` +- `maxAffectedTargets` +- `maxLevel` +- `maxTargetLevel` +- `mechanic` +- `minFactionId` +- `minReputation` +- `modalNextSpell` +- `powerType` +- `preventionType` +- `procChance` +- `procCharges` +- `procFlags` +- `rangeIndex` +- `recoveryTime` +- `requiredAuraVision` +- `school` +- `spellFamilyFlags` +- `spellFamilyName` +- `spellIconID` +- `spellLevel` +- `spellPriority` +- `spellVisual` +- `spellVisual2` +- `stackAmount` +- `stances` +- `stancesNot` +- `startRecoveryCategory` +- `startRecoveryTime` +- `targetAuraState` +- `targetCreatureType` +- `targets` + +### SpellRec Array Fields (alphabetical) +- `dmgMultiplier` +- `effect` +- `effectAmplitude` +- `effectApplyAuraName` +- `effectBaseDice` +- `effectBasePoints` +- `effectChainTarget` +- `effectDicePerLevel` +- `effectDieSides` +- `effectImplicitTargetA` +- `effectImplicitTargetB` +- `effectItemType` +- `effectMechanic` +- `effectMiscValue` +- `effectMultipleValue` +- `effectPointsPerComboPoint` +- `effectRadiusIndex` +- `effectRealPointsPerLevel` +- `effectTriggerSpell` +- `reagent` +- `reagentCount` +- `totem` diff --git a/README.md b/README.md index fc42a69..cc2d0b9 100644 --- a/README.md +++ b/README.md @@ -229,18 +229,24 @@ Examples: ``` #### GetItemStats(itemId) -Returns a Lua table containing all fields for the item’s `ItemStats_C` record (including localized `displayName` and `description`). Returns 0 if the item cannot be found or loaded. +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 or raises a Lua error if the item or field is not found. +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 0 if the spell cannot be found. +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 or raises a Lua error if the spell or field is not found. +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. -Struct references (see `nampower/dbc_fields.cpp` for full details): +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/nampower/scripts.cpp b/nampower/scripts.cpp index 56b1be8..2422937 100644 --- a/nampower/scripts.cpp +++ b/nampower/scripts.cpp @@ -21,6 +21,7 @@ namespace Nampower { // Export Lua functions for dbc_fields.hpp templates lua_pushnumberT lua_pushnumber = reinterpret_cast(Offsets::lua_pushnumber); lua_pushstringT lua_pushstring = reinterpret_cast(Offsets::lua_pushstring); + lua_pushnilT lua_pushnil = reinterpret_cast(Offsets::lua_pushnil); lua_newtableT lua_newtable = reinterpret_cast(Offsets::lua_newtable); lua_settableT lua_settable = reinterpret_cast(Offsets::lua_settable); @@ -438,8 +439,8 @@ namespace Nampower { // Get from cache game::ItemStats_C *item = GetItemStats(itemId); if (!item) { - lua_error(luaState, "Item not found"); - return 0; + lua_pushnil(luaState); + return 1; } // Create new table @@ -476,8 +477,8 @@ namespace Nampower { // Get spell info auto spell = game::GetSpellInfo(spellId); if (!spell) { - lua_error(luaState, "Spell not found"); - return 0; + lua_pushnil(luaState); + return 1; } // Create new table @@ -522,7 +523,7 @@ namespace Nampower { // Get from cache game::ItemStats_C *item = GetItemStats(itemId); if (!item) { - lua_pushnumber(luaState, 0); // Return nil/false to indicate not found + lua_pushnil(luaState); return 1; } @@ -614,7 +615,7 @@ namespace Nampower { // Get spell info auto spell = game::GetSpellInfo(spellId); if (!spell) { - lua_pushnumber(luaState, 0); // Return nil/false to indicate not found + lua_pushnil(luaState); return 1; } From 2c81677cfa500bdc7f70f19229674d450226e4f3 Mon Sep 17 00:00:00 2001 From: avitasia Date: Tue, 2 Dec 2025 13:27:59 -0800 Subject: [PATCH 5/6] 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(); +} From 341f4a350d54660233df6d464c9b76b2b9053d0d Mon Sep 17 00:00:00 2001 From: avitasia Date: Tue, 2 Dec 2025 13:35:04 -0800 Subject: [PATCH 6/6] fix unicode apos --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a64f340..9f4a222 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ Examples: ### 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. +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). @@ -151,7 +151,7 @@ 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. +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).