aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/server/game/Entities/Unit/Unit.cpp2
-rw-r--r--src/server/game/Spells/Auras/SpellAuras.cpp15
-rw-r--r--src/server/game/Spells/Auras/SpellAuras.h1
-rw-r--r--src/server/game/Spells/SpellScript.h59
-rw-r--r--src/server/scripts/Spells/spell_item.cpp17
5 files changed, 80 insertions, 14 deletions
diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp
index db5604c3d44..36a8ae32026 100644
--- a/src/server/game/Entities/Unit/Unit.cpp
+++ b/src/server/game/Entities/Unit/Unit.cpp
@@ -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
diff --git a/src/server/game/Spells/Auras/SpellAuras.cpp b/src/server/game/Spells/Auras/SpellAuras.cpp
index da09f519130..19bb668699b 100644
--- a/src/server/game/Spells/Auras/SpellAuras.cpp
+++ b/src/server/game/Spells/Auras/SpellAuras.cpp
@@ -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()
diff --git a/src/server/game/Spells/Auras/SpellAuras.h b/src/server/game/Spells/Auras/SpellAuras.h
index 08621eed649..2852c959a5b 100644
--- a/src/server/game/Spells/Auras/SpellAuras.h
+++ b/src/server/game/Spells/Auras/SpellAuras.h
@@ -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);
diff --git a/src/server/game/Spells/SpellScript.h b/src/server/game/Spells/SpellScript.h
index abe0608b45a..9c28dd10d7d 100644
--- a/src/server/game/Spells/SpellScript.h
+++ b/src/server/game/Spells/SpellScript.h
@@ -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);
diff --git a/src/server/scripts/Spells/spell_item.cpp b/src/server/scripts/Spells/spell_item.cpp
index f0c1e460815..1bc95391071 100644
--- a/src/server/scripts/Spells/spell_item.cpp
+++ b/src/server/scripts/Spells/spell_item.cpp
@@ -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);
}
};