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:
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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -304,8 +304,6 @@ public:
|
||||
SpellEffectImplicitTargetTypes GetImplicitTargetType() const;
|
||||
SpellTargetObjectTypes GetUsedTargetObjectType() const;
|
||||
|
||||
uint32 CalcPeriod(WorldObject const* caster, Spell* spell = nullptr) const;
|
||||
|
||||
private:
|
||||
struct StaticData
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user