diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index c75c1c25c5..905bf22043 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -2552,29 +2552,6 @@ void WorldObject::ModSpellCastTime(SpellInfo const* spellInfo, int32& castTime, castTime = 500; } -void WorldObject::ModSpellDurationTime(SpellInfo const* spellInfo, int32& duration, Spell* spell) -{ - if (!spellInfo || duration < 0) - return; - - if (spellInfo->IsChanneled() && !spellInfo->HasAttribute(SPELL_ATTR5_SPELL_HASTE_AFFECTS_PERIODIC)) - return; - - // called from caster - if (Player* modOwner = GetSpellModOwner()) - modOwner->ApplySpellMod(spellInfo->Id, SpellModOp::ChangeCastTime, duration, spell); - - Unit const* unitCaster = ToUnit(); - if (!unitCaster) - return; - - if (!(spellInfo->HasAttribute(SPELL_ATTR0_IS_ABILITY) || spellInfo->HasAttribute(SPELL_ATTR0_IS_TRADESKILL) || spellInfo->HasAttribute(SPELL_ATTR3_IGNORE_CASTER_MODIFIERS)) && - ((GetTypeId() == TYPEID_PLAYER && spellInfo->SpellFamilyName) || GetTypeId() == TYPEID_UNIT)) - duration = int32(float(duration) * GetFloatValue(UNIT_MOD_CAST_SPEED)); - else if (spellInfo->HasAttribute(SPELL_ATTR0_USES_RANGED_SLOT) && !spellInfo->HasAttribute(SPELL_ATTR2_AUTO_REPEAT)) - duration = int32(float(duration) * unitCaster->m_modAttackSpeedPct[RANGED_ATTACK]); -} - float WorldObject::MeleeSpellMissChance(Unit const* /*victim*/, WeaponAttackType /*attType*/, SpellInfo const* /*spellInfo*/) const { return 0.0f; diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h index 4d15b563e5..026b150a4a 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -446,7 +446,6 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation int32 CalcSpellDuration(SpellInfo const* spellInfo) const; int32 ModSpellDuration(SpellInfo const* spellProto, WorldObject const* target, int32 duration, bool positive, uint32 effectMask); void ModSpellCastTime(SpellInfo const* spellProto, int32& castTime, Spell* spell = nullptr); - void ModSpellDurationTime(SpellInfo const* spellProto, int32& castTime, Spell* spell = nullptr); virtual float MeleeSpellMissChance(Unit const* victim, WeaponAttackType attType, SpellInfo const* spellInfo = nullptr) const; virtual SpellMissInfo MeleeSpellHitResult(Unit* victim, SpellInfo const* spellInfo) const; diff --git a/src/server/game/Miscellaneous/SharedDefines.h b/src/server/game/Miscellaneous/SharedDefines.h index 5ce87dfcc9..2bbc8fa072 100644 --- a/src/server/game/Miscellaneous/SharedDefines.h +++ b/src/server/game/Miscellaneous/SharedDefines.h @@ -664,7 +664,7 @@ enum SpellAttr8 SPELL_ATTR8_USE_COMBO_POINTS_ON_ANY_TARGET = 0x00080000, // 19 allows to consume combo points from dead targets SPELL_ATTR8_ARMOR_SPECIALIZATION = 0x00100000, // 20 SPELL_ATTR8_UNK21 = 0x00200000, // 21 - SPELL_ATTR8_UNK22 = 0x00400000, // 22 + SPELL_ATTR8_MELEE_HASTE_AFFECTS_PERIODIC = 0x00400000, // 22 Melee Haste Affects Periodic SPELL_ATTR8_BATTLE_RESURRECTION = 0x00800000, // 23 Used to limit the Amount of Resurrections in Boss Encounters SPELL_ATTR8_HEAL_PREDICTION = 0x01000000, // 24 Displays a predicted health amount on the targets frame SPELL_ATTR8_UNK25 = 0x02000000, // 25 diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index 270a2920fe..e04007ce8c 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -629,9 +629,11 @@ uint32 AuraEffect::GetTotalTicks() const uint32 totalTicks = 0; if (_period && !GetBase()->IsPermanent()) { - totalTicks = static_cast(GetBase()->GetMaxDuration() / _period); + totalTicks = static_cast((GetBase()->GetMaxDuration() - GetBase()->GetRolledOverDuration()) / _period); if (m_spellInfo->HasAttribute(SPELL_ATTR5_EXTRA_INITIAL_PERIOD)) ++totalTicks; + if (GetBase()->GetRolledOverDuration() > 0) + ++totalTicks; } return totalTicks; @@ -680,8 +682,11 @@ void AuraEffect::CalculatePeriodic(Unit* caster, bool resetPeriodicTimer, bool l if (!m_isPeriodic) return; + if (_period == 0) + _period = 5200; // fallback value found in client + Player* modOwner = caster ? caster->GetSpellModOwner() : nullptr; - // Apply casting time mods + if (_period) { // Apply periodic time mod @@ -690,11 +695,12 @@ void AuraEffect::CalculatePeriodic(Unit* caster, bool resetPeriodicTimer, bool l if (caster) { - // Haste modifies periodic time of channeled spells - if (m_spellInfo->IsChanneled()) - caster->ModSpellDurationTime(m_spellInfo, _period); - else if (m_spellInfo->HasAttribute(SPELL_ATTR5_SPELL_HASTE_AFFECTS_PERIODIC)) - _period = int32(_period * caster->GetFloatValue(UNIT_MOD_CAST_SPEED)); + // Apply haste modifiers + if (m_spellInfo->HasAttribute(SPELL_ATTR5_SPELL_HASTE_AFFECTS_PERIODIC)) + _period = int32(_period * caster->GetFloatValue(UNIT_MOD_CAST_HASTE)); + + if (m_spellInfo->HasAttribute(SPELL_ATTR8_MELEE_HASTE_AFFECTS_PERIODIC) && caster->IsPlayer()) + _period = int32(_period * caster->GetFloatValue(PLAYER_FIELD_MOD_HASTE)); } } else // prevent infinite loop on Update diff --git a/src/server/game/Spells/Auras/SpellAuras.cpp b/src/server/game/Spells/Auras/SpellAuras.cpp index d371d8ee6e..84f7f01718 100644 --- a/src/server/game/Spells/Auras/SpellAuras.cpp +++ b/src/server/game/Spells/Auras/SpellAuras.cpp @@ -443,7 +443,7 @@ Aura* Aura::Create(AuraCreateInfo& createInfo) Aura::Aura(AuraCreateInfo const& createInfo) : m_spellInfo(createInfo._spellInfo), m_casterGuid(createInfo.CasterGUID), m_castItemGuid(createInfo.CastItemGUID), m_applyTime(GameTime::GetGameTime()), -m_owner(createInfo._owner), m_timeCla(0), m_updateTargetMapInterval(0), +m_owner(createInfo._owner), m_rolledOverDuration(0), m_timeCla(0), m_updateTargetMapInterval(0), _casterInfo(), m_procCharges(0), m_stackAmount(1), m_isRemoved(false), m_isSingleTarget(false), m_isUsingCharges(false), m_dropEvent(nullptr), m_procCooldown(std::chrono::steady_clock::time_point::min()) @@ -903,6 +903,10 @@ int32 Aura::CalcMaxDuration(Unit* caster) const if (maxDuration != -1 && modOwner) modOwner->ApplySpellMod(spellInfo->Id, SpellModOp::Duration, maxDuration); + // Calculate duration of periodics affected by haste. + if (caster && caster->IsUnit() && spellInfo->HasAttribute(SPELL_ATTR8_HASTE_AFFECTS_DURATION)) + maxDuration = int32(maxDuration * caster->GetFloatValue(UNIT_MOD_CAST_HASTE)); + return maxDuration; } @@ -917,21 +921,9 @@ void Aura::SetDuration(int32 duration, bool withMods) SetNeedClientUpdateForTargets(); } -void Aura::RefreshDuration(bool withMods) +void Aura::RefreshDuration() { - Unit* caster = GetCaster(); - if (withMods && caster) - { - int32 duration = m_spellInfo->GetMaxDuration(); - // Calculate duration of periodics affected by haste. - if (m_spellInfo->HasAttribute(SPELL_ATTR8_HASTE_AFFECTS_DURATION)) - duration = int32(duration * caster->GetFloatValue(UNIT_MOD_CAST_SPEED)); - - SetMaxDuration(duration); - SetDuration(duration); - } - else - SetDuration(GetMaxDuration()); + SetDuration(GetMaxDuration()); if (m_spellInfo->ManaPerSecond) m_timeCla = 1 * IN_MILLISECONDS; @@ -946,18 +938,19 @@ void Aura::RefreshTimers(bool resetPeriodicTimer) { m_maxDuration = CalcMaxDuration(); + if (!resetPeriodicTimer) { - int32 minPeriod = m_maxDuration; for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) - if (AuraEffect const* aurEff = GetEffect(i)) - if (int32 period = aurEff->GetPeriod()) - minPeriod = std::min(period, minPeriod); - - // If only one tick remaining, roll it over into new duration - if (GetDuration() <= minPeriod) { - m_maxDuration += GetDuration(); - resetPeriodicTimer = false; + if (AuraEffect const* aurEff = GetEffect(i)) + { + if (int32 period = aurEff->GetPeriod()) + { + m_rolledOverDuration = period - aurEff->GetPeriodicTimer(); + // For now we only check for a single periodic effect since we don't have enough data regarding multiple periodic effects + break; + } + } } } diff --git a/src/server/game/Spells/Auras/SpellAuras.h b/src/server/game/Spells/Auras/SpellAuras.h index a5883be7ae..2524916d5c 100644 --- a/src/server/game/Spells/Auras/SpellAuras.h +++ b/src/server/game/Spells/Auras/SpellAuras.h @@ -153,10 +153,11 @@ class TC_GAME_API Aura static int32 CalcMaxDuration(SpellInfo const* spellInfo, WorldObject* caster); int32 GetDuration() const { return m_duration; } void SetDuration(int32 duration, bool withMods = false); - void RefreshDuration(bool withMods = false); + void RefreshDuration(); void RefreshTimers(bool resetPeriodicTimer); bool IsExpired() const { return !GetDuration() && !m_dropEvent; } bool IsPermanent() const { return GetMaxDuration() == -1; } + int32 GetRolledOverDuration() const { return m_rolledOverDuration; } uint8 GetCharges() const { return m_procCharges; } void SetCharges(uint8 charges); @@ -290,6 +291,7 @@ class TC_GAME_API Aura int32 m_maxDuration; // Max aura duration int32 m_duration; // Current time + int32 m_rolledOverDuration; // Remaining time to next periodic tick before the aura got refreshed // This is normally the time remaining until the next tick of the dot when refreshed. int32 m_timeCla; // Timer for power per sec calcultion int32 m_updateTargetMapInterval; // Timer for UpdateTargetMapOfEffect diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 434e699d39..02175b2921 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -3029,7 +3029,19 @@ void Spell::DoSpellEffectHit(Unit* unit, uint8 effIndex, TargetInfo& hitInfo) if (!hitInfo.HitAura) { - bool const resetPeriodicTimer = (m_spellInfo->StackAmount < 2) && !(_triggeredCastFlags & TRIGGERED_DONT_RESET_PERIODIC_TIMER); + bool const resetPeriodicTimer = [&]() + { + // HoTs and DoTs don't reset their periodic timer and roll ther duration over + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + if (m_spellInfo->Effects[i].IsEffect() && (m_spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE + || m_spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_PERIODIC_HEAL)) + return false; + + if ((m_spellInfo->StackAmount < 2) && !(_triggeredCastFlags & TRIGGERED_DONT_RESET_PERIODIC_TIMER)) + return true; + + return true; + }(); uint8 const allAuraEffectMask = Aura::BuildEffectMaskForOwner(hitInfo.AuraSpellInfo, MAX_EFFECT_MASK, unit); int32 const* bp = hitInfo.AuraBasePoints; if (hitInfo.AuraSpellInfo == m_spellInfo) @@ -3065,23 +3077,38 @@ void Spell::DoSpellEffectHit(Unit* unit, uint8 effIndex, TargetInfo& hitInfo) if (hitInfo.AuraDuration > 0) { - // Haste modifies duration of channeled spells - if (m_spellInfo->IsChanneled()) - caster->ModSpellDurationTime(m_spellInfo, hitInfo.AuraDuration, this); - else if (m_spellInfo->HasAttribute(SPELL_ATTR8_HASTE_AFFECTS_DURATION)) + // Haste modifies duration and periodic of spells so we have to clamp the duration accordingly + if (m_spellInfo->HasAttribute(SPELL_ATTR8_HASTE_AFFECTS_DURATION) + || m_spellInfo->HasAttribute(SPELL_ATTR5_SPELL_HASTE_AFFECTS_PERIODIC) + || m_spellInfo->HasAttribute(SPELL_ATTR8_MELEE_HASTE_AFFECTS_PERIODIC)) { + // Haste based auras need their duration adapted. int32 origDuration = hitInfo.AuraDuration; hitInfo.AuraDuration = 0; for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { if (AuraEffect const* aurEff = hitInfo.HitAura->GetEffect(effIndex)) - if (int32 period = aurEff->GetPeriod()) // period is hastened by UNIT_MOD_CAST_SPEED - hitInfo.AuraDuration = std::max(std::max(origDuration / period, 1) * period, hitInfo.AuraDuration); + { + if (int32 period = aurEff->GetPeriod()) + { + hitInfo.AuraDuration = std::clamp(origDuration / period * period, 1, origDuration); + + // If the duration of the aura could fit in half of a tick, round up + if (origDuration % period >= (period / 2)) + hitInfo.AuraDuration += period; + break; + } + } + } // if there is no periodic effect if (!hitInfo.AuraDuration) - hitInfo.AuraDuration = int32(origDuration * m_originalCaster->GetFloatValue(UNIT_MOD_CAST_SPEED)); + hitInfo.AuraDuration = origDuration; } + + // Apply rolled over duration if there is any + hitInfo.AuraDuration += hitInfo.HitAura->GetRolledOverDuration(); } } @@ -3767,17 +3794,9 @@ void Spell::handle_immediate() // start channeling if applicable if (m_spellInfo->IsChanneled()) { - int32 duration = m_spellInfo->GetDuration(); + int32 duration = Aura::CalcMaxDuration(m_spellInfo, m_caster); if (duration > 0) { - // First mod_duration then haste - see Missile Barrage - // Apply duration mod - if (Player* modOwner = m_caster->GetSpellModOwner()) - modOwner->ApplySpellMod(m_spellInfo->Id, SpellModOp::Duration, duration); - - // Apply haste mods - m_caster->ModSpellDurationTime(m_spellInfo, duration, this); - m_channeledDuration = duration; SendChannelStart(duration); } diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp index 7548040fa7..405176d3af 100644 --- a/src/server/game/Spells/SpellInfo.cpp +++ b/src/server/game/Spells/SpellInfo.cpp @@ -423,25 +423,6 @@ bool SpellEffectInfo::IsUnitOwnedAuraEffect() const return IsAreaAuraEffect() || Effect == SPELL_EFFECT_APPLY_AURA || Effect == SPELL_EFFECT_APPLY_AURA_2; } -uint32 SpellEffectInfo::CalcPeriod(WorldObject const* caster, Spell* spell /* = nullptr */) const -{ - // Default value found in client. - uint32 period = AuraPeriod; - if (period == 0) - period = 5200; - - if (caster != nullptr) - { - if (Player* modOwner = caster->GetSpellModOwner()) - modOwner->ApplySpellMod(_spellInfo->Id, SpellModOp::Period, period, spell); - - if (caster->IsUnit() && _spellInfo->HasAttribute(SPELL_ATTR5_SPELL_HASTE_AFFECTS_PERIODIC) && !_spellInfo->HasAttribute(SPELL_ATTR3_IGNORE_CASTER_MODIFIERS)) - period = int32(period * caster->ToUnit()->GetFloatValue(UNIT_MOD_CAST_HASTE)); - } - - return period; -} - int32 SpellEffectInfo::CalcValue(WorldObject const* caster, int32 const* bp, Unit const* target) const { float basePointsPerLevel = RealPointsPerLevel; diff --git a/src/server/game/Spells/SpellInfo.h b/src/server/game/Spells/SpellInfo.h index 7a42d992c4..a1523197dc 100644 --- a/src/server/game/Spells/SpellInfo.h +++ b/src/server/game/Spells/SpellInfo.h @@ -304,8 +304,6 @@ public: SpellEffectImplicitTargetTypes GetImplicitTargetType() const; SpellTargetObjectTypes GetUsedTargetObjectType() const; - uint32 CalcPeriod(WorldObject const* caster, Spell* spell = nullptr) const; - private: struct StaticData { diff --git a/src/server/scripts/Spells/spell_warlock.cpp b/src/server/scripts/Spells/spell_warlock.cpp index c7750db696..9fa206cc85 100644 --- a/src/server/scripts/Spells/spell_warlock.cpp +++ b/src/server/scripts/Spells/spell_warlock.cpp @@ -456,7 +456,7 @@ class spell_warl_everlasting_affliction : public SpellScript { aurEff->RecalculateAmount(caster); aurEff->CalculatePeriodic(caster, false, false); - aurEff->GetBase()->RefreshDuration(true); + aurEff->GetBase()->RefreshDuration(); } } } @@ -1631,7 +1631,7 @@ class spell_warl_pandemic_script : public SpellScript { aurEff->RecalculateAmount(caster); aurEff->CalculatePeriodic(caster, false, false); - aurEff->GetBase()->RefreshDuration(true); + aurEff->GetBase()->RefreshDuration(); } }