Exemple #1
0
 public CombatFactors(Character character, Stats stats, CalculationOptionsDPSWarr calcOpts, BossOptions bossOpts) {
     Char = character;
     MH = Char == null || Char.MainHand == null ? new Knuckles() : Char.MainHand.Item;
     OH = Char == null || Char.OffHand  == null || Char.WarriorTalents.TitansGrip == 0 ? null : Char.OffHand.Item;
     Talents = Char == null || Char.WarriorTalents == null ? new WarriorTalents() : Char.WarriorTalents;
     //TalentsCata = Char == null || Char.WarriorTalentsCata == null ? new WarriorTalentsCata() : Char.WarriorTalentsCata;
     CalcOpts = (calcOpts == null ? new CalculationOptionsDPSWarr() : calcOpts);
     BossOpts = (bossOpts == null ? new BossOptions() : bossOpts);
     StatS = stats;
     critProcs = new WeightedStat[] { new WeightedStat() { Chance = 1f, Value = 0f } };
     InvalidateCache();
     // Optimizations
     
     //Set_c_values();
 }
        public CombatFactors(Character character, Base.StatsWarrior stats, CalculationOptionsDPSWarr calcOpts, BossOptions bossOpts) {
            Char = character;
            MH = Char == null || Char.MainHand == null ? new Knuckles() : Char.MainHand.Item;
            OH = Char == null || Char.OffHand  == null || (Char.WarriorTalents.TitansGrip == 0 && Char.WarriorTalents.SingleMindedFury == 0) ? null : Char.OffHand.Item;
            Talents = Char == null || Char.WarriorTalents == null ? new WarriorTalents() : Char.WarriorTalents;
            CalcOpts = (calcOpts == null ? new CalculationOptionsDPSWarr() : calcOpts);
            BossOpts = (bossOpts == null ? new BossOptions() : bossOpts);
            StatS = stats;
            CritProcs = new WeightedStat[] { new WeightedStat() { Chance = 1f, Value = 0f } };
            InvalidateCache();
            // Optimizations
            
            //SetCvalues();
#if DEBUG
            //ConstructionCounts["CombatFactors"]++;
#endif
        }
Exemple #3
0
        public CombatFactors(Character character, Base.StatsWarrior stats, CalculationOptionsDPSWarr calcOpts, BossOptions bossOpts)
        {
            Char      = character;
            MH        = Char == null || Char.MainHand == null ? new Knuckles() : Char.MainHand.Item;
            OH        = Char == null || Char.OffHand == null || (Char.WarriorTalents.TitansGrip == 0 && Char.WarriorTalents.SingleMindedFury == 0) ? null : Char.OffHand.Item;
            Talents   = Char == null || Char.WarriorTalents == null ? new WarriorTalents() : Char.WarriorTalents;
            CalcOpts  = (calcOpts == null ? new CalculationOptionsDPSWarr() : calcOpts);
            BossOpts  = (bossOpts == null ? new BossOptions() : bossOpts);
            StatS     = stats;
            CritProcs = new WeightedStat[] { new WeightedStat()
                                             {
                                                 Chance = 1f, Value = 0f
                                             } };
            InvalidateCache();
            // Optimizations

            //SetCvalues();
#if DEBUG
            //ConstructionCounts["CombatFactors"]++;
#endif
        }
