diff options
author | Shauren <shauren.trinity@gmail.com> | 2024-05-01 22:26:53 +0200 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2024-05-01 22:26:53 +0200 |
commit | a39d0db9ec64f6bf38716abaade5b7835f2db338 (patch) | |
tree | 1e18f96b3600ab02d9cb8fc6288ac0cded23b6ce /src/server/game/Spells/Spell.cpp | |
parent | cdc6719b8368907292f090978f6bdd6b8c73834d (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.cpp | 222 |
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 |