예제 #1
0
        public Stats getSpecialEffects(CalculationOptionsDPSDK calcOpts, SpecialEffect effect)
        {
            Stats    statsAverage = new Stats();
            Rotation rotation     = calcOpts.rotation;

            if (effect.Trigger == Trigger.Use)
            {
                effect.AccumulateAverageStats(statsAverage);
                foreach (SpecialEffect e in effect.Stats.SpecialEffects())
                {
                    statsAverage.Accumulate(this.getSpecialEffects(calcOpts, e), (effect.Duration / effect.Cooldown));
                }
            }
            else
            {
                double trigger             = 0f;
                float  chance              = 0f;
                float  unhastedAttackSpeed = 2f;
                switch (effect.Trigger)
                {
                case Trigger.MeleeCrit:
                case Trigger.PhysicalCrit:
                    trigger             = (1f / ((rotation.getMeleeSpecialsPerSecond() * (combatTable.DW ? 2f : 1f)) + (combatTable.combinedSwingTime != 0 ? 1f / combatTable.combinedSwingTime : 0.5f)));
                    chance              = combatTable.physCrits;
                    unhastedAttackSpeed = (combatTable.MH != null ? combatTable.MH.baseSpeed : 2.0f);
                    break;

                case Trigger.MeleeHit:
                case Trigger.PhysicalHit:
                    trigger             = (1f / ((rotation.getMeleeSpecialsPerSecond() * (combatTable.DW ? 2f : 1f)) + (combatTable.combinedSwingTime != 0 ? 1f / combatTable.combinedSwingTime : 0.5f)));
                    chance              = 1f - (combatTable.missedSpecial + combatTable.dodgedSpecial) * (1f - combatTable.totalMHMiss);
                    unhastedAttackSpeed = (combatTable.MH != null ? combatTable.MH.baseSpeed : 2.0f);
                    break;

                case Trigger.CurrentHandHit:
                    trigger = (1f / ((rotation.getMeleeSpecialsPerSecond()) + (combatTable.combinedSwingTime != 0 ? 1f / combatTable.combinedSwingTime : 0.5f)));
                    chance  = 1f - (combatTable.missedSpecial + combatTable.dodgedSpecial) * (1f - combatTable.totalMHMiss);
                    // TODO: need to know if this is MH or OH.
                    unhastedAttackSpeed = (combatTable.MH != null ? combatTable.MH.baseSpeed : 2.0f);
                    break;

                case Trigger.MainHandHit:
                    trigger             = (1f / ((rotation.getMeleeSpecialsPerSecond()) + (combatTable.combinedSwingTime != 0 ? 1f / combatTable.combinedSwingTime : 0.5f)));
                    chance              = 1f - (combatTable.missedSpecial + combatTable.dodgedSpecial) * (1f - combatTable.totalMHMiss);
                    unhastedAttackSpeed = (combatTable.MH != null ? combatTable.MH.baseSpeed : 2.0f);
                    break;

                case Trigger.OffHandHit:
                    trigger             = (1f / ((rotation.getMeleeSpecialsPerSecond()) + (combatTable.combinedSwingTime != 0 ? 1f / combatTable.combinedSwingTime : 0.5f)));
                    chance              = 1f - (combatTable.missedSpecial + combatTable.dodgedSpecial) * (1f - combatTable.totalMHMiss);
                    unhastedAttackSpeed = (combatTable.OH != null ? combatTable.OH.baseSpeed : 2.0f);
                    break;

                case Trigger.DamageDone:
                    trigger             = 1f / (((rotation.getMeleeSpecialsPerSecond() * (combatTable.DW ? 2f : 1f)) + rotation.getSpellSpecialsPerSecond()) + (combatTable.combinedSwingTime != 0 ? 1f / combatTable.combinedSwingTime: 0.5f));
                    chance              = (1f - (combatTable.missedSpecial + combatTable.dodgedSpecial)) * (1f - combatTable.totalMHMiss);
                    unhastedAttackSpeed = (combatTable.MH != null ? combatTable.MH.baseSpeed : 2.0f);
                    break;

                case Trigger.DamageOrHealingDone:
                    // Need to add Self Healing
                    trigger             = 1f / (((rotation.getMeleeSpecialsPerSecond() * (combatTable.DW ? 2f : 1f)) + rotation.getSpellSpecialsPerSecond()) + (combatTable.combinedSwingTime != 0 ? 1f / combatTable.combinedSwingTime : 0.5f));
                    chance              = (1f - (combatTable.missedSpecial + combatTable.dodgedSpecial)) * (1f - combatTable.totalMHMiss);
                    unhastedAttackSpeed = (combatTable.MH != null ? combatTable.MH.baseSpeed : 2.0f);
                    break;

                case Trigger.DamageSpellCast:
                case Trigger.SpellCast:
                case Trigger.DamageSpellHit:
                case Trigger.SpellHit:
                    trigger = 1f / rotation.getSpellSpecialsPerSecond();
                    chance  = 1f - combatTable.spellResist;
                    break;

                case Trigger.DamageSpellCrit:
                case Trigger.SpellCrit:
                    trigger = 1f / rotation.getSpellSpecialsPerSecond();
                    chance  = combatTable.spellCrits;
                    break;

                case Trigger.BloodStrikeHit:
                    trigger = rotation.CurRotationDuration / (rotation.BloodStrike * (combatTable.DW ? 2f : 1f));
                    chance  = 1f;
                    break;

                case Trigger.HeartStrikeHit:
                    trigger = rotation.CurRotationDuration / rotation.HeartStrike;
                    chance  = 1f;
                    break;

                case Trigger.BloodStrikeOrHeartStrikeHit:
                    trigger = rotation.CurRotationDuration / ((rotation.BloodStrike + rotation.HeartStrike) * (combatTable.DW ? 2f : 1f));
                    chance  = 1f;
                    break;

                case Trigger.ObliterateHit:
                    trigger = rotation.CurRotationDuration / (rotation.Obliterate * (combatTable.DW ? 2f : 1f));
                    chance  = 1f;
                    break;

                case Trigger.ScourgeStrikeHit:
                    trigger = rotation.CurRotationDuration / rotation.ScourgeStrike;
                    chance  = 1f;
                    break;

                case Trigger.DeathStrikeHit:
                    trigger = rotation.CurRotationDuration / rotation.DeathStrike;
                    chance  = 1f;
                    break;

                case Trigger.PlagueStrikeHit:
                    trigger = rotation.CurRotationDuration / (rotation.PlagueStrike * (combatTable.DW ? 2f : 1f));
                    chance  = 1f;
                    break;

                case Trigger.DoTTick:
                    trigger = (rotation.BloodPlague + rotation.FrostFever) / 3;
                    chance  = 1f;
                    break;
                }
#if false // Pull out the embedded handling in this situation.
                foreach (SpecialEffect e in effect.Stats.SpecialEffects())
                {
                    statsAverage.Accumulate(this.getSpecialEffects(calcOpts, e));
                }
 #endif
                if (effect.MaxStack > 1)
                {
                    float timeToMax    = (float)Math.Min(calcOpts.FightLength * 60, effect.GetChance(unhastedAttackSpeed) * trigger * effect.MaxStack);
                    float buffDuration = calcOpts.FightLength * 60f;
                    if (effect.Stats.AttackPower == 250f || effect.Stats.AttackPower == 215f || effect.Stats.HasteRating == 57f || effect.Stats.HasteRating == 64f)
                    {
                        buffDuration = 20f;
                    }
                    if (timeToMax * .5f > buffDuration)
                    {
                        timeToMax = 2 * buffDuration;
                    }
                    statsAverage.Accumulate(effect.Stats, effect.GetAverageStackSize((float)trigger, chance, unhastedAttackSpeed, buffDuration));
                }
                else
                {
                    effect.AccumulateAverageStats(statsAverage, (float)trigger, chance, unhastedAttackSpeed, calcOpts.FightLength * 60);
                }
            }

            return(statsAverage);
        }
