public float CalculateFightHealing(CharacterCalculationsHealadin calc) { #region Copying Stuff to Calc calc.FightLength = FightLength; calc.HL = hl; calc.FoL = fol; calc.HS = hs; calc.DL = dl; calc.WoG = wog; calc.LoD = lod; calc.HR = hr; calc.JotP = jotp; calc.BoL = bol; calc.LoH = loh; calc.PotI = poti; calc.EJ = ej; calc.JudgeCasts = jotp.Casts(); calc.RotationJudge = jotp.Time() + CalcOpts.Userdelay * calc.JudgeCasts; calc.UsageJudge = jotp.Usage(); if (Talents.BeaconOfLight > 0) { calc.RotationBoL = bol.Time(); calc.UsageBoL = bol.Usage(); } calc.RotationHS = hs.Time() + hs.Casts() + CalcOpts.Userdelay; calc.HealedHS = hs.Healed(); calc.UsageHS = hs.Usage(); calc.RotationLoH = loh.Casts() * (loh.CastTime() + CalcOpts.Userdelay); calc.HealedLoH = loh.Casts() * loh.AverageHealed(); calc.UsageLoH = 0f; // always 0, costs no mana, code was: loh.Casts() * loh.BaseMana; calc.UsageCleanse = CalcOpts.Cleanse * cleanse.BaseMana; calc.RotationCleanse = CalcOpts.Cleanse * (cleanse.CastTime() + CalcOpts.Userdelay); calc.CleanseCasts = CalcOpts.Cleanse; #endregion Stats.BonusCritChanceObliterate = CalcOpts.CritOverheals; // I really need to put some variables in Stats that are for holy pally calc.HasteJotP = Talents.JudgementsOfThePure * 3f; calc.HasteSoL = Talents.SpeedOfLight * 1f; calc.SpellPowerTotal = Stats.Intellect + Stats.SpellPower; #region Divine Favor - old code, commented out for now /* if (Talents.DivineFavor > 0) * { * DivineFavor df = new DivineFavor(this); * calc.RotationHL += df.Time(); * calc.UsageHL += df.Usage(); * calc.HealedHL += df.Healed(); * } */ #endregion #region active time, remaining mana / time float remainingMana = calc.TotalMana = ManaPool(calc); remainingMana -= calc.UsageJudge + calc.UsageBoL + calc.UsageHS + calc.UsageHR + calc.UsageCleanse; // start with amount of time you are active in a fight calc.ActiveTime = FightLength * CalcOpts.Activity; float remainingTime = calc.ActiveTime; // now subtract time for lots of stuff we need to cast remainingTime -= calc.RotationJudge + calc.RotationBoL + calc.RotationHS + calc.RotationLoH + calc.RotationHR + calc.RotationCleanse; float RotationDP = DivinePleas * (calc.HS.CastTime() + CalcOpts.Userdelay); remainingTime -= RotationDP; // subtract divine plea cast times. HS is also a GCD, so I just used that to calculate it #endregion #region HS and holy power calc.HolyPowerCasts = hs.Casts() / 3f; if (Talents.LightOfDawn != 0) { calc.LoDCasts = (float)Math.Floor(calc.HolyPowerCasts * CalcOpts.HolyPoints); calc.WoGCasts = (float)Math.Floor(calc.HolyPowerCasts * (1f - CalcOpts.HolyPoints)); } else { calc.WoGCasts = (float)Math.Floor(calc.HolyPowerCasts); calc.LoDCasts = 0; } calc.RotationWoG = calc.WoGCasts * (wog.CastTime() + CalcOpts.Userdelay); calc.UsageWoG = calc.WoGCasts * wog.BaseMana; calc.HealedWoG = calc.WoGCasts * wog.AverageHealed(); calc.RotationLoD = calc.LoDCasts * (lod.CastTime() + CalcOpts.Userdelay); calc.UsageLoD = calc.LoDCasts * lod.BaseMana; calc.HealedLoD = calc.LoDCasts * lod.AverageHealed() * CalcOpts.LoDTargets; remainingTime -= calc.RotationWoG + calc.RotationLoD; #endregion #region Holy Radiance calc.HRCasts = (float)Math.Floor(FightLength / CalcOpts.HRCasts); calc.RotationHR = calc.HRCasts * (hr.CastTime() + CalcOpts.Userdelay); calc.UsageHR = calc.HRCasts * hr.BaseMana; calc.HealedHR = calc.HRCasts * hr.AverageHealed() * CalcOpts.HREff; remainingTime -= calc.RotationHR; #endregion #region Melee mana regen // Melee hits generate 4% base mana. From what I read, this proc has a 4 sec CD. 15ppm max. float InstantCastRotationTotal = calc.RotationLoD + calc.RotationWoG + calc.RotationHS + calc.RotationHR + calc.RotationJudge + calc.RotationBoL + calc.RotationLoH + calc.RotationCleanse; float MeleeTime = InstantCastRotationTotal * CalcOpts.Melee; // Ideally the SwingTime would be the weapon speed, then logic would be added to estimate the # of procs. As the # of swings increases, // the number of procs would have dimishing returns. (because more frequent swings means more swings happen during the 4 sec CD, and don't proc) // For now I am going to fudge it and estimate a proc every 5 secs. Every 4 is unrealistic since 4 is not divisable by weapon speed, // so some time is always wasted. Also consider when one alternates between swings and casts, then each swing would proc. Whatever, 5 is good enough for now. float SwingTime = 5f; float MeleeManaPerProc = HealadinConstants.basemana * 0.04f; // calc.MeleeSwings = MeleeTime / SwingTime; calc.MeleeProcs = MeleeTime / SwingTime; // find a way to model this better, as swings approaches MaxMeleeProcs, it should have diminishing returns somehow calc.ManaMelee = calc.MeleeProcs * MeleeManaPerProc; remainingMana += calc.ManaMelee; calc.TotalMana += calc.ManaMelee; #endregion Melee mana regen #region Filler casts // now that we did everything from the options panel, fill in the rest of the available cast time // we will use Holy Light to fill in the time. If we still have mana left, we will replace Holy Light casts with Divine Light, until we run out of mana. // Note, when I kept the filler casts to whole numbers, Haste was not valued. Trying the other way to see how haste is valued then. // DL and HL have the same casttime, so lets just figure max number of casts with time remaining float IoLprocs = hs.Casts() * Talents.InfusionOfLight * hs.ChanceToCrit(); // Infusion of Light procs - -0.75 sec to next HL or DL casttime per point remainingTime += IoLprocs * 0.75f; // assuming we will be casting at least a few HL or DL, and can cast them when IoL procs, before next HS cast float fill_casts = /*(float)Math.Floor*/ (remainingTime / (hl.CastTime() + CalcOpts.Userdelay)); calc.HLCasts = 0; // Holy Light calc.DLCasts = 0; // Divine Light calc.FoLCasts = 0; float mana_fill_hl = fill_casts * hl.BaseMana; float mana_fill_dl = fill_casts * dl.BaseMana; if (remainingMana < mana_fill_hl) // if you would run out of mana just casting Holy Light { // then figure out how many Holy Lights you can cast // calc.HLCasts = /*(float)Math.Floor*/(remainingMana / hl.BaseMana); float timeleft = calc.HLCasts * (hl.CastTime() + +CalcOpts.Userdelay); float MeleeMPS = calc.MeleeProcs / MeleeManaPerProc; calc.HLCasts = ((remainingMana / (hl.MPS() - MeleeMPS) / hl.CastTime())); // use time to spare to melee for more mana, for more HL casts remainingTime -= calc.HLCasts * (hl.CastTime() + +CalcOpts.Userdelay); float moremeleemana = remainingTime / SwingTime * MeleeManaPerProc; calc.RotationMelee = remainingTime; calc.MeleeProcs += remainingTime / SwingTime; calc.ManaMelee += moremeleemana; calc.TotalMana += moremeleemana; } else if (remainingMana < mana_fill_dl) // else if you would run out of mana just casting Divine Light { remainingMana -= fill_casts * hl.BaseMana; // how much mana do we have to spare if we casts all Holy Lights calc.DLCasts = /*(float)Math.Floor*/ (remainingMana / (dl.BaseMana - hl.BaseMana)); calc.HLCasts = fill_casts - calc.DLCasts; } else // my God, you have a crapton of mana, start casting Flash of Light { calc.DLCasts = (IoLprocs * 0.75f) / dl.CastTime(); remainingTime -= IoLprocs * 0.75f; remainingMana -= calc.DLCasts * dl.BaseMana; remainingMana -= fill_casts * dl.BaseMana; // how much mana do we have to spare if we casts all Divine Lights calc.FoLCasts = /*(float)Math.Floor*/ ((remainingMana / (fol.MPS() - dl.MPS()) / fol.CastTime())); if (calc.FoLCasts > (remainingTime / (fol.CastTime() + CalcOpts.Userdelay))) { calc.FoLCasts = remainingTime / (fol.CastTime() + CalcOpts.Userdelay); } remainingTime -= calc.FoLCasts * (fol.CastTime() + CalcOpts.Userdelay); calc.DLCasts += /*(float)Math.Floor*/ (remainingTime / (dl.CastTime() + CalcOpts.Userdelay)); } calc.RotationHL = calc.HLCasts * (hl.CastTime() + CalcOpts.Userdelay); calc.UsageHL = calc.HLCasts * hl.BaseMana; calc.HealedHL = calc.HLCasts * hl.AverageHealed(); calc.RotationDL = calc.DLCasts * (dl.CastTime() + CalcOpts.Userdelay); calc.UsageDL = calc.DLCasts * dl.BaseMana; calc.HealedDL = calc.DLCasts * dl.AverageHealed(); calc.RotationFoL = calc.FoLCasts * (fol.CastTime() + CalcOpts.Userdelay); calc.UsageFoL = calc.FoLCasts * fol.BaseMana; calc.HealedFoL = calc.FoLCasts * fol.AverageHealed(); if (calc.RotationHL > (IoLprocs * 0.75f)) { calc.RotationHL -= IoLprocs * 0.75f; } else if (calc.RotationDL > (IoLprocs * 0.75f)) { calc.RotationDL -= IoLprocs * 0.75f; } #endregion Filler Casts #region Conviction // does enlightened judgements crit seperately from judgement? does it count towards conviction proc? // does Protector of the Innocent crit seperately? does it count towards conviction proc? // I'm assuming yes for all these questions for now. // Frankly this was my first pass at modeling Conviction. It's far from perfect, but the results are // in the same ballpark as combatlogs, and should be somewhat accurate in this talent's effect on // the value of crit. float ConvictionUptime; // time having 3 stacks up float ConvictionCasts = 2f * (calc.HLCasts + calc.DLCasts + calc.FoLCasts + calc.WoGCasts + calc.JudgeCasts + calc.HSCasts) + calc.LoHCasts + calc.LoDCasts * CalcOpts.LoDTargets * (5f + (Talents.GlyphOfLightOfDawn ? 1f : 0f)) + calc.MeleeSwings; float CastsPerSec = ConvictionCasts / ActiveTime; // Average crit chance is for all spells. Start with spellcrit, then add extra for each spell. float AverageCritChance = Stats.SpellCrit + (hs.Casts() / ConvictionCasts * (hs.ChanceToCrit() - Stats.SpellCrit)) + (calc.HLCasts / ConvictionCasts * (hl.ChanceToCrit() - Stats.SpellCrit)); float ChancePerCastToDrop = (float)Math.Pow((1 - AverageCritChance), (CastsPerSec * 15)); float FullStackTime = // average time we keep 3 stacks once we have it // might not be the best way, but for now using the time to get to 60% chance the stack has dropped // considering the tail 40% can take very long to drop potentially, pulling the median out past 50% // combat logs show this is the correct ballpark 1f / CastsPerSec * (float)Math.Log(0.4f, (1f - ChancePerCastToDrop)); float AverageStackBuildTime = // average time to build from 0 to 3 stacks 1f / CastsPerSec * (3 / AverageCritChance); // time to get 3 stacks, assuming you dont drop the stack along the way // now should add time for if stacks drop along the way float StackBuilds = // number of times we will build the stack in the fight 1 + (ActiveTime - AverageStackBuildTime) / (AverageStackBuildTime + FullStackTime); ConvictionUptime = StackBuilds * AverageStackBuildTime / 3f + // lets count stack builds as having 1 stack on average for now (StackBuilds - 1f) * FullStackTime; float ConvictionBuff = 1f + ConvictionUptime / ActiveTime * 0.03f * Talents.Conviction; calc.HealedHL *= ConvictionBuff; calc.HealedDL *= ConvictionBuff; calc.HealedFoL *= ConvictionBuff; calc.HealedHS *= ConvictionBuff; calc.HealedWoG *= ConvictionBuff; calc.HealedLoD *= ConvictionBuff; calc.HealedLoH *= ConvictionBuff; #endregion Conviction #region Talent heals: Enlightened Judgement, Protector of the Innocent, Illuminated healing, Beacon // Enlightened Judgement talent heals calc.HealedJudge = calc.JudgeCasts * ej.AverageHealed() * ConvictionBuff; // Protector of the Innocent calc.PotICasts = fill_casts + calc.WoGCasts + calc.LoDCasts + calc.LoHCasts + calc.HSCasts; calc.HealedPotI = calc.PotICasts * poti.AverageHealed() * ConvictionBuff; calc.UsageTotal = calc.UsageFoL + calc.UsageDL + calc.UsageHL + calc.UsageLoD + calc.UsageWoG + calc.UsageHS + calc.UsageHR + calc.UsageJudge + calc.UsageBoL + calc.UsageCleanse; calc.RotationTotal = calc.RotationFoL + calc.RotationDL + calc.RotationHL + calc.RotationLoD + calc.RotationWoG + calc.RotationHS + calc.RotationHR + calc.RotationJudge + calc.RotationBoL + calc.RotationLoH + calc.RotationCleanse + calc.RotationMelee + RotationDP; // Illumnated Healing calc.HealedIH = calc.HealedFoL + calc.HealedHL + calc.HealedHS + calc.HealedWoG + calc.HealedLoD + calc.HealedDL; calc.TotalHealed = calc.HealedIH + calc.HealedPotI + calc.HealedJudge; // BoL doesn't use LoH, but Illuminated healing does - so add LoH to IH and do final IH calcs calc.HealedIH += calc.HealedLoH; calc.HealedIH *= CalcOpts.IHEff * (0.12f + ((8 + Stats.MasteryRating / 179.28f) * 0.015f)); // Beacon of Light calc.HealedBoL = bol.HealingDone(calc.TotalHealed + calc.HealedHL); // added HL heals again to reflect 4.2 change, where HL transferes 100% to beacon target, rather than 50% for everything else #endregion // adding Holy Radiance and LoH after BoL calculation, as they do not count towards BoL heals calc.TotalHealed += calc.HealedHR + calc.HealedLoH + calc.HealedIH + calc.HealedBoL; calc.HealedOther = Stats.Healed + Stats.HealedPerSP * calc.SpellPowerTotal; calc.HealedOther += calc.TotalHealed * Stats.ShieldFromHealedProc; calc.TotalHealed += calc.HealedOther; calc.AvgHPS = calc.TotalHealed / FightLength; calc.AvgHPM = calc.TotalHealed / calc.TotalMana; return(calc.AvgHPS * (1f - CalcOpts.BurstScale)); }
public float CalculateFightHealing(CharacterCalculationsHealadin calc) { float fol_BaseHealed = 0f; #region Copying Stuff to Calc calc.FightLength = FightLength; calc.HL = hl; calc.FoL = fol; calc.HS = hs; calc.SS = ss; calc.JotP = jotp; calc.BoL = bol; calc.RotationJotP = jotp.Time(); calc.UsageJotP = jotp.Usage(); calc.RotationSS = ss.Time(); calc.UsageSS = ss.Usage(); calc.HealedSS = ss.TotalAborb(); calc.RotationBoL = bol.Time(); calc.UsageBoL = bol.Usage(); calc.RotationHS = hs.Time(); calc.HealedHS = hs.Healed(); calc.UsageHS = hs.Usage(); #endregion #region Divine Illumination if (Talents.DivineIllumination > 0) { DivineIllumination di = new DivineIllumination(this); calc.RotationHL += di.Time(); calc.UsageHL += di.Usage(); calc.HealedHL += di.Healed(); } #endregion #region Divine Favor if (Talents.DivineFavor > 0) { DivineFavor df = new DivineFavor(this); calc.RotationHL += df.Time(); calc.UsageHL += df.Usage(); calc.HealedHL += df.Healed(); } #endregion #region Infusion of Light float iol_hlcasts = 0; float iol_folcasts = 0; if (CalcOpts.InfusionOfLight) { float iol_count = hs.Casts() * hs.ChanceToCrit(); HolyLight hl_iol = new HolyLight(this) { ExtraCritChance = .1f * Talents.InfusionOfLight }; if (Stats.HolyLightCastTimeReductionFromHolyShock > 0) { hl_iol.CastTimeReductionFromHolyShock = true; } iol_hlcasts = hs.Casts() * CalcOpts.IoLHolyLight * hs.ChanceToCrit(); calc.UsageHL += iol_hlcasts * hl_iol.AverageCost(); calc.RotationHL += iol_hlcasts * hl_iol.CastTime(); calc.HealedHL += iol_hlcasts * hl_iol.AverageHealed(); iol_folcasts = hs.Casts() * (1f - CalcOpts.IoLHolyLight) * hs.ChanceToCrit(); } #endregion float remainingMana = calc.TotalMana = ManaPool(calc); remainingMana -= calc.UsageJotP + calc.UsageBoL + calc.UsageHS + calc.UsageHL + calc.UsageFoL + calc.UsageSS; float remainingTime = FightLength * CalcOpts.Activity; remainingTime -= calc.RotationJotP + calc.RotationBoL + calc.RotationSS + calc.RotationHS + calc.RotationFoL + calc.RotationHL; FoLCasts = 0f; if (remainingMana > 0) { if (Stats.HolyLightCastTimeReductionFromHolyShock > 0) // Calculations for Holy Light with cost reduction from Holy Shock { // Get the Holy Light model for cast time reduction from Holy Shock HolyLight hl_hs = new HolyLight(this) { CastTimeReductionFromHolyShock = true }; // Calculate how much time we have available to cast Holy Lights after Holy Shock float hl_hs_time_available = Math.Min(remainingTime, Math.Max(0, (remainingMana - (remainingTime * fol.MPS())) / (hl_hs.MPS() - fol.MPS()))); // Calculate the maximum number of casts available with the cast time reduction float hs_hlcasts = hs.Casts() - iol_hlcasts; // Calculate the amount of time needed for all of the casts float hl_hs_time_needed = hs_hlcasts * hl_hs.CastTime(); float manaCost = 0f; float timeCost = 0f; if (hl_hs_time_available > 0 && hl_hs_time_available < hl_hs_time_needed) // We have enough time to cast the Holy Lights needed, cast them all { // Calculate mana and time cost manaCost = hs_hlcasts * hl_hs.AverageCost(); timeCost = hs_hlcasts * hl_hs.CastTime(); // Update Holy Light stats calc.UsageHL += manaCost; calc.RotationHL += timeCost; calc.HealedHL += hs_hlcasts * hl_hs.AverageHealed(); } else if (hl_hs_time_available >= remainingTime) // There's not enough time to cast the Holy Lights needed, so we'll only cast what we can { // Calculate mana and time cost float remainingCasts = remainingTime / hl_hs.CastTime(); manaCost = remainingCasts * hl_hs.AverageCost(); timeCost = remainingCasts * hl_hs.CastTime(); // Update Holy Light stats calc.UsageHL += manaCost; calc.RotationHL += timeCost; calc.HealedHL += remainingCasts * hl_hs.AverageHealed(); } // Update remaining mana and remaining time remainingMana -= manaCost; remainingTime -= timeCost; } // Calculate how much time we have available to cast regular Holy Lights float hl_time_available = Math.Min(remainingTime, Math.Max(0, (remainingMana - (remainingTime * fol.MPS())) / (hl.MPS() - fol.MPS()))); // The rest of the time will be for Flash of Light float fol_time = remainingTime - hl_time_available; if (hl_time_available == 0) // If we didn't have any time available for Holy Lights, check to see when we run out of mana while casting Flash of Light { fol_time = Math.Min(remainingTime, remainingMana / fol.MPS()); } else // If we do have time available for Holy Lights, update the Holy Light stats { calc.HealedHL += hl.HPS() * hl_time_available; calc.UsageHL += hl.MPS() * hl_time_available; calc.RotationHL += hl_time_available; } // Calculate Flash of Light data if (iol_folcasts > 0) // We are using Infusion of Light, so we must calculate it differently than normal { // Get the Flash of Light model for Infusion of Light FlashOfLight fol_iol = new FlashOfLight(this) { InfusionOfLight = true }; // If there is less time remaining than it would take to cast all of the // Infusion of Light procs, we are forced to drop some of them. Note // that this should *never* happen, but is here for a sanity check. if (fol_time <= iol_folcasts * fol_iol.CastTime()) { // Determine how many total casts we are going to be able to do FoLCasts = fol_time / fol_iol.CastTime(); // Adding Flash of Light with Infusion of Light stats calc.UsageFoL += FoLCasts * fol_iol.AverageCost(); calc.RotationFoL += FoLCasts * fol_iol.CastTime(); calc.HealedFoL += FoLCasts * fol_iol.AverageHealed(); fol_BaseHealed += FoLCasts * fol_iol.BaseAverageHealed(); } else { // Adding Flash of Light with Infusion of Light calc.UsageFoL += iol_folcasts * fol_iol.AverageCost(); calc.RotationFoL += iol_folcasts * fol_iol.CastTime(); calc.HealedFoL += iol_folcasts * fol_iol.AverageHealed(); fol_BaseHealed += iol_folcasts * fol_iol.BaseAverageHealed(); // Determine how much time we have left to cast Flash of Light without the procs fol_time = fol_time - iol_folcasts * fol.CastTime(); // Determine how many total casts, with and without Infusion of Light float remaining_folcasts = fol_time / fol.CastTime(); FoLCasts = iol_folcasts + remaining_folcasts; // Adding Flash of Light stats calc.RotationFoL += fol_time; calc.UsageFoL += fol.MPS() * fol_time; calc.HealedFoL += fol.HPS() * fol_time; fol_BaseHealed += remaining_folcasts * fol.BaseAverageHealed(); } } else // We don't use Infusion of Light, so just calculate normal data for Flash of Light { // Determine how many total casts, with and without Infusion of Light FoLCasts = fol_time / fol.CastTime(); // Adding Flash of Light stats calc.RotationFoL += fol_time; calc.UsageFoL += fol.MPS() * fol_time; calc.HealedFoL += fol.HPS() * fol_time; fol_BaseHealed += FoLCasts * fol.BaseAverageHealed(); } } calc.TotalHealed = calc.HealedFoL + calc.HealedHL + calc.HealedHS; if (Talents.BeaconOfLight > 0) { calc.TotalHealed += calc.HealedBoL = bol.HealingDone(fol_BaseHealed + calc.HealedHL + calc.HealedHS); } calc.TotalHealed += calc.HealedGHL = hl.GlyphOfHolyLight(calc.HealedHL); calc.HealedOther = Stats.Healed; calc.HealedOther += calc.TotalHealed * Stats.ShieldFromHealed; calc.TotalHealed += calc.HealedOther; calc.TotalHealed += calc.HealedSS; calc.AvgHPS = calc.TotalHealed / FightLength; calc.AvgHPM = calc.TotalHealed / calc.TotalMana; return(calc.AvgHPS * (1f - CalcOpts.BurstScale)); }