public EffectCooldown NewEffectCooldown() { if (effectIndex < effectPool.Length) { EffectCooldown effect = effectPool[effectIndex]; if (effect == null) { goto COLD; } effectIndex++; return(effect); } COLD: return(NewEffectCooldownCold()); }
private EffectCooldown NewEffectCooldownCold() { if (effectIndex >= effectPool.Length) { EffectCooldown[] arr = new EffectCooldown[effectPool.Length * 2]; Array.Copy(effectPool, arr, effectPool.Length); effectPool = arr; } EffectCooldown effect = effectPool[effectIndex]; if (effect == null) { effect = new EffectCooldown(); effectPool[effectIndex] = effect; } effectIndex++; return(effect); }
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; } }
public float CalculateCastTime(CastingState castingState, float interruptProtection, float critRate, bool pom, float baseCastTime, out float channelReduction) { CalculationOptionsMage calculationOptions = castingState.CalculationOptions; float castingSpeed = castingState.CastingSpeed; float spellHasteRating = castingState.SpellHasteRating; float levelScalingFactor = calculationOptions.LevelScalingFactor; float hasteFactor = levelScalingFactor / 1000f; float rootCastingSpeed = castingSpeed / (1 + spellHasteRating * hasteFactor); float InterruptFactor = 0f; float maxPushback = 0f; if (calculationOptions.InterruptFrequency > 0f) { // interrupt factors of more than once per spell are not supported, so put a limit on it (up to twice is probably approximately correct) InterruptFactor = Math.Min(calculationOptions.InterruptFrequency, 2 * castingSpeed / baseCastTime); if (castingState.IcyVeins) { interruptProtection = 1; } maxPushback = 0.5f * Math.Max(0, 1 - interruptProtection); if (Channeled) { maxPushback = 0.0f; } } if (pom) { baseCastTime = 0.0f; } float globalCooldown = Math.Max(Spell.GlobalCooldownLimit, 1.5f / castingSpeed); float latency; if (baseCastTime <= 1.5f || Instant) { latency = calculationOptions.LatencyGCD; } else if (Channeled) { latency = calculationOptions.LatencyChannel; } else { latency = calculationOptions.LatencyCast; } float averageTicks = Ticks; float castTime = baseCastTime / castingSpeed; if (calculationOptions.Beta && Channeled) { float tickCastTime = castTime / Ticks; averageTicks = (float)Math.Floor(baseCastTime / tickCastTime); castTime = averageTicks * tickCastTime; } if (InterruptFactor > 0) { castTime = castTime * (1 + InterruptFactor * maxPushback) - (maxPushback * 0.5f) * maxPushback * InterruptFactor; } castTime += latency; float gcdcap = globalCooldown + calculationOptions.LatencyGCD; if (castTime < gcdcap) { castTime = gcdcap; } channelReduction = 0.0f; if (!calculationOptions.AdvancedHasteProcs) { for (int i = 0; i < castingState.Solver.HasteRatingEffectsCount; i++) { SpecialEffect effect = castingState.Solver.HasteRatingEffects[i]; float procs = 0.0f; int triggers = 0; switch (effect.Trigger) { case Trigger.DamageSpellCast: case Trigger.SpellCast: procs = CastProcs; triggers = (int)procs; break; case Trigger.DamageSpellCrit: case Trigger.SpellCrit: procs = critRate * averageTicks; triggers = (int)averageTicks; break; case Trigger.DamageSpellHit: case Trigger.SpellHit: procs = HitRate * averageTicks; triggers = (int)averageTicks; break; } if (procs == 0.0f) { continue; } float procHaste = effect.Stats.HasteRating; if (effect.Cooldown >= effect.Duration) { // hasted casttime float speed = rootCastingSpeed * (1 + (spellHasteRating + procHaste) * hasteFactor); float gcd = Math.Max(Spell.GlobalCooldownLimit, 1.5f / speed); float cast = baseCastTime / speed; if (calculationOptions.Beta && Channeled) { float tickCastTime = cast / Ticks; cast = (float)Math.Floor(baseCastTime / tickCastTime) * tickCastTime; } if (InterruptFactor > 0) { cast = cast * (1 + InterruptFactor * maxPushback) - (maxPushback * 0.5f) * maxPushback * InterruptFactor; } cast += latency; gcdcap = gcd + calculationOptions.LatencyGCD; if (cast < gcdcap) { cast = gcdcap; } float castsAffected = 0; if (triggers > 1) { // multi tick spell (i.e. AM) for (int c = 0; c < triggers; c++) { castsAffected += (float)Math.Ceiling((effect.Duration - c * castTime / triggers) / cast); } castsAffected /= triggers; } else { // single tick spell castsAffected = (float)Math.Ceiling(effect.Duration / cast); // should the first proc be already hasted? } float effectiveDuration = castsAffected * cast; // this isn't completely accurate, we should have made a separate SpecialEffect and change the actual duration // but performance would hurt so this'll have to do spellHasteRating += procHaste * (effectiveDuration / effect.Duration) * effect.GetAverageUptime(castTime / triggers, procs / triggers, 3.0f, calculationOptions.FightDuration); //spellHasteRating += procHaste * castsAffected * cast / (effect.Cooldown + castTime / procs / effect.Chance); //Haste += castingState.BasicStats.SpellHasteFor6SecOnCast_15_45 * 6f / (45f + CastTime / CastProcs / 0.15f); castingSpeed = rootCastingSpeed * (1 + spellHasteRating * hasteFactor); globalCooldown = Math.Max(Spell.GlobalCooldownLimit, 1.5f / castingSpeed); castTime = baseCastTime / castingSpeed; if (calculationOptions.Beta && Channeled) { float tickCastTime = castTime / Ticks; averageTicks = (float)Math.Floor(baseCastTime / tickCastTime); castTime = averageTicks * tickCastTime; } if (InterruptFactor > 0) { castTime = castTime * (1 + InterruptFactor * maxPushback) - (maxPushback * 0.5f) * maxPushback * InterruptFactor; } castTime += latency; gcdcap = globalCooldown + calculationOptions.LatencyGCD; if (castTime < gcdcap) { castTime = gcdcap; } } else if (effect.Cooldown == 0 && (effect.Trigger == Trigger.SpellCrit || effect.Trigger == Trigger.DamageSpellCrit)) { float rawHaste = spellHasteRating; castingSpeed /= (1 + spellHasteRating / 1000f * levelScalingFactor); float proccedSpeed = castingSpeed * (1 + (rawHaste + procHaste) / 1000f * levelScalingFactor); float proccedGcd = Math.Max(Spell.GlobalCooldownLimit, 1.5f / proccedSpeed); float proccedCastTime = baseCastTime / proccedSpeed; float proccedTicks = averageTicks; if (calculationOptions.Beta && Channeled) { float tickCastTime = proccedCastTime / Ticks; proccedTicks = (float)Math.Floor(baseCastTime / tickCastTime); castTime = proccedTicks * tickCastTime; } if (InterruptFactor > 0) { proccedCastTime = proccedCastTime * (1 + InterruptFactor * maxPushback) - (maxPushback * 0.5f) * maxPushback * InterruptFactor; } proccedCastTime += latency; if (proccedCastTime < proccedGcd + calculationOptions.LatencyGCD) { proccedCastTime = proccedGcd + calculationOptions.LatencyGCD; } int chancesToProc = (int)(((int)Math.Floor(effect.Duration / proccedCastTime) + 1) * proccedTicks); if (!(Instant || pom)) { chancesToProc -= 1; } if (AreaEffect) { chancesToProc *= calculationOptions.AoeTargets; } spellHasteRating = rawHaste + procHaste * (1 - (float)Math.Pow(1 - effect.Chance * critRate, chancesToProc)); //Haste = rawHaste + castingState.BasicStats.SpellHasteFor5SecOnCrit_50 * ProcBuffUp(1 - (float)Math.Pow(1 - 0.5f * CritRate, HitProcs), 5, CastTime); castingSpeed *= (1 + spellHasteRating / 1000f * levelScalingFactor); globalCooldown = Math.Max(Spell.GlobalCooldownLimit, 1.5f / castingSpeed); castTime = baseCastTime / castingSpeed; if (calculationOptions.Beta && Channeled) { float tickCastTime = castTime / Ticks; averageTicks = (float)Math.Floor(baseCastTime / tickCastTime); castTime = averageTicks * tickCastTime; } if (InterruptFactor > 0) { castTime = castTime * (1 + InterruptFactor * maxPushback) - (maxPushback * 0.5f + latency) * maxPushback * InterruptFactor; } castTime += latency; if (castTime < globalCooldown + calculationOptions.LatencyGCD) { castTime = globalCooldown + calculationOptions.LatencyGCD; } } } // on use stacking items for (int i = 0; i < castingState.Solver.StackingHasteEffectCooldownsCount; i++) { EffectCooldown effectCooldown = castingState.Solver.StackingHasteEffectCooldowns[i]; if (castingState.EffectsActive(effectCooldown.Mask)) { Stats stats = effectCooldown.SpecialEffect.Stats; for (int j = 0; j < stats._rawSpecialEffectDataSize; j++) { SpecialEffect effect = stats._rawSpecialEffectData[j]; float procHaste = effect.Stats.HasteRating; if (procHaste > 0) { float procs = 0.0f; switch (effect.Trigger) { case Trigger.DamageSpellCast: case Trigger.SpellCast: procs = CastProcs; break; case Trigger.DamageSpellCrit: case Trigger.SpellCrit: procs = critRate * averageTicks; break; case Trigger.DamageSpellHit: case Trigger.SpellHit: procs = HitRate * averageTicks; break; } if (procs == 0.0f) { continue; } // until they put in some good trinkets with such effects just do a quick dirty calculation float effectHasteRating; if (procs > averageTicks) { // some 100% on cast procs, happens because AM has 6 cast procs and only 5 ticks effectHasteRating = effect.GetAverageStackSize(castTime / procs, 1.0f, 3.0f, effectCooldown.SpecialEffect.Duration) * procHaste; } else { effectHasteRating = effect.GetAverageStackSize(castTime / averageTicks, procs / averageTicks, 3.0f, effectCooldown.SpecialEffect.Duration) * procHaste; } castingSpeed /= (1 + spellHasteRating / 1000f * levelScalingFactor); spellHasteRating += effectHasteRating; castingSpeed *= (1 + spellHasteRating / 1000f * levelScalingFactor); globalCooldown = Math.Max(Spell.GlobalCooldownLimit, 1.5f / castingSpeed); castTime = baseCastTime / castingSpeed; if (calculationOptions.Beta && Channeled) { float tickCastTime = castTime / Ticks; averageTicks = (float)Math.Floor(baseCastTime / tickCastTime); castTime = averageTicks * tickCastTime; } if (InterruptFactor > 0) { castTime = castTime * (1 + InterruptFactor * maxPushback) - (maxPushback * 0.5f) * maxPushback * InterruptFactor; } castTime += latency; if (castTime < globalCooldown + calculationOptions.LatencyGCD) { castTime = globalCooldown + calculationOptions.LatencyGCD; } } } } } } if (Channeled && calculationOptions.Beta) { channelReduction = 1 - averageTicks / Ticks; } // channeled pushback if (Channeled && InterruptFactor > 0) { int maxLostTicks = (int)Math.Ceiling(averageTicks * 0.25f * Math.Max(0, 1 - interruptProtection)); // pushbacks that happen up to pushbackCastTime cut the cast time to pushbackCastTime // pushbacks that happen after just terminate the channel // [---|---X---|---|---] castTime -= latency; float tickFactor = 0; for (int i = 0; i < maxLostTicks; i++) { tickFactor += InterruptFactor * castTime / averageTicks * (i + 1) / averageTicks; } tickFactor += InterruptFactor * (averageTicks - maxLostTicks) * castTime / averageTicks * maxLostTicks / averageTicks; if (calculationOptions.Beta) { channelReduction = 1 - averageTicks * (1 - tickFactor) / Ticks; } else { channelReduction = tickFactor; } castTime = castTime * (1 - tickFactor) + latency; } return(castTime); }