From 46c26689e4170a454918d9f99e67a9fdd6aaf0d9 Mon Sep 17 00:00:00 2001 From: avitasia Date: Fri, 5 Dec 2025 18:45:25 -0800 Subject: [PATCH] fix item export, allow positioning aoe target during queuing --- nampower/game.hpp | 34 +++++++ nampower/helper.cpp | 6 +- nampower/items.cpp | 162 ++++++++++++++++++++------------ nampower/items.hpp | 7 +- nampower/main.cpp | 13 +++ nampower/main.hpp | 2 + nampower/offsets.hpp | 6 ++ nampower/spellcast.cpp | 205 ++++++++++++++++++++++++++--------------- nampower/spellcast.hpp | 4 + nampower/types.h | 3 + 10 files changed, 306 insertions(+), 136 deletions(-) diff --git a/nampower/game.hpp b/nampower/game.hpp index 42a5c08..fbeb776 100644 --- a/nampower/game.hpp +++ b/nampower/game.hpp @@ -156,6 +156,17 @@ namespace game { unsigned __int64 *pos; }; + struct C3Vector { + float x; + float y; + float z; + }; + + struct CTerrainClickEvent { + C3Vector point; + unsigned int button; + }; + enum ItemStatsFlags { ITEM_FLAG_NO_PICKUP = 1, ITEM_FLAG_CONJURED = 2, @@ -740,6 +751,29 @@ namespace game { TARGET_LOCATION_UNIT_POSITION = 64, }; + // SpellEntry::Targets + enum SpellCastTargetFlags + { + TARGET_FLAG_SELF = 0x00000000, + TARGET_FLAG_UNUSED1 = 0x00000001, // not used in any spells (can be set dynamically) + TARGET_FLAG_UNIT = 0x00000002, // pguid + TARGET_FLAG_UNUSED2 = 0x00000004, // not used in any spells (can be set dynamically) + TARGET_FLAG_UNUSED3 = 0x00000008, // not used in any spells (can be set dynamically) + TARGET_FLAG_ITEM = 0x00000010, // pguid + TARGET_FLAG_SOURCE_LOCATION = 0x00000020, // 3 float + TARGET_FLAG_DEST_LOCATION = 0x00000040, // 3 float + TARGET_FLAG_OBJECT_UNK = 0x00000080, // used in 7 spells only + TARGET_FLAG_UNIT_UNK = 0x00000100, // looks like self target (389 spells) + TARGET_FLAG_PVP_CORPSE = 0x00000200, // pguid + TARGET_FLAG_UNIT_CORPSE = 0x00000400, // 10 spells (gathering professions) + TARGET_FLAG_OBJECT = 0x00000800, // pguid, 0 spells + TARGET_FLAG_TRADE_ITEM = 0x00001000, // pguid, 0 spells + TARGET_FLAG_STRING = 0x00002000, // string, 0 spells + TARGET_FLAG_UNK1 = 0x00004000, // 199 spells, opening object/lock + TARGET_FLAG_CORPSE = 0x00008000, // pguid, resurrection spells + TARGET_FLAG_UNK2 = 0x00010000, // pguid, not used in any spells (can be set dynamically) + }; + enum Events : std::uint32_t { UNIT_NAME_UPDATE = 183, UNIT_PORTRAIT_UPDATE = 184, diff --git a/nampower/helper.cpp b/nampower/helper.cpp index 973bab9..d52fef0 100644 --- a/nampower/helper.cpp +++ b/nampower/helper.cpp @@ -30,7 +30,8 @@ namespace Nampower { const std::string key = normalize(spellName); - auto const spellNameMatchesAtSlot = [&normalize](const char *requestedLower, uint32_t slot, uint32_t type) -> bool { + auto const spellNameMatchesAtSlot = [&normalize](const char *requestedLower, uint32_t slot, + uint32_t type) -> bool { if (!requestedLower) { return false; } @@ -169,7 +170,8 @@ namespace Nampower { } bool SpellIsTargeting(const game::SpellRec *spell) { - return spell->Targets == game::SpellTarget::TARGET_LOCATION_UNIT_POSITION; + return spell->Targets & game::SpellCastTargetFlags::TARGET_FLAG_SOURCE_LOCATION || + spell->Targets & game::SpellCastTargetFlags::TARGET_FLAG_DEST_LOCATION; } bool SpellIsOnSwing(const game::SpellRec *spell) { diff --git a/nampower/items.cpp b/nampower/items.cpp index 532ac02..7e8005b 100644 --- a/nampower/items.cpp +++ b/nampower/items.cpp @@ -10,59 +10,24 @@ #include #include #include +#include namespace Nampower { // Global dictionary to store itemId -> ItemStats_C mappings static std::unordered_map itemStatsCache; + // Track pending async loads (can have multiple at once) + static std::unordered_set pendingItemIds; + + // Export state tracking + static bool isExporting = false; + static uint32_t currentExportItemId = 0; + static const uint32_t MAX_EXPORT_ITEM_ID = 100000; + static const uint32_t ITEMS_PER_FRAME = 20; + 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"; @@ -104,21 +69,77 @@ namespace Nampower { 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"); + // Callback function for DBCache + void __fastcall ItemStatsCallback(uint32_t itemId, const uint64_t *ownerGuid, void *userData, bool resultFlag) { + if (resultFlag) { + uint64_t guid; + auto itemStats = getRow(itemCache, itemId, &guid, &ItemStatsCallback, nullptr, false); + if (itemStats) { + // Store in cache + DEBUG_LOG("Added itemId " << itemId); + itemStatsCache[itemId] = reinterpret_cast(itemStats); + } else { + DEBUG_LOG("Still missing " << itemId); } } + // Remove from pending set + pendingItemIds.erase(itemId); + } + + bool LoadItem(uint32_t itemId) { + // Check if already in cache + auto it = itemStatsCache.find(itemId); + if (it != itemStatsCache.end()) { + return false; // Already loaded, no wait needed + } + + uint64_t guid; + auto itemStats = getRow(itemCache, itemId, &guid, &ItemStatsCallback, nullptr, true); + if (itemStats) { + // Item was available immediately, store in cache + itemStatsCache[itemId] = reinterpret_cast(itemStats); + return false; // Loaded immediately, no wait needed + } else { + // Item not immediately available, need to wait for callback + pendingItemIds.insert(itemId); + return true; // Caller should wait for callback + } + } + + bool ProcessItemExport() { + if (!isExporting) { + return false; // Not currently exporting + } + + // Try to load items until we have 20 pending or run out of items + uint32_t itemsRequestedThisFrame = 0; + while (pendingItemIds.size() < ITEMS_PER_FRAME && currentExportItemId <= MAX_EXPORT_ITEM_ID) { + LoadItem(currentExportItemId); + + // Log progress every 1000 items + if (currentExportItemId % 1000 == 0) { + DEBUG_LOG("Progress: " << currentExportItemId << " / " << MAX_EXPORT_ITEM_ID + << " items requested, " << pendingItemIds.size() << " pending callbacks"); + } + + currentExportItemId++; + itemsRequestedThisFrame++; + + // Safety check - don't request too many in a single frame + if (itemsRequestedThisFrame >= ITEMS_PER_FRAME) { + break; + } + } + + // Check if we're done (all items requested and no pending callbacks) + if (currentExportItemId > MAX_EXPORT_ITEM_ID && pendingItemIds.empty()) { + // Fall through to export completion + } else { + return true; // Still exporting, more items to process or waiting for callbacks + } + + // Export complete, write to file DEBUG_LOG("Finished fetching items, found " << itemStatsCache.size() << " valid items"); DEBUG_LOG("Writing items to items.json"); @@ -127,7 +148,8 @@ namespace Nampower { std::ofstream jsonFile("items.json", std::ios::trunc); if (!jsonFile.is_open()) { DEBUG_LOG("Failed to open items.json for writing"); - return; + isExporting = false; + return false; } jsonFile << "[\n"; @@ -289,5 +311,29 @@ namespace Nampower { } catch (const std::exception &e) { DEBUG_LOG("Exception writing to items.json: " << e.what()); } + + isExporting = false; + return false; // Export complete + } + + game::ItemStats_C *GetItemStats(uint32_t itemId) { + auto it = itemStatsCache.find(itemId); + if (it != itemStatsCache.end()) { + return it->second; + } + return nullptr; + } + + // Register a function if you want to use this + // Don't want everyone scraping all items. + void ExportAllItems() { + DEBUG_LOG("Starting item export - will fetch items 1 to " << MAX_EXPORT_ITEM_ID << " with up to " << ITEMS_PER_FRAME << " concurrent requests"); + + // Initialize export state + isExporting = true; + currentExportItemId = 1; + pendingItemIds.clear(); + + DEBUG_LOG("Export initialized. ProcessItemExport will be called each frame from processQueues."); } } diff --git a/nampower/items.hpp b/nampower/items.hpp index bfb1432..5a8fbd1 100644 --- a/nampower/items.hpp +++ b/nampower/items.hpp @@ -7,11 +7,12 @@ #include "main.hpp" namespace Nampower { - using TooltipItemStatsCallbackT = void (__stdcall *)(uintptr_t *tooltip, char param_2); + using TooltipItemStatsCallbackT = void (__fastcall *)(uint32_t itemId, const uint64_t *ownerGuid, void *userData, bool resultFlag); - 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); + using DBCache_ItemCacheDBGetRowT = uint32_t * (__thiscall *)(void *this_ptr, uint32_t itemId, uint64_t *guid, TooltipItemStatsCallbackT callback, uintptr_t *userData, bool requestIfMissing); void ExportAllItems(); - void LoadItem(uint32_t itemId); + bool LoadItem(uint32_t itemId); // Returns true if item needs async load (caller should wait) + bool ProcessItemExport(); // Process one item export per frame, returns true if still exporting game::ItemStats_C* GetItemStats(uint32_t itemId); } diff --git a/nampower/main.cpp b/nampower/main.cpp index 8fd5913..16cdcfe 100644 --- a/nampower/main.cpp +++ b/nampower/main.cpp @@ -94,6 +94,7 @@ namespace Nampower { std::unique_ptr> gSignalEventDetour; std::unique_ptr> gSpellFailedDetour; std::unique_ptr> gSpell_C_GetSpellModifiersDetour; + std::unique_ptr> gSpell_C_GetSpellRadiusDetour; std::unique_ptr gCastbarPatch; std::unique_ptr> gIEndSceneDetour; std::unique_ptr> gSpell_C_GetAutoRepeatingSpellDetour; @@ -103,6 +104,7 @@ namespace Nampower { std::unique_ptr> gSpellStopCastingDetour; std::unique_ptr> gOnSpriteRightClickDetour; std::unique_ptr> gSpell_C_HandleSpriteClickDetour; + std::unique_ptr> gSpell_C_HandleTerrainClickDetour; std::unique_ptr> gSpell_C_TargetSpellDetour; std::unique_ptr> gSpellCooldownDetour; @@ -328,6 +330,8 @@ namespace Nampower { gCastData.nonGcdSpellQueued = false; gCastData.cooldownNonGcdSpellQueued = false; } + gCastData.targetingSpellQueued = false; + gCastData.targetingSpellId = 0; } void checkForStopChanneling() { @@ -457,6 +461,8 @@ namespace Nampower { << " due to max time since last cast"); TriggerSpellQueuedEvent(NORMAL_QUEUE_POPPED, gLastNormalCastParams.spellId); gCastData.normalSpellQueued = false; + gCastData.targetingSpellQueued = false; + gCastData.targetingSpellId = 0; } } } @@ -481,6 +487,9 @@ namespace Nampower { } } + // Process item export if active (low priority, runs when nothing else is queued) + // ProcessItemExport() + return false; } @@ -1120,6 +1129,8 @@ namespace Nampower { &Script_SpellStopCastingHook); gSpell_C_TargetSpellDetour = createHook(process, Offsets::Spell_C_TargetSpell, &Spell_C_TargetSpellHook); + gSpell_C_HandleTerrainClickDetour = createHook(process, Offsets::Spell_C_HandleTerrainClick, + &Spell_C_HandleTerrainClickHook); gOnSpriteRightClickDetour = createHook(process, Offsets::OnSpriteRightClick, OnSpriteRightClickHook); gIEndSceneDetour = createHook(process, Offsets::ISceneEndPtr, &ISceneEndHook); @@ -1127,6 +1138,8 @@ namespace Nampower { &InvalidFunctionPtrCheckHook); gGetSpellSlotFromLuaDetour = createHook(process, Offsets::GetSpellSlotFromLua, &GetSpellSlotFromLuaHook); + gSpell_C_GetSpellRadiusDetour = createHook(process, Offsets::Spell_C_GetSpellRadius, + &Spell_C_GetSpellRadiusHook); } diff --git a/nampower/main.hpp b/nampower/main.hpp index a15522a..b72b1a3 100644 --- a/nampower/main.hpp +++ b/nampower/main.hpp @@ -84,6 +84,7 @@ namespace Nampower { using Spell_C_GetAutoRepeatingSpellT = int (__cdecl *)(); using SpellGoT = void (__fastcall *)(uint64_t *, uint64_t *, uint32_t, CDataStore *); using Spell_C_HandleSpriteClickT = bool (__fastcall *)(game::CSpriteClickEvent *event); + using Spell_C_HandleTerrainClickT = uint32_t (__fastcall *)(game::CTerrainClickEvent *event); using Spell_C_TargetSpellT = bool (__fastcall *)( uint32_t *player, uint32_t *spellId, @@ -93,6 +94,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_GetSpellRadiusT = float (__fastcall *)(); 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 63f1801..d4e9bf5 100644 --- a/nampower/offsets.hpp +++ b/nampower/offsets.hpp @@ -47,6 +47,7 @@ enum class Offsets : std::uint32_t { CastingItemIdPtr = 0X00CECAB0, CastingSpellId = 0xCECA88, AutoRepeatingSpellId = 0xCEAC30, + SpellNeedsTargets = 0X00CECAC0, VisualSpellId = 0X00CEAC58, GetSpellSlotAndType = 0X004B3950, GetSpellSlotFromLua = 0X004B3EC0, @@ -100,10 +101,12 @@ enum class Offsets : std::uint32_t { Spell_C_CastSpellByID = 0x6E5A90, Spell_C_GetAutoRepeatingSpell = 0x006E9FD0, Spell_C_HandleSpriteClick = 0x006E5B10, + Spell_C_HandleTerrainClick = 0x006E60F0, Spell_C_TargetSpell = 0x006E5250, Spell_C_GetSpellCooldown = 0X006E2EA0, Spell_C_IsSpellUsable = 0X006E3D60, Spell_C_GetDuration = 0X006EA000, + Spell_C_GetSpellRadius = 0x006E6350, Spell_C_ApplySpellModifiers = 0x006e6af0, Spell_C_GetSpellModifierValues = 0x006e6b30, @@ -174,4 +177,7 @@ enum class Offsets : std::uint32_t { DBCache_ItemCacheDBGetRow = 0x0055BA30, InvalidFunctionPtrCheck = 0x0042A320, + + CursorSetCursorMode = 0x00523d20, + CursorModelSetSequence = 0x00523c20, }; diff --git a/nampower/spellcast.cpp b/nampower/spellcast.cpp index 1242d15..d661cc5 100644 --- a/nampower/spellcast.cpp +++ b/nampower/spellcast.cpp @@ -10,6 +10,111 @@ namespace Nampower { auto const APPLY_BUFFER_TO_GCD = false; // gcd issue seems fixed for now + void SetReleaseAction(uint32_t input) { + uint32_t activeControl = *reinterpret_cast(Offsets::CGInputControlGetActive); + + typedef void(__thiscall *SetReleaseActionT)(uint32_t, uint32_t); + auto SetReleaseAction = reinterpret_cast(Offsets::CGInputControlSetReleaseAction); + SetReleaseAction(activeControl, input); + } + + void SetControlBit(uint32_t input) { + uint32_t activeControl = *reinterpret_cast(Offsets::CGInputControlGetActive); + auto *LastHardwareAction = reinterpret_cast(Offsets::LastHardwareAction); + + typedef void(__thiscall *SetControlBitT)(uint32_t, uint32_t, uint32_t, uintptr_t *, int); + auto SetControlBit = reinterpret_cast(Offsets::CGInputControlSetControlBit); + SetControlBit(activeControl, 2, input, LastHardwareAction, 0); + } + + void CameraOrSelectOrMoveStart() { + SetReleaseAction(1); + SetControlBit(1); + } + + void CameraOrSelectOrMoveStop() { + SetControlBit(0); + } + + + uint32_t Spell_C_HandleTerrainClickHook(hadesmem::PatchDetourBase *detour, game::CTerrainClickEvent *event) { + if(!gCastData.targetingSpellQueued){ + auto const handleTerrainClick = detour->GetTrampolineT(); + return handleTerrainClick(event); + } + return 0; + } + + float Spell_C_GetSpellRadiusHook(hadesmem::PatchDetourBase *detour) { + auto originalSpellId = *reinterpret_cast(Offsets::VisualSpellId); + if(gCastData.targetingSpellQueued){ + // switch out visual spellid to the queued spell so that we get the correct radius + *reinterpret_cast(Offsets::VisualSpellId) = gCastData.targetingSpellId; + } + + auto const getSpellRadius = detour->GetTrampolineT(); + auto radius = getSpellRadius(); + + if(gCastData.targetingSpellQueued){ + // switch back to originalSpellId + *reinterpret_cast(Offsets::VisualSpellId) = originalSpellId; + } + + return radius; + } + + void EnableSpellTargeting(uint32_t *casterUnit, const game::SpellRec *spell, uintptr_t *unk, uint64_t guid) { + // set s_needtargets 00cecac0 + auto s_needtargets = reinterpret_cast(Offsets::SpellNeedsTargets); + *s_needtargets = spell->Targets; + + auto const CursorSetCursorMode = reinterpret_cast(Offsets::CursorSetCursorMode); + CursorSetCursorMode(1); // CAST_CURSOR + auto const CursorModelSetSequence = reinterpret_cast(Offsets::CursorModelSetSequence); + CursorModelSetSequence(22); // CAST_CURSOR + } + + bool Spell_C_TargetSpellHook(hadesmem::PatchDetourBase *detour, + uint32_t *player, + uint32_t *spellId, + uint32_t unk3, + float unk4) { + auto const spellTarget = detour->GetTrampolineT(); + auto result = spellTarget(player, spellId, unk3, unk4); + + if (!result) { + auto const spellName = game::GetSpellName(*spellId); + auto const spell = game::GetSpellInfo(*spellId); + + if (spell->Targets == game::SpellCastTargetFlags::TARGET_FLAG_DEST_LOCATION && + spell->Effect[0] != game::SPELL_EFFECT_SUMMON_GUARDIAN) { + // if quickcast is on instantly trigger all casts + // otherwise if this is a queued cast, trigger it instant cast + if (gUserSettings.quickcastTargetingSpells || + (gUserSettings.queueTargetingSpells && gCastData.castingQueuedSpell)) { + DEBUG_LOG("Quickcasting terrain spell " << spellName + << " quickcast: " + << gUserSettings.quickcastTargetingSpells + << " queuetrigger: " << gCastData.castingQueuedSpell); + + // store the current target + auto const targetGuid = game::GetCurrentTargetGuid(); + + CameraOrSelectOrMoveStart(); + CameraOrSelectOrMoveStop(); + + // check if target changed + if (targetGuid != game::GetCurrentTargetGuid()) { + DEBUG_LOG("Target changed during quick cast, restoring previous target " << targetGuid); + auto const targetUnit = reinterpret_cast(Offsets::CGGameUI_Target); + targetUnit(targetGuid); + } + } + } + } + return result; + } + uint32_t GetChannelBaseDuration(const game::SpellRec *spell) { auto const duration = game::GetDurationObject(spell->DurationIndex); if (duration == nullptr) { @@ -117,6 +222,8 @@ namespace Nampower { DEBUG_LOG("Triggering queued non gcd cast of " << game::GetSpellName(nonGcdCastParams.spellId)); gCastData.castingQueuedSpell = true; + gCastData.targetingSpellQueued = false; // make sure this is off so SendCast is allowed + gCastData.targetingSpellId = 0; gCastData.numRetries = nonGcdCastParams.numRetries; Spell_C_CastSpellHook(castSpellDetour, nonGcdCastParams.casterUnit, nonGcdCastParams.spellId, nonGcdCastParams.item, nonGcdCastParams.guid); @@ -128,6 +235,8 @@ namespace Nampower { TriggerSpellQueuedEvent(NON_GCD_QUEUE_POPPED, nonGcdCastParams.spellId); gCastData.nonGcdSpellQueued = !gNonGcdCastQueue.isEmpty(); gCastData.castingQueuedSpell = false; + gCastData.targetingSpellQueued = false; + gCastData.targetingSpellId = 0; gCastData.numRetries = 0; } } @@ -137,6 +246,8 @@ namespace Nampower { if (gLastNormalCastParams.spellId > 0) { DEBUG_LOG("Triggering queued cast of " << game::GetSpellName(gLastNormalCastParams.spellId)); gCastData.castingQueuedSpell = true; + gCastData.targetingSpellQueued = false; // make sure this is off so SendCast is allowed + gCastData.targetingSpellId = 0; gCastData.numRetries = gLastNormalCastParams.numRetries; Spell_C_CastSpellHook(castSpellDetour, gLastNormalCastParams.casterUnit, gLastNormalCastParams.spellId, gLastNormalCastParams.item, gLastNormalCastParams.guid); @@ -148,6 +259,8 @@ namespace Nampower { TriggerSpellQueuedEvent(NORMAL_QUEUE_POPPED, gLastNormalCastParams.spellId); gCastData.normalSpellQueued = false; gCastData.castingQueuedSpell = false; + gCastData.targetingSpellQueued = false; + gCastData.targetingSpellId = 0; gCastData.numRetries = 0; } } @@ -377,18 +490,28 @@ namespace Nampower { if (gUserSettings.queueTargetingSpells) { if (castTime > 0 && inSpellQueueWindow) { if (gUserSettings.queueCastTimeSpells) { + // call EnableSpellTargeting to set s_needTargets and trigger the targeting indicator + EnableSpellTargeting(casterUnit, spell, nullptr, guid); + DEBUG_LOG("Queuing targeting for after cast/gcd: " << remainingCD << "ms " << spellName); TriggerSpellQueuedEvent(NORMAL_QUEUED, spellId); gCastData.normalSpellQueued = true; + gCastData.targetingSpellQueued = true; + gCastData.targetingSpellId = spellId; return false; } } else if (inSpellQueueWindow) { if (gUserSettings.queueInstantSpells) { if (spellOnGcd) { + // call EnableSpellTargeting to set s_needTargets and trigger the targeting indicator + EnableSpellTargeting(casterUnit, spell, nullptr, guid); + DEBUG_LOG("Queuing instant cast targeting for after cast/gcd: " << remainingCD << "ms " << spellName); TriggerSpellQueuedEvent(NORMAL_QUEUED, spellId); gCastData.normalSpellQueued = true; + gCastData.targetingSpellQueued = true; + gCastData.targetingSpellId = spellId; return false; } else if (remainingEffectiveCastTime > 0) { auto castParams = gNonGcdCastQueue.findSpellId(spellId); @@ -397,6 +520,9 @@ namespace Nampower { castParams->guid = guid; return false; } else { + // call EnableSpellTargeting to set s_needTargets and trigger the targeting indicator + EnableSpellTargeting(casterUnit, spell, nullptr, guid); + DEBUG_LOG("Queuing instant cast non GCD targeting for after cast/gcd: " << remainingEffectiveCastTime << "ms " << spellName << " gcd category " @@ -410,6 +536,8 @@ namespace Nampower { false}, gUserSettings.replaceMatchingNonGcdCategory); TriggerSpellQueuedEvent(NON_GCD_QUEUED, spellId); gCastData.nonGcdSpellQueued = true; + gCastData.targetingSpellQueued = true; + gCastData.targetingSpellId = spellId; return false; } } @@ -640,74 +768,6 @@ namespace Nampower { } } - void SetReleaseAction(uint32_t input) { - uint32_t activeControl = *reinterpret_cast(Offsets::CGInputControlGetActive); - - typedef void(__thiscall *SetReleaseActionT)(uint32_t, uint32_t); - auto SetReleaseAction = reinterpret_cast(Offsets::CGInputControlSetReleaseAction); - SetReleaseAction(activeControl, input); - } - - void SetControlBit(uint32_t input) { - uint32_t activeControl = *reinterpret_cast(Offsets::CGInputControlGetActive); - auto *LastHardwareAction = reinterpret_cast(Offsets::LastHardwareAction); - - typedef void(__thiscall *SetControlBitT)(uint32_t, uint32_t, uint32_t, uintptr_t *, int); - auto SetControlBit = reinterpret_cast(Offsets::CGInputControlSetControlBit); - SetControlBit(activeControl, 2, input, LastHardwareAction, 0); - } - - - void CameraOrSelectOrMoveStart() { - SetReleaseAction(1); - SetControlBit(1); - } - - void CameraOrSelectOrMoveStop() { - SetControlBit(0); - } - - bool Spell_C_TargetSpellHook(hadesmem::PatchDetourBase *detour, - uint32_t *player, - uint32_t *spellId, - uint32_t unk3, - float unk4) { - auto const spellTarget = detour->GetTrampolineT(); - auto result = spellTarget(player, spellId, unk3, unk4); - - if (!result) { - auto const spellName = game::GetSpellName(*spellId); - auto const spell = game::GetSpellInfo(*spellId); - - if (spell->Targets == game::SpellTarget::TARGET_LOCATION_UNIT_POSITION && - spell->Effect[0] != game::SPELL_EFFECT_SUMMON_GUARDIAN) { - // if quickcast is on instantly trigger all casts - // otherwise if this is a queued cast, trigger it instant cast - if (gUserSettings.quickcastTargetingSpells || - (gUserSettings.queueTargetingSpells && gCastData.castingQueuedSpell)) { - DEBUG_LOG("Quickcasting terrain spell " << spellName - << " quickcast: " - << gUserSettings.quickcastTargetingSpells - << " queuetrigger: " << gCastData.castingQueuedSpell); - - // store the current target - auto const targetGuid = game::GetCurrentTargetGuid(); - - CameraOrSelectOrMoveStart(); - CameraOrSelectOrMoveStop(); - - // check if target changed - if (targetGuid != game::GetCurrentTargetGuid()) { - DEBUG_LOG("Target changed during quick cast, restoring previous target " << targetGuid); - auto const targetUnit = reinterpret_cast(Offsets::CGGameUI_Target); - targetUnit(targetGuid); - } - } - } - } - return result; - } - void CancelSpellHook(hadesmem::PatchDetourBase *detour, bool failed, bool notifyServer, game::SpellCastResult reason) { @@ -724,11 +784,10 @@ namespace Nampower { } void SendCastHook(hadesmem::PatchDetourBase *detour, game::SpellCast *cast, char unk) { - auto const sendCast = detour->GetTrampolineT(); - sendCast(cast, unk); + auto const sendCast = detour->GetTrampolineT(); + sendCast(cast, unk); - auto const spell = game::GetSpellInfo(cast->spellId); - BeginCast(gCastData.attemptedCastTimeMs, spell, cast); + auto const spell = game::GetSpellInfo(cast->spellId); + BeginCast(gCastData.attemptedCastTimeMs, spell, cast); } - } \ No newline at end of file diff --git a/nampower/spellcast.hpp b/nampower/spellcast.hpp index 88e4048..6438793 100644 --- a/nampower/spellcast.hpp +++ b/nampower/spellcast.hpp @@ -32,5 +32,9 @@ namespace Nampower { uint32_t unk3, float unk4); + uint32_t Spell_C_HandleTerrainClickHook(hadesmem::PatchDetourBase *detour, game::CTerrainClickEvent *event); + + float Spell_C_GetSpellRadiusHook(hadesmem::PatchDetourBase *detour); + void SendCastHook(hadesmem::PatchDetourBase *detour, game::SpellCast *cast, char unk); } \ No newline at end of file diff --git a/nampower/types.h b/nampower/types.h index 237b992..f601c3e 100644 --- a/nampower/types.h +++ b/nampower/types.h @@ -120,6 +120,9 @@ struct CastData { bool normalSpellQueued; bool nonGcdSpellQueued; + bool targetingSpellQueued; + + uint32_t targetingSpellId; bool castingQueuedSpell; uint32_t numRetries;