From 6afb3377704aeaa31bfa9eb6bea3bf84dc101afe Mon Sep 17 00:00:00 2001 From: avitasia Date: Tue, 9 Dec 2025 13:30:01 -0800 Subject: [PATCH] add alternate quickcast mode --- README.md | 1 + nampower/helper.cpp | 12 +- nampower/main.cpp | 17 +++ nampower/main.hpp | 3 +- nampower/offsets.hpp | 2 + nampower/scripts.cpp | 4 +- nampower/spellcast.cpp | 302 +++++++++++++++++++++++++---------------- nampower/spellcast.hpp | 2 + nampower/types.h | 1 + 9 files changed, 216 insertions(+), 128 deletions(-) diff --git a/README.md b/README.md index 3eeb78c..ae60580 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,7 @@ SET NP_TargetingQueueWindowMs "1000" - `NP_RetryServerRejectedSpells` - Whether to retry spells that are rejected by the server for these reasons: SPELL_FAILED_ITEM_NOT_READY, SPELL_FAILED_NOT_READY, SPELL_FAILED_SPELL_IN_PROGRESS. 0 to disable, 1 to enable. Default is 1. - `NP_QuickcastTargetingSpells` - Whether to enable quick casting for ALL spells with terrain targeting. This will cause the spell to instantly cast on your cursor without waiting for you to confirm the targeting circle. Queuing targeting spells will use quickcasting regardless of this value. 0 to disable, 1 to enable. Default is 0. +- `NP_QuickcastOnDoubleCast` - Whether to allow casting targeting spells by attempting to cast them twice in quick succession, as opposed to the default client behavior which cancels the targeting indicator on double cast. This provides an alternative way to quickcast targeting spells without enabling it for all targeting spells. 0 to disable, 1 to enable. Default is 0. - `NP_ReplaceMatchingNonGcdCategory` - Whether to replace any queued non gcd spell when a new non gcd spell with the same StartRecoveryCategory is cast (more explanation below). 0 to disable, 1 to enable. Default is 0. - `NP_OptimizeBufferUsingPacketTimings` - Whether to attempt to optimize your buffer using your latency and server packet timings (more explanation below). 0 to disable, 1 to enable. Default is 0. diff --git a/nampower/helper.cpp b/nampower/helper.cpp index d52fef0..a3bba89 100644 --- a/nampower/helper.cpp +++ b/nampower/helper.cpp @@ -38,9 +38,9 @@ namespace Nampower { uint32_t spellId = 0; if (type == 0) { - spellId = *reinterpret_cast(uint32_t(Offsets::CGSpellBook_mKnownSpells) + slot * 4); + spellId = *reinterpret_cast(static_cast(Offsets::CGSpellBook_mKnownSpells) + slot * 4); } else if (type == 1) { - spellId = *reinterpret_cast(uint32_t(Offsets::CGSpellBook_mKnownPetSpells) + slot * 4); + spellId = *reinterpret_cast(static_cast(Offsets::CGSpellBook_mKnownPetSpells) + slot * 4); } else { return false; } @@ -94,10 +94,10 @@ namespace Nampower { uint32_t spellId = 0; if (spellSlot < 1024) { if (bookType == 0) { - spellId = *reinterpret_cast(uint32_t(Offsets::CGSpellBook_mKnownSpells) + + spellId = *reinterpret_cast(static_cast(Offsets::CGSpellBook_mKnownSpells) + spellSlot * 4); } else { - spellId = *reinterpret_cast(uint32_t(Offsets::CGSpellBook_mKnownPetSpells) + + spellId = *reinterpret_cast(static_cast(Offsets::CGSpellBook_mKnownPetSpells) + spellSlot * 4); } } @@ -115,9 +115,9 @@ namespace Nampower { } uint32_t slotSpellId = 0; if (bookType == 0) { - slotSpellId = *reinterpret_cast(uint32_t(Offsets::CGSpellBook_mKnownSpells) + slot * 4); + slotSpellId = *reinterpret_cast(static_cast(Offsets::CGSpellBook_mKnownSpells) + slot * 4); } else { - slotSpellId = *reinterpret_cast(uint32_t(Offsets::CGSpellBook_mKnownPetSpells) + slot * 4); + slotSpellId = *reinterpret_cast(static_cast(Offsets::CGSpellBook_mKnownPetSpells) + slot * 4); } return slotSpellId == expectedId; }; diff --git a/nampower/main.cpp b/nampower/main.cpp index 4ef4f92..a9fc474 100644 --- a/nampower/main.cpp +++ b/nampower/main.cpp @@ -90,6 +90,7 @@ namespace Nampower { std::unique_ptr> gSetEventCountDetour; std::unique_ptr> gSetCVarDetour; + std::unique_ptr> gCGSpellBook_CastSpellDetour; std::unique_ptr> gCastDetour; std::unique_ptr> gSendCastDetour; std::unique_ptr> gCancelSpellDetour; @@ -613,6 +614,10 @@ namespace Nampower { gUserSettings.doubleCastToEndChannelEarly = atoi(value) != 0; DEBUG_LOG("Set NP_DoubleCastToEndChannelEarly to " << gUserSettings.doubleCastToEndChannelEarly); + } else if (strcmp(cvar, "NP_QuickcastOnDoubleCast") == 0) { + gUserSettings.quickcastOnDoubleCast = atoi(value) != 0; + DEBUG_LOG("Set NP_QuickcastOnDoubleCast to " << gUserSettings.quickcastOnDoubleCast); + } else if (strcmp(cvar, "NP_SpamProtectionEnabled") == 0) { gUserSettings.spamProtectionEnabled = atoi(value) != 0; DEBUG_LOG("Set NP_SpamProtectionEnabled to " << gUserSettings.spamProtectionEnabled); @@ -757,6 +762,7 @@ namespace Nampower { gUserSettings.preventRightClickPvPAttack = true; gUserSettings.doubleCastToEndChannelEarly = false; + gUserSettings.quickcastOnDoubleCast = false; gUserSettings.spamProtectionEnabled = true; @@ -1002,6 +1008,16 @@ namespace Nampower { 0, // unk2 0); // unk3 + char NP_QuickcastOnDoubleCast[] = "NP_QuickcastOnDoubleCast"; + CVarRegister(NP_QuickcastOnDoubleCast, // name + nullptr, // help + 0, // unk1 + gUserSettings.quickcastOnDoubleCast ? defaultTrue : defaultFalse, // default value address + nullptr, // callback + 1, // category + 0, // unk2 + 0); // unk3 + char NP_SpamProtectionEnabled[] = "NP_SpamProtectionEnabled"; CVarRegister(NP_SpamProtectionEnabled, // name nullptr, // help @@ -1086,6 +1102,7 @@ namespace Nampower { const hadesmem::Process process(::GetCurrentProcessId()); gSetCVarDetour = createHook(process, Offsets::Script_SetCVar, &Script_SetCVarHook); + gCGSpellBook_CastSpellDetour = createHook(process, Offsets::CGSpellBook_CastSpell, &CGSpellBook_CastSpellHook); gCastDetour = createHook(process, Offsets::Spell_C_CastSpell, &Spell_C_CastSpellHook); gSendCastDetour = createHook(process, Offsets::SendCast, &SendCastHook); gCancelSpellDetour = createHook(process, Offsets::CancelSpell, &CancelSpellHook); diff --git a/nampower/main.hpp b/nampower/main.hpp index b5962c3..5ba7aef 100644 --- a/nampower/main.hpp +++ b/nampower/main.hpp @@ -30,7 +30,7 @@ namespace Nampower { constexpr uint32_t BUFFER_DECREASE_FREQUENCY = 10000; // time in ms between changes to lower buffer constexpr uint32_t MAJOR_VERSION = 2; - constexpr uint32_t MINOR_VERSION = 14; + constexpr uint32_t MINOR_VERSION = 15; constexpr uint32_t PATCH_VERSION = 0; constexpr int32_t LUA_REGISTRYINDEX = -10000; @@ -68,6 +68,7 @@ namespace Nampower { using RangeCheckSelectedT = bool (__fastcall *)(uintptr_t *playerUnit, const game::SpellRec *, std::uint64_t targetGuid, char ignoreErrors); + using CGSpellBook_CastSpellT = void (__fastcall *)(uint32_t spellSlot, int bookType, uint64_t target); using CastSpellT = bool (__fastcall *)(uintptr_t *playerUnit, uint32_t spellId, uintptr_t *item, std::uint64_t targetGuid); using SendCastT = void (__fastcall *)(game::SpellCast *, char unk); diff --git a/nampower/offsets.hpp b/nampower/offsets.hpp index 8caa37f..3c02dc3 100644 --- a/nampower/offsets.hpp +++ b/nampower/offsets.hpp @@ -43,6 +43,7 @@ enum class Offsets : std::uint32_t { Language = 0xC0E080, SpellDb = 0xC0D780, CursorMode = 0xBE2C4C, + CursorType = 0X00BE2C2C, SpellIsTargeting = 0xCECAC0, CastingItemIdPtr = 0X00CECAB0, CastingSpellId = 0xCECA88, @@ -61,6 +62,7 @@ enum class Offsets : std::uint32_t { CGSpellBook_mKnownSpells = 0xB700F0, CGSpellBook_mKnownPetSpells = 0XB6F098, + CGSpellBook_CastSpell = 0x004b3300, IsSpellInRangeOfUnit = 0X004E56F0, CancelSpell = 0x6E4940, diff --git a/nampower/scripts.cpp b/nampower/scripts.cpp index 48a8cb3..868fe7e 100644 --- a/nampower/scripts.cpp +++ b/nampower/scripts.cpp @@ -314,10 +314,10 @@ namespace Nampower { if (spellSlot > 0 && spellSlot < 1024) { uint32_t spellId = 0; if (bookType == 0) { - spellId = *reinterpret_cast(uint32_t(Offsets::CGSpellBook_mKnownSpells) + + spellId = *reinterpret_cast(static_cast(Offsets::CGSpellBook_mKnownSpells) + spellSlot * 4); } else { - spellId = *reinterpret_cast(uint32_t(Offsets::CGSpellBook_mKnownPetSpells) + + spellId = *reinterpret_cast(static_cast(Offsets::CGSpellBook_mKnownPetSpells) + spellSlot * 4); } lua_pushnumber(luaState, spellId); diff --git a/nampower/spellcast.cpp b/nampower/spellcast.cpp index e17d407..9aa690c 100644 --- a/nampower/spellcast.cpp +++ b/nampower/spellcast.cpp @@ -8,12 +8,12 @@ #include "logging.hpp" namespace Nampower { - auto const APPLY_BUFFER_TO_GCD = false; // gcd issue seems fixed for now + 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); + typedef void (__thiscall *SetReleaseActionT)(uint32_t, uint32_t); auto SetReleaseAction = reinterpret_cast(Offsets::CGInputControlSetReleaseAction); SetReleaseAction(activeControl, input); } @@ -22,7 +22,7 @@ namespace Nampower { 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); + 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); } @@ -43,45 +43,47 @@ namespace Nampower { auto const CursorSetCursorMode = reinterpret_cast(Offsets::CursorSetCursorMode); CursorSetCursorMode(2); // CAST_CURSOR - auto const CursorModelSetSequence = reinterpret_cast(Offsets::CursorModelSetSequence); + auto const CursorModelSetSequence = reinterpret_cast( + Offsets::CursorModelSetSequence); CursorModelSetSequence(2); // CAST_CURSOR } uint32_t Spell_C_HandleTerrainClickHook(hadesmem::PatchDetourBase *detour, game::CTerrainClickEvent *event) { - if(!gCastData.targetingSpellQueued){ + if (!gCastData.targetingSpellQueued) { auto const handleTerrainClick = detour->GetTrampolineT(); return handleTerrainClick(event); } return 0; } - void CGWorldFrame_OnLayerTrackTerrainHook(hadesmem::PatchDetourBase *detour, void *thisptr, int dummy_edx, int param_1) { - auto originalSpellId = *reinterpret_cast(Offsets::VisualSpellId); - auto originalCasterGuid = *reinterpret_cast(Offsets::CasterGuid); + void CGWorldFrame_OnLayerTrackTerrainHook(hadesmem::PatchDetourBase *detour, void *thisptr, int dummy_edx, + int param_1) { + auto originalSpellId = *reinterpret_cast(Offsets::VisualSpellId); + auto originalCasterGuid = *reinterpret_cast(Offsets::CasterGuid); - if(gCastData.targetingSpellQueued){ + if (gCastData.targetingSpellQueued) { // switch out visual spellid to the queued spell so that we get the correct radius/range - *reinterpret_cast(Offsets::VisualSpellId) = gCastData.targetingSpellId; + *reinterpret_cast(Offsets::VisualSpellId) = gCastData.targetingSpellId; // set casterguid to active player so range checks work correctly - *reinterpret_cast(Offsets::CasterGuid) = game::ClntObjMgrGetActivePlayerGuid(); + *reinterpret_cast(Offsets::CasterGuid) = game::ClntObjMgrGetActivePlayerGuid(); } auto const onLayerTrackTerrain = detour->GetTrampolineT(); onLayerTrackTerrain(thisptr, dummy_edx, param_1); - if(gCastData.targetingSpellQueued){ + if (gCastData.targetingSpellQueued) { // switch back to originalSpellId - *reinterpret_cast(Offsets::VisualSpellId) = originalSpellId; + *reinterpret_cast(Offsets::VisualSpellId) = originalSpellId; // restore original caster guid - *reinterpret_cast(Offsets::CasterGuid) = originalCasterGuid; + *reinterpret_cast(Offsets::CasterGuid) = originalCasterGuid; } } float Spell_C_GetSpellRadiusHook(hadesmem::PatchDetourBase *detour) { - auto originalSpellId = *reinterpret_cast(Offsets::VisualSpellId); - if(gCastData.targetingSpellQueued){ + 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; + *reinterpret_cast(Offsets::VisualSpellId) = gCastData.targetingSpellId; } auto const getSpellRadius = detour->GetTrampolineT(); @@ -90,6 +92,21 @@ namespace Nampower { return radius; } + void TriggerQuickcast() { + // 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); + } + } + bool Spell_C_TargetSpellHook(hadesmem::PatchDetourBase *detour, uint32_t *player, uint32_t *spellId, @@ -109,22 +126,10 @@ namespace Nampower { 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); - } + << " quickcast: " + << gUserSettings.quickcastTargetingSpells + << " queuetrigger: " << gCastData.castingQueuedSpell); + TriggerQuickcast(); } } } @@ -143,7 +148,7 @@ namespace Nampower { void BeginCast(uint32_t castTime, const game::SpellRec *spell, const game::SpellCast *cast) { if (cast != nullptr && cast->itemTarget == 0 && cast->caster != game::ClntObjMgrGetActivePlayerGuid()) { DEBUG_LOG("Ignoring non active player begin cast of spell " << game::GetSpellName(cast->spellId) << " " - << cast->spellId); + << cast->spellId); return; } @@ -195,24 +200,25 @@ namespace Nampower { gCastData.gcdEndMs = currentTime + gcdTime + bufferMs; DEBUG_LOG("BeginCast #" << lastCastId - << " " << game::GetSpellName(spell->Id) - << "(" << spell->Id << ")" - << " cast time: " << castTime - << " buffer: " << bufferMs - << " Gcd: " << gcdTime - << " latency: " << GetLatencyMs() - << " time since last cast " << currentTime - gLastCastData.startTimeMs); + << " " << game::GetSpellName(spell->Id) + << "(" << spell->Id << ")" + << " cast time: " << castTime + << " buffer: " << bufferMs + << " Gcd: " << gcdTime + << " latency: " << GetLatencyMs() + << " time since last cast " << currentTime - gLastCastData.startTimeMs); } else { gCastData.delayEndMs = currentTime + - gUserSettings.nonGcdBufferTimeMs; // set small "cast time" to avoid attempting next spell too fast + gUserSettings.nonGcdBufferTimeMs; + // set small "cast time" to avoid attempting next spell too fast DEBUG_LOG("BeginCast #" << lastCastId - << " " << game::GetSpellName(spell->Id) - << "(" << spell->Id << ")" - << " cast time: " << castTime - << " buffer: " << bufferMs - << " NO Gcd" - << " latency: " << GetLatencyMs() - << " time since last cast " << currentTime - gLastCastData.startTimeMs); + << " " << game::GetSpellName(spell->Id) + << "(" << spell->Id << ")" + << " cast time: " << castTime + << " buffer: " << bufferMs + << " NO Gcd" + << " latency: " << GetLatencyMs() + << " time since last cast " << currentTime - gLastCastData.startTimeMs); } gCastData.castEndMs = castTime ? currentTime + castTime + bufferMs : 0; @@ -313,10 +319,10 @@ namespace Nampower { void TriggerSpellQueuedEvent(QueueEvents queueEventCode, uint32_t spellId) { ((int (__cdecl *)(int, char *, uint32_t, uint32_t)) Offsets::SignalEventParam)( - game::SPELL_QUEUE_EVENT, // SPELL_QUEUE_EVENT event we are adding - (char *) Offsets::IntIntParamFormat, - queueEventCode, - spellId); + game::SPELL_QUEUE_EVENT, // SPELL_QUEUE_EVENT event we are adding + (char *) Offsets::IntIntParamFormat, + queueEventCode, + spellId); } void @@ -326,13 +332,13 @@ namespace Nampower { std::snprintf(guidStr, 21, "0x%016llX", static_cast(guid)); ((int (__cdecl *)(int, char *, uint32_t, uint32_t, uint32_t, char *, uint32_t)) Offsets::SignalEventParam)( - game::SPELL_CAST_EVENT, // SPELL_CAST_EVENT event we are adding - format, - result, - spellId, - castType, - guidStr, - itemId); + game::SPELL_CAST_EVENT, // SPELL_CAST_EVENT event we are adding + format, + result, + spellId, + castType, + guidStr, + itemId); delete[] guidStr; } @@ -347,8 +353,53 @@ namespace Nampower { } } + void CGSpellBook_CastSpellHook(hadesmem::PatchDetourBase *detour, uint32_t spellSlot, int bookType, + uint64_t target) { + auto const cgSpellBookCastSpell = detour->GetTrampolineT(); + + // check for double cast to trigger quickcast on targeting spells + if (gUserSettings.quickcastOnDoubleCast) { + // check if needs_targets set + auto s_needTargets = *reinterpret_cast(Offsets::SpellNeedsTargets); + if (s_needTargets & game::SpellCastTargetFlags::TARGET_FLAG_SOURCE_LOCATION || + s_needTargets & game::SpellCastTargetFlags::TARGET_FLAG_DEST_LOCATION) { + + uint32_t spellId = 0; + if (bookType == 0) { + spellId = *reinterpret_cast( + static_cast(Offsets::CGSpellBook_mKnownSpells) + spellSlot * 4); + } else if (bookType == 1) { + spellId = *reinterpret_cast( + static_cast(Offsets::CGSpellBook_mKnownPetSpells) + spellSlot * 4); + } else { + // call original + cgSpellBookCastSpell(spellSlot, bookType, target); + return; + } + + if (spellId > 0) { + // check if aoe spell targeting is active + if (gLastCastData.attemptSpellId == spellId){ + auto spell = game::GetSpellInfo(spellId); + auto spellName = game::GetSpellName(spellId); + + // don't mess with summon guardian + if (spell->Effect[0] != game::SPELL_EFFECT_SUMMON_GUARDIAN) { + DEBUG_LOG("Double cast detected for targeting spell " << spellName << ", triggering quickcast"); + TriggerQuickcast(); + return; // don't cast again + } + } + } + } + + cgSpellBookCastSpell(spellSlot, bookType, target); + } + } + bool - Spell_C_CastSpellHook(hadesmem::PatchDetourBase *detour, uint32_t *casterUnit, uint32_t spellId, uintptr_t *item, + Spell_C_CastSpellHook(hadesmem::PatchDetourBase *detour, uint32_t *casterUnit, uint32_t spellId, + uintptr_t *item, std::uint64_t guid) { // save the detour to allow quickly calling this hook castSpellDetour = detour; @@ -417,8 +468,8 @@ namespace Nampower { } DEBUG_LOG("Attempt cast " << spellName << " item " << item << " on guid " << guid << " target " - << currentTargetGuid - << ", time since last cast " << currentTime - gLastCastData.startTimeMs); + << currentTargetGuid + << ", time since last cast " << currentTime - gLastCastData.startTimeMs); // clear cooldown queue if we are casting a spell if (spellOnGcd && gCastData.cooldownNormalSpellQueued) { @@ -437,13 +488,15 @@ namespace Nampower { castTime, currentTime, ON_SWING, 0); - gCastHistory.pushFront({gNextCastId, casterUnit, spellId, item, guid, - spell->StartRecoveryCategory, - castTime, - currentTime, - ON_SWING, - gCastData.numRetries, - CastResult::WAITING_FOR_CAST}); + gCastHistory.pushFront({ + gNextCastId, casterUnit, spellId, item, guid, + spell->StartRecoveryCategory, + castTime, + currentTime, + ON_SWING, + gCastData.numRetries, + CastResult::WAITING_FOR_CAST + }); gNextCastId++; // try to cast the spell @@ -511,7 +564,8 @@ namespace Nampower { // call EnableSpellTargeting to set s_needTargets and trigger the targeting indicator EnableSpellTargeting(spell); - DEBUG_LOG("Queuing targeting for after cast/gcd: " << remainingCD << "ms " << spellName); + DEBUG_LOG( + "Queuing targeting for after cast/gcd: " << remainingCD << "ms " << spellName); TriggerSpellQueuedEvent(NORMAL_QUEUED, spellId); gCastData.normalSpellQueued = true; gCastData.targetingSpellQueued = true; @@ -524,8 +578,9 @@ namespace Nampower { // call EnableSpellTargeting to set s_needTargets and trigger the targeting indicator EnableSpellTargeting(spell); - DEBUG_LOG("Queuing instant cast targeting for after cast/gcd: " << remainingCD << "ms " - << spellName); + DEBUG_LOG( + "Queuing instant cast targeting for after cast/gcd: " << remainingCD << "ms " + << spellName); TriggerSpellQueuedEvent(NORMAL_QUEUED, spellId); gCastData.normalSpellQueued = true; gCastData.targetingSpellQueued = true; @@ -542,16 +597,18 @@ namespace Nampower { EnableSpellTargeting(spell); DEBUG_LOG("Queuing instant cast non GCD targeting for after cast/gcd: " - << remainingEffectiveCastTime << "ms " << spellName - << " gcd category " - << spell->StartRecoveryCategory); + << remainingEffectiveCastTime << "ms " << spellName + << " gcd category " + << spell->StartRecoveryCategory); - gNonGcdCastQueue.push({0, casterUnit, spellId, item, guid, - spell->StartRecoveryCategory, - castTime, - 0, - ::NON_GCD, - false}, gUserSettings.replaceMatchingNonGcdCategory); + gNonGcdCastQueue.push({ + 0, casterUnit, spellId, item, guid, + spell->StartRecoveryCategory, + castTime, + 0, + ::NON_GCD, + false + }, gUserSettings.replaceMatchingNonGcdCategory); TriggerSpellQueuedEvent(NON_GCD_QUEUED, spellId); gCastData.nonGcdSpellQueued = true; gCastData.targetingSpellQueued = true; @@ -577,15 +634,17 @@ namespace Nampower { return false; } else { DEBUG_LOG("Queuing non GCD for after cast/gcd: " - << remainingEffectiveCastTime << "ms " << spellName << " gcd category " - << spell->StartRecoveryCategory); + << remainingEffectiveCastTime << "ms " << spellName << " gcd category " + << spell->StartRecoveryCategory); - gNonGcdCastQueue.push({0, casterUnit, spellId, item, guid, - spell->StartRecoveryCategory, - castTime, - 0, - ::NON_GCD, - false}, gUserSettings.replaceMatchingNonGcdCategory); + gNonGcdCastQueue.push({ + 0, casterUnit, spellId, item, guid, + spell->StartRecoveryCategory, + castTime, + 0, + ::NON_GCD, + false + }, gUserSettings.replaceMatchingNonGcdCategory); TriggerSpellQueuedEvent(NON_GCD_QUEUED, spellId); gCastData.nonGcdSpellQueued = true; return false; @@ -595,14 +654,14 @@ namespace Nampower { } else if (inSpellQueueWindow) { if ((spellIsChanneling && gUserSettings.queueChannelingSpells) || (!spellIsChanneling && gUserSettings.queueInstantSpells)) { - auto desc = "instant cast"; if (spellIsChanneling) { desc = "channeling"; } if (spellOnGcd) { - DEBUG_LOG("Queuing " << desc << " for after cast/gcd: " << remainingCD << "ms " << spellName); + DEBUG_LOG( + "Queuing " << desc << " for after cast/gcd: " << remainingCD << "ms " << spellName); TriggerSpellQueuedEvent(NORMAL_QUEUED, spellId); gCastData.normalSpellQueued = true; return false; @@ -614,15 +673,17 @@ namespace Nampower { return false; } else { DEBUG_LOG("Queuing " << desc << " non GCD for after cast/gcd: " - << remainingEffectiveCastTime << "ms " << spellName << " gcd category " - << spell->StartRecoveryCategory); + << remainingEffectiveCastTime << "ms " << spellName << " gcd category " + << spell->StartRecoveryCategory); - gNonGcdCastQueue.push({0, casterUnit, spellId, item, guid, - spell->StartRecoveryCategory, - castTime, - 0, - ::NON_GCD, - false}, gUserSettings.replaceMatchingNonGcdCategory); + gNonGcdCastQueue.push({ + 0, casterUnit, spellId, item, guid, + spell->StartRecoveryCategory, + castTime, + 0, + ::NON_GCD, + false + }, gUserSettings.replaceMatchingNonGcdCategory); TriggerSpellQueuedEvent(NON_GCD_QUEUED, spellId); gCastData.nonGcdSpellQueued = true; return false; @@ -660,7 +721,7 @@ namespace Nampower { if (castParams && currentTime - castParams->castStartTimeMs < 500) { DEBUG_LOG("Ignoring " << spellName - << " cast still waiting for server result for the same spell"); + << " cast still waiting for server result for the same spell"); return false; } else { castParams = gCastHistory.findNewestSuccessfulSpellId(spellId); @@ -668,7 +729,7 @@ namespace Nampower { castParams->guid == guid && currentTime - castParams->castStartTimeMs < 500) { DEBUG_LOG("Ignoring " << spellName - << " cast recently succeeded for the same spell and target"); + << " cast recently succeeded for the same spell and target"); return false; } } @@ -696,13 +757,15 @@ namespace Nampower { castType = CastType::NON_GCD; } - gCastHistory.pushFront({gNextCastId, casterUnit, spellId, item, guid, - spell->StartRecoveryCategory, - castTime, - currentTime, - castType, - gCastData.numRetries, - CastResult::WAITING_FOR_CAST}); + gCastHistory.pushFront({ + gNextCastId, casterUnit, spellId, item, guid, + spell->StartRecoveryCategory, + castTime, + currentTime, + castType, + gCastData.numRetries, + CastResult::WAITING_FOR_CAST + }); gNextCastId++; auto ret = castSpell(casterUnit, spellId, item, guid); @@ -720,7 +783,7 @@ namespace Nampower { if (*reinterpret_cast(Offsets::SpellIsTargeting) == 0 && !gCastData.pendingOnSwingCast && !IsSpellOnCooldown(spellId)) { DEBUG_LOG("Canceling spell cast due to previous spell having cast time of " - << gLastCastData.castTimeMs); + << gLastCastData.castTimeMs); //JT: Suggest replacing CancelSpell with InterruptSpell (the API called when moving during casting). // The address of InterruptSpell needs to be dug out. It could possibly fix the sometimes broken animations. @@ -751,7 +814,8 @@ namespace Nampower { } void - SpellGoHook(hadesmem::PatchDetourBase *detour, uint64_t *casterGUID, uint64_t *targetGUID, uint32_t spellId, + SpellGoHook(hadesmem::PatchDetourBase *detour, uint64_t *casterGUID, uint64_t *targetGUID, + uint32_t spellId, CDataStore *spellData) { auto const spellGo = detour->GetTrampolineT(); spellGo(casterGUID, targetGUID, spellId, spellData); @@ -771,8 +835,8 @@ namespace Nampower { if (gCastData.onSwingQueued) { DEBUG_LOG("On swing spell " << game::GetSpellName(spellId) << - " resolved, casting queued on swing spell " - << game::GetSpellName(gLastOnSwingCastParams.spellId)); + " resolved, casting queued on swing spell " + << game::GetSpellName(gLastOnSwingCastParams.spellId)); TriggerSpellQueuedEvent(ON_SWING_QUEUE_POPPED, gLastOnSwingCastParams.spellId); @@ -794,7 +858,7 @@ namespace Nampower { ResetCastFlags(); } else if (failed) { DEBUG_LOG("Cancel spell cast failed:" << failed << - " notifyServer:" << notifyServer << " reason:" << int(reason)); + " notifyServer:" << notifyServer << " reason:" << int(reason)); } auto const cancelSpell = detour->GetTrampolineT(); @@ -802,10 +866,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 7308c79..265e598 100644 --- a/nampower/spellcast.hpp +++ b/nampower/spellcast.hpp @@ -17,6 +17,8 @@ namespace Nampower { void TriggerSpellQueuedEvent(QueueEvents queueEventCode, uint32_t spellId); + void CGSpellBook_CastSpellHook(hadesmem::PatchDetourBase *detour, uint32_t spellSlot, int bookType, uint64_t target); + bool Spell_C_CastSpellHook(hadesmem::PatchDetourBase *detour, uint32_t *casterUnit, uint32_t spellId, uintptr_t *item, std::uint64_t guid); diff --git a/nampower/types.h b/nampower/types.h index f601c3e..75619f5 100644 --- a/nampower/types.h +++ b/nampower/types.h @@ -25,6 +25,7 @@ struct UserSettings { bool preventRightClickPvPAttack; bool doubleCastToEndChannelEarly; + bool quickcastOnDoubleCast; bool spamProtectionEnabled;