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
        }
Esempio n. 2
0
        /// <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;
        }