Exemple #1
0
        private Stats CalcNormalProc(SpecialEffect effect, Dictionary <int, float> periods, Dictionary <int, float> chances)
        {
            Stats effectStats = effect.Stats;
            Stats proc        = effect.GetAverageStats(periods[(int)effect.Trigger], chances[(int)effect.Trigger], CalculationsWarlock.AVG_UNHASTED_CAST_TIME, BossOpts.BerserkTimer);

            // Handle "recursive effects" - i.e. those that *enable* a
            // proc during a short window.
            if (effect.Stats._rawSpecialEffectDataSize == 1 && periods.ContainsKey((int)effect.Stats._rawSpecialEffectData[0].Trigger))
            {
                SpecialEffect inner      = effect.Stats._rawSpecialEffectData[0];
                Stats         innerStats = inner.GetAverageStats(periods[(int)inner.Trigger], chances[(int)inner.Trigger], 1f, effect.Duration);
                float         upTime     = effect.GetAverageUptime(periods[(int)effect.Trigger], chances[(int)effect.Trigger], 1f, BossOpts.BerserkTimer);
                proc.Accumulate(innerStats, upTime);
            }

            // E.g. Sorrowsong
            if (effect.LimitedToExecutePhase)
            {
                proc *= CalcOpts.ThirtyFive;
            }

            return(proc);
        }
        /// <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);
        }
        private Stats CalcNormalProc(SpecialEffect effect, Dictionary<int, float> periods, Dictionary<int, float> chances)
        {
            Stats effectStats = effect.Stats;
            Stats proc = effect.GetAverageStats(periods[(int)effect.Trigger], chances[(int)effect.Trigger], CalculationsWarlock.AVG_UNHASTED_CAST_TIME, BossOpts.BerserkTimer);

            // Handle "recursive effects" - i.e. those that *enable* a
            // proc during a short window.
            if (effect.Stats._rawSpecialEffectDataSize == 1 && periods.ContainsKey((int)effect.Stats._rawSpecialEffectData[0].Trigger))
            {
                SpecialEffect inner = effect.Stats._rawSpecialEffectData[0];
                Stats innerStats = inner.GetAverageStats(periods[(int)inner.Trigger], chances[(int)inner.Trigger], 1f, effect.Duration);
                float upTime = effect.GetAverageUptime(periods[(int)effect.Trigger], chances[(int)effect.Trigger], 1f, BossOpts.BerserkTimer);
                proc.Accumulate(innerStats, upTime);
            }

            // E.g. Sorrowsong
            if (effect.LimitedToExecutePhase)
            {
                proc *= CalcOpts.ThirtyFive;
            }

            return proc;
        }
        public Stats getSpecialEffects(CalculationOptionsTankDK calcOpts, SpecialEffect effect)
        {
            Stats    statsAverage = new Stats();
            Rotation rRotation    = calcOpts.m_Rotation;

            if (effect.Trigger == Trigger.Use)
            {
                if (calcOpts.bUseOnUseAbilities == true)
                {
                    statsAverage.Accumulate(effect.GetAverageStats());
                }
            }
            else
            {
                float trigger             = 0f;
                float chance              = effect.Chance;
                float duration            = effect.Duration;
                float unhastedAttackSpeed = 2f;
                switch (effect.Trigger)
                {
                case Trigger.MeleeCrit:
                case Trigger.PhysicalCrit:
                    trigger             = (1f / rRotation.getMeleeSpecialsPerSecond()) + (combatTable.combinedSwingTime != 0 ? 1f / combatTable.combinedSwingTime : 0.5f);
                    chance              = combatTable.physCrits * effect.Chance;
                    unhastedAttackSpeed = (combatTable.MH != null ? combatTable.MH.baseSpeed : 2.0f);
                    break;

                case Trigger.MeleeHit:
                case Trigger.PhysicalHit:
                    trigger             = (1f / (rRotation.getMeleeSpecialsPerSecond() * (combatTable.m_bDW ? 2 : 1))) + (combatTable.combinedSwingTime != 0 ? 1f / combatTable.combinedSwingTime : 0.5f);
                    chance              = effect.Chance * (1f - (combatTable.missedSpecial + combatTable.dodgedSpecial));
                    unhastedAttackSpeed = (combatTable.MH != null ? combatTable.MH.baseSpeed : 2.0f);
                    break;

                case Trigger.CurrentHandHit:
                case Trigger.MainHandHit:
                    trigger             = (1f / rRotation.getMeleeSpecialsPerSecond()) + (combatTable.MH.hastedSpeed != 0 ? 1f / combatTable.MH.hastedSpeed : 0.5f);
                    chance              = effect.Chance * (1f - (combatTable.missedSpecial + combatTable.dodgedSpecial));
                    unhastedAttackSpeed = (combatTable.MH != null ? combatTable.MH.baseSpeed : 2.0f);
                    break;

                case Trigger.OffHandHit:
                    trigger             = (1f / rRotation.getMeleeSpecialsPerSecond()) + (combatTable.OH.hastedSpeed != 0 ? 1f / combatTable.OH.hastedSpeed : 0.5f);
                    chance              = effect.Chance * (1f - (combatTable.missedSpecial + combatTable.dodgedSpecial));
                    unhastedAttackSpeed = (combatTable.MH != null ? combatTable.MH.baseSpeed : 2.0f);
                    break;

                case Trigger.DamageDone:
                case Trigger.DamageOrHealingDone:
                    trigger             = (1f / rRotation.getMeleeSpecialsPerSecond()) + (combatTable.combinedSwingTime != 0 ? 1f / combatTable.combinedSwingTime : 0.5f);
                    unhastedAttackSpeed = (combatTable.MH != null ? combatTable.MH.baseSpeed : 2.0f);
                    chance = effect.Chance * (1f - (combatTable.missedSpecial + combatTable.dodgedSpecial));
                    break;

                case Trigger.DamageSpellCast:
                case Trigger.SpellCast:
                case Trigger.DamageSpellHit:
                case Trigger.SpellHit:
                    trigger = 1f / rRotation.getSpellSpecialsPerSecond();
                    chance  = 1f - combatTable.spellResist;
                    break;

                case Trigger.DamageSpellCrit:
                case Trigger.SpellCrit:
                    trigger = 1f / rRotation.getSpellSpecialsPerSecond();
                    chance  = combatTable.spellCrits * effect.Chance;
                    break;

                case Trigger.DoTTick:
                    trigger = (rRotation.BloodPlague + rRotation.FrostFever) / 3;
                    break;

                case Trigger.DamageTaken:
                case Trigger.DamageTakenPhysical:
                    trigger             = calcOpts.BossAttackSpeed;
                    chance             *= 1f - (stats.Dodge + stats.Parry + stats.Miss);
                    unhastedAttackSpeed = calcOpts.BossAttackSpeed;
                    break;

                case Trigger.DamageTakenMagical:
                    trigger = calcOpts.IncomingFromMagicFrequency;
                    break;

                //////////////////////////////////
                // DK specific triggers:
                case Trigger.BloodStrikeHit:
                case Trigger.HeartStrikeHit:
                    trigger = rRotation.curRotationDuration / (rRotation.BloodStrike + rRotation.HeartStrike);
                    break;

                case Trigger.PlagueStrikeHit:
                    trigger = rRotation.curRotationDuration / rRotation.PlagueStrike;
                    break;

                case Trigger.RuneStrikeHit:
                    trigger = rRotation.curRotationDuration / rRotation.RuneStrike;
                    break;

                case Trigger.IcyTouchHit:
                    trigger = rRotation.curRotationDuration / rRotation.IcyTouch;
                    break;

                case Trigger.DeathStrikeHit:
                    trigger = rRotation.curRotationDuration / rRotation.DeathStrike;
                    break;

                case Trigger.ObliterateHit:
                    trigger = rRotation.curRotationDuration / rRotation.Obliterate;
                    break;

                case Trigger.ScourgeStrikeHit:
                    trigger = rRotation.curRotationDuration / rRotation.ScourgeStrike;
                    break;

                case Trigger.FrostFeverHit:
                    // Icy Talons triggers off this.
                    trigger = rRotation.curRotationDuration / rRotation.IcyTouch;
                    if (character.DeathKnightTalents.GlyphofHowlingBlast)
                    {
                        trigger += rRotation.curRotationDuration / rRotation.HowlingBlast;
                    }
                    break;
                }
                if (!float.IsInfinity(trigger) && !float.IsNaN(trigger))
                {
                    if (effect.UsesPPM())
                    {
                        // If effect.chance < 0 , then it's using PPM.
                        // Let's get the duration * how many times it procs per min:
                        float UptimePerMin = 0;
                        float fWeight      = 0;
                        if (duration == 0) // Duration of 0 means that it's a 1 time effect that procs every time the proc comes up.
                        {
                            fWeight = Math.Abs(effect.Chance) / 60;
                        }
                        else
                        {
                            UptimePerMin = duration * Math.Abs(effect.Chance);
                            fWeight      = UptimePerMin / 60;
                        }
                        statsAverage.Accumulate(effect.Stats, fWeight);
                    }
                    else
                    {
                        effect.AccumulateAverageStats(statsAverage, trigger, chance, unhastedAttackSpeed, calcOpts.FightLength * 60);
                    }
                }
            }
            return(statsAverage);
        }