public void SetDPS(CharacterCalculationsRetribution calc) { calc.WhiteSkill = White; calc.SealSkill = Seal; calc.SealDotSkill = SealDot; calc.CommandSkill = SoC; calc.JudgementSkill = Judge; calc.TemplarsVerdictSkill = TV; calc.CrusaderStrikeSkill = CS; calc.ConsecrationSkill = Cons; calc.ExorcismSkill = Exo; calc.HolyWrathSkill = HW; calc.HammerOfWrathSkill = HoW; calc.GoakSkill = GoaK; calc.DPSPoints = White.GetDPS() + Seal.GetDPS() + SealDot.GetDPS() + SoC.GetDPS() + Judge.GetDPS() + CS.GetDPS() + TV.GetDPS() + Exo.GetDPS() + HW.GetDPS() + Cons.GetDPS() + HoW.GetDPS() + GoaK.GetDPS() + calc.OtherDPS; }
public override void SetCharacterCalculations(CharacterCalculationsRetribution calc) { calc.Solution = new RotationSolution(); foreach (Skill skill in new[] { Judge, CS, DS, Cons, Exo, HoW }) { float effectiveCooldown; if (skill.UsableAfter20PercentHealth) { if (skill.UsableBefore20PercentHealth) { effectiveCooldown = Combats.CalcOpts.GetEffectiveAbilityCooldown(skill.RotationAbility.Value) * (1f - Combats.CalcOpts.TimeUnder20) + Combats.CalcOpts.GetEffectiveAbilityCooldownAfter20PercentHealth( skill.RotationAbility.Value) * Combats.CalcOpts.TimeUnder20; } else { effectiveCooldown = Combats.CalcOpts.GetEffectiveAbilityCooldownAfter20PercentHealth( skill.RotationAbility.Value); } } else { if (skill.UsableBefore20PercentHealth) { effectiveCooldown = Combats.CalcOpts.GetEffectiveAbilityCooldown( skill.RotationAbility.Value); } else { effectiveCooldown = 0; } } calc.Solution.SetAbilityEffectiveCooldown(skill.RotationAbility.Value, effectiveCooldown); } calc.Rotation = null; }
public void SetDPS(CharacterCalculationsRetribution calc) { SetCharacterCalculations(calc); calc.AverageSoVStack = AverageSoVStackSize(); calc.SoVOvertake = SoVOvertakeTime(); calc.WhiteDPS = White.WhiteDPS(); calc.SealDPS = SealDPS(Seal, SealDot); calc.JudgementDPS = GetAbilityDps(Judge); calc.DivineStormDPS = GetAbilityDps(DS); calc.CrusaderStrikeDPS = GetAbilityDps(CS); calc.ConsecrationDPS = GetAbilityDps(Cons); calc.ExorcismDPS = GetAbilityDps(Exo); calc.HammerOfWrathDPS = GetAbilityDps(HoW); calc.HandOfReckoningDPS = HandOfReckoningDPS(HoR); calc.WhiteSkill = White; calc.SealSkill = Seal; calc.JudgementSkill = Judge; calc.DivineStormSkill = DS; calc.CrusaderStrikeSkill = CS; calc.ConsecrationSkill = Cons; calc.ExorcismSkill = Exo; calc.HammerOfWrathSkill = HoW; calc.HandOfReckoningSkill = HoR; calc.DPSPoints = calc.WhiteDPS + calc.SealDPS + calc.JudgementDPS + calc.CrusaderStrikeDPS + calc.DivineStormDPS + calc.ExorcismDPS + calc.HandOfReckoningDPS + calc.ConsecrationDPS + calc.HammerOfWrathDPS + calc.OtherDPS; }
public override void SetCharacterCalculations(CharacterCalculationsRetribution calc) { calc.Solution = Solution; calc.Rotation = Rotation; }
public abstract void SetCharacterCalculations(CharacterCalculationsRetribution calc);
public CharacterCalculationsBase GetCharacterCalculations(Character character, Item additionalItem, RotationCalculation rot) { // First things first, we need to ensure that we aren't using bad data CharacterCalculationsRetribution calc = new CharacterCalculationsRetribution(); if (character == null) { return calc; } if (rot == null) { return calc; } calc.CombatStats = rot.Stats; calc.Character = rot.Character; calc.BasicStats = GetCharacterStats(character, additionalItem, false); //Damage procs are modeled as DPS calc.OtherDPS = new MagicDamage("", character, rot.Stats, DamageType.Arcane).AverageDamage + new MagicDamage("", character, rot.Stats, DamageType.Fire).AverageDamage + new MagicDamage("", character, rot.Stats, DamageType.Shadow).AverageDamage + new MagicDamage("", character, rot.Stats, DamageType.Frost).AverageDamage + new MagicDamage("", character, rot.Stats, DamageType.Nature).AverageDamage + new MagicDamage("", character, rot.Stats, DamageType.Holy).AverageDamage; rot.SetDPS(calc); calc.OverallPoints = calc.DPSPoints; return calc; }
/// <summary> /// GetCharacterCalculations is the primary method of each model, where a majority of the calculations /// and formulae will be used. GetCharacterCalculations should call GetCharacterStats(), and based on /// those total stats for the character, and any calculationoptions on the character, perform all the /// calculations required to come up with the final calculations defined in /// CharacterDisplayCalculationLabels, including an Overall rating, and all Sub ratings defined in /// SubPointNameColors. /// </summary> /// <param name="character">The character to perform calculations for.</param> /// <param name="additionalItem">An additional item to treat the character as wearing. /// This is used for gems, which don't have a slot on the character to fit in, so are just /// added onto the character, in order to get gem calculations.</param> /// <returns>A custom CharacterCalculations object which inherits from CharacterCalculationsBase, /// containing all of the final calculations defined in CharacterDisplayCalculationLabels. See /// CharacterCalculationsBase comments for more details.</returns> public override CharacterCalculationsBase GetCharacterCalculations(Character character, Item additionalItem) { CalculationOptionsRetribution calcOpts = character.CalculationOptions as CalculationOptionsRetribution; GetTalents(character); Stats stats = GetCharacterStats(character, additionalItem); CharacterCalculationsRetribution calcs = new CharacterCalculationsRetribution(); calcs.BasicStats = stats; calcs.ActiveBuffs = new List <Buff>(character.ActiveBuffs); #region Damage Multipliers float twoHandedSpec = 1f + (0.02f * (float)calcOpts.TwoHandedSpec); float impSancAura = 1f + 0.01f * (float)calcOpts.ImprovedSanctityAura; float crusade = 1f + 0.01f * (float)calcOpts.Crusade; float vengeance = 1f + 0.03f * (float)calcOpts.Vengeance; float sancAura = 1f + 0.1f * (float)calcOpts.SanctityAura; float spellPower = 1f + stats.BonusSpellPowerMultiplier; // Covers all % spell damage increases. Misery, FI. float physPower = 1f + stats.BonusPhysicalDamageMultiplier; // Covers all % physical damage increases. Blood Frenzy, FI. float partialResist = 0.953f; // Average of 4.7% damage lost to partial resists on spells float physCritMult = 1f + stats.BonusCritMultiplier * 2f; float spellCritMult = 1f + stats.BonusCritMultiplier * 3f; float jotc = 219f; // Avenging Wrath -- Calculating uptime float fightDuration = calcOpts.FightLength * 60; int remainder = 0, noOfFullAW = 0; int div = Math.DivRem(Convert.ToInt32(fightDuration), 180, out remainder); if (remainder == 0) { noOfFullAW = div; } else { noOfFullAW = Convert.ToInt32(Math.Ceiling(Convert.ToDouble((fightDuration + 20) / 180))); } float partialUptime = fightDuration - noOfFullAW * 180; if (partialUptime < 0) { partialUptime = 0; } float totalUptime = partialUptime + noOfFullAW * 20f; float avWrath = 1f + 0.30f * totalUptime / fightDuration; // Combined damage multipliers float allDamMult = avWrath * crusade * impSancAura; float holyDamMult = allDamMult * spellPower * sancAura * vengeance; float physDamMult = allDamMult * physPower * twoHandedSpec * vengeance; #endregion float dpsWhite = 0f, dpsSeal = 0f, dpsWindfury = 0f, dpsCrusader = 0f, dpsJudgement = 0f, dpsConsecration = 0f, dpsExorcism = 0f; float baseSpeed = 1f, hastedSpeed = 1f, baseDamage = 0f, mitigation = 1f; float whiteHit = 0f, physCrits = 0f, totalMiss = 0f, spellCrits = 0f, spellResist = 0f; float chanceToGlance = 0.25f, glancingAmount = 0.35f; if (character.MainHand != null) { baseSpeed = character.MainHand.Speed; baseDamage = (character.MainHand.MinDamage + character.MainHand.MaxDamage) / 2f + stats.WeaponDamage; } #region Attack Speed { hastedSpeed = baseSpeed / (1f + (stats.HasteRating / 1576f)); // Mongoose Enchant grants 2% haste if (stats.MongooseProc > 0) { hastedSpeed /= 1f + (0.02f * 0.4f); // ASSUMPTION: Mongoose has a 40% uptime } if (stats.Bloodlust > 0) { float bloodlustUptime = (calcOpts.Bloodlust * 40f); if (bloodlustUptime > fightDuration) { bloodlustUptime = 1f; } else { bloodlustUptime /= fightDuration; } hastedSpeed /= 1f + (0.3f * bloodlustUptime); } } #endregion #region Mitigation { float targetArmor = calcOpts.BossArmor, totalArP = stats.ArmorPenetration; // Effective armor after ArP targetArmor -= totalArP; if (targetArmor < 0) { targetArmor = 0f; } // Convert armor to mitigation mitigation = 1f - (targetArmor / (targetArmor + 10557.5f)); // Executioner enchant. ASSUMPTION: Executioner has a 40% uptime. if (stats.ExecutionerProc > 0) { float exeArmor = targetArmor, exeMitigation = 1.0f, exeUptime = 0.4f, exeArmorPen = 840f; // Find mitigation while Executioner is up exeArmor = targetArmor - exeArmorPen; if (exeArmor < 0) { exeArmor = 0f; } exeMitigation = 1f - (exeArmor / (exeArmor + 10557.5f)); // Weighted average of mitigation with and without Executioner, based on Executioner uptime mitigation = (exeMitigation * exeUptime) + (mitigation * (1 - exeUptime)); } } #endregion #region Crits, Misses, Resists { // Crit: Base .65% physCrits = .0065f; physCrits += stats.CritRating / 2208f; physCrits += stats.Agility / 2500f; physCrits += stats.Crit; // Dodge: Base 6.5%, Minimum 0% float chanceDodged = .065f; //chanceDodged -= stats.ExpertiseRating / 1576f; chanceDodged -= stats.Expertise * .0025f; if (chanceDodged < 0f) { chanceDodged = 0f; } calcs.DodgedAttacks = chanceDodged; // Miss: Base 9%, Minimum 0% float chanceMiss = .09f; chanceMiss -= stats.HitRating / 1576f; chanceMiss -= stats.Hit; if (chanceMiss < 0f) { chanceMiss = 0f; } calcs.MissedAttacks = chanceMiss; // Spell Crit: Base 3.26% **TODO: include intellect spellCrits = .0326f; spellCrits += stats.SpellCritRating / 2208f; spellCrits += stats.SpellCrit; // Resists: Base 17%, Minimum 1% spellResist = .17f; spellResist -= stats.SpellHitRating / 1262f; spellResist -= stats.Hit; if (spellResist < .01f) { spellResist = .01f; } // Total physical misses totalMiss = chanceDodged + chanceMiss; } #endregion #region White Damage { float whiteAvgDam = 0f, dpsSSO = 0.0f; #region SSO Neck Procs if (stats.ShatteredSunMightProc > 0) { string shattrathFaction = calcOpts.ShattrathFaction; switch (shattrathFaction) { case "Aldor": stats.AttackPower += 39.13f; break; case "Scryer": dpsSSO = 350f * allDamMult * spellPower; dpsSSO *= 1f + physCrits * physCritMult - totalMiss; dpsSSO /= 50; // 50 seconds between procs break; } } #endregion // White damage per hit. Basic white hits are use elsewhere. whiteHit = baseDamage + (stats.AttackPower / 14.0f) * baseSpeed; whiteAvgDam = whiteHit * physDamMult; // Average white damage per swing whiteAvgDam *= (1f + physCrits * physCritMult - totalMiss - chanceToGlance * glancingAmount) * mitigation; // Total white DPS. Scryer SSO neck added as "white" dpsWhite = whiteAvgDam / hastedSpeed; dpsWhite += dpsSSO; } #endregion #region Seal { float sealActualPPM = 0f, sealAvgDam = 0f, windProcRate = .2f; if (calcOpts.Seal == 0) // Seal of Command { float socPPM = 7f, socCoeff = 0.2f, socHolyCoeff = 0.29f; // Find real PPM. Procs 7 times per minute before misses sealActualPPM = socPPM * (1f - totalMiss); // Chain Procs: Windfury procs Seal of Command if (stats.WindfuryAPBonus > 0) { // Chance SoC has proc'd on a swing float sealProcChance = socPPM / (60f / hastedSpeed); // Proc chain fails if Windfury misses windProcRate *= (1 - totalMiss); // SoC procs off of Windfury only if SoC has not already proc'd sealActualPPM *= 1 + windProcRate * (1 - sealProcChance); } // Seal Damage per hit sealAvgDam = 0.7f * whiteHit * twoHandedSpec + socCoeff * stats.SpellDamageRating; sealAvgDam = sealAvgDam * holyDamMult + socHolyCoeff * jotc; } else // Seal of Blood { // Find real PPM. Procs on every hit. sealActualPPM = (60f / hastedSpeed) * (1 - totalMiss); // Chain Procs: Windfury procs Seal of Blood if (stats.WindfuryAPBonus > 0) { sealActualPPM *= 1 + windProcRate * (1 - totalMiss); } // Seal Damage per hit sealAvgDam = 0.35f * whiteHit * holyDamMult * twoHandedSpec; } // Seal average damage per proc sealAvgDam *= (1f + physCrits * physCritMult - totalMiss) * partialResist; // Total Seal DPS dpsSeal = sealAvgDam * sealActualPPM / 60f; } #endregion #region Windfury if (stats.WindfuryAPBonus > 0) { float windProcRate = .2f, windPerMin = 0f, windAvgDam = 0f, windAPBonus = stats.WindfuryAPBonus; // Find real PPM. Chance to proc on every hit. and damage per hit windPerMin = (60f / hastedSpeed) * (1 - totalMiss) * windProcRate; // Chain Procs: Seal of Command can procs Windfury if (calcOpts.Seal == 0) { float socPPM = 7f; // Proc chain fails if either swing or SoC misses socPPM *= (1 - totalMiss) * (1 - totalMiss); // Windfury procs off of SoC only if Windfury has not already proc'd windPerMin += socPPM * windProcRate * (1 - windProcRate); } // Windfury damage per hit windAvgDam = whiteHit + (windAPBonus / 14) * baseSpeed; windAvgDam *= physDamMult; // Windfury average damage per proc windAvgDam *= (1f + physCrits * physCritMult - totalMiss) * mitigation; // Total Windfury DPS dpsWindfury = windAvgDam * windPerMin / 60f; } #endregion #region Crusader Strike { float crusCD = 6f, crusCoeff = 1.1f, crusAvgDam = 0f; // Crusader Strike damage per hit crusAvgDam = baseDamage + 3.3f * (stats.AttackPower / 14f); crusAvgDam *= crusCoeff * physDamMult * (1f + stats.BonusCrusaderStrikeDamageMultiplier); // Crusader Strike average damage per swing crusAvgDam *= (1f + physCrits * physCritMult - totalMiss) * mitigation; // Total Crusader Strike DPS dpsCrusader = crusAvgDam / crusCD; } #endregion #region Judgement { float judgeCD = 9.0f, judgeCoeff = 0.429f, judgeAvgDam = 0.0f; float judgeCrit = physCrits + (0.03f * (float)calcOpts.Fanaticism); if (calcOpts.Seal == 0) { judgeAvgDam = 240f; } else { judgeAvgDam = 310f; } judgeAvgDam = (judgeAvgDam + judgeCoeff * stats.SpellDamageRating) * holyDamMult + judgeCoeff * jotc; // Judgement average damage per cast. JoBlood does not get full resisted. if (calcOpts.Seal == 0) { judgeAvgDam *= (1f + judgeCrit * physCritMult - spellResist); } else { judgeAvgDam *= 1f + judgeCrit * physCritMult; } judgeAvgDam *= partialResist; // Total Judgement DPS dpsJudgement = judgeAvgDam / judgeCD; } #endregion #region Consecration if (calcOpts.ConsecRank != 0) { float consCD = 9f, consCoeff = 0.952f, consAvgDam = 0f; int consRank = calcOpts.ConsecRank; // Consecration damage pre-resists switch (consRank) // Rank damage + coeff * level reduction * spelldamage { case 1: consAvgDam = 64f + consCoeff * (35f / 70f) * stats.SpellDamageRating; break; case 2: consAvgDam = 120f + consCoeff * (45f / 70f) * stats.SpellDamageRating; break; case 3: consAvgDam = 184f + consCoeff * (55f / 70f) * stats.SpellDamageRating; break; case 4: consAvgDam = 280f + consCoeff * (65f / 70f) * stats.SpellDamageRating; break; case 5: consAvgDam = 384f + consCoeff * stats.SpellDamageRating; break; case 6: consAvgDam = 512f + consCoeff * stats.SpellDamageRating; break; } consAvgDam = consAvgDam * holyDamMult + consCoeff * jotc; // Consecration average damage post-resists. consAvgDam *= partialResist; // Total Consecration DPS dpsConsecration = consAvgDam / consCD; } #endregion #region Exorcism if (calcOpts.Exorcism) { float exorCD = 18f, exorCoeff = 0.429f, exorAvgDmg = 0f; // Exorcism damage per spell hit exorAvgDmg = 665f + exorCoeff * stats.SpellDamageRating; exorAvgDmg = exorAvgDmg * holyDamMult + exorCoeff * jotc; // Exorcism average damage per cast exorAvgDmg *= (1f + (spellCrits / 2f) * spellCritMult - spellResist) * partialResist; // Total Exorcism DPS dpsExorcism = exorAvgDmg / exorCD; } #endregion calcs.WeaponDamage = whiteHit * physDamMult; calcs.AttackSpeed = hastedSpeed; calcs.CritChance = physCrits; calcs.AvoidedAttacks = totalMiss; calcs.EnemyMitigation = 1 - mitigation; calcs.WhiteDPS = dpsWhite; calcs.SealDPS = dpsSeal; calcs.WindfuryDPS = dpsWindfury; calcs.CrusaderDPS = dpsCrusader; calcs.JudgementDPS = dpsJudgement; calcs.ConsecrationDPS = dpsConsecration; calcs.ExorcismDPS = dpsExorcism; calcs.DPSPoints = dpsWhite + dpsSeal + dpsWindfury + dpsCrusader + dpsJudgement + dpsConsecration + dpsExorcism; calcs.OverallPoints = calcs.DPSPoints; return(calcs); }
public GraphForm(Character character, bool bStats) { InitializeComponent(); if (!bStats) { Text = "Effective Cooldown Graph"; } // Create ARGB bitmap matching the size of the picturebox and associate with picturebox bmp = new Bitmap(pictureBoxGraph.Size.Width, pictureBoxGraph.Size.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); pictureBoxGraph.Image = bmp; // Greate & initialise GDI+ graphics object via bitmap gfx = Graphics.FromImage(bmp); gfx.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; gfx.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; #region Coordinates and scaling. /* * +-----------------------------------------------------+-rcBmp --- * | | |EndY * | ^ | --- * | | | * | | | * | | | * | | | * | | | * | | | * | | | * | | | * | | | * | | | * | | | * | | | * | | | * | +---------------------------------------------> | --- * | | | OrgY * +-----------------------------------------------------+ --- * * |----|OrgX |--|EndX * * * */ // Apply a translateion transformation. We'll move the 0,0 to the 0,0 of the chart we want to display // This will make most of the rest of the charting easier since all translation are then done by GDI+ // It also makes working with 'y' a bit easier as it's just flipped in sign rather than translated also. int OrgX = 50; // Leftmost edge of char int OrgY = 50; // Bottom edge of char int EndX = 15; // Margin right of chart int EndY = 15; // Margin top of chart gfx.TranslateTransform(OrgX, pictureBoxGraph.Size.Height - OrgY); // Bitmap coordinates translated back into transform Rectangle rcBmp = new Rectangle(0, 0, pictureBoxGraph.Size.Width, pictureBoxGraph.Size.Height); rcBmp.Offset(-OrgX, -(pictureBoxGraph.Size.Height - OrgY)); // rcChart is image rectangle, watch out, Y has positive value ! Rectangle rcChart = new Rectangle(0, 0, pictureBoxGraph.Size.Width - OrgX - EndX, pictureBoxGraph.Size.Height - OrgY - EndY); int YAxisValueX = -5; // Right alignment of values on the Y axis int XAxisValueY = +5; // Top margin of values on the X axis int YChartMargin = 5; #endregion #region Define and calculate charts ChartData[] aCharts; float DpsMax; float DpsMin; float DpsScaling; if (bStats) { aCharts = new ChartData[] { new ChartData(rcChart.Width, Color.FromArgb(255, 192, 0, 0), "1 Strength", new Stats() { Strength = 1 }), new ChartData(rcChart.Width, Color.FromArgb(255, 192, 192, 96), "1.167 Spell Power", new Stats() { SpellPower = 7 / 6 }), new ChartData(rcChart.Width, Color.FromArgb(255, 0, 0, 192), "1 Armor Pen.", new Stats() { ArmorPenetrationRating = 1 }), new ChartData(rcChart.Width, Color.FromArgb(255, 192, 0, 192), "1 Hit Rating", new Stats() { HitRating = 1 }), new ChartData(rcChart.Width, Color.FromArgb(255, 0, 192, 0), "1 Expertise Rating", new Stats() { ExpertiseRating = 1 }), new ChartData(rcChart.Width, Color.FromArgb(255, 192, 127, 96), "1 Agility", new Stats() { Agility = 1 }), new ChartData(rcChart.Width, Color.FromArgb(255, 192, 127, 0), "2 Attack Power", new Stats() { AttackPower = 2 }), new ChartData(rcChart.Width, Color.FromArgb(255, 96, 127, 192), "1 Crit Rating", new Stats() { CritRating = 1 }), new ChartData(rcChart.Width, Color.FromArgb(255, 0, 0, 0), "1 Haste Rating", new Stats() { HasteRating = 1 }), }; // Calculate charts CalculationsRetribution Calc = new CalculationsRetribution(); DpsMax = 0; DpsMin = 999999999; foreach (ChartData cd in aCharts) { for (int count = 0; count < rcChart.Width; count++) { Stats chartstats = cd.stats.Clone(); chartstats *= count - (rcChart.Width / 2); CharacterCalculationsRetribution chartCalc = Calc.GetCharacterCalculations(character, new Item() { Stats = chartstats }) as CharacterCalculationsRetribution; float Dps = chartCalc.DPSPoints; if (Dps > DpsMax) { DpsMax = Dps; } if (Dps < DpsMin) { DpsMin = Dps; } cd.dps[count] = Dps; } } DpsScaling = (rcChart.Height - 2 * YChartMargin /* some extra margin*/) / (DpsMax - DpsMin); } else { aCharts = new ChartData[] { new ChartData(rcChart.Width, Color.FromArgb(255, 192, 0, 0), "White", new Stats() { HasteRating = 1 }), new ChartData(rcChart.Width, Color.FromArgb(255, 192, 192, 96), "CS", new Stats() { HasteRating = 1 }), new ChartData(rcChart.Width, Color.FromArgb(255, 0, 0, 192), "J", new Stats() { HasteRating = 1 }), new ChartData(rcChart.Width, Color.FromArgb(255, 192, 0, 192), "DS", new Stats() { HasteRating = 1 }), new ChartData(rcChart.Width, Color.FromArgb(255, 0, 192, 0), "Cons", new Stats() { HasteRating = 1 }), new ChartData(rcChart.Width, Color.FromArgb(255, 192, 127, 96), "Exo", new Stats() { HasteRating = 1 }), //new ChartData(rcChart.Width, Color.FromArgb(255, 192, 127, 0), "HoW", new Stats() { HasteRating = 1 }), }; // Calculate charts CalculationsRetribution Calc = new CalculationsRetribution(); DpsMax = 0; DpsMin = 999999999; Stats stats = new Stats() { HasteRating = 1 }; for (int count = 0; count < rcChart.Width; count++) { Stats chartstats = stats.Clone(); chartstats *= count - (rcChart.Width / 2); CharacterCalculationsRetribution chartCalc = Calc.GetCharacterCalculations(character, new Item() { Stats = chartstats }) as CharacterCalculationsRetribution; float Ecd; Ecd = chartCalc.AttackSpeed; if (Ecd > DpsMax) { DpsMax = Ecd; } if (Ecd < DpsMin) { DpsMin = Ecd; } aCharts[0].dps[count] = Ecd; Ecd = 1 / chartCalc.Solution.GetAbilityUsagePerSecond(Ability.CrusaderStrike); if (Ecd > 50) // infinity fix { Ecd = 0; } if (Ecd > DpsMax) { DpsMax = Ecd; } if (Ecd < DpsMin) { DpsMin = Ecd; } aCharts[1].dps[count] = Ecd; Ecd = 1 / chartCalc.Solution.GetAbilityUsagePerSecond(Ability.Judgement); if (Ecd > 50) { Ecd = 0; } if (Ecd > DpsMax) { DpsMax = Ecd; } if (Ecd < DpsMin) { DpsMin = Ecd; } aCharts[2].dps[count] = Ecd; Ecd = 1 / chartCalc.Solution.GetAbilityUsagePerSecond(Ability.DivineStorm); if (Ecd > 50) { Ecd = 0; } if (Ecd > DpsMax) { DpsMax = Ecd; } if (Ecd < DpsMin) { DpsMin = Ecd; } aCharts[3].dps[count] = Ecd; Ecd = 1 / chartCalc.Solution.GetAbilityUsagePerSecond(Ability.Consecration); if (Ecd > 50) { Ecd = 0; } if (Ecd > DpsMax) { DpsMax = Ecd; } if (Ecd < DpsMin) { DpsMin = Ecd; } aCharts[4].dps[count] = Ecd; Ecd = 1 / chartCalc.Solution.GetAbilityUsagePerSecond(Ability.Exorcism); if (Ecd > 50) { Ecd = 0; } if (Ecd > DpsMax) { DpsMax = Ecd; } if (Ecd < DpsMin) { DpsMin = Ecd; } aCharts[5].dps[count] = Ecd; /*Ecd = 1 / chartCalc.Solution.GetAbilityUsagePerSecond(Ability.HammerOfWrath); * if (Ecd > 50) * Ecd = 0; * if (Ecd > DpsMax) * DpsMax = Ecd; * if (Ecd < DpsMin) * DpsMin = Ecd; * aCharts[6].dps[count] = Ecd; */ } DpsScaling = (rcChart.Height - 2 * YChartMargin /* some extra margin*/) / (DpsMax - DpsMin); } #endregion #region Fonts, Pens, Brushes and other helper variables Font fntValue = new Font("Arial", 8); Font fntTitle = new Font("Arial", 16); Pen penBlack = new Pen(Color.FromArgb(255, 0, 0, 0)); Pen penGray50 = new Pen(Color.FromArgb(255, 127, 127, 127)); Pen penGray75 = new Pen(Color.FromArgb(255, 196, 196, 196)); Pen penGray90 = new Pen(Color.FromArgb(255, 230, 230, 230)); Pen penRed = new Pen(Color.FromArgb(255, 255, 0, 0)); Brush brBlack = new SolidBrush(Color.FromArgb(255, 0, 0, 0)); Brush brWhite = new SolidBrush(Color.FromArgb(255, 255, 255, 255)); Brush brRed = new SolidBrush(Color.FromArgb(255, 255, 0, 0)); StringFormat fmtRAlignCenter = new StringFormat(); fmtRAlignCenter.Alignment = StringAlignment.Far; fmtRAlignCenter.LineAlignment = StringAlignment.Center; StringFormat fmtRAlignBottom = new StringFormat(); fmtRAlignBottom.Alignment = StringAlignment.Far; fmtRAlignBottom.LineAlignment = StringAlignment.Far; StringFormat fmtLAlignTop = new StringFormat(); fmtLAlignTop.Alignment = StringAlignment.Near; fmtLAlignTop.LineAlignment = StringAlignment.Near; StringFormat fmtCAlignTop = new StringFormat(); fmtCAlignTop.Alignment = StringAlignment.Center; fmtCAlignTop.LineAlignment = StringAlignment.Near; #endregion // Clear bitmap to all white. gfx.FillRectangle(brWhite, rcBmp); #region Draw Axis and titles // Stats ticks (draw this first, so other axis information is drawn on top) const int Steps = 10; // Chart is per 1 stat, typically, 20 stats go in a iLevel. for (int i = 1; i < (rcChart.Width / 2) / Steps; i++) { Pen pen; if (i % 5 == 0) { pen = penGray75; gfx.DrawString((-i * 10).ToString(), fntValue, brBlack, rcChart.Width / 2 - i * 10, XAxisValueY, fmtCAlignTop); gfx.DrawString((i * 10).ToString(), fntValue, brBlack, rcChart.Width / 2 + i * 10, XAxisValueY, fmtCAlignTop); } else { pen = penGray90; } gfx.DrawLine(pen, rcChart.Width / 2 - i * 10, 0, rcChart.Width / 2 - i * 10, -rcChart.Height); gfx.DrawLine(pen, rcChart.Width / 2 + i * 10, 0, rcChart.Width / 2 + i * 10, -rcChart.Height); } // DPS ticks (Draw this first, so other axis information is drawn on top) const int MaxDpsLines = 25; float DpsDelta = DpsMax - DpsMin; int[] Ranges = { 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 25000, 50000, }; int Range = 1; foreach (int r in Ranges) { if (MaxDpsLines * r > DpsDelta) { Range = r; break; } } int Y = (int)(Math.Floor(DpsMin / Range) * Range); do { float Y2 = (Y - DpsMin) * DpsScaling; if (Y2 > 0) { gfx.DrawLine(penGray75, 0, -Y2, rcChart.Width - 5, -Y2); gfx.DrawString(Y.ToString(), fntValue, brBlack, YAxisValueX, -Y2, fmtRAlignCenter); } Y += Range; }while (Y < DpsMax); // Y Axis gfx.DrawLine(penBlack, 0, 0, 0, -rcChart.Height); gfx.DrawString(bStats ? "DPS" : "ECD", fntTitle, brBlack, 5, -(rcChart.Height - 5), fmtLAlignTop); // X-axis gfx.DrawLine(penBlack, 0, 0, rcChart.Width, 0); gfx.DrawString(bStats ? "Stats" : "Haste Rating", fntTitle, brBlack, rcChart.Width - 5, -5, fmtRAlignBottom); // Center Y Axis. gfx.DrawLine(penBlack, rcChart.Width / 2, 0, rcChart.Width / 2, -rcChart.Height); gfx.DrawString("0", fntValue, brBlack, rcChart.Width / 2, XAxisValueY, fmtCAlignTop); // "Experimental" text gfx.DrawString("Experimental", fntTitle, brRed, rcChart.Width - 5, OrgY, fmtRAlignBottom); #endregion // Draw charts int l = 0; foreach (ChartData cd in aCharts) { for (int count = 0; count < rcChart.Width - 1; count++) { gfx.DrawLine(cd.pen, count, -(cd.dps[count] - DpsMin) * DpsScaling - YChartMargin, count + 1, -(cd.dps[count + 1] - DpsMin) * DpsScaling - YChartMargin); } gfx.DrawString(cd.name, fntValue, cd.brush, 5, -(rcChart.Height - fntTitle.GetHeight() - 5 - l * fntValue.GetHeight())); l++; } Invalidate(); Update(); }
private void btnGraph_Click(object sender, EventArgs e) { CalculationsRetribution retCalc = new CalculationsRetribution(); CharacterCalculationsRetribution baseCalc = retCalc.GetCharacterCalculations(Character) as CharacterCalculationsRetribution; Bitmap _prerenderedGraph = global::Rawr.Retribution.Properties.Resources.GraphBase; Graphics g = Graphics.FromImage(_prerenderedGraph); g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; float graphHeight = 700f, graphStart = 100f; Color[] colors = new Color[] { Color.FromArgb(127, 202, 180, 96), // Strength Color.FromArgb(127, 101, 225, 240), // Agility Color.FromArgb(127, 0, 4, 3), // Attack Power Color.FromArgb(127, 123, 238, 199), // Crit Rating Color.FromArgb(127, 45, 112, 63), // Hit Rating Color.FromArgb(127, 121, 72, 210), //Expertise Rating Color.FromArgb(127, 217, 100, 54), // Haste Rating Color.FromArgb(127, 210, 72, 195), // Armor Penetration Color.FromArgb(127, 206, 189, 191), // Spell Damage }; Stats[] statsList = new Stats[] { new Stats() { Strength = 10 }, new Stats() { Agility = 10 }, new Stats() { AttackPower = 20 }, new Stats() { CritRating = 10 }, new Stats() { HitRating = 10 }, new Stats() { ExpertiseRating = 10 }, new Stats() { HasteRating = 10 }, new Stats() { ArmorPenetration = 66.667f }, new Stats() { SpellDamageRating = 11.17f }, }; for (int index = 0; index < statsList.Length; index++) { Stats newStats = new Stats(); Point[] points = new Point[100]; for (int count = 0; count < 100; count++) { newStats = newStats + statsList[index]; CharacterCalculationsRetribution currentCalc = retCalc.GetCharacterCalculations(Character, new Item() { Stats = newStats }) as CharacterCalculationsRetribution; float overallPoints = currentCalc.DPSPoints - baseCalc.DPSPoints; if ((graphHeight - overallPoints) > 16) { points[count] = new Point(Convert.ToInt32(graphStart + count * 5), (Convert.ToInt32(graphHeight - overallPoints))); } else { points[count] = points[count - 1]; } } Brush statBrush = new SolidBrush(colors[index]); g.DrawLines(new Pen(statBrush, 3), points); } #region Graph Ticks float graphWidth = 500f;// this.Width - 150f; float graphEnd = graphStart + graphWidth; //float graphStartY = 16f; float maxScale = 100f; float[] ticks = new float[] { (float)Math.Round(graphStart + graphWidth * 0.5f), (float)Math.Round(graphStart + graphWidth * 0.75f), (float)Math.Round(graphStart + graphWidth * 0.25f), (float)Math.Round(graphStart + graphWidth * 0.125f), (float)Math.Round(graphStart + graphWidth * 0.375f), (float)Math.Round(graphStart + graphWidth * 0.625f), (float)Math.Round(graphStart + graphWidth * 0.875f) }; Pen black200 = new Pen(Color.FromArgb(200, 0, 0, 0)); Pen black150 = new Pen(Color.FromArgb(150, 0, 0, 0)); Pen black75 = new Pen(Color.FromArgb(75, 0, 0, 0)); Pen black50 = new Pen(Color.FromArgb(50, 0, 0, 0)); Pen black25 = new Pen(Color.FromArgb(25, 0, 0, 0)); StringFormat formatTick = new StringFormat(); formatTick.LineAlignment = StringAlignment.Far; formatTick.Alignment = StringAlignment.Center; Brush black200brush = new SolidBrush(Color.FromArgb(200, 0, 0, 0)); Brush black150brush = new SolidBrush(Color.FromArgb(150, 0, 0, 0)); Brush black75brush = new SolidBrush(Color.FromArgb(75, 0, 0, 0)); Brush black50brush = new SolidBrush(Color.FromArgb(50, 0, 0, 0)); Brush black25brush = new SolidBrush(Color.FromArgb(25, 0, 0, 0)); g.DrawLine(black200, graphStart - 4, 20, graphEnd + 4, 20); g.DrawLine(black200, graphStart, 16, graphStart, _prerenderedGraph.Height - 16); g.DrawLine(black200, graphEnd, 16, graphEnd, 19); g.DrawLine(black200, ticks[0], 16, ticks[0], 19); g.DrawLine(black150, ticks[1], 16, ticks[1], 19); g.DrawLine(black150, ticks[2], 16, ticks[2], 19); g.DrawLine(black75, ticks[3], 16, ticks[3], 19); g.DrawLine(black75, ticks[4], 16, ticks[4], 19); g.DrawLine(black75, ticks[5], 16, ticks[5], 19); g.DrawLine(black75, ticks[6], 16, ticks[6], 19); g.DrawLine(black75, graphEnd, 21, graphEnd, _prerenderedGraph.Height - 4); g.DrawLine(black75, ticks[0], 21, ticks[0], _prerenderedGraph.Height - 4); g.DrawLine(black50, ticks[1], 21, ticks[1], _prerenderedGraph.Height - 4); g.DrawLine(black50, ticks[2], 21, ticks[2], _prerenderedGraph.Height - 4); g.DrawLine(black25, ticks[3], 21, ticks[3], _prerenderedGraph.Height - 4); g.DrawLine(black25, ticks[4], 21, ticks[4], _prerenderedGraph.Height - 4); g.DrawLine(black25, ticks[5], 21, ticks[5], _prerenderedGraph.Height - 4); g.DrawLine(black25, ticks[6], 21, ticks[6], _prerenderedGraph.Height - 4); g.DrawLine(black200, graphStart - 4, _prerenderedGraph.Height - 20, graphEnd + 4, _prerenderedGraph.Height - 20); Font tickFont = new Font("Calibri", 11); g.DrawString((0f).ToString(), tickFont, black200brush, graphStart, 16, formatTick); g.DrawString((maxScale).ToString(), tickFont, black200brush, graphEnd, 16, formatTick); g.DrawString((maxScale * 0.5f).ToString(), tickFont, black200brush, ticks[0], 16, formatTick); g.DrawString((maxScale * 0.75f).ToString(), tickFont, black150brush, ticks[1], 16, formatTick); g.DrawString((maxScale * 0.25f).ToString(), tickFont, black150brush, ticks[2], 16, formatTick); g.DrawString((maxScale * 0.125f).ToString(), tickFont, black75brush, ticks[3], 16, formatTick); g.DrawString((maxScale * 0.375f).ToString(), tickFont, black75brush, ticks[4], 16, formatTick); g.DrawString((maxScale * 0.625f).ToString(), tickFont, black75brush, ticks[5], 16, formatTick); g.DrawString((maxScale * 0.875f).ToString(), tickFont, black75brush, ticks[6], 16, formatTick); g.DrawString((0f).ToString(), tickFont, black200brush, graphStart, _prerenderedGraph.Height - 16, formatTick); g.DrawString((maxScale).ToString(), tickFont, black200brush, graphEnd, _prerenderedGraph.Height - 16, formatTick); g.DrawString((maxScale * 0.5f).ToString(), tickFont, black200brush, ticks[0], _prerenderedGraph.Height - 16, formatTick); g.DrawString((maxScale * 0.75f).ToString(), tickFont, black150brush, ticks[1], _prerenderedGraph.Height - 16, formatTick); g.DrawString((maxScale * 0.25f).ToString(), tickFont, black150brush, ticks[2], _prerenderedGraph.Height - 16, formatTick); g.DrawString((maxScale * 0.125f).ToString(), tickFont, black75brush, ticks[3], _prerenderedGraph.Height - 16, formatTick); g.DrawString((maxScale * 0.375f).ToString(), tickFont, black75brush, ticks[4], _prerenderedGraph.Height - 16, formatTick); g.DrawString((maxScale * 0.625f).ToString(), tickFont, black75brush, ticks[5], _prerenderedGraph.Height - 16, formatTick); g.DrawString((maxScale * 0.875f).ToString(), tickFont, black75brush, ticks[6], _prerenderedGraph.Height - 16, formatTick); #endregion Graph graph = new Graph(_prerenderedGraph); graph.Show(); }