예제 #2
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);
        }
예제 #3
0
        public void Solve(Character character, ref CharacterCalculationsMoonkin calcs)
        {
            CalculationOptionsMoonkin calcOpts = character.CalculationOptions as CalculationOptionsMoonkin;
            DruidTalents talents = character.DruidTalents;

            procEffects = new List <ProcEffect>();
            RecreateSpells(talents, ref calcs);
            cachedResults = new Dictionary <string, RotationData>();

            float trinketDPS     = 0.0f;
            float baseSpellPower = calcs.SpellPower;
            float baseHit        = GetSpellHit(calcs);
            float baseCrit       = calcs.SpellCrit;
            float baseHaste      = calcs.SpellHaste;

            BuildProcList(calcs);

            float         maxDamageDone      = 0.0f;
            float         maxBurstDamageDone = 0.0f;
            SpellRotation maxBurstRotation   = rotations[0];
            SpellRotation maxRotation        = rotations[0];

            float manaPool = GetEffectiveManaPool(character, calcOpts, calcs);

            // Do tree calculations: Calculate damage per cast.
            float treeDamage = (talents.ForceOfNature == 1) ? DoTreeCalcs(baseSpellPower, calcs.BasicStats.PhysicalHaste, calcs.BasicStats.ArmorPenetration, calcs.BasicStats.PhysicalCrit, calcOpts.TreantLifespan, character.DruidTalents.Brambles) : 0.0f;
            // Extend that to number of casts per fight.
            float treeCasts = (float)Math.Floor(calcs.FightLength / 3) + 1.0f;

            // Partial cast: If the fight lasts 3.x minutes and x is less than 0.5 (30 sec tree duration), calculate a partial cast
            if ((int)calcs.FightLength % 3 == 0 && calcs.FightLength - (int)calcs.FightLength < 0.5)
            {
                treeCasts += (calcs.FightLength - (int)calcs.FightLength) / 0.5f - 1.0f;
            }
            treeDamage *= treeCasts;
            // Multiply by raid-wide damage increases.
            treeDamage *= (1 + calcs.BasicStats.BonusDamageMultiplier) * (1 + calcs.BasicStats.BonusPhysicalDamageMultiplier);
            // Calculate the DPS averaged over the fight length.
            float treeDPS = treeDamage / (calcs.FightLength * 60.0f);
            // Calculate mana usage for trees.
            float treeManaUsage = (float)Math.Ceiling(treeCasts) * CalculationsMoonkin.BaseMana * 0.12f;

            manaPool -= talents.ForceOfNature == 1 ? treeManaUsage : 0.0f;

            // Do Starfall calculations.
            bool  starfallGlyph    = talents.GlyphOfStarfall;
            Buff  tier102PieceBuff = character.ActiveBuffs.Find(theBuff => theBuff.Name == "Lasherweave Regalia (T10) 2 Piece Bonus");
            float numberOfStarHits = 10.0f;
            float starfallDamage   = (talents.Starfall == 1) ? DoStarfallCalcs(baseSpellPower, baseHit, baseCrit,
                                                                               (1 + calcs.BasicStats.BonusDamageMultiplier) *
                                                                               (1 + calcs.BasicStats.BonusSpellPowerMultiplier) *
                                                                               (1 + calcs.BasicStats.BonusArcaneDamageMultiplier) *
                                                                               (1 + (talents.GlyphOfFocus ? 0.1f : 0.0f)), Wrath.CriticalDamageModifier, out numberOfStarHits) : 0.0f;
            float starfallCD       = 1.5f - (starfallGlyph ? 0.5f : 0.0f);
            float numStarfallCasts = (float)Math.Floor(calcs.FightLength / starfallCD) + 1.0f;
            // Partial cast: If the difference between fight length and total starfall CD time is less than 10 seconds (duration),
            // calculate a partial cast
            float starfallDiff = calcs.FightLength * 60.0f - (numStarfallCasts - 1) * starfallCD * 60.0f;

            if (starfallDiff > 0 && starfallDiff < 10)
            {
                numStarfallCasts += starfallDiff / 60.0f / (1.0f / 6.0f) - 1.0f;
            }
            starfallDamage *= numStarfallCasts;
            float starfallManaUsage = (float)Math.Ceiling(numStarfallCasts) * CalculationsMoonkin.BaseMana * 0.39f;

            manaPool -= talents.Starfall == 1 ? starfallManaUsage : 0.0f;

            // Simple faerie fire mana calc
            float faerieFireCasts = (float)Math.Floor(calcs.FightLength / 5) + (calcs.FightLength % 5 != 0 ? 1.0f : 0.0f);
            float faerieFireMana  = faerieFireCasts * CalculationsMoonkin.BaseMana * 0.08f;

            if (talents.ImprovedFaerieFire > 0)
            {
                manaPool -= faerieFireMana;
            }

            // Calculate effect of casting Starfall/Treants/ImpFF (regular FF is assumed to be provided by a feral)
            float globalCooldown = 1.5f / (1 + baseHaste) + calcs.Latency;
            float treantTime     = (talents.ForceOfNature == 1) ? globalCooldown * (float)Math.Ceiling(treeCasts) : 0.0f;
            float starfallTime   = (talents.Starfall == 1) ? globalCooldown * (float)Math.Ceiling(numStarfallCasts) : 0.0f;
            float faerieFireTime = (talents.ImprovedFaerieFire > 0) ? globalCooldown * faerieFireCasts : 0.0f;

            float totalTimeInRotation   = calcs.FightLength * 60.0f - (treantTime + starfallTime + faerieFireTime);
            float percentTimeInRotation = totalTimeInRotation / (calcs.FightLength * 60.0f);

#if RAWR3
            BossOptions bossOpts = character.BossOptions;
            if (bossOpts == null)
            {
                bossOpts = new BossOptions();
            }

            float fearShare         = (bossOpts.FearingTargsDur / 1000) / bossOpts.FearingTargsFreq;
            float stunShare         = (bossOpts.StunningTargsDur / 1000) / bossOpts.StunningTargsFreq;
            float invulnerableShare = bossOpts.TimeBossIsInvuln / bossOpts.BerserkTimer;

            List <Attack> attacks = bossOpts.GetFilteredAttackList(ATTACK_TYPES.AT_AOE);
            attacks.AddRange(bossOpts.GetFilteredAttackList(ATTACK_TYPES.AT_RANGED));
            int   movementCount           = attacks.Count;
            float assumedMovementDuration = 2.0f;   // Assume 2 seconds per move
            float accumulatedDurations    = 0.0f;
            foreach (Attack a in attacks)
            {
                accumulatedDurations += a.AttackSpeed;
            }

            float movementShare = (movementCount == 0 ? 0 : assumedMovementDuration / (accumulatedDurations / movementCount) / (1 + calcs.BasicStats.MovementSpeed));

            percentTimeInRotation -= movementShare + fearShare + stunShare + invulnerableShare;
#endif

            float manaGained = manaPool - calcs.BasicStats.Mana;

            float oldArcaneMultiplier = calcs.BasicStats.BonusArcaneDamageMultiplier;
            float oldNatureMultiplier = calcs.BasicStats.BonusNatureDamageMultiplier;

            foreach (SpellRotation rot in rotations)
            {
                rot.Solver = this;
                float             accumulatedDamage = 0.0f;
                float             totalUpTime       = 0.0f;
                float[]           spellDetails      = new float[NUM_SPELL_DETAILS];
                List <ProcEffect> activatedEffects  = new List <ProcEffect>();
                List <ProcEffect> alwaysUpEffects   = new List <ProcEffect>();

                // Pre-calculate rotational variables with base stats
                rot.DamageDone(talents, calcs, baseSpellPower, baseHit, baseCrit, baseHaste);

                // Reset variables modified in the pre-loop to base values
                float currentSpellPower = baseSpellPower;
                float currentCrit       = baseCrit;
                float currentHaste      = baseHaste;
                calcs.BasicStats.BonusArcaneDamageMultiplier = oldArcaneMultiplier;
                calcs.BasicStats.BonusNatureDamageMultiplier = oldNatureMultiplier;
                // Calculate spell power/spell damage modifying trinkets in a separate pre-loop
                foreach (ProcEffect proc in procEffects)
                {
                    if (proc.Effect.Stats.SpellPower > 0 || proc.Effect.Stats.Spirit > 0)
                    {
                        float procSpellPower = proc.Effect.Stats.SpellPower;
                        if (proc.Effect.Stats.Spirit > 0)
                        {
                            procSpellPower += proc.Effect.Stats.Spirit * (0.1f * talents.ImprovedMoonkinForm);
                        }

                        float triggerInterval = 0.0f, triggerChance = 1.0f;
                        switch (proc.Effect.Trigger)
                        {
                        case Trigger.DamageDone:
                        case Trigger.DamageOrHealingDone:
                            triggerInterval = ((rot.Duration / rot.CastCount) + (rot.Duration / (rot.MoonfireTicks + rot.InsectSwarmTicks))) / 2.0f;
                            break;

                        case Trigger.Use:
                            break;

                        case Trigger.SpellHit:
                        case Trigger.DamageSpellHit:
                            triggerInterval = rot.Duration / rot.CastCount;
                            triggerChance   = GetSpellHit(calcs);
                            break;

                        case Trigger.SpellCrit:
                        case Trigger.DamageSpellCrit:
                            triggerInterval = rot.Duration / (rot.CastCount - rot.InsectSwarmCasts);
                            triggerChance   = baseCrit;
                            break;

                        case Trigger.SpellCast:
                        case Trigger.DamageSpellCast:
                            triggerInterval = rot.Duration / rot.CastCount;
                            break;

                        case Trigger.MoonfireCast:
                            triggerInterval = rot.Duration / rot.MoonfireCasts;
                            break;

                        case Trigger.DoTTick:
                        case Trigger.InsectSwarmOrMoonfireTick:
                            triggerInterval = rot.Duration / (rot.InsectSwarmTicks + rot.MoonfireTicks);
                            break;

                        case Trigger.MoonfireTick:
                            triggerInterval = rot.Duration / rot.MoonfireTicks;
                            break;

                        case Trigger.InsectSwarmTick:
                            triggerInterval = rot.Duration / rot.InsectSwarmTicks;
                            break;

                        default:
                            triggerChance = 0.0f;
                            break;
                        }
                        if (triggerChance > 0)
                        {
                            currentSpellPower += (proc.Effect.MaxStack > 1 ? proc.Effect.GetAverageStackSize(triggerInterval, triggerChance, 3.0f, calcs.FightLength * 60.0f) : 1) *
                                                 proc.Effect.GetAverageUptime(triggerInterval, triggerChance) * procSpellPower;
                        }
                    }
                    // 2T10 (both if statements, which is why it isn't else-if)
                    if (proc.Effect.Stats.BonusArcaneDamageMultiplier > 0)
                    {
                        calcs.BasicStats.BonusArcaneDamageMultiplier += proc.Effect.GetAverageUptime(rot.Duration / rot.CastCount, 1f) * proc.Effect.Stats.BonusArcaneDamageMultiplier;
                    }
                    if (proc.Effect.Stats.BonusNatureDamageMultiplier > 0)
                    {
                        calcs.BasicStats.BonusNatureDamageMultiplier += proc.Effect.GetAverageUptime(rot.Duration / rot.CastCount, 1f) * proc.Effect.Stats.BonusNatureDamageMultiplier;
                    }
                    if (proc.Effect.Stats._rawSpecialEffectDataSize > 0)
                    {
                        SpecialEffect childEffect = proc.Effect.Stats._rawSpecialEffectData[0];
                        // Nevermelting Ice Crystal
                        if (childEffect.Stats.CritRating != 0)
                        {
                            float maxStack             = proc.Effect.Stats.CritRating;
                            float numNegativeStacks    = childEffect.GetAverageStackSize(rot.Duration / (rot.CastCount - rot.InsectSwarmCasts), baseCrit, 3.0f, proc.Effect.Duration);
                            float averageNegativeValue = childEffect.Stats.CritRating * numNegativeStacks;
                            float averageCritRating    = maxStack + averageNegativeValue;
                            currentCrit += StatConversion.GetSpellCritFromRating(averageCritRating * proc.Effect.GetAverageUptime(0f, 1f));
                        }
                        // Fetish of Volatile Power
                        else if (childEffect.Stats.HasteRating != 0)
                        {
                            currentHaste += StatConversion.GetSpellHasteFromRating(childEffect.Stats.HasteRating * childEffect.GetAverageStackSize(rot.Duration / rot.CastCount, 1f, 3.0f, proc.Effect.Duration) * proc.Effect.GetAverageUptime(0f, 1f));
                        }
                    }
                }
                // Calculate damage and mana contributions for non-stat-boosting trinkets
                // Separate stat-boosting proc trinkets into their own list
                foreach (ProcEffect proc in procEffects)
                {
                    if (proc.CalculateDPS != null)
                    {
                        accumulatedDamage += proc.CalculateDPS(rot, calcs, currentSpellPower, baseHit, currentCrit, currentHaste) * rot.Duration;
                    }
                    else if (proc.Activate != null)
                    {
                        float upTime = proc.UpTime(rot, calcs);
                        // Procs with 100% uptime should be activated and not put into the combination loop
                        if (upTime == 1)
                        {
                            alwaysUpEffects.Add(proc);
                            proc.Activate(character, calcs, ref currentSpellPower, ref baseHit, ref currentCrit, ref currentHaste);
                        }
                        // Procs with uptime 0 < x < 100 should be activated
                        else if (upTime > 0)
                        {
                            activatedEffects.Add(proc);
                        }
                    }
                    else if (proc.CalculateMP5 != null)
                    {
                        manaGained += proc.CalculateMP5(rot, calcs, currentSpellPower, baseHit, currentCrit, currentHaste) / 5.0f * calcs.FightLength * 60.0f;
                    }
                }
                // Calculate stat-boosting trinkets, taking into effect interactions with other stat-boosting procs
                int sign = 1;
                Dictionary <int, float>   cachedDamages = new Dictionary <int, float>();
                Dictionary <int, float>   cachedUptimes = new Dictionary <int, float>();
                Dictionary <int, float[]> cachedDetails = new Dictionary <int, float[]>();
                // Iterate over the entire set of trinket combinations (each trinket by itself, 2 at a time, ...)
                for (int i = 1; i <= activatedEffects.Count; ++i)
                {
                    // Create a new combination generator for this "level" of trinket interaction
                    CombinationGenerator gen = new CombinationGenerator(activatedEffects.Count, i);
                    // Iterate over all combinations
                    while (gen.HasNext())
                    {
                        float tempUpTime    = 1.0f;
                        int[] vals          = gen.GetNext();
                        int   pairs         = 0;
                        int   lengthCounter = 0;
                        // Activate the trinkets, calculate the damage and uptime, then deactivate them
                        foreach (int idx in vals)
                        {
                            pairs |= 1 << idx;
                            ++lengthCounter;
                            activatedEffects[idx].Activate(character, calcs, ref currentSpellPower, ref baseHit, ref currentCrit, ref currentHaste);
                        }
                        float tempDPS = rot.DamageDone(talents, calcs, currentSpellPower, baseHit, currentCrit, currentHaste) / rot.Duration;
                        spellDetails[0]  = Starfire.DamagePerHit;
                        spellDetails[1]  = Wrath.DamagePerHit;
                        spellDetails[2]  = Moonfire.DamagePerHit + Moonfire.DotEffect.DamagePerHit;
                        spellDetails[3]  = InsectSwarm.DotEffect.DamagePerHit;
                        spellDetails[4]  = Starfire.CastTime;
                        spellDetails[5]  = Wrath.CastTime;
                        spellDetails[6]  = Moonfire.CastTime;
                        spellDetails[7]  = InsectSwarm.CastTime;
                        spellDetails[8]  = Starfire.NGCastTime;
                        spellDetails[9]  = Wrath.NGCastTime;
                        spellDetails[10] = Starfire.ManaCost;
                        spellDetails[11] = Wrath.ManaCost;
                        spellDetails[12] = Moonfire.ManaCost;
                        spellDetails[13] = InsectSwarm.ManaCost;
                        foreach (int idx in vals)
                        {
                            tempUpTime *= activatedEffects[idx].UpTime(rot, calcs);
                            activatedEffects[idx].Deactivate(character, calcs, ref currentSpellPower, ref baseHit, ref currentCrit, ref currentHaste);
                        }
                        if (tempUpTime == 0)
                        {
                            continue;
                        }
                        // Adjust previous probability tables by the current factor
                        // At the end of the algorithm, this ensures that the probability table will contain the individual
                        // probabilities of each effect or set of effects.
                        // These adjustments only need to be made for higher levels of the table, and if the current probability is > 0.
                        if (lengthCounter > 1)
                        {
                            List <int> keys = new List <int>(cachedUptimes.Keys);
                            foreach (int subset in keys)
                            {
                                // Truly a subset?
                                if ((pairs & subset) != subset)
                                {
                                    continue;
                                }

                                // Calculate the "layer" of the current subset by getting the set bit count.
                                int subsetLength = 0;
                                for (int j = subset; j > 0; ++subsetLength)
                                {
                                    j &= --j;
                                }

                                // Set the sign of the operation: Evenly separated layers are added, oddly separated layers are subtracted
                                int newSign = ((lengthCounter - subsetLength) % 2 == 0) ? 1 : -1;

                                // Adjust by current uptime * sign of operation.
                                cachedUptimes[subset] += newSign * tempUpTime;
                            }
                        }
                        // Cache the results to be calculated later
                        cachedUptimes[pairs] = tempUpTime;
                        cachedDamages[pairs] = tempDPS;
                        cachedDetails[pairs] = new float[NUM_SPELL_DETAILS];
                        Array.Copy(spellDetails, cachedDetails[pairs], NUM_SPELL_DETAILS);
                        totalUpTime += sign * tempUpTime;
                    }
                    sign = -sign;
                }
                float accumulatedDPS = 0.0f;
                Array.Clear(spellDetails, 0, spellDetails.Length);
                // Apply the above-calculated probabilities to the previously stored damage calculations and add to the result.
                foreach (KeyValuePair <int, float> kvp in cachedUptimes)
                {
                    if (kvp.Value == 0)
                    {
                        continue;
                    }
                    accumulatedDPS += kvp.Value * cachedDamages[kvp.Key];
                    for (int i = 0; i < NUM_SPELL_DETAILS; ++i)
                    {
                        spellDetails[i] += kvp.Value * cachedDetails[kvp.Key][i];
                    }
                }
                float damageDone = rot.DamageDone(talents, calcs, currentSpellPower, baseHit, currentCrit, currentHaste);
                accumulatedDPS   += (1 - totalUpTime) * damageDone / rot.Duration;
                spellDetails[0]  += (1 - totalUpTime) * Starfire.DamagePerHit;
                spellDetails[1]  += (1 - totalUpTime) * Wrath.DamagePerHit;
                spellDetails[2]  += (1 - totalUpTime) * Moonfire.DamagePerHit + Moonfire.DotEffect.DamagePerHit;
                spellDetails[3]  += (1 - totalUpTime) * InsectSwarm.DotEffect.DamagePerHit;
                spellDetails[4]  += (1 - totalUpTime) * Starfire.CastTime;
                spellDetails[5]  += (1 - totalUpTime) * Wrath.CastTime;
                spellDetails[6]  += (1 - totalUpTime) * Moonfire.CastTime;
                spellDetails[7]  += (1 - totalUpTime) * InsectSwarm.CastTime;
                spellDetails[8]  += (1 - totalUpTime) * Starfire.NGCastTime;
                spellDetails[9]  += (1 - totalUpTime) * Wrath.NGCastTime;
                spellDetails[10] += (1 - totalUpTime) * Starfire.ManaCost;
                spellDetails[11] += (1 - totalUpTime) * Wrath.ManaCost;
                spellDetails[12] += (1 - totalUpTime) * Moonfire.ManaCost;
                spellDetails[13] += (1 - totalUpTime) * InsectSwarm.ManaCost;

                accumulatedDamage += accumulatedDPS * rot.Duration;

                float burstDPS     = accumulatedDamage / rot.Duration * percentTimeInRotation;
                float sustainedDPS = burstDPS;
                float timeToOOM    = (manaPool / rot.RotationData.ManaUsed) * rot.Duration;
                if (timeToOOM <= 0)
                {
                    timeToOOM = calcs.FightLength * 60.0f;                   // Happens when ManaUsed is less than 0
                }
                if (timeToOOM < calcs.FightLength * 60.0f)
                {
                    rot.RotationData.TimeToOOM = new TimeSpan(0, (int)(timeToOOM / 60), (int)(timeToOOM % 60));
                    sustainedDPS = burstDPS * timeToOOM / (calcs.FightLength * 60.0f);
                }
                float t10StarfallDamage = starfallDamage;
                // Approximate the effect of the 2T10 set bonus
                if (tier102PieceBuff != null && character.DruidTalents.OmenOfClarity == 1)
                {
                    Stats.SpecialEffectEnumerator enumerator = tier102PieceBuff.Stats.SpecialEffects();
                    enumerator.MoveNext();
                    SpecialEffect effect = enumerator.Current;
                    float         upTime = effect.GetAverageUptime(rot.Duration / rot.CastCount, 1f);
                    t10StarfallDamage = upTime * (starfallDamage * (1 + effect.Stats.BonusArcaneDamageMultiplier)) + (1 - upTime) * starfallDamage;
                }
                float starfallDPS = t10StarfallDamage / (calcs.FightLength * 60.0f);
                burstDPS     += trinketDPS + treeDPS + starfallDPS;
                sustainedDPS += trinketDPS + treeDPS + starfallDPS;

                rot.StarfallDamage        = t10StarfallDamage / numStarfallCasts;
                rot.StarfallStars         = numberOfStarHits;
                rot.RotationData.BurstDPS = burstDPS;
                rot.RotationData.DPS      = sustainedDPS;
                rot.StarfireAvgHit        = spellDetails[0];
                rot.WrathAvgHit           = spellDetails[1];
                rot.MoonfireAvgHit        = spellDetails[2];
                rot.InsectSwarmAvgHit     = spellDetails[3];
                rot.StarfireAvgCast       = spellDetails[4];
                rot.WrathAvgCast          = spellDetails[5];
                rot.MoonfireCastTime      = spellDetails[6];
                rot.InsectSwarmCastTime   = spellDetails[7];
                rot.StarfireNGCastTime    = spellDetails[8];
                rot.WrathNGCastTime       = spellDetails[9];
                rot.StarfireManaCost      = spellDetails[10];
                rot.WrathManaCost         = spellDetails[11];
                rot.MoonfireManaCost      = spellDetails[12];
                rot.InsectSwarmManaCost   = spellDetails[13];

                // Update the sustained DPS rotation if any one of the following three cases is true:
                // 1) No user rotation is selected and sustained DPS is maximum
                // 2) A user rotation is selected, Eclipse is not present, and the user rotation matches the current rotation
                // 3) A user rotation is selected, Eclipse is present, and the user rotation's dot spells matches this rotation's
                if ((calcOpts.UserRotation == "None" && sustainedDPS > maxDamageDone) ||
                    (character.DruidTalents.Eclipse == 0 && calcOpts.UserRotation == rot.Name) ||
                    (character.DruidTalents.Eclipse > 0 && (calcOpts.UserRotation == rot.Name.Replace("Filler", "SF") ||
                                                            calcOpts.UserRotation == rot.Name.Replace("Filler", "W"))))
                {
                    maxDamageDone = sustainedDPS;
                    maxRotation   = rot;
                }
                if (burstDPS > maxBurstDamageDone)
                {
                    maxBurstDamageDone = burstDPS;
                    maxBurstRotation   = rot;
                }
                rot.ManaGained += manaGained / (calcs.FightLength * 60.0f) * rot.Duration;
                rot.RotationData.ManaGained += manaGained / (calcs.FightLength * 60.0f) * rot.Duration;
                if (rot.Name.Contains("Filler"))
                {
                    cachedResults[rot.Name.Replace("Filler", "SF")] = rot.RotationData;
                    cachedResults[rot.Name.Replace("Filler", "W")]  = rot.RotationData;
                }
                else
                {
                    cachedResults[rot.Name] = rot.RotationData;
                }

                // Deactivate always-up procs
                foreach (ProcEffect proc in alwaysUpEffects)
                {
                    proc.Deactivate(character, calcs, ref currentSpellPower, ref baseHit, ref currentCrit, ref currentHaste);
                }
            }
            // Present the findings to the user.
            calcs.TreantDamage     = treeDamage / treeCasts;
            calcs.StarfallMana     = starfallManaUsage / numStarfallCasts;
            calcs.SelectedRotation = maxRotation;
            calcs.BurstDPSRotation = maxBurstRotation;
            calcs.SubPoints        = new float[] { maxDamageDone, maxBurstDamageDone };
            calcs.OverallPoints    = calcs.SubPoints[0] + calcs.SubPoints[1];
            calcs.Rotations        = cachedResults;
        }
