/// <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; }
/// <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; }