Skip to content

Commit

Permalink
I found the following 5 issues when using the master version of nampo…
Browse files Browse the repository at this point in the history
…wer:

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.
  • Loading branch information
Dimitar committed Jan 12, 2023
1 parent e45316a commit b5e1d69
Showing 1 changed file with 41 additions and 37 deletions.
78 changes: 41 additions & 37 deletions nampower/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<decltype(signalEvent)>(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<decltype(signalEvent)>(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)
Expand Down Expand Up @@ -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<CancelSpellT>(Offsets::CancelSpell);
cancelSpell(false, false, game::SpellFailedReason::SPELL_FAILED_ERROR);

Expand All @@ -150,8 +152,8 @@ bool CastSpellHook(hadesmem::PatchDetourBase *detour, void *unit, int spellId, v
}

auto const cursorMode = *reinterpret_cast<int *>(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;
Expand Down Expand Up @@ -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<int *>(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<SendCastT>();
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<int *>(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<SendCastT>();
// sendCast(cast);
//}

void SignalEventHook(hadesmem::PatchDetourBase *detour, game::Events eventId)
{
Expand Down Expand Up @@ -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<SignalEventT>();
Expand Down Expand Up @@ -310,11 +313,11 @@ extern "C" __declspec(dllexport) DWORD Load()
auto const cancelSpellOrig = hadesmem::detail::AliasCast<CancelSpellT>(Offsets::CancelSpell);
gCancelSpellDetour = std::make_unique<hadesmem::PatchDetour<CancelSpellT>>(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<SendCastT>(Offsets::SendCast);
gSendCastDetour = std::make_unique<hadesmem::PatchDetour<SendCastT>>(process, sendCastOrig, &SendCastHook);
gSendCastDetour->Apply();
//auto const sendCastOrig = hadesmem::detail::AliasCast<SendCastT>(Offsets::SendCast);
//gSendCastDetour = std::make_unique<hadesmem::PatchDetour<SendCastT>>(process, sendCastOrig, &SendCastHook);
//gSendCastDetour->Apply();

// this hook will alter cast bar behavior based on events from the game
auto const signalEventOrig = hadesmem::detail::AliasCast<SignalEventT>(Offsets::SignalEvent);
Expand All @@ -325,11 +328,12 @@ extern "C" __declspec(dllexport) DWORD Load()
auto const spellDelayedOrig = hadesmem::detail::AliasCast<PacketHandlerT>(Offsets::SpellDelayed);
gSpellDelayedDetour = std::make_unique<hadesmem::PatchDetour<PacketHandlerT>>(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<std::uint8_t> patch(5, 0x90);
gCastbarPatch = std::make_unique<hadesmem::PatchRaw>(process, reinterpret_cast<void *>(Offsets::CreateCastbar), patch);
gCastbarPatch->Apply();
//const std::vector<std::uint8_t> patch(5, 0x90);
//gCastbarPatch = std::make_unique<hadesmem::PatchRaw>(process, reinterpret_cast<void *>(Offsets::CreateCastbar), patch);
//gCastbarPatch->Apply();

return EXIT_SUCCESS;
}

0 comments on commit b5e1d69

Please sign in to comment.