Example #1
0
		public CatAbilityBuilder(StatsCat stats, DruidTalents talents, float weaponDPS, float attackSpeed, float armorDamageMultiplier, 
			float hasteBonus, float critMultiplier, float chanceAvoided, float chanceCritMelee, 
			float chanceCritFurySwipes, float chanceCritMangle, float chanceCritRavage, float chanceCritShred, 
			float chanceCritRake, float chanceCritRip, float chanceCritBite, float chanceGlance)
		{
			Stats = stats;
            Talents = talents;
			WeaponDPS = weaponDPS;
			AttackSpeed = attackSpeed;
			ArmorDamageMultiplier = armorDamageMultiplier;
			HasteBonus = hasteBonus;
			CritMultiplier = critMultiplier;
			ChanceAvoided = chanceAvoided;
			ChanceCritMelee = chanceCritMelee;
			ChanceCritFurySwipes = chanceCritFurySwipes;
			ChanceCritMangle = chanceCritMangle;
			ChanceCritRavage = chanceCritRavage;
			ChanceCritShred = chanceCritShred;
			ChanceCritRake = chanceCritRake;
			ChanceCritRip = chanceCritRip;
			ChanceCritBite = chanceCritBite;
			ChanceGlance = chanceGlance;

			BaseDamage = WeaponDPS + AttackPower / 14f + stats.WeaponDamage;
		}
