aboutsummaryrefslogtreecommitdiff
path: root/src/server/game/Spells/Spell.cpp
diff options
context:
space:
mode:
authorShauren <shauren.trinity@gmail.com>2024-05-01 22:26:53 +0200
committerShauren <shauren.trinity@gmail.com>2024-05-01 22:26:53 +0200
commita39d0db9ec64f6bf38716abaade5b7835f2db338 (patch)
tree1e18f96b3600ab02d9cb8fc6288ac0cded23b6ce /src/server/game/Spells/Spell.cpp
parentcdc6719b8368907292f090978f6bdd6b8c73834d (diff)
Core/Spells: Implemented evoker empower spell mechanic
Diffstat (limited to 'src/server/game/Spells/Spell.cpp')
-rw-r--r--src/server/game/Spells/Spell.cpp222
1 files changed, 190 insertions, 32 deletions
diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp
index 22095eae8c7..fd354cdcfb1 100644
--- a/src/server/game/Spells/Spell.cpp
+++ b/src/server/game/Spells/Spell.cpp
@@ -511,6 +511,7 @@ m_spellValue(new SpellValue(m_spellInfo, caster)), _spellEvent(nullptr)
m_referencedFromCurrentSpell = false;
m_executedCurrently = false;
m_delayStart = 0;
+ m_delayMoment = 0;
m_delayAtDamageCount = 0;
m_applyMultiplierMask = 0;
@@ -601,6 +602,9 @@ m_spellValue(new SpellValue(m_spellInfo, caster)), _spellEvent(nullptr)
m_channelTargetEffectMask = 0;
+ if (m_spellInfo->IsEmpowerSpell())
+ m_empower = std::make_unique<EmpowerData>();
+
// Determine if spell can be reflected back to the caster
// Patch 1.2 notes: Spell Reflection no longer reflects abilities
m_canReflect = caster->IsUnit()
@@ -608,8 +612,6 @@ m_spellValue(new SpellValue(m_spellInfo, caster)), _spellEvent(nullptr)
&& !m_spellInfo->HasAttribute(SPELL_ATTR1_NO_REFLECTION) && !m_spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES)
&& !m_spellInfo->IsPassive();
- CleanupTargetList();
-
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
m_destTargets[i] = SpellDestination(*m_caster);
}
@@ -1531,7 +1533,7 @@ void Spell::SelectImplicitCasterDestTargets(SpellEffectInfo const& spellEffectIn
if (liquidLevel <= ground) // When there is no liquid Map::GetWaterOrGroundLevel returns ground level
{
SendCastResult(SPELL_FAILED_NOT_HERE);
- SendChannelUpdate(0);
+ SendChannelUpdate(0, SPELL_FAILED_NOT_HERE);
finish(SPELL_FAILED_NOT_HERE);
return;
}
@@ -1539,7 +1541,7 @@ void Spell::SelectImplicitCasterDestTargets(SpellEffectInfo const& spellEffectIn
if (ground + 0.75 > liquidLevel)
{
SendCastResult(SPELL_FAILED_TOO_SHALLOW);
- SendChannelUpdate(0);
+ SendChannelUpdate(0, SPELL_FAILED_TOO_SHALLOW);
finish(SPELL_FAILED_TOO_SHALLOW);
return;
}
@@ -3504,7 +3506,7 @@ SpellCastResult Spell::prepare(SpellCastTargets const& targets, AuraEffect const
// a possible alternative sollution for those would be validating aura target on unit state change
if (triggeredByAura && triggeredByAura->IsPeriodic() && !triggeredByAura->GetBase()->IsPassive())
{
- SendChannelUpdate(0);
+ SendChannelUpdate(0, result);
triggeredByAura->GetBase()->SetDuration(0);
}
@@ -3628,7 +3630,7 @@ void Spell::cancel()
if (Unit* unit = m_caster->GetGUID() == targetInfo.TargetGUID ? m_caster->ToUnit() : ObjectAccessor::GetUnit(*m_caster, targetInfo.TargetGUID))
unit->RemoveOwnedAura(m_spellInfo->Id, m_originalCasterGUID, 0, AURA_REMOVE_BY_CANCEL);
- SendChannelUpdate(0);
+ SendChannelUpdate(0, SPELL_FAILED_INTERRUPTED);
SendInterrupted(0);
SendCastResult(SPELL_FAILED_INTERRUPTED);
@@ -3997,10 +3999,12 @@ void Spell::handle_immediate()
if (m_spellInfo->IsChanneled())
{
int32 duration = m_spellInfo->GetDuration();
- if (duration > 0 || m_spellValue->Duration)
+ if (duration > 0 || m_spellValue->Duration > 0)
{
if (!m_spellValue->Duration)
{
+ int32 originalDuration = duration;
+
// First mod_duration then haste - see Missile Barrage
// Apply duration mod
if (Player* modOwner = m_caster->GetSpellModOwner())
@@ -4010,6 +4014,27 @@ void Spell::handle_immediate()
// Apply haste mods
m_caster->ModSpellDurationTime(m_spellInfo, duration, this);
+
+ if (IsEmpowerSpell())
+ {
+ float ratio = float(duration) / float(originalDuration);
+ m_empower->StageDurations.resize(m_spellInfo->EmpowerStageThresholds.size());
+ Milliseconds totalExceptLastStage = 0ms;
+ for (std::size_t i = 0; i < m_empower->StageDurations.size() - 1; ++i)
+ {
+ m_empower->StageDurations[i] = Milliseconds(int64(m_spellInfo->EmpowerStageThresholds[i].count() * ratio));
+ totalExceptLastStage += m_empower->StageDurations[i];
+ }
+
+ m_empower->StageDurations.back() = Milliseconds(duration) - totalExceptLastStage;
+
+ if (Player const* playerCaster = m_caster->ToPlayer())
+ m_empower->MinHoldTime = Milliseconds(int64(m_empower->StageDurations[0].count() * playerCaster->GetEmpowerMinHoldStagePercent()));
+ else
+ m_empower->MinHoldTime = m_empower->StageDurations[0];
+
+ duration += SPELL_EMPOWER_HOLD_TIME_AT_MAX;
+ }
}
else
duration = *m_spellValue->Duration;
@@ -4306,9 +4331,47 @@ void Spell::update(uint32 difftime)
}
}
+ if (IsEmpowerSpell())
+ {
+ auto getCompletedEmpowerStages = [&]() -> int32
+ {
+ Milliseconds passed(m_channeledDuration - m_timer);
+ for (std::size_t i = 0; i < m_empower->StageDurations.size(); ++i)
+ {
+ passed -= m_empower->StageDurations[i];
+ if (passed < 0ms)
+ return i;
+ }
+
+ return m_empower->StageDurations.size();
+ };
+
+ int32 completedStages = getCompletedEmpowerStages();
+ if (completedStages != m_empower->CompletedStages)
+ {
+ WorldPackets::Spells::SpellEmpowerSetStage empowerSetStage;
+ empowerSetStage.CastID = m_castId;
+ empowerSetStage.CasterGUID = m_caster->GetGUID();
+ empowerSetStage.Stage = m_empower->CompletedStages;
+ m_caster->SendMessageToSet(empowerSetStage.Write(), true);
+
+ m_empower->CompletedStages = completedStages;
+ m_caster->ToUnit()->SetSpellEmpowerStage(completedStages);
+
+ CallScriptEmpowerStageCompletedHandlers(completedStages);
+ }
+
+ if (CanReleaseEmpowerSpell())
+ {
+ m_empower->IsReleased = true;
+ m_timer = 0;
+ CallScriptEmpowerCompletedHandlers(m_empower->CompletedStages);
+ }
+ }
+
if (m_timer == 0)
{
- SendChannelUpdate(0);
+ SendChannelUpdate(0, SPELL_CAST_OK);
finish();
// We call the hook here instead of in Spell::finish because we only want to call it for completed channeling. Everything else is handled by interrupts
@@ -4371,6 +4434,9 @@ void Spell::finish(SpellCastResult result)
if (WorldPackets::Traits::TraitConfig const* traitConfig = std::any_cast<WorldPackets::Traits::TraitConfig>(&m_customArg))
m_caster->ToPlayer()->SendDirectMessage(WorldPackets::Traits::TraitConfigCommitFailed(traitConfig->ID).Write());
+ if (IsEmpowerSpell())
+ unitCaster->GetSpellHistory()->ResetCooldown(m_spellInfo->Id, true);
+
return;
}
@@ -4400,6 +4466,13 @@ void Spell::finish(SpellCastResult result)
// Stop Attack for some spells
if (m_spellInfo->HasAttribute(SPELL_ATTR0_CANCELS_AUTO_ATTACK_COMBAT))
unitCaster->AttackStop();
+
+ if (IsEmpowerSpell())
+ {
+ // Empower spells trigger gcd at the end of cast instead of at start
+ if (SpellInfo const* gcd = sSpellMgr->GetSpellInfo(SPELL_EMPOWER_HARDCODED_GCD, DIFFICULTY_NONE))
+ unitCaster->GetSpellHistory()->AddGlobalCooldown(gcd, Milliseconds(gcd->StartRecoveryTime));
+ }
}
template<class T>
@@ -5210,7 +5283,7 @@ void Spell::SendInterrupted(uint8 result)
m_caster->SendMessageToSet(failedPacket.Write(), true);
}
-void Spell::SendChannelUpdate(uint32 time)
+void Spell::SendChannelUpdate(uint32 time, Optional<SpellCastResult> result)
{
// GameObjects don't channel
Unit* unitCaster = m_caster->ToUnit();
@@ -5222,12 +5295,31 @@ void Spell::SendChannelUpdate(uint32 time)
unitCaster->ClearChannelObjects();
unitCaster->SetChannelSpellId(0);
unitCaster->SetChannelVisual({});
+ unitCaster->SetSpellEmpowerStage(-1);
}
- WorldPackets::Spells::SpellChannelUpdate spellChannelUpdate;
- spellChannelUpdate.CasterGUID = unitCaster->GetGUID();
- spellChannelUpdate.TimeRemaining = time;
- unitCaster->SendMessageToSet(spellChannelUpdate.Write(), true);
+ if (IsEmpowerSpell())
+ {
+ WorldPackets::Spells::SpellEmpowerUpdate spellEmpowerUpdate;
+ spellEmpowerUpdate.CastID = m_castId;
+ spellEmpowerUpdate.CasterGUID = unitCaster->GetGUID();
+ spellEmpowerUpdate.TimeRemaining = Milliseconds(time);
+ if (time > 0)
+ spellEmpowerUpdate.StageDurations.assign(m_empower->StageDurations.begin(), m_empower->StageDurations.end());
+ else if (result && result != SPELL_CAST_OK)
+ spellEmpowerUpdate.Status = 1;
+ else
+ spellEmpowerUpdate.Status = 4;
+
+ unitCaster->SendMessageToSet(spellEmpowerUpdate.Write(), true);
+ }
+ else
+ {
+ WorldPackets::Spells::SpellChannelUpdate spellChannelUpdate;
+ spellChannelUpdate.CasterGUID = unitCaster->GetGUID();
+ spellChannelUpdate.TimeRemaining = time;
+ unitCaster->SendMessageToSet(spellChannelUpdate.Write(), true);
+ }
}
void Spell::SendChannelStart(uint32 duration)
@@ -5285,32 +5377,57 @@ void Spell::SendChannelStart(uint32 duration)
unitCaster->SetChannelSpellId(m_spellInfo->Id);
unitCaster->SetChannelVisual(m_SpellVisual);
- WorldPackets::Spells::SpellChannelStart spellChannelStart;
- spellChannelStart.CasterGUID = unitCaster->GetGUID();
- spellChannelStart.SpellID = m_spellInfo->Id;
- spellChannelStart.Visual = m_SpellVisual;
- spellChannelStart.ChannelDuration = duration;
+ auto setImmunitiesAndHealPrediction = [&](Optional<WorldPackets::Spells::SpellChannelStartInterruptImmunities>& interruptImmunities, Optional<WorldPackets::Spells::SpellTargetedHealPrediction>& healPrediction)
+ {
+ uint32 schoolImmunityMask = unitCaster->GetSchoolImmunityMask();
+ uint32 mechanicImmunityMask = unitCaster->GetMechanicImmunityMask();
- uint32 schoolImmunityMask = unitCaster->GetSchoolImmunityMask();
- uint32 mechanicImmunityMask = unitCaster->GetMechanicImmunityMask();
+ if (schoolImmunityMask || mechanicImmunityMask)
+ {
+ interruptImmunities.emplace();
+ interruptImmunities->SchoolImmunities = schoolImmunityMask;
+ interruptImmunities->Immunities = mechanicImmunityMask;
+ }
- if (schoolImmunityMask || mechanicImmunityMask)
- {
- spellChannelStart.InterruptImmunities.emplace();
- spellChannelStart.InterruptImmunities->SchoolImmunities = schoolImmunityMask;
- spellChannelStart.InterruptImmunities->Immunities = mechanicImmunityMask;
- }
+ if (m_spellInfo->HasAttribute(SPELL_ATTR8_HEAL_PREDICTION) && m_caster->IsUnit())
+ {
+ healPrediction.emplace();
+ if (unitCaster->m_unitData->ChannelObjects.size() == 1 && unitCaster->m_unitData->ChannelObjects[0].IsUnit())
+ healPrediction->TargetGUID = unitCaster->m_unitData->ChannelObjects[0];
- if (m_spellInfo->HasAttribute(SPELL_ATTR8_HEAL_PREDICTION) && m_caster->IsUnit())
+ UpdateSpellHealPrediction(healPrediction->Predict, true);
+ }
+ };
+
+ if (IsEmpowerSpell())
{
- WorldPackets::Spells::SpellTargetedHealPrediction& healPrediction = spellChannelStart.HealPrediction.emplace();
- if (unitCaster->m_unitData->ChannelObjects.size() == 1 && unitCaster->m_unitData->ChannelObjects[0].IsUnit())
- healPrediction.TargetGUID = unitCaster->m_unitData->ChannelObjects[0];
+ unitCaster->SetSpellEmpowerStage(0);
- UpdateSpellHealPrediction(healPrediction.Predict, true);
+ WorldPackets::Spells::SpellEmpowerStart spellEmpowerStart;
+ spellEmpowerStart.CastID = m_castId;
+ spellEmpowerStart.CasterGUID = unitCaster->GetGUID();
+ spellEmpowerStart.SpellID = m_spellInfo->Id;
+ spellEmpowerStart.Visual = m_SpellVisual;
+ spellEmpowerStart.EmpowerDuration = std::reduce(m_empower->StageDurations.begin(), m_empower->StageDurations.end());
+ spellEmpowerStart.MinHoldTime = m_empower->MinHoldTime;
+ spellEmpowerStart.HoldAtMaxTime = Milliseconds(SPELL_EMPOWER_HOLD_TIME_AT_MAX);
+ spellEmpowerStart.Targets.assign(unitCaster->m_unitData->ChannelObjects.begin(), unitCaster->m_unitData->ChannelObjects.end());
+ spellEmpowerStart.StageDurations.assign(m_empower->StageDurations.begin(), m_empower->StageDurations.end());
+ setImmunitiesAndHealPrediction(spellEmpowerStart.InterruptImmunities, spellEmpowerStart.HealPrediction);
+
+ unitCaster->SendMessageToSet(spellEmpowerStart.Write(), true);
}
+ else
+ {
+ WorldPackets::Spells::SpellChannelStart spellChannelStart;
+ spellChannelStart.CasterGUID = unitCaster->GetGUID();
+ spellChannelStart.SpellID = m_spellInfo->Id;
+ spellChannelStart.Visual = m_SpellVisual;
+ spellChannelStart.ChannelDuration = duration;
+ setImmunitiesAndHealPrediction(spellChannelStart.InterruptImmunities, spellChannelStart.HealPrediction);
- unitCaster->SendMessageToSet(spellChannelStart.Write(), true);
+ unitCaster->SendMessageToSet(spellChannelStart.Write(), true);
+ }
}
void Spell::SendResurrectRequest(Player* target)
@@ -8209,6 +8326,23 @@ bool Spell::IsPositive() const
return m_spellInfo->IsPositive() && (!m_triggeredByAuraSpell || m_triggeredByAuraSpell->IsPositive());
}
+void Spell::SetEmpowerReleasedByClient(bool release)
+{
+ m_empower->IsReleasedByClient = release;
+}
+
+bool Spell::CanReleaseEmpowerSpell() const
+{
+ if (m_empower->IsReleased)
+ return false;
+
+ if (!m_empower->IsReleasedByClient && m_timer)
+ return false;
+
+ Milliseconds passedTime(m_channeledDuration - m_timer);
+ return passedTime >= m_empower->MinHoldTime;
+}
+
bool Spell::IsNeedSendToClient() const
{
return m_SpellVisual.SpellXSpellVisualID || m_SpellVisual.ScriptVisualID || m_spellInfo->IsChanneled() ||
@@ -8897,6 +9031,30 @@ void Spell::CallScriptOnResistAbsorbCalculateHandlers(DamageInfo const& damageIn
}
}
+void Spell::CallScriptEmpowerStageCompletedHandlers(int32 completedStagesCount)
+{
+ for (SpellScript* script : m_loadedScripts)
+ {
+ script->_PrepareScriptCall(SPELL_SCRIPT_HOOK_EMPOWER_STAGE_COMPLETED);
+ for (SpellScript::EmpowerStageCompletedHandler const& empowerStageCompleted : script->OnEmpowerStageCompleted)
+ empowerStageCompleted.Call(script, completedStagesCount);
+
+ script->_FinishScriptCall();
+ }
+}
+
+void Spell::CallScriptEmpowerCompletedHandlers(int32 completedStagesCount)
+{
+ for (SpellScript* script : m_loadedScripts)
+ {
+ script->_PrepareScriptCall(SPELL_SCRIPT_HOOK_EMPOWER_COMPLETED);
+ for (SpellScript::EmpowerStageCompletedHandler const& empowerStageCompleted : script->OnEmpowerCompleted)
+ empowerStageCompleted.Call(script, completedStagesCount);
+
+ script->_FinishScriptCall();
+ }
+}
+
bool Spell::CheckScriptEffectImplicitTargets(uint32 effIndex, uint32 effIndexToCheck)
{
// Skip if there are not any script