Core/Spells: fixed duration clamping for haste based periodic auras and added support for rolling any tick over

* roll over remaining time until next tick over into a refreshed periodic aura's duration
* this behavior is now being used for stackable spells, all HoTs and DoTs
* fixed adding extra ticks when at certain tick thresholds
* corrected implementation of SPELL_ATTR8_HASTE_AFFECTS_DURATION
* implemented SPELL_ATTR8_MELEE_HASTE_AFFECTS_PERIODIC
This commit is contained in:
Ovahlord committed 2022-10-01 13:47:14 +02:00
1 parent b6f0650303
commit b6f8a68d61
10 files changed
+72 -97

No files matched your search

@@ -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;
-1
View File
@@ -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;
@@ -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
@@ -629,9 +629,11 @@ uint32 AuraEffect::GetTotalTicks() const
uint32 totalTicks = 0;
if (_period && !GetBase()->IsPermanent())
{
totalTicks = static_cast<uint32>(GetBase()->GetMaxDuration() / _period);
totalTicks = static_cast<uint32>((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
+17 -24
View File
@@ -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;
}
}
}
}
+3 -1
View File
@@ -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
+36 -17
View File
@@ -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);
}
-19
View File
@@ -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;
-2
View File
@@ -304,8 +304,6 @@ public:
SpellEffectImplicitTargetTypes GetImplicitTargetType() const;
SpellTargetObjectTypes GetUsedTargetObjectType() const;
uint32 CalcPeriod(WorldObject const* caster, Spell* spell = nullptr) const;
private:
struct StaticData
{
+2 -2
View File
@@ -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();
}
}