public CombatFactors(Character character, Stats stats, CalculationOptionsDPSWarr calcOpts, BossOptions bossOpts) { Char = character; MH = Char == null || Char.MainHand == null ? new Knuckles() : Char.MainHand.Item; OH = Char == null || Char.OffHand == null || Char.WarriorTalents.TitansGrip == 0 ? null : Char.OffHand.Item; Talents = Char == null || Char.WarriorTalents == null ? new WarriorTalents() : Char.WarriorTalents; //TalentsCata = Char == null || Char.WarriorTalentsCata == null ? new WarriorTalentsCata() : Char.WarriorTalentsCata; CalcOpts = (calcOpts == null ? new CalculationOptionsDPSWarr() : calcOpts); BossOpts = (bossOpts == null ? new BossOptions() : bossOpts); StatS = stats; critProcs = new WeightedStat[] { new WeightedStat() { Chance = 1f, Value = 0f } }; InvalidateCache(); // Optimizations //Set_c_values(); }
public CombatFactors(Character character, Base.StatsWarrior stats, CalculationOptionsDPSWarr calcOpts, BossOptions bossOpts) { Char = character; MH = Char == null || Char.MainHand == null ? new Knuckles() : Char.MainHand.Item; OH = Char == null || Char.OffHand == null || (Char.WarriorTalents.TitansGrip == 0 && Char.WarriorTalents.SingleMindedFury == 0) ? null : Char.OffHand.Item; Talents = Char == null || Char.WarriorTalents == null ? new WarriorTalents() : Char.WarriorTalents; CalcOpts = (calcOpts == null ? new CalculationOptionsDPSWarr() : calcOpts); BossOpts = (bossOpts == null ? new BossOptions() : bossOpts); StatS = stats; CritProcs = new WeightedStat[] { new WeightedStat() { Chance = 1f, Value = 0f } }; InvalidateCache(); // Optimizations //SetCvalues(); #if DEBUG //ConstructionCounts["CombatFactors"]++; #endif }
protected void Initialize(Character character, Stats stats, bool useSpellHit, bool alwaysHit, int delta) { Char = character; StatS = stats; this.useSpellHit = useSpellHit; LevelDelta = delta; critProcs = new WeightedStat[] { new WeightedStat() { Chance = 1f, Value = 0f } }; /*// Defaults * Miss * Dodge * Parry * Block * Glance * Critical * Hit*/ // Start a calc Reset(alwaysHit); }
protected void Initialize(Character character, Stats stats, bool useSpellHit, bool alwaysHit, int delta) { Char = character; StatS = stats; this.useSpellHit = useSpellHit; LevelDelta = delta; critProcs = new WeightedStat[] { new WeightedStat() { Chance = 1f, Value = 0f } }; /*// Defaults Miss Dodge Parry Block Glance Critical Hit*/ // Start a calc Reset(alwaysHit); }
private void CalcHasteAndManaProcs() { float nonProcHaste = StatUtils.CalcSpellHaste(PreProcStats); if (Options.NoProcs) { WeightedStat staticHaste = new WeightedStat(); staticHaste.Chance = 1f; staticHaste.Value = nonProcHaste; Haste = new List <WeightedStat> { staticHaste }; AvgHaste = nonProcHaste; return; } // the trigger rates are all guestimates at this point, since the // real values depend on haste (which obviously has not been // finalized yet) Dictionary <int, float> periods = new Dictionary <int, float>(); Dictionary <int, float> chances = new Dictionary <int, float>(); float corruptionPeriod = 0f; if (Options.GetActiveRotation().Contains("Corruption")) { corruptionPeriod = 3.1f; if (Talents.GlyphQuickDecay) { corruptionPeriod /= nonProcHaste; } } PopulateTriggers( periods, chances, CalculationsWarlock.AVG_UNHASTED_CAST_TIME / nonProcHaste + Options.Latency, 1 / 1.5f, corruptionPeriod, 1f); // calculate the haste procs Haste = new List <WeightedStat>(); WeightedStat[] percentages = GetUptimes( Stats, periods, chances, s => s.SpellHaste, (a, b, c, d, e, f, g, h) => SpecialEffect .GetAverageCombinedUptimeCombinationsMultiplicative( a, b, c, d, e, f, g, h)); WeightedStat[] ratings = GetUptimes( Stats, periods, chances, s => s.HasteRating, (a, b, c, d, e, f, g, h) => SpecialEffect.GetAverageCombinedUptimeCombinations( a, b, c, d, e, f, g, h)); for (int p = percentages.Length, f = 0; --p >= 0;) { if (percentages[p].Chance == 0) { continue; } for (int r = ratings.Length; --r >= 0; ++f) { if (ratings[r].Chance == 0) { continue; } WeightedStat s = new WeightedStat(); s.Chance = percentages[p].Chance * ratings[r].Chance; s.Value = (1 + percentages[p].Value) * (1 + StatConversion.GetSpellHasteFromRating( ratings[r].Value + Stats.HasteRating)) * (1 + Stats.SpellHaste); Haste.Add(s); AvgHaste += s.Chance * s.Value; } } // calculate mana procs Stats procStats = new Stats(); foreach (SpecialEffect effect in Stats.SpecialEffects()) { if (!periods.ContainsKey((int)effect.Trigger)) { continue; } Stats proc = effect.GetAverageStats( periods[(int)effect.Trigger], chances[(int)effect.Trigger], CalculationsWarlock.AVG_UNHASTED_CAST_TIME, Options.Duration); if (proc.ManaRestore > 0) { proc.ManaRestore *= Options.Duration; } procStats.Accumulate(proc); } Stats.Mana += procStats.Mana; Stats.ManaRestore += procStats.ManaRestore; Stats.ManaRestoreFromBaseManaPPM += procStats.ManaRestoreFromBaseManaPPM; Stats.ManaRestoreFromMaxManaPerSecond += procStats.ManaRestoreFromMaxManaPerSecond; Stats.Mp5 += procStats.Mp5; }
private void CalcHasteAndManaProcs() { float nonProcHaste = StatUtils.CalcSpellHaste(PreProcStats, CalcOpts.PlayerLevel); if (CalcOpts.NoProcs) { WeightedStat staticHaste = new WeightedStat(); staticHaste.Chance = 1f; staticHaste.Value = nonProcHaste; Haste = new List<WeightedStat> { staticHaste }; AvgHaste = nonProcHaste; return; } // the trigger rates are all guestimates at this point, since the // real values depend on haste (which obviously has not been // finalized yet) Dictionary<int, float> periods = new Dictionary<int, float>(); Dictionary<int, float> chances = new Dictionary<int, float>(); float corruptionPeriod = 0f; if (CalcOpts.GetActiveRotation().Contains("Corruption")) { corruptionPeriod = 3.1f / nonProcHaste; } PopulateTriggers(periods, chances, CalculationsWarlock.AVG_UNHASTED_CAST_TIME / nonProcHaste + CalcOpts.Latency, 1 / 1.5f, corruptionPeriod, 1f); // calculate the haste procs Haste = new List<WeightedStat>(); WeightedStat[] percentages = GetUptimes(Stats, periods, chances, s => s.SpellHaste, (a, b, c, d, e, f, g, h) => SpecialEffect.GetAverageCombinedUptimeCombinationsMultiplicative(a, b, c, d, e, f, g, h)); WeightedStat[] ratings = GetUptimes(Stats, periods, chances, s => s.HasteRating, (a, b, c, d, e, f, g, h) => SpecialEffect.GetAverageCombinedUptimeCombinations(a, b, c, d, e, f, g, h)); for (int p = percentages.Length, f = 0; --p >= 0; ) { if (percentages[p].Chance == 0) { continue; } for (int r = ratings.Length; --r >= 0; ++f) { if (ratings[r].Chance == 0) { continue; } WeightedStat s = new WeightedStat(); s.Chance = percentages[p].Chance * ratings[r].Chance; s.Value = (1 + percentages[p].Value) * (1 + StatUtils.GetSpellHasteFromRating(ratings[r].Value + Stats.HasteRating, CalcOpts.PlayerLevel)) * (1 + Stats.SpellHaste); Haste.Add(s); AvgHaste += s.Chance * s.Value; } } // calculate mana procs Stats procStats = new Stats(); foreach (SpecialEffect effect in Stats.SpecialEffects()) { if (!periods.ContainsKey((int)effect.Trigger)) { continue; } Stats proc = effect.GetAverageStats(periods[(int)effect.Trigger], chances[(int)effect.Trigger], CalculationsWarlock.AVG_UNHASTED_CAST_TIME, BossOpts.BerserkTimer); if (proc.ManaRestore > 0) { proc.ManaRestore *= BossOpts.BerserkTimer; } procStats.Accumulate(proc); } Stats.Mana += procStats.Mana; Stats.ManaRestore += procStats.ManaRestore; Stats.ManaRestoreFromMaxManaPerSecond += procStats.ManaRestoreFromMaxManaPerSecond; Stats.Mp5 += procStats.Mp5; }
private Stats IterativeSpecialEffectsStats(DPSWarrCharacter charStruct, List<SpecialEffect> specialEffects, List<SpecialEffect> critEffects, Dictionary<Trigger, float> triggerIntervals, Dictionary<Trigger, float> triggerChances, float oldFlurryUptime, bool iterate, Base.StatsWarrior iterateOld, Base.StatsWarrior originalStats) { #if DEBUG //ConstructionCounts["IterativeSpecialEffectsStats"]++; #endif WarriorTalents talents = charStruct.Char.WarriorTalents; float fightDuration = charStruct.BossOpts.BerserkTimer; Base.StatsWarrior statsProcs = new Base.StatsWarrior(); try { float dmgTakenInterval = fightDuration / charStruct.BossOpts.AoETargsFreq; //float attempted = charStruct.Rot.AttemptedAtksOverDur; //float land = charStruct.Rot.LandedAtksOverDur; //float crit = charStruct.Rot.CriticalAtksOverDur; //int LevelDif = charStruct.bossOpts.Level - charStruct.Char.Level; List<Trigger> critTriggers = new List<Trigger>(); List<float> critWeights = new List<float>(); bool needsHitTableReset = false; foreach (SpecialEffect effect in critEffects) { needsHitTableReset = true; critTriggers.Add(effect.Trigger); critWeights.Add(1f); } foreach (SpecialEffect effect in specialEffects) { #region old arp code /*if (effect.Stats.ArmorPenetrationRating > 0) { float arpenBuffs = ((combatFactors.CmhItemType == ItemType.TwoHandMace) ? talents.MaceSpecialization * 0.03f : 0.00f) + (!calcOpts.FuryStance ? (0.10f + originalStats.BonusWarrior_T9_2P_ArP) : 0.0f); float OriginalArmorReduction = StatConversion.GetArmorDamageReduction(Char.Level, (int)StatConversion.NPC_ARMOR[LevelDif], originalStats.ArmorPenetration, arpenBuffs, originalStats.ArmorPenetrationRating); float ProccedArmorReduction = StatConversion.GetArmorDamageReduction(Char.Level, (int)StatConversion.NPC_ARMOR[LevelDif], originalStats.ArmorPenetration + effect.Stats.ArmorPenetration, arpenBuffs, originalStats.ArmorPenetrationRating + effect.Stats.ArmorPenetrationRating); Stats dummyStats = new Stats(); float procUptime = ApplySpecialEffect(effect, Char, Rot, combatFactors, calcOpts, originalStats.Dodge + originalStats.Parry, ref dummyStats); float targetReduction = ProccedArmorReduction * procUptime + OriginalArmorReduction * (1f - procUptime); //float arpDiff = OriginalArmorReduction - targetReduction; float procArp = StatConversion.GetRatingFromArmorReduction(Char.Level, (int)StatConversion.NPC_ARMOR[LevelDif], originalStats.ArmorPenetration, arpenBuffs, targetReduction); statsProcs.ArmorPenetrationRating += (procArp - originalStats.ArmorPenetrationRating); } else */ #endregion float numProcs = 0; if (effect.Stats.ManaorEquivRestore > 0f && effect.Stats.HealthRestoreFromMaxHealth > 0f) { // effect.Duration = 0, so GetAverageStats won't work float value1 = effect.Stats.ManaorEquivRestore; float value2 = effect.Stats.HealthRestoreFromMaxHealth; SpecialEffect dummy = new SpecialEffect(effect.Trigger, new Stats() { ManaorEquivRestore = value1, HealthRestoreFromMaxHealth = value2 }, effect.Duration, effect.Cooldown, effect.Chance) { BypassCache = true }; numProcs = dummy.GetAverageProcsPerSecond(dmgTakenInterval, originalStats.Dodge + originalStats.Parry, 0f, 0f) * fightDuration; statsProcs.ManaorEquivRestore += dummy.Stats.ManaorEquivRestore * numProcs; dummy.Stats.ManaorEquivRestore = 0f; //numProcs = effect.GetAverageProcsPerSecond(triggerIntervals[Trigger.PhysicalCrit], triggerChances[Trigger.PhysicalCrit], 0f, 0f) * fightDuration; //statsProcs.HealthRestoreFromMaxHealth += effect.Stats.HealthRestoreFromMaxHealth * numProcs; ApplySpecialEffect(dummy, charStruct, triggerIntervals, triggerChances, ref statsProcs); } else if (effect.Stats.ManaorEquivRestore > 0f) { // effect.Duration = 0, so GetAverageStats won't work numProcs = effect.GetAverageProcsPerSecond(dmgTakenInterval, originalStats.Dodge + originalStats.Parry, 0f, 0f) * fightDuration; statsProcs.ManaorEquivRestore += effect.Stats.ManaorEquivRestore * numProcs; } else if (effect.Stats.HealthRestoreFromMaxHealth > 0f) { // effect.Duration = 0, so GetAverageStats won't work numProcs = effect.GetAverageProcsPerSecond(dmgTakenInterval, originalStats.Dodge + originalStats.Parry, 0f, 0f) * fightDuration; statsProcs.HealthRestoreFromMaxHealth += effect.Stats.HealthRestoreFromMaxHealth * numProcs; } else { ApplySpecialEffect(effect, charStruct, triggerIntervals, triggerChances, ref statsProcs); } } WeightedStat[] critProcs; if (critEffects.Count == 0) { critProcs = new WeightedStat[] { new WeightedStat() { Value = 0f, Chance = 1f } }; } else if (critEffects.Count == 1) { float interval = triggerIntervals[critEffects[0].Trigger]; float chance = triggerChances[critEffects[0].Trigger]; float upTime = critEffects[0].GetAverageStackSize(interval, chance, charStruct.CombatFactors.CMHItemSpeed, (charStruct.CalcOpts.SE_UseDur ? charStruct.BossOpts.BerserkTimer : 0f)); upTime *= critWeights[0]; critProcs = new WeightedStat[] { new WeightedStat() { Value = critEffects[0].Stats.CritRating, Chance = upTime }, new WeightedStat() { Value = 0f, Chance = 1f - upTime } }; } else { float[] intervals = new float[critEffects.Count]; float[] chances = new float[critEffects.Count]; float[] offset = new float[critEffects.Count]; for (int i = 0; i < critEffects.Count; i++) { intervals[i] = triggerIntervals[critEffects[i].Trigger]; chances[i] = triggerChances[critEffects[i].Trigger]; } critProcs = SpecialEffect.GetAverageCombinedUptimeCombinations(critEffects.ToArray(), intervals, chances, offset, critWeights.ToArray(), charStruct.CombatFactors.CMHItemSpeed, charStruct.BossOpts.BerserkTimer, AdditiveStat.CritRating); } charStruct.CombatFactors.CritProcs = critProcs; float flurryUptime = 0f; if (iterate && talents.Flurry > 0f && charStruct.CombatFactors.FuryStance && charStruct.Char.MainHand != null && charStruct.Char.MainHand.Item != null) { float numFlurryHits = 3f; // default float mhPerc = 1f; // 100% by default float flurryHaste = 0.25f / 3f * talents.Flurry; bool useOffHand = false; float flurryHitsPerSec = charStruct.CombatFactors.TotalHaste * (1f + flurryHaste) / (1f + flurryHaste * oldFlurryUptime); float temp = 1f / charStruct.Char.MainHand.Item.Speed; if (charStruct.Char.OffHand != null && charStruct.Char.OffHand.Item != null) { useOffHand = true; temp += 1f / charStruct.Char.OffHand.Item.Speed; mhPerc = (charStruct.Char.MainHand.Speed / charStruct.Char.OffHand.Speed) / (1f + charStruct.Char.MainHand.Speed / charStruct.Char.OffHand.Speed); if (charStruct.Char.OffHand.Speed == charStruct.Char.MainHand.Speed) numFlurryHits = 4f; } flurryHitsPerSec *= temp; float flurryDuration = numFlurryHits / flurryHitsPerSec; flurryUptime = 1f; foreach (AbilityWrapper aw in charStruct.Rot.DamagingAbilities) { if (aw.Ability.CanCrit && aw.AllNumActivates > 0f) { float tempFactor = (float) Math.Pow(1f - aw.Ability.MHAtkTable.Crit, flurryDuration* (aw.AllNumActivates*aw.Ability.SwingsPerActivate* aw.Ability.AvgTargets/fightDuration)); flurryUptime *= tempFactor; if (aw.Ability.SwingsOffHand && useOffHand) { flurryUptime *= (float) Math.Pow(1f - aw.Ability.OHAtkTable.Crit, flurryDuration* (aw.AllNumActivates*aw.Ability.SwingsPerActivate* aw.Ability.AvgTargets/fightDuration)); } } } flurryUptime *= (float)Math.Pow(1f - charStruct.Rot.DPSWarrChar.Whiteattacks.MHAtkTable.Crit, numFlurryHits * mhPerc); flurryUptime *= (float)Math.Pow(1f - charStruct.Rot.DPSWarrChar.Whiteattacks.OHAtkTable.Crit, numFlurryHits * (1f - mhPerc)); flurryUptime = 1f - flurryUptime; statsProcs.PhysicalHaste = (1f + statsProcs.PhysicalHaste) * (1f + flurryHaste * flurryUptime) - 1f; } charStruct.CombatFactors.StatS = UpdateStatsAndAdd(statsProcs, originalStats, charStruct.Char); charStruct.CombatFactors.InvalidateCache(); //Rot.InvalidateCache(); if (iterate) { const float precisionWhole = 0.01f; const float precisionDec = 0.0001f; if (statsProcs.Agility - iterateOld.Agility > precisionWhole || statsProcs.HasteRating - iterateOld.HasteRating > precisionWhole || statsProcs.HitRating - iterateOld.HitRating > precisionWhole || statsProcs.CritRating - iterateOld.CritRating > precisionWhole || statsProcs.PhysicalHaste - iterateOld.PhysicalHaste > precisionDec || statsProcs.PhysicalCrit - iterateOld.PhysicalCrit > precisionDec || statsProcs.PhysicalHit - iterateOld.PhysicalHit > precisionDec) { if (needsHitTableReset) charStruct.Rot.ResetHitTables(); charStruct.Rot.DoIterations(); CalculateTriggers(charStruct, triggerIntervals, triggerChances); return IterativeSpecialEffectsStats(charStruct, specialEffects, critEffects, triggerIntervals, triggerChances, flurryUptime, true, statsProcs, originalStats); } else { /*int j = 0;*/ } } return statsProcs; } catch (Exception ex) { new Base.ErrorBox() { Title = "Error in creating SpecialEffects Stats", Function = "GetSpecialEffectsStats()", TheException = ex, }.Show(); return new Stats(); } }
private Stats GetCharacterStatsWithTemporaryEffects(Character character, Item additionalItem, /*out WeightedStat[] armorPenetrationUptimes,*/ out WeightedStat[] critRatingUptimes) { RogueTalents talents = character.RogueTalents; #region Spec determination int spec; int assCounter = 0, combatCounter = 0, subtCounter = 0; for (int i = 0; i <= 18; i++) assCounter += int.Parse(talents.ToString()[i].ToString()); for (int i = 19; i <= 37; i++) combatCounter += int.Parse(talents.ToString()[i].ToString()); for (int i = 38; i <= 56; i++) subtCounter += int.Parse(talents.ToString()[i].ToString()); if (assCounter > combatCounter && assCounter > subtCounter) spec = 0; else if (combatCounter > subtCounter) spec = 1; else spec = 2; #endregion CalculationOptionsRogue calcOpts = character.CalculationOptions as CalculationOptionsRogue; BossOptions bossOpts = character.BossOptions; int targetLevel = bossOpts.Level; bool targetPoisonable = calcOpts.TargetPoisonable; Stats statsRace = BaseStats.GetBaseStats(character.Level, character.Class, character.Race); Stats statsItems = GetItemStats(character, additionalItem); Stats statsBuffs = GetBuffsStats(character, calcOpts); SpecialEffect LegendarySpecialEffect = new SpecialEffect(Trigger.MeleeHit, new Stats() { Agility = 2f }, 20f, 0f, 1f, 50); Stats statsSetBonus = new Stats(); int LegendaryPartA; character.SetBonusCount.TryGetValue("Jaws of Retribution", out LegendaryPartA); if (LegendaryPartA >= 2) { statsSetBonus.AddSpecialEffect(LegendarySpecialEffect); } LegendarySpecialEffect = new SpecialEffect(Trigger.MeleeHit, new Stats() { Agility = 5f }, 20f, 0f, 1f, 50); int LegendaryPartB; character.SetBonusCount.TryGetValue("Maw of Oblivion", out LegendaryPartB); if (LegendaryPartB >= 2) { statsSetBonus.AddSpecialEffect(LegendarySpecialEffect); } // Needs work.... LegendarySpecialEffect = new SpecialEffect(Trigger.MeleeHit, new Stats() { Agility = 17f }, 20f, 0f, 1f, 50); Stats LegendaryStats = new Stats(); LegendaryStats.AddSpecialEffect(LegendarySpecialEffect); // Assume it resets every 45 seconds LegendarySpecialEffect = new SpecialEffect(Trigger.MeleeHit, LegendaryStats, 45f, 45f); int LegendaryPartC; character.SetBonusCount.TryGetValue("Fangs of the Father", out LegendaryPartC); if (LegendaryPartC >= 2) { statsSetBonus.AddSpecialEffect(LegendarySpecialEffect); LegendarySpecialEffect = new SpecialEffect(Trigger.MeleeHit, new Stats() { FangsoftheFather = 1f }, 6f, 45f); statsSetBonus.AddSpecialEffect(LegendarySpecialEffect); } Stats statsTalents = new Stats() { BonusAgilityMultiplier = (1f + (spec == 2 ? RV.Mastery.SinisterCallingMult : 0f)) * (1f + RV.LeatherSpecialization) - 1f, BonusAttackPowerMultiplier = (1f + (spec == 1 ? RV.Mastery.VitalityAPMult : 0f)) * (1f + RV.Talents.SavageCombatMult * talents.SavageCombat) - 1f, BonusCritChance = character.ActiveBuffs.Contains(Buff.GetBuffByName("Rampage")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Leader of the Pack")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Honor Among Thieves")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Terrifying Roar")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Furious Howl")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Elemental Oath")) ? 0f : RV.Talents.HonorAmongThievesCritBonus * talents.HonorAmongThieves, BonusDamageMultiplier = RV.Vendetta.DmgMult * talents.Vendetta * (RV.Vendetta.Duration * (talents.GlyphOfVendetta ? 1f + RV.Glyph.VendettaDurationMult : 1f)) / RV.Vendetta.CD + talents.SanguinaryVein * RV.Talents.SanguinaryVein + RV.Mastery.MasterOfSubtletyDmgMult * RV.Mastery.MasterOfSubtletyDuration / (RV.Vanish.CD - RV.Talents.ElusivenessVanishCDReduc * talents.Elusiveness) + (talents.Preparation > 0 ? RV.Mastery.MasterOfSubtletyDmgMult * RV.Mastery.MasterOfSubtletyDuration / (RV.Talents.PreparationCD * talents.Preparation) : 0f), BonusPhysicalDamageMultiplier = character.ActiveBuffs.Contains(Buff.GetBuffByName("Ravage")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Acid Spit")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Brittle Bones")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Blood Frenzy")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Savage Combat")) ? 0f : RV.Talents.SavageCombatMult * talents.SavageCombat, BonusBleedDamageMultiplier = character.ActiveBuffs.Contains(Buff.GetBuffByName("Mangle")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Hemorrhage")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Blood Frenzy")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Gore")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Stampede")) || character.ActiveBuffs.Contains(Buff.GetBuffByName("Tendon Rip")) ? 0f : RV.Hemo.BleedDmgMult * talents.Hemorrhage, PhysicalHit = RV.Talents.PrecisionMult * talents.Precision, SpellHit = RV.Talents.PrecisionMult * talents.Precision, }; Stats statsGearEnchantsBuffs = statsItems + statsBuffs + statsSetBonus; Stats statsTotal = statsRace + statsItems; statsTotal.Accumulate(statsBuffs); statsTotal.Accumulate(statsTalents); 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 - statsRace.Agility) * (1f + statsTotal.BonusAgilityMultiplier)) + statsRace.Agility; statsTotal.AttackPower += (statsTotal.Strength - RV.BaseStatCalcReduc / 2) + RV.APperAgi * (statsTotal.Agility - RV.BaseStatCalcReduc) + RV.BaseStatCalcReduc; statsTotal.AttackPower = (float)Math.Floor(statsTotal.AttackPower * (1f + statsTotal.BonusAttackPowerMultiplier)); statsTotal.Health += (float)Math.Floor((statsTotal.Stamina - RV.BaseStatCalcReduc) * RV.HPPerStam + RV.BaseStatCalcReduc); 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; float hasteBonus = (1f + StatConversion.GetPhysicalHasteFromRating(statsTotal.HasteRating, CharacterClass.Rogue)) * (1f + statsTotal.PhysicalHaste) - 1f; float speedBonus = (1f + hasteBonus) * (1f + RV.SnD.SpeedBonus) * (1f + (spec == 2 ? RV.Mastery.Executioner + RV.Mastery.ExecutionerPerMast * StatConversion.GetMasteryFromRating(statsTotal.MasteryRating) : 0f)) - 1f; float mHSpeed = (character.MainHand == null ? 2 : character.MainHand.Speed); float oHSpeed = (character.OffHand == null ? 2 : character.OffHand.Speed); float meleeHitInterval = 1f / ((mHSpeed + oHSpeed) / speedBonus); //To calculate the Poison hit interval only white attacks are taken into account, IP is assumed on the slowest and DP on the fastest weapon float dPPS = bossOpts.BerserkTimer / (Math.Min(mHSpeed, oHSpeed) / speedBonus) * RV.DP.Chance + (spec == 0 ? RV.Mastery.ImprovedPoisonsDPBonus : 0); float poisonHitInterval = 1 / (Math.Max(mHSpeed, mHSpeed) * RV.IP.Chance * (1f + RV.Mastery.ImprovedPoisonsIPFreqMult) / RV.IP.NormWeapSpeed + dPPS); float hitBonus = StatConversion.GetPhysicalHitFromRating(statsTotal.HitRating) + statsTotal.PhysicalHit; float spellHitBonus = StatConversion.GetSpellHitFromRating(statsTotal.HitRating) + statsTotal.SpellHit; float expertiseBonus = StatConversion.GetDodgeParryReducFromExpertise(StatConversion.GetExpertiseFromRating(statsTotal.ExpertiseRating) + statsTotal.Expertise); float chanceDodge = Math.Max(0f, StatConversion.WHITE_DODGE_CHANCE_CAP[targetLevel - character.Level] - expertiseBonus); float chanceParry = 0f; //Math.Max(0f, StatConversion.WHITE_PARRY_CHANCE_CAP[targetLevel - character.Level] - expertiseBonus); float chanceMiss = Math.Max(0f, StatConversion.WHITE_MISS_CHANCE_CAP[targetLevel - character.Level] - hitBonus); float chanceAvoided = chanceMiss + chanceDodge + chanceParry; float chancePoisonAvoided = Math.Max(0f, StatConversion.GetSpellMiss(character.Level - targetLevel, false) - spellHitBonus); float rawChanceCrit = StatConversion.GetPhysicalCritFromRating(statsTotal.CritRating) + StatConversion.GetPhysicalCritFromAgility(statsTotal.Agility, CharacterClass.Rogue) + statsTotal.PhysicalCrit + StatConversion.NPC_LEVEL_CRIT_MOD[targetLevel - character.Level]; float chanceCrit = rawChanceCrit * (1f - chanceAvoided); float chanceHit = 1f - chanceAvoided; Dictionary<Trigger, float> triggerIntervals = new Dictionary<Trigger, float>(); Dictionary<Trigger, float> triggerChances = new Dictionary<Trigger, float>(); triggerIntervals[Trigger.Use] = 0f; triggerIntervals[Trigger.MeleeHit] = meleeHitInterval; triggerIntervals[Trigger.PhysicalHit] = meleeHitInterval; triggerIntervals[Trigger.MeleeAttack] = meleeHitInterval; triggerIntervals[Trigger.PhysicalAttack] = meleeHitInterval; triggerIntervals[Trigger.MeleeCrit] = meleeHitInterval; triggerIntervals[Trigger.PhysicalCrit] = meleeHitInterval; triggerIntervals[Trigger.DoTTick] = 0f; triggerIntervals[Trigger.DamageDone] = meleeHitInterval / 2f; triggerIntervals[Trigger.DamageOrHealingDone] = meleeHitInterval / 2f; // Need to add Self-heals triggerIntervals[Trigger.SpellHit] = poisonHitInterval; triggerIntervals[Trigger.EnergyOrFocusDropsBelow20PercentOfMax] = 4f; // Approximating as 80% chance every 4 seconds. TODO: Actually model this triggerChances[Trigger.Use] = 1f; triggerChances[Trigger.MeleeHit] = Math.Max(0f, chanceHit); triggerChances[Trigger.PhysicalHit] = Math.Max(0f, chanceHit); triggerChances[Trigger.PhysicalAttack] = triggerChances[Trigger.MeleeAttack] = 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.SpellHit] = Math.Max(0f, 1f - chancePoisonAvoided); triggerChances[Trigger.EnergyOrFocusDropsBelow20PercentOfMax] = 0.80f; // Approximating as 80% chance every 4 seconds. TODO: Actually model this // 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), 1f); } } 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 + RV.APperAgi * statsProcs.Agility; statsProcs.AttackPower = (float)Math.Floor(statsProcs.AttackPower * (1f + statsTotal.BonusAttackPowerMultiplier)); statsProcs.Health += (float)Math.Floor(statsProcs.Stamina * RV.HPPerStam); statsProcs.Armor = (float)Math.Floor(statsProcs.Armor * (1f + statsTotal.BonusArmorMultiplier)); float HighestSecondaryStatValue = statsProcs.HighestSecondaryStat; // how much HighestSecondaryStat to add statsProcs.HighestSecondaryStat = 0f; // remove HighestSecondaryStat stat, since it's not needed if (statsTotal.CritRating > statsTotal.HasteRating && statsTotal.CritRating > statsTotal.MasteryRating) { statsProcs.CritRating += HighestSecondaryStatValue; } else if (statsTotal.HasteRating > statsTotal.CritRating && statsTotal.HasteRating > statsTotal.MasteryRating) { statsProcs.HasteRating += HighestSecondaryStatValue; } else /*if (statsTotal.MasteryRating > statsTotal.CritRating && statsTotal.MasteryRating > statsTotal.HasteRating)*/ { statsProcs.MasteryRating += HighestSecondaryStatValue; } //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 critRatingUptimes = 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) { critRatingUptimes = 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 = (effect.Stats.Agility + effect.Stats.HighestStat + effect.Stats.Paragon) * (1f + statsTotal.BonusAgilityMultiplier); critRatingUptimes = new WeightedStat[] { new WeightedStat() { Chance = uptime, Value = effect.Stats.CritRating + StatConversion.GetCritFromAgility(totalAgi - 10, CharacterClass.Rogue) * 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 - 10, CharacterClass.Rogue) * 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()); critRatingUptimes = critWeights; } return statsTotal; }