From b5e1d69649d143388413d003791de4e5b37325a5 Mon Sep 17 00:00:00 2001 From: Dimitar Date: Thu, 12 Jan 2023 17:38:00 +0100 Subject: [PATCH] I found the following 5 issues when using the master version of nampower: 1. The cast animations never working. 2. Sometimes the spell interrupt by walking did not register properly and as such after taking a step you could not start casting until the cast time of the interrupted spell passed. 3. HealComm did not register the casts 4. QuickHeal did not work 5. "Create All" for items such as bandages would break after the 1st item is created. To fix point 1 I disabled the "SendCast" hook. This hook seems to be only necessary for aoe spells (such as blizzard - with the green marker on the ground). Now the animations usually play as expected. They still do not play right if the cast is started before the ACK from the server for finishing the previous cast has arrived. I think to fix this we could try calling "interruptSpell" instead of "cancelSpell". However, the address of "interruptSpell" is not listed in "Offsets" and I do not feel like disassembling WoW.exe at this point in time. The author of nampower probably already has a nicely formatted disassembly and will find the address much faster than I. I also enabled CastSpellHook to act on cursorMode == 2 - this is necessary for click casting. To fix point 2 I added handling of SPELLCAST_INTERRUPTED event in the eventhandler setting cooldown to 0. To fix point 3 I disabled the patch for CreateCastbar and the manual sending of SPELLCAST_START event right after calling castSpell; and allow the notification from the server to trigger the castbar. This visually delays the appearance of the castbar by the ping time. It does not alter the main purpose of this mod however - the actual casting time is still "patched". This is necessary, because HealComm sets up on a hook over CastSpell and triggers on a event filter over SPELLCAST_START. Having the manual signalEvent(SPELLCAST_START) call results in HealComm receiving SPELLCAST_START before CastSpell and as such cannot display anything because it does not yet know which spell this "START" is for. To fix point 4 - QuickHeal works by sending a "CastSpell" immediately followed by "CancelSpell" with parameter notifyServer = true. The internal cooldown tracking of nampower only got reset when receiving (SPELLCAST_FAILED && !gNotifyServer). I simplified the condition to not be affected by the state of gNotifyServer. I am not aware of any adverse effects this might cause anywhere nor am I aware why this was there in the first place. Caution is advised here. To fix poing 5 - I added and exclusion to handling spells with effect SPELL_EFFECT_CREATE_ITEM. --- nampower/main.cpp | 78 +++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 37 deletions(-) diff --git a/nampower/main.cpp b/nampower/main.cpp index 90aa3d8..72c645c 100644 --- a/nampower/main.cpp +++ b/nampower/main.cpp @@ -97,12 +97,12 @@ void BeginCast(DWORD currentTime, std::uint32_t castTime, int spellId) gLastCast = currentTime; #endif - - if (castTime) - { - void(*signalEvent)(std::uint32_t, const char *, ...) = reinterpret_cast(Offsets::SignalEventParam); - signalEvent(game::Events::SPELLCAST_START, "%s%d", game::GetSpellName(spellId), castTime); - } + //JT: Use the notification from server to activate cast bar - it is needed to have HealComm work. + //if (castTime) + //{ + // void(*signalEvent)(std::uint32_t, const char *, ...) = reinterpret_cast(Offsets::SignalEventParam); + // signalEvent(game::Events::SPELLCAST_START, "%s%d", game::GetSpellName(spellId), castTime); + //} } bool CastSpellHook(hadesmem::PatchDetourBase *detour, void *unit, int spellId, void *item, std::uint64_t guid) @@ -131,15 +131,17 @@ bool CastSpellHook(hadesmem::PatchDetourBase *detour, void *unit, int spellId, v // if this is a trade skill or item enchant, do nothing further if (spell->Effect[0] == game::SpellEffects::SPELL_EFFECT_TRADE_SKILL || spell->Effect[0] == game::SpellEffects::SPELL_EFFECT_ENCHANT_ITEM || - spell->Effect[0] == game::SpellEffects::SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY) - return ret; + spell->Effect[0] == game::SpellEffects::SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY || + spell->Effect[0] == game::SpellEffects::SPELL_EFFECT_CREATE_ITEM) + return ret; // haven't gotten spell result from the previous cast yet, probably due to latency. // simulate a cancel to clear the cast bar but only when there should be a cast time if (!ret && castTime) { gCancelling = true; - + //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. auto const cancelSpell = reinterpret_cast(Offsets::CancelSpell); cancelSpell(false, false, game::SpellFailedReason::SPELL_FAILED_ERROR); @@ -150,8 +152,8 @@ bool CastSpellHook(hadesmem::PatchDetourBase *detour, void *unit, int spellId, v } auto const cursorMode = *reinterpret_cast(Offsets::CursorMode); - - if (ret && !!spell && !(spell->Attributes & game::SPELL_ATTR_RANGED) && cursorMode != 2) + //JT: cursorMode == 2 is for clickcasting + if (ret && !!spell && !(spell->Attributes & game::SPELL_ATTR_RANGED)/* && cursorMode != 2*/) BeginCast(currentTime, castTime, spellId); gCasting = false; @@ -180,23 +182,23 @@ int CancelSpellHook(hadesmem::PatchDetourBase *detour, bool failed, bool notifyS return ret; } - -void SendCastHook(hadesmem::PatchDetourBase *detour, game::SpellCast *cast) -{ - auto const cursorMode = *reinterpret_cast(Offsets::CursorMode); - - // if we were waiting for a target, it means there is no cast bar yet. make one \o/ - if (cursorMode == 2) - { - auto const unit = game::GetObjectPtr(cast->caster); - auto const castTime = game::GetCastTime(unit, cast->spellId); - - BeginCast(::GetTickCount(), castTime, cast->spellId); - } - - auto const sendCast = detour->GetTrampolineT(); - sendCast(cast); -} +//JT: Using this breaks animations. It is only useful for AOE spells. We can live without speeding those up. +//void SendCastHook(hadesmem::PatchDetourBase *detour, game::SpellCast *cast) +//{ +// auto const cursorMode = *reinterpret_cast(Offsets::CursorMode); +// +// // if we were waiting for a target, it means there is no cast bar yet. make one \o/ +// if (cursorMode == 2) +// { +// auto const unit = game::GetObjectPtr(cast->caster); +// auto const castTime = game::GetCastTime(unit, cast->spellId); +// +// BeginCast(::GetTickCount(), castTime, cast->spellId); +// } +// +// auto const sendCast = detour->GetTrampolineT(); +// sendCast(cast); +//} void SignalEventHook(hadesmem::PatchDetourBase *detour, game::Events eventId) { @@ -249,7 +251,8 @@ void SignalEventHook(hadesmem::PatchDetourBase *detour, game::Events eventId) // the server perceives too little time as having passed to allow another cast. i dont // think there is anything we can do about this except to honor the servers request to // abort the cast. reset our cooldown and allow - else if (eventId == game::Events::SPELLCAST_FAILED && !gNotifyServer) + else if (eventId == game::Events::SPELLCAST_FAILED/* && !gNotifyServer*/ || //JT: !gNotifyServer breaks QuickHeal + eventId == game::Events::SPELLCAST_INTERRUPTED) //JT: moving to cancel the spell sends "SPELLCAST_INTERRUPTED" gCooldown = 0; auto const signalEvent = detour->GetTrampolineT(); @@ -310,11 +313,11 @@ extern "C" __declspec(dllexport) DWORD Load() auto const cancelSpellOrig = hadesmem::detail::AliasCast(Offsets::CancelSpell); gCancelSpellDetour = std::make_unique>(process, cancelSpellOrig, &CancelSpellHook); gCancelSpellDetour->Apply(); - + //JT: Disable this. This is only for AOE spells and breaks animations. // monitor for spell cast triggered after target (terrain, item, etc.) is selected - auto const sendCastOrig = hadesmem::detail::AliasCast(Offsets::SendCast); - gSendCastDetour = std::make_unique>(process, sendCastOrig, &SendCastHook); - gSendCastDetour->Apply(); + //auto const sendCastOrig = hadesmem::detail::AliasCast(Offsets::SendCast); + //gSendCastDetour = std::make_unique>(process, sendCastOrig, &SendCastHook); + //gSendCastDetour->Apply(); // this hook will alter cast bar behavior based on events from the game auto const signalEventOrig = hadesmem::detail::AliasCast(Offsets::SignalEvent); @@ -325,11 +328,12 @@ extern "C" __declspec(dllexport) DWORD Load() auto const spellDelayedOrig = hadesmem::detail::AliasCast(Offsets::SpellDelayed); gSpellDelayedDetour = std::make_unique>(process, spellDelayedOrig, &SpellDelayedHook); gSpellDelayedDetour->Apply(); - + //JT: Disabling SPELLCAST_START from the server breaks HealComm. It needs time to have passed between CastSpell and + // SPELLCAST_START. Rather have the castbar appear slightly late (only visual mismatch). // prevent spellbar re-activation upon successful cast notification from server - const std::vector patch(5, 0x90); - gCastbarPatch = std::make_unique(process, reinterpret_cast(Offsets::CreateCastbar), patch); - gCastbarPatch->Apply(); + //const std::vector patch(5, 0x90); + //gCastbarPatch = std::make_unique(process, reinterpret_cast(Offsets::CreateCastbar), patch); + //gCastbarPatch->Apply(); return EXIT_SUCCESS; }