public virtual void CalculateDerivedStats(CastingState castingState, bool outOfFiveSecondRule, bool pom, bool spammedDot, bool round, bool forceHit, bool forceMiss, bool dotUptime) { MageTalents mageTalents = castingState.MageTalents; Stats baseStats = castingState.BaseStats; CalculationOptionsMage calculationOptions = castingState.CalculationOptions; if (AreaEffect) { // do not count debuffs for aoe effects, can't assume it will be up on all // do not include molten fury (molten fury relates to boss), instead amplify all by average if (castingState.MoltenFury) { SpellModifier /= (1 + 0.04f * castingState.MageTalents.MoltenFury); } if (castingState.MageTalents.MoltenFury > 0) { SpellModifier *= (1 + 0.04f * castingState.MageTalents.MoltenFury * castingState.CalculationOptions.MoltenFuryPercentage); } } SpellModifier *= AdditiveSpellModifier; if (CritRate < 0.0f) { CritRate = 0.0f; } if (CritRate > 1.0f) { CritRate = 1.0f; } Ticks = template.Ticks; CastProcs = template.CastProcs; HitProcs = Ticks * HitRate; if (AreaEffect) { TargetProcs = HitProcs * castingState.CalculationOptions.AoeTargets; } else { TargetProcs = HitProcs; } Pom = pom; if (Instant) { InterruptProtection = 1; } CastTime = template.CalculateCastTime(castingState, InterruptProtection, CritRate, pom, BaseCastTime, out ChannelReduction); // add crit rate for on use stacking crit effects (would be better if it was computed // on cycle level, but right now the architecture doesn't allow that too well) // we'd actually need some iterations of this as cast time can depend on crit etc, just ignore that for now for (int i = 0; i < castingState.Solver.StackingNonHasteEffectCooldownsCount; i++) { EffectCooldown effectCooldown = castingState.Solver.StackingNonHasteEffectCooldowns[i]; if (castingState.EffectsActive(effectCooldown.Mask)) { Stats stats = effectCooldown.SpecialEffect.Stats; for (int j = 0; j < stats._rawSpecialEffectDataSize; j++) { SpecialEffect effect = stats._rawSpecialEffectData[j]; if (effect.Chance == 1f && effect.Cooldown == 0f && (effect.Trigger == Trigger.DamageSpellCrit || effect.Trigger == Trigger.SpellCrit)) { if (effect.Stats.CritRating < 0 && effectCooldown.SpecialEffect.Stats.CritRating > 0) { float critScale = castingState.CalculationOptions.LevelScalingFactor / 1400f; CritRate += SpecialEffect.GetAverageStackingCritRate(CastTime, effectCooldown.SpecialEffect.Duration, HitRate, CritRate, effectCooldown.SpecialEffect.Stats.CritRating * critScale, effect.Stats.CritRating * critScale, effect.MaxStack); if (CritRate > 1.0f) { CritRate = 1.0f; } } } } } } if (castingState.Frozen) { CritRate *= (1 + castingState.MageTalents.Shatter); if (CritRate > 1.0f) { CritRate = 1.0f; } } CritProcs = HitProcs * CritRate; if ((MagicSchool == MagicSchool.Fire || MagicSchool == MagicSchool.FrostFire) && mageTalents.Ignite > 0) { IgniteProcs = CritProcs; } else { IgniteProcs = 0; } if (DotTickInterval > 0) { // non-spammed we have to take into account haste on dot duration and increase in number of ticks // probably don't want to take into account haste procs as that might skew optimization thresholds as we're averaging cast time over procs // reevaluate this if needed float x = DotTickInterval / DotDuration; DotExtraTicks = (float)Math.Floor((castingState.CastingSpeed - 1) / x + 0.5); DotFullDuration = (DotDuration + DotTickInterval * DotExtraTicks) / castingState.CastingSpeed; if (spammedDot) { // spammed dots no longer clip on reapplication DotProcs = Math.Min(CastTime, DotDuration) / DotTickInterval; } else { DotProcs = DotDuration / DotTickInterval + DotExtraTicks; } } else { DotProcs = 0; DotFullDuration = 0; DotExtraTicks = 0; } SpammedDot = spammedDot; if ((BaseMinDamage > 0 || BasePeriodicDamage > 0) && !forceMiss) { if (dotUptime) { CalculateDirectAverageDamage(castingState.Solver, RawSpellDamage, forceHit); AverageThreat = AverageDamage * ThreatMultiplier; CalculateDotAverageDamage(castingState.Solver, RawSpellDamage, forceHit); DotAverageThreat = DotAverageDamage * ThreatMultiplier; } else { CalculateAverageDamage(castingState.Solver, RawSpellDamage, spammedDot, forceHit); AverageThreat = AverageDamage * ThreatMultiplier; } } else { AverageDamage = 0; AverageThreat = 0; DamagePerSpellPower = 0; DamagePerMastery = 0; DamagePerCrit = 0; IgniteDamage = 0; IgniteDamagePerSpellPower = 0; IgniteDamagePerMastery = 0; IgniteDamagePerCrit = 0; if (dotUptime) { DotAverageDamage = 0; DotAverageThreat = 0; DotDamagePerSpellPower = 0; DotDamagePerMastery = 0; DotDamagePerCrit = 0; } } if (ChannelReduction != 0) { Ticks *= (1 - ChannelReduction); HitProcs *= (1 - ChannelReduction); CritProcs *= (1 - ChannelReduction); TargetProcs *= (1 - ChannelReduction); CastProcs = CastProcs2 + (CastProcs - CastProcs2) * (1 - ChannelReduction); AverageDamage *= (1 - ChannelReduction); AverageThreat *= (1 - ChannelReduction); DamagePerSpellPower *= (1 - ChannelReduction); DamagePerMastery *= (1 - ChannelReduction); DamagePerCrit *= (1 - ChannelReduction); } AverageCost = CalculateCost(castingState.Solver, round); Absorb = 0; TotalAbsorb = 0; if (outOfFiveSecondRule) { OO5SR = 1; } else { OO5SR = 0; } }
public virtual void CalculateDerivedStats(CastingState castingState, bool outOfFiveSecondRule, bool pom, bool spammedDot, bool round, bool forceHit, bool forceMiss, bool dotUptime) { MageTalents mageTalents = castingState.MageTalents; Stats baseStats = castingState.BaseStats; CalculationOptionsMage calculationOptions = castingState.CalculationOptions; SpellModifier *= AdditiveSpellModifier; if (CritRate < 0.0f) { CritRate = 0.0f; } if (CritRate > 1.0f) { CritRate = 1.0f; } Ticks = template.Ticks; CastProcs = template.CastProcs; HitProcs = Ticks * HitRate; CritProcs = HitProcs * CritRate; if ((MagicSchool == MagicSchool.Fire || MagicSchool == MagicSchool.FrostFire) && mageTalents.Ignite > 0) { IgniteProcs = CritProcs; } else { IgniteProcs = 0; } TargetProcs = HitProcs; Pom = pom; if (Instant) { InterruptProtection = 1; } CastTime = template.CalculateCastTime(castingState, InterruptProtection, CritRate, pom, BaseCastTime, out ChannelReduction); // add crit rate for on use stacking crit effects (would be better if it was computed // on cycle level, but right now the architecture doesn't allow that too well) // we'd actually need some iterations of this as cast time can depend on crit etc, just ignore that for now for (int i = 0; i < castingState.Solver.StackingNonHasteEffectCooldownsCount; i++) { EffectCooldown effectCooldown = castingState.Solver.StackingNonHasteEffectCooldowns[i]; if (castingState.EffectsActive(effectCooldown.Mask)) { Stats stats = effectCooldown.SpecialEffect.Stats; for (int j = 0; j < stats._rawSpecialEffectDataSize; j++) { SpecialEffect effect = stats._rawSpecialEffectData[j]; if (effect.Chance == 1f && effect.Cooldown == 0f && (effect.Trigger == Trigger.DamageSpellCrit || effect.Trigger == Trigger.SpellCrit)) { if (effect.Stats.CritRating < 0 && effectCooldown.SpecialEffect.Stats.CritRating > 0) { float critScale = castingState.CalculationOptions.LevelScalingFactor / 1400f; CritRate += SpecialEffect.GetAverageStackingCritRate(CastTime, effectCooldown.SpecialEffect.Duration, HitRate, CritRate, effectCooldown.SpecialEffect.Stats.CritRating * critScale, effect.Stats.CritRating * critScale, effect.MaxStack); if (CritRate > 1.0f) { CritRate = 1.0f; } } } } } } if (DotTickInterval > 0) { if (spammedDot) { DotProcs = (float)Math.Floor(Math.Min(CastTime, DotDuration) / DotTickInterval); } else { DotProcs = DotDuration / DotTickInterval; } } else { DotProcs = 0; } SpammedDot = spammedDot; if (Ticks > 0 && !forceMiss) { if (dotUptime) { AverageDamage = CalculateDirectAverageDamage(castingState.Solver, RawSpellDamage, forceHit, out DamagePerSpellPower, out IgniteDamage, out IgniteDamagePerSpellPower); AverageThreat = AverageDamage * ThreatMultiplier; DotAverageDamage = CalculateDotAverageDamage(baseStats, calculationOptions, RawSpellDamage, forceHit, out DotDamagePerSpellPower); DotAverageThreat = DotAverageDamage * ThreatMultiplier; } else { AverageDamage = CalculateAverageDamage(castingState.Solver, RawSpellDamage, spammedDot, forceHit, out DamagePerSpellPower, out IgniteDamage, out IgniteDamagePerSpellPower); AverageThreat = AverageDamage * ThreatMultiplier; } } else { AverageDamage = 0; AverageThreat = 0; DamagePerSpellPower = 0; IgniteDamage = 0; IgniteDamagePerSpellPower = 0; if (dotUptime) { DotAverageDamage = 0; DotAverageThreat = 0; DotDamagePerSpellPower = 0; } } if (ChannelReduction != 0) { Ticks *= (1 - ChannelReduction); HitProcs *= (1 - ChannelReduction); CritProcs *= (1 - ChannelReduction); TargetProcs *= (1 - ChannelReduction); CastProcs = CastProcs2 + (CastProcs - CastProcs2) * (1 - ChannelReduction); AverageDamage *= (1 - ChannelReduction); AverageThreat *= (1 - ChannelReduction); DamagePerSpellPower *= (1 - ChannelReduction); } AverageCost = CalculateCost(castingState.Solver, round); Absorb = 0; TotalAbsorb = 0; if (outOfFiveSecondRule) { OO5SR = 1; } else { OO5SR = 0; } }