Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented OnHeartbeat AuraScript hook and refactored an aurascript to use it as example #29945

Merged
merged 3 commits into from
Apr 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/server/game/Entities/Unit/Unit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ void Unit::Heartbeat()
// SMSG_FLIGHT_SPLINE_SYNC for cyclic splines
SendFlightSplineSyncUpdate();

// Trigger heartbeat procs and generic aura behavior such as food emotes
// Trigger heartbeat procs and generic aura behavior such as food emotes and invoking aura script hooks
TriggerAuraHeartbeat();

// Update Vignette position and visibility
Expand Down
15 changes: 15 additions & 0 deletions src/server/game/Spells/Auras/SpellAuras.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2066,6 +2066,18 @@ void Aura::CallScriptAfterDispel(DispelInfo* dispelInfo)
}
}

void Aura::CallScriptOnHeartbeat()
{
for (AuraScript* script : m_loadedScripts)
{
script->_PrepareScriptCall(AURA_SCRIPT_HOOK_ON_HEARTBEAT);
for (AuraScript::AuraHeartbeatHandler const& onHeartbeat : script->OnHeartbeat)
onHeartbeat.Call(script);

script->_FinishScriptCall();
}
}

bool Aura::CallScriptEffectApplyHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp, AuraEffectHandleModes mode)
{
bool preventDefault = false;
Expand Down Expand Up @@ -2620,6 +2632,9 @@ void UnitAura::Heartbeat()

// Periodic food and drink emote animation
HandlePeriodicFoodSpellVisualKit();

// Invoke the OnHeartbeat AuraScript hook
CallScriptOnHeartbeat();
}

void UnitAura::HandlePeriodicFoodSpellVisualKit()
Expand Down
1 change: 1 addition & 0 deletions src/server/game/Spells/Auras/SpellAuras.h
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ class TC_GAME_API Aura
bool CallScriptCheckAreaTargetHandlers(Unit* target);
void CallScriptDispel(DispelInfo* dispelInfo);
void CallScriptAfterDispel(DispelInfo* dispelInfo);
void CallScriptOnHeartbeat();
bool CallScriptEffectApplyHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp, AuraEffectHandleModes mode);
bool CallScriptEffectRemoveHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp, AuraEffectHandleModes mode);
void CallScriptAfterEffectApplyHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp, AuraEffectHandleModes mode);
Expand Down
59 changes: 59 additions & 0 deletions src/server/game/Spells/SpellScript.h
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,7 @@ enum AuraScriptHookType
AURA_SCRIPT_HOOK_CHECK_AREA_TARGET,
AURA_SCRIPT_HOOK_DISPEL,
AURA_SCRIPT_HOOK_AFTER_DISPEL,
AURA_SCRIPT_HOOK_ON_HEARTBEAT,
AURA_SCRIPT_HOOK_ENTER_LEAVE_COMBAT,
// Spell Proc Hooks
AURA_SCRIPT_HOOK_CHECK_PROC,
Expand Down Expand Up @@ -1174,6 +1175,58 @@ class TC_GAME_API AuraScript : public SpellScriptBase
SafeWrapperType _safeWrapper;
};

