Core/Auras: Implemented OnHeartbeat AuraScript hook and refactored an aurascript to use it as example (#29945)

* Updated Amalgam's Seventh Spine dummy aura script to use the new AuraScript hook

(cherry picked from commit 55ce5b150f)
This commit is contained in:
Ovahlord
2024-04-27 19:04:10 +02:00
parent 4de6799403
commit cbc6aed437
5 changed files with 80 additions and 14 deletions

View File

@@ -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

View File

@@ -2073,6 +2073,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;
@@ -2627,6 +2639,9 @@ void UnitAura::Heartbeat()
// Periodic food and drink emote animation
HandlePeriodicFoodSpellVisualKit();
// Invoke the OnHeartbeat AuraScript hook
CallScriptOnHeartbeat();
}
void UnitAura::HandlePeriodicFoodSpellVisualKit()

View File

@@ -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);

View File

@@ -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,
@@ -1174,6 +1175,58 @@ public:
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:
@@ -2017,6 +2070,12 @@ public:
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);

View File

@@ -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;
@@ -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));
};
switch (target->GetClass())
@@ -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);
}
};