public Stats getSpecialEffects(SpecialEffect effect) { Stats statsAverage = new Stats(); if (effect == mainHandEnchant || effect == offHandEnchant) { if (mainHandEnchant != null && !mhProcessed) { statsAverage.Accumulate(mainHandEnchant.Stats, GetMHUptime()); mhProcessed = true; } else if (offHandEnchant != null && !ohProcessed) { statsAverage.Accumulate(offHandEnchant.Stats, GetOHUptime()); ohProcessed = true; } } else if (effect.Trigger == Trigger.Use) { effect.AccumulateAverageStats(statsAverage); foreach (SpecialEffect e in effect.Stats.SpecialEffects()) statsAverage.Accumulate(this.getSpecialEffects(e) * (effect.Duration / effect.Cooldown)); } else { SetTriggerChanceAndSpeed(effect); foreach (SpecialEffect e in effect.Stats.SpecialEffects()) // deal with secondary effects { statsAverage.Accumulate(this.getSpecialEffects(e)); } if (effect.MaxStack > 1) { if (effect.Stats.MoteOfAnger > 0) { // When in effect stats, MoteOfAnger is % of melee hits // When in character stats, MoteOfAnger is average procs per second statsAverage.Accumulate(new Stats() { MoteOfAnger = effect.Stats.MoteOfAnger * effect.GetAverageProcsPerSecond(trigger, chance, unhastedAttackSpeed, 0f) / effect.MaxStack }); } else { float timeToMax = (float)Math.Min(_cs.FightLength, effect.GetChance(unhastedAttackSpeed) * trigger * effect.MaxStack); float buffDuration = _cs.FightLength; if (effect.Stats.AttackPower == 250f || effect.Stats.AttackPower == 215f || effect.Stats.HasteRating == 57f || effect.Stats.HasteRating == 64f) { buffDuration = 20f; } if (timeToMax * .5f > buffDuration) { timeToMax = 2 * buffDuration; } statsAverage.Accumulate(effect.Stats * (effect.MaxStack * (((buffDuration) - .5f * timeToMax) / (buffDuration)))); } } else { effect.AccumulateAverageStats(statsAverage, trigger, chance, unhastedAttackSpeed); } } return statsAverage; }
public void UpdateStatsGraph(Character character, Stats[] statsList, Color[] colors, int scale, string explanatoryText, string calculation) { CharacterCalculationsBase baseCalc = Calculations.GetCharacterCalculations(character); float baseFigure = GetCalculationValue(baseCalc, calculation); if (statsList.Length == 0 || statsList.Length > colors.Length) { return; // more than 12 elements for the array would run out of colours } Point[][] points = new Point[statsList.Length][]; Chart.Series.Clear(); for (int index = 0; index < statsList.Length; index++) { Stats newStats = new Stats(); points[index] = new Point[2 * scale + 1]; newStats.Accumulate(statsList[index], -scale - 1); for (int count = -scale; count <= scale; count++) { newStats.Accumulate(statsList[index]); CharacterCalculationsBase currentCalc = Calculations.GetCharacterCalculations(character, new Item() { Stats = newStats }, false, false, false); float currentFigure = GetCalculationValue(currentCalc, calculation); float dpsChange = currentFigure - baseFigure; points[index][count + scale] = new Point(count, dpsChange); } Style dataPointStyle = new Style(typeof(LineDataPoint)); dataPointStyle.Setters.Add(new Setter(DataPoint.TemplateProperty, Resources["InvisibleDataPointTemplate"])); dataPointStyle.Setters.Add(new Setter(DataPoint.BackgroundProperty, new SolidColorBrush(colors[index]))); Chart.Series.Add(new LineSeries() { Title = statsList[index].ToString(), ItemsSource = points[index], IndependentValuePath = "X", DependentValuePath = "Y", DataPointStyle = dataPointStyle, }); } Chart.Axes.Clear(); Chart.Axes.Add(new LinearAxis() { Orientation = AxisOrientation.X, Title = "Stat Change", ShowGridLines = true, }); Chart.Axes.Add(new LinearAxis() { Orientation = AxisOrientation.Y, Title = calculation, ShowGridLines = true, }); orgDataDirty = true; }
public static void RenderStatsGraph(Graphics g, int graphWidth, int graphHeight, Character character, Stats[] statsList, Color[] colors, int scale, string explanatoryText, string calculation, Style style) { CharacterCalculationsBase baseCalc = Calculations.GetCharacterCalculations(character); float baseFigure = GetCalculationValue(baseCalc, calculation); g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; float graphOffset = graphWidth / 2.0f, graphStep = (graphWidth - 100) / 2.0f / scale; if (statsList.Length == 0 || statsList.Length > colors.Length) return; // more than 12 elements for the array would run out of colours float minDpsChange = 0f, maxDpsChange = 0f; PointF[][] points = new PointF[statsList.Length][]; for (int index = 0; index < statsList.Length; index++) { Stats newStats = new Stats(); points[index] = new PointF[2 * scale + 1]; newStats.Accumulate(statsList[index], -scale - 1); for (int count = -scale; count <= scale; count++) { newStats.Accumulate(statsList[index]); CharacterCalculationsBase currentCalc = Calculations.GetCharacterCalculations(character, new Item() { Stats = newStats }, false, false, false); float currentFigure = GetCalculationValue(currentCalc, calculation); float dpsChange = currentFigure - baseFigure; points[index][count + scale] = new PointF(graphOffset + count * graphStep, dpsChange); if (dpsChange < minDpsChange) minDpsChange = dpsChange; if (dpsChange > maxDpsChange) maxDpsChange = dpsChange; } } float DpsVariance = maxDpsChange - minDpsChange; if (DpsVariance == 0) DpsVariance = 1; for (int index = 0; index < statsList.Length; index++) { for (int count = -scale; count <= scale; count++) { points[index][count + scale].Y = (int)((maxDpsChange - points[index][count + scale].Y) * (graphHeight - 48) / DpsVariance) + 20; } Brush statBrush = new SolidBrush(colors[index]); switch (style) { case Style.DpsWarr: g.DrawLines(new Pen(statBrush, 3), points[index]); break; case Style.Mage: g.DrawLines(new Pen(statBrush, 1), points[index]); break; } } RenderGrid(g, graphWidth, graphHeight, character, statsList, colors, scale, 1f, "F1", explanatoryText, calculation, style, minDpsChange, maxDpsChange, DpsVariance, true); }
/// <summary> /// Generate a graph to compare stat progressions. /// </summary> /// <param name="character"></param> /// <param name="statsList">Array of Stats objects that contain what stats to evalutate.</param> /// <param name="colors">Color for each stat object. Should be same or > # of elements as statsList</param> /// <param name="scale">How many increments of the stats values should be performed? Larger values mean much larger calculation times.</param> /// <param name="explanatoryText"></param> /// <param name="calculation">[In] Generally pass in null or "Overall Points" otherwise pass in the specific subpoint string you are concerned about.</param> public void UpdateStatsGraph(Character character, Stats[] statsList, Color[] colors, int scale, string explanatoryText, string calculation) { CharacterCalculationsBase baseCalc = Calculations.GetCharacterCalculations(character); float baseFigure = GetCalculationValue(baseCalc, calculation); if (statsList.Length == 0 || statsList.Length > colors.Length) return; // more than 12 elements for the array would run out of colours Point[][] points = new Point[statsList.Length][]; Chart.Series.Clear(); for (int index = 0; index < statsList.Length; index++) { Stats newStats = new Stats(); points[index] = new Point[2 * scale + 1]; newStats.Accumulate(statsList[index], -scale - 1); for (int count = -scale; count <= scale; count++) { newStats.Accumulate(statsList[index]); CharacterCalculationsBase currentCalc = Calculations.GetCharacterCalculations(character, new Item() { Stats = newStats }, false, false, false); float currentFigure = GetCalculationValue(currentCalc, calculation); float dpsChange = currentFigure - baseFigure; points[index][count + scale] = new Point(count, dpsChange); } Style dataPointStyle = new Style(typeof(LineDataPoint)); dataPointStyle.Setters.Add(new Setter(DataPoint.TemplateProperty, Resources["InvisibleDataPointTemplate"])); dataPointStyle.Setters.Add(new Setter(DataPoint.BackgroundProperty, new SolidColorBrush(colors[index]))); Chart.Series.Add(new LineSeries() { Title = statsList[index].ToString(), ItemsSource = points[index], IndependentValuePath = "X", DependentValuePath = "Y", DataPointStyle = dataPointStyle, }); } Chart.Axes.Clear(); Chart.Axes.Add(new LinearAxis() { Orientation = AxisOrientation.X, Title = "Stat Change", ShowGridLines = true, }); Chart.Axes.Add(new LinearAxis() { Orientation = AxisOrientation.Y, Title = calculation, ShowGridLines = true, }); orgDataDirty = true; }
private Stats CalcCritProcs() { if (Options.NoProcs) { return(new Stats()); } Dictionary <int, float> periods = new Dictionary <int, float>(); Dictionary <int, float> chances = new Dictionary <int, float>(); PopulateTriggers(periods, chances); Stats procStats = new Stats(); foreach (SpecialEffect effect in Stats.SpecialEffects()) { if (!periods.ContainsKey((int)effect.Trigger)) { continue; } Stats proc = CalcNormalProc(effect, periods, chances); procStats.Accumulate(proc); if (effect.Trigger == Trigger.Use && !IsDoublePot(effect)) { ExtraCritAtMax += StatUtils.CalcSpellCrit(effect.Stats) - StatUtils.CalcSpellCrit(proc); } } return(procStats); }
public AbilityHandler(Stats s, CombatTable t, Character c, CalculationOptionsDPSDK opts) { stats = new Stats(); stats.Accumulate(s); combatTable = t; character = c; calcOpts = opts; talents = character.DeathKnightTalents; initialize(); }
public Stats getSpecialEffects() { Stats statsAverage = new Stats(); foreach (SpecialEffect effect in _stats.SpecialEffects()) { statsAverage.Accumulate(getSpecialEffects(effect)); } AddParagon(statsAverage); AddHighestStat(statsAverage); AddHighestSecondaryStat(statsAverage); return statsAverage; }
public Stats getSpecialEffects() { Stats statsAverage = new Stats(); foreach (SpecialEffect effect in _stats.SpecialEffects()) { statsAverage.Accumulate(getSpecialEffects(effect)); } AddParagon(statsAverage); AddHighestStat(statsAverage); AddDeathBringerProc(statsAverage); return(statsAverage); }
protected override void Calculate() { // Hack to not count holy shield when we are trying to calculate crit chance without it if (!UseHolyShield && CalcOpts.UseHolyShield) { Stats.Accumulate(new Stats() { Block = -0.3f }); } float tableSize = 0.0f; #if (RAWR3) int targetLevel = BossOpts.Level; #else int targetLevel = CalcOpts.TargetLevel; #endif // Miss Miss = Math.Min(1.0f - tableSize, Lookup.AvoidanceChance(Character, Stats, HitResult.Miss, targetLevel)); tableSize += Miss; // Dodge Dodge = Math.Min(1.0f - tableSize, Lookup.AvoidanceChance(Character, Stats, HitResult.Dodge, targetLevel)); tableSize += Dodge; // Parry Parry = Math.Min(1.0f - tableSize, Lookup.AvoidanceChance(Character, Stats, HitResult.Parry, targetLevel)); tableSize += Parry; // Block if (Character.OffHand != null && Character.OffHand.Type == ItemType.Shield) { Block = Math.Min(1.0f - tableSize, Lookup.AvoidanceChance(Character, Stats, HitResult.Block, targetLevel)); tableSize += Block; } // Critical Hit Critical = Math.Min(1.0f - tableSize, Lookup.TargetCritChance(Character, Stats, targetLevel)); tableSize += Critical; // Normal Hit Hit = Math.Max(0.0f, 1.0f - tableSize); // Partial Resists don't belong in the combat table Resist = 1.0f - StatConversion.GetResistanceTable(targetLevel, Character.Level, Stats.FrostResistance, 0.0f)[0]; // Hack to put back holy shield when we are trying to calculate crit chance without it if (!UseHolyShield && CalcOpts.UseHolyShield) { Stats.Accumulate(new Stats() { Block = 0.3f }); } }
public virtual void AccumulateBuffsStats(Stats stats, IEnumerable <string> buffs) { foreach (string buffName in buffs) { if (!string.IsNullOrEmpty(buffName)) { Buff buff = Buff.GetBuffByName(buffName); if (buff != null) { stats.Accumulate(buff.Stats); } } } }
/// <summary> /// Beware when updating: The spells from an earlier returned Rotation are references to the SpellBox from this Estimation. /// </summary> /// <param name="baseStats"></param> /// <param name="procStats"></param> /// <param name="talents"></param> /// <param name="calcOpts"></param> public void Update(Stats baseStats, Stats procStats, PriestTalents talents, CalculationOptionsShadowPriest calcOpts) { this.baseStats = baseStats; this.procStats = procStats; this.talents = talents; this.calcOpts = calcOpts; Stats addedStats = baseStats.Clone(); addedStats.Accumulate(procStats); CombatFactors combatFactors = new CombatFactors(talents, addedStats, Math.Max(calcOpts.NumberOfTargets - 1, 0), calcOpts.LatencyCast, calcOpts.LatencyGcd); spellbox.Update(combatFactors); }
/// <summary> /// Beware when updating: The spells from an earlier returned Rotation are references to the SpellBox from this Estimation. /// </summary> /// <param name="baseStats"></param> /// <param name="procStats"></param> /// <param name="talents"></param> /// <param name="calcOpts"></param> public void Update(Stats baseStats, Stats procStats, ShamanTalents talents, CalculationOptionsElemental calcOpts) { this.baseStats = baseStats; this.procStats = procStats; this.talents = talents; this.calcOpts = calcOpts; Stats addedStats = baseStats.Clone(); addedStats.Accumulate(procStats); CombatFactors combatFactors = new CombatFactors(talents, addedStats, Math.Max(calcOpts.NumberOfTargets - 1, 0), calcOpts.LatencyCast, calcOpts.LatencyGcd, calcOpts.UseFireNova, calcOpts.UseChainLightning, calcOpts.UseDpsTotem); spellbox.Update(combatFactors); }
public void CalcStats1() { WarlockTalents talents = Mommy.Talents; //Stats.Strength = 453f; //Stats.Agility = 883f; Stats.SpellPower = CalcSpellPower(); Stats.AttackPower = CalcAttackPower(); Stats.PhysicalCrit = .0328f; Stats.Accumulate(Mommy.PetBuffs); FinalizeModifiers(); SpecialModifiers.Accumulate(TotalModifiers); MeleeModifiers.Accumulate(TotalModifiers); Stats.SpellHaste = GetHasteWithProcs(s => s.SpellHaste); Stats.PhysicalHaste = GetHasteWithProcs(s => s.PhysicalHaste); }
public void CalcStats1() { // Stam & Int have to happen in this stage, so that Demonic // Knowledge is in effect when calculating the benefit of a // Demonic Pact proc. // Crit has to happen in this stage, so that Empowered Imp gives // the warlocks the right amount of crit. WarlockTalents talents = Mommy.Talents; float vitality = talents.FelVitality * .05f; float tacticsCrit = .02f * talents.DemonicTactics + .1f * talents.ImprovedDemonicTactics * (Mommy.CalcSpellCrit() - Mommy.Stats.SpellCritOnTarget); Stats = new Stats() { Stamina = BaseStamina + StaminaCoef * Mommy.CalcStamina(), Intellect = BaseIntellect + IntellectCoef * Mommy.CalcIntellect(), Strength = 297f, Agility = 90f, BonusStaminaMultiplier = vitality, BonusIntellectMultiplier = vitality, SpellCrit = BaseSpellCrit + tacticsCrit + Mommy.Stats.Warlock2T9, SpellPower = BaseSpellPower, AttackPower = BaseAttackPower, PhysicalCrit = .0329f + tacticsCrit + Mommy.Stats.Warlock2T9, }; Stats.Accumulate(Mommy.PetBuffs); Mommy.Add4pT10(TotalModifiers); FinalizeModifiers(); SpecialModifiers.Accumulate(TotalModifiers); MeleeModifiers.Accumulate(TotalModifiers); Stats.SpellHaste = GetHasteWithProcs(s => s.SpellHaste); Stats.PhysicalHaste = GetHasteWithProcs(s => s.PhysicalHaste); }
protected static void AccumulateSpecialEffects(Character character, ref Stats stats, float FightDuration, Dictionary <Trigger, float> triggerIntervals, Dictionary <Trigger, float> triggerChances, List <SpecialEffect> effects, float weight) { foreach (SpecialEffect effect in effects) { Stats effectStats = effect.Stats; float upTime = 0f; if (effect.Trigger == Trigger.Use) { if (effect.Stats._rawSpecialEffectDataSize >= 1) { upTime = effect.GetAverageUptime(0f, 1f, 0, FightDuration); List <SpecialEffect> nestedEffect = new List <SpecialEffect>(effect.Stats.SpecialEffects()); Stats _stats2 = effectStats.Clone(); AccumulateSpecialEffects(character, ref _stats2, effect.Duration, triggerIntervals, triggerChances, nestedEffect, upTime); effectStats = _stats2; } else { upTime = effect.GetAverageStackSize(0f, 1f, 0, FightDuration); } } else if (effect.Duration == 0f) { upTime = effect.GetAverageProcsPerSecond(triggerIntervals[effect.Trigger], triggerChances[effect.Trigger], 0, FightDuration); } else if (triggerIntervals.ContainsKey(effect.Trigger)) { upTime = effect.GetAverageStackSize(triggerIntervals[effect.Trigger], triggerChances[effect.Trigger], 0, FightDuration); } if (upTime > 0f) { stats.Accumulate(effectStats, upTime * weight); } } }
public override Stats GetCharacterStats(Character character, Item additionalItem) { WarlockTalents talents = character.WarlockTalents; CalculationOptionsWarlock calcOpts = character.CalculationOptions as CalculationOptionsWarlock; BossOptions bossOpts = character.BossOptions; Stats stats = BaseStats.GetBaseStats(character); AccumulateItemStats(stats, character, additionalItem); AccumulateBuffsStats(stats, character.ActiveBuffs); AccumulateSetBonusStats(stats, character.SetBonusCount); ApplyPetsRaidBuff(stats, calcOpts.Pet, talents, character.ActiveBuffs, calcOpts); float[] demonicEmbraceValues = { 0f, .04f, .07f, .1f }; Stats statsTalents = new Stats { BonusStaminaMultiplier = demonicEmbraceValues[talents.DemonicEmbrace] //Demonic Embrace }; if (talents.Eradication > 0) { float[] eradicationValues = { 0f, .06f, .12f, .20f }; statsTalents.AddSpecialEffect( new SpecialEffect( Trigger.CorruptionTick, new Stats() { SpellHaste = eradicationValues[talents.Eradication] }, 10f, //duration 0f, //cooldown .06f)); //chance } stats.Accumulate(statsTalents); stats.ManaRestoreFromMaxManaPerSecond = Math.Max( stats.ManaRestoreFromMaxManaPerSecond, .001f * Spell.CalcUprate(talents.SoulLeech > 0 ? 1f : 0f, 15f, bossOpts.BerserkTimer * 1.1f)); return(stats); }
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 static Stats GetBaseStats(int level, CharacterClass characterClass, CharacterRace characterRace, DruidForm characterForm) { // Health, Mana and some other things are same for every race. lock (syncLock) { #region Cache if (level == _lastLevel && characterClass == _lastClass && characterRace == _lastRace && characterForm == _lastForm) { return(_lastStats.Clone()); } _lastLevel = level; _lastClass = characterClass; _lastRace = characterRace; _lastForm = characterForm; #endregion Stats S = new Stats(); #region Race, not class benefit // Most Level 85 Race and Class Stats come from: // http://code.google.com/p/simulationcraft/source/browse/branches/cataclysm/engine/sc_rating.cpp?r=6207 // When they were still at 80 as of Jan 01st, 2011 // From SimCraft Stats race = new Stats(); switch (characterRace) { // Alliance case CharacterRace.Human: race.Strength = 20; race.Agility = 20; race.Stamina = 20; race.Intellect = 20; race.Spirit = 20; break; case CharacterRace.Dwarf: race.Strength = 25; race.Agility = 16; race.Stamina = 21; race.Intellect = 19; race.Spirit = 19; break; case CharacterRace.NightElf: race.Strength = 16; race.Agility = 24; race.Stamina = 20; race.Intellect = 20; race.Spirit = 20; break; case CharacterRace.Gnome: race.Strength = 15; race.Agility = 22; race.Stamina = 20; race.Intellect = 24; race.Spirit = 20; break; case CharacterRace.Draenei: race.Strength = 21; race.Agility = 17; race.Stamina = 20; race.Intellect = 20; race.Spirit = 22; break; case CharacterRace.Worgen: race.Strength = 23; race.Agility = 22; race.Stamina = 20; race.Intellect = 16; race.Spirit = 19; break; // Horde case CharacterRace.Orc: race.Strength = 23; race.Agility = 17; race.Stamina = 21; race.Intellect = 17; race.Spirit = 22; break; case CharacterRace.Undead: race.Strength = 19; race.Agility = 18; race.Stamina = 20; race.Intellect = 18; race.Spirit = 25; break; case CharacterRace.Tauren: race.Strength = 25; race.Agility = 16; race.Stamina = 21; race.Intellect = 16; race.Spirit = 22; break; case CharacterRace.Troll: race.Strength = 21; race.Agility = 22; race.Stamina = 20; race.Intellect = 16; race.Spirit = 21; break; case CharacterRace.BloodElf: race.Strength = 17; race.Agility = 22; race.Stamina = 20; race.Intellect = 23; race.Spirit = 18; break; case CharacterRace.Goblin: race.Strength = 17; race.Agility = 22; race.Stamina = 20; race.Intellect = 23; race.Spirit = 20; break; default: { break; } } ; // From Chardev (85) //Class Str Agi Sta Int Spi //Druid 76 69 86 136 153 //Shaman 111 60 128 119 136 //Death Knight 171 101 154 16 44 //Hunter 60 178 119 77 88 //Mage 17 26 43 187 175 //Paladin 144 77 136 86 97 //Priest 26 34 51 188 183 //Rogue 102 186 94 26 53 //Warlock 43 51 76 161 166 //Warrior 169 103 153 17 44 #endregion #region Base Stats #region All Classes S.Miss = 0.05f; S.Block = 0.00f; S.Parry = 0.00f; #endregion switch (characterClass) { #region Death Knight case CharacterClass.DeathKnight: Stats dk = new Stats() { Strength = 171, Agility = 101, Stamina = 274, Intellect = 16, Spirit = 44, Health = 43025f, Dodge = 0.05f, Parry = 0.05f, Block = 0.00f, PhysicalCrit = 0.0049f, AttackPower = 595f, }; S.Accumulate(race); S.Accumulate(dk); break; #endregion #region Druid case CharacterClass.Druid: Stats druid = new Stats() { Strength = 76, Agility = 69, Stamina = 86, Intellect = 136, Spirit = 153, Health = 39533f, Mana = 18635f, Dodge = 0.03758f, Parry = 0.05f, Block = 0.05f, PhysicalCrit = 0.03192f, AttackPower = 613f, SpellCrit = 0.0185f, Mp5 = 931f, }; S.Accumulate(race); S.Accumulate(druid); switch (characterForm) { case DruidForm.Moonkin: case DruidForm.Caster: S.AttackPower = -10; S.PhysicalCrit = 0.0743f; S.Dodge = 0.0556970f; //?? break; case DruidForm.Bear: S.AttackPower = 255; S.PhysicalCrit = 0.074755f; S.Dodge = 0.0556970f; S.BonusStaminaMultiplier = 0.2f; break; case DruidForm.Cat: S.AttackPower = 235; S.PhysicalCrit = 0.074755f; S.Dodge = 0.0556970f; break; default: break; } break; #endregion #region Hunter case CharacterClass.Hunter: Stats hun = new Stats() { // Stats updated 8/19/2011 4.2 w/ Troll Hunter: Tsevon @ US-Dragonmaw w/ no spec. Strength = 60, Agility = 178, Stamina = 119, Intellect = 77, Spirit = 88, Health = 39037, Dodge = 0.03758f, Parry = 0.05f, // This assumes ALL AP from full AP += AGI * 2 // So naked Hunter has 31 AP un accounted for. // Naked troll, no gear, no spec, LW & Skinning. PhysicalCrit = 0, AttackPower = 31f, RangedAttackPower = 31f, }; S.Accumulate(race); S.Accumulate(hun); break; #endregion #region Mage case CharacterClass.Mage: Stats mag = new Stats() { Strength = 17, Agility = 26, Stamina = 43, Intellect = 187, Spirit = 175, Health = 36853f, Mana = 17138f, Dodge = 0.03758f, Parry = 0.05f, }; S.Accumulate(race); S.Accumulate(mag); break; #endregion #region Paladin case CharacterClass.Paladin: Stats pal = new Stats() { Strength = 144, Agility = 77, Stamina = 136, Intellect = 86, Spirit = 97, Health = 43285f, Mana = 23422, Dodge = 0.05f, Parry = 0.05f, Block = 0.05f, PhysicalCrit = 0.00652f, AttackPower = 235f, SpellCrit = 0.033355f, }; S.Accumulate(race); S.Accumulate(pal); break; #endregion #region Priest case CharacterClass.Priest: // added adjustments to the base race here because the math using the previous stats // just don't work for the in game calculations on priest tests. // also unsure how these changes would effect other modules if moved up. // adding or subracting from the priest stats don't work and throws all other class // calculations off. switch (characterRace) { case CharacterRace.Human: race.Spirit = 19; break; case CharacterRace.Gnome: race.Intellect = 23; break; case CharacterRace.Goblin: race.Spirit = 18; break; } Stats pri = new Stats() { Strength = 26, Agility = 34, Stamina = 51, Intellect = 169, Spirit = 178, Health = 43285f, Mana = 20590f, Dodge = 0.0337780f, Parry = 0.05f, PhysicalCrit = 0.027f, SpellCrit = 0.012375f, }; pri.Mp5 = pri.Mana * 0.05f; // Always 5% of base mana in regen. S.Accumulate(race); S.Accumulate(pri); break; #endregion #region Rogue case CharacterClass.Rogue: Stats rog = new Stats() { Strength = 102, Agility = 186, Stamina = 94, Intellect = 26, Spirit = 53, Health = 40529f, Dodge = 0.03758f, Parry = 0.05f, PhysicalCrit = 0.03192f, AttackPower = 613f, }; S.Accumulate(race); S.Accumulate(rog); break; #endregion #region Shaman case CharacterClass.Shaman: Stats sha = new Stats() { Strength = 111, Agility = 60, Stamina = 128, Intellect = 119, Spirit = 136, Health = 37037f, Mana = 23430f, Dodge = 0.0193f, Parry = 0.05f, Block = 0.05f, PhysicalCrit = 0.02910375f, AttackPower = 140f, SpellCrit = 0.022057f, SpellPower = -10, }; S.Accumulate(race); S.Accumulate(sha); break; #endregion #region Warlock case CharacterClass.Warlock: Stats warlock = new Stats() { Strength = 43, Agility = 51, Stamina = 76, Intellect = 153, Spirit = 161, Health = 38184f, Mana = 20553f, Dodge = 0.0238110f, Parry = 0.05f, Block = 0.05f, PhysicalCrit = 0.026219999417663f, SpellCrit = 0.017000000923872f, }; S.Accumulate(race); S.Accumulate(warlock); break; #endregion #region Warrior case CharacterClass.Warrior: Stats war = new Stats() { Strength = 169, Agility = 103, Stamina = 153, Intellect = 17, Spirit = 44, Health = 43285f, Dodge = 0.05f, Parry = 0.05f, Block = 0.05f, PhysicalCrit = 0.03192f, AttackPower = 613f, }; S.Accumulate(race); S.Accumulate(war); break; #endregion #region No Class default: break; #endregion } #endregion #region Racials // Resistance do not stack with other buffs. Until then I'll commenting them out if (characterRace == CharacterRace.Gnome) //CATA: changed from 5% int to 5% mana { // S.ArcaneResistance += 85f; S.BonusManaMultiplier = 0.05f; //S.BonusIntellectMultiplier = 0.05f; } else if (characterRace == CharacterRace.Human) { S.BonusSpiritMultiplier = 0.03f; // Patch 4.0.6+ changed from a 3 minute cooldown to 2 minute cooldown S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { PVPTrinket = 1 }, 0f, 120f)); } else if (characterRace == CharacterRace.NightElf) { // S.NatureResistance += 85f; S.Miss += 0.02f; } else if (characterRace == CharacterRace.Dwarf) { // S.FrostResistance += 85f; // Armor +10% for 8 Sec. S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { BaseArmorMultiplier = .1f }, 8, 120)); // TODO: Add debuff removal. Doesn't work on all bosses so not sure if we want to. } else if (characterRace == CharacterRace.Draenei) { // S.ArcaneResistance += 85f; S.SpellHit += 0.01f; S.PhysicalHit += 0.01f; // Patch 4.0.6+ changed from a scaling Health restore to a flat 20% of max health S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { HealthRestoreFromMaxHealth = 0.2f / 15f }, 15f, 180f)); } else if (characterRace == CharacterRace.Worgen) { // S.NatureResistance = 64f; // S.ShadowResistance = 64f; // Patch 4.0.6+ Darkflight changed from a 3 minute CD to a 2 minute CD S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { MovementSpeed = 0.40f }, 10f, 120f)); S.PhysicalCrit += 0.01f; S.SpellCrit += 0.01f; } else if (characterRace == CharacterRace.Tauren) { // S.NatureResistance = 85f; S.Health = (float)Math.Floor(S.Health * 1.05f); } else if (characterRace == CharacterRace.Troll) { S.SnareRootDurReduc = .15f; if (characterClass == CharacterClass.DeathKnight || characterClass == CharacterClass.Warrior || characterClass == CharacterClass.Rogue) { S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { PhysicalHaste = 0.2f }, 10f, 180f)); } else { S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { SpellHaste = 0.2f, PhysicalHaste = 0.2f }, 10f, 180f)); } } else if (characterRace == CharacterRace.Undead) { S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { FearDurReduc = 1f }, .1f, 120f)); } else if (characterRace == CharacterRace.Orc) { S.StunDurReduc = 0.15f; if (characterClass == CharacterClass.Shaman) { S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { AttackPower = 65 + (level * 13), SpellPower = 75 + (level * 6) }, 15f, 120f)); } else if (characterClass == CharacterClass.Warlock || characterClass == CharacterClass.Mage) { S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { SpellPower = 75 + (level * 6) }, 15f, 120f)); } else { S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { AttackPower = 65 + (level * 13) }, 15f, 120f)); } } else if (characterRace == CharacterRace.BloodElf) { // S.ArcaneResistance += 85f; if (characterClass == CharacterClass.DeathKnight || characterClass == CharacterClass.Rogue || characterClass == CharacterClass.Hunter) { S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { ManaorEquivRestore = .15f }, 0f, 120f)); } else if (characterClass == CharacterClass.Warrior) { S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { BonusRageGen = 15f }, 0f, 120f)); } else { S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { ManaorEquivRestore = .06f }, 0f, 120f)); } } else if (characterRace == CharacterRace.Goblin) { S.PhysicalHaste += 0.01f; S.SpellHaste += 0.01f; // TODO: The damage of the rocket belt proc is dependent on the character's current AP and SP S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { FireDamage = 1f + (level * 2) }, 0f, 120f)); } else if (characterRace == CharacterRace.PandarenAlliance || characterRace == CharacterRace.PandarenHorde) { } #endregion _lastStats = S.Clone(); return(S); } }
/// <summary> /// Computes average scaled stats given the frequency of triggers; also computers average effect /// </summary> /// <param name="stats">Stats object into which the average stats will be accumulated.</param> /// <param name="triggerInterval">Average time interval between triggers in seconds.</param> /// <param name="triggerChance">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)</param> /// <param name="attackSpeed">Average unhasted attack speed, used in PPM calculations.</param> /// <param name="fightDuration">Duration of fight in seconds.</param> /// <param name="scale">Scale factor.</param> public float AccumulateAverageStats(Stats stats, Dictionary<Trigger, float> triggerIntervals, Dictionary<Trigger, float> triggerChances, float attackSpeed = 3.0f, float fightDuration = 0.0f, float scale = 1.0f) { float factor = scale; if (triggerIntervals.ContainsKey(Trigger) && triggerChances.ContainsKey(Trigger)) factor *= GetAverageFactor(triggerIntervals[Trigger], triggerChances[Trigger], attackSpeed, fightDuration); else factor *= 0; Stats.GenerateSparseData(); if (Stats.ContainsSpecialEffect()) { if (Stats.sparseIndices.Length != 0) { Stats effectStats = Stats.Clone(); effectStats.ClearSpecialEffects(); effectStats.InvalidateSparseData(); foreach (SpecialEffect effect in Stats.SpecialEffects()) { // this assumes that the child effect gets consumed at the end of the duration // this assumes that the child effect is off cooldown before the next occurrence of the parent effect effect.AccumulateAverageStats(effectStats, triggerIntervals, triggerChances, attackSpeed, Duration); } stats.Accumulate(effectStats, factor); } else { // this path is an optimization to avoid intermediate dense storage foreach (SpecialEffect effect in Stats.SpecialEffects()) { // this assumes that the child effect gets consumed at the end of the duration // this assumes that the child effect is off cooldown before the next occurrence of the parent effect effect.AccumulateAverageStats(stats, triggerIntervals, triggerChances, attackSpeed, Duration, factor); } } } else stats.Accumulate(Stats, factor); return factor; }
public Stats GetCharacterStats(Character character, Item additionalItem, bool computeAverageStats, CharacterCalculationsHealadin calc) { CalculationOptionsHealadin calcOpts = character.CalculationOptions as CalculationOptionsHealadin; BossOptions bossOpts = character.BossOptions; PaladinTalents talents = character.PaladinTalents; float fightLength = bossOpts.BerserkTimer; Stats statsRace = BaseStats.GetBaseStats(character.Level, CharacterClass.Paladin, character.Race); Stats statsBaseGear = GetItemStats(character, additionalItem); Stats statsBuffs = GetBuffsStats(character, calcOpts); Stats stats = statsBaseGear + statsBuffs + statsRace; ConvertRatings(stats, talents, calcOpts); if (computeAverageStats) { Stats statsAverage = new Stats(); foreach (SpecialEffect effect in stats.SpecialEffects()) { float trigger = 0f; if (calc == null) { trigger = 1.5f / calcOpts.Activity / (1f + stats.SpellHaste); if (effect.Trigger == Trigger.HealingSpellCrit || effect.Trigger == Trigger.SpellCrit) trigger *= stats.SpellCrit; } else { if (effect.Trigger == Trigger.HealingSpellCast || effect.Trigger == Trigger.HealingSpellHit) trigger = 1f / Rotation.GetHealingCastsPerSec(calc); else if (effect.Trigger == Trigger.HealingSpellCrit || effect.Trigger == Trigger.SpellCrit) trigger = 1f / Rotation.GetHealingCritsPerSec(calc); else if (effect.Trigger == Trigger.SpellCast || effect.Trigger == Trigger.SpellHit) trigger = 1f / Rotation.GetSpellCastsPerSec(calc); else if (effect.Trigger == Trigger.DamageOrHealingDone) trigger = 1f / Rotation.GetHealingCastsPerSec(calc); else if (effect.Trigger == Trigger.Use) { trigger = 0f; foreach (SpecialEffect childEffect in effect.Stats.SpecialEffects()) { float childTrigger = 0f; if (effect.Trigger == Trigger.HealingSpellCast || effect.Trigger == Trigger.HealingSpellHit) childTrigger = 1f / Rotation.GetHealingCastsPerSec(calc); else if (effect.Trigger == Trigger.HealingSpellCrit || effect.Trigger == Trigger.SpellCrit) childTrigger = 1f / Rotation.GetHealingCritsPerSec(calc); else if (effect.Trigger == Trigger.SpellCast || effect.Trigger == Trigger.SpellHit) childTrigger = 1f / Rotation.GetSpellCastsPerSec(calc); statsAverage.Accumulate(childEffect.Stats, effect.GetAverageUptime(0.0f, 1.0f) * childEffect.GetAverageStackSize(childTrigger, 1f, 1.5f, effect.Duration)); } } else continue; } statsAverage.Accumulate(effect.GetAverageStats(trigger, 1f, 1.5f, fightLength)); } statsAverage.ManaRestore *= fightLength; statsAverage.Healed *= fightLength; statsAverage.HealedPerSP *= fightLength; stats = statsBaseGear + statsBuffs + statsRace + statsAverage; ConvertRatings(stats, talents, calcOpts); } #region Set Bonuses int T11Count; character.SetBonusCount.TryGetValue("Reinforced Sapphirium Regalia", out T11Count); stats.BonusCritChanceDeathCoil = 0; // using this for Holy Light crit bonus, for now stats.BonusCritChanceFrostStrike = 0; // yes, I'm pure evil, using this to track 4T11 if (T11Count >= 2) { // T11 Pally 2 piece bonus: add 5% crit to HL stats.BonusCritChanceDeathCoil = 0.05f; } if (T11Count >= 4) { // T11 Pally 4 piece bonus: 540 spirit buff for 6 secs after HS cast stats.BonusCritChanceFrostStrike = 1; } #endregion Set Bonuses return stats; }
public static void RenderScalingGraph(Graphics g, int graphWidth, int graphHeight, Character character, Stats[] statsList, Stats baseStat, bool requiresReferenceCalculations, Color[] colors, int scale, string explanatoryText, string calculation, Style style) { CharacterCalculationsBase baseCalc = Calculations.GetCharacterCalculations(character); g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; float graphOffset = graphWidth / 2.0f, graphStep = (graphWidth - 100) / 2.0f / scale; if (statsList.Length == 0 || statsList.Length > colors.Length) { return; // more than 12 elements for the array would run out of colours } float minDpsChange = 0f, maxDpsChange = 0f; PointF[][] points = new PointF[statsList.Length][]; // extract property data for relative stats calculations KeyValuePair <PropertyInfo, float>[] properties = new KeyValuePair <PropertyInfo, float> [statsList.Length]; for (int index = 0; index < statsList.Length; index++) { var p = statsList[index].Values(x => x > 0); foreach (var kvp in p) { properties[index] = kvp; } points[index] = new PointF[2 * scale + 1]; } for (int count = -scale; count <= scale; count++) { Stats newStats = new Stats(); newStats.Accumulate(baseStat, count); Item item = new Item() { Stats = newStats }; if (requiresReferenceCalculations) { Calculations.GetCharacterCalculations(character, item, true, false, false); } for (int index = 0; index < statsList.Length; index++) { ComparisonCalculationBase currentCalc = CalculationsBase.GetRelativeStatValue(character, properties[index].Key, item, properties[index].Value); float dpsChange = GetCalculationValue(currentCalc, calculation); points[index][count + scale] = new PointF(graphOffset + count * graphStep, dpsChange); if (dpsChange < minDpsChange) { minDpsChange = dpsChange; } if (dpsChange > maxDpsChange) { maxDpsChange = dpsChange; } } } // restore reference calculation if (requiresReferenceCalculations) { Stats newStats = new Stats(); Item item = new Item() { Stats = newStats }; Calculations.GetCharacterCalculations(character, item, true, false, false); } // increase the spread a bit to so that you can see if something is at the edges and straight float DpsVariance = maxDpsChange - minDpsChange; minDpsChange -= DpsVariance * 0.05f; maxDpsChange += DpsVariance * 0.05f; DpsVariance = maxDpsChange - minDpsChange; if (DpsVariance == 0) { DpsVariance = 1; } for (int index = 0; index < statsList.Length; index++) { for (int count = -scale; count <= scale; count++) { points[index][count + scale].Y = (int)((maxDpsChange - points[index][count + scale].Y) * (graphHeight - 48) / DpsVariance) + 20; } Brush statBrush = new SolidBrush(colors[index]); switch (style) { case Style.DpsWarr: g.DrawLines(new Pen(statBrush, 3), points[index]); break; case Style.Mage: g.DrawLines(new Pen(statBrush, 1), points[index]); break; } } float unit = 1f; var bp = baseStat.Values(x => x > 0); foreach (var kvp in bp) { unit = kvp.Value; } RenderGrid(g, graphWidth, graphHeight, character, statsList, colors, scale, unit, "F", explanatoryText, calculation, style, minDpsChange, maxDpsChange, DpsVariance, false); }
public static void RenderScalingGraph(Graphics g, int graphWidth, int graphHeight, Character character, Stats[] statsList, Stats baseStat, bool requiresReferenceCalculations, Color[] colors, int scale, string explanatoryText, string calculation, Style style) { CharacterCalculationsBase baseCalc = Calculations.GetCharacterCalculations(character); g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; float graphOffset = graphWidth / 2.0f, graphStep = (graphWidth - 100) / 2.0f / scale; if (statsList.Length == 0 || statsList.Length > colors.Length) return; // more than 12 elements for the array would run out of colours float minDpsChange = 0f, maxDpsChange = 0f; PointF[][] points = new PointF[statsList.Length][]; // extract property data for relative stats calculations KeyValuePair<PropertyInfo, float>[] properties = new KeyValuePair<PropertyInfo,float>[statsList.Length]; for (int index = 0; index < statsList.Length; index++) { var p = statsList[index].Values(x => x > 0); foreach (var kvp in p) { properties[index] = kvp; } points[index] = new PointF[2 * scale + 1]; } for (int count = -scale; count <= scale; count++) { Stats newStats = new Stats(); newStats.Accumulate(baseStat, count); Item item = new Item() { Stats = newStats }; if (requiresReferenceCalculations) { Calculations.GetCharacterCalculations(character, item, true, false, false); } for (int index = 0; index < statsList.Length; index++) { ComparisonCalculationBase currentCalc = CalculationsBase.GetRelativeStatValue(character, properties[index].Key, item, properties[index].Value); float dpsChange = GetCalculationValue(currentCalc, calculation); points[index][count + scale] = new PointF(graphOffset + count * graphStep, dpsChange); if (dpsChange < minDpsChange) minDpsChange = dpsChange; if (dpsChange > maxDpsChange) maxDpsChange = dpsChange; } } // restore reference calculation if (requiresReferenceCalculations) { Stats newStats = new Stats(); Item item = new Item() { Stats = newStats }; Calculations.GetCharacterCalculations(character, item, true, false, false); } // increase the spread a bit to so that you can see if something is at the edges and straight float DpsVariance = maxDpsChange - minDpsChange; minDpsChange -= DpsVariance * 0.05f; maxDpsChange += DpsVariance * 0.05f; DpsVariance = maxDpsChange - minDpsChange; if (DpsVariance == 0) DpsVariance = 1; for (int index = 0; index < statsList.Length; index++) { for (int count = -scale; count <= scale; count++) { points[index][count + scale].Y = (int)((maxDpsChange - points[index][count + scale].Y) * (graphHeight - 48) / DpsVariance) + 20; } Brush statBrush = new SolidBrush(colors[index]); switch (style) { case Style.DpsWarr: g.DrawLines(new Pen(statBrush, 3), points[index]); break; case Style.Mage: g.DrawLines(new Pen(statBrush, 1), points[index]); break; } } float unit = 1f; var bp = baseStat.Values(x => x > 0); foreach (var kvp in bp) { unit = kvp.Value; } RenderGrid(g, graphWidth, graphHeight, character, statsList, colors, scale, unit, "F", explanatoryText, calculation, style, minDpsChange, maxDpsChange, DpsVariance, false); }
public void UpdateScalingGraph(Character character, Stats[] statsList, Stats baseStat, bool requiresReferenceCalculations, Color[] colors, int scale, string explanatoryText, string calculation) { CharacterCalculationsBase baseCalc = Calculations.GetCharacterCalculations(character); if (statsList.Length == 0 || statsList.Length > colors.Length) return; // more than 12 elements for the array would run out of colours Point[][] points = new Point[statsList.Length][]; // extract property data for relative stats calculations KeyValuePair<PropertyInfo, float>[] properties = new KeyValuePair<PropertyInfo, float>[statsList.Length]; for (int index = 0; index < statsList.Length; index++) { var p = statsList[index].Values(x => x > 0); foreach (var kvp in p) { properties[index] = kvp; } points[index] = new Point[2 * scale + 1]; } float unit = 1f; var bp = baseStat.Values(x => x > 0); foreach (var kvp in bp) { unit = kvp.Value; } Chart.Series.Clear(); for (int count = -scale; count <= scale; count++) { Stats newStats = new Stats(); newStats.Accumulate(baseStat, count); Item item = new Item() { Stats = newStats }; if (requiresReferenceCalculations) { Calculations.GetCharacterCalculations(character, item, true, false, false); } for (int index = 0; index < statsList.Length; index++) { ComparisonCalculationBase currentCalc = CalculationsBase.GetRelativeStatValue(character, properties[index].Key, item, properties[index].Value); float dpsChange = GetCalculationValue(currentCalc, calculation); points[index][count + scale] = new Point(count * unit, dpsChange); } } for (int index = 0; index < statsList.Length; index++) { Style dataPointStyle = new Style(typeof(LineDataPoint)); dataPointStyle.Setters.Add(new Setter(DataPoint.TemplateProperty, Resources["InvisibleDataPointTemplate"])); dataPointStyle.Setters.Add(new Setter(DataPoint.BackgroundProperty, new SolidColorBrush(colors[index]))); Chart.Series.Add(new LineSeries() { Title = statsList[index].ToString(), ItemsSource = points[index], IndependentValuePath = "X", DependentValuePath = "Y", DataPointStyle = dataPointStyle, }); } Chart.Axes.Clear(); Chart.Axes.Add(new LinearAxis() { Orientation = AxisOrientation.X, Title = "Stat Change", ShowGridLines = true, }); Chart.Axes.Add(new LinearAxis() { Orientation = AxisOrientation.Y, Title = calculation, ShowGridLines = true, }); // restore reference calculation if (requiresReferenceCalculations) { Stats newStats = new Stats(); Item item = new Item() { Stats = newStats }; Calculations.GetCharacterCalculations(character, item, true, false, false); } orgDataDirty = true; }
public StatsPaladin GetCharacterStats(Character character, Item additionalItem, CalculationOptionsProtPaladin calcOpts, BossOptions bossOpts) { PaladinTalents talents = character.PaladinTalents; Stats statsBase = BaseStats.GetBaseStats(character.Level, CharacterClass.Paladin, character.Race); statsBase.Expertise += BaseStats.GetRacialExpertise(character, ItemSlot.MainHand); Stats statsBuffs = GetBuffsStats(character, calcOpts); Stats statsItems = GetItemStats(character, additionalItem, calcOpts); Stats statsTalents = new Stats() { BaseArmorMultiplier = talents.Toughness * 0.1f / 3f, BonusStaminaMultiplier = 0.15f // Touched by the Light }; Stats statsGearEnchantsBuffs = new Stats(); statsGearEnchantsBuffs.Accumulate(statsItems); statsGearEnchantsBuffs.Accumulate(statsBuffs); StatsPaladin statsTotal = new StatsPaladin(); statsTotal.Accumulate(statsBase); statsTotal.Accumulate(statsItems); statsTotal.Accumulate(statsBuffs); statsTotal.Accumulate(statsTalents); int T11count; character.SetBonusCount.TryGetValue("Reinforced Sapphirium Battlearmor", out T11count); if (T11count >= 2) { statsTotal.BonusDamageMultiplierCrusaderStrike = 0.05f; } if (T11count >= 4) { statsTotal.BonusDurationMultiplierGuardianOfAncientKings = 0.50f; } int T12count; character.SetBonusCount.TryGetValue("Battlearmor of Immolation", out T12count); if (T12count >= 2) { statsTotal.BonusDamageShieldofRighteous = 0.20f; } if (T12count >= 4) { statsBuffs.AddSpecialEffect(new SpecialEffect(Trigger.DivineProtection, new Stats() { Parry = 0.12f, }, 10f, 60f)); } statsTotal.Intellect = (float)Math.Floor(statsBase.Intellect * (1.0f + statsTalents.BonusIntellectMultiplier)); statsTotal.Intellect += (float)Math.Floor((statsItems.Intellect + statsBuffs.Intellect) * (1.0f + statsTalents.BonusIntellectMultiplier)); statsTotal.BaseAgility = statsBase.Agility + statsTalents.Agility; statsTotal.Stamina = (float)Math.Floor(statsBase.Stamina + statsItems.Stamina + statsBuffs.Stamina); statsTotal.Stamina = (float)Math.Floor(statsTotal.Stamina * (1.0f + statsTotal.BonusStaminaMultiplier) * (Character.ValidateArmorSpecialization(character, ItemType.Plate) ? 1.05f : 1f)); // Plate specialization statsTotal.Strength = (float)Math.Floor((statsBase.Strength + statsTalents.Strength) * (1.0f + statsTotal.BonusStrengthMultiplier)); statsTotal.Strength += (float)Math.Floor((statsItems.Strength + statsBuffs.Strength) * (1.0f + statsTotal.BonusStrengthMultiplier)); statsTotal.ParryRating += (float)Math.Floor((statsTotal.Strength - statsBase.Strength) * 0.27f); statsTotal.SpellPower = statsTotal.Strength * 0.60f; // Touched by the Light statsTotal.SpellPower += statsTotal.Intellect - 10f; if (talents.GlyphOfSealOfTruth && calcOpts.SealChoice == "Seal of Truth") { statsTotal.Expertise += 10.0f; } statsTotal.Agility = (float)Math.Floor((statsBase.Agility + statsTalents.Agility) * (1.0f + statsTotal.BonusAgilityMultiplier)); statsTotal.Agility += (float)Math.Floor((statsItems.Agility + statsBuffs.Agility) * (1.0f + statsTotal.BonusAgilityMultiplier)); statsTotal.Health += StatConversion.GetHealthFromStamina(statsTotal.Stamina, CharacterClass.Paladin); statsTotal.Health *= 1f + statsTotal.BonusHealthMultiplier; statsTotal.Mana += StatConversion.GetManaFromIntellect(statsTotal.Intellect, CharacterClass.Paladin) * (1f + statsTotal.BonusManaMultiplier); // Armor statsTotal.Armor = (float)Math.Floor(statsTotal.Armor * (1f + statsTotal.BaseArmorMultiplier)); statsTotal.Armor += (float)Math.Floor(statsTotal.BonusArmor * (1f + statsTotal.BonusArmorMultiplier)); statsTotal.AttackPower += ((statsTotal.Strength - 10f) * 2f); statsTotal.AttackPower = (float)Math.Floor(statsTotal.AttackPower * (1f + statsTotal.BonusAttackPowerMultiplier)); statsTotal.NatureResistance += statsTotal.NatureResistanceBuff; statsTotal.FireResistance += statsTotal.FireResistanceBuff; statsTotal.FrostResistance += statsTotal.FrostResistanceBuff; statsTotal.ShadowResistance += statsTotal.ShadowResistanceBuff; statsTotal.ArcaneResistance += statsTotal.ArcaneResistanceBuff; //statsTotal.BonusCritDamageMultiplier = statsBase.BonusCritDamageMultiplier + statsGearEnchantsBuffs.BonusCritDamageMultiplier; statsTotal.CritRating = statsBase.CritRating + statsGearEnchantsBuffs.CritRating; statsTotal.ExpertiseRating = statsBase.ExpertiseRating + statsGearEnchantsBuffs.ExpertiseRating; statsTotal.HasteRating = statsBase.HasteRating + statsGearEnchantsBuffs.HasteRating; statsTotal.HitRating = statsBase.HitRating + statsGearEnchantsBuffs.HitRating; statsTotal.MasteryRating = statsBase.MasteryRating + statsGearEnchantsBuffs.MasteryRating; statsTotal.BlockRating = statsBase.BlockRating + statsGearEnchantsBuffs.BlockRating; statsTotal.WeaponDamage += Lookup.WeaponDamage(character, statsTotal.AttackPower, false); //statsTotal.ExposeWeakness = statsBase.ExposeWeakness + statsGearEnchantsBuffs.ExposeWeakness; // Nerfed in 3.1 // Calculate Procs and Special Effects statsTotal.Accumulate(GetSpecialEffectStats(character, statsTotal, calcOpts, bossOpts)); return statsTotal; }
public Stats getSpecialEffects(SpecialEffect effect) { Stats statsAverage = new Stats(); if (effect == mainHandEnchant || effect == offHandEnchant) { if (mainHandEnchant != null && !mhProcessed) { statsAverage.Accumulate(mainHandEnchant.Stats, GetMHUptime()); mhProcessed = true; } else if (offHandEnchant != null && !ohProcessed) { statsAverage.Accumulate(offHandEnchant.Stats, GetOHUptime()); ohProcessed = true; } } else if (effect.Trigger == Trigger.Use) { effect.AccumulateAverageStats(statsAverage); foreach (SpecialEffect e in effect.Stats.SpecialEffects()) { statsAverage.Accumulate(this.getSpecialEffects(e) * (effect.Duration / effect.Cooldown)); } } else { SetTriggerChanceAndSpeed(effect); foreach (SpecialEffect e in effect.Stats.SpecialEffects()) // deal with secondary effects { statsAverage.Accumulate(this.getSpecialEffects(e)); } if (effect.MaxStack > 1) { if (effect.Stats.MoteOfAnger > 0) { // When in effect stats, MoteOfAnger is % of melee hits // When in character stats, MoteOfAnger is average procs per second statsAverage.Accumulate(new Stats() { MoteOfAnger = effect.Stats.MoteOfAnger * effect.GetAverageProcsPerSecond(trigger, chance, unhastedAttackSpeed, 0f) / effect.MaxStack }); } else { float timeToMax = (float)Math.Min(_cs.FightLength, effect.GetChance(unhastedAttackSpeed) * trigger * effect.MaxStack); float buffDuration = _cs.FightLength; if (effect.Stats.AttackPower == 250f || effect.Stats.AttackPower == 215f || effect.Stats.HasteRating == 57f || effect.Stats.HasteRating == 64f) { buffDuration = 20f; } if (timeToMax * .5f > buffDuration) { timeToMax = 2 * buffDuration; } statsAverage.Accumulate(effect.Stats * (effect.MaxStack * (((buffDuration) - .5f * timeToMax) / (buffDuration)))); } } else { effect.AccumulateAverageStats(statsAverage, trigger, chance, unhastedAttackSpeed); } } return(statsAverage); }
protected static void AccumulateSpecialEffects(Character character, ref Stats stats, float FightDuration, Dictionary<Trigger, float> triggerIntervals, Dictionary<Trigger, float> triggerChances, List<SpecialEffect> effects, float weight) { foreach (SpecialEffect effect in effects) { Stats effectStats = effect.Stats; float upTime = 0f; if (effect.Trigger == Trigger.Use) { if (effect.Stats._rawSpecialEffectDataSize >= 1) { upTime = effect.GetAverageUptime(0f, 1f, 0, FightDuration); List<SpecialEffect> nestedEffect = new List<SpecialEffect>(effect.Stats.SpecialEffects()); Stats _stats2 = effectStats.Clone(); AccumulateSpecialEffects(character, ref _stats2, effect.Duration, triggerIntervals, triggerChances, nestedEffect, upTime); effectStats = _stats2; } else { upTime = effect.GetAverageStackSize(0f, 1f, 0, FightDuration); } } else if (effect.Duration == 0f) { upTime = effect.GetAverageProcsPerSecond(triggerIntervals[effect.Trigger], triggerChances[effect.Trigger], 0, FightDuration); } else if (triggerIntervals.ContainsKey(effect.Trigger)) { upTime = effect.GetAverageStackSize(triggerIntervals[effect.Trigger], triggerChances[effect.Trigger], 0, FightDuration); } if (upTime > 0f) { stats.Accumulate(effectStats, upTime * weight); } } }
public StatsRetri GetCharacterStats(Character character, Item additionalItem, bool computeAverageStats) { PaladinTalents talents = character.PaladinTalents; CalculationOptionsRetribution calcOpts = character.CalculationOptions as CalculationOptionsRetribution; StatsRetri stats = new StatsRetri(); stats.Accumulate(BaseStats.GetBaseStats(character.Level, CharacterClass.Paladin, character.Race)); //Race stats.Accumulate(GetItemStats(character, additionalItem)); //Items stats.Accumulate(GetBuffsStats(character, calcOpts)); //Buffs // Adjust expertise for racial passive stats.Expertise += BaseStats.GetRacialExpertise(character, ItemSlot.MainHand); // Judgements of the pure (Flat because it has nearly always a 100% chance. stats.PhysicalHaste += PaladinConstants.JUDGEMENTS_OF_THE_PURE * talents.JudgementsOfThePure; stats.SpellHaste += PaladinConstants.JUDGEMENTS_OF_THE_PURE * talents.JudgementsOfThePure; //Sets stats.SetSets(character); // If wanted, Average out any Proc and OnUse effects into the stats if (computeAverageStats) { StatsRetri statsTmp = stats.Clone(); ConvertRatings(statsTmp, talents, character);// Convert ratings so we have right value for haste, weaponspeed and talents etc. RotationCalculation rot = CreateRotation(character, statsTmp); Dictionary<Trigger, float> triggerIntervals = new Dictionary<Trigger, float>(); Dictionary<Trigger, float> triggerChances = new Dictionary<Trigger, float>(); CalculateTriggers(triggerIntervals, triggerChances, rot); //Talent special effects //GoaK Strength stats.AddSpecialEffect(_GoakSE); // Average out proc effects, and add to global stats. Stats statsAverage = new Stats(); foreach (SpecialEffect effect in stats.SpecialEffects()) statsAverage.Accumulate(effect.GetAverageStats(triggerIntervals, triggerChances, AbilityHelper.BaseWeaponSpeed(character), character.BossOptions.BerserkTimer)); stats.Accumulate(statsAverage); } // No negative values (from possible charts) if (stats.Strength < 0) stats.Strength = 0; if (stats.Agility < 0) stats.Agility = 0; if (stats.AttackPower < 0) stats.AttackPower = 0; if (stats.ExpertiseRating < 0) stats.ExpertiseRating = 0; if (stats.HitRating < 0) stats.HitRating = 0; if (stats.CritRating < 0) stats.CritRating = 0; if (stats.HasteRating < 0) stats.HasteRating = 0; if (stats.SpellPower < 0) stats.SpellPower = 0; if (stats.MasteryRating < 0) stats.MasteryRating = 0; // ConvertRatings needs to be done AFTER accounting for the averaged stats, since stat multipliers // should affect the averaged stats also. ConvertRatings(stats, talents, character); return stats; }
public override Stats GetCharacterStats(Character character, Item additionalItem) { PriestTalents talents = character.PriestTalents; Stats statsTotal = new Stats(); Stats baseStats = BaseStats.GetBaseStats(character.Level, character.Class, character.Race); Stats itemStats = GetItemStats(character, additionalItem); Stats buffStats = GetBuffsStats(character, _calculationOptions); // Get the gear/enchants/buffs stats loaded in statsTotal.Accumulate(baseStats); statsTotal.Accumulate(itemStats); statsTotal.Accumulate(buffStats); Stats statsTalents = new Stats() { // we can only wear items that are cloth so we always have our specialization, even naked. BonusIntellectMultiplier = 0.05f, BonusShadowDamageMultiplier = (1 + 0.02f * talents.TwinDisciplines) * (1 + 0.02f * talents.TwistedFaith) * (1 + 0.15f * talents.Shadowform) - 1, BonusHolyDamageMultiplier = (1 + 0.02f * talents.TwinDisciplines) - 1, // this is the shadow priest model so they must have 'Shadow Power' BonusSpellPowerMultiplier = .15f, }; statsTotal.Accumulate(statsTalents); statsTotal.Stamina = (float)Math.Floor(statsTotal.Stamina * (1 + statsTotal.BonusStaminaMultiplier)); statsTotal.Intellect += (float)Math.Floor(itemStats.Intellect * statsTotal.BonusIntellectMultiplier); statsTotal.Spirit = (float)Math.Round(statsTotal.Spirit * (1 + statsTotal.BonusSpiritMultiplier)); statsTotal.Health += (float)Math.Floor(StatConversion.GetHealthFromStamina(statsTotal.Stamina) * (1f + statsTotal.BonusHealthMultiplier)); statsTotal.Mana = (float)Math.Round(statsTotal.Mana + StatConversion.GetManaFromIntellect(statsTotal.Intellect)); statsTotal.Mana = (float)Math.Round(statsTotal.Mana * (1f + statsTotal.BonusManaMultiplier)); statsTotal.SpellPower += statsTotal.Intellect - 10; float hasteFromRating = StatConversion.GetSpellHasteFromRating(statsTotal.HasteRating); float talentedHaste = (1 + hasteFromRating) * (1 + talents.Darkness * .01f) - 1; statsTotal.SpellHaste += character.Race == CharacterRace.Goblin ? talentedHaste * 1.01f : talentedHaste; float baseBonus = (float)Math.Floor(baseStats.Spirit * statsTotal.BonusSpiritMultiplier); float itemBonus = (float)Math.Floor(itemStats.Spirit * statsTotal.BonusSpiritMultiplier); float spiritFromItemsAndEffects = baseBonus + itemBonus + itemStats.Spirit; float hitRatingFromSpirit = (0.5f * talents.TwistedFaith) * Math.Max(0f, spiritFromItemsAndEffects); statsTotal.HitRating += hitRatingFromSpirit; statsTotal.SpellHit += StatConversion.GetSpellHitFromRating(statsTotal.HitRating); // ignoring the base crit percentage here as the in-game tooltip says that the int -> crit conversion contains the base. float critFromInt = StatConversion.GetSpellCritFromIntellect(statsTotal.Intellect) + 0.012375f; float critFromRating = StatConversion.GetSpellCritFromRating(statsTotal.CritRating); statsTotal.SpellCrit = character.Race == CharacterRace.Worgen ? (critFromInt + critFromRating) + .01f : (critFromInt + critFromRating); // Armor statsTotal.Armor = statsTotal.Armor * (1f + statsTotal.BaseArmorMultiplier); statsTotal.BonusArmor = statsTotal.BonusArmor * (1f + statsTotal.BonusArmorMultiplier); statsTotal.Armor += statsTotal.BonusArmor; statsTotal.Armor = (float)Math.Round(statsTotal.Armor); return(statsTotal); }
public override Stats GetCharacterStats(Character character, Item additionalItem) { CalculationOptionsTree opts = character.CalculationOptions as CalculationOptionsTree; if (opts == null) opts = new CalculationOptionsTree(); Stats stats = new Stats(); Stats statsBase = BaseStats.GetBaseStats(character.Level, character.Class, character.Race); stats.Accumulate(statsBase); stats.BaseAgility = statsBase.Agility; // Get the gear/enchants/buffs stats loaded in stats.Accumulate(GetItemStats(character, additionalItem)); stats.Accumulate(GetBuffsStats(character, opts)); // Talented bonus multipliers Stats statsTalents = new Stats() { BonusIntellectMultiplier = (1 + 0.02f * character.DruidTalents.HeartOfTheWild) * (Character.ValidateArmorSpecialization(character, ItemType.Leather) ? 1.05f : 1f) - 1f, BonusManaMultiplier = 0.05f * character.DruidTalents.Furor, ManaCostReductionMultiplier = character.DruidTalents.Moonglow * 0.03f, SpellCrit = 0.02f * character.DruidTalents.NaturesMajesty }; stats.Accumulate(statsTalents); FinalizeStats(stats, stats); // Derived stats: Health, mana pool, armor stats.Health = (float)Math.Round(stats.Health + StatConversion.GetHealthFromStamina(stats.Stamina)); stats.Health = (float)Math.Floor(stats.Health * (1f + stats.BonusHealthMultiplier)); stats.Mana = (float)Math.Round(stats.Mana + StatConversion.GetManaFromIntellect(stats.Intellect)); stats.Mana = (float)Math.Floor(stats.Mana * (1f + stats.BonusManaMultiplier)); // Armor stats.Armor = stats.Armor * (1f + stats.BaseArmorMultiplier); stats.BonusArmor = stats.BonusArmor * (1f + stats.BonusArmorMultiplier); stats.Armor += stats.BonusArmor; stats.Armor = (float)Math.Round(stats.Armor); int T13Count; character.SetBonusCount.TryGetValue("Deep Earth Vestments", out T13Count); if (character.DruidTalents.NaturesGrace > 0) stats.AddSpecialEffect(NaturesGrace[character.DruidTalents.NaturesGrace]); if (character.DruidTalents.TreeOfLife > 0) stats.AddSpecialEffect(TreeOfLife[character.DruidTalents.NaturalShapeshifter]); if (T13Count >= 2) stats.AddSpecialEffect(T13InnervateManaCostReduction); return stats; }
void computeStats() { RandomIntellectEffects = new List<RandomIntellectEffect>(); RandomIntellectEffects.Add(new RandomIntellectEffect(0, null, 0, 0)); OnUseIntellectProcsMana = 0.0f; calc.MeanMana = calc.BasicStats.Mana; List<KeyValuePair<double, SpecialEffect>> hasteProcsList = new List<KeyValuePair<double, SpecialEffect>>(); // ToL, NG, Heroism, Shard of Woe, etc. List<SpecialEffect> dividingEffectsList = new List<SpecialEffect>(); List<int> dividingEffectBucketsList = new List<int>(); KeyValuePair<double, SpecialEffect>[] hasteProcs; Stats statsDividing = new Stats(); Stats statsProcs = new Stats(); Dictionary<Trigger, float> triggerIntervals = new Dictionary<Trigger,float>(); Dictionary<Trigger, float> triggerChances = new Dictionary<Trigger, float>(); triggerIntervals[Trigger.Use] = 0; triggerIntervals[Trigger.HealingSpellCast] = triggerIntervals[Trigger.HealingSpellCrit] = triggerIntervals[Trigger.HealingSpellHit] = triggerIntervals[Trigger.SpellCast] = triggerIntervals[Trigger.SpellCrit] = triggerIntervals[Trigger.SpellHit] = triggerIntervals[Trigger.DamageOrHealingDone] = (float)calc.ProcTriggerInterval; triggerIntervals[Trigger.HoTTick] = (float)calc.ProcPeriodicTriggerInterval; foreach(Trigger trigger in triggerIntervals.Keys) triggerChances[trigger] = 1; // NOTE: this ignores crit from procs, but hopefully this shouldn't matter much triggerChances[Trigger.HealingSpellCrit] = triggerChances[Trigger.SpellCrit] = StatConversion.GetSpellCritFromIntellect(calc.BasicStats.Intellect) + StatConversion.GetSpellCritFromRating(calc.BasicStats.CritRating) + calc.BasicStats.SpellCrit; if(opts.TriggerDamageEffects) { foreach (SpecialEffect effect in calc.BasicStats.SpecialEffects()) { if (effect.Trigger == Trigger.DamageSpellCast || effect.Trigger == Trigger.DamageSpellHit || effect.Trigger == Trigger.DoTTick || effect.Trigger == Trigger.DamageSpellCrit) InsectSwarm = true; } } if (InsectSwarm) { triggerIntervals[Trigger.DamageSpellCast] = triggerIntervals[Trigger.DamageSpellHit] = triggerIntervals[Trigger.DoTTick] = triggerIntervals[Trigger.DamageSpellCrit] = (float)calc.DamageProcPeriodicTriggerInterval; triggerChances[Trigger.DamageSpellCast] = triggerChances[Trigger.DamageSpellHit] = triggerChances[Trigger.DoTTick] = 1; triggerChances[Trigger.DamageSpellCrit] = StatConversion.GetSpellCritFromIntellect(calc.BasicStats.Intellect) + StatConversion.GetSpellCritFromRating(calc.BasicStats.CritRating) + calc.BasicStats.SpellCrit; } foreach (SpecialEffect effect in calc.BasicStats.SpecialEffects()) { if (triggerIntervals.ContainsKey(effect.Trigger)) { if (effect.Stats.Intellect > 0 || effect.Stats.HighestStat > 0) { double mana = StatConversion.GetManaFromIntellect((effect.Stats.Intellect + effect.Stats.HighestStat) * (1 + calc.BasicStats.BonusIntellectMultiplier)) * (1 + calc.BasicStats.BonusManaMultiplier); double avgMana = mana * effect.GetAverageFactor(triggerIntervals[effect.Trigger], triggerChances[effect.Trigger], 3.0f, character.BossOptions.BerserkTimer); if (effect.Trigger != Trigger.Use) RandomIntellectEffects.Add(new RandomIntellectEffect(mana - avgMana, effect, triggerIntervals[effect.Trigger], triggerChances[effect.Trigger])); if (effect.Trigger == Trigger.Use && effect.Cooldown <= 180.0f) OnUseIntellectProcsMana += mana - avgMana; calc.MeanMana += avgMana; } Stats stats = effect.GetAverageStats(triggerIntervals, triggerChances, 3.0f, character.BossOptions.BerserkTimer); if (effect.Trigger == Trigger.Use && effect.MaxStack <= 1 && (effect.Stats is CalculationsTree.TreeOfLifeStats || effect.Stats.HasteRating > 0 || effect.Stats.SpellHaste > 0 || effect.Stats.Intellect > 0 || effect.Stats.SpellPower > 0 || effect.Stats.CritRating > 0 || effect.Stats.SpellCrit > 0 || effect.Stats.MasteryRating > 0 || effect.Stats.NatureSpellsManaCostReduction != 0 || effect.Stats.SpellsManaCostReduction != 0 || effect.Stats.ManaCostReductionMultiplier != 0) ) { dividingEffectsList.Add(effect); int bucket = -1; if(opts.SeparateHasteEffects && (effect.Stats.HasteRating > 0 || effect.Stats.SpellHaste > 0)) bucket = 0; dividingEffectBucketsList.Add(bucket); statsDividing.Accumulate(stats); } else { if (effect.Stats.HasteRating > 0 || effect.Stats.SpellHaste > 0) { double uptime = effect.GetAverageUptime(triggerIntervals[effect.Trigger], triggerChances[effect.Trigger], 3.0f, character.BossOptions.BerserkTimer); hasteProcsList.Add(new KeyValuePair<double, SpecialEffect>(uptime, effect)); stats.HasteRating = 0; stats.SpellHaste = 0; } statsProcs.Accumulate(stats); } } } MeanStats = new Stats(); MeanStats.Accumulate(statsProcs); MeanStats.Accumulate(statsDividing); // XXX: this fixes Tyrande's Favorite Doll, but seems to be a bug in Rawr MeanStats.Mp5 = MeanStats.ManaRestore * 5; MeanStats.ManaRestore = 0; if (T11Count >= 4) MeanStats.Spirit += 540 * opts.HarmonyPeriodicRate; CalculationsTree.FinalizeStats(MeanStats, calc.BasicStats); MeanStats.Accumulate(calc.BasicStats); hasteProcs = hasteProcsList.ToArray(); calc.Division = FightDivision.ComputeNaive(dividingEffectsList.ToArray(), calc.FightLength, dividingEffectBucketsList.ToArray()); if (opts.ActivityRate < 1.0f) { SpecialEffect[] newEffects = new SpecialEffect[calc.Division.Effects.Length + 1]; Array.Copy(calc.Division.Effects, newEffects, calc.Division.Effects.Length); newEffects[calc.Division.Effects.Length] = CalculationsTree.InactiveEffect; int[] newEffectMasks = new int[calc.Division.Count + 1]; Array.Copy(calc.Division.EffectMasks, newEffectMasks, calc.Division.Count); newEffectMasks[calc.Division.Count] = 1 << calc.Division.Effects.Length; double[] newFractions = new double[calc.Division.Count + 1]; Array.Copy(calc.Division.Fractions, newFractions, calc.Division.Count); double curActivityRate = 1; for (int i = 0; i < calc.Division.Count; ++i) { if (calc.Division.EffectMasks[i] == 0) { double t = Math.Min(1 - opts.ActivityRate, newFractions[i]); curActivityRate -= t; newFractions[i] -= t; } } if (curActivityRate > opts.ActivityRate) { for (int i = 0; i < calc.Division.Count; ++i) { newFractions[i] *= opts.ActivityRate; } } newFractions[calc.Division.Count] = 1 - opts.ActivityRate; calc.Division.Fractions = newFractions; calc.Division.Effects = newEffects; calc.Division.EffectMasks = newEffectMasks; ++calc.Division.Count; } double baseSpellPower = (float)(MeanStats.SpellPower + Math.Max(0f, calc.BasicStats.Intellect - 10)); // TODO: does nurturing instinct actually work like this? baseSpellPower += Talents.NurturingInstinct * 0.5 * calc.BasicStats.Agility; baseSpellPower *= 1 + calc.BasicStats.BonusSpellPowerMultiplier; calc.BaseSpellPower = baseSpellPower; calc.Stats = new TreeStats[calc.Division.Count]; for (int div = 0; div < calc.Division.Fractions.Length; ++div) { Stats statsWithOnUse = new Stats(); bool treeOfLifeActive = false; for (int j = 0; j < calc.Division.Effects.Length; ++j) { if ((calc.Division.EffectMasks[div] & (1 << j)) != 0) { if (calc.Division.Effects[j].Stats is CalculationsTree.TreeOfLifeStats) treeOfLifeActive = true; else statsWithOnUse.Accumulate(calc.Division.Effects[j].Stats); } } statsWithOnUse.Accumulate(statsProcs); // XXX: this fixes Tyrande's Favorite Doll, but seems to be a bug in Rawr statsWithOnUse.Mp5 = statsWithOnUse.ManaRestore * 5; statsWithOnUse.ManaRestore = 0; CalculationsTree.FinalizeStats(statsWithOnUse, calc.BasicStats); statsWithOnUse.Accumulate(calc.BasicStats); calc.Stats[div] = new TreeStats(character, statsWithOnUse, hasteProcs, treeOfLifeActive ? 1.0 : 0.0); } }
public Stats GetCharacterStats(Character character, Item additionalItem, bool computeAverageStats, CharacterCalculationsHealadin calc) { PaladinTalents talents = character.PaladinTalents; CalculationOptionsHealadin calcOpts = character.CalculationOptions as CalculationOptionsHealadin; if (calcOpts == null) { calcOpts = new CalculationOptionsHealadin(); } #if (RAWR3) BossOptions bossOpts = character.BossOptions; if (bossOpts == null) { bossOpts = new BossOptions(); } #endif #if (RAWR3) float fightLength = bossOpts.BerserkTimer * 60f; #else float fightLength = calcOpts.Length * 60f; #endif Stats statsRace = BaseStats.GetBaseStats(character.Level, CharacterClass.Paladin, character.Race); Stats statsBaseGear = GetItemStats(character, additionalItem); Stats statsBuffs = GetBuffsStats(character, calcOpts); Stats stats = statsBaseGear + statsBuffs + statsRace; ConvertRatings(stats, talents, calcOpts); if (computeAverageStats) { Stats statsAverage = new Stats(); foreach (SpecialEffect effect in stats.SpecialEffects()) { float trigger = 0f; if (calc == null) { trigger = 1.5f / calcOpts.Activity / (1f + stats.SpellHaste); if (effect.Trigger == Trigger.HealingSpellCrit || effect.Trigger == Trigger.SpellCrit) { trigger *= stats.SpellCrit; } else if (effect.Trigger == Trigger.HolyShockCast) { trigger = 6f / calcOpts.HolyShock; } } else { if (effect.Trigger == Trigger.HealingSpellCast || effect.Trigger == Trigger.HealingSpellHit) { trigger = 1f / Rotation.GetHealingCastsPerSec(calc); } else if (effect.Trigger == Trigger.HealingSpellCrit || effect.Trigger == Trigger.SpellCrit) { trigger = 1f / Rotation.GetHealingCritsPerSec(calc); } else if (effect.Trigger == Trigger.SpellCast || effect.Trigger == Trigger.SpellHit) { trigger = 1f / Rotation.GetSpellCastsPerSec(calc); } else if (effect.Trigger == Trigger.DamageOrHealingDone) { trigger = 1f / Rotation.GetHealingCastsPerSec(calc); } else if (effect.Trigger == Trigger.HolyShockCast) { trigger = 6f / calcOpts.HolyShock; } else if (effect.Trigger == Trigger.Use) { trigger = 0f; foreach (SpecialEffect childEffect in effect.Stats.SpecialEffects()) { float childTrigger = 0f; if (effect.Trigger == Trigger.HealingSpellCast || effect.Trigger == Trigger.HealingSpellHit) { childTrigger = 1f / Rotation.GetHealingCastsPerSec(calc); } else if (effect.Trigger == Trigger.HealingSpellCrit || effect.Trigger == Trigger.SpellCrit) { childTrigger = 1f / Rotation.GetHealingCritsPerSec(calc); } else if (effect.Trigger == Trigger.SpellCast || effect.Trigger == Trigger.SpellHit) { childTrigger = 1f / Rotation.GetSpellCastsPerSec(calc); } statsAverage.Accumulate(childEffect.Stats, effect.GetAverageUptime(0.0f, 1.0f) * childEffect.GetAverageStackSize(childTrigger, 1f, 1.5f, effect.Duration)); } } else if (effect.Trigger == Trigger.HolyLightCast) { trigger = 1f / Rotation.GetHolyLightCastsPerSec(calc); } else { continue; } } statsAverage.Accumulate(effect.GetAverageStats(trigger, 1f, 1.5f, fightLength)); } statsAverage.ManaRestore *= fightLength; statsAverage.Healed *= fightLength; stats = statsBaseGear + statsBuffs + statsRace + statsAverage; ConvertRatings(stats, talents, calcOpts); } return(stats); }
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); }
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 StatsWarrior GetSpecialEffectStats(Player player) { StatsWarrior statsSpecialEffects = new StatsWarrior(); Dictionary<Trigger, float> triggerIntervals = new Dictionary<Trigger, float>(); Dictionary<Trigger, float> triggerChances = new Dictionary<Trigger, float>(); player.DefendModel = new DefendModel(player); player.AttackModel = new AttackModel(player, AttackModelMode.Optimal); float effectiveMasteryRating = Lookup.MaxEffectiveMasteryRating(player); float effectiveBuffedMasteryRating = effectiveMasteryRating * (1.0f - 10.0f / player.CalcOpts.ShieldBlockInterval) + Lookup.MaxEffectiveBuffedMasteryRating(player) * (10.0f / player.CalcOpts.ShieldBlockInterval); triggerIntervals[Trigger.Use] = 0.0f; triggerIntervals[Trigger.MeleeAttack] = player.AttackModel.WeaponAttacksPerSecond; triggerIntervals[Trigger.MeleeHit] = triggerIntervals[Trigger.MeleeAttack]; triggerIntervals[Trigger.MeleeCrit] = triggerIntervals[Trigger.MeleeAttack]; triggerIntervals[Trigger.PhysicalHit] = triggerIntervals[Trigger.MeleeAttack]; triggerIntervals[Trigger.PhysicalAttack] = triggerIntervals[Trigger.MeleeAttack]; triggerIntervals[Trigger.PhysicalCrit] = triggerIntervals[Trigger.MeleeAttack]; triggerIntervals[Trigger.ExecuteHit] = triggerIntervals[Trigger.MeleeAttack]; triggerIntervals[Trigger.DoTTick] = (player.Talents.DeepWounds > 0) ? 2.0f : 0.0f; triggerIntervals[Trigger.DamageDone] = triggerIntervals[Trigger.MeleeAttack] + triggerIntervals[Trigger.DoTTick]; triggerIntervals[Trigger.DamageOrHealingDone] = triggerIntervals[Trigger.DamageDone]; triggerIntervals[Trigger.DamageTaken] = 1.0f / player.DefendModel.AttackerSwingsPerSecond; triggerIntervals[Trigger.DamageAvoided] = triggerIntervals[Trigger.DamageTaken]; triggerIntervals[Trigger.DamageParried] = triggerIntervals[Trigger.DamageTaken]; triggerIntervals[Trigger.DamageTakenPutsMeBelow35PercHealth] = triggerIntervals[Trigger.DamageTaken]; triggerIntervals[Trigger.ShieldBlock] = 60f - (player.Talents.ShieldMastery * 10f); triggerChances[Trigger.Use] = 1.0f; triggerChances[Trigger.MeleeAttack] = 1.0f; triggerChances[Trigger.MeleeHit] = player.AttackModel.HitsPerSecond / player.AttackModel.WeaponAttacksPerSecond; triggerChances[Trigger.MeleeCrit] = player.AttackModel.CritsPerSecond / player.AttackModel.WeaponAttacksPerSecond; triggerChances[Trigger.PhysicalAttack] = 1.0f; triggerChances[Trigger.PhysicalHit] = triggerChances[Trigger.MeleeHit]; triggerChances[Trigger.PhysicalCrit] = triggerChances[Trigger.MeleeCrit]; triggerChances[Trigger.ExecuteHit] = triggerChances[Trigger.MeleeHit]; triggerChances[Trigger.DoTTick] = (player.Talents.DeepWounds > 0) ? 1.0f : 0.0f; triggerChances[Trigger.DamageDone] = (player.AttackModel.HitsPerSecond + ((player.Talents.DeepWounds > 0) ? 2.0f : 0.0f)) / (player.AttackModel.WeaponAttacksPerSecond + ((player.Talents.DeepWounds > 0) ? 1.0f : 0.0f)); triggerChances[Trigger.DamageOrHealingDone] = triggerChances[Trigger.DamageDone]; triggerChances[Trigger.DamageTaken] = player.DefendModel.AttackerHitsPerSecond / player.DefendModel.AttackerSwingsPerSecond; triggerChances[Trigger.DamageAvoided] = player.DefendModel.DefendTable.AnyAvoid; triggerChances[Trigger.DamageParried] = player.DefendModel.DefendTable.Parry; triggerChances[Trigger.DamageTakenPutsMeBelow35PercHealth] = triggerChances[Trigger.DamageTaken] * 0.35f; triggerChances[Trigger.ShieldBlock] = 1.0f; foreach (SpecialEffect effect in player.Stats.SpecialEffects()) { if (RelevantTriggers.Contains(effect.Trigger)) { // Effective Mastery Capping on Large Proc Effects if ((effect.Trigger == Trigger.Use && effect.Stats.MasteryRating > effectiveMasteryRating) || effect.Stats.MasteryRating > effectiveBuffedMasteryRating) { Stats cappedStats = new Stats(); cappedStats.Accumulate(effect.Stats); // Assume Use Effects Bypass Shield Block Collision if (effect.Trigger == Trigger.Use) cappedStats.MasteryRating = effectiveMasteryRating; else cappedStats.MasteryRating = effectiveBuffedMasteryRating; // calculate average up-time of this trinket float averageUpTime = 0.0f; if (effect.Trigger == Trigger.ExecuteHit) averageUpTime = effect.GetAverageFactor(triggerIntervals[effect.Trigger], triggerChances[effect.Trigger], player.AttackModel.WeaponSpeed, player.BossOpts.BerserkTimer * (float)player.BossOpts.Under20Perc); else averageUpTime = effect.GetAverageFactor(triggerIntervals[effect.Trigger], triggerChances[effect.Trigger], player.AttackModel.WeaponSpeed, player.BossOpts.BerserkTimer); // accumulate the capped stats from the trinket into our final stats statsSpecialEffects.Accumulate(cappedStats, averageUpTime); } else { if (effect.Trigger == Trigger.ExecuteHit) effect.AccumulateAverageStats(statsSpecialEffects, triggerIntervals, triggerChances, player.AttackModel.WeaponSpeed, player.BossOpts.BerserkTimer * (float)player.BossOpts.Under20Perc); else effect.AccumulateAverageStats(statsSpecialEffects, triggerIntervals, triggerChances, player.AttackModel.WeaponSpeed, player.BossOpts.BerserkTimer); } } } // Base Stats statsSpecialEffects.Stamina = (float)Math.Floor(statsSpecialEffects.Stamina * (1.0f + player.Stats.BonusStaminaMultiplier)); statsSpecialEffects.Strength = (float)Math.Floor(statsSpecialEffects.Strength * (1.0f + player.Stats.BonusStrengthMultiplier)); statsSpecialEffects.Agility = (float)Math.Floor(statsSpecialEffects.Agility * (1.0f + player.Stats.BonusAgilityMultiplier)); return statsSpecialEffects; }
private float CalcRemainingProcs() { if (Options.NoProcs) { return(0f); } Dictionary <int, float> periods = new Dictionary <int, float>(); Dictionary <int, float> chances = new Dictionary <int, float>(); PopulateTriggers(periods, chances); float procdDamage = 0f; Stats procStats = new Stats(); foreach (SpecialEffect effect in Stats.SpecialEffects()) { if (!periods.ContainsKey((int)effect.Trigger)) { continue; } Stats effectStats = effect.Stats; if (effectStats.ValkyrDamage > 0) { SpellModifiers mods = new SpellModifiers(); mods.AddCritChance(.05f + Stats.SpellCritOnTarget); mods.AddMultiplicativeMultiplier( Stats.BonusHolyDamageMultiplier); procdDamage += CalcDamageProc( effect, effect.Stats.ValkyrDamage, periods, chances, mods); } else if ( effectStats.ShadowDamage > 0 || effectStats.FireDamage > 0 || effectStats.NatureDamage > 0 || effectStats.HolyDamage > 0 || effectStats.FrostDamage > 0) { SpellModifiers mods = new SpellModifiers(); mods.Accumulate(SpellModifiers); if (Options.Imbue.Equals("Grand Firestone")) { mods.AddAdditiveDirectMultiplier(.01f); } if (effectStats.ShadowDamage > 0) { AddShadowModifiers(mods); } else if (effectStats.FireDamage > 0) { AddFireModifiers(mods); } procdDamage += CalcDamageProc( effect, effectStats.ShadowDamage + effectStats.FireDamage + effectStats.NatureDamage + effectStats.HolyDamage + effectStats.FrostDamage, periods, chances, mods); } else { procStats.Accumulate( CalcNormalProc(effect, periods, chances)); } } procStats.HasteRating = procStats.SpellHaste = procStats.Mana = procStats.ManaRestore = procStats.ManaRestoreFromBaseManaPPM = procStats.ManaRestoreFromMaxManaPerSecond = procStats.Mp5 = procStats.CritRating = procStats.SpellCrit = procStats.SpellCritOnTarget = procStats.PhysicalCrit = 0; Stats.Accumulate(procStats); return(procdDamage); }
// this is now included in accumulating item stats /*public unsafe virtual void AccumulateEnchantsStats(Stats stats, Character character) { fixed (float* pRawAdditiveData = stats._rawAdditiveData, pRawMultiplicativeData = stats._rawMultiplicativeData, pRawNoStackData = stats._rawNoStackData) { stats.BeginUnsafe(pRawAdditiveData, pRawMultiplicativeData, pRawNoStackData); stats.AccumulateUnsafe(character.BackEnchant.Stats, true); stats.AccumulateUnsafe(character.ChestEnchant.Stats, true); stats.AccumulateUnsafe(character.FeetEnchant.Stats, true); stats.AccumulateUnsafe(character.Finger1Enchant.Stats, true); stats.AccumulateUnsafe(character.Finger2Enchant.Stats, true); stats.AccumulateUnsafe(character.HandsEnchant.Stats, true); stats.AccumulateUnsafe(character.HeadEnchant.Stats, true); stats.AccumulateUnsafe(character.LegsEnchant.Stats, true); stats.AccumulateUnsafe(character.ShouldersEnchant.Stats, true); if (character.MainHand != null && (character.MainHandEnchant.Slot == ItemSlot.OneHand || (character.MainHandEnchant.Slot == ItemSlot.TwoHand && character.MainHand.Slot == ItemSlot.TwoHand))) { stats.AccumulateUnsafe(character.MainHandEnchant.Stats, true); } if (character.OffHand != null && IncludeOffHandInCalculations(character) && ( ( character.OffHandEnchant.Slot == ItemSlot.OneHand && (character.OffHand.Slot == ItemSlot.OneHand || character.OffHand.Slot == ItemSlot.OffHand) && character.OffHand.Type != ItemType.None && character.OffHand.Type != ItemType.Shield ) || ( character.OffHandEnchant.Slot == ItemSlot.TwoHand && character.OffHand.Slot == ItemSlot.TwoHand ) || ( character.OffHandEnchant.Slot == ItemSlot.OffHand && character.OffHand.Slot == ItemSlot.OffHand && character.OffHand.Type == ItemType.Shield ) ) ) { stats.AccumulateUnsafe(character.OffHandEnchant.Stats, true); } stats.AccumulateUnsafe(character.RangedEnchant.Stats, true); stats.AccumulateUnsafe(character.WristEnchant.Stats, true); stats.EndUnsafe(); } }*/ /*public virtual Stats GetEnchantsStats(Character character) { Stats stats = new Stats(); AccumulateEnchantsStats(stats, character); return stats; }*/ public virtual void AccumulateBuffsStats(Stats stats, List<string> buffs) { foreach (string buffName in buffs) if (!string.IsNullOrEmpty(buffName)) { Buff buff = Buff.GetBuffByName(buffName); if (buff != null) { stats.Accumulate(buff.Stats); } } }
public Stats GetCharacterStats(Character character, Item additionalItem, bool computeAverageStats, CharacterCalculationsHealadin calc) { CalculationOptionsHealadin calcOpts = character.CalculationOptions as CalculationOptionsHealadin; BossOptions bossOpts = character.BossOptions; PaladinTalents talents = character.PaladinTalents; float fightLength = bossOpts.BerserkTimer; Stats statsRace = BaseStats.GetBaseStats(character.Level, CharacterClass.Paladin, character.Race); Stats statsBaseGear = GetItemStats(character, additionalItem); Stats statsBuffs = GetBuffsStats(character, calcOpts); Stats stats = statsBaseGear + statsBuffs + statsRace; ConvertRatings(stats, talents, calcOpts); if (computeAverageStats) { Stats statsAverage = new Stats(); foreach (SpecialEffect effect in stats.SpecialEffects()) { float trigger = 0f; if (calc == null) { trigger = 1.5f / calcOpts.Activity / (1f + stats.SpellHaste); if (effect.Trigger == Trigger.HealingSpellCrit || effect.Trigger == Trigger.SpellCrit) { trigger *= stats.SpellCrit; } } else { if (effect.Trigger == Trigger.HealingSpellCast || effect.Trigger == Trigger.HealingSpellHit) { trigger = 1f / Rotation.GetHealingCastsPerSec(calc); } else if (effect.Trigger == Trigger.HealingSpellCrit || effect.Trigger == Trigger.SpellCrit) { trigger = 1f / Rotation.GetHealingCritsPerSec(calc); } else if (effect.Trigger == Trigger.SpellCast || effect.Trigger == Trigger.SpellHit) { trigger = 1f / Rotation.GetSpellCastsPerSec(calc); } else if (effect.Trigger == Trigger.DamageOrHealingDone) { trigger = 1f / Rotation.GetHealingCastsPerSec(calc); } else if (effect.Trigger == Trigger.Use) { trigger = 0f; foreach (SpecialEffect childEffect in effect.Stats.SpecialEffects()) { float childTrigger = 0f; if (effect.Trigger == Trigger.HealingSpellCast || effect.Trigger == Trigger.HealingSpellHit) { childTrigger = 1f / Rotation.GetHealingCastsPerSec(calc); } else if (effect.Trigger == Trigger.HealingSpellCrit || effect.Trigger == Trigger.SpellCrit) { childTrigger = 1f / Rotation.GetHealingCritsPerSec(calc); } else if (effect.Trigger == Trigger.SpellCast || effect.Trigger == Trigger.SpellHit) { childTrigger = 1f / Rotation.GetSpellCastsPerSec(calc); } statsAverage.Accumulate(childEffect.Stats, effect.GetAverageUptime(0.0f, 1.0f) * childEffect.GetAverageStackSize(childTrigger, 1f, 1.5f, effect.Duration)); } } else { continue; } } statsAverage.Accumulate(effect.GetAverageStats(trigger, 1f, 1.5f, fightLength)); } statsAverage.ManaRestore *= fightLength; statsAverage.Healed *= fightLength; statsAverage.HealedPerSP *= fightLength; stats = statsBaseGear + statsBuffs + statsRace + statsAverage; ConvertRatings(stats, talents, calcOpts); } #region Set Bonuses int T11Count; character.SetBonusCount.TryGetValue("Reinforced Sapphirium Regalia", out T11Count); stats.BonusCritChanceDeathCoil = 0; // using this for Holy Light crit bonus, for now stats.BonusCritChanceFrostStrike = 0; // yes, I'm pure evil, using this to track 4T11 if (T11Count >= 2) { // T11 Pally 2 piece bonus: add 5% crit to HL stats.BonusCritChanceDeathCoil = 0.05f; } if (T11Count >= 4) { // T11 Pally 4 piece bonus: 540 spirit buff for 6 secs after HS cast stats.BonusCritChanceFrostStrike = 1; } #endregion Set Bonuses return(stats); }
public Stats getSpecialEffects(CalculationOptionsDPSDK calcOpts, SpecialEffect effect) { Stats statsAverage = new Stats(); Rotation rotation = calcOpts.rotation; if (effect.Trigger == Trigger.Use) { effect.AccumulateAverageStats(statsAverage); foreach (SpecialEffect e in effect.Stats.SpecialEffects()) { statsAverage.Accumulate(this.getSpecialEffects(calcOpts, e), (effect.Duration / effect.Cooldown)); } } else { double trigger = 0f; float chance = 0f; float unhastedAttackSpeed = 2f; switch (effect.Trigger) { case Trigger.MeleeCrit: case Trigger.PhysicalCrit: trigger = (1f / ((rotation.getMeleeSpecialsPerSecond() * (combatTable.DW ? 2f : 1f)) + (combatTable.combinedSwingTime != 0 ? 1f / combatTable.combinedSwingTime : 0.5f))); chance = combatTable.physCrits; unhastedAttackSpeed = (combatTable.MH != null ? combatTable.MH.baseSpeed : 2.0f); break; case Trigger.MeleeHit: case Trigger.PhysicalHit: trigger = (1f / ((rotation.getMeleeSpecialsPerSecond() * (combatTable.DW ? 2f : 1f)) + (combatTable.combinedSwingTime != 0 ? 1f / combatTable.combinedSwingTime : 0.5f))); chance = 1f - (combatTable.missedSpecial + combatTable.dodgedSpecial) * (1f - combatTable.totalMHMiss); unhastedAttackSpeed = (combatTable.MH != null ? combatTable.MH.baseSpeed : 2.0f); break; case Trigger.CurrentHandHit: trigger = (1f / ((rotation.getMeleeSpecialsPerSecond()) + (combatTable.combinedSwingTime != 0 ? 1f / combatTable.combinedSwingTime : 0.5f))); chance = 1f - (combatTable.missedSpecial + combatTable.dodgedSpecial) * (1f - combatTable.totalMHMiss); // TODO: need to know if this is MH or OH. unhastedAttackSpeed = (combatTable.MH != null ? combatTable.MH.baseSpeed : 2.0f); break; case Trigger.MainHandHit: trigger = (1f / ((rotation.getMeleeSpecialsPerSecond()) + (combatTable.combinedSwingTime != 0 ? 1f / combatTable.combinedSwingTime : 0.5f))); chance = 1f - (combatTable.missedSpecial + combatTable.dodgedSpecial) * (1f - combatTable.totalMHMiss); unhastedAttackSpeed = (combatTable.MH != null ? combatTable.MH.baseSpeed : 2.0f); break; case Trigger.OffHandHit: trigger = (1f / ((rotation.getMeleeSpecialsPerSecond()) + (combatTable.combinedSwingTime != 0 ? 1f / combatTable.combinedSwingTime : 0.5f))); chance = 1f - (combatTable.missedSpecial + combatTable.dodgedSpecial) * (1f - combatTable.totalMHMiss); unhastedAttackSpeed = (combatTable.OH != null ? combatTable.OH.baseSpeed : 2.0f); break; case Trigger.DamageDone: trigger = 1f / (((rotation.getMeleeSpecialsPerSecond() * (combatTable.DW ? 2f : 1f)) + rotation.getSpellSpecialsPerSecond()) + (combatTable.combinedSwingTime != 0 ? 1f / combatTable.combinedSwingTime: 0.5f)); chance = (1f - (combatTable.missedSpecial + combatTable.dodgedSpecial)) * (1f - combatTable.totalMHMiss); unhastedAttackSpeed = (combatTable.MH != null ? combatTable.MH.baseSpeed : 2.0f); break; case Trigger.DamageOrHealingDone: // Need to add Self Healing trigger = 1f / (((rotation.getMeleeSpecialsPerSecond() * (combatTable.DW ? 2f : 1f)) + rotation.getSpellSpecialsPerSecond()) + (combatTable.combinedSwingTime != 0 ? 1f / combatTable.combinedSwingTime : 0.5f)); chance = (1f - (combatTable.missedSpecial + combatTable.dodgedSpecial)) * (1f - combatTable.totalMHMiss); unhastedAttackSpeed = (combatTable.MH != null ? combatTable.MH.baseSpeed : 2.0f); break; case Trigger.DamageSpellCast: case Trigger.SpellCast: case Trigger.DamageSpellHit: case Trigger.SpellHit: trigger = 1f / rotation.getSpellSpecialsPerSecond(); chance = 1f - combatTable.spellResist; break; case Trigger.DamageSpellCrit: case Trigger.SpellCrit: trigger = 1f / rotation.getSpellSpecialsPerSecond(); chance = combatTable.spellCrits; break; case Trigger.BloodStrikeHit: trigger = rotation.CurRotationDuration / (rotation.BloodStrike * (combatTable.DW ? 2f : 1f)); chance = 1f; break; case Trigger.HeartStrikeHit: trigger = rotation.CurRotationDuration / rotation.HeartStrike; chance = 1f; break; case Trigger.BloodStrikeOrHeartStrikeHit: trigger = rotation.CurRotationDuration / ((rotation.BloodStrike + rotation.HeartStrike) * (combatTable.DW ? 2f : 1f)); chance = 1f; break; case Trigger.ObliterateHit: trigger = rotation.CurRotationDuration / (rotation.Obliterate * (combatTable.DW ? 2f : 1f)); chance = 1f; break; case Trigger.ScourgeStrikeHit: trigger = rotation.CurRotationDuration / rotation.ScourgeStrike; chance = 1f; break; case Trigger.DeathStrikeHit: trigger = rotation.CurRotationDuration / rotation.DeathStrike; chance = 1f; break; case Trigger.PlagueStrikeHit: trigger = rotation.CurRotationDuration / (rotation.PlagueStrike * (combatTable.DW ? 2f : 1f)); chance = 1f; break; case Trigger.DoTTick: trigger = (rotation.BloodPlague + rotation.FrostFever) / 3; chance = 1f; break; } #if false // Pull out the embedded handling in this situation. foreach (SpecialEffect e in effect.Stats.SpecialEffects()) { statsAverage.Accumulate(this.getSpecialEffects(calcOpts, e)); } #endif if (effect.MaxStack > 1) { float timeToMax = (float)Math.Min(calcOpts.FightLength * 60, effect.GetChance(unhastedAttackSpeed) * trigger * effect.MaxStack); float buffDuration = calcOpts.FightLength * 60f; if (effect.Stats.AttackPower == 250f || effect.Stats.AttackPower == 215f || effect.Stats.HasteRating == 57f || effect.Stats.HasteRating == 64f) { buffDuration = 20f; } if (timeToMax * .5f > buffDuration) { timeToMax = 2 * buffDuration; } statsAverage.Accumulate(effect.Stats, effect.GetAverageStackSize((float)trigger, chance, unhastedAttackSpeed, buffDuration)); } else { effect.AccumulateAverageStats(statsAverage, (float)trigger, chance, unhastedAttackSpeed, calcOpts.FightLength * 60); } } return(statsAverage); }
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; }
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; }
/// <summary>Gets the total Stats of the Character</summary> /// <param name="character"> /// The Character to get the total Stats of /// </param> /// <param name="additionalItem"> /// An additional item to grant the Character the stats of (as if it /// were worn) /// </param> /// <returns>The total stats for the Character</returns> public override Stats GetCharacterStats( Character character, Item additionalItem) { WarlockTalents talents = character.WarlockTalents; CalculationOptionsWarlock options = character.CalculationOptions as CalculationOptionsWarlock; Stats stats = BaseStats.GetBaseStats(character); // Items AccumulateItemStats(stats, character, additionalItem); // Buffs AccumulateBuffsStats(stats, character.ActiveBuffs); if (options.Imbue.Equals("Grand Spellstone")) { stats.HasteRating += 60f * (1f + talents.MasterConjuror * 1.5f); } else { Debug.Assert(options.Imbue.Equals("Grand Firestone")); stats.CritRating += 49f * (1f + talents.MasterConjuror * 1.5f); } ApplyPetsRaidBuff( stats, options.Pet, talents, character.ActiveBuffs); float aegis = 1f + talents.DemonicAegis * 0.10f; stats.SpellPower += 180f * aegis; // fel armor stats.SpellDamageFromSpiritPercentage += .3f * aegis; // fel armor // Talents float[] talentValues = { 0f, .04f, .07f, .1f }; Stats statsTalents = new Stats { //Demonic Embrace: increases your stamina by 4/7/10% BonusStaminaMultiplier = talentValues[talents.DemonicEmbrace], //Fel Vitality: increases your maximum Health & Mana by 1/2/3% BonusHealthMultiplier = talents.FelVitality * 0.01f, BonusManaMultiplier = talents.FelVitality * 0.01f, //Suppression: increases your chance to hit with spells by //1/2/3% SpellHit = (talents.Suppression * 0.01f), //Demonic Tactics: increases your spell crit chance by //2/4/6/8/10% //Backlash: increases your spell crit chance by 1/2/3% BonusCritChance = talents.DemonicTactics * 0.02f + talents.Backlash * 0.01f }; if (talents.Eradication > 0) { talentValues = new float[] { 0f, .06f, .12f, .20f }; statsTalents.AddSpecialEffect( new SpecialEffect( Trigger.CorruptionTick, new Stats() { SpellHaste = talentValues[talents.Eradication] }, 6f, 0f, .06f)); } stats.Accumulate(statsTalents); stats.ManaRestoreFromMaxManaPerSecond = Math.Max( stats.ManaRestoreFromMaxManaPerSecond, .002f * Spell.CalcUprate( talents.ImprovedSoulLeech * .5f, 15f, options.Duration * 1.1f)); return(stats); }
private Stats CalcCritProcs() { if (CalcOpts.NoProcs) { return new Stats(); } Dictionary<int, float> periods = new Dictionary<int, float>(); Dictionary<int, float> chances = new Dictionary<int, float>(); PopulateTriggers(periods, chances); Stats procStats = new Stats(); foreach (SpecialEffect effect in Stats.SpecialEffects()) { if (!periods.ContainsKey((int)effect.Trigger)) { continue; } Stats proc = CalcNormalProc(effect, periods, chances); procStats.Accumulate(proc); if (effect.Trigger == Trigger.Use && !IsDoublePot(effect)) { ExtraCritAtMax += StatUtils.CalcSpellCrit(effect.Stats, BaseIntellect, CalcOpts.PlayerLevel) - StatUtils.CalcSpellCrit(proc, BaseIntellect, CalcOpts.PlayerLevel); } } return procStats; }
public static void RenderStatsGraph(Graphics g, int graphWidth, int graphHeight, Character character, Stats[] statsList, Color[] colors, int scale, string explanatoryText, string calculation, Style style) { CharacterCalculationsBase baseCalc = Calculations.GetCharacterCalculations(character); float baseFigure = GetCalculationValue(baseCalc, calculation); g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; float graphOffset = graphWidth / 2.0f, graphStep = (graphWidth - 100) / 2.0f / scale; if (statsList.Length == 0 || statsList.Length > colors.Length) { return; // more than 12 elements for the array would run out of colours } float minDpsChange = 0f, maxDpsChange = 0f; PointF[][] points = new PointF[statsList.Length][]; for (int index = 0; index < statsList.Length; index++) { Stats newStats = new Stats(); points[index] = new PointF[2 * scale + 1]; newStats.Accumulate(statsList[index], -scale - 1); for (int count = -scale; count <= scale; count++) { newStats.Accumulate(statsList[index]); CharacterCalculationsBase currentCalc = Calculations.GetCharacterCalculations(character, new Item() { Stats = newStats }, false, false, false); float currentFigure = GetCalculationValue(currentCalc, calculation); float dpsChange = currentFigure - baseFigure; points[index][count + scale] = new PointF(graphOffset + count * graphStep, dpsChange); if (dpsChange < minDpsChange) { minDpsChange = dpsChange; } if (dpsChange > maxDpsChange) { maxDpsChange = dpsChange; } } } float DpsVariance = maxDpsChange - minDpsChange; if (DpsVariance == 0) { DpsVariance = 1; } for (int index = 0; index < statsList.Length; index++) { for (int count = -scale; count <= scale; count++) { points[index][count + scale].Y = (int)((maxDpsChange - points[index][count + scale].Y) * (graphHeight - 48) / DpsVariance) + 20; } Brush statBrush = new SolidBrush(colors[index]); switch (style) { case Style.DpsWarr: g.DrawLines(new Pen(statBrush, 3), points[index]); break; case Style.Mage: g.DrawLines(new Pen(statBrush, 1), points[index]); break; } } RenderGrid(g, graphWidth, graphHeight, character, statsList, colors, scale, 1f, "F1", explanatoryText, calculation, style, minDpsChange, maxDpsChange, DpsVariance, true); }
private float CalcRemainingProcs() { if (CalcOpts.NoProcs) { return 0f; } Dictionary<int, float> periods = new Dictionary<int, float>(); Dictionary<int, float> chances = new Dictionary<int, float>(); PopulateTriggers(periods, chances); float procdDamage = 0f; Stats procStats = new Stats(); foreach (SpecialEffect effect in Stats.SpecialEffects()) { if (!periods.ContainsKey((int)effect.Trigger)) { continue; } Stats effectStats = effect.Stats; if (effectStats.HolySummonedDamage > 0) { SpellModifiers mods = new SpellModifiers(); mods.AddCritChance(.05f + Stats.SpellCritOnTarget); mods.AddMultiplicativeMultiplier(Stats.BonusHolyDamageMultiplier); procdDamage += CalcDamageProc(effect, effect.Stats.HolySummonedDamage, periods, chances, mods); } else if ( effectStats.ShadowDamage > 0 || effectStats.FireDamage > 0 || effectStats.NatureDamage > 0 || effectStats.HolyDamage > 0 || effectStats.FrostDamage > 0) { SpellModifiers mods = new SpellModifiers(); mods.Accumulate(SpellModifiers); if (effectStats.ShadowDamage > 0) { AddShadowModifiers(mods); } else if (effectStats.FireDamage > 0) { AddFireModifiers(mods); } procdDamage += CalcDamageProc( effect, effectStats.ShadowDamage + effectStats.FireDamage + effectStats.NatureDamage + effectStats.HolyDamage + effectStats.FrostDamage, periods, chances, mods); } else { procStats.Accumulate(CalcNormalProc(effect, periods, chances)); } } procStats.HasteRating = procStats.SpellHaste = procStats.Mana = procStats.ManaRestore = procStats.ManaRestoreFromMaxManaPerSecond = procStats.Mp5 = procStats.CritRating = procStats.SpellCrit = procStats.SpellCritOnTarget = procStats.PhysicalCrit = 0; Stats.Accumulate(procStats); return procdDamage; }
public void UpdateScalingGraph(Character character, Stats[] statsList, Stats baseStat, bool requiresReferenceCalculations, Color[] colors, int scale, string explanatoryText, string calculation) { CharacterCalculationsBase baseCalc = Calculations.GetCharacterCalculations(character); if (statsList.Length == 0 || statsList.Length > colors.Length) { return; // more than 12 elements for the array would run out of colours } Point[][] points = new Point[statsList.Length][]; // extract property data for relative stats calculations KeyValuePair <PropertyInfo, float>[] properties = new KeyValuePair <PropertyInfo, float> [statsList.Length]; for (int index = 0; index < statsList.Length; index++) { var p = statsList[index].Values(x => x > 0); foreach (var kvp in p) { properties[index] = kvp; } points[index] = new Point[2 * scale + 1]; } float unit = 1f; var bp = baseStat.Values(x => x > 0); foreach (var kvp in bp) { unit = kvp.Value; } Chart.Series.Clear(); for (int count = -scale; count <= scale; count++) { Stats newStats = new Stats(); newStats.Accumulate(baseStat, count); Item item = new Item() { Stats = newStats }; if (requiresReferenceCalculations) { Calculations.GetCharacterCalculations(character, item, true, false, false); } for (int index = 0; index < statsList.Length; index++) { ComparisonCalculationBase currentCalc = CalculationsBase.GetRelativeStatValue(character, properties[index].Key, item, properties[index].Value); float dpsChange = GetCalculationValue(currentCalc, calculation); points[index][count + scale] = new Point(count * unit, dpsChange); } } for (int index = 0; index < statsList.Length; index++) { Style dataPointStyle = new Style(typeof(LineDataPoint)); dataPointStyle.Setters.Add(new Setter(DataPoint.TemplateProperty, Resources["InvisibleDataPointTemplate"])); dataPointStyle.Setters.Add(new Setter(DataPoint.BackgroundProperty, new SolidColorBrush(colors[index]))); Chart.Series.Add(new LineSeries() { Title = statsList[index].ToString(), ItemsSource = points[index], IndependentValuePath = "X", DependentValuePath = "Y", DataPointStyle = dataPointStyle, }); } Chart.Axes.Clear(); Chart.Axes.Add(new LinearAxis() { Orientation = AxisOrientation.X, Title = "Stat Change", ShowGridLines = true, }); Chart.Axes.Add(new LinearAxis() { Orientation = AxisOrientation.Y, Title = calculation, ShowGridLines = true, }); // restore reference calculation if (requiresReferenceCalculations) { Stats newStats = new Stats(); Item item = new Item() { Stats = newStats }; Calculations.GetCharacterCalculations(character, item, true, false, false); } orgDataDirty = true; }
public float AccumulateAverageStats(Stats stats, float triggerInterval = 0.0f, float triggerChance = 1.0f, float attackSpeed = 3.0f, float fightDuration = 0.0f) { Stats.GenerateSparseData(); float factor = GetAverageFactor(triggerInterval, triggerChance, attackSpeed, fightDuration); stats.Accumulate(Stats, factor); return factor; }
//NOTE: This is currently unused, because it doesn't account for procs which are both mitigation and survival (ie armor and agility procs) private void AccumulateSpecialEffect(Stats statsProcs, Stats statsEffect, float effectUptime, float temporarySurvivalScale) { if (temporarySurvivalScale != 1f && statsEffect.Armor + statsEffect.Health + statsEffect.Stamina > 0f) { //Subject to Temporary Survival scaling if (temporarySurvivalScale < 1f) { statsProcs.Accumulate(statsEffect, effectUptime * temporarySurvivalScale); } else { statsProcs.Accumulate(statsEffect, 1f - ((2f - temporarySurvivalScale) * (1f - effectUptime))); } } else { statsProcs.Accumulate(statsEffect, effectUptime); } }
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; }
public override Stats GetCharacterStats(Character character, Item additionalItem) { PriestTalents talents = character.PriestTalents; Stats statsTotal = new Stats(); Stats baseStats = BaseStats.GetBaseStats(character.Level, character.Class, character.Race); Stats itemStats = GetItemStats(character, additionalItem); Stats buffStats = GetBuffsStats(character, _calculationOptions); // Get the gear/enchants/buffs stats loaded in statsTotal.Accumulate(baseStats); statsTotal.Accumulate(itemStats); statsTotal.Accumulate(buffStats); Stats statsTalents = new Stats() { // we can only wear items that are cloth so we always have our specialization, even naked. BonusIntellectMultiplier = 0.05f, BonusShadowDamageMultiplier = (1 + 0.02f*talents.TwinDisciplines)* (1 + 0.02f*talents.TwistedFaith)* (1 + 0.15f*talents.Shadowform) - 1, BonusHolyDamageMultiplier = (1 + 0.02f*talents.TwinDisciplines) - 1, // this is the shadow priest model so they must have 'Shadow Power' BonusSpellPowerMultiplier = .15f, }; statsTotal.Accumulate(statsTalents); statsTotal.Stamina = (float)Math.Floor(statsTotal.Stamina * (1 + statsTotal.BonusStaminaMultiplier)); statsTotal.Intellect += (float)Math.Floor(itemStats.Intellect * statsTotal.BonusIntellectMultiplier); statsTotal.Spirit = (float) Math.Round(statsTotal.Spirit * (1 + statsTotal.BonusSpiritMultiplier)); statsTotal.Health += (float)Math.Floor(StatConversion.GetHealthFromStamina(statsTotal.Stamina) * (1f + statsTotal.BonusHealthMultiplier)); statsTotal.Mana = (float) Math.Round(statsTotal.Mana + StatConversion.GetManaFromIntellect(statsTotal.Intellect)); statsTotal.Mana = (float) Math.Round(statsTotal.Mana*(1f + statsTotal.BonusManaMultiplier)); statsTotal.SpellPower += statsTotal.Intellect - 10; float hasteFromRating = StatConversion.GetSpellHasteFromRating(statsTotal.HasteRating); float talentedHaste = (1 + hasteFromRating) * (1 + talents.Darkness * .01f) - 1; statsTotal.SpellHaste += character.Race == CharacterRace.Goblin ? talentedHaste*1.01f : talentedHaste; float baseBonus = (float) Math.Floor(baseStats.Spirit*statsTotal.BonusSpiritMultiplier); float itemBonus = (float) Math.Floor(itemStats.Spirit*statsTotal.BonusSpiritMultiplier); float spiritFromItemsAndEffects = baseBonus + itemBonus + itemStats.Spirit; float hitRatingFromSpirit = (0.5f * talents.TwistedFaith) * Math.Max(0f, spiritFromItemsAndEffects); statsTotal.HitRating += hitRatingFromSpirit; statsTotal.SpellHit += StatConversion.GetSpellHitFromRating(statsTotal.HitRating); // ignoring the base crit percentage here as the in-game tooltip says that the int -> crit conversion contains the base. float critFromInt = StatConversion.GetSpellCritFromIntellect(statsTotal.Intellect) + 0.012375f; float critFromRating = StatConversion.GetSpellCritFromRating(statsTotal.CritRating); statsTotal.SpellCrit = character.Race == CharacterRace.Worgen ? (critFromInt + critFromRating) + .01f : (critFromInt + critFromRating); // Armor statsTotal.Armor = statsTotal.Armor * (1f + statsTotal.BaseArmorMultiplier); statsTotal.BonusArmor = statsTotal.BonusArmor * (1f + statsTotal.BonusArmorMultiplier); statsTotal.Armor += statsTotal.BonusArmor; statsTotal.Armor = (float)Math.Round(statsTotal.Armor); return statsTotal; }
public static Stats GetBaseStats(int level, CharacterClass characterClass, CharacterRace characterRace, DruidForm characterForm) { // Health, Mana and some other things are same for every race. lock (syncLock) { #region Cache if (level == _lastLevel && characterClass == _lastClass && characterRace == _lastRace && characterForm == _lastForm) return _lastStats.Clone(); _lastLevel = level; _lastClass = characterClass; _lastRace = characterRace; _lastForm = characterForm; #endregion Stats S = new Stats(); #region Race, not class benefit // Most Level 85 Race and Class Stats come from: // http://code.google.com/p/simulationcraft/source/browse/branches/cataclysm/engine/sc_rating.cpp?r=6207 // When they were still at 80 as of Jan 01st, 2011 // From SimCraft Stats race = new Stats(); switch (characterRace) { // Alliance case CharacterRace.Human: race.Strength = 20; race.Agility = 20; race.Stamina = 20; race.Intellect = 20; race.Spirit = 20; break; case CharacterRace.Dwarf: race.Strength = 25; race.Agility = 16; race.Stamina = 21; race.Intellect = 19; race.Spirit = 19; break; case CharacterRace.NightElf: race.Strength = 16; race.Agility = 24; race.Stamina = 20; race.Intellect = 20; race.Spirit = 20; break; case CharacterRace.Gnome: race.Strength = 15; race.Agility = 22; race.Stamina = 20; race.Intellect = 24; race.Spirit = 20; break; case CharacterRace.Draenei: race.Strength = 21; race.Agility = 17; race.Stamina = 20; race.Intellect = 20; race.Spirit = 22; break; case CharacterRace.Worgen: race.Strength = 23; race.Agility = 22; race.Stamina = 20; race.Intellect = 16; race.Spirit = 19; break; // Horde case CharacterRace.Orc: race.Strength = 23; race.Agility = 17; race.Stamina = 21; race.Intellect = 17; race.Spirit = 22; break; case CharacterRace.Undead: race.Strength = 19; race.Agility = 18; race.Stamina = 20; race.Intellect = 18; race.Spirit = 25; break; case CharacterRace.Tauren: race.Strength = 25; race.Agility = 16; race.Stamina = 21; race.Intellect = 16; race.Spirit = 22; break; case CharacterRace.Troll: race.Strength = 21; race.Agility = 22; race.Stamina = 20; race.Intellect = 16; race.Spirit = 21; break; case CharacterRace.BloodElf: race.Strength = 17; race.Agility = 22; race.Stamina = 20; race.Intellect = 23; race.Spirit = 18; break; case CharacterRace.Goblin: race.Strength = 17; race.Agility = 22; race.Stamina = 20; race.Intellect = 23; race.Spirit = 20; break; default: { break; } }; // From Chardev (85) //Class Str Agi Sta Int Spi //Druid 76 69 86 136 153 //Shaman 111 60 128 119 136 //Death Knight 171 101 154 16 44 //Hunter 60 178 119 77 88 //Mage 17 26 43 187 175 //Paladin 144 77 136 86 97 //Priest 26 34 51 188 183 //Rogue 102 186 94 26 53 //Warlock 43 51 76 161 166 //Warrior 169 103 153 17 44 #endregion #region Base Stats #region All Classes S.Miss = 0.05f; S.Block = 0.00f; S.Parry = 0.00f; #endregion switch (characterClass) { #region Death Knight case CharacterClass.DeathKnight: Stats dk = new Stats() { Strength = 171, Agility = 101, Stamina = 274, Intellect = 16, Spirit = 44, Health = 43025f, Dodge = 0.05f, Parry = 0.05f, Block = 0.00f, PhysicalCrit = 0.0049f, AttackPower = 595f, }; S.Accumulate(race); S.Accumulate(dk); break; #endregion #region Druid case CharacterClass.Druid: Stats druid = new Stats() { Strength = 76, Agility = 69, Stamina = 86, Intellect = 136, Spirit = 153, Health = 39533f, Mana = 18635f, Dodge = 0.03758f, Parry = 0.05f, Block = 0.05f, PhysicalCrit = 0.03192f, AttackPower = 613f, SpellCrit = 0.0185f, Mp5 = 931f, }; S.Accumulate(race); S.Accumulate(druid); switch (characterForm) { case DruidForm.Moonkin: case DruidForm.Caster: S.AttackPower = -10; S.PhysicalCrit = 0.0743f; S.Dodge = 0.0556970f; //?? break; case DruidForm.Bear: S.AttackPower = 255; S.PhysicalCrit = 0.074755f; S.Dodge = 0.0556970f; S.BonusStaminaMultiplier = 0.2f; break; case DruidForm.Cat: S.AttackPower = 235; S.PhysicalCrit = 0.074755f; S.Dodge = 0.0556970f; break; default: break; } break; #endregion #region Hunter case CharacterClass.Hunter: Stats hun = new Stats() { // Stats updated 8/19/2011 4.2 w/ Troll Hunter: Tsevon @ US-Dragonmaw w/ no spec. Strength = 60, Agility = 178, Stamina = 119, Intellect = 77, Spirit = 88, Health = 39037, Dodge = 0.03758f, Parry = 0.05f, // This assumes ALL AP from full AP += AGI * 2 // So naked Hunter has 31 AP un accounted for. // Naked troll, no gear, no spec, LW & Skinning. PhysicalCrit = 0, AttackPower = 31f, RangedAttackPower = 31f, }; S.Accumulate(race); S.Accumulate(hun); break; #endregion #region Mage case CharacterClass.Mage: Stats mag = new Stats() { Strength = 17, Agility = 26, Stamina = 43, Intellect = 187, Spirit = 175, Health = 36853f, Mana = 17138f, Dodge = 0.03758f, Parry = 0.05f, }; S.Accumulate(race); S.Accumulate(mag); break; #endregion #region Paladin case CharacterClass.Paladin: Stats pal = new Stats() { Strength = 144, Agility = 77, Stamina = 136, Intellect = 86, Spirit = 97, Health = 43285f, Mana = 23422, Dodge = 0.05f, Parry = 0.05f, Block = 0.05f, PhysicalCrit = 0.00652f, AttackPower = 235f, SpellCrit = 0.033355f, }; S.Accumulate(race); S.Accumulate(pal); break; #endregion #region Priest case CharacterClass.Priest: // added adjustments to the base race here because the math using the previous stats // just don't work for the in game calculations on priest tests. // also unsure how these changes would effect other modules if moved up. // adding or subracting from the priest stats don't work and throws all other class // calculations off. switch (characterRace) { case CharacterRace.Human: race.Spirit = 19; break; case CharacterRace.Gnome: race.Intellect = 23; break; case CharacterRace.Goblin: race.Spirit = 18; break; } Stats pri = new Stats() { Strength = 26, Agility = 34, Stamina = 51, Intellect = 169, Spirit = 178, Health = 43285f, Mana = 20590f, Dodge = 0.0337780f, Parry = 0.05f, PhysicalCrit = 0.027f, SpellCrit = 0.012375f, }; pri.Mp5 = pri.Mana * 0.05f; // Always 5% of base mana in regen. S.Accumulate(race); S.Accumulate(pri); break; #endregion #region Rogue case CharacterClass.Rogue: Stats rog = new Stats() { Strength = 102, Agility = 186, Stamina = 94, Intellect = 26, Spirit = 53, Health = 40529f, Dodge = 0.03758f, Parry = 0.05f, PhysicalCrit = 0.03192f, AttackPower = 613f, }; S.Accumulate(race); S.Accumulate(rog); break; #endregion #region Shaman case CharacterClass.Shaman: Stats sha = new Stats() { Strength = 111, Agility = 60, Stamina = 128, Intellect = 119, Spirit = 136, Health = 37037f, Mana = 23430f, Dodge = 0.0193f, Parry = 0.05f, Block = 0.05f, PhysicalCrit = 0.02910375f, AttackPower = 140f, SpellCrit = 0.022057f, SpellPower = -10, }; S.Accumulate(race); S.Accumulate(sha); break; #endregion #region Warlock case CharacterClass.Warlock: Stats warlock = new Stats() { Strength = 43, Agility = 51, Stamina = 76, Intellect = 153, Spirit = 161, Health = 38184f, Mana = 20553f, Dodge = 0.0238110f, Parry = 0.05f, Block = 0.05f, PhysicalCrit = 0.026219999417663f, SpellCrit = 0.017000000923872f, }; S.Accumulate(race); S.Accumulate(warlock); break; #endregion #region Warrior case CharacterClass.Warrior: Stats war = new Stats() { Strength = 169, Agility = 103, Stamina = 153, Intellect = 17, Spirit = 44, Health = 43285f, Dodge = 0.05f, Parry = 0.05f, Block = 0.05f, PhysicalCrit = 0.03192f, AttackPower = 613f, }; S.Accumulate(race); S.Accumulate(war); break; #endregion #region No Class default: break; #endregion } #endregion #region Racials // Resistance do not stack with other buffs. Until then I'll commenting them out if (characterRace == CharacterRace.Gnome) //CATA: changed from 5% int to 5% mana { // S.ArcaneResistance += 85f; S.BonusManaMultiplier = 0.05f; //S.BonusIntellectMultiplier = 0.05f; } else if (characterRace == CharacterRace.Human) { S.BonusSpiritMultiplier = 0.03f; // Patch 4.0.6+ changed from a 3 minute cooldown to 2 minute cooldown S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { PVPTrinket = 1 }, 0f, 120f)); } else if (characterRace == CharacterRace.NightElf) { // S.NatureResistance += 85f; S.Miss += 0.02f; } else if (characterRace == CharacterRace.Dwarf) { // S.FrostResistance += 85f; // Armor +10% for 8 Sec. S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { BaseArmorMultiplier = .1f }, 8, 120)); // TODO: Add debuff removal. Doesn't work on all bosses so not sure if we want to. } else if (characterRace == CharacterRace.Draenei) { // S.ArcaneResistance += 85f; S.SpellHit += 0.01f; S.PhysicalHit += 0.01f; // Patch 4.0.6+ changed from a scaling Health restore to a flat 20% of max health S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { HealthRestoreFromMaxHealth = 0.2f / 15f }, 15f, 180f)); } else if (characterRace == CharacterRace.Worgen) { // S.NatureResistance = 64f; // S.ShadowResistance = 64f; // Patch 4.0.6+ Darkflight changed from a 3 minute CD to a 2 minute CD S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { MovementSpeed = 0.40f }, 10f, 120f)); S.PhysicalCrit += 0.01f; S.SpellCrit += 0.01f; } else if (characterRace == CharacterRace.Tauren) { // S.NatureResistance = 85f; S.Health = (float)Math.Floor(S.Health * 1.05f); } else if (characterRace == CharacterRace.Troll) { S.SnareRootDurReduc = .15f; if (characterClass == CharacterClass.DeathKnight || characterClass == CharacterClass.Warrior || characterClass == CharacterClass.Rogue) S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { PhysicalHaste = 0.2f }, 10f, 180f)); else S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { SpellHaste = 0.2f, PhysicalHaste = 0.2f }, 10f, 180f)); } else if (characterRace == CharacterRace.Undead) { S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { FearDurReduc = 1f }, .1f, 120f)); } else if (characterRace == CharacterRace.Orc) { S.StunDurReduc = 0.15f; if (characterClass == CharacterClass.Shaman) S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { AttackPower = 65 + (level * 13), SpellPower = 75 + (level * 6) }, 15f, 120f)); else if (characterClass == CharacterClass.Warlock || characterClass == CharacterClass.Mage) S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { SpellPower = 75 + (level * 6) }, 15f, 120f)); else S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { AttackPower = 65 + (level * 13) }, 15f, 120f)); } else if (characterRace == CharacterRace.BloodElf) { // S.ArcaneResistance += 85f; if (characterClass == CharacterClass.DeathKnight || characterClass == CharacterClass.Rogue || characterClass == CharacterClass.Hunter) S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { ManaorEquivRestore = .15f }, 0f, 120f)); else if (characterClass == CharacterClass.Warrior) S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { BonusRageGen = 15f }, 0f, 120f)); else S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { ManaorEquivRestore = .06f }, 0f, 120f)); } else if (characterRace == CharacterRace.Goblin) { S.PhysicalHaste += 0.01f; S.SpellHaste += 0.01f; // TODO: The damage of the rocket belt proc is dependent on the character's current AP and SP S.AddSpecialEffect(new SpecialEffect(Trigger.Use, new Stats() { FireDamage = 1f + (level * 2) }, 0f, 120f)); } else if (characterRace == CharacterRace.PandarenAlliance || characterRace == CharacterRace.PandarenHorde) { } #endregion _lastStats = S.Clone(); return S; } }