Exemple #4
0
        protected void Initialize(Character character, Stats stats, bool useSpellHit, bool alwaysHit, int delta)
        {
            Char             = character;
            StatS            = stats;
            this.useSpellHit = useSpellHit;
            LevelDelta       = delta;
            critProcs        = new WeightedStat[] { new WeightedStat()
                                                    {
                                                        Chance = 1f, Value = 0f
                                                    } };

            /*// Defaults
             * Miss
             * Dodge
             * Parry
             * Block
             * Glance
             * Critical
             * Hit*/
            // Start a calc
            Reset(alwaysHit);
        }
 protected void Initialize(Character character, Stats stats, bool useSpellHit, bool alwaysHit, int delta) {
     Char = character;
     StatS = stats;
     this.useSpellHit = useSpellHit;
     LevelDelta = delta;
     critProcs = new WeightedStat[] { new WeightedStat() { Chance = 1f, Value = 0f } };
     /*// Defaults
     Miss 
     Dodge
     Parry
     Block
     Glance
     Critical
     Hit*/
     // Start a calc            
     Reset(alwaysHit);            
 }
        private void CalcHasteAndManaProcs()
        {
            float nonProcHaste = StatUtils.CalcSpellHaste(PreProcStats);

            if (Options.NoProcs)
            {
                WeightedStat staticHaste = new WeightedStat();
                staticHaste.Chance = 1f;
                staticHaste.Value  = nonProcHaste;
                Haste = new List <WeightedStat> {
                    staticHaste
                };
                AvgHaste = nonProcHaste;
                return;
            }

            // the trigger rates are all guestimates at this point, since the
            // real values depend on haste (which obviously has not been
            // finalized yet)
            Dictionary <int, float> periods
                = new Dictionary <int, float>();
            Dictionary <int, float> chances
                = new Dictionary <int, float>();
            float corruptionPeriod = 0f;

            if (Options.GetActiveRotation().Contains("Corruption"))
            {
                corruptionPeriod = 3.1f;
                if (Talents.GlyphQuickDecay)
                {
                    corruptionPeriod /= nonProcHaste;
                }
            }
            PopulateTriggers(
                periods,
                chances,
                CalculationsWarlock.AVG_UNHASTED_CAST_TIME / nonProcHaste
                + Options.Latency,
                1 / 1.5f,
                corruptionPeriod,
                1f);

            // calculate the haste procs
            Haste = new List <WeightedStat>();
            WeightedStat[] percentages
                = GetUptimes(
                      Stats,
                      periods,
                      chances,
                      s => s.SpellHaste,
                      (a, b, c, d, e, f, g, h)
                      => SpecialEffect
                      .GetAverageCombinedUptimeCombinationsMultiplicative(
                          a, b, c, d, e, f, g, h));
            WeightedStat[] ratings
                = GetUptimes(
                      Stats,
                      periods,
                      chances,
                      s => s.HasteRating,
                      (a, b, c, d, e, f, g, h)
                      => SpecialEffect.GetAverageCombinedUptimeCombinations(
                          a, b, c, d, e, f, g, h));
            for (int p = percentages.Length, f = 0; --p >= 0;)
            {
                if (percentages[p].Chance == 0)
                {
                    continue;
                }

                for (int r = ratings.Length; --r >= 0; ++f)
                {
                    if (ratings[r].Chance == 0)
                    {
                        continue;
                    }
                    WeightedStat s = new WeightedStat();
                    s.Chance = percentages[p].Chance * ratings[r].Chance;
                    s.Value
                        = (1 + percentages[p].Value)
                          * (1 + StatConversion.GetSpellHasteFromRating(
                                 ratings[r].Value + Stats.HasteRating))
                          * (1 + Stats.SpellHaste);
                    Haste.Add(s);
                    AvgHaste += s.Chance * s.Value;
                }
            }

            // calculate mana procs
            Stats procStats = new Stats();

            foreach (SpecialEffect effect in Stats.SpecialEffects())
            {
                if (!periods.ContainsKey((int)effect.Trigger))
                {
                    continue;
                }

                Stats proc = effect.GetAverageStats(
                    periods[(int)effect.Trigger],
                    chances[(int)effect.Trigger],
                    CalculationsWarlock.AVG_UNHASTED_CAST_TIME,
                    Options.Duration);
                if (proc.ManaRestore > 0)
                {
                    proc.ManaRestore *= Options.Duration;
                }
                procStats.Accumulate(proc);
            }
            Stats.Mana        += procStats.Mana;
            Stats.ManaRestore += procStats.ManaRestore;
            Stats.ManaRestoreFromBaseManaPPM
                += procStats.ManaRestoreFromBaseManaPPM;
            Stats.ManaRestoreFromMaxManaPerSecond
                      += procStats.ManaRestoreFromMaxManaPerSecond;
            Stats.Mp5 += procStats.Mp5;
        }
        private void CalcHasteAndManaProcs()
        {
            float nonProcHaste = StatUtils.CalcSpellHaste(PreProcStats, CalcOpts.PlayerLevel);
            if (CalcOpts.NoProcs)
            {
                WeightedStat staticHaste = new WeightedStat();
                staticHaste.Chance = 1f;
                staticHaste.Value = nonProcHaste;
                Haste = new List<WeightedStat> { staticHaste };
                AvgHaste = nonProcHaste;
                return;
            }

            // the trigger rates are all guestimates at this point, since the
            // real values depend on haste (which obviously has not been
            // finalized yet)
            Dictionary<int, float> periods = new Dictionary<int, float>();
            Dictionary<int, float> chances = new Dictionary<int, float>();
            float corruptionPeriod = 0f;
            if (CalcOpts.GetActiveRotation().Contains("Corruption"))
            {
                corruptionPeriod = 3.1f / nonProcHaste;
            }
            PopulateTriggers(periods, chances, CalculationsWarlock.AVG_UNHASTED_CAST_TIME / nonProcHaste + CalcOpts.Latency, 1 / 1.5f, corruptionPeriod, 1f);

            // calculate the haste procs
            Haste = new List<WeightedStat>();
            WeightedStat[] percentages = GetUptimes(Stats, periods, chances, s => s.SpellHaste,
                    (a, b, c, d, e, f, g, h) => SpecialEffect.GetAverageCombinedUptimeCombinationsMultiplicative(a, b, c, d, e, f, g, h));
            WeightedStat[] ratings = GetUptimes(Stats, periods, chances, s => s.HasteRating,
                    (a, b, c, d, e, f, g, h) => SpecialEffect.GetAverageCombinedUptimeCombinations(a, b, c, d, e, f, g, h));
            for (int p = percentages.Length, f = 0; --p >= 0; )
            {
                if (percentages[p].Chance == 0)
                {
                    continue;
                }

                for (int r = ratings.Length; --r >= 0; ++f)
                {
                    if (ratings[r].Chance == 0)
                    {
                        continue;
                    }
                    WeightedStat s = new WeightedStat();
                    s.Chance = percentages[p].Chance * ratings[r].Chance;
                    s.Value =   (1 + percentages[p].Value) 
                              * (1 + StatUtils.GetSpellHasteFromRating(ratings[r].Value + Stats.HasteRating, CalcOpts.PlayerLevel))
                              * (1 + Stats.SpellHaste);
                    Haste.Add(s);
                    AvgHaste += s.Chance * s.Value;
                }
            }

            // calculate mana procs
            Stats procStats = new Stats();
            foreach (SpecialEffect effect in Stats.SpecialEffects())
            {
                if (!periods.ContainsKey((int)effect.Trigger))
                {
                    continue;
                }

                Stats proc = effect.GetAverageStats(periods[(int)effect.Trigger], chances[(int)effect.Trigger], CalculationsWarlock.AVG_UNHASTED_CAST_TIME, BossOpts.BerserkTimer);
                if (proc.ManaRestore > 0)
                {
                    proc.ManaRestore *= BossOpts.BerserkTimer;
                }
                procStats.Accumulate(proc);
            }
            Stats.Mana += procStats.Mana;
            Stats.ManaRestore += procStats.ManaRestore;
            Stats.ManaRestoreFromMaxManaPerSecond += procStats.ManaRestoreFromMaxManaPerSecond;
            Stats.Mp5 += procStats.Mp5;
        }
        private Stats IterativeSpecialEffectsStats(DPSWarrCharacter charStruct, List<SpecialEffect> specialEffects, List<SpecialEffect> critEffects,
            Dictionary<Trigger, float> triggerIntervals, Dictionary<Trigger, float> triggerChances, float oldFlurryUptime,
            bool iterate, Base.StatsWarrior iterateOld, Base.StatsWarrior originalStats)
        {
#if DEBUG
            //ConstructionCounts["IterativeSpecialEffectsStats"]++;
#endif
            WarriorTalents talents = charStruct.Char.WarriorTalents;
            float fightDuration = charStruct.BossOpts.BerserkTimer;
            Base.StatsWarrior statsProcs = new Base.StatsWarrior();
            try {
                float dmgTakenInterval = fightDuration / charStruct.BossOpts.AoETargsFreq;

                //float attempted = charStruct.Rot.AttemptedAtksOverDur;
                //float land = charStruct.Rot.LandedAtksOverDur;
                //float crit = charStruct.Rot.CriticalAtksOverDur;

                //int LevelDif = charStruct.bossOpts.Level - charStruct.Char.Level;
                List<Trigger> critTriggers = new List<Trigger>();
                List<float> critWeights = new List<float>();
                bool needsHitTableReset = false;
                foreach (SpecialEffect effect in critEffects) {
                    needsHitTableReset = true;
                    critTriggers.Add(effect.Trigger);
                    critWeights.Add(1f);
                }
                foreach (SpecialEffect effect in specialEffects) {
                    #region old arp code
                    /*if (effect.Stats.ArmorPenetrationRating > 0) {
                        float arpenBuffs =
                            ((combatFactors.CmhItemType == ItemType.TwoHandMace) ? talents.MaceSpecialization * 0.03f : 0.00f) +
                            (!calcOpts.FuryStance ? (0.10f + originalStats.BonusWarrior_T9_2P_ArP) : 0.0f);

                        float OriginalArmorReduction = StatConversion.GetArmorDamageReduction(Char.Level, (int)StatConversion.NPC_ARMOR[LevelDif],
                            originalStats.ArmorPenetration, arpenBuffs, originalStats.ArmorPenetrationRating);
                        float ProccedArmorReduction = StatConversion.GetArmorDamageReduction(Char.Level, (int)StatConversion.NPC_ARMOR[LevelDif],
                            originalStats.ArmorPenetration + effect.Stats.ArmorPenetration, arpenBuffs, originalStats.ArmorPenetrationRating + effect.Stats.ArmorPenetrationRating);

                        Stats dummyStats = new Stats();
                        float procUptime = ApplySpecialEffect(effect, Char, Rot, combatFactors, calcOpts, originalStats.Dodge + originalStats.Parry, ref dummyStats);

                        float targetReduction = ProccedArmorReduction * procUptime + OriginalArmorReduction * (1f - procUptime);
                        //float arpDiff = OriginalArmorReduction - targetReduction;
                        float procArp = StatConversion.GetRatingFromArmorReduction(Char.Level, (int)StatConversion.NPC_ARMOR[LevelDif],
                            originalStats.ArmorPenetration, arpenBuffs, targetReduction);
                        statsProcs.ArmorPenetrationRating += (procArp - originalStats.ArmorPenetrationRating);
                    } 
                    else */
                    #endregion
                    
                    float numProcs = 0;
                    if (effect.Stats.ManaorEquivRestore > 0f && effect.Stats.HealthRestoreFromMaxHealth > 0f) {
                        // effect.Duration = 0, so GetAverageStats won't work
                        float value1 = effect.Stats.ManaorEquivRestore;
                        float value2 = effect.Stats.HealthRestoreFromMaxHealth;
                        SpecialEffect dummy = new SpecialEffect(effect.Trigger, new Stats() { ManaorEquivRestore = value1, HealthRestoreFromMaxHealth = value2 }, effect.Duration, effect.Cooldown, effect.Chance) { BypassCache = true };
                        numProcs = dummy.GetAverageProcsPerSecond(dmgTakenInterval, originalStats.Dodge + originalStats.Parry, 0f, 0f) * fightDuration;
                        statsProcs.ManaorEquivRestore += dummy.Stats.ManaorEquivRestore * numProcs;
                        dummy.Stats.ManaorEquivRestore = 0f;
                        //numProcs = effect.GetAverageProcsPerSecond(triggerIntervals[Trigger.PhysicalCrit], triggerChances[Trigger.PhysicalCrit], 0f, 0f) * fightDuration;
                        //statsProcs.HealthRestoreFromMaxHealth += effect.Stats.HealthRestoreFromMaxHealth * numProcs;
                        ApplySpecialEffect(dummy, charStruct, triggerIntervals, triggerChances, ref statsProcs);
                    } else if (effect.Stats.ManaorEquivRestore > 0f) {
                        // effect.Duration = 0, so GetAverageStats won't work
                        numProcs = effect.GetAverageProcsPerSecond(dmgTakenInterval, originalStats.Dodge + originalStats.Parry, 0f, 0f) * fightDuration;
                        statsProcs.ManaorEquivRestore += effect.Stats.ManaorEquivRestore * numProcs;
                    } else if (effect.Stats.HealthRestoreFromMaxHealth > 0f) {
                        // effect.Duration = 0, so GetAverageStats won't work
                        numProcs = effect.GetAverageProcsPerSecond(dmgTakenInterval, originalStats.Dodge + originalStats.Parry, 0f, 0f) * fightDuration;
                        statsProcs.HealthRestoreFromMaxHealth += effect.Stats.HealthRestoreFromMaxHealth * numProcs;
                    } else {
                        ApplySpecialEffect(effect, charStruct, triggerIntervals, triggerChances, ref statsProcs);
                    }
                }

                WeightedStat[] critProcs;
                if (critEffects.Count == 0) {
                    critProcs = new WeightedStat[] { new WeightedStat() { Value = 0f, Chance = 1f } };
                } else if (critEffects.Count == 1) {
                    float interval = triggerIntervals[critEffects[0].Trigger];
                    float chance = triggerChances[critEffects[0].Trigger];
                    float upTime = critEffects[0].GetAverageStackSize(interval, chance, charStruct.CombatFactors.CMHItemSpeed,
                        (charStruct.CalcOpts.SE_UseDur ? charStruct.BossOpts.BerserkTimer : 0f));
                    upTime *= critWeights[0];
                    critProcs = new WeightedStat[] { new WeightedStat() { Value = critEffects[0].Stats.CritRating, Chance = upTime },
                                                     new WeightedStat() { Value = 0f, Chance = 1f - upTime } };
                } else {
                    float[] intervals = new float[critEffects.Count];
                    float[] chances = new float[critEffects.Count];
                    float[] offset = new float[critEffects.Count];
                    for (int i = 0; i < critEffects.Count; i++) {
                        intervals[i] = triggerIntervals[critEffects[i].Trigger];
                        chances[i] = triggerChances[critEffects[i].Trigger];
                    }

                    critProcs = SpecialEffect.GetAverageCombinedUptimeCombinations(critEffects.ToArray(), intervals, chances, offset, critWeights.ToArray(), charStruct.CombatFactors.CMHItemSpeed,
                        charStruct.BossOpts.BerserkTimer, AdditiveStat.CritRating);
                }
                charStruct.CombatFactors.CritProcs = critProcs;
                float flurryUptime = 0f;
                if (iterate && talents.Flurry > 0f && charStruct.CombatFactors.FuryStance && charStruct.Char.MainHand != null && charStruct.Char.MainHand.Item != null)
                {
                    float numFlurryHits = 3f; // default
                    float mhPerc = 1f; // 100% by default
                    float flurryHaste = 0.25f / 3f * talents.Flurry;
                    bool useOffHand = false;
                    
                    float flurryHitsPerSec = charStruct.CombatFactors.TotalHaste * (1f + flurryHaste) / (1f + flurryHaste * oldFlurryUptime);
                    float temp = 1f / charStruct.Char.MainHand.Item.Speed;
                    if (charStruct.Char.OffHand != null && charStruct.Char.OffHand.Item != null) {
                        useOffHand = true;
                        temp += 1f / charStruct.Char.OffHand.Item.Speed;
                        mhPerc = (charStruct.Char.MainHand.Speed / charStruct.Char.OffHand.Speed) / (1f + charStruct.Char.MainHand.Speed / charStruct.Char.OffHand.Speed);
                        if (charStruct.Char.OffHand.Speed == charStruct.Char.MainHand.Speed) numFlurryHits = 4f;
                    }
                    
                    flurryHitsPerSec *= temp;
                    float flurryDuration = numFlurryHits / flurryHitsPerSec;
                    flurryUptime = 1f;
                    foreach (AbilityWrapper aw in charStruct.Rot.DamagingAbilities) {
                        if (aw.Ability.CanCrit && aw.AllNumActivates > 0f)
                        {
                            float tempFactor = (float) Math.Pow(1f - aw.Ability.MHAtkTable.Crit,
                                                                flurryDuration*
                                                                (aw.AllNumActivates*aw.Ability.SwingsPerActivate*
                                                                 aw.Ability.AvgTargets/fightDuration));
                            flurryUptime *= tempFactor;
                            if (aw.Ability.SwingsOffHand && useOffHand)
                            {
                                flurryUptime *= (float) Math.Pow(1f - aw.Ability.OHAtkTable.Crit,
                                                                 flurryDuration*
                                                                 (aw.AllNumActivates*aw.Ability.SwingsPerActivate*
                                                                  aw.Ability.AvgTargets/fightDuration));
                            }
                        }
                    }
                    flurryUptime *= (float)Math.Pow(1f - charStruct.Rot.DPSWarrChar.Whiteattacks.MHAtkTable.Crit, numFlurryHits * mhPerc);
                    flurryUptime *= (float)Math.Pow(1f - charStruct.Rot.DPSWarrChar.Whiteattacks.OHAtkTable.Crit, numFlurryHits * (1f - mhPerc));
                    flurryUptime = 1f - flurryUptime;
                    statsProcs.PhysicalHaste = (1f + statsProcs.PhysicalHaste) * (1f + flurryHaste * flurryUptime) - 1f;
                }

                charStruct.CombatFactors.StatS = UpdateStatsAndAdd(statsProcs, originalStats, charStruct.Char);
                charStruct.CombatFactors.InvalidateCache();
                //Rot.InvalidateCache();
                if (iterate) {
                    const float precisionWhole = 0.01f;
                    const float precisionDec = 0.0001f;
                    if (statsProcs.Agility - iterateOld.Agility > precisionWhole ||
                        statsProcs.HasteRating - iterateOld.HasteRating > precisionWhole ||
                        statsProcs.HitRating - iterateOld.HitRating > precisionWhole ||
                        statsProcs.CritRating - iterateOld.CritRating > precisionWhole ||
                        statsProcs.PhysicalHaste - iterateOld.PhysicalHaste > precisionDec ||
                        statsProcs.PhysicalCrit - iterateOld.PhysicalCrit > precisionDec ||
                        statsProcs.PhysicalHit - iterateOld.PhysicalHit > precisionDec)
                    {
                        if (needsHitTableReset) charStruct.Rot.ResetHitTables();
                        charStruct.Rot.DoIterations();
                        CalculateTriggers(charStruct, triggerIntervals, triggerChances);
                        return IterativeSpecialEffectsStats(charStruct,
                            specialEffects, critEffects, triggerIntervals, triggerChances, flurryUptime, true, statsProcs, originalStats);
                    } else { /*int j = 0;*/ }
                }

                return statsProcs;
            } catch (Exception ex) {
                new Base.ErrorBox()
                {
                    Title = "Error in creating SpecialEffects Stats",
                    Function = "GetSpecialEffectsStats()",
                    TheException = ex,
                }.Show();
                return new Stats();
            }
        }
        private Stats GetCharacterStatsWithTemporaryEffects(Character character, Item additionalItem, /*out WeightedStat[] armorPenetrationUptimes,*/ out WeightedStat[] critRatingUptimes)
        {
            RogueTalents talents = character.RogueTalents;
            #region Spec determination
            int spec;
            int assCounter = 0, combatCounter = 0, subtCounter = 0;
            for (int i = 0; i <= 18; i++) assCounter += int.Parse(talents.ToString()[i].ToString());
            for (int i = 19; i <= 37; i++) combatCounter += int.Parse(talents.ToString()[i].ToString());
            for (int i = 38; i <= 56; i++) subtCounter += int.Parse(talents.ToString()[i].ToString());
            if (assCounter > combatCounter && assCounter > subtCounter) spec = 0;
            else if (combatCounter > subtCounter) spec = 1;
            else spec = 2;
            #endregion

            CalculationOptionsRogue calcOpts = character.CalculationOptions as CalculationOptionsRogue;
            BossOptions bossOpts = character.BossOptions;
            int targetLevel = bossOpts.Level;
            bool targetPoisonable = calcOpts.TargetPoisonable;

            Stats statsRace = BaseStats.GetBaseStats(character.Level, character.Class, character.Race);

            Stats statsItems = GetItemStats(character, additionalItem);
            Stats statsBuffs = GetBuffsStats(character, calcOpts);

            SpecialEffect LegendarySpecialEffect = new SpecialEffect(Trigger.MeleeHit, new Stats() { Agility = 2f }, 20f, 0f, 1f, 50);
            Stats statsSetBonus = new Stats();
            int LegendaryPartA;
            character.SetBonusCount.TryGetValue("Jaws of Retribution", out LegendaryPartA);
            if (LegendaryPartA >= 2)
            {
                statsSetBonus.AddSpecialEffect(LegendarySpecialEffect);
            }

            LegendarySpecialEffect = new SpecialEffect(Trigger.MeleeHit, new Stats() { Agility = 5f }, 20f, 0f, 1f, 50);
            int LegendaryPartB;
            character.SetBonusCount.TryGetValue("Maw of Oblivion", out LegendaryPartB);
            if (LegendaryPartB >= 2)
            {
                statsSetBonus.AddSpecialEffect(LegendarySpecialEffect);
            }

            // Needs work....
            LegendarySpecialEffect = new SpecialEffect(Trigger.MeleeHit, new Stats() { Agility = 17f }, 20f, 0f, 1f, 50);
            Stats LegendaryStats = new Stats();
            LegendaryStats.AddSpecialEffect(LegendarySpecialEffect);
            // Assume it resets every 45 seconds
            LegendarySpecialEffect = new SpecialEffect(Trigger.MeleeHit, LegendaryStats, 45f, 45f);
            int LegendaryPartC;
            character.SetBonusCount.TryGetValue("Fangs of the Father", out LegendaryPartC);
            if (LegendaryPartC >= 2)
            {
                statsSetBonus.AddSpecialEffect(LegendarySpecialEffect);
                LegendarySpecialEffect = new SpecialEffect(Trigger.MeleeHit, new Stats() { FangsoftheFather = 1f }, 6f, 45f);
                statsSetBonus.AddSpecialEffect(LegendarySpecialEffect);
            }

            Stats statsTalents = new Stats()
            {
                BonusAgilityMultiplier = (1f + (spec == 2 ? RV.Mastery.SinisterCallingMult : 0f)) * (1f + RV.LeatherSpecialization) - 1f,
                BonusAttackPowerMultiplier = (1f + (spec == 1 ? RV.Mastery.VitalityAPMult : 0f)) * (1f + RV.Talents.SavageCombatMult * talents.SavageCombat) - 1f,
                BonusCritChance = character.ActiveBuffs.Contains(Buff.GetBuffByName("Rampage")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Leader of the Pack")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Honor Among Thieves")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Terrifying Roar")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Furious Howl")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Elemental Oath")) ? 0f : RV.Talents.HonorAmongThievesCritBonus * talents.HonorAmongThieves,
                BonusDamageMultiplier = RV.Vendetta.DmgMult * talents.Vendetta * (RV.Vendetta.Duration * (talents.GlyphOfVendetta ? 1f + RV.Glyph.VendettaDurationMult : 1f)) / RV.Vendetta.CD +
                    talents.SanguinaryVein * RV.Talents.SanguinaryVein +
                    RV.Mastery.MasterOfSubtletyDmgMult * RV.Mastery.MasterOfSubtletyDuration / (RV.Vanish.CD - RV.Talents.ElusivenessVanishCDReduc * talents.Elusiveness) +
                    (talents.Preparation > 0 ? RV.Mastery.MasterOfSubtletyDmgMult * RV.Mastery.MasterOfSubtletyDuration / (RV.Talents.PreparationCD * talents.Preparation) : 0f),
                BonusPhysicalDamageMultiplier = character.ActiveBuffs.Contains(Buff.GetBuffByName("Ravage")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Acid Spit")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Brittle Bones")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Blood Frenzy")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Savage Combat")) ? 0f : RV.Talents.SavageCombatMult * talents.SavageCombat,
                BonusBleedDamageMultiplier = character.ActiveBuffs.Contains(Buff.GetBuffByName("Mangle")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Hemorrhage")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Blood Frenzy")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Gore")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Stampede")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Tendon Rip")) ? 0f : RV.Hemo.BleedDmgMult * talents.Hemorrhage,
                PhysicalHit = RV.Talents.PrecisionMult * talents.Precision,
                SpellHit = RV.Talents.PrecisionMult * talents.Precision,
            };

            Stats statsGearEnchantsBuffs = statsItems + statsBuffs + statsSetBonus;
            Stats statsTotal = statsRace + statsItems;
            statsTotal.Accumulate(statsBuffs);
            statsTotal.Accumulate(statsTalents);

            statsTotal.Stamina = (float)Math.Floor(statsTotal.Stamina * (1f + statsTotal.BonusStaminaMultiplier));
            statsTotal.Strength = (float)Math.Floor(statsTotal.Strength * (1f + statsTotal.BonusStrengthMultiplier));
            statsTotal.Agility = (float)Math.Floor((statsTotal.Agility - statsRace.Agility) * (1f + statsTotal.BonusAgilityMultiplier)) + statsRace.Agility;
            statsTotal.AttackPower += (statsTotal.Strength - RV.BaseStatCalcReduc / 2) + RV.APperAgi * (statsTotal.Agility - RV.BaseStatCalcReduc) + RV.BaseStatCalcReduc;
            statsTotal.AttackPower = (float)Math.Floor(statsTotal.AttackPower * (1f + statsTotal.BonusAttackPowerMultiplier));
            statsTotal.Health += (float)Math.Floor((statsTotal.Stamina - RV.BaseStatCalcReduc) * RV.HPPerStam + RV.BaseStatCalcReduc);
            statsTotal.Armor = (float)Math.Floor(statsTotal.Armor * (1f + statsTotal.BonusArmorMultiplier));
            statsTotal.NatureResistance += statsTotal.NatureResistanceBuff;
            statsTotal.FireResistance += statsTotal.FireResistanceBuff;
            statsTotal.FrostResistance += statsTotal.FrostResistanceBuff;
            statsTotal.ShadowResistance += statsTotal.ShadowResistanceBuff;
            statsTotal.ArcaneResistance += statsTotal.ArcaneResistanceBuff;

            float hasteBonus = (1f + StatConversion.GetPhysicalHasteFromRating(statsTotal.HasteRating, CharacterClass.Rogue)) * (1f + statsTotal.PhysicalHaste) - 1f;
            float speedBonus = (1f + hasteBonus) * (1f + RV.SnD.SpeedBonus) * (1f + (spec == 2 ? RV.Mastery.Executioner + RV.Mastery.ExecutionerPerMast * StatConversion.GetMasteryFromRating(statsTotal.MasteryRating) : 0f)) - 1f;
            float mHSpeed = (character.MainHand == null ? 2 : character.MainHand.Speed);
            float oHSpeed = (character.OffHand == null ? 2 : character.OffHand.Speed);
            float meleeHitInterval = 1f / ((mHSpeed + oHSpeed) / speedBonus);

            //To calculate the Poison hit interval only white attacks are taken into account, IP is assumed on the slowest and DP on the fastest weapon
            float dPPS = bossOpts.BerserkTimer / (Math.Min(mHSpeed, oHSpeed) / speedBonus) * RV.DP.Chance + (spec == 0 ? RV.Mastery.ImprovedPoisonsDPBonus : 0);
            float poisonHitInterval = 1 / (Math.Max(mHSpeed, mHSpeed) * RV.IP.Chance * (1f + RV.Mastery.ImprovedPoisonsIPFreqMult) / RV.IP.NormWeapSpeed + dPPS);
            
            float hitBonus = StatConversion.GetPhysicalHitFromRating(statsTotal.HitRating) + statsTotal.PhysicalHit;
            float spellHitBonus = StatConversion.GetSpellHitFromRating(statsTotal.HitRating) + statsTotal.SpellHit;
            float expertiseBonus = StatConversion.GetDodgeParryReducFromExpertise(StatConversion.GetExpertiseFromRating(statsTotal.ExpertiseRating) + statsTotal.Expertise);
            float chanceDodge = Math.Max(0f, StatConversion.WHITE_DODGE_CHANCE_CAP[targetLevel - character.Level] - expertiseBonus);
            float chanceParry = 0f; //Math.Max(0f, StatConversion.WHITE_PARRY_CHANCE_CAP[targetLevel - character.Level] - expertiseBonus);
            float chanceMiss = Math.Max(0f, StatConversion.WHITE_MISS_CHANCE_CAP[targetLevel - character.Level] - hitBonus);
            float chanceAvoided = chanceMiss + chanceDodge + chanceParry;
            float chancePoisonAvoided = Math.Max(0f, StatConversion.GetSpellMiss(character.Level - targetLevel, false) - spellHitBonus);

            float rawChanceCrit = StatConversion.GetPhysicalCritFromRating(statsTotal.CritRating)
                                + StatConversion.GetPhysicalCritFromAgility(statsTotal.Agility, CharacterClass.Rogue)
                                + statsTotal.PhysicalCrit
                                + StatConversion.NPC_LEVEL_CRIT_MOD[targetLevel - character.Level];
            float chanceCrit = rawChanceCrit * (1f - chanceAvoided);
            float chanceHit = 1f - chanceAvoided;

            Dictionary<Trigger, float> triggerIntervals = new Dictionary<Trigger, float>();
            Dictionary<Trigger, float> triggerChances = new Dictionary<Trigger, float>();
            triggerIntervals[Trigger.Use] = 0f;
            triggerIntervals[Trigger.MeleeHit] = meleeHitInterval;
            triggerIntervals[Trigger.PhysicalHit] = meleeHitInterval;
            triggerIntervals[Trigger.MeleeAttack] = meleeHitInterval;
            triggerIntervals[Trigger.PhysicalAttack] = meleeHitInterval;
            triggerIntervals[Trigger.MeleeCrit] = meleeHitInterval;
            triggerIntervals[Trigger.PhysicalCrit] = meleeHitInterval;
            triggerIntervals[Trigger.DoTTick] = 0f;
            triggerIntervals[Trigger.DamageDone] = meleeHitInterval / 2f;
            triggerIntervals[Trigger.DamageOrHealingDone] = meleeHitInterval / 2f; // Need to add Self-heals
            triggerIntervals[Trigger.SpellHit] = poisonHitInterval;
            triggerIntervals[Trigger.EnergyOrFocusDropsBelow20PercentOfMax] = 4f; // Approximating as 80% chance every 4 seconds. TODO: Actually model this
            triggerChances[Trigger.Use] = 1f;
            triggerChances[Trigger.MeleeHit] = Math.Max(0f, chanceHit);
            triggerChances[Trigger.PhysicalHit] = Math.Max(0f, chanceHit);
            triggerChances[Trigger.PhysicalAttack] = triggerChances[Trigger.MeleeAttack] = 1f;
            triggerChances[Trigger.MeleeCrit] = Math.Max(0f, chanceCrit);
            triggerChances[Trigger.PhysicalCrit] = Math.Max(0f, chanceCrit);
            triggerChances[Trigger.DoTTick] = 1f;
            triggerChances[Trigger.DamageDone] = 1f - chanceAvoided / 2f;
            triggerChances[Trigger.DamageOrHealingDone] = 1f - chanceAvoided / 2f; // Need to add Self-heals
            triggerChances[Trigger.SpellHit] = Math.Max(0f, 1f - chancePoisonAvoided);
            triggerChances[Trigger.EnergyOrFocusDropsBelow20PercentOfMax] = 0.80f; // Approximating as 80% chance every 4 seconds. TODO: Actually model this

            // Handle Trinket procs
            Stats statsProcs = new Stats();
            foreach (SpecialEffect effect in statsTotal.SpecialEffects(se => triggerIntervals.ContainsKey(se.Trigger)))
            {
                // JOTHAY's NOTE: The following is an ugly hack to add Recursive Effects to Cat
                // so Victor's Call and similar trinkets can be given more appropriate value
                if (effect.Trigger == Trigger.Use && effect.Stats._rawSpecialEffectDataSize == 1
                    && triggerIntervals.ContainsKey(effect.Stats._rawSpecialEffectData[0].Trigger))
                {
                    float upTime = effect.GetAverageUptime(triggerIntervals[effect.Trigger],
                        triggerChances[effect.Trigger], 1f, bossOpts.BerserkTimer);
                    statsProcs.Accumulate(effect.Stats._rawSpecialEffectData[0].GetAverageStats(
                        triggerIntervals[effect.Stats._rawSpecialEffectData[0].Trigger],
                        triggerChances[effect.Stats._rawSpecialEffectData[0].Trigger], 1f, bossOpts.BerserkTimer),
                        upTime);
                }
                else if (effect.Stats.MoteOfAnger > 0)
                {
                    // When in effect stats, MoteOfAnger is % of melee hits
                    // When in character stats, MoteOfAnger is average procs per second
                    statsProcs.MoteOfAnger = effect.Stats.MoteOfAnger * effect.GetAverageProcsPerSecond(triggerIntervals[effect.Trigger],
                        triggerChances[effect.Trigger], 1f, bossOpts.BerserkTimer) / effect.MaxStack;
                }
                else
                {
                    statsProcs.Accumulate(effect.GetAverageStats(triggerIntervals[effect.Trigger],
                        triggerChances[effect.Trigger], 1f, bossOpts.BerserkTimer),
                        1f);
                }
            }

            statsProcs.Agility += statsProcs.HighestStat + statsProcs.Paragon;
            statsProcs.Stamina = (float)Math.Floor(statsProcs.Stamina * (1f + statsTotal.BonusStaminaMultiplier));
            statsProcs.Strength = (float)Math.Floor(statsProcs.Strength * (1f + statsTotal.BonusStrengthMultiplier));
            statsProcs.Agility = (float)Math.Floor(statsProcs.Agility * (1f + statsTotal.BonusAgilityMultiplier));
            statsProcs.AttackPower += statsProcs.Strength + RV.APperAgi * statsProcs.Agility;
            statsProcs.AttackPower = (float)Math.Floor(statsProcs.AttackPower * (1f + statsTotal.BonusAttackPowerMultiplier));
            statsProcs.Health += (float)Math.Floor(statsProcs.Stamina * RV.HPPerStam);
            statsProcs.Armor = (float)Math.Floor(statsProcs.Armor * (1f + statsTotal.BonusArmorMultiplier));

            float HighestSecondaryStatValue = statsProcs.HighestSecondaryStat; // how much HighestSecondaryStat to add
            statsProcs.HighestSecondaryStat = 0f; // remove HighestSecondaryStat stat, since it's not needed
            if (statsTotal.CritRating > statsTotal.HasteRating && statsTotal.CritRating > statsTotal.MasteryRating) {
                statsProcs.CritRating += HighestSecondaryStatValue;
            } else if (statsTotal.HasteRating > statsTotal.CritRating && statsTotal.HasteRating > statsTotal.MasteryRating) {
                statsProcs.HasteRating += HighestSecondaryStatValue;
            } else /*if (statsTotal.MasteryRating > statsTotal.CritRating && statsTotal.MasteryRating > statsTotal.HasteRating)*/ {
                statsProcs.MasteryRating += HighestSecondaryStatValue;
            }

            //Agility is only used for crit from here on out; we'll be converting Agility to CritRating, 
            //and calculating CritRating separately, so don't add any Agility or CritRating from procs here.
            statsProcs.CritRating = statsProcs.Agility = 0;
            statsTotal.Accumulate(statsProcs);

            //Handle Crit procs
            critRatingUptimes = new WeightedStat[0];
            List<SpecialEffect> tempCritEffects = new List<SpecialEffect>();
            List<float> tempCritEffectIntervals = new List<float>();
            List<float> tempCritEffectChances = new List<float>();
            List<float> tempCritEffectScales = new List<float>();

            foreach (SpecialEffect effect in statsTotal.SpecialEffects(se => triggerIntervals.ContainsKey(se.Trigger) && (se.Stats.CritRating + se.Stats.Agility + se.Stats.HighestStat + se.Stats.Paragon) > 0))
            {
                tempCritEffects.Add(effect);
                tempCritEffectIntervals.Add(triggerIntervals[effect.Trigger]);
                tempCritEffectChances.Add(triggerChances[effect.Trigger]);
                tempCritEffectScales.Add(1f);
            }

            if (tempCritEffects.Count == 0)
            {
                critRatingUptimes = new WeightedStat[] { new WeightedStat() { Chance = 1f, Value = 0f } };
            }
            else if (tempCritEffects.Count == 1)
            { //Only one, add it to
                SpecialEffect effect = tempCritEffects[0];
                float uptime = effect.GetAverageUptime(triggerIntervals[effect.Trigger], triggerChances[effect.Trigger], 1f, bossOpts.BerserkTimer) * tempCritEffectScales[0];
                float totalAgi = (effect.Stats.Agility + effect.Stats.HighestStat + effect.Stats.Paragon) * (1f + statsTotal.BonusAgilityMultiplier);
                critRatingUptimes = new WeightedStat[] { new WeightedStat() { Chance = uptime, Value = 
                        effect.Stats.CritRating + StatConversion.GetCritFromAgility(totalAgi - 10,
                        CharacterClass.Rogue) * StatConversion.RATING_PER_PHYSICALCRIT },
                    new WeightedStat() { Chance = 1f - uptime, Value = 0f }};
            }
            else if (tempCritEffects.Count > 1)
            {
                List<float> tempCritEffectsValues = new List<float>();
                foreach (SpecialEffect effect in tempCritEffects)
                {
                    float totalAgi = (float)effect.MaxStack * (effect.Stats.Agility + effect.Stats.HighestStat + effect.Stats.Paragon) * (1f + statsTotal.BonusAgilityMultiplier);
                    tempCritEffectsValues.Add(effect.Stats.CritRating +
                        StatConversion.GetCritFromAgility(totalAgi - 10, CharacterClass.Rogue) *
                        StatConversion.RATING_PER_PHYSICALCRIT);
                }

                float[] intervals = new float[tempCritEffects.Count];
                float[] chances = new float[tempCritEffects.Count];
                float[] offset = new float[tempCritEffects.Count];
                for (int i = 0; i < tempCritEffects.Count; i++)
                {
                    intervals[i] = triggerIntervals[tempCritEffects[i].Trigger];
                    chances[i] = triggerChances[tempCritEffects[i].Trigger];
                }
                if (tempCritEffects.Count >= 2)
                {
                    offset[0] = calcOpts.TrinketOffset;
                }
                WeightedStat[] critWeights = SpecialEffect.GetAverageCombinedUptimeCombinations(tempCritEffects.ToArray(), intervals, chances, offset, tempCritEffectScales.ToArray(), 1f, bossOpts.BerserkTimer, tempCritEffectsValues.ToArray());
                critRatingUptimes = critWeights;
            }
            return statsTotal;
        }