Example #2
0
        public override Stats GetCharacterStats(Character character, Item additionalItem)
        {
            CalculationOptionsCat calcOpts = character.CalculationOptions as CalculationOptionsCat ?? new CalculationOptionsCat();
            DruidTalents talents = character.DruidTalents;
            BossOptions bossOpts = character.BossOptions;

            bool hasCritBuff = false;
            foreach (Buff buff in character.ActiveBuffs)
            {
                if (buff.Group == "Critical Strike Chance")
                {
                    hasCritBuff = true;
                    break;
                }
            }

            StatsCat statsTotal = new StatsCat()
            {
                BonusAgilityMultiplier = Character.ValidateArmorSpecialization(character, ItemType.Leather) ? 0.05f : 0f,
                BonusAttackPowerMultiplier = (1f + 0.25f) * (1f + talents.HeartOfTheWild * 0.1f / 3f) - 1f,
                //BonusBleedDamageMultiplier = (character.ActiveBuffsConflictingBuffContains("Bleed Damage") ? 0f : 0.3f),

                MovementSpeed = 0.15f * talents.FeralSwiftness,
                RavageCritChanceOnTargetsAbove80Percent = 0.25f * talents.PredatoryStrikes,
                FurySwipesChance = 0.05f * talents.FurySwipes,
                CPOnCrit = 0.5f * talents.PrimalFury,
                FerociousBiteDamageMultiplier = 0.05f* talents.FeralAggression,
                EnergyOnTigersFury = 20f * talents.KingOfTheJungle,
                FreeRavageOnFeralChargeChance = 0.5f * talents.Stampede,
                PhysicalCrit = (hasCritBuff ? 0f : 0.05f * talents.LeaderOfThePack)
                                + (talents.MasterShapeshifter == 1 ? 0.04f : 0f),
                MaxEnergyOnTigersFuryBerserk = 10f * talents.PrimalMadness,
                RipRefreshChanceOnFerociousBiteOnTargetsBelow25Percent = 0.5f * talents.BloodInTheWater,
                ShredDamageMultiplier = talents.RendAndTear * 0.2f / 3f,

                BonusBerserkDuration = (talents.Berserk > 0 ? 15f + (talents.GlyphOfBerserk ? 10f : 0f) : 0f),
                MangleDamageMultiplier = talents.GlyphOfMangle ? 0.1f : 0f,
                SavageRoarDamageMultiplierIncrease = talents.GlyphOfSavageRoar ? 0.05f : 0f,
                FeralChargeCatCooldownReduction = talents.GlyphOfFeralCharge ? 2f : 0f,
//                FerociousBiteMaxExtraEnergyReduction = talents.GlyphOfFerociousBite ? 25f : 0f,
            };

            #region Set Bonuses
            int PvPCount;
            character.SetBonusCount.TryGetValue("Gladiator's Sanctuary", out PvPCount);
            if (PvPCount >= 2)
            {
                statsTotal.Agility += 70f;
                statsTotal.Resilience += 400f;
            }
            if (PvPCount >= 4)
            {
                // the 15% movement speed is only outdoors which most dungeons are not
                statsTotal.Agility += 90f;
            }
            int T11Count;
            character.SetBonusCount.TryGetValue("Stormrider's Battlegarb", out T11Count);
            if (T11Count >= 2) {
                statsTotal.BonusDamageMultiplierRakeTick = (1f + statsTotal.BonusDamageMultiplierRakeTick) * (1f + 0.10f) - 1f;
                statsTotal.BonusDamageMultiplierLacerate = (1f + statsTotal.BonusDamageMultiplierLacerate) * (1f + 0.10f) - 1f;
            }
            if (T11Count >= 4) {
                statsTotal.AddSpecialEffect(new SpecialEffect(Trigger.MangleCatAttack,
                    new Stats() { BonusAttackPowerMultiplier = 0.01f, },
                    30, 0, 1f, 3));
                statsTotal.Tier_11_4pc = true;
                //statsTotal.BonusSurvivalInstinctsDurationMultiplier = (1f + statsTotal.BonusSurvivalInstinctsDurationMultiplier) * (1f + 0.50f) - 1f;
            }
            int T12Count;
            character.SetBonusCount.TryGetValue("Obsidian Arborweave Battlegarb", out T12Count);
            if (T12Count >= 2)
            {
                statsTotal.MangleDamageMultiplier = (1f + statsTotal.MangleDamageMultiplier) * (1f + 0.10f) - 1f;
                statsTotal.ShredDamageMultiplier = (1f + statsTotal.ShredDamageMultiplier) * (1f + 0.10f) - 1f;
            }
            if (T12Count >= 4)
            {
                // Assume that all Finishing Moves are used at 5 combo points 
                // SpecialEffect primary = new SpecialEffect(Trigger.Berserk, new StatsCat(), statsTotal.BonusBerserkDuration, 180f);
                // SpecialEffect secondary = new SpecialEffect(Trigger.FinishingMove,
                //     new StatsCat() { BonusBerserkDuration = 2f, },
                //     0, 5f, 1f);
                // primary.Stats.AddSpecialEffect(secondary);
                // statsTotal.AddSpecialEffect(primary); 
                statsTotal.Tier_12_4pc = true;
            }
            statsTotal.Tier_13_2_piece = false;
            statsTotal.Tier_13_4_piece = false;
            int T13Count;
            character.SetBonusCount.TryGetValue("Deep Earth Battlegarb", out T13Count);
            if (T13Count >= 2)
            {
                // Your Blood in the Water talent now causes Ferocious Bite to refresh the duration of your Rip on targets with 60% or less health.
                statsTotal.Tier_13_2_piece = true;
            }
            if (T13Count >= 4)
            {
                // Your Stampede talent now grants two charges after using Feral Charge (Cat).
                statsTotal.Tier_13_4_piece = true;
            }
            #endregion

            statsTotal.Accumulate(BaseStats.GetBaseStats(character.Level, character.Class, character.Race, BaseStats.DruidForm.Cat));
            statsTotal.Accumulate(GetItemStats(character, additionalItem));
            AccumulateBuffsStats(statsTotal, character.ActiveBuffs);

            statsTotal.Stamina = (float)Math.Floor(statsTotal.Stamina * (1f + statsTotal.BonusStaminaMultiplier));
            statsTotal.Strength = (float)Math.Floor(statsTotal.Strength * (1f + statsTotal.BonusStrengthMultiplier));
            statsTotal.Agility = (float)Math.Floor(statsTotal.Agility * (1f + statsTotal.BonusAgilityMultiplier));
            statsTotal.AttackPower += statsTotal.Strength * 1f + (statsTotal.Agility * 2f - 20f); //-20 to account for the first 20 str and first 20 agi only giving 1ap each
            statsTotal.AttackPower = (float)Math.Floor(statsTotal.AttackPower * (1f + statsTotal.BonusAttackPowerMultiplier));
            statsTotal.Health += (float)Math.Floor((statsTotal.Stamina - 20f) * 14f + 20f);
            statsTotal.Health = (float)Math.Floor(statsTotal.Health * (1f + statsTotal.BonusHealthMultiplier));
            statsTotal.Armor = (float)Math.Floor(statsTotal.Armor * (1f + statsTotal.BonusArmorMultiplier));
            statsTotal.NatureResistance += statsTotal.NatureResistanceBuff;
            statsTotal.FireResistance += statsTotal.FireResistanceBuff;
            statsTotal.FrostResistance += statsTotal.FrostResistanceBuff;
            statsTotal.ShadowResistance += statsTotal.ShadowResistanceBuff;
            statsTotal.ArcaneResistance += statsTotal.ArcaneResistanceBuff;

            int targetLevel = bossOpts.Level;
            float hasteBonus = StatConversion.GetPhysicalHasteFromRating(statsTotal.HasteRating, CharacterClass.Druid);
            hasteBonus = (1f + hasteBonus) * (1f + statsTotal.PhysicalHaste) - 1f;
            float meleeHitInterval = 1f / ((1f + hasteBonus) + 1f / (3.5f / hasteBonus));
            float hitBonus = StatConversion.GetPhysicalHitFromRating(statsTotal.HitRating) + statsTotal.PhysicalHit;
            float expertiseBonus = StatConversion.GetDodgeParryReducFromExpertise(StatConversion.GetExpertiseFromRating(statsTotal.ExpertiseRating, CharacterClass.Druid) + statsTotal.Expertise, CharacterClass.Druid);
            float chanceDodge = Math.Max(0f, StatConversion.WHITE_DODGE_CHANCE_CAP[targetLevel - 85] - expertiseBonus);
            float chanceParry = 0f;
            float chanceMiss = Math.Max(0f, StatConversion.WHITE_MISS_CHANCE_CAP[targetLevel - 85] - hitBonus);
            float chanceAvoided = chanceMiss + chanceDodge + chanceParry;

            float rawChanceCrit = StatConversion.GetPhysicalCritFromRating(statsTotal.CritRating)
                                + StatConversion.GetPhysicalCritFromAgility(statsTotal.Agility, CharacterClass.Druid)
                                + statsTotal.PhysicalCrit
                                + StatConversion.NPC_LEVEL_CRIT_MOD[targetLevel - 85];
            float chanceCrit = rawChanceCrit * (1f - chanceAvoided);
            float chanceHit = 1f - chanceAvoided;
            bool usesMangle = (!character.ActiveBuffsContains("Mangle") && !character.ActiveBuffsContains("Trauma"));

            Dictionary<Trigger, float> triggerIntervals = new Dictionary<Trigger, float>();
            Dictionary<Trigger, float> triggerChances = new Dictionary<Trigger, float>();
            triggerIntervals[Trigger.Use] = 0f;
            triggerIntervals[Trigger.MeleeAttack] = meleeHitInterval;
            triggerIntervals[Trigger.MeleeHit] = meleeHitInterval;
            triggerIntervals[Trigger.PhysicalHit] = meleeHitInterval;
            triggerIntervals[Trigger.PhysicalAttack] = meleeHitInterval;
            triggerIntervals[Trigger.MeleeCrit] = meleeHitInterval;
            triggerIntervals[Trigger.PhysicalCrit] = meleeHitInterval;
            triggerIntervals[Trigger.DoTTick] = 1.5f;
            triggerIntervals[Trigger.DamageDone] = meleeHitInterval / 2f;
            triggerIntervals[Trigger.DamageOrHealingDone] = meleeHitInterval / 2f; // Need to Add Self-Heals
            triggerIntervals[Trigger.RakeTick] = 3f;
            if (usesMangle) {
                triggerIntervals[Trigger.MangleCatHit] = 60f;
                triggerIntervals[Trigger.MangleCatAttack] = 60f;
            }
            triggerIntervals[Trigger.MangleCatOrShredHit] = usesMangle ? 3.76f : 3.87f;
            triggerIntervals[Trigger.MangleCatOrShredOrInfectedWoundsHit] = triggerIntervals[Trigger.MangleCatOrShredHit] / ((talents.InfectedWounds > 0) ? 2f : 1f);
            triggerIntervals[Trigger.EnergyOrFocusDropsBelow20PercentOfMax] = 4f; // doing 80% chance every 4 seconds per Astry
            triggerIntervals[Trigger.FinishingMove] = 9f; // Assume it takes 9 seconds between to perform a finishing move
            triggerIntervals[Trigger.Berserk] = 180f;
            triggerChances[Trigger.Use] = 1f;
            triggerChances[Trigger.MeleeAttack] = 1f;
            triggerChances[Trigger.MeleeHit] = Math.Max(0f, chanceHit);
            triggerChances[Trigger.PhysicalHit] = Math.Max(0f, chanceHit);
            triggerChances[Trigger.PhysicalAttack] = 1f;
            triggerChances[Trigger.MeleeCrit] = Math.Max(0f, chanceCrit);
            triggerChances[Trigger.PhysicalCrit] = Math.Max(0f, chanceCrit);
            triggerChances[Trigger.DoTTick] = 1f;
            triggerChances[Trigger.DamageDone] = 1f - chanceAvoided / 2f;
            triggerChances[Trigger.DamageOrHealingDone] = 1f - chanceAvoided / 2f; // Need to Add Self-Heals
            triggerChances[Trigger.RakeTick] = 1f;
            if (usesMangle) {
                triggerChances[Trigger.MangleCatAttack] = 1f;
                triggerChances[Trigger.MangleCatHit] = chanceHit;
            }
            triggerChances[Trigger.MangleCatOrShredHit] = chanceHit;
            triggerChances[Trigger.MangleCatOrShredOrInfectedWoundsHit] = chanceHit;
            triggerChances[Trigger.EnergyOrFocusDropsBelow20PercentOfMax] = 0.80f; // doing 80% chance every 4 seconds per Astry
            triggerChances[Trigger.FinishingMove] = 1f;
            triggerChances[Trigger.Berserk] = 1f;

            // Handle Trinket procs
            Stats statsProcs = new Stats();
            foreach (SpecialEffect effect in statsTotal.SpecialEffects(se => triggerIntervals.ContainsKey(se.Trigger)))
            {
                // JOTHAY's NOTE: The following is an ugly hack to add Recursive Effects to Cat
                // so Victor's Call and similar trinkets can be given more appropriate value
                if (effect.Trigger == Trigger.Use && effect.Stats._rawSpecialEffectDataSize == 1
                    && triggerIntervals.ContainsKey(effect.Stats._rawSpecialEffectData[0].Trigger))
                {
                    float upTime = effect.GetAverageUptime(triggerIntervals[effect.Trigger],
                        triggerChances[effect.Trigger], 1f, bossOpts.BerserkTimer);
                    statsProcs.Accumulate(effect.Stats._rawSpecialEffectData[0].GetAverageStats(
                        triggerIntervals[effect.Stats._rawSpecialEffectData[0].Trigger],
                        triggerChances[effect.Stats._rawSpecialEffectData[0].Trigger], 1f, bossOpts.BerserkTimer),
                        upTime);
                } else if (effect.Stats.MoteOfAnger > 0) {
                    // When in effect stats, MoteOfAnger is % of melee hits
                    // When in character stats, MoteOfAnger is average procs per second
                    statsProcs.MoteOfAnger = effect.Stats.MoteOfAnger * effect.GetAverageProcsPerSecond(triggerIntervals[effect.Trigger],
                        triggerChances[effect.Trigger], 1f, bossOpts.BerserkTimer) / effect.MaxStack;
                } else {
                    statsProcs.Accumulate(effect.GetAverageStats(triggerIntervals[effect.Trigger], triggerChances[effect.Trigger], 1f, bossOpts.BerserkTimer));
                }
            }

            statsProcs.Agility += statsProcs.HighestStat + statsProcs.Paragon;
            statsProcs.Stamina = (float)Math.Floor(statsProcs.Stamina * (1f + statsTotal.BonusStaminaMultiplier));
            statsProcs.Strength = (float)Math.Floor(statsProcs.Strength * (1f + statsTotal.BonusStrengthMultiplier));
            statsProcs.Agility = (float)Math.Floor(statsProcs.Agility * (1f + statsTotal.BonusAgilityMultiplier));
            statsProcs.AttackPower += statsProcs.Strength * 1f + statsProcs.Agility * 2f;
            statsProcs.AttackPower = (float)Math.Floor(statsProcs.AttackPower * (1f + statsTotal.BonusAttackPowerMultiplier));
            statsProcs.Health += (float)Math.Floor(statsProcs.Stamina * 10f);
            //statsProcs.Armor += 2f * statsProcs.Agility;
            statsProcs.Armor = (float)Math.Floor(statsProcs.Armor * (1f + statsTotal.BonusArmorMultiplier));
            if (statsProcs.HighestSecondaryStat > 0)
            {
                if (statsTotal.CritRating > statsTotal.HasteRating && statsTotal.CritRating > statsTotal.MasteryRating) {
                    statsProcs.CritRating += statsProcs.HighestSecondaryStat; // this will be invalidated after this, but I'm at least putting it in for now
                } else if (statsTotal.HasteRating > statsTotal.CritRating && statsTotal.HasteRating > statsTotal.MasteryRating) {
                    statsProcs.HasteRating += statsProcs.HighestSecondaryStat;
                } else if (statsTotal.MasteryRating > statsTotal.CritRating && statsTotal.MasteryRating > statsTotal.HasteRating) {
                    statsProcs.MasteryRating += statsProcs.HighestSecondaryStat;
                }
                statsProcs.HighestSecondaryStat = 0;
            }


            //Agility is only used for crit from here on out; we'll be converting Agility to CritRating, 
            //and calculating CritRating separately, so don't add any Agility or CritRating from procs here.
            statsProcs.CritRating = statsProcs.Agility = 0;
            statsTotal.Accumulate(statsProcs);

            //Handle Crit procs
            statsTotal.TemporaryCritRatingUptimes = new WeightedStat[0];
            List<SpecialEffect> tempCritEffects = new List<SpecialEffect>();
            List<float> tempCritEffectIntervals = new List<float>();
            List<float> tempCritEffectChances = new List<float>();
            List<float> tempCritEffectScales = new List<float>();

            foreach (SpecialEffect effect in statsTotal.SpecialEffects(se => triggerIntervals.ContainsKey(se.Trigger) && (se.Stats.CritRating + se.Stats.Agility + se.Stats.HighestStat + se.Stats.Paragon) > 0))
            {
                tempCritEffects.Add(effect);
                tempCritEffectIntervals.Add(triggerIntervals[effect.Trigger]);
                tempCritEffectChances.Add(triggerChances[effect.Trigger]);
                tempCritEffectScales.Add(1f);
            }

            if (tempCritEffects.Count == 0)
            {
                statsTotal.TemporaryCritRatingUptimes = new WeightedStat[] { new WeightedStat() { Chance = 1f, Value = 0f } };
            }
            else if (tempCritEffects.Count == 1)
            { //Only one, add it to
                SpecialEffect effect = tempCritEffects[0];
                float uptime = effect.GetAverageUptime(triggerIntervals[effect.Trigger], triggerChances[effect.Trigger], 1f, bossOpts.BerserkTimer) * tempCritEffectScales[0];
                float totalAgi = (float)effect.MaxStack * (effect.Stats.Agility + effect.Stats.HighestStat + effect.Stats.Paragon) * (1f + statsTotal.BonusAgilityMultiplier);
                statsTotal.TemporaryCritRatingUptimes = new WeightedStat[] { new WeightedStat() { Chance = uptime, Value = 
                            effect.Stats.CritRating + StatConversion.GetCritFromAgility(totalAgi,
                            CharacterClass.Druid) * StatConversion.RATING_PER_PHYSICALCRIT },
                        new WeightedStat() { Chance = 1f - uptime, Value = 0f }};
            }
            else if (tempCritEffects.Count > 1)
            {
                List<float> tempCritEffectsValues = new List<float>();
                foreach (SpecialEffect effect in tempCritEffects)
                {
                    float totalAgi = (float)effect.MaxStack * (effect.Stats.Agility + effect.Stats.HighestStat + effect.Stats.Paragon) * (1f + statsTotal.BonusAgilityMultiplier);
                    tempCritEffectsValues.Add(effect.Stats.CritRating +
                        StatConversion.GetCritFromAgility(totalAgi, CharacterClass.Druid) *
                        StatConversion.RATING_PER_PHYSICALCRIT);
                }

                float[] intervals = new float[tempCritEffects.Count];
                float[] chances = new float[tempCritEffects.Count];
                float[] offset = new float[tempCritEffects.Count];
                for (int i = 0; i < tempCritEffects.Count; i++)
                {
                    intervals[i] = triggerIntervals[tempCritEffects[i].Trigger];
                    chances[i] = triggerChances[tempCritEffects[i].Trigger];
                }
                if (tempCritEffects.Count >= 2)
                {
                    offset[0] = calcOpts.TrinketOffset;
                }
                WeightedStat[] critWeights = SpecialEffect.GetAverageCombinedUptimeCombinations(tempCritEffects.ToArray(), intervals, chances, offset,
                    tempCritEffectScales.ToArray(), 1f, bossOpts.BerserkTimer, tempCritEffectsValues.ToArray());
                statsTotal.TemporaryCritRatingUptimes = critWeights;
            }

            return statsTotal;
        }
