Exemple #1
0
 public BearAbilityBuilder(StatsBear stats, float weaponDPS, float attackSpeed, float armorDamageMultiplier,
                           float chanceAvoided, float chanceResisted, float chanceCrit, float chanceCritSpell, float chanceGlance, float critMultiplier, float spellCritMultiplier)
 {
     Stats                 = stats;
     WeaponDPS             = weaponDPS;
     AttackSpeed           = attackSpeed;
     ArmorDamageMultiplier = armorDamageMultiplier;
     ChanceAvoided         = chanceAvoided;
     ChanceResisted        = chanceResisted;
     ChanceCrit            = chanceCrit;
     ChanceCritSpell       = chanceCritSpell;
     ChanceGlance          = chanceGlance;
     CritMultiplier        = critMultiplier;
     SpellCritMultiplier   = spellCritMultiplier;
     BaseDamage            = (WeaponDPS + AttackPower / 14f) * 2.5f;
 }
		public BearAbilityBuilder(StatsBear stats, float weaponDPS, float attackSpeed, float armorDamageMultiplier,
			float chanceAvoided, float chanceResisted, float chanceCrit, float chanceCritSpell, float chanceGlance, float critMultiplier, float spellCritMultiplier)
		{
			Stats = stats;
			WeaponDPS = weaponDPS;
			AttackSpeed = attackSpeed;
			ArmorDamageMultiplier = armorDamageMultiplier;
			ChanceAvoided = chanceAvoided;
			ChanceResisted = chanceResisted;
			ChanceCrit = chanceCrit;
			ChanceCritSpell = chanceCritSpell;
			ChanceGlance = chanceGlance;
			CritMultiplier = critMultiplier;
			SpellCritMultiplier = spellCritMultiplier;
			BaseDamage = (WeaponDPS + AttackPower / 14f) * 2.5f;
		}
        private static void AccumulateProcs(Character character, StatsBear statsTotal)
        {
            CalculationOptionsBear calcOpts = character.CalculationOptions as CalculationOptionsBear ?? new CalculationOptionsBear();
            BossOptions bossOpts = character.BossOptions;
            if (bossOpts.Attacks.Count < 1) { bossOpts.Attacks.Add(BossHandler.ADefaultMeleeAttack); bossOpts.Attacks[0].IsTheDefaultMelee = true; bossOpts.DamagingTargs = true; }
            if (bossOpts.DefaultMeleeAttack == null) { bossOpts.Attacks.Add(BossHandler.ADefaultMeleeAttack); bossOpts.Attacks[bossOpts.Attacks.Count-1].IsTheDefaultMelee = true; bossOpts.DamagingTargs = true; }
            Attack bossAttack = bossOpts.DefaultMeleeAttack;
            int targetLevel = bossOpts.Level;
            float fightDuration = bossOpts.BerserkTimer;

            float hasteBonus = StatConversion.GetHasteFromRating(statsTotal.HasteRating, CharacterClass.Druid);
            float playerHastedAttackSpeed = (2.5f / (1f + hasteBonus)) / (1f + statsTotal.PhysicalHaste);
            float meleeHitInterval = 1f / (1f / playerHastedAttackSpeed + 1f / 1.5f);

            int levelDifference = (bossOpts.Level - character.Level);
            float hitBonus = StatConversion.GetPhysicalHitFromRating(statsTotal.HitRating) + statsTotal.PhysicalHit;
            float expertiseBonus = StatConversion.GetDodgeParryReducFromExpertise(StatConversion.GetExpertiseFromRating(statsTotal.ExpertiseRating, CharacterClass.Druid) + statsTotal.Expertise, CharacterClass.Druid);
            float chanceDodge = Math.Max(0f, StatConversion.WHITE_DODGE_CHANCE_CAP[levelDifference] - expertiseBonus);
            float chanceParry = Math.Max(0f, StatConversion.WHITE_PARRY_CHANCE_CAP[levelDifference] - expertiseBonus);
            float chanceMiss = Math.Max(0f, StatConversion.WHITE_MISS_CHANCE_CAP[levelDifference] - hitBonus);
            float chanceAvoided = chanceMiss + chanceDodge + chanceParry;

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

            float levelDifferenceAvoidance = levelDifference * 0.002f;
            float baseAgi = character.Race == CharacterRace.NightElf ? 87 : 77;

            //float defSkill = (float)Math.Floor(StatConversion.GetDefenseFromRating(statsTotal.DefenseRating, CharacterClass.Druid));
            float dodgeNonDR = statsTotal.Dodge - levelDifferenceAvoidance + StatConversion.GetDodgeFromAgility(baseAgi, CharacterClass.Druid);
            float missNonDR = statsTotal.Miss - levelDifferenceAvoidance;
            float dodgePreDR = StatConversion.GetDodgeFromAgility(statsTotal.Agility - baseAgi, CharacterClass.Druid)
                             + StatConversion.GetDodgeFromRating(statsTotal.DodgeRating, CharacterClass.Druid)
                             /*+ defSkill * StatConversion.DEFENSE_RATING_AVOIDANCE_MULTIPLIER / 100f*/;
            float missPreDR = 0f;// (defSkill * StatConversion.DEFENSE_RATING_AVOIDANCE_MULTIPLIER / 100f);
            float dodgePostDR = 0.01f / (1f / 116.890707f + 0.00972f / dodgePreDR);
            float missPostDR = 0.01f / (1f / 16f + 0.00972f / missPreDR);
            float dodgeTotal = dodgeNonDR + dodgePostDR;
            float missTotal = missNonDR + missPostDR;

            float TargAttackSpeed = bossAttack.AttackSpeed / (1f - statsTotal.BossAttackSpeedReductionMultiplier);

            Stats statsProcs = new Stats();
            //float uptime;
            foreach (SpecialEffect effect in statsTotal.SpecialEffects())
            {
                switch (effect.Trigger)
                {
                    case Trigger.Use:
                        effect.AccumulateAverageStats(statsProcs, 0f, 1f, 2.5f, fightDuration);
                        break;
                    case Trigger.MeleeHit:
                    case Trigger.PhysicalHit:
                        effect.AccumulateAverageStats(statsProcs, meleeHitInterval, chanceHit, 2.5f, fightDuration);
                        break;
                    case Trigger.MeleeAttack:
                    case Trigger.PhysicalAttack:
                        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(meleeHitInterval, 1f, 2.5f, fightDuration) / effect.MaxStack;
                        } else {
                            effect.AccumulateAverageStats(statsProcs, meleeHitInterval, 1f, 2.5f, fightDuration);
                        }
                        break;
                    case Trigger.MeleeCrit:
                    case Trigger.PhysicalCrit:
                        effect.AccumulateAverageStats(statsProcs, meleeHitInterval, chanceCrit, 2.5f, fightDuration);
                        break;
                    case Trigger.DoTTick:
                        effect.AccumulateAverageStats(statsProcs, 3f, 1f, 2.5f, fightDuration);
                        break;
                    case Trigger.DamageDone:
                        effect.AccumulateAverageStats(statsProcs, meleeHitInterval / 2f, 1f, 2.5f, fightDuration);
                        break;
                    case Trigger.DamageOrHealingDone:
                        effect.AccumulateAverageStats(statsProcs, meleeHitInterval / 2f, 1f, 2.5f, fightDuration); // also needs healing
                        break;
                    case Trigger.MangleBearHit:
                        effect.AccumulateAverageStats(statsProcs, 6f - statsTotal.MangleCooldownReduction, chanceHit, 2.5f, fightDuration);
                        break;
                    case Trigger.MangleCatOrShredOrInfectedWoundsHit:
                        effect.AccumulateAverageStats(statsProcs, 1f /
                            (1f / (6f - statsTotal.MangleCooldownReduction) + //Mangles Per Second
                            1f / meleeHitInterval) //Mauls Per Second
                            , chanceHit, 2.5f, fightDuration);
                        break;
                    case Trigger.SwipeBearOrLacerateHit:
                        effect.AccumulateAverageStats(statsProcs, 2.25f, chanceHit, 2.5f, fightDuration);
                        break;
                    case Trigger.LacerateTick:
                        effect.AccumulateAverageStats(statsProcs, 3f, 1f, 2.5f, fightDuration);
                        break;
                    case Trigger.DamageTakenPutsMeBelow35PercHealth:
                        effect.AccumulateAverageStats(statsProcs, TargAttackSpeed * 0.8f, (1f - 0.8f * (dodgeTotal + missTotal)) * 0.35f, fightDuration); //Assume you get hit by other things, like dots, aoes, etc, making you get targeted with damage 35% more often than the boss, and half the hits you take are unavoidable.
                        break;
                    case Trigger.DamageTakenPutsMeBelow50PercHealth:
                        effect.AccumulateAverageStats(statsProcs, TargAttackSpeed * 0.8f, (1f - 0.8f * (dodgeTotal + missTotal)) * 0.50f, fightDuration); //Assume you get hit by other things, like dots, aoes, etc, making you get targeted with damage 50% more often than the boss, and half the hits you take are unavoidable.
                        break;
                    case Trigger.DamageTaken:
                        effect.AccumulateAverageStats(statsProcs, TargAttackSpeed * 0.8f, 1f - 0.8f * (dodgeTotal + missTotal), fightDuration); //Assume you get hit by other things, like dots, aoes, etc, making you get targeted with damage 25% more often than the boss, and half the hits you take are unavoidable.
                        break;
                    case Trigger.DamageTakenPhysical:
                        effect.AccumulateAverageStats(statsProcs, TargAttackSpeed, 1f - (dodgeTotal + missTotal), fightDuration); //Assume you get hit by other things, like dots, aoes, etc, making you get targeted with damage 25% more often than the boss, and half the hits you take are unavoidable.
                        break;
                    case Trigger.Barkskin:
                        effect.AccumulateAverageStats(statsProcs, 60f, 1f, 0f, fightDuration);
                        break;
                    case Trigger.Berserk:
                        effect.AccumulateAverageStats(statsProcs, 180f, 1, 0f, fightDuration);
                        break;
                }
            }

            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 + statsProcs.Agility * 2f;
            statsProcs.AttackPower = (float)Math.Floor(statsProcs.AttackPower * (1f + statsTotal.BonusAttackPowerMultiplier));
            statsProcs.Health += (float)Math.Floor(statsProcs.Stamina * 14f) + (float)Math.Floor(statsProcs.BattlemasterHealthProc);
            statsProcs.Health *= (1f + statsProcs.BonusHealthMultiplier);
            statsProcs.Armor += /*2f * (float)Math.Floor(statsProcs.Agility)*/ + statsProcs.BonusArmor; // Armor no longer gets bonuses from Agi in Cata
            statsProcs.Armor = (float)Math.Floor(statsProcs.Armor * (1f + statsTotal.BonusArmorMultiplier));
            if (statsProcs.HighestSecondaryStat > 0) {
                if (statsTotal.CritRating > statsTotal.HasteRating && statsTotal.CritRating > statsTotal.MasteryRating) {
                    statsProcs.CritRating += statsProcs.HighestSecondaryStat; // this will be invalidated after this, but I'm at least putting it in for now
                }
                else if (statsTotal.HasteRating > statsTotal.CritRating && statsTotal.HasteRating > statsTotal.MasteryRating) {
                    statsProcs.HasteRating += statsProcs.HighestSecondaryStat;
                }
                else if (statsTotal.MasteryRating > statsTotal.CritRating && statsTotal.MasteryRating > statsTotal.HasteRating) {
                    statsProcs.MasteryRating += statsProcs.HighestSecondaryStat;
                }
                statsProcs.HighestSecondaryStat = 0;
            }

            statsTotal.Accumulate(statsProcs);
        }
        /// <summary>
        /// Gets the total Stats of the Character
        /// </summary>
        /// <param name="character">The Character to get the total Stats of</param>
        /// <param name="additionalItem">An additional item to grant the Character the stats of (as if it were worn)</param>
        /// <returns>The total stats for the Character</returns>
        public override Stats GetCharacterStats(Character character, Item additionalItem)
        {
            CalculationOptionsBear calcOpts = character.CalculationOptions as CalculationOptionsBear ?? new CalculationOptionsBear();

            DruidTalents talents = character.DruidTalents;

            bool hasCritBuff = false;
            foreach (Buff buff in character.ActiveBuffs) {
                if (buff.Group == "Critical Strike Chance") {
                    hasCritBuff = true;
                    break;
                }
            }

            StatsBear statsTotal = new StatsBear()
            {
                BonusAttackPowerMultiplier = 0.25f,
                BonusBleedDamageMultiplier = (character.ActiveBuffsContains("Mangle") || character.ActiveBuffsContains("Trauma") ? 0f : 0.3f),

                Dodge = 0.02f * talents.FeralSwiftness + 0.03f * talents.NaturalReaction,
                FurySwipesChance = 0.05f * talents.FurySwipes,
                BonusEnrageDamageMultiplier = 0.05f * talents.KingOfTheJungle,
                HasteOnFeralCharge = 0.15f * talents.Stampede,
                BaseArmorMultiplier = 2.2f * (1f + 0.10f * talents.ThickHide / 3f) * (1f + 0.26f * talents.ThickHide) - 1f,
                CritChanceReduction = 0.02f * talents.ThickHide,
                PhysicalCrit = (hasCritBuff ? 0f : 0.05f * talents.LeaderOfThePack) + (talents.Pulverize > 0 ? 0.09f: 0f),
                SpellCrit = (hasCritBuff ? 0f : 0.05f * talents.LeaderOfThePack),
                BonusPulverizeDuration = 4f * talents.EndlessCarnage,
                DamageTakenReductionMultiplier = 0.09f * talents.NaturalReaction,
                BonusMaulDamageMultiplier = 0.04f * talents.RendAndTear,
                
                BonusStaminaMultiplier = (1f + 0.02f * talents.HeartOfTheWild) * (Character.ValidateArmorSpecialization(character, ItemType.Leather) ? 1.05f : 1f) - 1f,
                BonusPhysicalDamageMultiplier = 0.04f * talents.MasterShapeshifter,
                BonusMangleDamageMultiplier = talents.GlyphOfMangle ? 0.1f : 0f,
                BonusLacerateCritChance = talents.GlyphOfLacerate ? 0.05f : 0f,
                BonusFaerieFireStacks = talents.FeralAggression,
                BerserkDuration = (talents.GlyphOfBerserk ? 10f : 0f),
            };

            #region Set Bonuses
            int PvPCount;
            character.SetBonusCount.TryGetValue("Gladiator's Sanctuary", out PvPCount);
            if (PvPCount >= 2)
            {
                statsTotal.Agility += 70f;
                statsTotal.Resilience += 400f;
            }
            if (PvPCount >= 4)
            {
                // the 15% movement speed is only outdoors which most dungeons are not
                statsTotal.Agility += 90f;
            }
            int T11Count;
            character.SetBonusCount.TryGetValue("Stormrider's Battlegarb", out T11Count);
            if (T11Count >= 2) {
                //statsTotal.BonusDamageMultiplierRakeTick = (1f + statsTotal.BonusDamageMultiplierRakeTick) * (1f + 0.10f) - 1f;
                statsTotal.BonusDamageMultiplierLacerate = (1f + statsTotal.BonusDamageMultiplierLacerate) * (1f + 0.10f) - 1f;
            }
            if (T11Count >= 4)
            {
                /*statsBuffs.AddSpecialEffect(new SpecialEffect(Trigger.MangleCatHit,
                    new Stats() { BonusAttackPowerMultiplier = 0.01f, },
                    30, 0, 1f, 3));*/
                statsTotal.BonusSurvivalInstinctsDurationMultiplier = 0.5f;
            }
            int T12Count;
            character.SetBonusCount.TryGetValue("Obsidian Arborweave Battlegarb", out T12Count);
            if (T12Count >= 2)
            {
                statsTotal.BonusMangleDamageMultiplier = (1f + statsTotal.BonusMangleDamageMultiplier) * (1f + 0.10f) - 1f;
                statsTotal.BonusMaulDamageMultiplier = (1f + statsTotal.BonusMaulDamageMultiplier) * (1f + 0.10f) - 1f;
            }
            if (T12Count >= 4)
            {
                statsTotal.AddSpecialEffect(SpecialEffect4T12);
            }
            
            int T13Count;
            character.SetBonusCount.TryGetValue("Deep Earth Battlegarb", out T13Count);
            if (T13Count >= 2)
            {
                statsTotal.Tier_13_2_piece = true;
            }
            if (T13Count >= 4)
            {
                statsTotal.Tier_13_4_piece = (10f + 25f)/2;
            }
            #endregion

            // Leader of the Pack self-heal
            statsTotal.AddSpecialEffect(LeaderOfThePackSpecialEffect);

            // Survival Instincts
            SpecialEffect SurvivalInstinctsSpecialEffect = new SpecialEffect(Trigger.Use, new Stats() { DamageTakenReductionMultiplier = 0.50f, }, 12f * (1f + statsTotal.BonusSurvivalInstinctsDurationMultiplier), 180f, 1f);
            statsTotal.AddSpecialEffect(SurvivalInstinctsSpecialEffect);

            // Barkskin
            SpecialEffect BarkskinSpecialEffect = new SpecialEffect(Trigger.Use, new Stats() { DamageTakenReductionMultiplier = 0.20f, CritChanceReduction = (talents.GlyphOfBarkskin ? 0.25f : 0f), }, 12f, 60f, 1f);
            statsTotal.AddSpecialEffect(BarkskinSpecialEffect);

            // Frenzied Regeneration
            SpecialEffect FrenziedRegenerationSpecialEffect = new SpecialEffect(Trigger.Use, new Stats() { BonusHealthMultiplier = 0.15f, HealthRestoreFromMaxHealth = (talents.GlyphOfFrenziedRegeneration ? 0f : (0.015f * (1f + statsTotal.Tier_13_4_piece))), HealingReceivedMultiplier = (talents.GlyphOfFrenziedRegeneration ? (0.30f * (1f + statsTotal.Tier_13_4_piece)) : 0f) }, 20f, 180f, 1f);
            statsTotal.AddSpecialEffect(FrenziedRegenerationSpecialEffect);

            // Berserk
            StatsBear tempBear = new StatsBear();
            tempBear.AddSpecialEffect(new SpecialEffect(Trigger.LacerateTick, new StatsBear() { MangleCooldownReduction = 6f, MangleCostReduction = 1f }, float.PositiveInfinity, 0, 0.5f));
            SpecialEffect BerserkSpecialEffect = new SpecialEffect(Trigger.Use, tempBear, 15f + statsTotal.BerserkDuration, 180f, 1f);
            statsTotal.AddSpecialEffect(BerserkSpecialEffect);

            // Enrage
            SpecialEffect EnrageSpecialEffect = new SpecialEffect(Trigger.Use, new StatsBear() { BonusDamageMultiplier = (0.05f * talents.KingOfTheJungle) }, 10f, 60f, 1f);
            statsTotal.AddSpecialEffect(EnrageSpecialEffect);

            statsTotal.Accumulate(BaseStats.GetBaseStats(character.Level, character.Class, character.Race, BaseStats.DruidForm.Bear));
            statsTotal.Accumulate(GetItemStats(character, additionalItem));
            statsTotal.Accumulate(GetBuffsStats(character, calcOpts));

            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 * (1f + statsTotal.BonusAgilityMultiplier));
            statsTotal.AttackPower += (float)Math.Floor(statsTotal.Strength);
            statsTotal.AttackPower += (float)Math.Floor(statsTotal.Agility - 20f) * 2f + 20f;
            statsTotal.AttackPower = (float)Math.Floor(statsTotal.AttackPower * (1f + statsTotal.BonusAttackPowerMultiplier));
            statsTotal.Health += ((statsTotal.Stamina - 20f) * 14f) + 20f;
            statsTotal.Health *= (1f + statsTotal.BonusHealthMultiplier);
            statsTotal.Armor *= 1f + statsTotal.BaseArmorMultiplier;
            statsTotal.Armor += statsTotal.BonusArmor;
            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;

            AccumulateProcs(character, statsTotal);

            return statsTotal;
        }
        /// <summary>
        /// Calculates the threat properties of the Character
        /// </summary>
        /// <param name="stats">The total Stats of the character</param>
        /// <param name="targetLevel">The level of the target</param>
        /// <param name="calculatedStats">The CharacterCalculationsBear object to fill with results</param>
        /// <param name="character">The Character to calculate the threat properties of</param>
        private void CalculateThreat(StatsBear stats, int targetLevel, CharacterCalculationsBear calculatedStats, Character character)
        {
            CalculationOptionsBear calcOpts = character.CalculationOptions as CalculationOptionsBear ?? new CalculationOptionsBear();
            BossOptions bossOpts = character.BossOptions;
            if (bossOpts.Attacks.Count < 1) { bossOpts.Attacks.Add(BossHandler.ADefaultMeleeAttack); bossOpts.Attacks[0].IsTheDefaultMelee = true; bossOpts.DamagingTargs = true; }
            if (bossOpts.DefaultMeleeAttack == null) { bossOpts.Attacks.Add(BossHandler.ADefaultMeleeAttack); bossOpts.Attacks[bossOpts.Attacks.Count - 1].IsTheDefaultMelee = true; bossOpts.DamagingTargs = true; }
            Attack bossAttack = bossOpts.DefaultMeleeAttack;
            DruidTalents talents = character.DruidTalents;

            // Establish base multipliers and chances
            float modArmor = 1f - StatConversion.GetArmorDamageReduction(character.Level, bossOpts.Armor,
                stats.TargetArmorReduction, stats.ArmorPenetration);

            float critMultiplier = 2f * (1 + stats.BonusCritDamageMultiplier);
            float spellCritMultiplier = 2f * (1 + stats.BonusCritDamageMultiplier);

            float hasteBonus = StatConversion.GetPhysicalHasteFromRating(stats.HasteRating, CharacterClass.Druid);
            float attackSpeed = (2.5f) / (1f + hasteBonus);
            attackSpeed = attackSpeed / (1f + stats.PhysicalHaste);

            float hitBonus = StatConversion.GetPhysicalHitFromRating(stats.HitRating, CharacterClass.Druid) + stats.PhysicalHit;
            float expertiseBonus = StatConversion.GetDodgeParryReducFromExpertise(StatConversion.GetExpertiseFromRating(stats.ExpertiseRating, CharacterClass.Druid) + stats.Expertise, CharacterClass.Druid);
            float spellHitBonus = StatConversion.GetSpellHitFromRating(stats.HitRating, CharacterClass.Druid) + stats.SpellHit;
            
            int levelDifference = (targetLevel - character.Level);
            float chanceDodge = Math.Max(0f, StatConversion.WHITE_DODGE_CHANCE_CAP[levelDifference] - expertiseBonus);
            float chanceParry = Math.Max(0f, StatConversion.WHITE_PARRY_CHANCE_CAP[levelDifference] - expertiseBonus);
            float chanceMiss = Math.Max(0f, StatConversion.WHITE_MISS_CHANCE_CAP[levelDifference] - hitBonus);
            float chanceResist = Math.Max(0f, StatConversion.GetSpellMiss(-levelDifference, false) - spellHitBonus);
            
            float chanceGlance = StatConversion.WHITE_GLANCE_CHANCE_CAP[levelDifference]; //0.2335774f;
            //float glanceMultiplier = 0.7f;
            float chanceAvoided = chanceMiss + chanceDodge + chanceParry;
            
            float rawChanceCrit = StatConversion.GetPhysicalCritFromRating(stats.CritRating, CharacterClass.Druid)
                                + StatConversion.GetPhysicalCritFromAgility(stats.Agility, CharacterClass.Druid)
                                + stats.PhysicalCrit
                                + StatConversion.NPC_LEVEL_CRIT_MOD[levelDifference];
            float chanceCrit = rawChanceCrit * (1f - chanceAvoided);
            
            float rawChanceCritSpell = StatConversion.GetSpellCritFromRating(stats.CritRating, CharacterClass.Druid)
                                + StatConversion.GetSpellCritFromIntellect(stats.Intellect, CharacterClass.Druid)
                                + stats.SpellCrit + stats.SpellCritOnTarget
                                + StatConversion.NPC_LEVEL_CRIT_MOD[levelDifference];
            float chanceCritSpell = rawChanceCritSpell * (1f - chanceResist);

            calculatedStats.DodgedAttacks = chanceDodge;
            calculatedStats.ParriedAttacks = chanceParry;
            calculatedStats.MissedAttacks = chanceMiss;

            float movementdowntime = 3f / 5.5f / (1 + stats.MovementSpeed); // Movement Duration / Movement Frequency / (1 + Movement Speed)
            
            BearAbilityBuilder abilities = new BearAbilityBuilder(stats,
                character.MainHand == null ? 0.75f : ((character.MainHand.MinDamage + character.MainHand.MaxDamage) / 2f) / character.MainHand.Speed,
                attackSpeed, modArmor, chanceAvoided, chanceResist, chanceCrit, chanceCritSpell, chanceGlance, critMultiplier, spellCritMultiplier);
            var optimalRotations = BearRotationCalculator.GetOptimalRotations(abilities);
            calculatedStats.Abilities = abilities;
            calculatedStats.HighestDPSRotation = optimalRotations.Item1;
            float bonusdamage = stats.ArcaneDamage + stats.FireDamage + stats.FrostDamage + stats.NatureDamage + stats.ShadowDamage + stats.HolyDamage + stats.PhysicalDamage;
            calculatedStats.HighestDPSRotation.DPS += bonusdamage;
            calculatedStats.HighestDPSRotation.DPS *= 1 - movementdowntime;
            calculatedStats.HighestTPSRotation = optimalRotations.Item2;
            calculatedStats.HighestTPSRotation.TPS += bonusdamage * 5f;
            calculatedStats.HighestTPSRotation.TPS *= 1 - movementdowntime;
            calculatedStats.ThreatPoints = calculatedStats.HighestTPSRotation.TPS * calcOpts.ThreatScale / 10f;
        }