/// <summary> /// Get Character Stats for multiple calls. Allowing means by which to stack different sets/Special effects. /// </summary> /// <param name="character"></param> /// <param name="additionalItem"></param> /// <param name="sType">Enum describing which set of stats we want.</param> /// <returns></returns> private StatsDK GetCharacterStats(Character character, Item additionalItem, StatType sType, TankDKChar TDK, Rotation rot = null) { StatsDK statsTotal = new StatsDK(); if (null == character.CalculationOptions) { // Possibly put some error text here. return statsTotal; } // Warning TDK can be blank at this point. TDK.Char = character; TDK.calcOpts = character.CalculationOptions as CalculationOptionsTankDK; TDK.bo = character.BossOptions; // Start populating data w/ Basic racial & class baseline. Stats BStats = BaseStats.GetBaseStats(character); statsTotal.Accumulate(BStats); statsTotal.BaseAgility = BStats.Agility; AccumulateItemStats(statsTotal, character, additionalItem); // Stack only the info we care about. statsTotal = GetRelevantStatsLocal(statsTotal); AccumulateBuffsStats(statsTotal, character.ActiveBuffs); AccumulateSetBonusStats(statsTotal, character.SetBonusCount); #region Tier Bonuses: Tank #region T11 int tierCount; if (character.SetBonusCount.TryGetValue("Magma Plated Battlearmor", out tierCount)) { if (tierCount >= 2) { statsTotal.b2T11_Tank = true; } if (tierCount >= 4) { statsTotal.b4T11_Tank = true; } } if (statsTotal.b4T11_Tank) statsTotal.AddSpecialEffect(_SE_IBF[1]); else statsTotal.AddSpecialEffect(_SE_IBF[0]); #endregion #region T12 if (character.SetBonusCount.TryGetValue("Elementium Deathplate Battlearmor", out tierCount)) { if (tierCount >= 2) { statsTotal.b2T12_Tank = true; } if (tierCount >= 4) { statsTotal.b4T12_Tank = true; } } if (statsTotal.b2T12_Tank) { // Your melee attacks cause Burning Blood on your target, // which deals 800 Fire damage every 2 for 6 sec and // causes your abilities to behave as if you had 2 diseases // present on the target. // Implemented in CombatState DiseaseCount statsTotal.FireDamage = 800 / 2; } if (statsTotal.b4T12_Tank) { // Your when your Dancing Rune Weapon expires, it grants 15% additional parry chance for 12 sec. // Implemented in DRW talent Static Special Effect. } #endregion #region T13 if (character.SetBonusCount.TryGetValue("Necrotic Boneplate Armor", out tierCount)) { if (tierCount >= 2) { statsTotal.b2T13_Tank = true; } if (tierCount >= 4) { statsTotal.b4T13_Tank = true; } } if (statsTotal.b2T13_Tank) { // When an attack drops your health below 35%, one of your Blood Runes // will immediately activate and convert into a Death Rune for the next // 20 sec. This effect cannot occur more than once every 45 sec. } if (statsTotal.b4T13_Tank) { // Your Vampiric Blood ability also affects all party and raid members // for 50% of the effect it has on you. } #endregion #endregion Rawr.DPSDK.CalculationsDPSDK.RemoveDuplicateRunes(statsTotal, character, true/*statsTotal.bDW*/); Rawr.DPSDK.CalculationsDPSDK.AccumulateTalents(statsTotal, character); Rawr.DPSDK.CalculationsDPSDK.AccumulatePresenceStats(statsTotal, Presence.Blood, character.DeathKnightTalents); statsTotal.ArcaneResistance += statsTotal.ArcaneResistanceBuff; statsTotal.ArcaneResistanceBuff = 0f; statsTotal.FireResistance += statsTotal.FireResistanceBuff; statsTotal.FireResistanceBuff = 0f; statsTotal.FrostResistance += statsTotal.FrostResistanceBuff; statsTotal.FrostResistanceBuff = 0f; statsTotal.NatureResistance += statsTotal.NatureResistanceBuff; statsTotal.NatureResistanceBuff = 0f; statsTotal.ShadowResistance += statsTotal.ShadowResistanceBuff; statsTotal.ShadowResistanceBuff = 0f; /* At this point, we're combined all the data from gear and talents and all that happy jazz. * However, we haven't applied any special effects nor have we applied any multipliers. * Many special effects are now getting dependant upon combat info (rotations). */ StatsDK PreRatingsBase = statsTotal.Clone() as StatsDK; // Apply the ratings to actual stats. ProcessRatings(statsTotal); ProcessAvoidance(statsTotal, TDK.bo.Level, TDK.Char, PreRatingsBase); statsTotal.EffectiveParry = 0; if (character.MainHand != null) { statsTotal.EffectiveParry = statsTotal.Parry; } float fChanceToGetHit = 1f - Math.Min(1f, statsTotal.Miss + statsTotal.Dodge + statsTotal.EffectiveParry); // Now comes the special handling for secondary stats passes that are dependant upon Boss & Rotation values. if (sType != StatType.Unbuffed && (null != TDK.bo && null != rot)) // Make sure we have the rotation and Boss info. { #region Special Effects #region Talent: Bone Shield if (character.DeathKnightTalents.BoneShield > 0) { int BSStacks = 4; // The number of bones by default. if (Rawr.Properties.GeneralSettings.Default.PTRMode) BSStacks = 6; // The number of bones by default. float BoneLossRate = Math.Max(2f, TDK.bo.DynamicCompiler_Attacks.AttackSpeed / fChanceToGetHit); // 2 sec internal cooldown on loosing bones so the DK can't get spammed to death. float moveVal = character.DeathKnightTalents.GlyphofBoneShield ? 0.15f : 0f; SpecialEffect primary = new SpecialEffect(Trigger.Use, new Stats() { DamageTakenReductionMultiplier = 0.20f, BonusDamageMultiplier = 0.02f, MovementSpeed = moveVal, }, BoneLossRate * BSStacks, 60) {BypassCache = true,}; statsTotal.AddSpecialEffect(primary); } #endregion #region Vengeance // Vengence has the chance to increase AP. int iVengenceMax = (int)(statsTotal.Stamina + (BaseStats.GetBaseStats(character).Health) * .1); int iAttackPowerMax = (int)statsTotal.AttackPower + iVengenceMax; float mitigatedDPS = TDK.bo.GetDPSByType(TDK.role, 0, statsTotal.DamageTakenReductionMultiplier, 0, .14f, statsTotal.Miss, statsTotal.Dodge, statsTotal.EffectiveParry, 0, 0, 0, 0, 0, 0, 0); //statsTotal.ArcaneResistance, statsTotal.FireResistance, statsTotal.FrostResistance, statsTotal.NatureResistance, statsTotal.ShadowResistance); mitigatedDPS = mitigatedDPS * (1 - (float)StatConversion.GetArmorDamageReduction(TDK.bo.Level, statsTotal.Armor, 0f, 0f)); float APStackSingle = mitigatedDPS * 0.05f * TDK.bo.DynamicCompiler_Attacks.AttackSpeed; int APStackCountMax = (int)Math.Floor(iVengenceMax / APStackSingle); SpecialEffect seVeng = new SpecialEffect(Trigger.DamageTaken, new Stats() { AttackPower = APStackSingle }, 2 * 10, 0, 1, APStackCountMax) { BypassCache = true, }; Dictionary<Trigger, float> triggerInterval = new Dictionary<Trigger,float>(); Dictionary<Trigger, float> triggerChance = new Dictionary<Trigger,float>(); triggerInterval.Add(Trigger.DamageTaken, TDK.bo.DynamicCompiler_Attacks.AttackSpeed); triggerChance.Add(Trigger.DamageTaken, 1f); // MitigatedDPS already factors in avoidance. statsTotal.VengenceAttackPower = seVeng.GetAverageStats(triggerInterval, triggerChance).AttackPower; statsTotal.AttackPower += statsTotal.VengenceAttackPower * TDK.calcOpts.VengeanceWeight; #endregion statsTotal.AddSpecialEffect(_SE_DeathPact); // For now we just factor them in once. Rawr.DPSDK.StatsSpecialEffects se = new Rawr.DPSDK.StatsSpecialEffects(rot.m_CT, rot, TDK.bo); StatsDK statSE = new StatsDK(); foreach (SpecialEffect effect in statsTotal.SpecialEffects()) { if (HasRelevantStats(effect.Stats)) { statSE.Accumulate(se.getSpecialEffects(effect)); // statsTotal.Accumulate(se.getSpecialEffects(effect)); // This is done further down. } } // Darkmoon card greatness procs if (statSE.HighestStat > 0 || statSE.Paragon > 0) { if (statSE.Strength >= statSE.Agility) { statSE.Strength += statSE.HighestStat + statSE.Paragon; } else if (statSE.Agility > statSE.Strength) { statSE.Agility += statSE.HighestStat + statSE.Paragon; } statSE.HighestStat = 0; statSE.Paragon = 0; } // Any Modifiers from stats need to be applied to statSE statSE.Strength = StatConversion.ApplyMultiplier(statSE.Strength, statsTotal.BonusStrengthMultiplier); statSE.Agility = StatConversion.ApplyMultiplier(statSE.Agility, statsTotal.BonusAgilityMultiplier); statSE.Stamina = StatConversion.ApplyMultiplier(statSE.Stamina, statsTotal.BonusStaminaMultiplier); // statSE.Stamina = (float)Math.Floor(statSE.Stamina); statSE.Armor = StatConversion.ApplyMultiplier(statSE.Armor, statsTotal.BaseArmorMultiplier); statSE.AttackPower = StatConversion.ApplyMultiplier(statSE.AttackPower, statsTotal.BonusAttackPowerMultiplier); statSE.BonusArmor = StatConversion.ApplyMultiplier(statSE.BonusArmor, statsTotal.BonusArmorMultiplier); statSE.Armor += statSE.BonusArmor; statSE.Health += StatConversion.GetHealthFromStamina(statSE.Stamina) + statSE.BattlemasterHealthProc; statSE.Health = statSE.Health * (1 + statSE.BonusHealthMultiplier); statsTotal.BonusHealthMultiplier = ((1 + statsTotal.BonusHealthMultiplier) * (1 + statSE.BonusHealthMultiplier)) - 1 ; if (character.DeathKnightTalents.BladedArmor > 0) { statSE.AttackPower += (statSE.Armor / 180f) * (float)character.DeathKnightTalents.BladedArmor; } statSE.AttackPower += StatConversion.ApplyMultiplier((statSE.Strength * 2), statsTotal.BonusAttackPowerMultiplier); statSE.ParryRating += statSE.Strength * 0.27f; // Any Modifiers from statSE need to be applied to stats statsTotal.Strength = StatConversion.ApplyMultiplier(statsTotal.Strength, statSE.BonusStrengthMultiplier); statsTotal.Agility = StatConversion.ApplyMultiplier(statsTotal.Agility, statSE.BonusAgilityMultiplier); statsTotal.Stamina = StatConversion.ApplyMultiplier(statsTotal.Stamina, statSE.BonusStaminaMultiplier); // stats.Stamina = (float)Math.Floor(stats.Stamina); statsTotal.Armor = StatConversion.ApplyMultiplier(statsTotal.Armor, statSE.BaseArmorMultiplier); statsTotal.AttackPower = StatConversion.ApplyMultiplier(statsTotal.AttackPower, statSE.BonusAttackPowerMultiplier); statsTotal.BonusArmor = StatConversion.ApplyMultiplier(statsTotal.BonusArmor, statSE.BonusArmorMultiplier); statsTotal.Accumulate(statSE); PreRatingsBase.Miss += statSE.Miss; PreRatingsBase.Dodge += statSE.Dodge; PreRatingsBase.Parry += statSE.Parry; #if DEBUG if (float.IsNaN(statsTotal.Stamina)) throw new Exception("Something very wrong in stats."); #endif #endregion // Special effects } // Apply the Multipliers ProcessStatModifiers(statsTotal, character.DeathKnightTalents.BladedArmor, character); ProcessAvoidance(statsTotal, TDK.bo.Level, TDK.Char, PreRatingsBase); if (character.MainHand != null) { statsTotal.EffectiveParry = statsTotal.Parry; } return (statsTotal); }
/// <summary> /// GetCharacterCalculations is the primary method of each model, where a majority of the calculations /// and formulae will be used. GetCharacterCalculations should call GetCharacterStats(), and based on /// those total stats for the character, and any calculationoptions on the character, perform all the /// calculations required to come up with the final calculations defined in /// CharacterDisplayCalculationLabels, including an Overall rating, and all Sub ratings defined in /// SubPointNameColors. /// </summary> /// <param name="character">The character to perform calculations for.</param> /// <param name="additionalItem">An additional item to treat the character as wearing. /// This is used for gems, which don't have a slot on the character to fit in, so are just /// added onto the character, in order to get gem calculations.</param> /// <returns>A custom CharacterCalculations object which inherits from CharacterCalculationsBase, /// containing all of the final calculations defined in CharacterDisplayCalculationLabels. See /// CharacterCalculationsBase comments for more details.</returns> public override CharacterCalculationsBase GetCharacterCalculations(Character character, Item additionalItem, bool referenceCalculation, bool significantChange, bool needsDisplayCalculations) { // First things first, we need to ensure that we aren't using bad data CharacterCalculationsDPSDK calc = new CharacterCalculationsDPSDK(); if (character == null) { return calc; } CalculationOptionsDPSDK calcOpts = character.CalculationOptions as CalculationOptionsDPSDK; if (calcOpts == null) { return calc; } // StatsDK stats = new StatsDK(); DeathKnightTalents talents = character.DeathKnightTalents; // Setup initial Boss data. // Get Boss from BossOptions data. BossOptions hBossOptions = character.BossOptions; if (hBossOptions == null) hBossOptions = new BossOptions(); int targetLevel = hBossOptions.Level; stats = GetCharacterStats(character, additionalItem) as StatsDK; calc.BasicStats = stats.Clone() as StatsDK; ApplyRatings(calc.BasicStats); DKCombatTable combatTable = new DKCombatTable(character, calc.BasicStats, calc, calcOpts, hBossOptions); if (needsDisplayCalculations) combatTable.PostAbilitiesSingleUse(false); Rotation rot = new Rotation(combatTable); Rotation.Type RotT = rot.GetRotationType(character.DeathKnightTalents); // TODO: Fix this so we're not using pre-set rotations/priorities. if (RotT == Rotation.Type.Frost) rot.PRE_Frost(); else if (RotT == Rotation.Type.Unholy) rot.PRE_Unholy(); else if (RotT == Rotation.Type.Blood) rot.PRE_BloodDiseased(); else rot.Solver(); //TODO: This may need to be handled special since it's to update stats. AccumulateSpecialEffectStats(stats, character, calcOpts, combatTable, rot); // Now add in the special effects. ApplyRatings(stats); #region Cinderglacier if (stats.CinderglacierProc > 0) { // How many frost & shadow abilities do we have per min.? float CGabs = ((rot.m_FrostSpecials + rot.m_ShadowSpecials) / rot.CurRotationDuration) * 60f; float effCG = 0; if (CGabs > 0) // Since 3 of those abilities get the 20% buff // Get the effective ammount of CinderGlacier that would be applied across each ability. // it is a proc after all. effCG = 3 / CGabs; stats.BonusFrostDamageMultiplier += (.2f * effCG); stats.BonusShadowDamageMultiplier += (.2f * effCG); } #endregion // refresh w/ updated stats. combatTable = new DKCombatTable(character, stats, calc, calcOpts, hBossOptions); combatTable.PostAbilitiesSingleUse(false); rot = new Rotation(combatTable); RotT = rot.GetRotationType(character.DeathKnightTalents); // TODO: Fix this so we're not using pre-set rotations. if (RotT == Rotation.Type.Frost) rot.PRE_Frost(); else if (RotT == Rotation.Type.Unholy) rot.PRE_Unholy(); else if (RotT == Rotation.Type.Blood) rot.PRE_BloodDiseased(); else rot.Solver(); #region Pet Handling // For UH, this is valid. For Frost/Blood, we need to have this be 1/3 of the value since it has an uptime of 1 min for every 3. float ghouluptime = 1f; calc.dpsSub[(int)DKability.Gargoyle] = 0; if (RotT != Rotation.Type.Unholy) ghouluptime = 1f / 3f; else { // Unholy will also have gargoyles. Pet Gar = new Gargoyle(stats, talents, hBossOptions, calcOpts.presence); float garuptime = .5f/3f; calc.dpsSub[(int)DKability.Gargoyle] = Gar.DPS * garuptime; calc.damSub[(int)DKability.Gargoyle] = Gar.DPS * 30f; // Duration 30 seconds. } Pet ghoul = new Ghoul(stats, talents, hBossOptions, calcOpts.presence); calc.dpsSub[(int)DKability.Ghoul] = ghoul.DPS * ghouluptime; calc.damSub[(int)DKability.Ghoul] = ghoul.DPS * 60f; // Duration 1 min. #endregion // Stats as Fire damage additive value proc. if (stats.ArcaneDamage > 1) calc.dpsSub[(int)DKability.OtherArcane] += stats.ArcaneDamage; if (stats.FireDamage > 1) calc.dpsSub[(int)DKability.OtherFire] += stats.FireDamage; if (stats.FrostDamage > 1) calc.dpsSub[(int)DKability.OtherFrost] += stats.FrostDamage; if (stats.HolyDamage > 1) calc.dpsSub[(int)DKability.OtherHoly] += stats.HolyDamage; if (stats.NatureDamage > 1) calc.dpsSub[(int)DKability.OtherNature] += stats.NatureDamage; if (stats.ShadowDamage > 1) calc.dpsSub[(int)DKability.OtherShadow] += stats.ShadowDamage; // Fire Dam Multiplier. calc.RotationTime = rot.CurRotationDuration; calc.Blood = rot.m_BloodRunes; calc.Frost = rot.m_FrostRunes; calc.Unholy = rot.m_UnholyRunes; calc.Death = rot.m_DeathRunes; calc.RP = rot.m_RunicPower; calc.FreeRERunes = rot.m_FreeRunesFromRE; calc.EffectiveArmor = stats.Armor; calc.OverallPoints = calc.DPSPoints = rot.m_DPS // Add in supplemental damage from other sources + calc.dpsSub[(int)DKability.Ghoul] + calc.dpsSub[(int)DKability.Gargoyle] + calc.dpsSub[(int)DKability.OtherArcane] + calc.dpsSub[(int)DKability.OtherFire] + calc.dpsSub[(int)DKability.OtherFrost] + calc.dpsSub[(int)DKability.OtherHoly] + calc.dpsSub[(int)DKability.OtherNature] + calc.dpsSub[(int)DKability.OtherShadow]; if (needsDisplayCalculations) { AbilityDK_Base a = rot.GetAbilityOfType(DKability.White); if (rot.ml_Rot.Count > 1) { AbilityDK_Base b; b = rot.GetAbilityOfType(DKability.ScourgeStrike); if (b == null) b = rot.GetAbilityOfType(DKability.FrostStrike); if (b == null) b = rot.GetAbilityOfType(DKability.DeathStrike); calc.YellowHitChance = b.HitChance; } calc.WhiteHitChance = (a == null ? 0 : a.HitChance + a.CritChance + .23f); // + glancing calc.MHWeaponDPS = (a == null ? 0 : rot.GetAbilityOfType(DKability.White).DPS); if (null != combatTable.MH) { calc.MHWeaponDamage = combatTable.MH.damage; calc.MHAttackSpeed = combatTable.MH.hastedSpeed; calc.DodgedAttacks = combatTable.MH.chanceDodged; calc.AvoidedAttacks = combatTable.MH.chanceDodged; if (!hBossOptions.InBack) calc.AvoidedAttacks += combatTable.MH.chanceParried; calc.MissedAttacks = combatTable.MH.chanceMissed; } if (null != combatTable.OH) { a = rot.GetAbilityOfType(DKability.WhiteOH); calc.OHWeaponDPS = (a == null ? 0 : rot.GetAbilityOfType(DKability.WhiteOH).DPS); calc.OHWeaponDamage = combatTable.OH.damage; calc.OHAttackSpeed = combatTable.OH.hastedSpeed; } calcOpts.szRotReport = rot.ReportRotation(); calc.m_RuneCD = (float)rot.m_SingleRuneCD / 1000; calc.DPSBreakdown(rot); } return calc; }