예제 #4
0
        private float ApplySpecialEffect(SpecialEffect effect, DPSWarrCharacter charStruct, Dictionary<Trigger, float> triggerIntervals, Dictionary<Trigger, float> triggerChances, ref Base.StatsWarrior applyTo)
        {
#if DEBUG
            //ConstructionCounts["ApplySpecialEffect"]++;
#endif
            float fightDuration = charStruct.BossOpts.BerserkTimer;
            float fightDuration2Pass = charStruct.CalcOpts.SE_UseDur ? fightDuration : 0;

            Stats effectStats = effect.Stats;

            float upTime = 0f;
            //float avgStack = 1f;
            /*if (effect.Stats.TargetArmorReduction > 0f || effect.Stats.ArmorPenetrationRating > 0f) {
                //int j = 0;
            }*/
            if (effect.Trigger == Trigger.Use) {
                if (effect.Stats._rawSpecialEffectDataSize == 1) {
                    upTime = effect.GetAverageUptime(0f, 1f, charStruct.CombatFactors.CMHItemSpeed, fightDuration2Pass);
                    //float uptime =  (effect.Cooldown / fightDuration);
                    List<SpecialEffect> nestedEffect = new List<SpecialEffect>();
                    nestedEffect.Add(effect.Stats._rawSpecialEffectData[0]);
                    Base.StatsWarrior _stats2 = new Base.StatsWarrior();
                    ApplySpecialEffect(effect.Stats._rawSpecialEffectData[0], charStruct, triggerIntervals, triggerChances, ref _stats2);
                    effectStats = _stats2;
                } else {
                    upTime = effect.GetAverageStackSize(0f, 1f, charStruct.CombatFactors.CMHItemSpeed, fightDuration2Pass); 
                }
            } else if (effect.Duration == 0f && triggerIntervals.ContainsKey(effect.Trigger) && !float.IsInfinity(triggerIntervals[effect.Trigger])) {
                upTime = effect.GetAverageProcsPerSecond(triggerIntervals[effect.Trigger], 
                                                         triggerChances[effect.Trigger],
                                                         charStruct.CombatFactors.CMHItemSpeed,
                                                         fightDuration2Pass);
            } else if (effect.Trigger == Trigger.ExecuteHit) {
                upTime = effect.GetAverageStackSize(triggerIntervals[effect.Trigger], 
                                                         triggerChances[effect.Trigger],
                                                         charStruct.CombatFactors.CMHItemSpeed,
                                                         fightDuration2Pass * (float)charStruct.BossOpts.Under20Perc);
            } else if (triggerIntervals.ContainsKey(effect.Trigger) && !float.IsInfinity(triggerIntervals[effect.Trigger])) {
                upTime = effect.GetAverageStackSize(triggerIntervals[effect.Trigger], 
                                                         triggerChances[effect.Trigger],
                                                         charStruct.CombatFactors.CMHItemSpeed,
                                                         fightDuration2Pass);
            }

            if (upTime > 0f) {
                if (effect.Duration == 0f) {
                    applyTo.Accumulate(effectStats, upTime * fightDuration);
                } else if (upTime <= effect.MaxStack) {
                    applyTo.Accumulate(effectStats, upTime);
                }
                return upTime;
            }
            return 0f;
        }
예제 #5
0
 private float GetAverageFactor(SpecialEffect effect)
 {
     float triggerInterval;
     float triggerChance;
     if (GetTriggerData(effect, out triggerInterval, out triggerChance))
     {
         float durationMultiplier = 1;
         if (effect.LimitedToExecutePhase)
         {
             durationMultiplier = CastingState.CalculationOptions.MoltenFuryPercentage;
         }
         if (effect.MaxStack > 1)
         {
             return durationMultiplier * effect.GetAverageStackSize(triggerInterval, triggerChance, 3f, durationMultiplier * CastingState.CalculationOptions.FightDuration);
         }
         else if (effect.Duration > 0)
         {
             return durationMultiplier * effect.GetAverageUptime(triggerInterval, triggerChance, 3f, durationMultiplier * CastingState.CalculationOptions.FightDuration);
         }
         else
         {
             return durationMultiplier * effect.GetAverageProcsPerSecond(triggerInterval, triggerChance, 3f, durationMultiplier * CastingState.CalculationOptions.FightDuration);
         }
     }
     return 0;
 }