public TreeStats(Character character, Stats stats, KeyValuePair <double, SpecialEffect>[] hasteProcs, double treeOfLifeUptime) { CalculationOptionsTree opts = character.CalculationOptions as CalculationOptionsTree; bool Restoration = (opts != null) ? opts.Restoration : true; Haste = new HasteStats(stats, hasteProcs); SpellCrit = StatConversion.GetSpellCritFromIntellect(stats.Intellect) + StatConversion.GetSpellCritFromRating(stats.CritRating) + stats.SpellCrit; SpellPower = (float)(stats.SpellPower + Math.Max(0f, stats.Intellect - 10)); // TODO: does nurturing instinct actually work like this? SpellPower += character.DruidTalents.NurturingInstinct * 0.5 * stats.Agility; SpellPower *= (1 + stats.BonusSpellPowerMultiplier); TreeOfLifeUptime = treeOfLifeUptime; double mastery = 8.0f + StatConversion.GetMasteryFromRating(stats.MasteryRating); if (Restoration) { Harmony = mastery * 0.0125f; } SpellsManaCostReduction = stats.SpellsManaCostReduction + stats.NatureSpellsManaCostReduction; BonusCritHealMultiplier = stats.BonusCritHealMultiplier; PassiveDirectHealBonus = (Restoration ? 1.25f : 1.0f) + Harmony; PassivePeriodicHealBonus = (Restoration ? 1.25f : 1.0f) + opts.HarmonyPeriodicRate * Harmony + 0.02f * character.DruidTalents.Genesis; DirectHealMultiplier = (1 + stats.BonusHealingDoneMultiplier) * (1.0f + character.DruidTalents.MasterShapeshifter * 0.04f) * (1 + TreeOfLifeUptime * 0.15f); PeriodicHealMultiplier = DirectHealMultiplier * (1 + stats.BonusPeriodicHealingMultiplier); SpellsManaCostMultiplier = 1 - stats.ManaCostReductionMultiplier; Healed = stats.Healed + stats.HealedPerSP * SpellPower; }
public static float CalcSpellCrit(Stats stats) { return(stats.SpellCrit + StatConversion.GetSpellCritFromIntellect(CalcIntellect(stats)) + StatConversion.GetSpellCritFromRating(stats.CritRating) + stats.BonusCritChance + stats.SpellCritOnTarget); }
public override Stats GetCharacterStats(Character character, Item additionalItem) { CalculationOptionsHealPriest calcOpts = character.CalculationOptions as CalculationOptionsHealPriest; StatsPriest statsTotal = new StatsPriest() { SpellHaste = PriestInformation.GetDarkness(character.PriestTalents.Darkness), InnerFire = true, BonusIntellectMultiplier = 0.05f, // Cloth bonus. PriestSpec = PriestSpec.GetPriestSpec(character.PriestTalents), }; if (statsTotal.PriestSpec == ePriestSpec.Spec_Disc) { statsTotal.SpellCombatManaRegeneration = 0.5f; statsTotal.BonusIntellectMultiplier = 0.15f; } else if (statsTotal.PriestSpec == ePriestSpec.Spec_Holy) { statsTotal.SpellCombatManaRegeneration = 0.5f + PriestInformation.GetHolyConcentration(character.PriestTalents.HolyConcentration); statsTotal.BonusHealingDoneMultiplier = 0.15f; } else if (statsTotal.PriestSpec == ePriestSpec.Spec_ERROR) { throw new Exception("Unpossible Talent Spec!"); } statsTotal.Accumulate(BaseStats.GetBaseStats(character)); statsTotal.Accumulate(GetItemStats(character, additionalItem)); statsTotal.Accumulate(GetBuffsStats(character, calcOpts)); statsTotal.Stamina = (float)Math.Floor((statsTotal.Stamina) * (1 + statsTotal.BonusStaminaMultiplier)); statsTotal.Intellect = (float)Math.Floor((statsTotal.Intellect) * (1 + statsTotal.BonusIntellectMultiplier)); statsTotal.Spirit = (float)Math.Floor((statsTotal.Spirit) * (1 + statsTotal.BonusSpiritMultiplier)); statsTotal.SpellPower += (statsTotal.InnerFire ? PriestInformation.GetInnerFireSpellPowerBonus(character) : 0) + (statsTotal.Intellect - 10); statsTotal.SpellPower *= (1f + statsTotal.BonusSpellPowerMultiplier); statsTotal.Mana += StatConversion.GetManaFromIntellect(statsTotal.Intellect); statsTotal.Mana *= (1f + statsTotal.BonusManaMultiplier); statsTotal.Health += StatConversion.GetHealthFromStamina(statsTotal.Stamina); statsTotal.Health = (float)Math.Floor(statsTotal.Health * (1f + statsTotal.BonusHealthMultiplier)); statsTotal.SpellCrit += StatConversion.GetSpellCritFromIntellect(statsTotal.Intellect) + StatConversion.GetSpellCritFromRating(statsTotal.CritRating); statsTotal.SpellHaste = (1f + statsTotal.SpellHaste) * (1f + StatConversion.GetSpellHasteFromRating(statsTotal.HasteRating)) - 1f; statsTotal.Armor *= (1 + (statsTotal.InnerFire ? PriestInformation.GetInnerFireArmorBonus(character) : 0)); if (statsTotal.PriestSpec == ePriestSpec.Spec_Disc) { statsTotal.ShieldDiscipline = (PriestInformation.DisciplineMasteryBase + StatConversion.GetMasteryFromRating(statsTotal.MasteryRating)) * PriestInformation.DisciplineMasteryEffect; } else if (statsTotal.PriestSpec == ePriestSpec.Spec_Holy) { statsTotal.EchoofLight = (PriestInformation.HolyMasteryBase + StatConversion.GetMasteryFromRating(statsTotal.MasteryRating)) * PriestInformation.HolyMasteryEffect; } return(statsTotal); }
public static float GetSpellCritFromRating(float rating, int playerLevel) { if (playerLevel == 85) { return(StatConversion.GetSpellCritFromRating(rating)); } else { float[] scaling = { 45.905986785888672f, 60.278423309326172f, 79.155647277832031f, 103.985641479492188f, 136.538131713867188f }; return(rating / scaling[playerLevel - 80] * 0.01f); } }
private void ConvertRatings(Stats stats, PaladinTalents talents, CalculationOptionsHealadin calcOpts) { stats.Stamina *= 1f + stats.BonusStaminaMultiplier; // Intellect is used to calculate initial mana pool. // To avoid temporary intellect from highest stat procs changing initial mana pool // we track temporary intellect separatly in HighestStat property and combine it with intellect // when needed. // TODO: However this doesn't help to deal with pure temporary intellect procs (if there are any). // NOTE: If we add highest stat to intellect after we calculate mana, the only visible change // will be the displayed intellect for the character. stats.Intellect *= (1f + stats.BonusIntellectMultiplier); stats.HighestStat *= (1f + stats.BonusIntellectMultiplier); stats.SpellCrit = stats.SpellCrit + StatConversion.GetSpellCritFromIntellect( stats.Intellect + stats.HighestStat, CharacterClass.Paladin) + StatConversion.GetSpellCritFromRating(stats.CritRating, CharacterClass.Paladin); // I want to track haste before talent seperately, going to use RangedHaste for that. // I plan to use this on the "Stats" page so I can report sources of haste seperatly stats.RangedHaste = (1f + stats.SpellHaste) * (1f + StatConversion.GetSpellHasteFromRating(stats.HasteRating, CharacterClass.Paladin)) - 1f; // calculating physical haste for use in melee attacks, which will generate mana // can also divide spellhaste / physicalhaste to get orignal value of spellhaste, which is from buffs as far as I can tell stats.PhysicalHaste = (1f + talents.JudgementsOfThePure * 0.03f) * (1f + talents.SpeedOfLight * 0.01f) * (1f + StatConversion.GetSpellHasteFromRating(stats.HasteRating, CharacterClass.Paladin)) - 1f; stats.SpellHaste = (1f + talents.JudgementsOfThePure * 0.03f) * (1f + talents.SpeedOfLight * 0.01f) * (1f + stats.SpellHaste) * (1f + StatConversion.GetSpellHasteFromRating(stats.HasteRating, CharacterClass.Paladin)) - 1f; // GetManaFromIntellect/GetHealthFromStamina account for the fact // that the first 20 Int/Sta only give 1 Mana/Health each. stats.Mana += StatConversion.GetManaFromIntellect(stats.Intellect, CharacterClass.Paladin) * (1f + stats.BonusManaMultiplier); stats.Health += StatConversion.GetHealthFromStamina(stats.Stamina, CharacterClass.Paladin); stats.PhysicalHit += StatConversion.GetPhysicalHitFromRating( stats.HitRating, CharacterClass.Paladin); }
public static float CalcSpellCrit(Stats stats, float baseIntellect, int playerLevel) { if (playerLevel == 85) { return(stats.SpellCrit + StatConversion.GetSpellCritFromIntellect(CalcIntellect(stats, baseIntellect, playerLevel)) + StatConversion.GetSpellCritFromRating(stats.CritRating) + stats.BonusCritChance + stats.SpellCritOnTarget); } else { return(stats.SpellCrit + GetSpellCritFromIntellect(CalcIntellect(stats, baseIntellect, playerLevel), playerLevel) + GetSpellCritFromRating(stats.CritRating, playerLevel) + stats.BonusCritChance + stats.SpellCritOnTarget); } }
private void ConvertRatings(Stats stats, PaladinTalents talents, CalculationOptionsHealadin calcOpts) { stats.Stamina *= 1f + stats.BonusStaminaMultiplier; // Intellect is used to calculate initial mana pool. // To avoid temporary intellect from highest stat procs changing initial mana pool // we track temporary intellect separatly in HighestStat property and combine it with intellect // when needed. // TODO: However this doesn't help to deal with pure temporary intellect procs (if there are any). // NOTE: If we add highest stat to intellect after we calculate mana, the only visible change // will be the displayed intellect for the character. stats.Intellect *= (1f + stats.BonusIntellectMultiplier) * (1f + talents.DivineIntellect * .02f); stats.HighestStat *= (1f + stats.BonusIntellectMultiplier) * (1f + talents.DivineIntellect * .02f); stats.SpellPower += 0.04f * (stats.Intellect + stats.HighestStat) * talents.HolyGuidance; stats.SpellCrit = stats.SpellCrit + StatConversion.GetSpellCritFromIntellect( stats.Intellect + stats.HighestStat, CharacterClass.Paladin) + StatConversion.GetSpellCritFromRating(stats.CritRating, CharacterClass.Paladin) + talents.SanctityOfBattle * .01f + talents.Conviction * .01f; stats.SpellHaste = (1f + talents.JudgementsOfThePure * (calcOpts.JotP ? .03f : 0f)) * (1f + stats.SpellHaste) * (1f + StatConversion.GetSpellHasteFromRating(stats.HasteRating, CharacterClass.Paladin)) - 1f; // GetManaFromIntellect/GetHealthFromStamina account for the fact // that the first 20 Int/Sta only give 1 Mana/Health each. stats.Mana += StatConversion.GetManaFromIntellect(stats.Intellect, CharacterClass.Paladin) * (1f + stats.BonusManaMultiplier); stats.Health += StatConversion.GetHealthFromStamina(stats.Stamina, CharacterClass.Paladin); stats.PhysicalHit += StatConversion.GetPhysicalHitFromRating( stats.HitRating, CharacterClass.Paladin); }
public void CritsAndResists() { #region Crits, Resists { // Attack Rolltable (DW): // 27.0% miss (8.0% with 2H) // 6.5% dodge // 24.0% glancing (75% hit-dmg) // xx.x% crit // remaining = hit float targetArmor = calcOpts.BossArmor, totalArP = stats.ArmorPenetration; float arpBuffs = talents.BloodGorged * 2f / 100; physicalMitigation = 1f - StatConversion.GetArmorDamageReduction(character.Level, targetArmor, stats.ArmorPenetration, arpBuffs, stats.ArmorPenetrationRating); calcs.EnemyMitigation = 1f - physicalMitigation; calcs.EffectiveArmor = physicalMitigation; // Crit: Base .65% physCrits = .0065f; physCrits += StatConversion.GetPhysicalCritFromRating(stats.CritRating); physCrits += StatConversion.GetPhysicalCritFromAgility(stats.Agility, CharacterClass.DeathKnight); physCrits += .01f * (float)(talents.DarkConviction + talents.EbonPlaguebringer + talents.Annihilation); physCrits += stats.PhysicalCrit; calcs.CritChance = physCrits; float chanceAvoided = 0.335f; float chanceDodged = StatConversion.WHITE_DODGE_CHANCE_CAP[calcOpts.TargetLevel - 80]; calcs.DodgedMHAttacks = MH.chanceDodged; calcs.DodgedOHAttacks = OH.chanceDodged; if (character.MainHand != null) { chanceDodged = MH.chanceDodged; } if (character.OffHand != null) { if (DW) { chanceDodged += OH.chanceDodged; chanceDodged /= 2; } else if (character.MainHand == null) { chanceDodged = OH.chanceDodged; } } calcs.DodgedAttacks = chanceDodged; // Process White hits: float chanceMiss = DW ? StatConversion.WHITE_MISS_CHANCE_CAP_DW[calcOpts.TargetLevel - 80] : StatConversion.WHITE_MISS_CHANCE_CAP[calcOpts.TargetLevel - 80]; chanceMiss -= StatConversion.GetPhysicalHitFromRating(stats.HitRating); chanceMiss -= hitBonus; chanceMiss -= stats.PhysicalHit; // Cap the Miss rate at 0% chanceMiss = Math.Max(chanceMiss, 0f); calcs.MissedAttacks = chanceMiss; whiteMiss = chanceMiss; chanceAvoided = chanceDodged + chanceMiss; calcs.AvoidedAttacks = chanceDodged + chanceMiss; // Process Yellow hits chanceMiss = StatConversion.YELLOW_MISS_CHANCE_CAP[calcOpts.TargetLevel - 80]; chanceMiss -= StatConversion.GetPhysicalHitFromRating(stats.HitRating); chanceMiss -= hitBonus; chanceMiss -= stats.PhysicalHit; chanceMiss = Math.Max(chanceMiss, 0f); chanceDodged = MH.chanceDodged; missedSpecial = chanceMiss; dodgedSpecial = chanceDodged; // calcs.MissedAttacks = chanceMiss spellCrits = 0f; spellCrits += StatConversion.GetSpellCritFromRating(stats.CritRating); spellCrits += stats.SpellCrit + stats.SpellCritOnTarget; spellCrits += .01f * (float)(talents.DarkConviction + talents.EbonPlaguebringer); calcs.SpellCritChance = spellCrits; // Resists: Base 17% spellResist = .17f; spellResist -= StatConversion.GetSpellHitFromRating(stats.HitRating); spellResist -= .01f * talents.Virulence; spellResist -= stats.SpellHit; if (spellResist < 0f) { spellResist = 0f; } // Total physical misses totalMHMiss = calcs.DodgedMHAttacks + whiteMiss; totalOHMiss = calcs.DodgedOHAttacks + whiteMiss; double spellGCD = (calcOpts.rotation.presence == CalculationOptionsDPSDK.Presence.Blood ? 1.5d / ((1 + (StatConversion.GetHasteFromRating(stats.HasteRating, CharacterClass.DeathKnight))) * (1d + stats.SpellHaste)) < 1d ? 1d : 1.5d / ((1 + (StatConversion.GetHasteFromRating(stats.HasteRating, CharacterClass.DeathKnight))) * (1d + stats.SpellHaste)): 1d); double physicalGCD = (calcOpts.rotation.presence == CalculationOptionsDPSDK.Presence.Blood ? 1.5d : 1d); float minDuration = totalMeleeAbilities * (float)physicalGCD + totalSpellAbilities * (float)spellGCD; realDuration = (float)Math.Max(minDuration, calcOpts.rotation.CurRotationDuration); float dodgeMissPerRotation = (float)(totalMeleeAbilities - calcOpts.rotation.FrostStrike); chanceAvoided = chanceDodged + chanceMiss; double GChanceAvoided = (1 / (1 - chanceAvoided)) - 1; double GSpellResist = (1 / (1 - spellResist)) - 1; double ProbableGCDLossPerRotation = dodgeMissPerRotation * physicalGCD * GChanceAvoided + (calcOpts.rotation.IcyTouch + calcOpts.rotation.Pestilence) * spellGCD * GSpellResist; realDuration += (float)(((minDuration + ProbableGCDLossPerRotation) / realDuration < 1 ? (minDuration + ProbableGCDLossPerRotation) / realDuration : 1) * ProbableGCDLossPerRotation); // This last line is a bit hackish, but basically the extra GCD is more inconvenient the closer we are to having a GCD-capped rotation; once we're GCD-capped, they cost the full value. } #endregion }
public override Stats GetCharacterStats(Character character, Item additionalItem) { CalculationOptionsElemental calcOpts = character.CalculationOptions as CalculationOptionsElemental; Stats statsRace = BaseStats.GetBaseStats(character); Stats statsItems = GetItemStats(character, additionalItem); Stats statsBuffs = GetBuffsStats(character, calcOpts); Stats statsTotal = statsRace + statsItems + statsBuffs; if (statsTotal.HighestStat > 0) { if (statsTotal.Spirit > statsTotal.Intellect) { statsTotal.Spirit += (statsTotal.HighestStat * 15f / 50f); } else { statsTotal.Intellect += (statsTotal.HighestStat * 15f / 50f); } } statsTotal.Strength *= 1 + statsTotal.BonusStrengthMultiplier; statsTotal.Agility *= 1 + statsTotal.BonusAgilityMultiplier; statsTotal.Stamina *= 1 + statsTotal.BonusStaminaMultiplier; statsTotal.Intellect *= 1 + statsTotal.BonusIntellectMultiplier; statsTotal.Spirit *= 1 + statsTotal.BonusSpiritMultiplier; statsTotal.Strength = (float)Math.Floor(statsTotal.Strength); statsTotal.Agility = (float)Math.Floor(statsTotal.Agility); statsTotal.Stamina = (float)Math.Floor(statsTotal.Stamina); statsTotal.Intellect = (float)Math.Floor(statsTotal.Intellect); statsTotal.Spirit = (float)Math.Floor(statsTotal.Spirit); if (Character.ValidateArmorSpecialization(character, ItemType.Mail)) { statsTotal.Intellect *= 1.05f; } statsTotal.AttackPower += statsTotal.Strength + statsTotal.Agility; statsTotal.Mana += StatConversion.GetManaFromIntellect(statsTotal.Intellect); statsTotal.Mana *= (float)Math.Round(1f + statsTotal.BonusManaMultiplier); statsTotal.Health += StatConversion.GetHealthFromStamina(statsTotal.Stamina); statsTotal.Health *= (float)Math.Round(1f + statsTotal.BonusHealthMultiplier); statsTotal.SpellCrit += StatConversion.GetSpellCritFromRating(statsTotal.CritRating); statsTotal.SpellCrit += StatConversion.GetSpellCritFromIntellect(statsTotal.Intellect); statsTotal.SpellCrit += statsTotal.SpellCritOnTarget; statsTotal.SpellHit += StatConversion.GetSpellHitFromRating(statsTotal.HitRating + ((.33f * character.ShamanTalents.ElementalPrecision) * statsTotal.Spirit)); // Flametongue weapon assumed statsTotal.SpellPower += (float)Math.Floor(747.78 * (1f + character.ShamanTalents.ElementalWeapons * .2f)); if (character.ShamanTalents.GlyphofFlametongueWeapon) { statsTotal.SpellCrit += .02f; } // Water shield assumed statsTotal.Mp5 += 100; if (character.ShamanTalents.GlyphofWaterShield) { statsTotal.Mp5 += 30; } return(statsTotal); }
public override Dictionary <string, string> GetCharacterDisplayCalculationValues() { Dictionary <string, string> dictValues = new Dictionary <string, string>(); Stats baseStats = BaseStats.GetBaseStats(character); dictValues.Add("Health", BasicStats.Health.ToString()); dictValues.Add("Stamina", BasicStats.Stamina.ToString()); float ResilienceCap = 0.15f, ResilienceFromRating = StatConversion.GetCritReductionFromResilience(1); float Resilience = StatConversion.GetCritReductionFromResilience(BasicStats.Resilience); dictValues.Add("Resilience", string.Format("{0}*-{1}% Damage from DoT and Mana Drains\n\r-{1}% Chance to be crit\r\n-{2}% Damage from Crits.\r\n{3}", BasicStats.Resilience.ToString(), (Resilience * 100f).ToString("0.00"), (Resilience * 100f * 2.2f).ToString("0.00"), (Resilience > ResilienceCap)?(string.Format("{0} rating above cap", ((float)Math.Floor((Resilience - ResilienceCap) / ResilienceFromRating)).ToString("0"))):(string.Format("{0} rating below cap", ((float)Math.Ceiling((ResilienceCap - Resilience) / ResilienceFromRating)).ToString("0"))))); dictValues.Add("Mana", BasicStats.Mana.ToString()); dictValues.Add("Intellect", BasicStats.Intellect.ToString()); dictValues.Add("Spirit", Math.Floor(BasicStats.Spirit).ToString("0")); dictValues.Add("Spell Power", string.Format("{0}*{1} from Inner Fire", Math.Floor(BasicStats.SpellPower).ToString("0"), BasicStats.PriestInnerFire * CalculationsHealPriest.GetInnerFireSpellPowerBonus(character))); //dictValues.Add("Healing", Math.Floor(BasicStats.SpellPower * 1.88f).ToString("0")); dictValues.Add("In FSR MP5", string.Format("{0}*{1} from MP5\r\n{2} from Meditation\r\n{3} Outside FSR\r\n{4} OFSR w/MP5", (BasicStats.Mp5 + RegenInFSR).ToString("0"), BasicStats.Mp5.ToString("0"), RegenInFSR.ToString("0"), RegenOutFSR.ToString("0"), (BasicStats.Mp5 + RegenOutFSR).ToString("0"))); dictValues.Add("Spell Crit", string.Format("{0}%*{1}% from Intellect\r\n{2}% from {6} Crit rating\r\n{3}% from Focused Will\r\n{4}% Class Base\r\n{5}% from Buffs", (BasicStats.SpellCrit * 100f).ToString("0.00"), (StatConversion.GetSpellCritFromIntellect(BasicStats.Intellect) * 100f).ToString("0.00"), (StatConversion.GetSpellCritFromRating(BasicStats.CritRating) * 100f).ToString("0.00"), character.PriestTalents.FocusedWill.ToString("0"), (baseStats.SpellCrit * 100f).ToString("0.00"), (BasicStats.SpellCrit * 100f - baseStats.SpellCrit * 100f - StatConversion.GetSpellCritFromRating(BasicStats.CritRating) * 100f - StatConversion.GetSpellCritFromIntellect(BasicStats.Intellect) * 100f - character.PriestTalents.FocusedWill * 1f).ToString("0.00"), BasicStats.CritRating)); dictValues.Add("Healing Crit", string.Format("{0}%*{1} ({1}%) points in Holy Specialization\r\n{2} ({3}%) points in Renewed Hope", ((BasicStats.SpellCrit * 100f) + character.PriestTalents.HolySpecialization * 1f + character.PriestTalents.RenewedHope * 2f).ToString("0.00"), character.PriestTalents.HolySpecialization, character.PriestTalents.RenewedHope, character.PriestTalents.RenewedHope * 2)); dictValues.Add("Spell Haste", string.Format("{0}%*{1}% from {2} Haste rating\r\n{3}% ({6}) points in Enlightenment\r\n{4}% from Buffs\r\n{5}s Global Cooldown", (BasicStats.SpellHaste * 100f).ToString("0.00"), (StatConversion.GetSpellHasteFromRating(BasicStats.HasteRating) * 100f).ToString("0.00"), BasicStats.HasteRating.ToString(), (character.PriestTalents.Enlightenment * 2).ToString("0"), (((1 + BasicStats.SpellHaste) / (1 + StatConversion.GetSpellHasteFromRating(BasicStats.HasteRating)) / (1 + character.PriestTalents.Enlightenment * 0.02f) - 1) * 100f).ToString("0.00"), Math.Max(1.0f, 1.5f / (1 + BasicStats.SpellHaste)).ToString("0.00"), character.PriestTalents.Enlightenment)); dictValues.Add("Armor", string.Format("{0}*{1}% Damage Reduction.", (BasicStats.Armor + BasicStats.BonusArmor).ToString("0"), (StatConversion.GetArmorDamageReduction(80, (BasicStats.Armor + BasicStats.BonusArmor), 0f, 0f, 0f) * 100f).ToString("0.00"))); float[] Resistances = { 0, BasicStats.ArcaneResistance + BasicStats.ArcaneResistanceBuff, BasicStats.FireResistance + BasicStats.FireResistanceBuff, BasicStats.FrostResistance + BasicStats.FrostResistanceBuff, BasicStats.NatureResistance + BasicStats.NatureResistanceBuff, BasicStats.ShadowResistance + BasicStats.ShadowResistanceBuff, }; string[] ResistanceNames = { "None", "Arcane", "Fire", "Frost", "Nature", "Shadow", }; string ResistanceString = "*Resistances:"; float MaxResist = Resistances[0]; int MaxResistIndex = 0; float AvgResist = 0f; for (int x = 1; x < Resistances.Length; x++) { AvgResist += Resistances[x]; if (Resistances[x] > MaxResist) { MaxResist = Resistances[x]; MaxResistIndex = x; } ResistanceString += string.Format("\r\n{0} : {1}", ResistanceNames[x], Resistances[x]); } AvgResist /= (Resistances.Length - 1); if (AvgResist == 0) { ResistanceString = "None" + ResistanceString; } else { string ResistanceName = (MaxResist == AvgResist) ? "All" : ResistanceNames[MaxResistIndex]; ResistanceString = string.Format("{0} : {1}", ResistanceName, MaxResist.ToString("0")) + ResistanceString; ResistanceString += string.Format("\r\n\r\nResist ({0}):", ResistanceName); ResistanceString += string.Format("\r\n{0}", StatConversion.GetResistanceTableString(character.Level + 3, character.Level, MaxResist, 0)); } dictValues.Add("Resistance", ResistanceString); BaseSolver solver; if ((character.CalculationOptions as CalculationOptionsHealPriest).Role == eRole.CUSTOM) { solver = new AdvancedSolver(BasicStats, character); } else { solver = new Solver(BasicStats, character); } solver.Calculate(this); dictValues.Add("Role", string.Format("{0}*{1}", solver.Role, solver.ActionList)); dictValues.Add("Burst", string.Format("{0}", HPSBurstPoints.ToString("0"))); dictValues.Add("Sustained", string.Format("{0}", HPSSustainPoints.ToString("0"))); dictValues.Add("Renew", new Renew(BasicStats, character).ToString()); dictValues.Add("Flash Heal", new FlashHeal(BasicStats, character).ToString()); dictValues.Add("Greater Heal", new Heal(BasicStats, character).ToString()); dictValues.Add("PoH", new PrayerOfHealing(BasicStats, character).ToString()); dictValues.Add("Binding Heal", new BindingHeal(BasicStats, character).ToString()); dictValues.Add("Prayer of Mending", new PrayerOfMending(BasicStats, character).ToString()); dictValues.Add("Power Word Shield", new PowerWordShield(BasicStats, character).ToString()); dictValues.Add("Holy Nova", new HolyNova(BasicStats, character).ToString()); if (character.PriestTalents.CircleOfHealing > 0) { dictValues.Add("CoH", new CircleOfHealing(BasicStats, character).ToString()); } else { dictValues.Add("CoH", "- *No required talents"); } if (character.PriestTalents.Lightwell > 0) { dictValues.Add("Lightwell", new Lightwell(BasicStats, character).ToString()); } else { dictValues.Add("Lightwell", "- *No required talents"); } if (character.PriestTalents.Penance > 0) { dictValues.Add("Penance", new Penance(BasicStats, character).ToString()); } else { dictValues.Add("Penance", "- *No required talents"); } if (Race == CharacterRace.Draenei) { dictValues.Add("Gift of the Naaru", new GiftOfTheNaaru(BasicStats, character).ToString()); } else { dictValues.Add("Gift of the Naaru", "-"); } dictValues.Add("Divine Hymn", new DivineHymn(BasicStats, character).ToString()); dictValues.Add("Resurrection", new Resurrection(BasicStats, character).ToString()); return(dictValues); }
public void UpdateCalcs(bool firstPass) { // talents if (_calcOpts.PriorityInUse(EnhanceAbility.StormStrike)) { stormstrikeBonusCrit = .25f * _talents.Stormstrike + (_talents.GlyphofStormstrike ? .1f : 0f); } else { stormstrikeBonusCrit = 0f; } //set bonus enhance4T11 = 0f; _character.SetBonusCount.TryGetValue("Battlegear of the Raging Elements", out setCount); if (setCount >= 4) { enhance4T11 = 0.1f; } critMultiplierSpell = 1.5f * (1 + _stats.BonusSpellCritDamageMultiplier); critMultiplierMelee = 2f * (1 + _stats.BonusCritDamageMultiplier); // Melee float hitBonus = _stats.PhysicalHit + StatConversion.GetHitFromRating(_stats.HitRating) + 0.06f; //DualWieldSpecialization expertiseBonusMH = GetDPRfromExp(_stats.Expertise + StatConversion.GetExpertiseFromRating(_stats.ExpertiseRating) + BaseStats.GetRacialExpertise(_character, ItemSlot.MainHand)); expertiseBonusOH = GetDPRfromExp(_stats.Expertise + StatConversion.GetExpertiseFromRating(_stats.ExpertiseRating) + BaseStats.GetRacialExpertise(_character, ItemSlot.OffHand)); float meleeCritModifier = _stats.PhysicalCrit; float baseMeleeCrit = StatConversion.GetCritFromRating(_stats.CritRating) + StatConversion.GetCritFromAgility(_stats.Agility, _character.Class) + .01f * _talents.Acuity; chanceDodgeMH = Math.Max(0f, DodgeChanceCap - expertiseBonusMH); chanceDodgeOH = Math.Max(0f, DodgeChanceCap - expertiseBonusOH); float ParryChance = ParryChanceCap - expertiseBonusMH; //chanceParryMH = (float)Math.Max(0f, _bossOpts.InBack ? ParryChance * PecentageInfrontBoss : ParryChance); ParryChance = ParryChanceCap - expertiseBonusOH; //chanceParryOH = (float)Math.Max(0f, _bossOpts.InBack ? ParryChance * PecentageInfrontBoss : ParryChance); chanceWhiteMissMH = Math.Max(0f, WhiteHitCap - hitBonus) + chanceDodgeMH + chanceParryMH; chanceWhiteMissOH = Math.Max(0f, WhiteHitCap - hitBonus) + chanceDodgeOH + chanceParryOH; chanceYellowMissMH = Math.Max(0f, YellowHitCap - hitBonus) + chanceDodgeMH + chanceParryMH; // base miss 8% now chanceYellowMissOH = Math.Max(0f, YellowHitCap - hitBonus) + chanceDodgeOH + chanceParryOH; // base miss 8% now // SetCritValues((1 + _stats.BonusCritChance) * (baseMeleeCrit + meleeCritModifier) + .00005f); //fudge factor for rounding SetCritValues(baseMeleeCrit + meleeCritModifier + .00005f); //fudge factor for rounding // set two export values so that ED crit isn't included exportMeleeCritMH = chanceWhiteCritMH + whiteCritDepression; exportMeleeCritOH = chanceWhiteCritOH + whiteCritDepression; // Spells ftBonusCrit = 0f; if (_calcOpts.MainhandImbue == "Flametongue") { ftBonusCrit += _talents.GlyphofFlametongueWeapon ? .02f : 0f; } if (_calcOpts.OffhandImbue == "Flametongue") { ftBonusCrit += _talents.GlyphofFlametongueWeapon ? .02f : 0f; } if (baseStats == null) { baseStats = BaseStats.GetBaseStats(_character); } elemPrecMod = _talents.ElementalPrecision > 0 ? (_stats.Spirit - baseStats.Spirit) * (_talents.ElementalPrecision / 3f) : 0f; float hitBonusSpell = _stats.SpellHit + StatConversion.GetSpellHitFromRating(_stats.HitRating + elemPrecMod); chanceSpellMiss = Math.Max(0f, SpellMissRate - hitBonusSpell); overSpellHitCap = Math.Max(0f, hitBonusSpell - SpellMissRate); float spellCritModifier = _stats.SpellCrit + _stats.SpellCritOnTarget + ftBonusCrit; float baseSpellCrit = StatConversion.GetSpellCritFromRating(_stats.CritRating) + StatConversion.GetSpellCritFromIntellect(_stats.Intellect) + .01f * _talents.Acuity; //chanceSpellCrit = Math.Min(0.75f, (1 + _stats.BonusCritChance) * (baseSpellCrit + spellCritModifier) + .00005f); //fudge factor for rounding chanceSpellCrit = Math.Min(0.75f, baseSpellCrit + spellCritModifier + .00005f); //fudge factor for rounding float hasteBonus = StatConversion.GetHasteFromRating(_stats.HasteRating, _character.Class); unhastedMHSpeed = _character.MainHand == null ? 3.0f : _character.MainHand.Item.Speed; unhastedOHSpeed = _character.OffHand == null ? 3.0f : _character.OffHand.Item.Speed; float baseHastedMHSpeed = unhastedMHSpeed / (1f + hasteBonus) / (1f + _stats.PhysicalHaste); float baseHastedOHSpeed = unhastedOHSpeed / (1f + hasteBonus) / (1f + _stats.PhysicalHaste); float chanceToProcWFPerHit = .2f + (_character.ShamanTalents.GlyphofWindfuryWeapon ? .02f : 0f); if (_bossOpts.MultiTargs && _bossOpts.Targets != null && _bossOpts.Targets.Count > 0) { foreach (TargetGroup tg in _bossOpts.Targets) { if (tg.Frequency <= 0 || tg.Chance <= 0) { continue; //bad one, skip it } float upTime = (tg.Frequency / fightLength * (tg.Duration / 1000f) * tg.Chance) / fightLength; multiTargetMultiplier += (Math.Max(10, tg.NumTargs - (tg.NearBoss ? 0 : 1))) * upTime; } } //The Swing Loop //This is where we figure out feedback systems -- WF, MW, ED, Flurry, etc. //-------------- flurryUptime = 1f; uWUptime = 0f; //uFUptime = 0f; edUptime = 0f; float stormstrikeSpeed = firstPass ? (_talents.Stormstrike == 1 ? 8f : 0f) : AbilityCooldown(EnhanceAbility.StormStrike); float shockSpeed = firstPass ? BaseShockSpeed : AbilityCooldown(EnhanceAbility.EarthShock); float lavaLashSpeed = firstPass ? 10f : AbilityCooldown(EnhanceAbility.LavaLash); float fireNovaSpeed = firstPass ? BaseFireNovaSpeed : AbilityCooldown(EnhanceAbility.FireNova); if (_calcOpts.PriorityInUse(EnhanceAbility.MagmaTotem)) { fireTotemUptime = firstPass ? 1.0f : 60f / AbilityCooldown(EnhanceAbility.MagmaTotem); } else if (_calcOpts.PriorityInUse(EnhanceAbility.SearingTotem)) { fireTotemUptime = firstPass ? 1.0f : 60f / AbilityCooldown(EnhanceAbility.SearingTotem); } else if (_calcOpts.PriorityInUse(EnhanceAbility.RefreshTotems)) // if no Searing or Magma totem use refresh of Flametongue totem. { fireTotemUptime = firstPass ? 1.0f : 300f / AbilityCooldown(EnhanceAbility.RefreshTotems); } float mwPPM = (10f / 3f) * _talents.MaelstromWeapon; float flurryHasteBonus = .10f * _talents.Flurry; float uWHasteBonus = .4f + .1f * _talents.ElementalWeapons; float edCritBonus = .03f * _talents.ElementalDevastation; float staticShockChance = .15f * _character.ShamanTalents.StaticShock; hitsPerSMHSS = 0f; hitsPerSOHSS = 0f; hitsPerSOH = 0f; hitsPerSMH = 0f; hitsPerSWF = 0f; if (_talents.Stormstrike == 1) { hitsPerSMHSS = (1f - chanceYellowMissMH) / stormstrikeSpeed; hitsPerSOHSS = (1f - 2 * chanceYellowMissOH) / stormstrikeSpeed; //OH only swings if MH connect } hitsPerSLL = lavaLashSpeed == 0 ? 0f : (1f - chanceYellowMissOH) / lavaLashSpeed; float swingsPerSMHMelee = 0f; float swingsPerSOHMelee = 0f; float wfProcsPerSecond = 0f; float mwProcsPerSecond = 0f; secondsToFiveStack = 10f; float averageMeleeCritChance = (chanceWhiteCritMH + chanceWhiteCritOH + chanceYellowCritMH + chanceYellowCritOH) / 4f; float averageMeleeHitChance = ((1f - chanceWhiteMissMH - chanceDodgeMH - chanceParryMH) + (1f - chanceWhiteMissOH - chanceDodgeOH - chanceParryOH)) / 2f; float averageMeleeMissChance = (chanceWhiteMissMH + chanceWhiteMissOH) / 2f; float whiteHitsPerSMH = 0f; float whiteHitsPerSOH = 0f; float moteHitsPerS = 0f; float yellowHitsPerSMH = 0f; float yellowHitsPerSOH = 0f; float flameTongueHitsPerSecond = 0f; for (int i = 0; i < 5; i++) { // float bonusHaste = (1f + (flurryUptime * flurryHasteBonus)); float bonusHaste = 1 / (1 - flurryUptime + flurryUptime / (1 + flurryHasteBonus)) / (1 - uWUptime + uWUptime / (1 + uWHasteBonus)); // use time based not proc based flurryUptime hastedMHSpeed = baseHastedMHSpeed / bonusHaste; hastedOHSpeed = baseHastedOHSpeed / bonusHaste; swingsPerSMHMelee = 1f / hastedMHSpeed; swingsPerSOHMelee = (hastedOHSpeed == 0f) ? 0f : 1f / hastedOHSpeed; whiteHitsPerSMH = ChanceWhiteHitMH * swingsPerSMHMelee; whiteHitsPerSOH = ChanceWhiteHitOH * swingsPerSOHMelee; moteHitsPerS = _stats.MoteOfAnger * 2 * AverageWhiteHitChance; // Windfury model if (_calcOpts.MainhandImbue == "Windfury") { float hitsThatProcWFPerS = whiteHitsPerSMH + hitsPerSMHSS; if (unhastedOHSpeed != 0f) { hitsThatProcWFPerS += moteHitsPerS / 2; // half the hits will be OH and thus won't proc WF } else { hitsThatProcWFPerS += moteHitsPerS; // if no offhand then all motes will be MH weapon by definition } float maxExpectedWFPerFight = hitsThatProcWFPerS * chanceToProcWFPerHit * fightLength; float ineligibleSeconds = maxExpectedWFPerFight * (3.25f - hastedMHSpeed); float expectedWFPerFight = hitsThatProcWFPerS * chanceToProcWFPerHit * (fightLength - ineligibleSeconds); wfProcsPerSecond = expectedWFPerFight / fightLength; hitsPerSWF = 3f * wfProcsPerSecond * (1f - chanceYellowMissMH); } yellowHitsPerSMH = hitsPerSWF + hitsPerSMHSS; yellowHitsPerSOH = hitsPerSOHSS + hitsPerSLL; //Due to attack table, a white swing has the same chance to crit as a yellow hit // Old Flurry calc changed 10 Nov 2009 // couldCritSwingsPerSecond = whiteHitsPerSMH + whiteHitsPerSOH + yellowHitsPerSMH + yellowHitsPerSOH; // float swingsThatConsumeFlurryPerSecond = swingsPerSMHMelee + swingsPerSOHMelee; // flurryUptime = 1f - (float)Math.Pow(1 - averageMeleeCritChance, (3 / swingsThatConsumeFlurryPerSecond) * couldCritSwingsPerSecond); // old formulae flurryUptime = CalculateFlurryUptime(averageMeleeCritChance, averageMeleeHitChance, averageMeleeMissChance); //uWUptime = (float)Math.Max(12f / 15f, ??); //FIXME!!!! uWUptime = 7.5f / 15f; //Temp Uptime until above line is fixed // Maelstrom Weapon time to 5 stacks calc if (unhastedOHSpeed != 0f) { hitsPerSMH = whiteHitsPerSMH + yellowHitsPerSMH + moteHitsPerS / 2; hitsPerSOH = whiteHitsPerSOH + yellowHitsPerSOH + moteHitsPerS / 2; mwProcsPerSecond = (mwPPM / (60f / unhastedMHSpeed)) * hitsPerSMH + (mwPPM / (60f / unhastedOHSpeed)) * hitsPerSOH; } else { hitsPerSMH = whiteHitsPerSMH + yellowHitsPerSMH + moteHitsPerS; hitsPerSOH = 0f; mwProcsPerSecond = (mwPPM / (60f / unhastedMHSpeed)) * hitsPerSMH; } secondsToFiveStack = 5f / mwProcsPerSecond; // Elemental Devastation Uptime calc staticShocksPerSecond = (hitsPerSLL + hitsPerSMHSS + hitsPerSOHSS) * staticShockChance; flameTongueHitsPerSecond = (_calcOpts.MainhandImbue == "Flametongue" ? HitsPerSMH : 0f) + ((_calcOpts.OffhandImbue == "Flametongue") ? HitsPerSOH : 0f); spellAttacksPerSec = (1f / secondsToFiveStack + 1f / shockSpeed + 1f / fireNovaSpeed + staticShocksPerSecond) // + flameTongueHitsPerSecond) * (1f - chanceSpellMiss); float couldCritSpellsPerS = spellAttacksPerSec; edUptime = 1f - (float)Math.Pow(1 - chanceSpellCrit, 10 * couldCritSpellsPerS); averageMeleeCritChance = (chanceWhiteCritMH + chanceWhiteCritOH + chanceYellowCritMH + chanceYellowCritOH) / 4f + edUptime * edCritBonus; } float yellowAttacksPerSecond = hitsPerSWF + hitsPerSMHSS; if (unhastedMHSpeed != 0) { yellowAttacksPerSecond += hitsPerSOHSS; } // set output variables edBonusCrit = edUptime * edCritBonus; //SetCritValues((1 + _stats.BonusCritChance) * (baseMeleeCrit + meleeCritModifier) + edBonusCrit + .00005f); //fudge factor for rounding SetCritValues(baseMeleeCrit + meleeCritModifier + edBonusCrit + .00005f); //fudge factor for rounding meleeAttacksPerSec = hitsPerSMH + hitsPerSOH; meleeCritsPerSec = (whiteHitsPerSMH * chanceWhiteCritMH) + (whiteHitsPerSOH * chanceWhiteCritOH) + (yellowHitsPerSMH * chanceYellowCritMH) + (yellowHitsPerSOH * chanceYellowCritOH) + (_stats.MoteOfAnger * 2 * AverageWhiteCritChance); spellCritsPerSec = spellAttacksPerSec * ChanceSpellCrit; spellCastsPerSec = spellAttacksPerSec; spellMissesPerSec = spellAttacksPerSec * chanceSpellMiss; chanceMeleeHit = meleeAttacksPerSec / (swingsPerSMHMelee + swingsPerSOHMelee + 2f * wfProcsPerSecond + .25f + 1f / 6f); maxMana = _stats.Mana; float spellhaste = _stats.SpellHaste + StatConversion.GetSpellHasteFromRating(_stats.HasteRating); averageFSDotTime = _talents.GlyphofFlameShock ? 27f : 18f; averageFSTickTime = 3f / (1f + spellhaste); }
/// <summary> /// Unlike the melee combat system, spell crit makes absolutely no difference to hit chance. /// All spells, regardless of whether they are treated as binary or not, roll hit and crit separately. /// Conceptually, the game rolls for your hit chance first, and if the spell hits you have a separate roll for whether it crits. /// Overall chance to crit over all spells cast is thus affected by hit rate. /// To calculate overall crit rate, multiplying the two chances together: /// Crit rate over all spell casts = crit/// hit /// /// For example, a caster with no spell hit rating gear or talents, /// against a mob 3 levels higher (83% hit chance), and 30% crit rating from gear and talents: /// crit rate over all spell casts = 30%/// 83% = 24.9% /// /// A level 80 player against a level 83 boss needs +26.232*k hit rating, to achieve +k% chance to hit with spells. /// In addition, direct damage spells suffer from partial resistance, but again, that has no effect on whether a spell hits or not. /// </summary> /// <param name="character"></param> /// <param name="stats"></param> /// <returns></returns> public static float SpellCritChance(Character character, Stats stats, int targetLevel) { float spellCrit = Math.Min(1f, StatConversion.GetSpellCritFromRating(stats.CritRating, CharacterClass.Paladin) + StatConversion.GetSpellCritFromIntellect(stats.Intellect, CharacterClass.Paladin) + stats.SpellCrit + stats.SpellCritOnTarget); return(spellCrit * SpellHitChance(character, stats, targetLevel)); }
public override Dictionary <string, string> GetCharacterDisplayCalculationValues() { Dictionary <string, string> dictValues = new Dictionary <string, string>(); Buff activeBuff; string s; Stats baseStats = BaseStats.GetBaseStats(Character); #region General dictValues["Health"] = BasicStats.Health.ToString("0"); dictValues["Mana"] = BasicStats.Mana.ToString("0"); dictValues["Item Level"] = String.Format("{0}*Lowest: {1}\nHighest: {2}", Character.AvgWornItemLevel.ToString("0"), Character.MinWornItemLevel.ToString("0"), Character.MaxWornItemLevel.ToString("0")); dictValues["Speed"] = String.Format("{0}%*{0}% Run speed", ((1f + BasicStats.MovementSpeed) * 100f).ToString("0")); #endregion #region Attributes dictValues["Strength"] = BasicStats.Strength.ToString(); dictValues["Agility"] = BasicStats.Agility.ToString(); dictValues["Stamina"] = BasicStats.Stamina.ToString(); dictValues["Intellect"] = BasicStats.Intellect.ToString(); dictValues["Spirit"] = BasicStats.Spirit.ToString(); #endregion #region Spell #region Spell Power s = String.Empty; float intPower = BasicStats.Intellect - 10; if (BasicStats.InnerFire) { s += String.Format("\n{0} from Inner Fire", PriestInformation.GetInnerFireSpellPowerBonus(Character)); } activeBuff = GetActiveBuffsByGroup("Spell Power"); if (activeBuff != null) { s += makeActiveBuffTextPercent(activeBuff, activeBuff.Stats.BonusSpellPowerMultiplier); } dictValues["Spell Power"] = String.Format("{0}*{1} from {2} Intellect{3}", BasicStats.SpellPower.ToString("0"), intPower.ToString("0"), BasicStats.Intellect.ToString("0"), s ); #endregion #region Haste s = String.Empty; if (Character.PriestTalents.Darkness > 0) { s += String.Format("\n{0}% from {1} points in Darkness", Character.PriestTalents.Darkness, Character.PriestTalents.Darkness); } activeBuff = GetActiveBuffsByGroup("Spell Haste"); if (activeBuff != null) { s += makeActiveBuffTextPercent(activeBuff, activeBuff.Stats.SpellHaste); } activeBuff = GetActiveBuffsByGroup("Dark Intent"); if (activeBuff != null) { s += makeActiveBuffTextPercent(activeBuff, activeBuff.Stats.SpellHaste); } dictValues["Haste"] = String.Format("{0}%*{1}% from {2} Haste Rating{3}", (BasicStats.SpellHaste * 100f).ToString("0.00"), (StatConversion.GetSpellHasteFromRating(BasicStats.HasteRating) * 100f).ToString("0.00"), BasicStats.HasteRating.ToString("0"), s ); #endregion dictValues["Hit"] = (BasicStats.SpellHit * 100f).ToString("0.00"); dictValues["Penetration"] = BasicStats.SpellPenetration.ToString("0"); #region Mana Regen float manaRegen = StatConversion.GetSpiritRegenSec(BasicStats.Spirit, BasicStats.Intellect) * 5f; s = String.Format("\n{0} Mana per 5 sec from Base Mana Regeneration", (baseStats.Mana * 0.05f).ToString("0")); activeBuff = GetActiveBuffsByGroup("Mana Regeneration"); if (activeBuff != null) { s += makeActiveBuffText(activeBuff, activeBuff.Stats.Mp5); } dictValues["Mana Regen"] = String.Format("{0}*{1} from Spirit based regen{2}", (manaRegen + BasicStats.Mp5).ToString("0"), manaRegen.ToString("0"), s ); dictValues["Combat Regen"] = String.Format("{0}*{1} from Spirit based regen{2}", (manaRegen * BasicStats.SpellCombatManaRegeneration + BasicStats.Mp5).ToString("0"), (manaRegen * BasicStats.SpellCombatManaRegeneration).ToString("0"), s ); #endregion #region Crit s = String.Empty; activeBuff = GetActiveBuffsByGroup("Critical Strike Chance"); if (activeBuff != null) { s += makeActiveBuffTextPercent(activeBuff, activeBuff.Stats.SpellCrit); } activeBuff = GetActiveBuffsByGroup("Focus Magic, Spell Critical Strike Chance"); if (activeBuff != null) { s += makeActiveBuffTextPercent(activeBuff, activeBuff.Stats.SpellCrit); } dictValues["Crit Chance"] = String.Format("{0}%*{1}% from {2} Crit Rating\n{3}% from {4} Intellect\n{5}% from Priest base{6}", (BasicStats.SpellCrit * 100f).ToString("0.00"), (StatConversion.GetSpellCritFromRating(BasicStats.CritRating) * 100f).ToString("0.00"), BasicStats.CritRating.ToString("0"), (StatConversion.GetSpellCritFromIntellect(BasicStats.Intellect) * 100f).ToString("0.00"), BasicStats.Intellect.ToString("0"), (baseStats.SpellCrit * 100f).ToString("0.00"), s ); #endregion #region Mastery s = string.Empty; float masteryBase = 0, masteryBonus = 0; if (BasicStats.PriestSpec == ePriestSpec.Spec_Disc) { masteryBase = PriestInformation.DisciplineMasteryBase; masteryBonus = PriestInformation.DisciplineMasteryEffect * 100f; s += String.Format("\n\nEach point of mastery increases the potency of Absorbs by an additional {0}%.", masteryBonus.ToString("0.00")); } else if (BasicStats.PriestSpec == ePriestSpec.Spec_Holy) { masteryBase = PriestInformation.HolyMasteryBase; masteryBonus = PriestInformation.HolyMasteryEffect * 100f; s += String.Format("\n\nEach point of mastery provides an additional {0}% healing over 6 sec.", masteryBonus.ToString("0.00")); } dictValues["Mastery"] = String.Format("{0}%*{1}% from {2} Mastery Rating\n{3}% from {4} Base Mastery{5}", ((StatConversion.GetMasteryFromRating(BasicStats.MasteryRating) + masteryBase) * masteryBonus).ToString("0.00"), (StatConversion.GetMasteryFromRating(BasicStats.MasteryRating) * masteryBonus).ToString("0.00"), BasicStats.MasteryRating.ToString("0"), (masteryBase * masteryBonus).ToString("0.00"), masteryBase.ToString("0"), s ); #endregion #endregion #region Defense dictValues["Armor"] = String.Format("{0}*{1}% physical damage reduction from same level target", BasicStats.Armor.ToString("0"), (StatConversion.GetDamageReductionFromArmor(Character.Level, BasicStats.Armor) * 100f).ToString("0.00")); dictValues["Dodge"] = String.Format("{0}%", (BasicStats.Dodge * 100f).ToString("0.00")); dictValues["Resilience"] = String.Format("{0}*{1}% damage reduction on attacks from other players\n{2}% damage reduction from spells", BasicStats.Resilience.ToString("0"), (StatConversion.GetDamageReductionFromResilience(BasicStats.Resilience) * 100f).ToString("0.00"), (Character.PriestTalents.InnerSanctum * 2f).ToString("0")); #endregion #region Resistance string resistTxt = "{0}*PvP\n{1}\n\nBoss\n{2}"; string[] resistList = { "Arcane", "Fire", "Frost", "Nature", "Shadow" }; float[] resistances = { BasicStats.ArcaneResistance + BasicStats.ArcaneResistanceBuff, BasicStats.FireResistance + BasicStats.FireResistanceBuff, BasicStats.FrostResistance + BasicStats.FrostResistanceBuff, BasicStats.NatureResistance + BasicStats.NatureResistanceBuff, BasicStats.ShadowResistance + BasicStats.ShadowResistanceBuff }; for (int x = 0; x < resistList.Length; x++) { dictValues[resistList[x]] = String.Format(resistTxt, resistances[x].ToString("0"), StatConversion.GetResistanceTableString(Character.Level, Character.Level, resistances[x], 0f), StatConversion.GetResistanceTableString(Character.Level + 3, Character.Level, resistances[x], 0f) ); } #endregion #region Model CalculationOptionsHealPriest calcOpts = Character.CalculationOptions as CalculationOptionsHealPriest; if (calcOpts != null) { PriestSolver solver = PriestModels.GetModel(this, calcOpts, true); solver.Solve(); List <string> reqs = solver.MeetsRequirements(); string disp; if (reqs.Count > 0) { disp = String.Format("{0}\n\n{1}", solver.Name, String.Join("\n", reqs)); } else { disp = solver.Name; } dictValues["Role"] = disp; dictValues["Burst Goal"] = this.BurstGoal.ToString("0"); dictValues["Sust. Goal"] = this.SustainGoal.ToString("0"); dictValues["Mana Goal"] = this.ManaGoal.ToString("0"); dictValues["Burst"] = this.BurstPoints.ToString("0"); dictValues["Sustained"] = this.SustainPoints.ToString("0"); dictValues["Mana "] = this.ManaPoints.ToString("0"); } #endregion #region Holy Spells SpellHeal spellHeal = new SpellHeal(Character, BasicStats); dictValues["Heal"] = String.Format("{0}*{1}", spellHeal.HPS().ToString("0"), spellHeal.ToString()); SpellGreaterHeal spellGreaterHeal = new SpellGreaterHeal(Character, BasicStats); dictValues["Greater Heal"] = String.Format("{0}*{1}", spellGreaterHeal.HPS().ToString("0"), spellGreaterHeal.ToString()); SpellFlashHeal spellFlashHeal = new SpellFlashHeal(Character, BasicStats); dictValues["Flash Heal"] = String.Format("{0}*{1}", spellFlashHeal.HPS().ToString("0"), spellFlashHeal.ToString()); SpellBindingHeal spellBindingHeal = new SpellBindingHeal(Character, BasicStats); dictValues["Binding Heal"] = String.Format("{0}*{1}", spellBindingHeal.HPS().ToString("0"), spellBindingHeal.ToString()); SpellRenew spellRenew = new SpellRenew(Character, BasicStats); dictValues["Renew"] = String.Format("{0}*{1}", spellRenew.HPS().ToString("0"), spellRenew.ToString()); if (Character.PriestTalents.Lightwell > 0) { SpellLightwell spellLW = new SpellLightwell(Character, BasicStats); dictValues["Lightwell"] = String.Format("{0}*{1}", spellLW.HPS().ToString("0"), spellLW.ToString()); } else { dictValues["Lightwell"] = "N/A*You do not have the talent required."; } SpellPrayerOfHealing spellProH = new SpellPrayerOfHealing(Character, BasicStats); dictValues["ProH"] = String.Format("{0}*{1}", spellProH.HPS().ToString("0"), spellProH.ToString()); SpellHolyNova spellHolyNova = new SpellHolyNova(Character, BasicStats); dictValues["Holy Nova"] = String.Format("NYI*{1}", spellHolyNova.HPS().ToString("0"), spellHolyNova.ToString()); if (Character.PriestTalents.CircleOfHealing > 0) { SpellCircleOfHealing spellCoH = new SpellCircleOfHealing(Character, BasicStats); dictValues["CoH"] = String.Format("{0}*{1}", spellCoH.HPS().ToString("0"), spellCoH.ToString()); } else { dictValues["CoH"] = "N/A*You do not have the talent required."; } if (BasicStats.PriestSpec == ePriestSpec.Spec_Disc) { SpellPenance spellPenance = new SpellPenance(Character, BasicStats); dictValues["Penance"] = String.Format("{0}*{1}", spellPenance.HPS().ToString("0"), spellPenance.ToString()); } else { dictValues["Penance"] = "N/A*You do not have the correct Talent specialization."; } if (Character.PriestTalents.Revelations > 0) { SpellSerenity spellSerenity = new SpellSerenity(Character, BasicStats); dictValues["HW Serenity"] = String.Format("{0}*{1}", spellSerenity.HPS().ToString("0"), spellSerenity.ToString()); SpellSanctuary spellSanctuary = new SpellSanctuary(Character, BasicStats); dictValues["HW Sanctuary"] = String.Format("{0}*{1}", spellSanctuary.HPS().ToString("0"), spellSanctuary.ToString()); } else { dictValues["HW Serenity"] = "N/A*You do not have the talent required."; dictValues["HW Sanctuary"] = "N/A*You do not have the talent required."; } SpellPrayerOfMending spellProM = new SpellPrayerOfMending(Character, BasicStats, 1); dictValues["ProM"] = String.Format("{0}*{1}", spellProM.HPS().ToString("0"), spellProM.ToString()); spellProM = new SpellPrayerOfMending(Character, BasicStats); dictValues["ProM 5 Hits"] = String.Format("{0}*{1}", spellProM.HPS().ToString("0"), spellProM.ToString()); SpellPowerWordShield spellPWS = new SpellPowerWordShield(Character, BasicStats); dictValues["PWS"] = String.Format("{0}*{1}", spellPWS.HPS().ToString("0"), spellPWS.ToString()); SpellDivineHymn spellDivineHymn = new SpellDivineHymn(Character, BasicStats); dictValues["Divine Hymn"] = String.Format("{0}*{1}", spellDivineHymn.HPS().ToString("0"), spellDivineHymn.ToString()); if (Character.Race == CharacterRace.Draenei) { SpellGiftOfTheNaaru spellGoat = new SpellGiftOfTheNaaru(Character, BasicStats); dictValues["Gift of the Naaru"] = String.Format("{0}*{1}", spellGoat.HPS().ToString("0"), spellGoat.ToString()); } else { dictValues["Gift of the Naaru"] = "N/A*You are not a spacegoat!"; } SpellResurrection spellResurrection = new SpellResurrection(Character, BasicStats); dictValues["Resurrection"] = String.Format("{0}*{1}", spellResurrection.CastTime.ToString("0.00"), spellResurrection.ToString()); #endregion #region Shadow Spells #endregion return(dictValues); }
public override Dictionary <string, string> GetCharacterDisplayCalculationValues() { Dictionary <string, string> retVal = new Dictionary <string, string>(); retVal.Add("Health", baseStats.Health.ToString()); retVal.Add("Mana", baseStats.Mana.ToString()); retVal.Add("Armor", baseStats.Armor.ToString()); retVal.Add("Agility", baseStats.Agility.ToString()); retVal.Add("Stamina", baseStats.Stamina.ToString()); retVal.Add("Intellect", baseStats.Intellect.ToString()); retVal.Add("Spirit", baseStats.Spirit.ToString()); retVal.Add("Spell Power", SpellPower.ToString()); retVal.Add("Spell Hit", String.Format("{0:F}%*{1} Hit Rating, {2:F}% Hit From Gear, {3} Rating To Cap", 100 * SpellHit, baseStats.HitRating, 100 * StatConversion.GetSpellHitFromRating(baseStats.HitRating), StatConversion.GetRatingFromHit(Math.Max(0, 0.17f - SpellHit)))); retVal.Add("Spell Crit", String.Format("{0:F}%*{1} Crit Rating, {2:F}% Crit From Gear, {3:F}% Crit From Intellect", 100 * SpellCrit, baseStats.CritRating, 100 * StatConversion.GetSpellCritFromRating(baseStats.CritRating), 100 * StatConversion.GetSpellCritFromIntellect(baseStats.Intellect))); retVal.Add("Spell Haste", String.Format("{0:F}%*{1} Haste Rating, {2:F}% Haste From Gear", 100 * SpellHaste, baseStats.HasteRating, 100 * StatConversion.GetSpellHasteFromRating(baseStats.HasteRating))); retVal.Add("MP5 Not Casting", String.Format("{0:F0}", ManaRegen * 5.0f)); retVal.Add("MP5 While Casting", String.Format("{0:F0}", ManaRegen5SR * 5.0f)); retVal.Add("Total Score", String.Format("{0:F2}", SubPoints[0] + SubPoints[1])); retVal.Add("Selected Rotation", SelectedRotation.Name); retVal.Add("Selected DPS", String.Format("{0:F2}", SelectedRotation.RotationData.DPS)); retVal.Add("Selected Time To OOM", String.Format(SelectedRotation.RotationData.TimeToOOM > new TimeSpan(0, 0, 0) ? "{0} m {1} s" : "Not during fight", SelectedRotation.RotationData.TimeToOOM.Minutes, SelectedRotation.RotationData.TimeToOOM.Seconds)); retVal.Add("Selected Cycle Length", String.Format("{0:F1} s", SelectedRotation.Duration)); StringBuilder sb = new StringBuilder("*"); float rotationDamage = SelectedRotation.RotationData.DPS * SelectedRotation.Duration; sb.AppendLine(String.Format("{0}: {1:F2}%, {2:F2} damage, {3:F0} count", "Starfire", 100 * SelectedRotation.StarfireAvgHit * SelectedRotation.StarfireCount / rotationDamage, SelectedRotation.StarfireAvgHit * SelectedRotation.StarfireCount, SelectedRotation.StarfireCount)); sb.AppendLine(String.Format("{0}: {1:F2}%, {2:F2} damage, {3:F0} count", "Moonfire", 100 * (SelectedRotation.MoonfireAvgHit) * SelectedRotation.MoonfireCasts / rotationDamage, (SelectedRotation.MoonfireAvgHit) * SelectedRotation.MoonfireCasts, SelectedRotation.MoonfireCasts)); sb.AppendLine(String.Format("{0}: {1:F2}%, {2:F2} damage, {3:F0} count", "Insect Swarm", 100 * SelectedRotation.InsectSwarmAvgHit * SelectedRotation.InsectSwarmCasts / rotationDamage, SelectedRotation.InsectSwarmAvgHit * (SelectedRotation.InsectSwarmCasts), SelectedRotation.InsectSwarmCasts)); sb.AppendLine(String.Format("{0}: {1:F2}%, {2:F2} damage, {3:F0} count", "Wrath", 100 * SelectedRotation.WrathAvgHit * SelectedRotation.WrathCount / rotationDamage, SelectedRotation.WrathAvgHit * SelectedRotation.WrathCount, SelectedRotation.WrathCount)); retVal.Add("Selected Spell Breakdown", sb.ToString()); retVal.Add("Burst Rotation", BurstDPSRotation.Name); retVal.Add("Burst DPS", String.Format("{0:F2}", BurstDPSRotation.RotationData.BurstDPS)); retVal.Add("Burst Time To OOM", String.Format(BurstDPSRotation.RotationData.TimeToOOM > new TimeSpan(0, 0, 0) ? "{0} m {1} s" : "Not during fight", BurstDPSRotation.RotationData.TimeToOOM.Minutes, BurstDPSRotation.RotationData.TimeToOOM.Seconds)); retVal.Add("Burst Cycle Length", String.Format("{0:F1} s", BurstDPSRotation.Duration)); sb = new StringBuilder("*"); rotationDamage = BurstDPSRotation.RotationData.DPS * BurstDPSRotation.Duration; sb.AppendLine(String.Format("{0}: {1:F2}%, {2:F2} damage, {3:F0} count", "Starfire", 100 * BurstDPSRotation.StarfireAvgHit * BurstDPSRotation.StarfireCount / rotationDamage, BurstDPSRotation.StarfireAvgHit * BurstDPSRotation.StarfireCount, BurstDPSRotation.StarfireCount)); sb.AppendLine(String.Format("{0}: {1:F2}%, {2:F2} damage, {3:F0} count", "Moonfire", 100 * BurstDPSRotation.MoonfireAvgHit * SelectedRotation.MoonfireCasts / rotationDamage, (BurstDPSRotation.MoonfireAvgHit) * BurstDPSRotation.MoonfireCasts, BurstDPSRotation.MoonfireCasts)); sb.AppendLine(String.Format("{0}: {1:F2}%, {2:F2} damage, {3:F0} count", "Insect Swarm", 100 * BurstDPSRotation.InsectSwarmAvgHit * BurstDPSRotation.InsectSwarmCasts / rotationDamage, BurstDPSRotation.InsectSwarmAvgHit * BurstDPSRotation.InsectSwarmCasts, BurstDPSRotation.InsectSwarmCasts)); sb.AppendLine(String.Format("{0}: {1:F2}%, {2:F2} damage, {3:F0} count", "Wrath", 100 * BurstDPSRotation.WrathAvgHit * BurstDPSRotation.WrathCount / rotationDamage, BurstDPSRotation.WrathAvgHit * BurstDPSRotation.WrathCount, BurstDPSRotation.WrathCount)); retVal.Add("Burst Spell Breakdown", sb.ToString()); retVal.Add("Starfire", String.Format("{0:F2} dps*{1:F2} s avg\n{2:F2} s w/NG\n{3:F2}% max non-Eclipse\n{4:F2}% max Eclipse\n{5:F2} avg hit\n{6:F0} avg mana", SelectedRotation.StarfireAvgHit / (SelectedRotation.StarfireAvgCast > 0 ? SelectedRotation.StarfireAvgCast : 1f), SelectedRotation.StarfireAvgCast, SelectedRotation.StarfireNGCastTime, 100 * SelectedRotation.StarfireNonEclipseCrit, 100 * SelectedRotation.StarfireEclipseCrit, SelectedRotation.StarfireAvgHit, SelectedRotation.StarfireManaCost)); retVal.Add("Wrath", String.Format("{0:F2} dps*{1:F2} s avg\n{2:F2} s w/NG\n{3:F2} avg hit\n{4:F0} avg mana", SelectedRotation.WrathAvgHit / (SelectedRotation.WrathAvgCast > 0 ? SelectedRotation.WrathAvgCast : 1f), SelectedRotation.WrathAvgCast, SelectedRotation.WrathNGCastTime, SelectedRotation.WrathAvgHit, SelectedRotation.WrathManaCost)); retVal.Add("Moonfire", String.Format("{0:F2} dps*{1:F2} s avg\n{2:F2} avg hit\n{3:F0} avg mana", SelectedRotation.MoonfireAvgHit / (SelectedRotation.MoonfireDuration > 0 ? SelectedRotation.MoonfireDuration : 1f), SelectedRotation.MoonfireCastTime, (SelectedRotation.MoonfireAvgHit), SelectedRotation.MoonfireManaCost)); retVal.Add("Insect Swarm", String.Format("{0:F2} dps*{1:F2} s avg\n{2:F2} avg hit\n{3:F0} avg mana", SelectedRotation.InsectSwarmAvgHit / (SelectedRotation.InsectSwarmDuration > 0 ? SelectedRotation.InsectSwarmDuration : 1f), SelectedRotation.InsectSwarmCastTime, SelectedRotation.InsectSwarmAvgHit, SelectedRotation.InsectSwarmManaCost)); retVal.Add("Starfall", String.Format("{0:F2} dps*{1:F2} avg per cast\n{2:F2} avg per star\n{3:F0} avg mana", SelectedRotation.StarfallDamage / 10.0f, SelectedRotation.StarfallDamage, SelectedRotation.StarfallDamage / (SelectedRotation.StarfallStars > 0 ? SelectedRotation.StarfallStars : 1f), StarfallMana)); retVal.Add("Treants", String.Format("{0:F2} dps*{1:F2} avg per cast\n{2:F2} avg per tree", TreantDamage / 30.0f, TreantDamage, TreantDamage / 3.0f)); return(retVal); }
public override Dictionary <string, string> GetCharacterDisplayCalculationValues() { Dictionary <string, string> retVal = new Dictionary <string, string>(); // if (BasicStats == null) { BasicStats = new Stats(); } retVal.Add("Health", BasicStats.Health.ToString()); retVal.Add("Mana", BasicStats.Mana.ToString()); retVal.Add("Armor", BasicStats.Armor.ToString()); retVal.Add("Agility", Math.Floor(BasicStats.Agility).ToString()); retVal.Add("Stamina", Math.Floor(BasicStats.Stamina).ToString()); retVal.Add("Intellect", Math.Floor(BasicStats.Intellect).ToString()); retVal.Add("Spirit", Math.Floor(BasicStats.Spirit).ToString()); retVal.Add("Fight Length", String.Format("{0:F0}s", FightLength)); retVal.Add("Divisions", Division.Fractions.Length.ToString() + "*" + Division.GetDivisionDetailTooltip(i => String.Format("{0:F0}s ({1:F2}%)", Division.Fractions[i] * FightLength, 100 * Division.Fractions[i]))); retVal.Add("Innervates", String.Format("{0:F2}", Innervates) + ((Innervates != Math.Floor(Innervates)) ? "*Fractional values denote the reduced value of innervates near the fight beginning or end" : "")); retVal.Add("Innervate Effect Delay", String.Format("{0:F2}", InnervateEffectDelay) + ((InnervateEffect != null) ? "*Innervate when this effect is up, and it wouldn't cap mana:\n" + InnervateEffect.ToString() : "")); retVal.Add("Mean Mana", String.Format("{0:F0}", MeanMana)); retVal.Add("Innervate Mana", String.Format("{0:F0}", InnervateMana)); retVal.Add("Innervate Size", String.Format("{0:F0}", InnervateSize)); addSpellStatValues(retVal, "Spell Power", "{0:F0}", x => x.SpellPower, String.Format("{0:F0} Base Spell Power", Math.Floor(BaseSpellPower))); addSpellStatValues(retVal, "Spell Crit", "{0:F2}%", x => x.SpellCrit * 100, String.Format("{0} Crit Rating from Gear, {1:F}% Crit from Gear, {2:F}% Crit from Gear Intellect", BasicStats.CritRating, 100 * StatConversion.GetSpellCritFromRating(BasicStats.CritRating), 100 * StatConversion.GetSpellCritFromIntellect(BasicStats.Intellect))); addSpellStatValues(retVal, "Spell Haste", "{0:F2}%", x => 100 * (1 / x.Haste.HastedSecond - 1), String.Format("{0} Haste Rating from Gear, {1:F}% Haste from Gear, {2:F}% Haste from Gear and Buffs", (float)BasicStats.HasteRating, 100 * StatConversion.GetSpellHasteFromRating((float)BasicStats.HasteRating), 100 * ((1 + StatConversion.GetSpellHasteFromRating((float)BasicStats.HasteRating)) * (1.0f + BasicStats.SpellHaste) - 1) )); addSpellStatValues(retVal, "Spell Mana Cost Reduction", "{0:F0}", x => x.SpellsManaCostReduction); addSpellStatValues(retVal, "Spell Crit Extra Bonus", "{0:F0}%", x => 100 * x.BonusCritHealMultiplier); string masteryInfo = String.Format("{0:F0} Mastery Rating from Gear, {1:F2} Mastery from Gear", BasicStats.MasteryRating, 8 + StatConversion.GetMasteryFromRating(BasicStats.MasteryRating)); addSpellStatValues(retVal, "Harmony", "{0:F}%", x => 100 * x.Harmony, masteryInfo); retVal.Add("Mana Regen", String.Format("{0:F0}", ManaRegen)); retVal.Add("Base Mana Regen", String.Format("{0:F0}", BaseRegen)); retVal.Add("Initial Mana Pool Regen", String.Format("{0:F0}", ManaPoolRegen)); retVal.Add("Spirit Mana Regen", String.Format("{0:F0}", SpiritRegen)); retVal.Add("Innervate Mana Regen", String.Format("{0:F0}", InnervateRegen)); retVal.Add("Replenishment Mana Regen", String.Format("{0:F0}", ReplenishmentRegen)); retVal.Add("Revitalize Mana Regen", String.Format("{0:F0}", RevitalizeRegen)); retVal.Add("Potion Mana Regen", String.Format("{0:F0}", PotionRegen)); retVal.Add("Total Score", String.Format("{0:F2}", OverallPoints)); string[] longNames = { "Raid Sustained", "Raid Burst", "Tank Sustained", "Tank Burst" }; for (int i = 0; i < longNames.Length; ++i) { retVal.Add(longNames[i] + " HPS", String.Format("{0:F2}", Solutions[i].Distribution.TotalEPS())); retVal.Add(longNames[i] + " Directs/s", String.Format("{0:F2}", Solutions[i].Distribution.TotalCPS())); retVal.Add(longNames[i] + " Ticks/s", String.Format("{0:F2}", Solutions[i].Distribution.TotalTPS())); } retVal.Add("Proc trigger interval", String.Format("{0:F2}", ProcTriggerInterval)); retVal.Add("Proc periodic trigger interval", String.Format("{0:F2}", ProcPeriodicTriggerInterval)); ContinuousAction[] actions = ContinuousAction.AverageActionSets(Actions, Division.Fractions); for (int i = 0; i < actions.Length; ++i) { retVal.Add(CalculationsTree.ActionNames[i] + " HPCT", actions[i].EPSText + "*" + Division.GetDivisionDetailTooltip(div => Actions[div][i].EPSText)); retVal.Add(CalculationsTree.ActionNames[i] + " MPCT", actions[i].MPSText + "*" + Division.GetDivisionDetailTooltip(div => Actions[div][i].MPSText)); retVal.Add(CalculationsTree.ActionNames[i] + " HPM", actions[i].EPMText + "*" + Division.GetDivisionDetailTooltip(div => Actions[div][i].EPMText)); } string[] names = { "Raid S.", "Raid B.", "Tank S.", "Tank B." }; for (int i = 0; i < names.Length; ++i) { Solutions[i].GetProperties(retVal, names[i] + ' ', CalculationsTree.ActionNames, CalculationsTree.PassiveNames); } addSpellCalculationValues(retVal, "Time", s => s.Action.Time); addSpellCalculationValues(retVal, "Duration", s => s.Duration); addSpellCalculationValues(retVal, "Mana", s => s.Action.Mana); addSpellCalculationValues(retVal, "Direct", s => s.Action.Direct); addSpellCalculationValues(retVal, "Tick", s => s.Tick); addSpellCalculationValues(retVal, "Ticks", s => s.Ticks); addSpellCalculationValues(retVal, "Periodic", s => s.Action.Periodic); addSpellCalculationValues(retVal, "Raid Direct", s => s.RaidAction.Direct); addSpellCalculationValues(retVal, "Raid Periodic", s => s.RaidAction.Periodic); addSpellCalculationValues(retVal, "Tank Direct", s => s.TankAction.Direct); addSpellCalculationValues(retVal, "Tank Periodic", s => s.TankAction.Periodic); return(retVal); }
public ProcEffect(SpecialEffect effect) { this.Effect = effect; // Shadow damage procs - most widely varied at the current moment if (effect.Stats.ShadowDamage > 0) { CalculateDPS = delegate(SpellRotation r, CharacterCalculationsMoonkin c, float sp, float sHi, float sc, float sHa) { SpecialEffect e = Effect; float specialDamageModifier = (1 + c.BasicStats.BonusSpellPowerMultiplier) * (1 + c.BasicStats.BonusShadowDamageMultiplier) * (1 + c.BasicStats.BonusDamageMultiplier); float triggerInterval = 0.0f; switch (e.Trigger) { case Trigger.DoTTick: // Extract triggerInterval = r.Duration / r.DotTicks; break; case Trigger.SpellHit: // Pendulum triggerInterval = r.Duration / r.CastCount; break; case Trigger.DamageDone: // DMC: Death triggerInterval = r.Duration / (r.CastCount + r.DotTicks); break; case Trigger.DamageOrHealingDone: // DMC: Greatness // Need to add Self-Heals triggerInterval = r.Duration / (r.CastCount + r.DotTicks); break; default: return(0.0f); } float procsPerSecond = e.GetAverageProcsPerSecond(triggerInterval, 1.0f, 3.0f, c.FightLength * 60.0f); return(e.Stats.ShadowDamage * specialDamageModifier * procsPerSecond); }; } // Lightning Capacitor, Thunder Capacitor, Reign of the Unliving/Undead, Nibelung else if (effect.Stats.NatureDamage > 0 || effect.Stats.FireDamage > 0 || effect.Stats.ValkyrDamage > 0) { if (effect.Stats.NatureDamage > 0) { CalculateDPS = delegate(SpellRotation r, CharacterCalculationsMoonkin c, float sp, float sHi, float sc, float sHa) { float specialDamageModifier = (1 + c.BasicStats.BonusSpellPowerMultiplier) * (1 + c.BasicStats.BonusNatureDamageMultiplier) * (1 + c.BasicStats.BonusDamageMultiplier); float procsPerSecond = Effect.GetAverageProcsPerSecond(r.Duration / (r.CastCount * sc), 1.0f, 3.0f, c.FightLength * 60.0f); return(Effect.Stats.NatureDamage * specialDamageModifier * procsPerSecond); }; } else if (effect.Stats.FireDamage > 0) { CalculateDPS = delegate(SpellRotation r, CharacterCalculationsMoonkin c, float sp, float sHi, float sc, float sHa) { float specialDamageModifier = (1 + c.BasicStats.BonusSpellPowerMultiplier) * (1 + c.BasicStats.BonusFireDamageMultiplier) * (1 + c.BasicStats.BonusDamageMultiplier); float procsPerSecond = Effect.GetAverageProcsPerSecond(r.Duration / (r.CastCount * sc), 1.0f, 3.0f, c.FightLength * 60.0f); return(Effect.Stats.FireDamage * specialDamageModifier * procsPerSecond); }; } else { CalculateDPS = delegate(SpellRotation r, CharacterCalculationsMoonkin c, float sp, float sHi, float sc, float sHa) { float specialDamageModifier = (1 + c.BasicStats.BonusSpellPowerMultiplier) * (1 + c.BasicStats.BonusDamageMultiplier); float procsPerSecond = Effect.GetAverageProcsPerSecond(r.Duration / (r.CastCount * sc), 1.0f, 3.0f, c.FightLength * 60.0f); return(Effect.Stats.ValkyrDamage * specialDamageModifier * procsPerSecond); }; } } else if (effect.Stats.Mp5 > 0) { CalculateMP5 = delegate(SpellRotation r, CharacterCalculationsMoonkin c, float sp, float sHi, float sc, float sHa) { SpecialEffect e = Effect; float procsPerSecond = e.GetAverageProcsPerSecond(r.Duration / r.CastCount, 1.0f, 3.0f, c.FightLength * 60f); return((e.Stats.Mp5 / 5.0f * e.Duration) * procsPerSecond * 5.0f); }; } // Moonkin 4T8 set bonus (15% chance on IS tick to proc an instant-cast Starfire) else if (effect.Stats.StarfireProc == 1) { CalculateDPS = delegate(SpellRotation r, CharacterCalculationsMoonkin c, float sp, float sHi, float sc, float sHa) { if (r.InsectSwarmTicks == 0) { return(0.0f); } Spell newSF = new Spell() { AllDamageModifier = r.Solver.Starfire.AllDamageModifier, BaseCastTime = 1.5f, BaseDamage = r.Solver.Starfire.BaseDamage, BaseManaCost = r.Solver.Starfire.BaseManaCost, CriticalChanceModifier = r.Solver.Starfire.CriticalChanceModifier, CriticalDamageModifier = r.Solver.Starfire.CriticalDamageModifier, DotEffect = null, IdolExtraSpellPower = r.Solver.Starfire.IdolExtraSpellPower, Name = r.Solver.Starfire.Name, School = r.Solver.Starfire.School, SpellDamageModifier = r.Solver.Starfire.SpellDamageModifier }; r.DoSpecialStarfire(c, ref newSF, sp, sHi, sc, sHa); float timeBetweenProcs = r.Solver.InsectSwarm.DotEffect.TickLength / Effect.Chance; float replaceWrathWithSFDPS = (newSF.DamagePerHit / newSF.CastTime) - (r.Solver.Wrath.DamagePerHit / r.Solver.Wrath.CastTime); float replaceSFWithSFDPS = (newSF.DamagePerHit / newSF.CastTime) - (r.Solver.Starfire.DamagePerHit / r.Solver.Starfire.CastTime); return((replaceWrathWithSFDPS * (r.WrathCount / (r.WrathCount + r.StarfireCount)) + replaceSFWithSFDPS * (r.StarfireCount / (r.WrathCount + r.StarfireCount))) / timeBetweenProcs); }; } else if (Effect.Stats._rawSpecialEffectDataSize == 0 && (Effect.Trigger == Trigger.DamageDone || Effect.Trigger == Trigger.DamageOrHealingDone || Effect.Trigger == Trigger.DamageSpellCast || Effect.Trigger == Trigger.DamageSpellCrit || Effect.Trigger == Trigger.DamageSpellHit || Effect.Trigger == Trigger.SpellCast || Effect.Trigger == Trigger.SpellCrit || Effect.Trigger == Trigger.SpellHit || Effect.Trigger == Trigger.SpellMiss || Effect.Trigger == Trigger.Use || Effect.Trigger == Trigger.MoonfireCast || Effect.Trigger == Trigger.InsectSwarmOrMoonfireTick || Effect.Trigger == Trigger.MoonfireTick || Effect.Trigger == Trigger.InsectSwarmTick || Effect.Trigger == Trigger.DoTTick) && (Effect.Stats.HasteRating > 0 || Effect.Stats.SpellHaste > 0 || Effect.Stats.CritRating > 0 || Effect.Stats.HighestStat > 0)) { Activate = delegate(Character ch, CharacterCalculationsMoonkin c, ref float sp, ref float sHi, ref float sc, ref float sHa) { SpecialEffect e = Effect; int maxStack = e.MaxStack; Stats st = e.Stats; float critRating = st.CritRating; float spellCrit = StatConversion.GetSpellCritFromRating(critRating * maxStack); float hasteRating = st.HasteRating; float spellHaste = StatConversion.GetSpellHasteFromRating(hasteRating * maxStack); spellHaste += st.SpellHaste; float highestStat = st.HighestStat; if (critRating > 0) { sc += spellCrit; } if (spellHaste > 0) { sHa += spellHaste; } if (highestStat > 0) { if (c.BasicStats.Spirit > c.BasicStats.Intellect) { Stats s = c.BasicStats.Clone(); s.Spirit += highestStat; CharacterCalculationsMoonkin cNew = CalculationsMoonkin.GetInnerCharacterCalculations(ch, s, null); storedStats.SpellPower = cNew.SpellPower - c.SpellPower; sp += storedStats.SpellPower; } else { Stats s = c.BasicStats.Clone(); s.Intellect += highestStat; CharacterCalculationsMoonkin cNew = CalculationsMoonkin.GetInnerCharacterCalculations(ch, s, null); storedStats.SpellPower = cNew.SpellPower - c.SpellPower; storedStats.SpellCrit = cNew.SpellCrit - c.SpellCrit; sp += storedStats.SpellPower; sc += storedStats.SpellCrit; } } }; Deactivate = delegate(Character ch, CharacterCalculationsMoonkin c, ref float sp, ref float sHi, ref float sc, ref float sHa) { SpecialEffect e = Effect; int maxStack = e.MaxStack; Stats st = e.Stats; float critRating = st.CritRating; float spellCrit = StatConversion.GetSpellCritFromRating(critRating * maxStack); float hasteRating = st.HasteRating; float spellHaste = StatConversion.GetSpellHasteFromRating(hasteRating * maxStack); spellHaste += st.SpellHaste; float highestStat = st.HighestStat; if (critRating > 0) { sc -= spellCrit; } if (spellHaste > 0) { sHa -= spellHaste; } if (highestStat > 0) { sp -= storedStats.SpellPower; if (c.BasicStats.Intellect >= c.BasicStats.Spirit) { sc -= storedStats.SpellCrit; } } }; UpTime = delegate(SpellRotation r, CharacterCalculationsMoonkin c) { float upTime = 0.0f; switch (Effect.Trigger) { case Trigger.Use: upTime = Effect.GetAverageUptime(0f, 1f); break; case Trigger.SpellHit: case Trigger.DamageSpellHit: upTime = Effect.GetAverageUptime(r.Duration / r.CastCount, r.Solver.GetSpellHit(c)); break; case Trigger.DamageSpellCrit: case Trigger.SpellCrit: upTime = Effect.GetAverageUptime(r.Duration / (r.CastCount - (r.InsectSwarmTicks / r.Solver.InsectSwarm.DotEffect.NumberOfTicks)), c.SpellCrit); break; case Trigger.SpellCast: case Trigger.DamageSpellCast: upTime = Effect.GetAverageUptime(r.Duration / r.CastCount, 1f); break; case Trigger.MoonfireCast: upTime = Effect.GetAverageUptime(r.Duration / r.MoonfireCasts, 1f); break; case Trigger.InsectSwarmOrMoonfireTick: upTime = Effect.GetAverageUptime(r.Duration / (r.InsectSwarmTicks + r.MoonfireTicks), 1f); break; case Trigger.MoonfireTick: upTime = Effect.GetAverageUptime(r.Duration / r.MoonfireTicks, 1f); break; case Trigger.InsectSwarmTick: upTime = Effect.GetAverageUptime(r.Duration / r.InsectSwarmTicks, 1f); break; case Trigger.DoTTick: upTime = Effect.GetAverageUptime(r.Duration / (r.MoonfireTicks + r.InsectSwarmTicks), 1f); break; case Trigger.DamageDone: case Trigger.DamageOrHealingDone: upTime = Effect.GetAverageUptime(((r.Duration / r.CastCount) + (r.Duration / (r.MoonfireTicks + r.InsectSwarmTicks))) / 2.0f, 1f); break; default: break; } return(upTime); }; } }
/// <summary> /// Builds a dictionary containing the values to display for each of the calculations defined in /// CharacterDisplayCalculationLabels. The key should be the Label of each display calculation, /// and the value should be the value to display, optionally appended with '*' followed by any /// string you'd like displayed as a tooltip on the value. /// </summary> /// <returns>A Dictionary<string, string> containing the values to display for each of the /// calculations defined in CharacterDisplayCalculationLabels.</returns> public override Dictionary <string, string> GetCharacterDisplayCalculationValues() { Dictionary <string, string> dictValues = new Dictionary <string, string>(); #region Simulation stats dictValues.Add("Rotation", String.Format("{0}*{1}", Name, Abilities)); dictValues.Add("Warlock DPS", String.Format("{0:0}", DpsPoints)); dictValues.Add("Pet DPS", String.Format("{0:0}", PetDPSPoints)); dictValues.Add("Total DPS", String.Format("{0:0}", OverallPoints)); dictValues.Add("Damage Done", String.Format("{0:0}", OverallDamage)); dictValues.Add("Mana Used", String.Format("{0:0}", TotalManaCost)); #endregion #region HP/Mana stats dictValues.Add("Health", String.Format("{0:0}", TotalStats.Health)); dictValues.Add("Mana", String.Format("{0:0}", TotalStats.Mana)); #endregion #region Base stats dictValues.Add("Strength", String.Format("{0}", TotalStats.Strength)); dictValues.Add("Agility", String.Format("{0}", TotalStats.Agility)); dictValues.Add("Stamina", String.Format("{0}", TotalStats.Stamina)); dictValues.Add("Intellect", String.Format("{0}", TotalStats.Intellect)); dictValues.Add("Spirit", String.Format("{0}", TotalStats.Spirit)); dictValues.Add("Armor", String.Format("{0}", TotalStats.Armor)); #endregion #region Pet stats Minion minion = MinionFactory.CreateMinion(Options.Pet, Character, TotalStats); if (minion != null) { dictValues.Add("Pet Strength", String.Format("{0}", minion.Strength())); dictValues.Add("Pet Agility", String.Format("{0}", minion.Agility())); dictValues.Add("Pet Stamina", String.Format("{0:0}", minion.Stamina())); dictValues.Add("Pet Intellect", String.Format("{0:0}", minion.Intellect())); dictValues.Add("Pet Spirit", String.Format("{0}", minion.Spirit())); dictValues.Add("Pet Armor", String.Format("{0}", minion.Armor())); } #endregion #region Spell stats //pet scaling consts: http://www.wowwiki.com/Warlock_minions const float petInheritedAttackPowerPercentage = 0.57f; const float petInheritedSpellPowerPercentage = 0.15f; dictValues.Add("Bonus Damage", String.Format("{0}*Shadow Damage\t{1}\r\nFire Damage\t{2}\r\n\r\nYour Fire Damage increases your pet's Attack Power by {3} and Spell Damage by {4}.", TotalStats.SpellPower, TotalStats.SpellPower + TotalStats.SpellShadowDamageRating, TotalStats.SpellPower + TotalStats.SpellFireDamageRating, Math.Round((TotalStats.SpellPower + TotalStats.SpellFireDamageRating) * petInheritedAttackPowerPercentage, 0), Math.Round((TotalStats.SpellPower + TotalStats.SpellFireDamageRating) * petInheritedSpellPowerPercentage, 0) )); #region Hit / Miss chance //float bonusHit = TotalStats.SpellHit; float onePercentOfHitRating = (1 / StatConversion.GetSpellHitFromRating(1)); float hitFromRating = StatConversion.GetSpellHitFromRating(TotalStats.HitRating); float hitFromTalents = (Character.WarlockTalents.Suppression * 0.01f); float hitFromBuffs = (TotalStats.SpellHit - hitFromRating - hitFromTalents); float targetHit = (Options.TargetHit / 100f); float totalHit = (targetHit + TotalStats.SpellHit); float missChance = (totalHit > 1 ? 0 : (1 - totalHit)); //calculate the amount of hit rating that is over or under the cap float hitDelta = (totalHit > 1) ? (float)Math.Floor((totalHit - 1) * onePercentOfHitRating) : (float)Math.Ceiling((1 - totalHit) * onePercentOfHitRating); //now we can calculate the hitcap value float hitCap = (totalHit > 1) ? TotalStats.HitRating - hitDelta : TotalStats.HitRating + hitDelta; dictValues.Add("Hit Rating", String.Format("{0}*{1:0.00%} Hit Chance (max 100%) | {2:0.00%} Miss Chance \r\n\r\n" + "{3:0.00%}\t Base Hit Chance on a Level {4:0} target\r\n" + "{5:0.00%}\t from {6:0} Hit Rating [gear, food and/or flasks]\r\n" + "{7:0.00%}\t from Talent: Suppression\r\n" + "{8:0.00%}\t from Buffs: Racial and/or Spell Hit Chance Taken\r\n\r\n" + "{9}\r\n\r\n" + "Hit Rating caps:\r\n" + "446 - hard cap (no hit from talents, gear or buffs)\r\n" + "420 - Heroic Presence\r\n" + "368 - Suppression\r\n" + "342 - Suppression and Heroic Presence\r\n" + "289 - Suppression, Improved Faerie Fire / Misery\r\n" + "263 - Suppression, Improved Faerie Fire / Misery and Heroic Presence", TotalStats.HitRating, totalHit, missChance, targetHit, Options.TargetLevel, hitFromRating, TotalStats.HitRating, hitFromTalents, hitFromBuffs, String.Format("You are {0} hit rating {1} the {2} cap.", hitDelta, ((totalHit > 1) ? "above" : "below"), hitCap) )); dictValues.Add("Miss chance", String.Format("{0:0.00%}", missChance)); #endregion #region Crit % Stats statsBase = BaseStats.GetBaseStats(Character); float critFromRating = StatConversion.GetSpellCritFromRating(TotalStats.CritRating); float critFromIntellect = StatConversion.GetSpellCritFromIntellect(TotalStats.Intellect); float critFromBuffs = TotalStats.SpellCrit - statsBase.SpellCrit - critFromRating - critFromIntellect - (Character.WarlockTalents.DemonicTactics * 0.02f) - (Character.WarlockTalents.Backlash * 0.01f); dictValues.Add("Crit Chance", String.Format("{0:0.00%}*" + "{1:0.00%}\tfrom {2:0} Spell Crit rating\r\n" + "{3:0.00%}\tfrom {4:0} Intellect\r\n" + "{5:0.000%}\tfrom Warlock Class Bonus\r\n" + "{6:0%}\tfrom Talent: Demonic Tactics\r\n" + "{7:0%}\tfrom Talent: Backlash\r\n" + "{8:0%}\tfrom Buffs", TotalStats.SpellCrit, critFromRating, TotalStats.CritRating, critFromIntellect, TotalStats.Intellect, statsBase.SpellCrit, (Character.WarlockTalents.DemonicTactics * 0.02f), (Character.WarlockTalents.Backlash * 0.01f), critFromBuffs )); #endregion dictValues.Add("Haste Rating", String.Format("{0}%*{1}%\tfrom {2} Haste rating\r\n{3}%\tfrom Buffs\r\n{4}s\tGlobal Cooldown", (TotalStats.SpellHaste * 100f).ToString("0.00"), (StatConversion.GetSpellHasteFromRating(TotalStats.HasteRating) * 100f).ToString("0.00"), TotalStats.HasteRating, (TotalStats.SpellHaste * 100f - StatConversion.GetSpellHasteFromRating(TotalStats.HasteRating) * 100f).ToString("0.00"), Math.Max(1.0f, 1.5f / (1 + TotalStats.SpellHaste)).ToString("0.00"))); dictValues.Add("Mana Regen", String.Format("{0}*{0} mana regenerated every 5 seconds while not casting\r\n{1} mana regenerated every 5 seconds while casting", GetManaRegenOutOfCombat(), GetManaRegenInCombat())); #endregion #region Shadow school dictValues.Add("Shadow Bolt", new ShadowBolt(TotalStats, Character, Options).ToString()); if (Character.WarlockTalents.Haunt > 0) { dictValues.Add("Haunt", new Haunt(TotalStats, Character, Options).ToString()); } else { dictValues.Add("Haunt", "- *Required talent not available"); } dictValues.Add("Corruption", new Corruption(TotalStats, Character, Options).ToString()); dictValues.Add("Curse of Agony", new CurseOfAgony(TotalStats, Character, Options).ToString()); dictValues.Add("Curse of Doom", new CurseOfDoom(TotalStats, Character, Options).ToString()); if (Character.WarlockTalents.UnstableAffliction > 0) { dictValues.Add("Unstable Affliction", new UnstableAffliction(TotalStats, Character, Options).ToString()); } else { dictValues.Add("Unstable Affliction", "- *Required talent not available"); } dictValues.Add("Death Coil", new DeathCoil(TotalStats, Character, Options).ToString()); dictValues.Add("Drain Life", new DrainLife(TotalStats, Character, Options).ToString()); dictValues.Add("Drain Soul", new DrainSoul(TotalStats, Character, Options).ToString()); dictValues.Add("Seed of Corruption", new SeedOfCorruption(TotalStats, Character, Options).ToString()); dictValues.Add("Shadowflame", new Shadowflame(TotalStats, Character, Options).ToString()); if (Character.WarlockTalents.Shadowburn > 0) { dictValues.Add("Shadowburn", new Shadowburn(TotalStats, Character, Options).ToString()); } else { dictValues.Add("Shadowburn", "- *Required talent not available"); } if (Character.WarlockTalents.Shadowfury > 0) { dictValues.Add("Shadowfury", new Shadowfury(TotalStats, Character, Options).ToString()); } else { dictValues.Add("Shadowfury", "- *Required talent not available"); } dictValues.Add("Life Tap", new LifeTap(TotalStats, Character, Options).ToString()); if (Character.WarlockTalents.DarkPact > 0) { dictValues.Add("Dark Pact", new DarkPact(TotalStats, Character, Options).ToString()); } else { dictValues.Add("Dark Pact", "- *Required talent not available"); } #endregion #region Fire school dictValues.Add("Incinerate", new Incinerate(TotalStats, Character, Options).ToString()); dictValues.Add("Immolate", new Immolate(TotalStats, Character, Options).ToString()); if (Character.WarlockTalents.Conflagrate > 0) { dictValues.Add("Conflagrate", new Conflagrate(TotalStats, Character, Options).ToString()); } else { dictValues.Add("Conflagrate", "- *Required talent not available"); } if (Character.WarlockTalents.ChaosBolt > 0) { dictValues.Add("Chaos Bolt", new ChaosBolt(TotalStats, Character, Options).ToString()); } else { dictValues.Add("Chaos Bolt", "- *Required talent not available"); } dictValues.Add("Rain of Fire", new RainOfFire(TotalStats, Character, Options).ToString()); dictValues.Add("Hellfire", new Hellfire(TotalStats, Character, Options).ToString()); if (Character.WarlockTalents.Metamorphosis > 0) { dictValues.Add("Immolation Aura", new ImmolationAura(TotalStats, Character, Options).ToString()); } else { dictValues.Add("Immolation Aura", "- *Required talent not available"); } dictValues.Add("Searing Pain", new SearingPain(TotalStats, Character, Options).ToString()); dictValues.Add("Soul Fire", new SoulFire(TotalStats, Character, Options).ToString()); #endregion return(dictValues); }
public override Dictionary <string, string> GetCharacterDisplayCalculationValues() { Dictionary <string, string> retVal = new Dictionary <string, string>(); // if (baseStats == null) { baseStats = new StatsMoonkin(); } if (SelectedRotation == null) { SelectedRotation = new RotationData(); } if (BurstRotation == null) { BurstRotation = new RotationData(); } // retVal.Add("Health", baseStats.Health.ToString()); retVal.Add("Mana", baseStats.Mana.ToString()); retVal.Add("Armor", baseStats.Armor.ToString()); retVal.Add("Agility", baseStats.Agility.ToString()); retVal.Add("Stamina", baseStats.Stamina.ToString()); retVal.Add("Intellect", baseStats.Intellect.ToString()); retVal.Add("Spirit", baseStats.Spirit.ToString()); retVal.Add("Spell Power", SpellPower.ToString()); float totalHitDelta = SpellHitCap - SpellHit; retVal.Add("Spell Hit", String.Format("{0:F}%*{1} Hit Rating, {2:F}% Hit From Gear" + (totalHitDelta != 0 ? ", {3} Rating {4}" : ""), 100 * SpellHit, baseStats.HitRating, 100 * StatConversion.GetSpellHitFromRating(baseStats.HitRating), StatConversion.GetRatingFromHit(Math.Abs(totalHitDelta)), totalHitDelta > 0 ? "To Cap" : "Over Cap")); retVal.Add("Spell Crit", String.Format("{0:F}%*{1} Crit Rating, {2:F}% Crit From Gear, {3:F}% Crit From Intellect", 100 * SpellCrit, baseStats.CritRating, 100 * StatConversion.GetSpellCritFromRating(baseStats.CritRating), 100 * StatConversion.GetSpellCritFromIntellect(baseStats.Intellect))); retVal.Add("Spell Haste", String.Format("{0:F}%*{1} Haste Rating, {2:F}% Haste From Gear", 100 * SpellHaste, baseStats.HasteRating, 100 * StatConversion.GetSpellHasteFromRating(baseStats.HasteRating))); retVal.Add("Mastery", String.Format("{0:F}*{1:F} Eclipse %, {2} Rating", Mastery, Mastery * 2.0f, baseStats.MasteryRating)); retVal.Add("Mana Regen", String.Format("{0:F0}", ManaRegen * 5.0f)); retVal.Add("Total Score", String.Format("{0:F2}", OverallPoints)); retVal.Add("Selected Rotation", String.Format("*{0}", SelectedRotation.Name)); retVal.Add("Selected DPS", String.Format("{0:F2}", SelectedRotation.SustainedDPS)); retVal.Add("Selected Time To OOM", String.Format(SelectedRotation.TimeToOOM > new TimeSpan(0, 0, 0) ? "{0} m {1} s" : "Not during fight", SelectedRotation.TimeToOOM.Minutes, SelectedRotation.TimeToOOM.Seconds)); retVal.Add("Selected Cycle Length", String.Format("{0:F1} s", SelectedRotation.Duration)); StringBuilder sb = new StringBuilder("*"); float rotationDamage = SelectedRotation.SustainedDPS * SelectedRotation.Duration; sb.AppendLine(String.Format("{0}: {1:F2}%, {2:F2} damage, {3:F0} count", "Starfire", 100 * SelectedRotation.StarfireAvgHit * SelectedRotation.StarfireCount / rotationDamage, SelectedRotation.StarfireAvgHit * SelectedRotation.StarfireCount, SelectedRotation.StarfireCount)); sb.AppendLine(String.Format("{0}: {1:F2}%, {2:F2} damage, {3:F0} count", "Moonfire", 100 * (SelectedRotation.MoonfireAvgHit) * SelectedRotation.MoonfireCasts / rotationDamage, (SelectedRotation.MoonfireAvgHit) * SelectedRotation.MoonfireCasts, SelectedRotation.MoonfireCasts)); sb.AppendLine(String.Format("{0}: {1:F2}%, {2:F2} damage, {3:F0} count", "Insect Swarm", 100 * SelectedRotation.InsectSwarmAvgHit * SelectedRotation.InsectSwarmCasts / rotationDamage, SelectedRotation.InsectSwarmAvgHit * (SelectedRotation.InsectSwarmCasts), SelectedRotation.InsectSwarmCasts)); sb.AppendLine(String.Format("{0}: {1:F2}%, {2:F2} damage, {3:F0} count", "Wrath", 100 * SelectedRotation.WrathAvgHit * SelectedRotation.WrathCount / rotationDamage, SelectedRotation.WrathAvgHit * SelectedRotation.WrathCount, SelectedRotation.WrathCount)); sb.AppendLine(String.Format("{0}: {1:F2}%, {2:F2} damage, {3:F0} count", "Starsurge", 100 * SelectedRotation.StarSurgeAvgHit * SelectedRotation.StarSurgeCount / rotationDamage, SelectedRotation.StarSurgeAvgHit * SelectedRotation.StarSurgeCount, SelectedRotation.StarSurgeCount)); sb.AppendLine(String.Format("{0}: {1:F2}%, {2:F2} damage, {3:F0} count", "Starfall", 100 * SelectedRotation.StarfallDamage * SelectedRotation.StarfallCasts / rotationDamage, SelectedRotation.StarfallDamage * SelectedRotation.StarfallCasts, SelectedRotation.StarfallCasts)); sb.AppendLine(String.Format("{0}: {1:F2}%, {2:F2} damage, {3:F0} count", "Wild Mushroom", 100 * SelectedRotation.MushroomDamage * SelectedRotation.MushroomCasts / rotationDamage, SelectedRotation.MushroomDamage * SelectedRotation.MushroomCasts, SelectedRotation.MushroomCasts)); retVal.Add("Selected Spell Breakdown", sb.ToString()); retVal.Add("Burst Rotation", String.Format("*{0}", BurstRotation.Name)); retVal.Add("Burst DPS", String.Format("{0:F2}", BurstRotation.BurstDPS)); retVal.Add("Burst Time To OOM", String.Format(BurstRotation.TimeToOOM > new TimeSpan(0, 0, 0) ? "{0} m {1} s" : "Not during fight", BurstRotation.TimeToOOM.Minutes, BurstRotation.TimeToOOM.Seconds)); retVal.Add("Burst Cycle Length", String.Format("{0:F1} s", BurstRotation.Duration)); sb = new StringBuilder("*"); rotationDamage = BurstRotation.BurstDPS * BurstRotation.Duration; sb.AppendLine(String.Format("{0}: {1:F2}%, {2:F2} damage, {3:F0} count", "Starfire", 100 * BurstRotation.StarfireAvgHit * BurstRotation.StarfireCount / rotationDamage, BurstRotation.StarfireAvgHit * BurstRotation.StarfireCount, BurstRotation.StarfireCount)); sb.AppendLine(String.Format("{0}: {1:F2}%, {2:F2} damage, {3:F0} count", "Moonfire", 100 * (BurstRotation.MoonfireAvgHit) * BurstRotation.MoonfireCasts / rotationDamage, (BurstRotation.MoonfireAvgHit) * BurstRotation.MoonfireCasts, BurstRotation.MoonfireCasts)); sb.AppendLine(String.Format("{0}: {1:F2}%, {2:F2} damage, {3:F0} count", "Insect Swarm", 100 * BurstRotation.InsectSwarmAvgHit * BurstRotation.InsectSwarmCasts / rotationDamage, BurstRotation.InsectSwarmAvgHit * (BurstRotation.InsectSwarmCasts), BurstRotation.InsectSwarmCasts)); sb.AppendLine(String.Format("{0}: {1:F2}%, {2:F2} damage, {3:F0} count", "Wrath", 100 * BurstRotation.WrathAvgHit * BurstRotation.WrathCount / rotationDamage, BurstRotation.WrathAvgHit * BurstRotation.WrathCount, BurstRotation.WrathCount)); sb.AppendLine(String.Format("{0}: {1:F2}%, {2:F2} damage, {3:F0} count", "Starsurge", 100 * BurstRotation.StarSurgeAvgHit * BurstRotation.StarSurgeCount / rotationDamage, BurstRotation.StarSurgeAvgHit * BurstRotation.StarSurgeCount, BurstRotation.StarSurgeCount)); sb.AppendLine(String.Format("{0}: {1:F2}%, {2:F2} damage, {3:F0} count", "Starfall", 100 * BurstRotation.StarfallDamage * BurstRotation.StarfallCasts / rotationDamage, BurstRotation.StarfallDamage * BurstRotation.StarfallCasts, BurstRotation.StarfallCasts)); sb.AppendLine(String.Format("{0}: {1:F2}%, {2:F2} damage, {3:F0} count", "Wild Mushroom", 100 * BurstRotation.MushroomDamage * BurstRotation.MushroomCasts / rotationDamage, BurstRotation.MushroomDamage * BurstRotation.MushroomCasts, BurstRotation.MushroomCasts)); retVal.Add("Burst Spell Breakdown", sb.ToString()); retVal.Add("Nature's Grace Uptime", String.Format("{0:F2}%", SelectedRotation.NaturesGraceUptime * 100)); retVal.Add("Solar Eclipse Uptime", String.Format("{0:F2}%", SelectedRotation.SolarUptime * 100)); retVal.Add("Lunar Eclipse Uptime", String.Format("{0:F2}%", SelectedRotation.LunarUptime * 100)); retVal.Add("Starfire", String.Format("{0:F2} dps*{1:F2} s avg\n {2:F2} avg hit\n{3:F0} avg energy", SelectedRotation.StarfireAvgHit / (SelectedRotation.StarfireAvgCast > 0 ? SelectedRotation.StarfireAvgCast : 1f), SelectedRotation.StarfireAvgCast, SelectedRotation.StarfireAvgHit, SelectedRotation.StarfireAvgEnergy)); retVal.Add("Wrath", String.Format("{0:F2} dps*{1:F2} s avg\n {2:F2} avg hit\n{3:F0} avg energy", SelectedRotation.WrathAvgHit / (SelectedRotation.WrathAvgCast > 0 ? SelectedRotation.WrathAvgCast : 1f), SelectedRotation.WrathAvgCast, SelectedRotation.WrathAvgHit, SelectedRotation.WrathAvgEnergy)); retVal.Add("Starsurge", String.Format("{0:F2} dps*{1:F2} s avg\n {2:F2} avg hit\n{3:F0} avg energy", SelectedRotation.StarSurgeAvgHit / (SelectedRotation.StarSurgeAvgCast > 0 ? SelectedRotation.StarSurgeAvgCast : 1f), SelectedRotation.StarSurgeAvgCast, SelectedRotation.StarSurgeAvgHit, SelectedRotation.StarSurgeAvgEnergy)); retVal.Add("Moonfire", String.Format("{0:F2} dps*{1:F2} s avg\n{2:F2} avg hit", SelectedRotation.MoonfireAvgHit / (SelectedRotation.MoonfireDuration > 0 ? SelectedRotation.MoonfireDuration : 1f), SelectedRotation.MoonfireAvgCast, SelectedRotation.MoonfireAvgHit)); retVal.Add("Insect Swarm", String.Format("{0:F2} dps*{1:F2} s avg\n{2:F2} avg hit", SelectedRotation.InsectSwarmAvgHit / (SelectedRotation.InsectSwarmDuration > 0 ? SelectedRotation.InsectSwarmDuration : 1f), SelectedRotation.InsectSwarmAvgCast, SelectedRotation.InsectSwarmAvgHit)); retVal.Add("Starfall", String.Format("{0:F2} dps*{1:F2} avg per cast\n{2:F2} avg per star", SelectedRotation.StarfallDamage / 10.0f, SelectedRotation.StarfallDamage, SelectedRotation.StarfallDamage / (SelectedRotation.StarfallStars > 0 ? SelectedRotation.StarfallStars : 1f))); retVal.Add("Treants", String.Format("{0:F2} dps*{1:F2} avg per cast\n{2:F2} avg per tree", SelectedRotation.TreantDamage / 30.0f, SelectedRotation.TreantDamage, SelectedRotation.TreantDamage / 3.0f)); retVal.Add("Wild Mushroom", String.Format("{0:F2} dps*{1:F2} avg per cast\n{2:F2} avg per mushroom", SelectedRotation.MushroomDamage / 10.0f, SelectedRotation.MushroomDamage, SelectedRotation.MushroomDamage / 3f)); return(retVal); }
public void UpdateCalcs(bool firstPass) { // talents callOfThunder = .05f * _talents.CallOfThunder; critMultiplierMelee = 2f * (1 + _stats.BonusCritMultiplier); critMultiplierSpell = (1.5f + .1f * _character.ShamanTalents.ElementalFury) * (1 + _stats.BonusSpellCritMultiplier); // Melee float hitBonus = _stats.PhysicalHit + StatConversion.GetHitFromRating(_stats.HitRating) + 0.02f * _talents.DualWieldSpecialization; expertiseBonusMH = GetDPRfromExp(_stats.Expertise + StatConversion.GetExpertiseFromRating(_stats.ExpertiseRating)); expertiseBonusOH = GetDPRfromExp(_stats.Expertise + StatConversion.GetExpertiseFromRating(_stats.ExpertiseRating)); // Need to modify expertiseBonusMH & OH if Orc and have racial bonus weapons if (_character.Race == CharacterRace.Orc) { ItemType mhType = _character.MainHand == null ? ItemType.None : _character.MainHand.Type; ItemType ohType = _character.OffHand == null ? ItemType.None : _character.OffHand.Type; if (mhType == ItemType.OneHandAxe || mhType == ItemType.FistWeapon) // patch 3.2 includes fists { expertiseBonusMH += 0.0125f; } if (ohType == ItemType.OneHandAxe || ohType == ItemType.FistWeapon) // patch 3.2 includes fists { expertiseBonusOH += 0.0125f; } } float meleeCritModifier = _stats.PhysicalCrit; float baseMeleeCrit = StatConversion.GetCritFromRating(_stats.CritRating) + StatConversion.GetCritFromAgility(_stats.Agility, _character.Class) + .01f * _talents.ThunderingStrikes; chanceDodgeMH = Math.Max(0f, DodgeChanceCap - expertiseBonusMH); chanceDodgeOH = Math.Max(0f, DodgeChanceCap - expertiseBonusOH); float ParryChance = ParryChanceCap - expertiseBonusMH; chanceParryMH = (float)Math.Max(0f, _calcOpts.InBack ? ParryChance * (1f - _calcOpts.InBackPerc / 100f) : ParryChance); ParryChance = ParryChanceCap - expertiseBonusOH; chanceParryOH = (float)Math.Max(0f, _calcOpts.InBack ? ParryChance * (1f - _calcOpts.InBackPerc / 100f) : ParryChance); chanceWhiteMissMH = Math.Max(0f, WhiteHitCap - hitBonus) + chanceDodgeMH + chanceParryMH; chanceWhiteMissOH = Math.Max(0f, WhiteHitCap - hitBonus) + chanceDodgeOH + chanceParryOH; chanceYellowMissMH = Math.Max(0f, YellowHitCap - hitBonus) + chanceDodgeMH + chanceParryMH; // base miss 8% now chanceYellowMissOH = Math.Max(0f, YellowHitCap - hitBonus) + chanceDodgeOH + chanceParryOH; // base miss 8% now // SetCritValues((1 + _stats.BonusCritChance) * (baseMeleeCrit + meleeCritModifier) + .00005f); //fudge factor for rounding SetCritValues(baseMeleeCrit + meleeCritModifier + .00005f); //fudge factor for rounding // set two export values so that ED crit isn't included exportMeleeCritMH = chanceWhiteCritMH + whiteCritDepression; exportMeleeCritOH = chanceWhiteCritOH + whiteCritDepression; // Spells ftBonusCrit = 0f; if (_calcOpts.MainhandImbue == "Flametongue") { ftBonusCrit += _talents.GlyphofFlametongueWeapon ? .02f : 0f; } if (_calcOpts.OffhandImbue == "Flametongue" && _talents.DualWield == 1) { ftBonusCrit += _talents.GlyphofFlametongueWeapon ? .02f : 0f; } float spellCritModifier = _stats.SpellCrit + _stats.SpellCritOnTarget + ftBonusCrit; float hitBonusSpell = _stats.SpellHit + StatConversion.GetSpellHitFromRating(_stats.HitRating); chanceSpellMiss = Math.Max(0f, SpellMissRate - hitBonusSpell); overSpellHitCap = Math.Max(0f, hitBonusSpell - SpellMissRate); float baseSpellCrit = StatConversion.GetSpellCritFromRating(_stats.CritRating) + StatConversion.GetSpellCritFromIntellect(_stats.Intellect) + .01f * _talents.ThunderingStrikes; //chanceSpellCrit = Math.Min(0.75f, (1 + _stats.BonusCritChance) * (baseSpellCrit + spellCritModifier) + .00005f); //fudge factor for rounding chanceSpellCrit = Math.Min(0.75f, baseSpellCrit + spellCritModifier + .00005f); //fudge factor for rounding float hasteBonus = StatConversion.GetHasteFromRating(_stats.HasteRating, _character.Class); unhastedMHSpeed = _character.MainHand == null ? 3.0f : _character.MainHand.Item.Speed; unhastedOHSpeed = _character.OffHand == null ? 3.0f : _character.OffHand.Item.Speed; float baseHastedMHSpeed = unhastedMHSpeed / (1f + hasteBonus) / (1f + _stats.PhysicalHaste); float baseHastedOHSpeed = unhastedOHSpeed / (1f + hasteBonus) / (1f + _stats.PhysicalHaste); float chanceToProcWFPerHit = .2f + (_character.ShamanTalents.GlyphofWindfuryWeapon ? .02f : 0f); //The Swing Loop //This is where we figure out feedback systems -- WF, MW, ED, Flurry, etc. //-------------- flurryUptime = 1f; edUptime = 0f; float stormstrikeSpeed = firstPass ? (_talents.Stormstrike == 1 ? 8f : 0f) : AbilityCooldown(EnhanceAbility.StormStrike); float shockSpeed = firstPass ? BaseShockSpeed : AbilityCooldown(EnhanceAbility.EarthShock); float lavaLashSpeed = firstPass ? (_talents.LavaLash == 1 ? 6f : 0f) : AbilityCooldown(EnhanceAbility.LavaLash); float fireNovaSpeed = firstPass ? BaseFireNovaSpeed : AbilityCooldown(EnhanceAbility.FireNova); if (_calcOpts.PriorityInUse(EnhanceAbility.MagmaTotem)) { fireTotemUptime = firstPass ? 1.0f : 20f / AbilityCooldown(EnhanceAbility.MagmaTotem); } else if (_calcOpts.PriorityInUse(EnhanceAbility.SearingTotem)) { fireTotemUptime = firstPass ? 1.0f : 60f / AbilityCooldown(EnhanceAbility.SearingTotem); } else if (_calcOpts.PriorityInUse(EnhanceAbility.RefreshTotems)) // if no Searing or Magma totem use refresh of Flametongue totem. { fireTotemUptime = firstPass ? 1.0f : 300f / AbilityCooldown(EnhanceAbility.RefreshTotems); } float mwPPM = 2 * _talents.MaelstromWeapon * (1 + _stats.Enhance4T8 * 0.2f); float flurryHasteBonus = .06f * _talents.Flurry + _stats.Enhance4T7; float edCritBonus = .03f * _talents.ElementalDevastation; float staticShockChance = (.02f * _character.ShamanTalents.StaticShock + (_stats.Enhance2T9 == 1f ? 0.03f : 0f)); hitsPerSMHSS = 0f; hitsPerSOHSS = 0f; hitsPerSOH = 0f; hitsPerSMH = 0f; hitsPerSWF = 0f; if (_talents.Stormstrike == 1) { hitsPerSMHSS = (1f - chanceYellowMissMH) / stormstrikeSpeed; hitsPerSOHSS = _character.ShamanTalents.DualWield == 1 ? ((1f - 2 * chanceYellowMissOH) / stormstrikeSpeed) : 0f; //OH only swings if MH connects } hitsPerSLL = lavaLashSpeed == 0 ? 0f : (1f - chanceYellowMissOH) / lavaLashSpeed; float swingsPerSMHMelee = 0f; float swingsPerSOHMelee = 0f; float wfProcsPerSecond = 0f; float mwProcsPerSecond = 0f; secondsToFiveStack = 10f; float averageMeleeCritChance = (chanceWhiteCritMH + chanceWhiteCritOH + chanceYellowCritMH + chanceYellowCritOH) / 4f; float averageMeleeHitChance = ((1f - chanceWhiteMissMH - chanceDodgeMH - chanceParryMH) + (1f - chanceWhiteMissOH - chanceDodgeOH - chanceParryOH)) / 2f; float averageMeleeMissChance = (chanceWhiteMissMH + chanceWhiteMissOH) / 2f; float whiteHitsPerSMH = 0f; float whiteHitsPerSOH = 0f; float moteHitsPerS = 0f; float yellowHitsPerSMH = 0f; float yellowHitsPerSOH = 0f; float flameTongueHitsPerSecond = 0f; for (int i = 0; i < 5; i++) { // float bonusHaste = (1f + (flurryUptime * flurryHasteBonus)); float bonusHaste = 1 / (1 - flurryUptime + flurryUptime / (1 + flurryHasteBonus)); // use time based not proc based flurryUptime hastedMHSpeed = baseHastedMHSpeed / bonusHaste; hastedOHSpeed = baseHastedOHSpeed / bonusHaste; swingsPerSMHMelee = 1f / hastedMHSpeed; swingsPerSOHMelee = (hastedOHSpeed == 0f) ? 0f : 1f / hastedOHSpeed; whiteHitsPerSMH = ChanceWhiteHitMH * swingsPerSMHMelee; whiteHitsPerSOH = ChanceWhiteHitOH * swingsPerSOHMelee; moteHitsPerS = _stats.MoteOfAnger * 2 * AverageWhiteHitChance; // Windfury model if (_calcOpts.MainhandImbue == "Windfury") { float hitsThatProcWFPerS = whiteHitsPerSMH + hitsPerSMHSS; if (_character.ShamanTalents.DualWield == 1) { hitsThatProcWFPerS += moteHitsPerS / 2; // half the hits will be OH and thus won't proc WF } else { hitsThatProcWFPerS += moteHitsPerS; // if no offhand then all motes will be MH weapon by definition } float maxExpectedWFPerFight = hitsThatProcWFPerS * chanceToProcWFPerHit * fightLength; float ineligibleSeconds = maxExpectedWFPerFight * (3.25f - hastedMHSpeed); float expectedWFPerFight = hitsThatProcWFPerS * chanceToProcWFPerHit * (fightLength - ineligibleSeconds); wfProcsPerSecond = expectedWFPerFight / fightLength; hitsPerSWF = 2f * wfProcsPerSecond * (1f - chanceYellowMissMH); } yellowHitsPerSMH = hitsPerSWF + hitsPerSMHSS; yellowHitsPerSOH = hitsPerSOHSS + hitsPerSLL; //Due to attack table, a white swing has the same chance to crit as a yellow hit // Old Flurry calc changed 10 Nov 2009 // couldCritSwingsPerSecond = whiteHitsPerSMH + whiteHitsPerSOH + yellowHitsPerSMH + yellowHitsPerSOH; // float swingsThatConsumeFlurryPerSecond = swingsPerSMHMelee + swingsPerSOHMelee; // flurryUptime = 1f - (float)Math.Pow(1 - averageMeleeCritChance, (3 / swingsThatConsumeFlurryPerSecond) * couldCritSwingsPerSecond); // old formulae flurryUptime = CalculateFlurryUptime(averageMeleeCritChance, averageMeleeHitChance, averageMeleeMissChance); // Maelstrom Weapon time to 5 stacks calc if (_character.ShamanTalents.DualWield == 1 && unhastedOHSpeed != 0f) { hitsPerSMH = whiteHitsPerSMH + yellowHitsPerSMH + moteHitsPerS / 2; hitsPerSOH = whiteHitsPerSOH + yellowHitsPerSOH + moteHitsPerS / 2; mwProcsPerSecond = (mwPPM / (60f / unhastedMHSpeed)) * hitsPerSMH + (mwPPM / (60f / unhastedOHSpeed)) * hitsPerSOH; } else { hitsPerSMH = whiteHitsPerSMH + yellowHitsPerSMH + moteHitsPerS; hitsPerSOH = 0f; mwProcsPerSecond = (mwPPM / (60f / unhastedMHSpeed)) * hitsPerSMH; } secondsToFiveStack = 5f / mwProcsPerSecond; // Elemental Devastation Uptime calc staticShocksPerSecond = (hitsPerSMH + hitsPerSOH) * staticShockChance; flameTongueHitsPerSecond = (_calcOpts.MainhandImbue == "Flametongue" ? HitsPerSMH : 0f) + ((_calcOpts.OffhandImbue == "Flametongue" && _talents.DualWield == 1) ? HitsPerSOH : 0f); spellAttacksPerSec = (1f / secondsToFiveStack + 1f / shockSpeed + 1f / fireNovaSpeed + staticShocksPerSecond) // + flameTongueHitsPerSecond) * (1f - chanceSpellMiss); float couldCritSpellsPerS = spellAttacksPerSec - staticShocksPerSecond; // LS procs from Static Shock cannot crit edUptime = 1f - (float)Math.Pow(1 - chanceSpellCrit, 10 * couldCritSpellsPerS); averageMeleeCritChance = (chanceWhiteCritMH + chanceWhiteCritOH + chanceYellowCritMH + chanceYellowCritOH) / 4f + edUptime * edCritBonus; } float yellowAttacksPerSecond = hitsPerSWF + hitsPerSMHSS; if (_character.ShamanTalents.DualWield == 1 && unhastedMHSpeed != 0) { yellowAttacksPerSecond += hitsPerSOHSS; } // set output variables edBonusCrit = edUptime * edCritBonus; //SetCritValues((1 + _stats.BonusCritChance) * (baseMeleeCrit + meleeCritModifier) + edBonusCrit + .00005f); //fudge factor for rounding SetCritValues(baseMeleeCrit + meleeCritModifier + edBonusCrit + .00005f); //fudge factor for rounding meleeAttacksPerSec = hitsPerSMH + hitsPerSOH; meleeCritsPerSec = (whiteHitsPerSMH * chanceWhiteCritMH) + (whiteHitsPerSOH * chanceWhiteCritOH) + (yellowHitsPerSMH * chanceYellowCritMH) + (yellowHitsPerSOH * chanceYellowCritOH) + (_stats.MoteOfAnger * 2 * AverageWhiteCritChance); spellCritsPerSec = spellAttacksPerSec * ChanceSpellCrit; spellCastsPerSec = spellAttacksPerSec; spellMissesPerSec = spellAttacksPerSec * chanceSpellMiss; chanceMeleeHit = meleeAttacksPerSec / (swingsPerSMHMelee + swingsPerSOHMelee + 2f * wfProcsPerSecond + .25f + 1f / 6f); maxMana = _stats.Mana; float spellhaste = _stats.SpellHaste + StatConversion.GetSpellHasteFromRating(_stats.HasteRating); averageFSDotTime = 18f / (1f + spellhaste); }
public void Solve(Character character, ref CharacterCalculationsMoonkin calcs) { CalculationOptionsMoonkin calcOpts = character.CalculationOptions as CalculationOptionsMoonkin; DruidTalents talents = character.DruidTalents; procEffects = new List <ProcEffect>(); RecreateSpells(talents, ref calcs); cachedResults = new Dictionary <string, RotationData>(); float trinketDPS = 0.0f; float baseSpellPower = calcs.SpellPower; float baseHit = GetSpellHit(calcs); float baseCrit = calcs.SpellCrit; float baseHaste = calcs.SpellHaste; BuildProcList(calcs); float maxDamageDone = 0.0f; float maxBurstDamageDone = 0.0f; SpellRotation maxBurstRotation = rotations[0]; SpellRotation maxRotation = rotations[0]; float manaPool = GetEffectiveManaPool(character, calcOpts, calcs); // Do tree calculations: Calculate damage per cast. float treeDamage = (talents.ForceOfNature == 1) ? DoTreeCalcs(baseSpellPower, calcs.BasicStats.PhysicalHaste, calcs.BasicStats.ArmorPenetration, calcs.BasicStats.PhysicalCrit, calcOpts.TreantLifespan, character.DruidTalents.Brambles) : 0.0f; // Extend that to number of casts per fight. float treeCasts = (float)Math.Floor(calcs.FightLength / 3) + 1.0f; // Partial cast: If the fight lasts 3.x minutes and x is less than 0.5 (30 sec tree duration), calculate a partial cast if ((int)calcs.FightLength % 3 == 0 && calcs.FightLength - (int)calcs.FightLength < 0.5) { treeCasts += (calcs.FightLength - (int)calcs.FightLength) / 0.5f - 1.0f; } treeDamage *= treeCasts; // Multiply by raid-wide damage increases. treeDamage *= (1 + calcs.BasicStats.BonusDamageMultiplier) * (1 + calcs.BasicStats.BonusPhysicalDamageMultiplier); // Calculate the DPS averaged over the fight length. float treeDPS = treeDamage / (calcs.FightLength * 60.0f); // Calculate mana usage for trees. float treeManaUsage = (float)Math.Ceiling(treeCasts) * CalculationsMoonkin.BaseMana * 0.12f; manaPool -= talents.ForceOfNature == 1 ? treeManaUsage : 0.0f; // Do Starfall calculations. bool starfallGlyph = talents.GlyphOfStarfall; Buff tier102PieceBuff = character.ActiveBuffs.Find(theBuff => theBuff.Name == "Lasherweave Regalia (T10) 2 Piece Bonus"); float numberOfStarHits = 10.0f; float starfallDamage = (talents.Starfall == 1) ? DoStarfallCalcs(baseSpellPower, baseHit, baseCrit, (1 + calcs.BasicStats.BonusDamageMultiplier) * (1 + calcs.BasicStats.BonusSpellPowerMultiplier) * (1 + calcs.BasicStats.BonusArcaneDamageMultiplier) * (1 + (talents.GlyphOfFocus ? 0.1f : 0.0f)), Wrath.CriticalDamageModifier, out numberOfStarHits) : 0.0f; float starfallCD = 1.5f - (starfallGlyph ? 0.5f : 0.0f); float numStarfallCasts = (float)Math.Floor(calcs.FightLength / starfallCD) + 1.0f; // Partial cast: If the difference between fight length and total starfall CD time is less than 10 seconds (duration), // calculate a partial cast float starfallDiff = calcs.FightLength * 60.0f - (numStarfallCasts - 1) * starfallCD * 60.0f; if (starfallDiff > 0 && starfallDiff < 10) { numStarfallCasts += starfallDiff / 60.0f / (1.0f / 6.0f) - 1.0f; } starfallDamage *= numStarfallCasts; float starfallManaUsage = (float)Math.Ceiling(numStarfallCasts) * CalculationsMoonkin.BaseMana * 0.39f; manaPool -= talents.Starfall == 1 ? starfallManaUsage : 0.0f; // Simple faerie fire mana calc float faerieFireCasts = (float)Math.Floor(calcs.FightLength / 5) + (calcs.FightLength % 5 != 0 ? 1.0f : 0.0f); float faerieFireMana = faerieFireCasts * CalculationsMoonkin.BaseMana * 0.08f; if (talents.ImprovedFaerieFire > 0) { manaPool -= faerieFireMana; } // Calculate effect of casting Starfall/Treants/ImpFF (regular FF is assumed to be provided by a feral) float globalCooldown = 1.5f / (1 + baseHaste) + calcs.Latency; float treantTime = (talents.ForceOfNature == 1) ? globalCooldown * (float)Math.Ceiling(treeCasts) : 0.0f; float starfallTime = (talents.Starfall == 1) ? globalCooldown * (float)Math.Ceiling(numStarfallCasts) : 0.0f; float faerieFireTime = (talents.ImprovedFaerieFire > 0) ? globalCooldown * faerieFireCasts : 0.0f; float totalTimeInRotation = calcs.FightLength * 60.0f - (treantTime + starfallTime + faerieFireTime); float percentTimeInRotation = totalTimeInRotation / (calcs.FightLength * 60.0f); #if RAWR3 BossOptions bossOpts = character.BossOptions; if (bossOpts == null) { bossOpts = new BossOptions(); } float fearShare = (bossOpts.FearingTargsDur / 1000) / bossOpts.FearingTargsFreq; float stunShare = (bossOpts.StunningTargsDur / 1000) / bossOpts.StunningTargsFreq; float invulnerableShare = bossOpts.TimeBossIsInvuln / bossOpts.BerserkTimer; List <Attack> attacks = bossOpts.GetFilteredAttackList(ATTACK_TYPES.AT_AOE); attacks.AddRange(bossOpts.GetFilteredAttackList(ATTACK_TYPES.AT_RANGED)); int movementCount = attacks.Count; float assumedMovementDuration = 2.0f; // Assume 2 seconds per move float accumulatedDurations = 0.0f; foreach (Attack a in attacks) { accumulatedDurations += a.AttackSpeed; } float movementShare = (movementCount == 0 ? 0 : assumedMovementDuration / (accumulatedDurations / movementCount) / (1 + calcs.BasicStats.MovementSpeed)); percentTimeInRotation -= movementShare + fearShare + stunShare + invulnerableShare; #endif float manaGained = manaPool - calcs.BasicStats.Mana; float oldArcaneMultiplier = calcs.BasicStats.BonusArcaneDamageMultiplier; float oldNatureMultiplier = calcs.BasicStats.BonusNatureDamageMultiplier; foreach (SpellRotation rot in rotations) { rot.Solver = this; float accumulatedDamage = 0.0f; float totalUpTime = 0.0f; float[] spellDetails = new float[NUM_SPELL_DETAILS]; List <ProcEffect> activatedEffects = new List <ProcEffect>(); List <ProcEffect> alwaysUpEffects = new List <ProcEffect>(); // Pre-calculate rotational variables with base stats rot.DamageDone(talents, calcs, baseSpellPower, baseHit, baseCrit, baseHaste); // Reset variables modified in the pre-loop to base values float currentSpellPower = baseSpellPower; float currentCrit = baseCrit; float currentHaste = baseHaste; calcs.BasicStats.BonusArcaneDamageMultiplier = oldArcaneMultiplier; calcs.BasicStats.BonusNatureDamageMultiplier = oldNatureMultiplier; // Calculate spell power/spell damage modifying trinkets in a separate pre-loop foreach (ProcEffect proc in procEffects) { if (proc.Effect.Stats.SpellPower > 0 || proc.Effect.Stats.Spirit > 0) { float procSpellPower = proc.Effect.Stats.SpellPower; if (proc.Effect.Stats.Spirit > 0) { procSpellPower += proc.Effect.Stats.Spirit * (0.1f * talents.ImprovedMoonkinForm); } float triggerInterval = 0.0f, triggerChance = 1.0f; switch (proc.Effect.Trigger) { case Trigger.DamageDone: case Trigger.DamageOrHealingDone: triggerInterval = ((rot.Duration / rot.CastCount) + (rot.Duration / (rot.MoonfireTicks + rot.InsectSwarmTicks))) / 2.0f; break; case Trigger.Use: break; case Trigger.SpellHit: case Trigger.DamageSpellHit: triggerInterval = rot.Duration / rot.CastCount; triggerChance = GetSpellHit(calcs); break; case Trigger.SpellCrit: case Trigger.DamageSpellCrit: triggerInterval = rot.Duration / (rot.CastCount - rot.InsectSwarmCasts); triggerChance = baseCrit; break; case Trigger.SpellCast: case Trigger.DamageSpellCast: triggerInterval = rot.Duration / rot.CastCount; break; case Trigger.MoonfireCast: triggerInterval = rot.Duration / rot.MoonfireCasts; break; case Trigger.DoTTick: case Trigger.InsectSwarmOrMoonfireTick: triggerInterval = rot.Duration / (rot.InsectSwarmTicks + rot.MoonfireTicks); break; case Trigger.MoonfireTick: triggerInterval = rot.Duration / rot.MoonfireTicks; break; case Trigger.InsectSwarmTick: triggerInterval = rot.Duration / rot.InsectSwarmTicks; break; default: triggerChance = 0.0f; break; } if (triggerChance > 0) { currentSpellPower += (proc.Effect.MaxStack > 1 ? proc.Effect.GetAverageStackSize(triggerInterval, triggerChance, 3.0f, calcs.FightLength * 60.0f) : 1) * proc.Effect.GetAverageUptime(triggerInterval, triggerChance) * procSpellPower; } } // 2T10 (both if statements, which is why it isn't else-if) if (proc.Effect.Stats.BonusArcaneDamageMultiplier > 0) { calcs.BasicStats.BonusArcaneDamageMultiplier += proc.Effect.GetAverageUptime(rot.Duration / rot.CastCount, 1f) * proc.Effect.Stats.BonusArcaneDamageMultiplier; } if (proc.Effect.Stats.BonusNatureDamageMultiplier > 0) { calcs.BasicStats.BonusNatureDamageMultiplier += proc.Effect.GetAverageUptime(rot.Duration / rot.CastCount, 1f) * proc.Effect.Stats.BonusNatureDamageMultiplier; } if (proc.Effect.Stats._rawSpecialEffectDataSize > 0) { SpecialEffect childEffect = proc.Effect.Stats._rawSpecialEffectData[0]; // Nevermelting Ice Crystal if (childEffect.Stats.CritRating != 0) { float maxStack = proc.Effect.Stats.CritRating; float numNegativeStacks = childEffect.GetAverageStackSize(rot.Duration / (rot.CastCount - rot.InsectSwarmCasts), baseCrit, 3.0f, proc.Effect.Duration); float averageNegativeValue = childEffect.Stats.CritRating * numNegativeStacks; float averageCritRating = maxStack + averageNegativeValue; currentCrit += StatConversion.GetSpellCritFromRating(averageCritRating * proc.Effect.GetAverageUptime(0f, 1f)); } // Fetish of Volatile Power else if (childEffect.Stats.HasteRating != 0) { currentHaste += StatConversion.GetSpellHasteFromRating(childEffect.Stats.HasteRating * childEffect.GetAverageStackSize(rot.Duration / rot.CastCount, 1f, 3.0f, proc.Effect.Duration) * proc.Effect.GetAverageUptime(0f, 1f)); } } } // Calculate damage and mana contributions for non-stat-boosting trinkets // Separate stat-boosting proc trinkets into their own list foreach (ProcEffect proc in procEffects) { if (proc.CalculateDPS != null) { accumulatedDamage += proc.CalculateDPS(rot, calcs, currentSpellPower, baseHit, currentCrit, currentHaste) * rot.Duration; } else if (proc.Activate != null) { float upTime = proc.UpTime(rot, calcs); // Procs with 100% uptime should be activated and not put into the combination loop if (upTime == 1) { alwaysUpEffects.Add(proc); proc.Activate(character, calcs, ref currentSpellPower, ref baseHit, ref currentCrit, ref currentHaste); } // Procs with uptime 0 < x < 100 should be activated else if (upTime > 0) { activatedEffects.Add(proc); } } else if (proc.CalculateMP5 != null) { manaGained += proc.CalculateMP5(rot, calcs, currentSpellPower, baseHit, currentCrit, currentHaste) / 5.0f * calcs.FightLength * 60.0f; } } // Calculate stat-boosting trinkets, taking into effect interactions with other stat-boosting procs int sign = 1; Dictionary <int, float> cachedDamages = new Dictionary <int, float>(); Dictionary <int, float> cachedUptimes = new Dictionary <int, float>(); Dictionary <int, float[]> cachedDetails = new Dictionary <int, float[]>(); // Iterate over the entire set of trinket combinations (each trinket by itself, 2 at a time, ...) for (int i = 1; i <= activatedEffects.Count; ++i) { // Create a new combination generator for this "level" of trinket interaction CombinationGenerator gen = new CombinationGenerator(activatedEffects.Count, i); // Iterate over all combinations while (gen.HasNext()) { float tempUpTime = 1.0f; int[] vals = gen.GetNext(); int pairs = 0; int lengthCounter = 0; // Activate the trinkets, calculate the damage and uptime, then deactivate them foreach (int idx in vals) { pairs |= 1 << idx; ++lengthCounter; activatedEffects[idx].Activate(character, calcs, ref currentSpellPower, ref baseHit, ref currentCrit, ref currentHaste); } float tempDPS = rot.DamageDone(talents, calcs, currentSpellPower, baseHit, currentCrit, currentHaste) / rot.Duration; spellDetails[0] = Starfire.DamagePerHit; spellDetails[1] = Wrath.DamagePerHit; spellDetails[2] = Moonfire.DamagePerHit + Moonfire.DotEffect.DamagePerHit; spellDetails[3] = InsectSwarm.DotEffect.DamagePerHit; spellDetails[4] = Starfire.CastTime; spellDetails[5] = Wrath.CastTime; spellDetails[6] = Moonfire.CastTime; spellDetails[7] = InsectSwarm.CastTime; spellDetails[8] = Starfire.NGCastTime; spellDetails[9] = Wrath.NGCastTime; spellDetails[10] = Starfire.ManaCost; spellDetails[11] = Wrath.ManaCost; spellDetails[12] = Moonfire.ManaCost; spellDetails[13] = InsectSwarm.ManaCost; foreach (int idx in vals) { tempUpTime *= activatedEffects[idx].UpTime(rot, calcs); activatedEffects[idx].Deactivate(character, calcs, ref currentSpellPower, ref baseHit, ref currentCrit, ref currentHaste); } if (tempUpTime == 0) { continue; } // Adjust previous probability tables by the current factor // At the end of the algorithm, this ensures that the probability table will contain the individual // probabilities of each effect or set of effects. // These adjustments only need to be made for higher levels of the table, and if the current probability is > 0. if (lengthCounter > 1) { List <int> keys = new List <int>(cachedUptimes.Keys); foreach (int subset in keys) { // Truly a subset? if ((pairs & subset) != subset) { continue; } // Calculate the "layer" of the current subset by getting the set bit count. int subsetLength = 0; for (int j = subset; j > 0; ++subsetLength) { j &= --j; } // Set the sign of the operation: Evenly separated layers are added, oddly separated layers are subtracted int newSign = ((lengthCounter - subsetLength) % 2 == 0) ? 1 : -1; // Adjust by current uptime * sign of operation. cachedUptimes[subset] += newSign * tempUpTime; } } // Cache the results to be calculated later cachedUptimes[pairs] = tempUpTime; cachedDamages[pairs] = tempDPS; cachedDetails[pairs] = new float[NUM_SPELL_DETAILS]; Array.Copy(spellDetails, cachedDetails[pairs], NUM_SPELL_DETAILS); totalUpTime += sign * tempUpTime; } sign = -sign; } float accumulatedDPS = 0.0f; Array.Clear(spellDetails, 0, spellDetails.Length); // Apply the above-calculated probabilities to the previously stored damage calculations and add to the result. foreach (KeyValuePair <int, float> kvp in cachedUptimes) { if (kvp.Value == 0) { continue; } accumulatedDPS += kvp.Value * cachedDamages[kvp.Key]; for (int i = 0; i < NUM_SPELL_DETAILS; ++i) { spellDetails[i] += kvp.Value * cachedDetails[kvp.Key][i]; } } float damageDone = rot.DamageDone(talents, calcs, currentSpellPower, baseHit, currentCrit, currentHaste); accumulatedDPS += (1 - totalUpTime) * damageDone / rot.Duration; spellDetails[0] += (1 - totalUpTime) * Starfire.DamagePerHit; spellDetails[1] += (1 - totalUpTime) * Wrath.DamagePerHit; spellDetails[2] += (1 - totalUpTime) * Moonfire.DamagePerHit + Moonfire.DotEffect.DamagePerHit; spellDetails[3] += (1 - totalUpTime) * InsectSwarm.DotEffect.DamagePerHit; spellDetails[4] += (1 - totalUpTime) * Starfire.CastTime; spellDetails[5] += (1 - totalUpTime) * Wrath.CastTime; spellDetails[6] += (1 - totalUpTime) * Moonfire.CastTime; spellDetails[7] += (1 - totalUpTime) * InsectSwarm.CastTime; spellDetails[8] += (1 - totalUpTime) * Starfire.NGCastTime; spellDetails[9] += (1 - totalUpTime) * Wrath.NGCastTime; spellDetails[10] += (1 - totalUpTime) * Starfire.ManaCost; spellDetails[11] += (1 - totalUpTime) * Wrath.ManaCost; spellDetails[12] += (1 - totalUpTime) * Moonfire.ManaCost; spellDetails[13] += (1 - totalUpTime) * InsectSwarm.ManaCost; accumulatedDamage += accumulatedDPS * rot.Duration; float burstDPS = accumulatedDamage / rot.Duration * percentTimeInRotation; float sustainedDPS = burstDPS; float timeToOOM = (manaPool / rot.RotationData.ManaUsed) * rot.Duration; if (timeToOOM <= 0) { timeToOOM = calcs.FightLength * 60.0f; // Happens when ManaUsed is less than 0 } if (timeToOOM < calcs.FightLength * 60.0f) { rot.RotationData.TimeToOOM = new TimeSpan(0, (int)(timeToOOM / 60), (int)(timeToOOM % 60)); sustainedDPS = burstDPS * timeToOOM / (calcs.FightLength * 60.0f); } float t10StarfallDamage = starfallDamage; // Approximate the effect of the 2T10 set bonus if (tier102PieceBuff != null && character.DruidTalents.OmenOfClarity == 1) { Stats.SpecialEffectEnumerator enumerator = tier102PieceBuff.Stats.SpecialEffects(); enumerator.MoveNext(); SpecialEffect effect = enumerator.Current; float upTime = effect.GetAverageUptime(rot.Duration / rot.CastCount, 1f); t10StarfallDamage = upTime * (starfallDamage * (1 + effect.Stats.BonusArcaneDamageMultiplier)) + (1 - upTime) * starfallDamage; } float starfallDPS = t10StarfallDamage / (calcs.FightLength * 60.0f); burstDPS += trinketDPS + treeDPS + starfallDPS; sustainedDPS += trinketDPS + treeDPS + starfallDPS; rot.StarfallDamage = t10StarfallDamage / numStarfallCasts; rot.StarfallStars = numberOfStarHits; rot.RotationData.BurstDPS = burstDPS; rot.RotationData.DPS = sustainedDPS; rot.StarfireAvgHit = spellDetails[0]; rot.WrathAvgHit = spellDetails[1]; rot.MoonfireAvgHit = spellDetails[2]; rot.InsectSwarmAvgHit = spellDetails[3]; rot.StarfireAvgCast = spellDetails[4]; rot.WrathAvgCast = spellDetails[5]; rot.MoonfireCastTime = spellDetails[6]; rot.InsectSwarmCastTime = spellDetails[7]; rot.StarfireNGCastTime = spellDetails[8]; rot.WrathNGCastTime = spellDetails[9]; rot.StarfireManaCost = spellDetails[10]; rot.WrathManaCost = spellDetails[11]; rot.MoonfireManaCost = spellDetails[12]; rot.InsectSwarmManaCost = spellDetails[13]; // Update the sustained DPS rotation if any one of the following three cases is true: // 1) No user rotation is selected and sustained DPS is maximum // 2) A user rotation is selected, Eclipse is not present, and the user rotation matches the current rotation // 3) A user rotation is selected, Eclipse is present, and the user rotation's dot spells matches this rotation's if ((calcOpts.UserRotation == "None" && sustainedDPS > maxDamageDone) || (character.DruidTalents.Eclipse == 0 && calcOpts.UserRotation == rot.Name) || (character.DruidTalents.Eclipse > 0 && (calcOpts.UserRotation == rot.Name.Replace("Filler", "SF") || calcOpts.UserRotation == rot.Name.Replace("Filler", "W")))) { maxDamageDone = sustainedDPS; maxRotation = rot; } if (burstDPS > maxBurstDamageDone) { maxBurstDamageDone = burstDPS; maxBurstRotation = rot; } rot.ManaGained += manaGained / (calcs.FightLength * 60.0f) * rot.Duration; rot.RotationData.ManaGained += manaGained / (calcs.FightLength * 60.0f) * rot.Duration; if (rot.Name.Contains("Filler")) { cachedResults[rot.Name.Replace("Filler", "SF")] = rot.RotationData; cachedResults[rot.Name.Replace("Filler", "W")] = rot.RotationData; } else { cachedResults[rot.Name] = rot.RotationData; } // Deactivate always-up procs foreach (ProcEffect proc in alwaysUpEffects) { proc.Deactivate(character, calcs, ref currentSpellPower, ref baseHit, ref currentCrit, ref currentHaste); } } // Present the findings to the user. calcs.TreantDamage = treeDamage / treeCasts; calcs.StarfallMana = starfallManaUsage / numStarfallCasts; calcs.SelectedRotation = maxRotation; calcs.BurstDPSRotation = maxBurstRotation; calcs.SubPoints = new float[] { maxDamageDone, maxBurstDamageDone }; calcs.OverallPoints = calcs.SubPoints[0] + calcs.SubPoints[1]; calcs.Rotations = cachedResults; }
public override Dictionary <string, string> GetCharacterDisplayCalculationValues() { Dictionary <string, string> dictValues = new Dictionary <string, string>(); dictValues.Add("Health", BasicStats.Health.ToString("F0", CultureInfo.InvariantCulture)); dictValues.Add("Mana", BasicStats.Mana.ToString("F0", CultureInfo.InvariantCulture)); dictValues.Add("Attack Power", BasicStats.AttackPower.ToString("F0", CultureInfo.InvariantCulture)); dictValues.Add("Agility", BasicStats.Agility.ToString("F0", CultureInfo.InvariantCulture)); dictValues.Add("Strength", BasicStats.Strength.ToString("F0", CultureInfo.InvariantCulture)); dictValues.Add("Intellect", BasicStats.Intellect.ToString("F0", CultureInfo.InvariantCulture)); dictValues.Add("White Hit", WhiteHit.ToString("F2", CultureInfo.InvariantCulture) + "%"); if (YellowHit < 100f && TotalExpertiseMH < 26) { float ratingRequired = (float)Math.Ceiling(4f * StatConversion.GetRatingFromExpertise(100f - YellowHit)); dictValues.Add("Yellow Hit", String.Format("{0}% (Under Cap)*You need {1} more expertise to cap specials (WF,SS)", YellowHit.ToString("F2", CultureInfo.InvariantCulture), ratingRequired.ToString("F0", CultureInfo.InvariantCulture))); } else { if (ParriedAttacks > 0) { float ratingRequired = (float)Math.Ceiling(4f * StatConversion.GetRatingFromExpertise(100f - YellowHit)); dictValues.Add("Yellow Hit", String.Format("{0}%*Being in front of boss allows your attacks to be parried\r\nYou would need {1} more expertise to cap specials (WF,SS)", YellowHit.ToString("F2", CultureInfo.InvariantCulture), ratingRequired.ToString("F0", CultureInfo.InvariantCulture))); } else { dictValues.Add("Yellow Hit", YellowHit.ToString("F2", CultureInfo.InvariantCulture) + "%"); } } if (OverSpellHitCap > 0.38f) // only warn if more than .38% over cap (equivalent to 10 hit rating) { dictValues.Add("Spell Hit", String.Format("{0}% (Over Cap)*Over Spell Hit Cap by {1}%", SpellHit.ToString("F2", CultureInfo.InvariantCulture), OverSpellHitCap.ToString("F2", CultureInfo.InvariantCulture))); } else { if (SpellHit < 100f) { float ratingRequired = (float)Math.Ceiling(StatConversion.GetRatingFromHit(1f - SpellHit / 100f)); dictValues.Add("Spell Hit", String.Format("{0}% (Under Cap)*You need {1} more hit rating to cap spells (ES, LB etc)", SpellHit.ToString("F2", CultureInfo.InvariantCulture), ratingRequired.ToString("F0", CultureInfo.InvariantCulture))); } else { dictValues.Add("Spell Hit", SpellHit.ToString("F2", CultureInfo.InvariantCulture) + "%"); } } if (OverMeleeCritCap > 0.21f) // only warn if more than .21% over cap (equivalent to 10 crit rating) { dictValues.Add("Melee Crit", String.Format("{0} (Over Cap)*Crit Rating {1} (+{2}% crit chance)\r\nOver Soft Cap by {3}%", MeleeCrit.ToString("F2", CultureInfo.InvariantCulture) + "%", BasicStats.CritRating.ToString("F0", CultureInfo.InvariantCulture), (StatConversion.GetCritFromRating(BasicStats.CritRating) * 100f).ToString("F2", CultureInfo.InvariantCulture), OverMeleeCritCap.ToString("F2", CultureInfo.InvariantCulture))); } else { dictValues.Add("Melee Crit", String.Format("{0}*Crit Rating {1} (+{2}% crit chance)", MeleeCrit.ToString("F2", CultureInfo.InvariantCulture) + "%", BasicStats.CritRating.ToString("F0", CultureInfo.InvariantCulture), (StatConversion.GetCritFromRating(BasicStats.CritRating) * 100f).ToString("F2", CultureInfo.InvariantCulture))); } dictValues.Add("Spell Crit", String.Format("{0}*Crit Rating {1} (+{2}% crit chance)", SpellCrit.ToString("F2", CultureInfo.InvariantCulture) + "%", BasicStats.CritRating.ToString("F0", CultureInfo.InvariantCulture), (StatConversion.GetSpellCritFromRating(BasicStats.CritRating) * 100f).ToString("F2", CultureInfo.InvariantCulture))); dictValues.Add("Spellpower", BasicStats.SpellPower.ToString("F0", CultureInfo.InvariantCulture)); dictValues.Add("Total Expertise", getExpertiseString()); dictValues.Add("Haste Rating", String.Format("{0}*{1}% Melee Haste\r\n{2}% Spell Haste", BasicStats.HasteRating.ToString("F0", CultureInfo.InvariantCulture), (StatConversion.GetHasteFromRating(BasicStats.HasteRating, CharacterClass.Shaman) * 100f).ToString("F2", CultureInfo.InvariantCulture), (StatConversion.GetSpellHasteFromRating(BasicStats.HasteRating, CharacterClass.Shaman) * 100f).ToString("F2", CultureInfo.InvariantCulture))); dictValues.Add("Hit Rating", String.Format("{0}*{1}% Melee Hit\r\n{2}% Spell Hit", BasicStats.HitRating.ToString("F0", CultureInfo.InvariantCulture), (StatConversion.GetHitFromRating(BasicStats.HitRating) * 100f).ToString("F2", CultureInfo.InvariantCulture), (StatConversion.GetSpellHitFromRating(BasicStats.HitRating) * 100f).ToString("F2", CultureInfo.InvariantCulture))); dictValues.Add("Armour Pen Rating", String.Format("{0}*{1}% Armour Penetration", BasicStats.ArmorPenetrationRating.ToString("F0", CultureInfo.InvariantCulture), (StatConversion.GetArmorPenetrationFromRating(BasicStats.ArmorPenetrationRating) * 100f).ToString("F2", CultureInfo.InvariantCulture))); float spellMiss = 100 - SpellHit; dictValues.Add("Avoided Attacks", String.Format("{0}%*{1}% Boss Dodged\r\n{2}% Boss Parried\r\n{3}% Spell Misses\r\n{4}% White Misses", AvoidedAttacks.ToString("F2", CultureInfo.InvariantCulture), DodgedAttacks.ToString("F2", CultureInfo.InvariantCulture), ParriedAttacks.ToString("F2", CultureInfo.InvariantCulture), spellMiss.ToString("F2", CultureInfo.InvariantCulture), MissedAttacks.ToString("F2", CultureInfo.InvariantCulture))); dictValues.Add("Avg MH Speed", AvMHSpeed.ToString("F2", CultureInfo.InvariantCulture)); dictValues.Add("Avg OH Speed", AvOHSpeed.ToString("F2", CultureInfo.InvariantCulture)); dictValues.Add("Armor Mitigation", ArmorMitigation.ToString("F2", CultureInfo.InvariantCulture) + "%*Amount of physical damage lost due to boss armor"); dictValues.Add("ED Uptime", String.Format("{0}%*{1}% ED Bonus Crit", EDUptime.ToString("F2", CultureInfo.InvariantCulture), EDBonusCrit.ToString("F2", CultureInfo.InvariantCulture))); dictValues.Add("Flurry Uptime", FlurryUptime.ToString("F2", CultureInfo.InvariantCulture) + "%"); dictValues.Add("Avg Time to 5 Stack", String.Format("{0} sec*{1} PPM", SecondsTo5Stack.ToString("F2", CultureInfo.InvariantCulture), _MWPPM.ToString("F2", CultureInfo.InvariantCulture))); dictValues.Add("MH Enchant Uptime", MHEnchantUptime.ToString("F2", CultureInfo.InvariantCulture) + "%"); dictValues.Add("OH Enchant Uptime", OHEnchantUptime.ToString("F2", CultureInfo.InvariantCulture) + "%"); dictValues.Add("Trinket 1 Uptime", Trinket1Uptime.ToString("F2", CultureInfo.InvariantCulture) + "%"); dictValues.Add("Trinket 2 Uptime", Trinket2Uptime.ToString("F2", CultureInfo.InvariantCulture) + "%"); dictValues.Add("Fire Totem Uptime", FireTotemUptime.ToString("F2", CultureInfo.InvariantCulture) + "%"); dictValues.Add("Tier 10 2 pc Uptime", T10_2Uptime.ToString("F2", CultureInfo.InvariantCulture) + "%"); dictValues.Add("Tier 10 4 pc Uptime", T10_4Uptime.ToString("F2", CultureInfo.InvariantCulture) + "%"); dictValues.Add("DPS Points", DPSPoints.ToString("F2", CultureInfo.InvariantCulture)); dictValues.Add("Survivability Points", SurvivabilityPoints.ToString("F2", CultureInfo.InvariantCulture)); dictValues.Add("Overall Points", OverallPoints.ToString("F2", CultureInfo.InvariantCulture)); dictValues.Add("White Damage", dpsOutputFormat(SwingDamage, DPSPoints, true)); dictValues.Add("Windfury Attack", dpsOutputFormat(WindfuryAttack, DPSPoints, true)); dictValues.Add("Flametongue Attack", dpsOutputFormat(FlameTongueAttack, DPSPoints, false)); dictValues.Add("Lightning Bolt", dpsOutputFormat(LightningBolt, DPSPoints, false)); dictValues.Add("Earth Shock", dpsOutputFormat(EarthShock, DPSPoints, false)); dictValues.Add("Flame Shock", dpsOutputFormat(FlameShock, DPSPoints, false)); dictValues.Add("Searing/Magma Totem", dpsOutputFormat(SearingMagma, DPSPoints, false)); dictValues.Add("Stormstrike", dpsOutputFormat(Stormstrike, DPSPoints, true)); dictValues.Add("Spirit Wolf", dpsOutputFormat(SpiritWolf, DPSPoints, true)); dictValues.Add("Fire Nova", dpsOutputFormat(FireNova, DPSPoints, false)); dictValues.Add("Fire Elemental", FireElemental.getDPSOutput()); dictValues.Add("Lightning Shield", dpsOutputFormat(LightningShield, DPSPoints, false)); dictValues.Add("Lava Lash", dpsOutputFormat(LavaLash, DPSPoints, true)); dictValues.Add("Total DPS", DPSPoints.ToString("F2", CultureInfo.InvariantCulture)); dictValues.Add("Enhance Version", _version); dictValues.Add("Status", String.Format("Enhance Model : DPS Points {0}, Survivability Points {1}, Overall Points {2}", DPSPoints.ToString("F2", CultureInfo.InvariantCulture), SurvivabilityPoints.ToString("F2", CultureInfo.InvariantCulture), OverallPoints.ToString("F2", CultureInfo.InvariantCulture))); return(dictValues); }
public override Stats GetCharacterStats(Character character, Item additionalItem) { PriestTalents talents = character.PriestTalents; Stats statsTotal = new Stats(); Stats baseStats = BaseStats.GetBaseStats(character.Level, character.Class, character.Race); Stats itemStats = GetItemStats(character, additionalItem); Stats buffStats = GetBuffsStats(character, _calculationOptions); // Get the gear/enchants/buffs stats loaded in statsTotal.Accumulate(baseStats); statsTotal.Accumulate(itemStats); statsTotal.Accumulate(buffStats); Stats statsTalents = new Stats() { // we can only wear items that are cloth so we always have our specialization, even naked. BonusIntellectMultiplier = 0.05f, BonusShadowDamageMultiplier = (1 + 0.02f * talents.TwinDisciplines) * (1 + 0.02f * talents.TwistedFaith) * (1 + 0.15f * talents.Shadowform) - 1, BonusHolyDamageMultiplier = (1 + 0.02f * talents.TwinDisciplines) - 1, // this is the shadow priest model so they must have 'Shadow Power' BonusSpellPowerMultiplier = .15f, }; statsTotal.Accumulate(statsTalents); statsTotal.Stamina = (float)Math.Floor(statsTotal.Stamina * (1 + statsTotal.BonusStaminaMultiplier)); statsTotal.Intellect += (float)Math.Floor(itemStats.Intellect * statsTotal.BonusIntellectMultiplier); statsTotal.Spirit = (float)Math.Round(statsTotal.Spirit * (1 + statsTotal.BonusSpiritMultiplier)); statsTotal.Health += (float)Math.Floor(StatConversion.GetHealthFromStamina(statsTotal.Stamina) * (1f + statsTotal.BonusHealthMultiplier)); statsTotal.Mana = (float)Math.Round(statsTotal.Mana + StatConversion.GetManaFromIntellect(statsTotal.Intellect)); statsTotal.Mana = (float)Math.Round(statsTotal.Mana * (1f + statsTotal.BonusManaMultiplier)); statsTotal.SpellPower += statsTotal.Intellect - 10; float hasteFromRating = StatConversion.GetSpellHasteFromRating(statsTotal.HasteRating); float talentedHaste = (1 + hasteFromRating) * (1 + talents.Darkness * .01f) - 1; statsTotal.SpellHaste += character.Race == CharacterRace.Goblin ? talentedHaste * 1.01f : talentedHaste; float baseBonus = (float)Math.Floor(baseStats.Spirit * statsTotal.BonusSpiritMultiplier); float itemBonus = (float)Math.Floor(itemStats.Spirit * statsTotal.BonusSpiritMultiplier); float spiritFromItemsAndEffects = baseBonus + itemBonus + itemStats.Spirit; float hitRatingFromSpirit = (0.5f * talents.TwistedFaith) * Math.Max(0f, spiritFromItemsAndEffects); statsTotal.HitRating += hitRatingFromSpirit; statsTotal.SpellHit += StatConversion.GetSpellHitFromRating(statsTotal.HitRating); // ignoring the base crit percentage here as the in-game tooltip says that the int -> crit conversion contains the base. float critFromInt = StatConversion.GetSpellCritFromIntellect(statsTotal.Intellect) + 0.012375f; float critFromRating = StatConversion.GetSpellCritFromRating(statsTotal.CritRating); statsTotal.SpellCrit = character.Race == CharacterRace.Worgen ? (critFromInt + critFromRating) + .01f : (critFromInt + critFromRating); // Armor statsTotal.Armor = statsTotal.Armor * (1f + statsTotal.BaseArmorMultiplier); statsTotal.BonusArmor = statsTotal.BonusArmor * (1f + statsTotal.BonusArmorMultiplier); statsTotal.Armor += statsTotal.BonusArmor; statsTotal.Armor = (float)Math.Round(statsTotal.Armor); return(statsTotal); }
public override Dictionary <string, string> GetCharacterDisplayCalculationValues() { Dictionary <string, string> dictValues = new Dictionary <string, string>(); dictValues.Add("DPS Points", DPS.ToString("F2", CultureInfo.InvariantCulture)); dictValues.Add("Survivability Points", Survivability.ToString("F2", CultureInfo.InvariantCulture)); dictValues.Add("Overall Points", OverallPoints.ToString("F2", CultureInfo.InvariantCulture)); dictValues.Add("Health", BasicStats.Health.ToString("F0", CultureInfo.InvariantCulture)); dictValues.Add("Mana", BasicStats.Mana.ToString("F0", CultureInfo.InvariantCulture)); dictValues.Add("Strength", String.Format("{0}*Increases Attack Power by {1}", BasicStats.Strength.ToString("F0", CultureInfo.InvariantCulture), (BasicStats.Strength - 10f).ToString("F0", CultureInfo.InvariantCulture))); dictValues.Add("Agility", String.Format("{0}*Increases Attack Power by {1}\r\nIncreases Critical Hit chance by {2}%", BasicStats.Agility.ToString("F0", CultureInfo.InvariantCulture), ((BasicStats.Agility * 2f) - 20f).ToString("F0", CultureInfo.InvariantCulture), (StatConversion.GetCritFromAgility(BasicStats.Agility, CharacterClass.Shaman) * 100f).ToString("F2", CultureInfo.InvariantCulture))); dictValues.Add("Stamina", String.Format("{0}*Increase Health by {1}", BasicStats.Stamina.ToString("F0", CultureInfo.InvariantCulture), (StatConversion.GetHealthFromStamina(BasicStats.Stamina)).ToString("F0", CultureInfo.InvariantCulture))); dictValues.Add("Intellect", String.Format("{0}*Increases Mana by {1}\r\nIncreases Spell Power by {2}\r\nIncreases Spell Critical Hit chance by {3}%", BasicStats.Intellect.ToString("F0", CultureInfo.InvariantCulture), (StatConversion.GetManaFromIntellect(BasicStats.Intellect)).ToString("F0", CultureInfo.InvariantCulture), (BasicStats.Intellect - 10f).ToString("F0", CultureInfo.InvariantCulture), (StatConversion.GetSpellCritFromIntellect(BasicStats.Intellect, CharacterClass.Shaman) * 100f).ToString("F2", CultureInfo.InvariantCulture))); dictValues.Add("Spirit", String.Format("{0}*Increases mana regeneration by {1} every 5 seconds while not casting", BasicStats.Spirit.ToString("F0", CultureInfo.InvariantCulture), (StatConversion.GetSpiritRegenSec(BasicStats.Spirit, BasicStats.Intellect) * 5f).ToString("F0", CultureInfo.InvariantCulture))); dictValues.Add("Mastery", String.Format("{0}*Mastery rating of {1} adds {2} Mastery\r\nIncreases all Fire, Frost, and Nature Damage by {3}%.", (8f + StatConversion.GetMasteryFromRating(BasicStats.MasteryRating)).ToString("F2", CultureInfo.InvariantCulture), BasicStats.MasteryRating.ToString("F0", CultureInfo.InvariantCulture), (StatConversion.GetMasteryFromRating(BasicStats.MasteryRating)).ToString("F2", CultureInfo.InvariantCulture), ((8f + StatConversion.GetMasteryFromRating(BasicStats.MasteryRating)) * 2.5f).ToString("F2", CultureInfo.InvariantCulture))); //dictValues.Add("Damage" //dictValues.Add("DPS" dictValues.Add("Attack Power", BasicStats.AttackPower.ToString("F0", CultureInfo.InvariantCulture)); //dictValues.Add("Speed" dictValues.Add("Melee Haste", String.Format("{0}%*Haste Rating of {1} adds {2}% Haste", (StatConversion.GetHasteFromRating(BasicStats.HasteRating, CharacterClass.Shaman) * 100f).ToString("F2", CultureInfo.InvariantCulture), BasicStats.HasteRating.ToString("F0", CultureInfo.InvariantCulture), (StatConversion.GetHasteFromRating(BasicStats.HasteRating, CharacterClass.Shaman) * 100f).ToString("F2", CultureInfo.InvariantCulture))); dictValues.Add("Melee Hit", String.Format("{0}%*Hit Rating of {1} adds {2}% Hit chance\r\n{3}% Draenei Hit Bonus", (StatConversion.GetHitFromRating(BasicStats.HitRating) * 100f + DraeneiHitBonus * 100f).ToString("F2", CultureInfo.InvariantCulture), BasicStats.HitRating.ToString("F0", CultureInfo.InvariantCulture), (StatConversion.GetHitFromRating(BasicStats.HitRating) * 100f).ToString("F2", CultureInfo.InvariantCulture), DraeneiHitBonus * 100f)); dictValues.Add("Melee Crit", String.Format("{0}%*Crit Rating of {1} adds {2}% Crit chance", ((StatConversion.GetCritFromRating(BasicStats.CritRating, CharacterClass.Shaman) * 100f) + (StatConversion.GetCritFromAgility(BasicStats.Agility, CharacterClass.Shaman) * 100f)).ToString("F2", CultureInfo.InvariantCulture), BasicStats.CritRating.ToString("F0", CultureInfo.InvariantCulture), (StatConversion.GetCritFromRating(BasicStats.CritRating, CharacterClass.Shaman) * 100f).ToString("F2", CultureInfo.InvariantCulture))); dictValues.Add("Expertise", getExpertiseString());// String.Format("{0} / {1}*Reduces chance to be dodged or parried by {2}% / {3}%\r\nExpertise Rating of {4} adds {5} Expertise", dictValues.Add("Spell Power", BasicStats.SpellPower.ToString("F0", CultureInfo.InvariantCulture)); dictValues.Add("Spell Haste", String.Format("{0}%*Haste Rating of {1} adds {2}% Haste", (StatConversion.GetSpellHasteFromRating(BasicStats.HasteRating, CharacterClass.Shaman) * 100f).ToString("F2", CultureInfo.InvariantCulture), BasicStats.HasteRating.ToString("F0", CultureInfo.InvariantCulture), (StatConversion.GetSpellHasteFromRating(BasicStats.HasteRating, CharacterClass.Shaman) * 100f).ToString("F2", CultureInfo.InvariantCulture))); dictValues.Add("Spell Hit", String.Format("{0}%*Hit Rating of {1} adds {2}% Hit chance\r\n{3}% Draenei Hit Bonus", (StatConversion.GetSpellHitFromRating(BasicStats.HitRating + ElemPrecMod) * 100f + DraeneiHitBonus * 100f).ToString("F2", CultureInfo.InvariantCulture), (BasicStats.HitRating + ElemPrecMod).ToString("F0", CultureInfo.InvariantCulture), (StatConversion.GetSpellHitFromRating(BasicStats.HitRating + ElemPrecMod) * 100f).ToString("F2", CultureInfo.InvariantCulture), DraeneiHitBonus * 100f)); dictValues.Add("Spell Crit", String.Format("{0}%*Crit Rating of {1} adds {2}% Crit chance", ((StatConversion.GetSpellCritFromRating(BasicStats.CritRating, CharacterClass.Shaman) * 100f) + (StatConversion.GetSpellCritFromIntellect(BasicStats.Intellect, CharacterClass.Shaman) * 100f)).ToString("F2", CultureInfo.InvariantCulture), BasicStats.CritRating.ToString("F0", CultureInfo.InvariantCulture), (StatConversion.GetSpellCritFromRating(BasicStats.CritRating, CharacterClass.Shaman) * 100f).ToString("F2", CultureInfo.InvariantCulture))); dictValues.Add("Combat Regen", String.Format("{0}*{0} mana regenerated every 5 seconds while in combat", BaseRegen.ToString("F0", CultureInfo.InvariantCulture))); //dictValues.Add("Avg Agility", _attackPower.ToString("F0", CultureInfo.InvariantCulture)); //dictValues.Add("Avg Intellect" //dictValues.Add("Avg Mastery" //dictValues.Add("Avg Attack Power" dictValues.Add("Avg Speed", String.Format("{0} / {1}", AvMHSpeed.ToString("F2", CultureInfo.InvariantCulture), AvOHSpeed.ToString("F2", CultureInfo.InvariantCulture))); //dictValues.Add("Avg Melee Haste" //dictValues.Add("Avg Melee Hit" //dictValues.Add("Avg Melee Crit" //dictValues.Add("Avg Expertise" //dictValues.Add("Avg Spell Power" //dictValues.Add("Avg Spell Haste" //dictValues.Add("Avg Spell Hit" //dictValues.Add("Avg Spell Crit" dictValues.Add("Avg Combat Regen", ManaRegen.ToString("F0", CultureInfo.InvariantCulture)); /*dictValues.Add("White Hit", WhiteHit.ToString("F2", CultureInfo.InvariantCulture) + "%"); * if (YellowHit < 100f && TotalExpertiseMH < 26) * { * float ratingRequired = (float)Math.Ceiling(4f * StatConversion.GetRatingFromExpertise(100f - YellowHit)); * dictValues.Add("Yellow Hit", String.Format("{0}% (Under Cap)*You need {1} more expertise to cap specials (WF,SS)", * YellowHit.ToString("F2", CultureInfo.InvariantCulture), * ratingRequired.ToString("F0", CultureInfo.InvariantCulture))); * } * else * { * if (ParriedAttacks > 0) * { * float ratingRequired = (float)Math.Ceiling(4f * StatConversion.GetRatingFromExpertise(100f - YellowHit)); * dictValues.Add("Yellow Hit", String.Format("{0}%*Being in front of boss allows your attacks to be parried\r\nYou would need {1} more expertise to cap specials (WF,SS)", * YellowHit.ToString("F2", CultureInfo.InvariantCulture), * ratingRequired.ToString("F0", CultureInfo.InvariantCulture))); * } * else * dictValues.Add("Yellow Hit", YellowHit.ToString("F2", CultureInfo.InvariantCulture) + "%"); * } * if (OverSpellHitCap > 0.38f) // only warn if more than .38% over cap (equivalent to 10 hit rating) * dictValues.Add("Spell Hit", String.Format("{0}% (Over Cap)*Over Spell Hit Cap by {1}%", * SpellHit.ToString("F2", CultureInfo.InvariantCulture), * OverSpellHitCap.ToString("F2", CultureInfo.InvariantCulture))); * else * { * if (SpellHit < 100f) * { * float ratingRequired = (float)Math.Ceiling(StatConversion.GetRatingFromSpellHit(1f - SpellHit/100f)); * dictValues.Add("Spell Hit", String.Format("{0}% (Under Cap)*You need {1} more hit rating to cap spells (ES, LB etc)", * SpellHit.ToString("F2", CultureInfo.InvariantCulture), * ratingRequired.ToString("F0", CultureInfo.InvariantCulture))); * } * else * dictValues.Add("Spell Hit", SpellHit.ToString("F2", CultureInfo.InvariantCulture) + "%"); * }*/ /*if (OverMeleeCritCap > 0.21f) // only warn if more than .21% over cap (equivalent to 10 crit rating) * dictValues.Add("Melee Crit", String.Format("{0} (Over Cap)*Crit Rating {1} (+{2}% crit chance)\r\nOver Soft Cap by {3}%", * MeleeCrit.ToString("F2", CultureInfo.InvariantCulture) + "%", * BasicStats.CritRating.ToString("F0", CultureInfo.InvariantCulture), * (StatConversion.GetCritFromRating(BasicStats.CritRating) * 100f).ToString("F2", CultureInfo.InvariantCulture), * OverMeleeCritCap.ToString("F2", CultureInfo.InvariantCulture))); * else * dictValues.Add("Melee Crit", String.Format("{0}*Crit Rating {1} (+{2}% crit chance)", * MeleeCrit.ToString("F2", CultureInfo.InvariantCulture) + "%", * BasicStats.CritRating.ToString("F0", CultureInfo.InvariantCulture), * (StatConversion.GetCritFromRating(BasicStats.CritRating) * 100f).ToString("F2", CultureInfo.InvariantCulture))); * * dictValues.Add("Spell Crit", String.Format("{0}*Crit Rating {1} (+{2}% crit chance)", * SpellCrit.ToString("F2", CultureInfo.InvariantCulture) + "%", * BasicStats.CritRating.ToString("F0", CultureInfo.InvariantCulture), * (StatConversion.GetSpellCritFromRating(BasicStats.CritRating) * 100f).ToString("F2", CultureInfo.InvariantCulture)));*/ float spellMiss = 100 - SpellHit; dictValues.Add("Avoided Attacks", String.Format("{0}%*{1}% Boss Dodged\r\n{2}% Boss Parried\r\n{3}% Spell Misses\r\n{4}% White Misses", AvoidedAttacks.ToString("F2", CultureInfo.InvariantCulture), DodgedAttacks.ToString("F2", CultureInfo.InvariantCulture), ParriedAttacks.ToString("F2", CultureInfo.InvariantCulture), spellMiss.ToString("F2", CultureInfo.InvariantCulture), MissedAttacks.ToString("F2", CultureInfo.InvariantCulture))); dictValues.Add("Armor Mitigation", ArmorMitigation.ToString("F2", CultureInfo.InvariantCulture) + "%*Amount of physical damage lost due to boss armor"); dictValues.Add("ED Uptime", String.Format("{0}%*{1}% ED Bonus Crit", EDUptime.ToString("F2", CultureInfo.InvariantCulture), EDBonusCrit.ToString("F2", CultureInfo.InvariantCulture))); dictValues.Add("Flurry Uptime", FlurryUptime.ToString("F2", CultureInfo.InvariantCulture) + "%"); dictValues.Add("Avg Time to 5 Stack", String.Format("{0} sec*{1} PPM", SecondsTo5Stack.ToString("F2", CultureInfo.InvariantCulture), _MWPPM.ToString("F2", CultureInfo.InvariantCulture))); dictValues.Add("MH Enchant Uptime", MHEnchantUptime.ToString("F2", CultureInfo.InvariantCulture) + "%"); dictValues.Add("OH Enchant Uptime", OHEnchantUptime.ToString("F2", CultureInfo.InvariantCulture) + "%"); dictValues.Add("Trinket 1 Uptime", Trinket1Uptime.ToString("F2", CultureInfo.InvariantCulture) + "%"); dictValues.Add("Trinket 2 Uptime", Trinket2Uptime.ToString("F2", CultureInfo.InvariantCulture) + "%"); dictValues.Add("Fire Totem Uptime", FireTotemUptime.ToString("F2", CultureInfo.InvariantCulture) + "%"); dictValues.Add("White Damage", dpsOutputFormat(SwingDamage, DPS, true)); dictValues.Add("Windfury Attack", dpsOutputFormat(WindfuryAttack, DPS, true)); dictValues.Add("Flametongue Attack", dpsOutputFormat(FlameTongueAttack, DPS, true)); dictValues.Add("Stormstrike", dpsOutputFormat(Stormstrike, DPS, true)); dictValues.Add("Lava Lash", dpsOutputFormat(LavaLash, DPS, true)); dictValues.Add("Searing/Magma Totem", dpsOutputFormat(SearingMagma, DPS, false)); dictValues.Add("Earth Shock", dpsOutputFormat(EarthShock, DPS, false)); dictValues.Add("Flame Shock", dpsOutputFormat(FlameShock, DPS, false)); dictValues.Add("Lightning Bolt", dpsOutputFormat(LightningBolt, DPS, false)); dictValues.Add("Unleash Wind", dpsOutputFormat(UnleashWind, DPS, true)); dictValues.Add("Unleash Flame", dpsOutputFormat(UnleashFlame, DPS, false)); dictValues.Add("Lightning Shield", dpsOutputFormat(LightningShield, DPS, false)); dictValues.Add("Chain Lightning", dpsOutputFormat(ChainLightning, DPS, false)); dictValues.Add("Fire Nova", dpsOutputFormat(FireNova, DPS, false)); dictValues.Add("Fire Elemental", FireElemental.getDPSOutput()); dictValues.Add("Spirit Wolf", dpsOutputFormat(SpiritWolf, DPS, true)); dictValues.Add("Other", dpsOutputFormat(Other, DPS, false)); dictValues.Add("Total DPS", DPS.ToString("F2", CultureInfo.InvariantCulture)); /*dictValues.Add("Status", String.Format("Enhance Model : DPS Points {0}, Survivability Points {1}, Overall Points {2}", * DPS.ToString("F2", CultureInfo.InvariantCulture), * Survivability.ToString("F2", CultureInfo.InvariantCulture), * OverallPoints.ToString("F2", CultureInfo.InvariantCulture)));*/ return(dictValues); }
public override Dictionary <string, string> GetCharacterDisplayCalculationValues() { Dictionary <string, string> dictValues = new Dictionary <string, string>(); CalculationOptionsShadowPriest calcOpts = character.CalculationOptions as CalculationOptionsShadowPriest; BossOptions BossOpts = character.BossOptions; Stats baseStats = BaseStats.GetBaseStats(character.Level, character.Class, character.Race); bool Ptr = calcOpts.PTR; dictValues.Add("Health", BasicStats.Health.ToString()); float ResilienceCap = 0.15f, ResilienceFromRating = StatConversion.GetCritReductionFromResilience(1); float Resilience = StatConversion.GetCritReductionFromResilience(BasicStats.Resilience); dictValues.Add("Resilience", string.Format("{0}*-{1}% Damage from DoT and Mana Drains\n\r-{1}% Chance to be crit\r\n-{2}% Damage from Crits.\r\n{3}", BasicStats.Resilience.ToString(), (Resilience * 100f).ToString("0.00"), (Resilience * 100f * 2.2f).ToString("0.00"), (Resilience > ResilienceCap) ? (string.Format("{0} rating above cap", ((float)Math.Floor((Resilience - ResilienceCap) / ResilienceFromRating)).ToString("0"))) : (string.Format("{0} rating below cap", ((float)Math.Ceiling((ResilienceCap - Resilience) / ResilienceFromRating)).ToString("0"))))); dictValues.Add("Stamina", BasicStats.Stamina.ToString()); dictValues.Add("Mana", BasicStats.Mana.ToString()); dictValues.Add("Intellect", BasicStats.Intellect.ToString()); dictValues.Add("Spirit", Math.Floor(BasicStats.Spirit).ToString("0")); dictValues.Add("Spell Power", String.Format("{0}*{1} Bonus Shadow\r\n{2} Bonus Holy\r\n{3} from Inner Fire", Math.Floor(BasicStats.SpellPower), Math.Floor(BasicStats.SpellPower + BasicStats.SpellShadowDamageRating), Math.Floor(BasicStats.SpellPower /*+ BasicStats.SpellHolyDamageRating*/), BasicStats.PriestInnerFire * CalculationsShadowPriest.GetInnerFireSpellPowerBonus(character))); dictValues.Add("Regen", String.Format("{0}*MP5: {1}\r\nOutFSR: {2}", RegenInFSR.ToString("0"), BasicStats.Mp5.ToString(), RegenOutFSR.ToString("0"))); dictValues.Add("Crit", string.Format("{0}%*{1}% from {2} Spell Crit rating\r\n{3}% from Intellect\r\n{4}% from Focused Will\r\n{5}% from Base Crit\r\n{6}% from Buffs\r\n{7}% on Mind Blast, Mind Flay and Mind Sear.\r\n{8}% on VT, SW:P and DP\r\n{9}% on Smite, Holy Fire and Penance.", (BasicStats.SpellCrit * 100f).ToString("0.00"), (StatConversion.GetSpellCritFromRating(BasicStats.CritRating) * 100f).ToString("0.00"), BasicStats.CritRating.ToString("0"), (StatConversion.GetSpellCritFromIntellect(BasicStats.Intellect) * 100f).ToString("0.00"), character.PriestTalents.FocusedWill, (baseStats.SpellCrit * 100f).ToString("0.00"), (BasicStats.SpellCrit * 100f - StatConversion.GetSpellCritFromRating(BasicStats.CritRating) * 100f - StatConversion.GetSpellCritFromIntellect(BasicStats.Intellect) * 100f - baseStats.SpellCrit * 100f - character.PriestTalents.FocusedWill).ToString("0.00"), (BasicStats.SpellCrit * 100f + character.PriestTalents.MindMelt * 2f).ToString("0.00"), (BasicStats.SpellCrit * 100f + character.PriestTalents.MindMelt * 3f).ToString("0.00"), (BasicStats.SpellCrit * 100f + character.PriestTalents.HolySpecialization * 1f).ToString("0.00"))); #if RAWR3 || SILVERLIGHT float Hit = 100 - (StatConversion.GetSpellMiss(character.Level - BossOpts.Level, false) * 100); #else float Hit = 100 - (StatConversion.GetSpellMiss(-calcOpts.TargetLevel, false) * 100); #endif float BonusHit = BasicStats.SpellHit * 100f; float RacialHit = 0; string RacialText = ""; if (character.Race == CharacterRace.Draenei) { RacialHit = 1; RacialText = "1% from Draenei Racial\r\n"; if (!character.ActiveBuffsContains("Heroic Presence")) { BonusHit += 1; } } float DebuffHit = character.PriestTalents.Misery * 1f; if (character.ActiveBuffsConflictingBuffContains("Spell Hit Chance Taken")) { DebuffHit = 3f; } else { BonusHit += DebuffHit; } float RHitRating = 0.01f / StatConversion.GetSpellHitFromRating(1); float ShadowFocusHit = character.PriestTalents.ShadowFocus * 1f; float HitShadow = Hit + BonusHit + ShadowFocusHit; float HitHoly = Hit + BonusHit; dictValues.Add("Hit", string.Format("{0}%*{1}% from {2} Hit Rating\r\n{3}% from Buffs\r\n{4}% from {5} points in Misery\r\n{6}% from {7} points in Shadow Focus\r\n{8}{9}% Hit with Shadow spells, {10}\r\n{11}% Hit with Holy spells, {12}", BonusHit.ToString("0.00"), (StatConversion.GetSpellHitFromRating(BasicStats.HitRating) * 100f).ToString("0.00"), BasicStats.HitRating, (BonusHit - StatConversion.GetSpellHitFromRating(BasicStats.HitRating) * 100f - RacialHit - DebuffHit).ToString("0.00"), DebuffHit, character.PriestTalents.Misery, ShadowFocusHit, character.PriestTalents.ShadowFocus, RacialText, HitShadow.ToString("0.00"), (HitShadow > 100f) ? string.Format("{0} hit rating above cap", Math.Floor((HitShadow - 100f) * RHitRating)) : string.Format("{0} hit rating below cap", Math.Ceiling((100f - HitShadow) * RHitRating)), HitHoly.ToString("0.00"), (HitHoly > 100f) ? string.Format("{0} hit rating above cap", Math.Floor((HitHoly - 100f) * RHitRating)) : string.Format("{0} hit rating below cap", Math.Ceiling((100f - HitHoly) * RHitRating)))); dictValues.Add("Haste", string.Format("{0}%*{1}% from {2} Haste rating\r\n{3}% ({6}) points in Enlightenment\r\n{4}% from Buffs\r\n{5}s Global Cooldown", (BasicStats.SpellHaste * 100f).ToString("0.00"), (StatConversion.GetSpellHasteFromRating(BasicStats.HasteRating) * 100f).ToString("0.00"), BasicStats.HasteRating.ToString(), (character.PriestTalents.Enlightenment * 2).ToString("0"), (((1f + BasicStats.SpellHaste) / (1f + StatConversion.GetSpellHasteFromRating(BasicStats.HasteRating)) / (1f + character.PriestTalents.Enlightenment * 0.02f) - 1f) * 100f).ToString("0.00"), Math.Max(1.0f, 1.5f / (1 + BasicStats.SpellHaste)).ToString("0.00"), character.PriestTalents.Enlightenment)); dictValues.Add("Armor", string.Format("{0}*{1}% Damage Reduction.", (BasicStats.Armor + BasicStats.BonusArmor).ToString("0"), (StatConversion.GetArmorDamageReduction(80, (BasicStats.Armor + BasicStats.BonusArmor), 0f, 0f, 0f) * 100f).ToString("0.00"))); float[] Resistances = { 0, BasicStats.ArcaneResistance + BasicStats.ArcaneResistanceBuff, BasicStats.FireResistance + BasicStats.FireResistanceBuff, BasicStats.FrostResistance + BasicStats.FrostResistanceBuff, BasicStats.NatureResistance + BasicStats.NatureResistanceBuff, BasicStats.ShadowResistance + BasicStats.ShadowResistanceBuff, }; string[] ResistanceNames = { "None", "Arcane", "Fire", "Frost", "Nature", "Shadow", }; string ResistanceString = "*Resistances:"; float MaxResist = Resistances[0]; int MaxResistIndex = 0; float AvgResist = 0f; for (int x = 1; x < Resistances.Length; x++) { AvgResist += Resistances[x]; if (Resistances[x] > MaxResist) { MaxResist = Resistances[x]; MaxResistIndex = x; } ResistanceString += string.Format("\r\n{0} : {1}", ResistanceNames[x], Resistances[x]); } AvgResist /= (Resistances.Length - 1); if (AvgResist == 0) { ResistanceString = "None" + ResistanceString; } else { string ResistanceName = (MaxResist == AvgResist) ? "All" : ResistanceNames[MaxResistIndex]; ResistanceString = string.Format("{0} : {1}", ResistanceName, MaxResist.ToString("0")) + ResistanceString; ResistanceString += string.Format("\r\n\r\nResist ({0}):", ResistanceName); ResistanceString += string.Format("\r\n{0}", StatConversion.GetResistanceTableString(character.Level + 3, character.Level, MaxResist, 0)); } dictValues.Add("Resistance", ResistanceString); SolverBase solver = GetSolver(character, BasicStats); solver.Calculate(this); dictValues.Add("Rotation", string.Format("{0}*{1}", solver.Name, solver.Rotation)); if (solver.SpellSimulation != null) { String s = "Spell Cast List:"; int i = 0; foreach (Spell spell in solver.SpellSimulation) { if (i++ % 10 == 0) { s += "\r\n"; } s += ", " + spell.Name; } s += "\r\n---Repeat---"; dictValues.Add("Castlist", string.Format("{0}*{1}", solver.SpellSimulation.Count, s)); } else { dictValues.Add("Castlist", "Empty"); } dictValues.Add("DPS", string.Format("{0}*Damage Pr Second", solver.DPS.ToString("0"))); //dictValues.Add("SustainDPS", string.Format("{0}*Mana restrained DPS", solver.SustainDPS.ToString("0"))); float baseMana = BaseStats.GetBaseStats(character).Mana; dictValues.Add("SW Pain", new ShadowWordPain(BasicStats, character, baseMana, Ptr).ToString()); DevouringPlague dp = new DevouringPlague(BasicStats, character, baseMana, Ptr); dictValues.Add("Devouring Plague", dp.ToString()); if (dp.ImprovedDP != null) { dictValues.Add("Imp. Devouring Plague", dp.ImprovedDP.ToString()); } else { dictValues.Add("Imp. Devouring Plague", "- *No required talents"); } dictValues.Add("SW Death", new ShadowWordDeath(BasicStats, character, baseMana, Ptr).ToString()); dictValues.Add("Mind Blast", new MindBlast(BasicStats, character, baseMana, Ptr).ToString()); dictValues.Add("PW Shield", new PowerWordShield(BasicStats, character, baseMana, Ptr).ToString()); if (character.PriestTalents.VampiricTouch > 0) { dictValues.Add("Vampiric Touch", new VampiricTouch(BasicStats, character, baseMana, Ptr).ToString()); } else { dictValues.Add("Vampiric Touch", "- *No required talents"); } if (character.PriestTalents.MindFlay > 0) { dictValues.Add("Mind Flay", new MindFlay(BasicStats, character, baseMana, Ptr).ToString()); } else { dictValues.Add("Mind Flay", "- *No required talents"); } dictValues.Add("Shadowfiend", new Shadowfiend(BasicStats, character, baseMana, Ptr).ToString()); dictValues.Add("Smite", new Smite(BasicStats, character, baseMana, Ptr).ToString()); dictValues.Add("Holy Fire", new HolyFire(BasicStats, character, baseMana, Ptr).ToString()); if (character.PriestTalents.Penance > 0) { dictValues.Add("Penance", new Penance(BasicStats, character, baseMana, Ptr).ToString()); } else { dictValues.Add("Penance", "- *No required talents"); } return(dictValues); }