class AuraHeartbeatHandler final
{
public:
union AuraHeartbeatFnType
{
void(AuraScript::* Member)();
void(*Static)();
};

using SafeWrapperType = void(*)(AuraScript* auraScript, AuraHeartbeatFnType callImpl);

template<typename ScriptFunc>
explicit AuraHeartbeatHandler(ScriptFunc handler)
{
using ScriptClass = GetScriptClass_t<ScriptFunc>;

static_assert(sizeof(AuraHeartbeatFnType) >= sizeof(ScriptFunc));
static_assert(alignof(AuraHeartbeatFnType) >= alignof(ScriptFunc));

if constexpr (!std::is_void_v<ScriptClass>)
{
static_assert(std::is_invocable_r_v<void, ScriptFunc, ScriptClass>,
"AuraHeartbeat signature must be \"void HandleHeartbeat()\"");

_callImpl = { .Member = reinterpret_cast<decltype(AuraHeartbeatFnType::Member)>(handler) };
_safeWrapper = [](AuraScript* auraScript, AuraHeartbeatFnType callImpl) -> void
{
return (static_cast<ScriptClass*>(auraScript)->*reinterpret_cast<ScriptFunc>(callImpl.Member))();
};
}
else
{
static_assert(std::is_invocable_r_v<void, ScriptFunc>,
"AuraHeartbeatHandler signature must be \"static void HandleHeartbeat()\"");

_callImpl = { .Static = reinterpret_cast<decltype(AuraHeartbeatFnType::Static)>(handler) };
_safeWrapper = [](AuraScript* /*auraScript*/, AuraHeartbeatFnType callImpl) -> void
{
return reinterpret_cast<ScriptFunc>(callImpl.Static)();
};
}
}

void Call(AuraScript* auraScript) const
{
return _safeWrapper(auraScript, _callImpl);
}
private:
AuraHeartbeatFnType _callImpl;
SafeWrapperType _safeWrapper;
};

class TC_GAME_API EffectBase : public EffectHook
{
public:
Expand Down Expand Up @@ -2017,6 +2070,12 @@ class TC_GAME_API AuraScript : public SpellScriptBase
HookList<AuraDispelHandler> AfterDispel;
#define AuraDispelFn(F) AuraDispelHandler(&F)

// executed on every heartbeat of a unit
// example: OnHeartbeat += AuraHeartbeatFn(class::function);
// where function is: void function ();
HookList<AuraHeartbeatHandler> OnHeartbeat;
#define AuraHeartbeatFn(F) AuraHeartbeatHandler(&F)

// executed when aura effect is applied with specified mode to target
// should be used when when effect handler preventing/replacing is needed, do not use this hook for triggering spellcasts/removing auras etc - may be unsafe
// example: OnEffectApply += AuraEffectApplyFn(class::function, EffectIndexSpecifier, EffectAuraNameSpecifier, AuraEffectHandleModes);
Expand Down
17 changes: 4 additions & 13 deletions src/server/scripts/Spells/spell_item.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4326,17 +4326,9 @@ class spell_item_amalgams_seventh_spine : public AuraScript
});
}

void ForcePeriodic(AuraEffect const* /*aurEff*/, bool& isPeriodic, int32& amplitude)
void UpdateSpecAura()
{
// simulate heartbeat timer
isPeriodic = true;
amplitude = 5000;
}

void UpdateSpecAura(AuraEffect const* aurEff)
{
PreventDefaultAction();
Player* target = GetTarget()->ToPlayer();
Player* target = GetUnitOwner()->ToPlayer();
if (!target)
return;

Expand All @@ -4345,7 +4337,7 @@ class spell_item_amalgams_seventh_spine : public AuraScript
if (target->GetPrimarySpecialization() != spec)
target->RemoveAurasDueToSpell(aura);
else if (!target->HasAura(aura))
target->CastSpell(target, aura, aurEff);
target->CastSpell(target, aura, GetEffect(EFFECT_0));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would we not need to validate this effect since OnHeartbeat is not really hooking any and implicitly validating any?

};

switch (target->GetClass())
Expand Down Expand Up @@ -4373,8 +4365,7 @@ class spell_item_amalgams_seventh_spine : public AuraScript

void Register() override
{
DoEffectCalcPeriodic += AuraEffectCalcPeriodicFn(spell_item_amalgams_seventh_spine::ForcePeriodic, EFFECT_0, SPELL_AURA_DUMMY);
OnEffectPeriodic += AuraEffectPeriodicFn(spell_item_amalgams_seventh_spine::UpdateSpecAura, EFFECT_0, SPELL_AURA_DUMMY);
OnHeartbeat += AuraHeartbeatFn(spell_item_amalgams_seventh_spine::UpdateSpecAura);
}
};

Expand Down