Example #1
0
        /// <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;
        }
Example #2
0
        /// <summary>
        /// Gets the results of the Character provided
        /// </summary>
        /// <param name="character">The Character to calculate results for</param>
        /// <param name="additionalItem">An additional item to grant the Character the stats of (as if it were worn)</param>
        /// <returns>The CharacterCalculationsBear containing the results of the calculations</returns>
        public override CharacterCalculationsBase GetCharacterCalculations(Character character, Item additionalItem, bool referenceCalculation, bool significantChange, bool needsDisplayCalculations)
        {
            #region Setup uniform variables from all models
            CharacterCalculationsBear calculatedStats = new CharacterCalculationsBear();
            if (character == null) { return calculatedStats; }
            CalculationOptionsBear calcOpts = character.CalculationOptions as CalculationOptionsBear;
            if (calcOpts == null) { return calculatedStats; }
            BossOptions bossOpts = character.BossOptions;
            // Make sure there is at least one attack in the list.
            // If there's not, add a Default Melee Attack for processing
            if (bossOpts.Attacks.Count < 1) {
                character.IsLoading = true;
                bossOpts.DamagingTargs = true;
                bossOpts.Attacks.Add(BossHandler.ADefaultMeleeAttack);
                character.IsLoading = false;
            }
            // Make sure there is a default melee attack
            // If the above processed, there will be one so this won't have to process
            // If the above didn't process and there isn't one, add one now
            if (bossOpts.DefaultMeleeAttack == null) {
                character.IsLoading = true;
                bossOpts.DamagingTargs = true;
                bossOpts.Attacks.Add(BossHandler.ADefaultMeleeAttack);
                character.IsLoading = false;
            }
            // Since the above forced there to be an attack it's safe to do this without a null check
            Attack bossAttack = bossOpts.DefaultMeleeAttack;
            StatsBear stats = GetCharacterStats(character, additionalItem) as StatsBear;
            calculatedStats.BasicStats = stats;
            calculatedStats.TargetLevel = bossOpts.Level;
            calculatedStats.CharacterLevel = character.Level;

            //calcOpts.SurvivalSoftCap = bossOpts.DefaultMeleeAttack.DamagePerHit * 3f;
            #endregion

            int levelDifference = (bossOpts.Level - character.Level);

            #region Player Stats

            #region Offensive
            float playerHasteBonus = StatConversion.GetHasteFromRating(stats.HasteRating, CharacterClass.Druid);
            float playerAttackSpeed = ((2.5f) / (1f + playerHasteBonus)) / (1f + stats.PhysicalHaste);

            float playerHitBonus = StatConversion.GetPhysicalHitFromRating(stats.HitRating, CharacterClass.Druid) + stats.PhysicalHit;
            float playerExpertiseBonus = StatConversion.GetDodgeParryReducFromExpertise(StatConversion.GetExpertiseFromRating(stats.ExpertiseRating, CharacterClass.Druid) + stats.Expertise, CharacterClass.Druid);
            float playerChanceToBeDodged = Math.Max(0f, StatConversion.WHITE_DODGE_CHANCE_CAP[levelDifference] - playerExpertiseBonus);
            float playerChancetoBeParried = Math.Max(0f, StatConversion.WHITE_PARRY_CHANCE_CAP[levelDifference] - playerExpertiseBonus);
            float playerChanceToMiss = Math.Max(0f, StatConversion.WHITE_MISS_CHANCE_CAP[levelDifference] - playerHitBonus);

            float playerChanceToBeAvoided = playerChanceToMiss + playerChanceToBeDodged + playerChancetoBeParried;

            float playerRawChanceToCrit = StatConversion.GetPhysicalCritFromRating(stats.CritRating, CharacterClass.Druid)
                                + StatConversion.GetPhysicalCritFromAgility(stats.Agility, CharacterClass.Druid)
                                + stats.PhysicalCrit
                                + StatConversion.NPC_LEVEL_CRIT_MOD[levelDifference];
            float playerChanceToCrit = playerRawChanceToCrit * (1f - playerChanceToBeAvoided);
            float playerChanceToCritBleed = playerRawChanceToCrit;
            #endregion

            Stats baseStats = BaseStats.GetBaseStats(character.Level, character.Class, character.Race, BaseStats.DruidForm.Bear);

            #region Defensive Part A
            //Calculate avoidance, considering diminishing returns
            float levelDifferenceAvoidance = levelDifference * 0.002f;

            float DR_k_coefficient = StatConversion.DR_COEFFIENT[11] * 0.01f; // This is the Diminishing Returns' "k" value that changes based on what class the user is
            float DR_C_d_coefficient = StatConversion.CAP_DODGE[11]; // This is the % cap for dodge
            float DR_miss_cap = 16f;

            //float defSkill = (float)Math.Floor(StatConversion.GetDefenseFromRating(stats.DefenseRating, CharacterClass.Druid));
            float dodgeThatsNotAffectedByDR = stats.Dodge - levelDifferenceAvoidance + StatConversion.GetDodgeFromAgility(baseStats.Agility, CharacterClass.Druid);
            float missThatsNotAffectedByDR = stats.Miss - levelDifferenceAvoidance;
            float dodgeBeforeDRApplied = StatConversion.GetDodgeFromAgility(stats.Agility - baseStats.Agility, CharacterClass.Druid)
                                       + StatConversion.GetDodgeFromRating(stats.DodgeRating, CharacterClass.Druid);
            float missBeforeDRApplied = 0f;
            float dodgeAfterDRApplied = 0.01f / (1f / DR_C_d_coefficient + DR_k_coefficient / dodgeBeforeDRApplied);
            float missAfterDRApplied = 0.01f / (1f / DR_miss_cap + DR_k_coefficient / missBeforeDRApplied);
            float dodgeTotal = dodgeThatsNotAffectedByDR + dodgeAfterDRApplied;
            float missTotal = missThatsNotAffectedByDR + missAfterDRApplied;

            calculatedStats.Miss = missTotal;
            calculatedStats.Dodge = Math.Min(1f - calculatedStats.Miss, dodgeTotal);
            calculatedStats.DamageReductionFromArmor = StatConversion.GetDamageReductionFromArmor(bossOpts.Level, stats.Armor);
            calculatedStats.TotalConstantDamageReduction = 1f - (1f - calculatedStats.DamageReductionFromArmor) * (1f - stats.DamageTakenReductionMultiplier) * (1f - stats.BossPhysicalDamageDealtReductionMultiplier);
            calculatedStats.AvoidancePreDR = dodgeThatsNotAffectedByDR + dodgeBeforeDRApplied + missThatsNotAffectedByDR + missBeforeDRApplied;
            calculatedStats.AvoidancePostDR = dodgeTotal + missTotal;
            calculatedStats.CritReduction = stats.CritChanceReduction;
            calculatedStats.CappedCritReduction = Math.Min(0.05f + levelDifferenceAvoidance, calculatedStats.CritReduction);
            #endregion

            #region Vengeance
            {
                // == Evaluate damage taken once ahead of time for vengeance ==
                //Out of 100 attacks, you'll take...
                float critsVeng = Math.Min(Math.Max(0f, 1f - calculatedStats.AvoidancePostDR), (0.05f + levelDifferenceAvoidance) - calculatedStats.CappedCritReduction);
                //float crushes = targetLevel == 73 ? Math.Max(0f, Math.Min(15f, 100f - (crits + calculatedStats.AvoidancePreDR)) - stats.CritChanceReduction) : 0f;
                float hitsVeng = Math.Max(0f, 1f - (critsVeng + calculatedStats.AvoidancePostDR));
                //Apply armor and multipliers for each attack type...
                critsVeng *= (1f - calculatedStats.TotalConstantDamageReduction) * 2f;
                //crushes *= (100f - calculatedStats.Mitigation) * .015f;
                hitsVeng *= (1f - calculatedStats.TotalConstantDamageReduction);
                float damageTakenPercent = (hitsVeng + critsVeng) * (1f - stats.BossAttackSpeedReductionMultiplier);
                float damageTakenPerHit = bossAttack.DamagePerHit * damageTakenPercent;
                float damageTakenPerSecond = damageTakenPerHit / bossAttack.AttackSpeed;
                float damageTakenPerVengeanceTick = damageTakenPerSecond * 2f;
                float vengeanceCap = stats.Stamina + baseStats.Health * 0.1f;
                float vengeanceAPPreAvoidance = Math.Min(vengeanceCap, damageTakenPerVengeanceTick) ;

                double chanceHit = 1f - calculatedStats.AvoidancePostDR;
                double vengeanceMultiplierFromAvoidance = //Best-fit of results from simulation of avoidance effects on vengeance
                    -46.288470839554d * Math.Pow(chanceHit, 6) 
                    + 143.12528411194400d * Math.Pow(chanceHit, 5) 
                    - 159.9833254324610000d * Math.Pow(chanceHit, 4)
                    + 74.0451030489808d * Math.Pow(chanceHit, 3) 
                    - 10.8422088672455d * Math.Pow(chanceHit, 2) 
                    + 0.935157126508557d * chanceHit;

                float vengeanceMultiplierFromSwingSpeed = bossAttack.AttackSpeed <= 2f ? 1f :
                    (1f - 0.1f * (1f - 2f / bossAttack.AttackSpeed)); //A percentage of the ticks will be guaranteed decays for attack speeds longer than 2sec, due to no swings occuring between the current and last tick

                float vengeanceAP = (float)(vengeanceAPPreAvoidance * vengeanceMultiplierFromAvoidance * vengeanceMultiplierFromSwingSpeed);

                stats.AttackPower += vengeanceAP * (1f + stats.BonusAttackPowerMultiplier);
                calculatedStats.AverageVengeanceAP = vengeanceAP;
            }
            #endregion

            #region Defensive Part B
            float targetAttackSpeedDebuffed = bossAttack.AttackSpeed / (1f - stats.BossAttackSpeedReductionMultiplier);
            float targetHitChance = 1f - calculatedStats.AvoidancePostDR;
            float autoSpecialAttacksPerSecond = 1f / 1.5f + 1f / playerAttackSpeed + 1f / 3f /*+ (stats.Tier_13_2_piece ? (1f / calculatedStats.Abilities.MangleStats.Cooldown) : 0)*/;

            float masteryMultiplier = 1f + (8f + StatConversion.GetMasteryFromRating(stats.MasteryRating)) * 0.04f;
            float totalAttacksPerSecond = autoSpecialAttacksPerSecond;
            float averageSDAttackCritChance = 0.5f * (playerChanceToCrit * (autoSpecialAttacksPerSecond / totalAttacksPerSecond)); //Include the 50% chance to proc per crit here.
            //float T13_2PSDAttackCritChance = (playerChanceToCrit * (autoSpecialAttacksPerSecond / totalAttacksPerSecond));
            //averageSDAttackCritChance = (1 + averageSDAttackCritChance) * (1 + (stats.Tier_13_2_piece ? T13_2PSDAttackCritChance : 0)) - 1f;
            float playerAttacksInterval = 1f / totalAttacksPerSecond;
            float blockChance = 1f - targetHitChance * ((float)Math.Pow(1f - averageSDAttackCritChance, targetAttackSpeedDebuffed / playerAttacksInterval)) *
                1f / (1f - (1f - targetHitChance) * (float)Math.Pow(1f - averageSDAttackCritChance, targetAttackSpeedDebuffed / playerAttacksInterval));
            float blockValue = stats.AttackPower * 0.35f * masteryMultiplier;
            float blockedPercent = Math.Min(1f, (blockValue * blockChance) / ((1f - calculatedStats.TotalConstantDamageReduction) * bossAttack.DamagePerHit));
            calculatedStats.SavageDefenseChance = (float)Math.Round(blockChance, 5);
            calculatedStats.SavageDefenseValue = (float)Math.Floor(blockValue);
            calculatedStats.SavageDefensePercent = (float)Math.Round(blockedPercent, 5);
            #endregion

            #endregion

            #region Mitigation Points
            // Out of 100 attacks, you'll take...
            float crits = Math.Min(Math.Max(0f, 1f - calculatedStats.AvoidancePostDR), (0.05f + levelDifferenceAvoidance) - calculatedStats.CappedCritReduction);
            float hits = Math.Max(0f, 1f - (crits + calculatedStats.AvoidancePostDR));
            // Apply armor and multipliers for each attack type...
            crits *= (1f - calculatedStats.TotalConstantDamageReduction) * 2f;
            hits  *= (1f - calculatedStats.TotalConstantDamageReduction);
            calculatedStats.DamageTaken = (hits + crits) * (1f - blockedPercent) * (1f - stats.BossAttackSpeedReductionMultiplier);
            calculatedStats.TotalMitigation = 1f - calculatedStats.DamageTaken;
            // Final Mitigation Score
            calculatedStats.MitigationPoints = (StatConversion.MitigationScaler / calculatedStats.DamageTaken);
            #endregion

            #region Survivability Points
            float healingincoming = (stats.Health / 2.5f) * (1f + stats.HealingReceivedMultiplier);
            float healthRestoreFromMaxHealth = stats.Health * stats.HealthRestoreFromMaxHealth;
            float healthrestore = stats.Healed + stats.HealthRestore + stats.BonusHealingReceived + ((healingincoming > healthRestoreFromMaxHealth) ? (healingincoming - healthRestoreFromMaxHealth) : healthRestoreFromMaxHealth);
            
            calculatedStats.SurvivalPointsRaw = ((stats.Health + stats.DamageAbsorbed + healthrestore) / (1f - calculatedStats.TotalConstantDamageReduction));
            double survivalCap = /*bossAttack.DamagePerHit * calcOpts.HitsToSurvive*/ calcOpts.SurvivalSoftCap / 1000d;
            double survivalRaw = calculatedStats.SurvivalPointsRaw / 1000d;

            //Implement Survival Soft Cap
            if (survivalRaw <= survivalCap) {
                calculatedStats.SurvivabilityPoints = 1000f * (float)survivalRaw;
            } else {
                double x = survivalRaw;
                double cap = survivalCap;
                double fourToTheNegativeFourThirds = Math.Pow(4d, -4d / 3d);
                double topLeft = Math.Pow(((x - cap) / cap) + fourToTheNegativeFourThirds, 1d / 4d);
                double topRight = Math.Pow(fourToTheNegativeFourThirds, 1d / 4d);
                double fracTop = topLeft - topRight;
                double fraction = fracTop / 2d;
                double y = (cap * fraction + cap);
                calculatedStats.SurvivabilityPoints = 1000f * (float)y;
            }
            #endregion

            #region Survivability for each Resistance Type (magical attacks)
            // Call new resistance formula and apply talent damage reduction
            // As for other survival, only use guaranteed reduction (MinimumResist), no luck
            float naturalReactionMOD = (1f - 0.09f * character.DruidTalents.NaturalReaction);
            calculatedStats.NatureSurvivalPoints = (float)(stats.Health / ((1f - StatConversion.GetMinimumResistance(bossOpts.Level, character.Level, stats.NatureResistance, 0)) * naturalReactionMOD));
            calculatedStats.FrostSurvivalPoints  = (float)(stats.Health / ((1f - StatConversion.GetMinimumResistance(bossOpts.Level, character.Level, stats.FrostResistance,  0)) * naturalReactionMOD));
            calculatedStats.FireSurvivalPoints   = (float)(stats.Health / ((1f - StatConversion.GetMinimumResistance(bossOpts.Level, character.Level, stats.FireResistance,   0)) * naturalReactionMOD));
            calculatedStats.ShadowSurvivalPoints = (float)(stats.Health / ((1f - StatConversion.GetMinimumResistance(bossOpts.Level, character.Level, stats.ShadowResistance, 0)) * naturalReactionMOD));
            calculatedStats.ArcaneSurvivalPoints = (float)(stats.Health / ((1f - StatConversion.GetMinimumResistance(bossOpts.Level, character.Level, stats.ArcaneResistance, 0)) * naturalReactionMOD));
            #endregion

            //Perform Threat calculations
            CalculateThreat(stats, bossOpts.Level, calculatedStats, character);

            calculatedStats.OverallPoints = calculatedStats.MitigationPoints + calculatedStats.SurvivabilityPoints + calculatedStats.ThreatPoints;

            return calculatedStats;
        }