/// <summary> /// Calculates a rotation based on the FS>LvB>LB priority. /// </summary> public void CalculateRotation(bool addlb1, bool addlb2, bool useFN, bool useCL, bool useDpsFireTotem, bool useFireEle) { if (Talents == null || LB == null || FS == null || LvBFS == null || LvB == null || CL == null || FN == null || ST == null || MT == null || FrS == null) { return; } this.useDpsFireTotem = useDpsFireTotem; spells.Clear(); Invalidate(); float LvBreadyAt = 0, FSdropsAt = 0, clReadyAt = 0, fnReadyAt = 0, activeTotemDropsAt = 0;//, fireEleDropsAt = 0, fireEleReadyAt = 0; //float lsCharges = 0f, fsTicks = 0f; #region GiantBlock switch (useDpsFireTotem) { case true: #region Massive Comment Block About Totem Math /// Reasoning for this block: /// Magma totem is always higher DpS than searing totem. The old formula, which used DpCT instead of /// periodic DpS, always favored searing totem over magma totem, which isn't true in all cases. The reason /// searing eventually "outscales" magma is because magma's excessive use of GCDs for 100% uptime makes it /// less favorable in gear with lots of haste, when the shaman begins approaching the GCD with spells. /// Losing a GCD when an LB takes 2 seconds to cast is less costly than a GCD with a 1.2 second LB. /// Theory behind it: /// The higher DpS spell between CL and LB will be the relative benchmark. The gap in DpS between magma /// and searing are taken and multiplied by 20 (the amount of damage gained by using magma in that period). /// It is then modified by the cast time difference between MT and the relativeDpSValue. This adjusts /// magma's value according to how much of a gap there is in between the GCD and the cast time of the /// benchmark spell. The larger the gap, the more valuable magma totem's gained DpS. If the gained DpS /// isn't more valuable than the direct DpS of the benchmark, searing is selected instead. #endregion Spell relativeDpSValue = (CL.DirectDpS > LB.DirectDpS) ? (Spell)CL : (Spell)LB; if ((((MT.PeriodicDpS - ST.PeriodicDpS) * MT.Duration) / (MT.CastTime / relativeDpSValue.CastTime)) > relativeDpSValue.DirectDpS) { ActiveTotem = MT; } else { ActiveTotem = ST; } break; case false: ActiveTotem = null; activeTotemDropsAt = float.PositiveInfinity; break; } while (true) { if (GetTime() >= LvBreadyAt) //LvB is ready { if (GetTime() + LvBFS.CastTimeWithoutGCD < FSdropsAt) //the LvB cast will be finished before FS runs out { AddSpell(LvBFS); LvBreadyAt = GetTime() + LvBFS.Cooldown; } else if (FSdropsAt == 0) //the first FS { FSdropsAt = GetTime() + FS.Duration; AddSpell(FS); } else //since FS would run out recast FS now -> Rotation end { break; //done } } else //LvB is not ready { if (GetTime() + LvB.CastTime > FSdropsAt) //FS will run out { if (((LB.DpCT > (Math.Max((useCL && clReadyAt < GetTime()) ? CL.DpCT : 0, (useFN && fnReadyAt < GetTime()) ? FN.DpCT : 0)))) && (LB.DpCT > (((activeTotemDropsAt < GetTime()) && useDpsFireTotem) ? ActiveTotem.PeriodicDamage() : 0))) { // LB is the best option available if (LvBreadyAt - (GetTime() + FS.CastTime) > LB.CastTime) //there is enough time to fit in another LB and a FS before LvB is ready { AddSpell(LB); } else //if (LvBreadyAt - (GetTime() + FS.CastTime) > 0 && addlb2) //FS would still fit in before LvB is ready { AddSpell(LB); break; //FS recast nescessary -> done } } else if ((((useCL && clReadyAt < GetTime()) ? CL.DpCT : 0) > ((useFN && fnReadyAt < GetTime()) ? FN.DpCT : 0)) && (CL.DpCT > ((activeTotemDropsAt < GetTime()) && (useDpsFireTotem) ? ActiveTotem.PeriodicDamage() : 0))) // CL > FN and CL > Totem [Or totem doesn't need refreshed] { if (LvBreadyAt - (GetTime() + FS.CastTime) > CL.CastTime) // Can fit another CL and FS { AddSpell(CL); clReadyAt = GetTime() + CL.Cooldown; } else { AddSpell(CL); clReadyAt = GetTime() + CL.Cooldown; break; } } else if ((useFN && fnReadyAt < GetTime()) && (activeTotemDropsAt > GetTime())) // FN > CL and FN > Totem [Or totem doesn't need refreshed] { if (LvBreadyAt - (GetTime() + FS.CastTime) > FN.CastTime) // Can fit another FN and FS { AddSpell(FN); fnReadyAt = GetTime() + FN.Cooldown; } else { AddSpell(FN); fnReadyAt = GetTime() + FN.Cooldown; break; } } else // If the totem needs refreshed... { AddSpell(ActiveTotem); activeTotemDropsAt = ActiveTotem.Duration + GetTime(); } } else if (LvBreadyAt - GetTime() <= LB.CastTime && !addlb1) //time before the next LvB is lower than LB cast time { AddSpell(new Wait(LvBreadyAt - GetTime())); } else //LvB is on cooldown, FS won't run out soon if (((LB.DpCT > (Math.Max((useCL && clReadyAt < GetTime()) ? CL.DpCT : 0, (useFN && fnReadyAt < GetTime()) ? FN.DpCT : 0)))) && (LB.DpCT > (((activeTotemDropsAt < GetTime()) && useDpsFireTotem) ? ActiveTotem.PeriodicDamage() : 0))) { //Is LB Dmg per cast time bigger than the Dpct of CL or FN and are these spells ready? AddSpell(LB); } else if ((((useCL && clReadyAt < GetTime()) ? CL.DpCT : 0) > ((useFN && fnReadyAt < GetTime()) ? FN.DpCT : 0)) && (CL.DpCT > ((activeTotemDropsAt < GetTime()) && (useDpsFireTotem) ? ActiveTotem.PeriodicDamage() : 0))) // CL > FN and CL > Totem [Or totem doesn't need refreshed] { AddSpell(CL); clReadyAt = GetTime() + CL.Cooldown; } else if ((useFN && fnReadyAt < GetTime()) && (activeTotemDropsAt > GetTime())) { //FN > CL AddSpell(FN); fnReadyAt = GetTime() + FN.Cooldown; } else // If the totem needs refreshed... { AddSpell(ActiveTotem); activeTotemDropsAt = ActiveTotem.Duration + GetTime(); } } } #endregion // Have to move to a priority queue. }