private float[] GetMitigation(ref TankDKChar TDK, StatsDK stats, Rotation rot, float fPercentCritMitigation, float ArmorDamageReduction, float[] fCurrentDTPS, float fMagicDR) { return GetMitigation(ref TDK, stats, rot, fPercentCritMitigation, ArmorDamageReduction, fCurrentDTPS, fMagicDR, true); }
private float[] GetMitigation(ref TankDKChar TDK, StatsDK stats, Rotation rot, float fPercentCritMitigation, float ArmorDamageReduction, float[] fCurrentDTPS, float fMagicDR, bool bFactorInAvoidance) { float[] fTotalMitigation = new float[EnumHelper.GetCount(typeof(MitigationSub))]; // Ensure the CurrentDTPS structure is the right size. if (fCurrentDTPS.Length < EnumHelper.GetCount(typeof(SurvivalSub))) { fCurrentDTPS = new float[EnumHelper.GetCount(typeof(SurvivalSub))]; } float fSegmentMitigation = 0f; float fSegmentDPS = 0f; float fPhyDamageDPS = fCurrentDTPS[(int)SurvivalSub.Physical]; float fMagicDamageDPS = fCurrentDTPS[(int)SurvivalSub.Magic]; float fBleedDamageDPS = fCurrentDTPS[(int)SurvivalSub.Bleed]; #region *** Dam Avoided (Crit, Haste, Avoidance) *** #region ** Crit Mitigation ** // Crit mitigation: // Crit mitigation work for Physical damage only. float fCritMultiplier = .12f; // Bleeds can't crit. // Neither can spells from bosses. (As per a Loading screen ToolTip.) float fCritDPS = fPhyDamageDPS * fCritMultiplier; fSegmentMitigation = (fCritDPS * fPercentCritMitigation); // Add in the value of crit mitigation. fTotalMitigation[(int)MitigationSub.Crit] = fSegmentMitigation; // The max damage at this point needs to include crit. fCurrentDTPS[(int)SurvivalSub.Physical] = fCurrentDTPS[(int)SurvivalSub.Physical] + (fCritDPS - fSegmentMitigation); #endregion #region ** Haste Mitigation ** // Placeholder for comparing differing DPS values related to haste. float fNewIncPhysDPS = 0; // Let's just look at Imp Icy Touch #region Improved Icy Touch // Get the new slowed AttackSpeed based on ImpIcyTouch // Factor in the base slow caused by FF (14% base). float fBossAttackSpeedReduction = 0.0f; if (rot.Contains(DKability.IcyTouch) || rot.Contains(DKability.FrostFever)) { fBossAttackSpeedReduction = 0.2f; } else if (stats.BossAttackSpeedReductionMultiplier > 0) // FF provided by someone else. { fBossAttackSpeedReduction = stats.BossAttackSpeedReductionMultiplier; } // Figure out what the new Physical DPS should be based on that. fSegmentDPS = TDK.bo.GetDPSByType(ATTACK_TYPES.AT_MELEE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); fNewIncPhysDPS = fSegmentDPS / (1 + fBossAttackSpeedReduction); // Send the difference to the Mitigation value. fSegmentMitigation = fSegmentDPS - fNewIncPhysDPS; fTotalMitigation[(int)MitigationSub.Haste] += fSegmentMitigation; fCurrentDTPS[(int)SurvivalSub.Physical] -= fSegmentMitigation; #endregion #endregion #region ** Avoidance Mitigation ** // Let's see how much damage was avoided. // Raise the total mitgation by that amount. if (bFactorInAvoidance) { fSegmentDPS = fNewIncPhysDPS; fNewIncPhysDPS = TDK.bo.GetDPSByType(ATTACK_TYPES.AT_MELEE, 0, 0, 0, fBossAttackSpeedReduction, stats.Miss, stats.Dodge, stats.EffectiveParry, 0, 0, 0, 0, 0, 0, 0); fSegmentMitigation = fSegmentDPS - fNewIncPhysDPS; fTotalMitigation[(int)MitigationSub.Avoidance] += fSegmentMitigation; fCurrentDTPS[(int)SurvivalSub.Physical] -= fSegmentMitigation; } #endregion #endregion #region *** Dam Reduced (AMS, Armor, Magic Resist, DamageTaken Modifiers) *** #region ** Anti-Magic Shell ** // TODO: This is a CD, so would only be in BURST. // Anti-Magic Shell. //////////////////////////////////////////////////////// // Talent: MagicSuppression increases AMS by 8/16/25% per point. // Glyph: GlyphofAntiMagicShell increases AMS by 2 sec. // AMS has a 45 sec CD. float amsDuration = (5f + (TDK.Char.DeathKnightTalents.GlyphofAntiMagicShell == true ? 2f : 0f)) * (1 + stats.DefensiveCooldownDurationMultiplier); float amsUptimePct = amsDuration / 45f; // AMS reduces damage taken by 75% up to a max of 50% health. float amsReduction = 0.75f * (1f + (TDK.Char.DeathKnightTalents.MagicSuppression * .25f / 3)); float amsReductionMax = stats.Health * 0.5f * (1 + stats.DefensiveCooldownReductionMultiplier); // up to 50% of health means that the amdDRvalue equates to the raw damage points removed. // This means that toon health and INC damage values from the options pane are going to affect this quite a bit. float amsDRvalue = (Math.Min(amsReductionMax, (fMagicDamageDPS * amsDuration) * amsReduction) * amsUptimePct); // Raise the TotalMitigation by that amount. fCurrentDTPS[(int)SurvivalSub.Magic] -= amsDRvalue; fTotalMitigation[(int)MitigationSub.AMS] += amsDRvalue; #endregion #region ** Armor Dam Mitigation ** // For any physical only damage reductions. // Factor in armor Dam Reduction fSegmentMitigation = fPhyDamageDPS * ArmorDamageReduction; // calcs.ArmorMitigation = fSegmentMitigation; fTotalMitigation[(int)MitigationSub.Armor] += fSegmentMitigation; fCurrentDTPS[(int)SurvivalSub.Physical] *= 1f - ArmorDamageReduction; #endregion #region ** Resistance Dam Mitigation ** // For any physical only damage reductions. // Factor in armor Dam Reduction fSegmentMitigation = fMagicDamageDPS * fMagicDR; fTotalMitigation[(int)MitigationSub.Magic] += fSegmentMitigation; fCurrentDTPS[(int)SurvivalSub.Magic] -= fCurrentDTPS[(int)SurvivalSub.Magic] * fMagicDR; #endregion #region ** Dam Taken Mitigation ** fTotalMitigation[(int)MitigationSub.DamageReduction] += fMagicDamageDPS * (1f - stats.DamageTakenReductionMultiplier) * (1f - stats.SpellDamageTakenReductionMultiplier ); fTotalMitigation[(int)MitigationSub.DamageReduction] += fBleedDamageDPS * (1f - stats.DamageTakenReductionMultiplier) * (1f - stats.PhysicalDamageTakenReductionMultiplier); fTotalMitigation[(int)MitigationSub.DamageReduction] += fPhyDamageDPS * (1f - stats.DamageTakenReductionMultiplier) * (1f - stats.PhysicalDamageTakenReductionMultiplier); fCurrentDTPS[(int)SurvivalSub.Magic] *= (1f - stats.DamageTakenReductionMultiplier) * (1f - stats.SpellDamageTakenReductionMultiplier ); fCurrentDTPS[(int)SurvivalSub.Bleed] *= (1f - stats.DamageTakenReductionMultiplier) * (1f - stats.PhysicalDamageTakenReductionMultiplier); fCurrentDTPS[(int)SurvivalSub.Physical] *= (1f - stats.DamageTakenReductionMultiplier) * (1f - stats.PhysicalDamageTakenReductionMultiplier); #endregion #region ** Dam Absorbed ** fTotalMitigation[(int)MitigationSub.DamageReduction] += stats.DamageAbsorbed; // fCurrentDTPS[(int)SurvivalSub.Magic] *= (1f - stats.DamageTakenReductionMultiplier) * (1f - stats.SpellDamageTakenReductionMultiplier); // fCurrentDTPS[(int)SurvivalSub.Bleed] *= (1f - stats.DamageTakenReductionMultiplier) * (1f - stats.PhysicalDamageTakenReductionMultiplier); // fCurrentDTPS[(int)SurvivalSub.Physical] *= (1f - stats.DamageTakenReductionMultiplier) * (1f - stats.PhysicalDamageTakenReductionMultiplier); #endregion #endregion #region ** Boss Handler Mitigation (Impedences) ** float TotalDuration = 0; float ImprovedDuration = 0; float ImpedenceMitigation = 0; #region ** Fear Based Mitigation (fear reduction) ** if (TDK.bo.Fears.Count > 0) { foreach (Impedance m in TDK.bo.Fears) { TotalDuration += m.Duration; } ImprovedDuration = TotalDuration / (1 + stats.FearDurReduc); fSegmentMitigation = (TotalDuration - ImprovedDuration) /* Add some multiplier to this */; ImpedenceMitigation += fSegmentMitigation; } #endregion #region ** Movement Based Mitigation (run speed) ** if (TDK.bo.Moves.Count > 0) { TotalDuration = 0; foreach (Impedance m in TDK.bo.Moves) { TotalDuration += m.Duration; } ImprovedDuration = TotalDuration / (1 + stats.MovementSpeed); fSegmentMitigation = (TotalDuration - ImprovedDuration) /* Add some multiplier to this */; ImpedenceMitigation += fSegmentMitigation; } #endregion #region ** Disarm Based Mitigation (Disarm reduction) ** if (TDK.bo.Disarms.Count > 0) { TotalDuration = 0; foreach (Impedance m in TDK.bo.Disarms) { TotalDuration += m.Duration; } ImprovedDuration = TotalDuration / (1 + stats.DisarmDurReduc); fSegmentMitigation = (TotalDuration - ImprovedDuration) /* Add some multiplier to this */; ImpedenceMitigation += fSegmentMitigation; } #endregion #region ** Stun Based Mitigation (stun reduction) ** if (TDK.bo.Stuns.Count > 0) { TotalDuration = 0; foreach (Impedance m in TDK.bo.Stuns) { TotalDuration += m.Duration; } ImprovedDuration = TotalDuration / (1 + stats.StunDurReduc); fSegmentMitigation = (TotalDuration - ImprovedDuration) /* Add some multiplier to this */; ImpedenceMitigation += fSegmentMitigation; } #endregion // calcs.ImpedenceMitigation = ImpedenceMitigation; fTotalMitigation[(int)MitigationSub.Impedences] += ImpedenceMitigation; #endregion #region ** Heals not from DS ** fSegmentMitigation = StatConversion.ApplyMultiplier(stats.Healed, stats.HealingReceivedMultiplier); fSegmentMitigation += (StatConversion.ApplyMultiplier(stats.Hp5, stats.HealingReceivedMultiplier) / 5); fSegmentMitigation += StatConversion.ApplyMultiplier(stats.HealthRestore, stats.HealingReceivedMultiplier); // Health Returned by other sources than DS: if (stats.HealthRestoreFromMaxHealth > 0) fSegmentMitigation += (stats.HealthRestoreFromMaxHealth * stats.Health); fTotalMitigation[(int)MitigationSub.Heals] = fSegmentMitigation; #endregion return fTotalMitigation; }
private CharacterCalculationsTankDK GetCharacterCalculations(TankDKChar TDK, StatsDK stats, Rotation rot, bool isBurstCalc, bool needsDisplayCalcs) { CharacterCalculationsTankDK calcs = new CharacterCalculationsTankDK(); // Level differences. int iLevelDiff = Math.Max(TDK.bo.Level - TDK.Char.Level, 0); float fChanceToGetHit = 1f - Math.Min(1f, stats.Miss + stats.Dodge + stats.EffectiveParry); float ArmorDamageReduction = (float)StatConversion.GetArmorDamageReduction(TDK.bo.Level, stats.Armor, 0f, 0f); #region **** Setup Fight parameters **** // Get the values of each type of damage in %. // So first we get each type of damage in the same units: DPS. // Get the total DPS. float[] fCurrentDTPS = new float[3]; fCurrentDTPS[(int)SurvivalSub.Physical] = 0f; fCurrentDTPS[(int)SurvivalSub.Bleed] = 0f; fCurrentDTPS[(int)SurvivalSub.Magic] = 0f; float[] fCurrentDmgBiggestHit = new float[3]; fCurrentDmgBiggestHit[(int)SurvivalSub.Physical] = 0f; fCurrentDmgBiggestHit[(int)SurvivalSub.Bleed] = 0f; fCurrentDmgBiggestHit[(int)SurvivalSub.Magic] = 0f; float[] fCurrentDTPSPerc = new float[3]; fCurrentDTPSPerc[(int)SurvivalSub.Physical] = 1f; fCurrentDTPSPerc[(int)SurvivalSub.Bleed] = 0f; fCurrentDTPSPerc[(int)SurvivalSub.Magic] = 0f; float fTotalDTPS = 0; float fAvoidanceTotal = 1f - fChanceToGetHit; // We want to start getting the Boss Handler stuff going on. // Setup initial Boss data. // How much of what kind of damage does this boss deal with? #region ** Incoming Boss Dam ** // Let's make sure this is even valid float DPHit = 0; float DPTick = 0; switch (TDK.calcOpts.PlayerRole) { case 0: TDK.role = PLAYER_ROLES.MainTank; break; case 1: TDK.role = PLAYER_ROLES.OffTank; break; case 2: TDK.role = PLAYER_ROLES.TertiaryTank; break; default: TDK.role = PLAYER_ROLES.MainTank; break; } TDK.role = PLAYER_ROLES.MainTank; foreach (Attack a in TDK.bo.Attacks) { // PlayerRole on calcOpts is MT=0, OT=1, TT=2, Any Tank = 3 // Any Tank means it should be affected by anything that affects a tanking role if (a.AffectsRole[PLAYER_ROLES.MainTank] && (TDK.calcOpts.PlayerRole == 0 || TDK.calcOpts.PlayerRole == 3) || a.AffectsRole[PLAYER_ROLES.OffTank] && (TDK.calcOpts.PlayerRole == 1 || TDK.calcOpts.PlayerRole == 3) || a.AffectsRole[PLAYER_ROLES.TertiaryTank] && (TDK.calcOpts.PlayerRole == 2 || TDK.calcOpts.PlayerRole == 3)) { // TODO: Figure out a way to get the phase changes handled. DPHit = a.DamagePerHit; DPTick = a.DamagePerTick; if (a.DamageIsPerc) { #if DEBUG if ((a.DamagePerHit >= 1f) || (a.DamagePerTick >= 1f)) throw new Exception("Percentage Damage is >= 100%."); #endif DPHit = a.DamagePerHit * stats.Health; DPTick = a.DamagePerTick * stats.Health; } // Bleeds vs Magic vs Physical if (a.DamageType == ItemDamageType.Physical) { // Bleed or Physical // Need to figure out how to determine bleed vs. physical hits. // Also need to balance out the physical hits and balance the hit rate. // JOTHAY NOTE: Bleeds are DoTs if (a.IsDoT) { fCurrentDTPS[(int)SurvivalSub.Bleed] += GetDPS(DPHit, a.AttackSpeed) + GetDPS(DPTick, a.TickInterval); if (fCurrentDmgBiggestHit[(int)SurvivalSub.Bleed] < DPHit + (DPTick * a.NumTicks)) fCurrentDmgBiggestHit[(int)SurvivalSub.Bleed] = DPHit + (DPTick * a.NumTicks); } else { fCurrentDTPS[(int)SurvivalSub.Physical] += GetDPS(DPHit, a.AttackSpeed); if (fCurrentDmgBiggestHit[(int)SurvivalSub.Physical] < DPHit) fCurrentDmgBiggestHit[(int)SurvivalSub.Physical] = DPHit; } } else { // Magic now covering magical dots. fCurrentDTPS[(int)SurvivalSub.Magic] += GetDPS(DPHit, a.AttackSpeed) + GetDPS(DPTick, a.TickInterval); if (fCurrentDmgBiggestHit[(int)SurvivalSub.Magic] < DPHit + (DPTick * a.NumTicks)) fCurrentDmgBiggestHit[(int)SurvivalSub.Magic] = DPHit + (DPTick * a.NumTicks); } } } fTotalDTPS += fCurrentDTPS[(int)SurvivalSub.Physical]; fTotalDTPS += fCurrentDTPS[(int)SurvivalSub.Bleed]; fTotalDTPS += fCurrentDTPS[(int)SurvivalSub.Magic]; if (fTotalDTPS > 0) { fCurrentDTPSPerc[(int)SurvivalSub.Physical] = fCurrentDTPS[(int)SurvivalSub.Physical] / fTotalDTPS; fCurrentDTPSPerc[(int)SurvivalSub.Bleed] = fCurrentDTPS[(int)SurvivalSub.Bleed] / fTotalDTPS; fCurrentDTPSPerc[(int)SurvivalSub.Magic] = fCurrentDTPS[(int)SurvivalSub.Magic] / fTotalDTPS; } #endregion // Set the Fight Duration to no larger than the Berserk Timer // Question: What is the units for Berserk & Speed Timer? MS/S/M? #endregion #region ***** Survival Rating ***** // Magical damage: // if there is a max resistance, then it's likely they are stacking for that resistance. So factor in that Max resistance. float fMaxResist = Math.Max(stats.ArcaneResistance, stats.FireResistance); fMaxResist = Math.Max(fMaxResist, stats.FrostResistance); fMaxResist = Math.Max(fMaxResist, stats.NatureResistance); fMaxResist = Math.Max(fMaxResist, stats.ShadowResistance); float fMagicDR = StatConversion.GetAverageResistance(TDK.bo.Level, TDK.Char.Level, fMaxResist, 0f); calcs.MagicDamageReduction = fMagicDR; float[] SurvivalResults = new float [EnumHelper.GetCount(typeof(SurvivalSub))]; SurvivalResults = GetSurvival(ref TDK, stats, fCurrentDTPSPerc, ArmorDamageReduction, fMagicDR); calcs.ArmorDamageReduction = ArmorDamageReduction; calcs.PhysicalSurvival = SoftCapSurvival(TDK, fCurrentDmgBiggestHit[(int)SurvivalSub.Physical], SurvivalResults[(int)SurvivalSub.Physical], isBurstCalc); calcs.BleedSurvival = SoftCapSurvival(TDK, fCurrentDmgBiggestHit[(int)SurvivalSub.Bleed], SurvivalResults[(int)SurvivalSub.Bleed], isBurstCalc); calcs.MagicSurvival = SoftCapSurvival(TDK, fCurrentDmgBiggestHit[(int)SurvivalSub.Magic], SurvivalResults[(int)SurvivalSub.Magic], isBurstCalc); calcs.HitsToSurvive = TDK.calcOpts.HitsToSurvive; #endregion #region ***** Threat Rating ***** rot.TotalDamage += (int)(stats.FireDamage * (1 + stats.BonusFireDamageMultiplier) * rot.CurRotationDuration); rot.TotalThreat += (int)(stats.FireDamage * (1 + stats.BonusFireDamageMultiplier) * rot.CurRotationDuration) * 2; calcs.RotationTime = rot.CurRotationDuration; // Display the rot in secs. calcs.Threat = rot.m_TPS; calcs.DPS = rot.m_DPS; calcs.Blood = rot.m_BloodRunes; calcs.Frost = rot.m_FrostRunes; calcs.Unholy = rot.m_UnholyRunes; calcs.Death = rot.m_DeathRunes; calcs.RP = rot.m_RunicPower; calcs.TotalThreat = (int)rot.TotalThreat; calcs.ThreatWeight = TDK.calcOpts.ThreatWeight; if (needsDisplayCalcs) { TDK.calcOpts.szRotReport = rot.ReportRotation(); } #endregion #region ***** Mitigation Rating ***** float[] fCurrentDTPSNoAvoid = new float[3]; fCurrentDTPSNoAvoid = fCurrentDTPS.Clone() as float[]; float[] fCurrentMitigation = GetMitigation(ref TDK, stats, rot, (stats.CritChanceReduction / .06f), ArmorDamageReduction, fCurrentDTPS, fMagicDR); calcs.ArmorMitigation = fCurrentMitigation[(int)MitigationSub.Armor]; calcs.AvoidanceMitigation = fCurrentMitigation[(int)MitigationSub.Avoidance]; calcs.CritMitigation = fCurrentMitigation[(int)MitigationSub.Crit]; calcs.DamageTakenMitigation = fCurrentMitigation[(int)MitigationSub.DamageReduction]; calcs.DamageTakenMitigation += fCurrentMitigation[(int)MitigationSub.Haste]; calcs.HealsMitigation = fCurrentMitigation[(int)MitigationSub.Heals]; calcs.ImpedenceMitigation = fCurrentMitigation[(int)MitigationSub.Impedences]; calcs.MagicDamageReductedByAmount = fCurrentMitigation[(int)MitigationSub.AMS]; calcs.MagicDamageReductedByAmount += fCurrentMitigation[(int)MitigationSub.Magic]; calcs.Crit = (.06f - stats.CritChanceReduction); calcs.DTPS = 0; calcs.DTPSNoAvoidance = 0; foreach (float f in fCurrentDTPS) { // These are sometimes coming back as negative. // Assuming we are just 100% absorbing the attack, no damage if (f > 0) { calcs.DTPS += f; } } if (TDK.calcOpts.b_RecoveryInclAvoidance == false) { GetMitigation(ref TDK, stats, rot, (stats.CritChanceReduction / .06f), ArmorDamageReduction, fCurrentDTPSNoAvoid, fMagicDR, false); foreach (float f in fCurrentDTPSNoAvoid) { // These are sometimes coming back as negative. // Assuming we are just 100% absorbing the attack, no damage if (f > 0) { calcs.DTPSNoAvoidance += f; } } } // Have to ensure we don't divide by 0 calcs.Mitigation = StatConversion.MitigationScaler / (Math.Max(1f, calcs.DTPS) / fTotalDTPS); #endregion return calcs; }
/// <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); }
public override CharacterCalculationsBase GetCharacterCalculations(Character character, Item additionalItem, bool referenceCalculation, bool significantChange, bool needsDisplayCalculations) { #region Setup CharacterCalculationsTankDK basecalcs = new CharacterCalculationsTankDK(); CharacterCalculationsTankDK calcs = new CharacterCalculationsTankDK(); TankDKChar TDK = new TankDKChar(); if (character == null) { return calcs; } TDK.calcOpts = character.CalculationOptions as CalculationOptionsTankDK; if (TDK.calcOpts == null) { return calcs; } TDK.Char = character; TDK.bo = character.BossOptions; // Make sure there is at least one attack in the list. // If there's not, add a Default Melee Attack for processing if (TDK.bo.Attacks.Count < 1) { TDK.Char.IsLoading = true; TDK.bo.DamagingTargs = true; TDK.bo.Attacks.Add(BossHandler.ADefaultMeleeAttack); TDK.Char.IsLoading = false; } // Make sure there is a default melee attack // If the above processed, there will be one so this won't have to process // If the above didn't process and there isn't one, add one now if (TDK.bo.DefaultMeleeAttack == null) { TDK.Char.IsLoading = true; TDK.bo.DamagingTargs = true; TDK.bo.Attacks.Add(BossHandler.ADefaultMeleeAttack); TDK.Char.IsLoading = false; } // Since the above forced there to be an attack it's safe to do this without a null check // Attack bossAttack = TDK.bo.DefaultMeleeAttack; #endregion #region Stats // Get base stats that will be used for paperdoll: StatsDK stats = GetCharacterStats(TDK.Char, additionalItem) as StatsDK; // validate that we get a stats object; if (null == stats) { return calcs; } // This is the point that SHOULD have the right values according to the paper-doll. StatsDK sPaperDoll = stats.Clone() as StatsDK; #endregion #region Evaluation Rawr.DPSDK.CharacterCalculationsDPSDK DPSCalcs = new Rawr.DPSDK.CharacterCalculationsDPSDK(); Rawr.DPSDK.CalculationOptionsDPSDK DPSopts = new Rawr.DPSDK.CalculationOptionsDPSDK(); DPSopts.presence = Presence.Blood; DKCombatTable ct = new DKCombatTable(TDK.Char, stats, DPSCalcs, DPSopts, TDK.bo); Rotation rot = new Rotation(ct, true); rot.PRE_BloodDiseased(); // Base calculation values. This will give us Mitigation, and Survival w/ base stats. basecalcs = GetCharacterCalculations(TDK, stats, rot, false, needsDisplayCalculations); // Setup max values w/ everything turned on. stats = GetCharacterStats(TDK.Char, additionalItem, StatType.Maximum, TDK, rot); calcs.SEStats = stats.Clone() as StatsDK; ct = new DKCombatTable(TDK.Char, stats, DPSCalcs, DPSopts, TDK.bo); rot = new Rotation(ct, true); rot.PRE_BloodDiseased(); calcs = GetCharacterCalculations(TDK, stats, rot, true, needsDisplayCalculations); #region Burst // Burst as On-Use Abilties. calcs.Burst = 0; calcs.BurstWeight = TDK.calcOpts.BurstWeight; if (calcs.BurstWeight > 0) { calcs.Burst += calcs.Survivability - basecalcs.Survivability; if (calcs.Burst < 0 || float.IsNaN(calcs.Burst)) { calcs.Burst = 0; } // This should never happen but just in case calcs.Burst += calcs.Mitigation - basecalcs.Mitigation; if (calcs.Burst < 0 || float.IsNaN(calcs.Burst)) { calcs.Burst = 0; } // This should never happen but just in case // Survival calcs.PhysicalSurvival = basecalcs.PhysicalSurvival; calcs.MagicSurvival = basecalcs.MagicSurvival; calcs.BleedSurvival = basecalcs.BleedSurvival; // Mitigation calcs.Mitigation = basecalcs.Mitigation; } #endregion #region **** Recovery: DS & Blood Shield **** float minDSHeal = stats.Health * .07f; // 4.1: DS Heals for 20% of Dam Taken over the last 5 secs. float DTPSFactor = calcs.DTPS * 5f; if (TDK.calcOpts.b_RecoveryInclAvoidance == false) { DTPSFactor = calcs.DTPSNoAvoidance * 5f; } float DamDSHeal = (DTPSFactor * .20f) * (1 + .15f * TDK.Char.DeathKnightTalents.ImprovedDeathStrike); // IDS increases heals by .15 * level float DSHeal = Math.Max(minDSHeal, DamDSHeal); calcs.DSHeal = DSHeal; calcs.DSOverHeal = DSHeal * TDK.calcOpts.pOverHealing; calcs.DSCount = TDK.bo.BerserkTimer * rot.m_DSperSec; float BloodShield = (DSHeal * .5f) * (1 + (stats.Mastery * .0625f)); calcs.BShield = BloodShield; // 4.3 Removing Hitchance for healing float DSHealsPSec = (DSHeal * rot.m_DSperSec * (1f - TDK.calcOpts.pOverHealing)); calcs.TotalDShealed = DSHealsPSec * TDK.bo.BerserkTimer; float BShieldPSec = BloodShield * rot.m_DSperSec; // A new shield w/ each DS. calcs.TotalBShield = BShieldPSec * TDK.bo.BerserkTimer; calcs.Recovery = BloodShield + (DSHeal * (1f - TDK.calcOpts.pOverHealing)); calcs.HPS += DSHealsPSec; calcs.DTPS -= BShieldPSec; calcs.RecoveryWeight = TDK.calcOpts.RecoveryWeight; #endregion #endregion #region Key Data Validation if (float.IsNaN(calcs.Threat) || float.IsNaN(calcs.Survivability) || float.IsNaN(calcs.Burst) || float.IsNaN(calcs.Recovery) || float.IsNaN(calcs.Mitigation) || float.IsNaN(calcs.OverallPoints)) { #if DEBUG throw new Exception("One of the Subpoints are Invalid."); #endif } #endregion #region Display only work calcs.Miss = sPaperDoll.Miss; calcs.Dodge = sPaperDoll.Dodge; calcs.Parry = sPaperDoll.Parry; calcs.BasicStats = sPaperDoll; calcs.SEStats = stats.Clone() as StatsDK; // The full character data. calcs.TargetLevel = TDK.bo.Level; if (null != rot.m_CT.MH) { calcs.TargetDodge = rot.m_CT.MH.chanceDodged; calcs.TargetMiss = rot.m_CT.MH.chanceMissed; calcs.TargetParry = rot.m_CT.MH.chanceParried; calcs.Expertise = rot.m_CT.m_Calcs.MHExpertise; } #endregion return calcs; }
public void DPSBreakdown(Rotation rot) { float[] PetDPS = new float[(int)EnumHelper.GetCount(typeof(DKability))]; PetDPS[(int)DKability.Army] = dpsSub[(int)DKability.Army]; PetDPS[(int)DKability.BloodParasite] = dpsSub[(int)DKability.BloodParasite]; PetDPS[(int)DKability.Gargoyle] = dpsSub[(int)DKability.Gargoyle]; PetDPS[(int)DKability.Ghoul] = dpsSub[(int)DKability.Ghoul]; float fTotalPetDamage = 0; foreach (DKability b in EnumHelper.GetValues(typeof(DKability))) { fTotalPetDamage += PetDPS[(int)b] * rot.CurRotationDuration; } float fTotalDam = rot.TotalDamage + fTotalPetDamage; float fTotalDPS = fTotalDam / rot.CurRotationDuration; // Check to mak sure fTotalDPS == DPSpoints fTotalDPS = Math.Max(fTotalDPS, DPSPoints); // We already have the total damage done. if (null != rot.ml_Rot) { foreach (AbilityDK_Base a in rot.ml_Rot) { // take each instance of each ability and gather the sums. damSub[a.AbilityIndex] += a.TotalDamage; threatSub[a.AbilityIndex] += a.TotalThreat; } } foreach (DKability b in EnumHelper.GetValues(typeof(DKability))) { // damSub[(int)b] += PetDPS[(int)b]; dpsSub[(int)b] = DPSPoints * (damSub[(int)b] / fTotalDam); tpsSub[(int)b] = rot.m_TPS * (threatSub[(int)b] / rot.TotalThreat); } }
public StatsSpecialEffects(DKCombatTable t, Rotation rot, BossOptions bo) { if (rot.ml_Rot == null || rot.ml_Rot.Count == 0) { #if DEBUG throw new Exception("Invalid or Incomplete rotation."); #endif } combatTable = t; m_Rot = rot; m_bo = bo; triggerIntervals = new Dictionary<Trigger, float>(); // Chance that trigger of correct type is produced (for example for /// SpellCrit trigger you would set triggerInterval to average time between hits and set /// triggerChance to crit chance) triggerChances = new Dictionary<Trigger, float>(); unhastedAttackSpeed = (combatTable.MH != null ? combatTable.MH.baseSpeed : 2.0f); float unhastedAttackSpeedOH = (combatTable.OH != null ? combatTable.OH.baseSpeed : 2.0f); float unhastedAttackSpeedSpells = (combatTable.OH != null ? combatTable.OH.baseSpeed : 2.0f); #region Use triggerIntervals.Add(Trigger.Use, 0); triggerChances.Add(Trigger.Use, 1); #endregion #region Basic Hit float fMeleeHitTriggerInterval = (1f / ((m_Rot.getMeleeSpecialsPerSecond() * (combatTable.DW ? 2f : 1f)) + (combatTable.combinedSwingTime != 0 ? 1f / combatTable.combinedSwingTime : 0.5f))); float fMeleeHitTriggerInterval1ofDW = (1f / ((m_Rot.getMeleeSpecialsPerSecond()) + (combatTable.combinedSwingTime != 0 ? 1f / combatTable.combinedSwingTime : 0.5f))); float fPhysicalHitChance = 1f - (combatTable.missedSpecial + combatTable.dodgedSpecial) * (1f - combatTable.totalMHMiss); triggerIntervals.Add(Trigger.MeleeHit, fMeleeHitTriggerInterval); triggerIntervals.Add(Trigger.PhysicalHit, fMeleeHitTriggerInterval); triggerIntervals.Add(Trigger.MeleeAttack, fMeleeHitTriggerInterval); triggerIntervals.Add(Trigger.PhysicalAttack, fMeleeHitTriggerInterval); triggerChances.Add(Trigger.MeleeHit, fPhysicalHitChance); triggerChances.Add(Trigger.PhysicalHit, fPhysicalHitChance); triggerChances.Add(Trigger.MeleeAttack, fPhysicalHitChance); triggerChances.Add(Trigger.PhysicalAttack, fPhysicalHitChance); // TODO: interval would be quicker since it should include DOTTick interval. triggerIntervals.Add(Trigger.DamageDone, fMeleeHitTriggerInterval); triggerChances.Add(Trigger.DamageDone, fPhysicalHitChance); triggerIntervals.Add(Trigger.DamageOrHealingDone, fMeleeHitTriggerInterval); triggerChances.Add(Trigger.DamageOrHealingDone, fPhysicalHitChance); #endregion #region Special Hit triggerIntervals.Add(Trigger.CurrentHandHit, fMeleeHitTriggerInterval1ofDW); triggerChances.Add(Trigger.CurrentHandHit, fPhysicalHitChance); triggerIntervals.Add(Trigger.MainHandHit, fMeleeHitTriggerInterval1ofDW); triggerChances.Add(Trigger.MainHandHit, fPhysicalHitChance); float fMeleeHitTriggerIntervalOH = (combatTable.OH == null ? 0 : fMeleeHitTriggerInterval1ofDW); triggerIntervals.Add(Trigger.OffHandHit, fMeleeHitTriggerIntervalOH); triggerChances.Add(Trigger.OffHandHit, fPhysicalHitChance); #endregion #region Basic Crit triggerIntervals.Add(Trigger.MeleeCrit, fMeleeHitTriggerInterval); triggerChances.Add(Trigger.MeleeCrit, combatTable.physCrits); triggerIntervals.Add(Trigger.PhysicalCrit, fMeleeHitTriggerInterval); triggerChances.Add(Trigger.PhysicalCrit, combatTable.physCrits); #endregion #region Spell Hit float fSpellHitInterval = 0; fSpellHitInterval = 1f / m_Rot.getSpellSpecialsPerSecond(); float fSpellHitChance = 0; float fSpellCritChance = 0; switch (m_Rot.curRotationType) { case Rotation.Type.Unholy: { // Unholy fSpellHitChance = m_Rot.GetAbilityOfType(DKability.DeathCoil).HitChance; fSpellCritChance = m_Rot.GetAbilityOfType(DKability.DeathCoil).CritChance; break; } case Rotation.Type.Frost: { if (m_Rot.Contains(DKability.HowlingBlast)) { fSpellHitChance = m_Rot.GetAbilityOfType(DKability.HowlingBlast).HitChance; fSpellCritChance = m_Rot.GetAbilityOfType(DKability.HowlingBlast).CritChance; } else { fSpellHitChance = m_Rot.GetAbilityOfType(DKability.IcyTouch).HitChance; fSpellCritChance = m_Rot.GetAbilityOfType(DKability.IcyTouch).CritChance; } break; } case Rotation.Type.Blood: { fSpellHitChance = m_Rot.GetAbilityOfType(DKability.IcyTouch).HitChance; fSpellCritChance = m_Rot.GetAbilityOfType(DKability.IcyTouch).CritChance; break; } } triggerIntervals.Add(Trigger.DamageSpellCast, fSpellHitInterval); triggerChances.Add(Trigger.DamageSpellCast, fSpellHitChance); triggerIntervals.Add(Trigger.SpellCast, fSpellHitInterval); triggerChances.Add(Trigger.SpellCast, fSpellHitChance); triggerIntervals.Add(Trigger.DamageSpellHit, fSpellHitInterval); triggerChances.Add(Trigger.DamageSpellHit, fSpellHitChance); triggerIntervals.Add(Trigger.SpellHit, fSpellHitInterval); triggerChances.Add(Trigger.SpellHit, fSpellHitChance); #endregion #region Spell Crit triggerIntervals.Add(Trigger.SpellCrit, fSpellHitInterval); triggerChances.Add(Trigger.SpellCrit, fSpellCritChance); triggerIntervals.Add(Trigger.DamageSpellCrit, fSpellHitInterval); triggerChances.Add(Trigger.DamageSpellCrit, fSpellCritChance); #endregion #region Specific Strikes // triggerIntervals.Add(Trigger.BloodStrikeHit, 0); // triggerChances.Add(Trigger.BloodStrikeHit, 0); if (m_Rot.HasTrigger(Trigger.BloodStrikeHit)) { triggerIntervals.Add(Trigger.BloodStrikeHit, m_Rot.CurRotationDuration / (m_Rot.CountTrigger(Trigger.BloodStrikeHit) * (combatTable.DW ? 2f : 1f))); triggerChances.Add(Trigger.BloodStrikeHit, m_Rot.GetAbilityOfType(DKability.BloodStrike).HitChance); } // triggerIntervals.Add(Trigger.HeartStrikeHit, 0); // triggerChances.Add(Trigger.HeartStrikeHit, 0); if (m_Rot.HasTrigger(Trigger.HeartStrikeHit)) { triggerIntervals.Add(Trigger.HeartStrikeHit, m_Rot.CurRotationDuration / m_Rot.CountTrigger(Trigger.HeartStrikeHit)); triggerChances.Add(Trigger.HeartStrikeHit, m_Rot.GetAbilityOfType(DKability.HeartStrike).HitChance); } // triggerIntervals.Add(Trigger.ObliterateHit, 0); // triggerChances.Add(Trigger.ObliterateHit, 0); if (m_Rot.HasTrigger(Trigger.ObliterateHit)) { triggerIntervals.Add(Trigger.ObliterateHit, m_Rot.CurRotationDuration / (m_Rot.CountTrigger(Trigger.ObliterateHit) * (combatTable.DW ? 2f : 1f))); triggerChances.Add(Trigger.ObliterateHit, m_Rot.GetAbilityOfType(DKability.Obliterate).HitChance); } // triggerIntervals.Add(Trigger.ScourgeStrikeHit, 0); // triggerChances.Add(Trigger.ScourgeStrikeHit, 0); if (m_Rot.HasTrigger(Trigger.ScourgeStrikeHit)) { triggerIntervals.Add(Trigger.ScourgeStrikeHit, m_Rot.CurRotationDuration / (m_Rot.CountTrigger(Trigger.ScourgeStrikeHit) * (combatTable.DW ? 2f : 1f))); triggerChances.Add(Trigger.ScourgeStrikeHit, m_Rot.GetAbilityOfType(DKability.ScourgeStrike).HitChance); } // triggerIntervals.Add(Trigger.DeathStrikeHit, 0); // triggerChances.Add(Trigger.DeathStrikeHit, 0); if (m_Rot.HasTrigger(Trigger.DeathStrikeHit)) { triggerIntervals.Add(Trigger.DeathStrikeHit, m_Rot.CurRotationDuration / (m_Rot.CountTrigger(Trigger.DeathStrikeHit) * (combatTable.DW ? 2f : 1f))); triggerChances.Add(Trigger.DeathStrikeHit, m_Rot.GetAbilityOfType(DKability.DeathStrike).HitChance); } // triggerIntervals.Add(Trigger.PlagueStrikeHit, 0); // triggerChances.Add(Trigger.PlagueStrikeHit, 0); if (m_Rot.HasTrigger(Trigger.PlagueStrikeHit)) { triggerIntervals.Add(Trigger.PlagueStrikeHit, m_Rot.CurRotationDuration / (m_Rot.CountTrigger(Trigger.PlagueStrikeHit) * (combatTable.DW ? 2f : 1f))); triggerChances.Add(Trigger.PlagueStrikeHit, m_Rot.GetAbilityOfType(DKability.PlagueStrike).HitChance); } // triggerIntervals.Add(Trigger.IcyTouchHit, 0); // triggerChances.Add(Trigger.IcyTouchHit, 0); if (m_Rot.HasTrigger(Trigger.IcyTouchHit)) { triggerIntervals.Add(Trigger.IcyTouchHit, m_Rot.CurRotationDuration / (m_Rot.CountTrigger(Trigger.IcyTouchHit) * (combatTable.DW ? 2f : 1f))); triggerChances.Add(Trigger.IcyTouchHit, m_Rot.GetAbilityOfType(DKability.IcyTouch).HitChance); } // triggerIntervals.Add(Trigger.RuneStrikeHit, 0); // triggerChances.Add(Trigger.RuneStrikeHit, 0); if (m_Rot.HasTrigger(Trigger.RuneStrikeHit)) { triggerIntervals.Add(Trigger.RuneStrikeHit, m_Rot.CurRotationDuration / (m_Rot.CountTrigger(Trigger.RuneStrikeHit) * (combatTable.DW ? 2f : 1f))); triggerChances.Add(Trigger.RuneStrikeHit, m_Rot.GetAbilityOfType(DKability.RuneStrike).HitChance); } #endregion #region Misc Offensive triggerIntervals.Add(Trigger.DeathRuneGained, (m_Rot.m_DeathRunes > 0) ? m_Rot.CurRotationDuration / (m_Rot.m_DeathRunes) : 0); triggerChances.Add(Trigger.DeathRuneGained, 1); triggerIntervals.Add(Trigger.KillingMachine, (combatTable.m_CState.m_Talents.KillingMachine > 0) ? ( 60 / (5 * combatTable.m_CState.m_Talents.KillingMachine / 3 ) ) : 0); // KM is a PPM triggerChances.Add(Trigger.KillingMachine, 1); triggerIntervals.Add(Trigger.DoTTick, 1); // TODO: assumes 2 diseases. but w/ Blood & unholy's chance for a 3rd plus UB could also tick.... should be dynamic. triggerChances.Add(Trigger.DoTTick, 1); #endregion #region Defensive triggerChances.Add(Trigger.DamageParried, Math.Min(1f, m_Rot.m_CT.m_CState.m_Stats.EffectiveParry)); triggerIntervals.Add(Trigger.DamageParried, m_bo.DynamicCompiler_FilteredAttacks(m_bo.GetFilteredAttackList(AVOIDANCE_TYPES.Parry)).AttackSpeed); float fAvoidance = (m_Rot.m_CT.m_CState.m_Stats.EffectiveParry + m_Rot.m_CT.m_CState.m_Stats.Dodge + m_Rot.m_CT.m_CState.m_Stats.Miss); triggerChances.Add(Trigger.DamageAvoided, Math.Min(1f, fAvoidance)); triggerIntervals.Add(Trigger.DamageAvoided, m_bo.DynamicCompiler_FilteredAttacks(m_bo.GetFilteredAttackList( AVOIDANCE_TYPES.Parry | AVOIDANCE_TYPES.Block | AVOIDANCE_TYPES.Dodge | AVOIDANCE_TYPES.Miss)).AttackSpeed); triggerChances.Add(Trigger.DamageTaken, Math.Min(1f, 1f - fAvoidance)); triggerIntervals.Add(Trigger.DamageTaken, m_bo.DynamicCompiler_Attacks.AttackSpeed); triggerChances.Add(Trigger.DamageTakenPhysical, Math.Min(1f, 1f - fAvoidance)); triggerIntervals.Add(Trigger.DamageTakenPhysical, m_bo.DynamicCompiler_FilteredAttacks(m_bo.GetFilteredAttackList(ItemDamageType.Physical)).AttackSpeed); triggerChances.Add(Trigger.DamageTakenPutsMeBelow35PercHealth, 0.35f); triggerIntervals.Add(Trigger.DamageTakenPutsMeBelow35PercHealth, m_bo.DynamicCompiler_Attacks.AttackSpeed); triggerChances.Add(Trigger.DamageTakenMagical, 1); List<Attack> attacks = new List<Attack>(); foreach (ItemDamageType i in EnumHelper.GetValues(typeof(ItemDamageType))) { if (i != ItemDamageType.Physical) { foreach (Attack a in m_bo.GetFilteredAttackList(i)) attacks.Add(a); } } triggerIntervals.Add(Trigger.DamageTakenMagical, m_bo.DynamicCompiler_FilteredAttacks(attacks).AttackSpeed); #endregion }
private void AccumulateSpecialEffectStats(StatsDK s, Character c, CalculationOptionsDPSDK calcOpts, DKCombatTable t, Rotation rot) { StatsSpecialEffects se = new StatsSpecialEffects(t, rot, c.BossOptions ); StatsDK statSE = new StatsDK(); foreach (SpecialEffect effect in s.SpecialEffects()) { if (HasRelevantStats(effect.Stats)) { statSE = se.getSpecialEffects(effect); s.Accumulate(statSE); } } }
/// <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; }