Example #3
0
        public Stats GetBuffsStats(Character character, CalculationOptionsCat calcOpts) {
            List<Buff> removedBuffs = new List<Buff>();
            List<Buff> addedBuffs = new List<Buff>();

            List<Buff> buffGroup = new List<Buff>();

            #region Maintenance Auto-Fixing
            // Removes the Sunder Armor if you are maintaining it yourself
            // Also removes Acid Spit and Expose Armor
            // We are now calculating this internally for better accuracy and to provide value to relevant talents
            if (calcOpts.CustomUseMangle)
            {
                buffGroup.Clear();
                buffGroup.Add(Buff.GetBuffByName("Sunder Armor"));
                buffGroup.Add(Buff.GetBuffByName("Expose Armor"));
                buffGroup.Add(Buff.GetBuffByName("Faerie Fire"));
                buffGroup.Add(Buff.GetBuffByName("Corrosive Spit"));
                buffGroup.Add(Buff.GetBuffByName("Tear Armor"));
                MaintBuffHelper(buffGroup, character, removedBuffs);
            }
            #endregion

            StatsCat statsBuffs = new StatsCat();
            statsBuffs.Accumulate(GetBuffsStats(character.ActiveBuffs, character.SetBonusCount));

            foreach (Buff b in removedBuffs) { character.ActiveBuffsAdd(b); }
            foreach (Buff b in addedBuffs) { character.ActiveBuffs.Remove(b); }

            return statsBuffs;
        }