/// <summary> /// Calculates the solution by combining subsolutions with and withou Bloodlust /// </summary> private static RotationSolution GetCombinedSolutionWithBloodlust( CombatStats combats, Ability[] rotation, decimal simulationTime) { const float bloodlustDuration = 40f; const float secondsPerMinute = 60f; const float bloodlustHaste = 0.3f; // in seconds float fightLengthWithBloodlust = combats.CalcOpts.Bloodlust ? Math.Min(combats.CalcOpts.FightLength * secondsPerMinute, bloodlustDuration) : 0; // in seconds float fightLengthWithoutBloodlust = Math.Max(0, combats.CalcOpts.FightLength * secondsPerMinute - fightLengthWithBloodlust); float bloodlustSpellHaste = (1 + combats.Stats.SpellHaste) * (1 + bloodlustHaste) - 1; float normalSwingTime = combats.BaseWeaponSpeed / (1 + combats.Stats.PhysicalHaste); float bloodlustSwingTime = normalSwingTime / (1 + bloodlustHaste); return(RotationSolution.Combine( () => GetCombinedSolutionWithDivineStormCooldown( combats, rotation, simulationTime, combats.Stats.SpellHaste, normalSwingTime), fightLengthWithoutBloodlust, () => GetCombinedSolutionWithDivineStormCooldown( combats, rotation, simulationTime, bloodlustSpellHaste, bloodlustSwingTime), fightLengthWithBloodlust)); }
/// <summary> /// Calculates the solution by combining subsolutions with the boss above and under 20% health /// </summary> private static RotationSolution GetCombinedSolutionWithUnder20PercentHealth( CombatStats combats, Ability[] rotation, decimal simulationTime, float divineStormCooldown, float spellHaste) { return(RotationSolution.Combine( () => GetSolution( combats, RemoveHammerOfWrathFromRotation(rotation), simulationTime, divineStormCooldown, spellHaste), 1 - combats.CalcOpts.TimeUnder20, () => GetSolution( combats, rotation, simulationTime, divineStormCooldown, spellHaste), combats.CalcOpts.TimeUnder20)); }
public static RotationSolution Combine( RotationSolution solution1, float solution1Weight, RotationSolution solution2, float solution2Weight) { RotationSolution result = new RotationSolution(); for (int ability = 0; ability <= (int)Ability.Last; ability++) { result.abilityUsagesPerSecond[ability] = Utilities.GetWeightedSum( solution1.abilityUsagesPerSecond[ability], solution1Weight, solution2.abilityUsagesPerSecond[ability], solution2Weight); // Zero CD means the ability is not in rotation if (solution1.abilityEffectiveCooldowns[ability] == 0) { result.abilityEffectiveCooldowns[ability] = solution2.abilityEffectiveCooldowns[ability]; } else if (solution2.abilityEffectiveCooldowns[ability] == 0) { result.abilityEffectiveCooldowns[ability] = solution1.abilityEffectiveCooldowns[ability]; } else { result.abilityEffectiveCooldowns[ability] = 1 / Utilities.GetWeightedSum( 1 / solution1.abilityEffectiveCooldowns[ability], solution1Weight, 1 / solution2.abilityEffectiveCooldowns[ability], solution2Weight); } } return(result); }
/// <summary> /// Calculates the solution by combining subsolutions with different Divine Storm cooldowns, /// if 2 piece T10 bonus is active. /// </summary> private static RotationSolution GetCombinedSolutionWithDivineStormCooldown( CombatStats combats, Ability[] rotation, decimal simulationTime, float spellHaste, float swingTime) { const float normalDivineStormCooldown = 10; const float cooldownRangeStep = 0.6f; if (combats.Stats.DivineStormRefresh == 0) { return(GetCombinedSolutionWithUnder20PercentHealth( combats, rotation, simulationTime, normalDivineStormCooldown, spellHaste)); } // Calculate solutions for different Divine Storm cooldowns // and combine them weighted by their neighbourhood cooldown range probabilities RotationSolution result = null; float resultProbability = 0; for ( float cooldownRangeStart = 0; cooldownRangeStart < normalDivineStormCooldown; cooldownRangeStart += cooldownRangeStep) { float currentSolutionProbability = GetT10DivineStormCooldownProbability( swingTime, cooldownRangeStart, Math.Min(normalDivineStormCooldown, cooldownRangeStart + cooldownRangeStep), combats.Stats.DivineStormRefresh); result = RotationSolution.Combine( () => result, resultProbability, () => GetCombinedSolutionWithUnder20PercentHealth( combats, rotation, simulationTime, Math.Min( normalDivineStormCooldown, cooldownRangeStart + cooldownRangeStart / 2 + combats.CalcOpts.Delay), spellHaste), currentSolutionProbability); resultProbability += currentSolutionProbability; } // Combine with normal Divine Storm cooldown in cases when T10 doesn't proc return(RotationSolution.Combine( () => result, resultProbability, () => GetCombinedSolutionWithUnder20PercentHealth( combats, rotation, simulationTime, normalDivineStormCooldown, spellHaste), 1 - resultProbability)); }
private static RotationSolution SimulateRotationCore(SimulatorParameters rot) { const int timeUnitsPerSecond = 100000; const int meleeAbilityGcd = (int)(1.5m * timeUnitsPerSecond); int fightLength = (int)(rot.SimulationTime * timeUnitsPerSecond); int spellGcd = (int)(rot.SpellGCD * timeUnitsPerSecond); SimulatorAbility.Delay = (int)(rot.Delay * timeUnitsPerSecond); SimulatorAbility.Wait = (int)(rot.Wait * timeUnitsPerSecond); SimulatorAbility[] abilities = new SimulatorAbility[(int)Ability.Last + 1]; abilities[(int)Ability.Judgement] = new SimulatorAbility( (10 - rot.ImpJudgements - (rot.T7_4pc ? 1 : 0)) * timeUnitsPerSecond, meleeAbilityGcd); abilities[(int)Ability.CrusaderStrike] = new SimulatorAbility( 4 * timeUnitsPerSecond, meleeAbilityGcd); abilities[(int)Ability.DivineStorm] = new SimulatorAbility( (int)(rot.DivineStormCooldown * timeUnitsPerSecond), meleeAbilityGcd); abilities[(int)Ability.Consecration] = new SimulatorAbility( (rot.GlyphConsecrate ? 10 : 8) * timeUnitsPerSecond, spellGcd); abilities[(int)Ability.Exorcism] = new SimulatorAbility( 15 * timeUnitsPerSecond, spellGcd); abilities[(int)Ability.HammerOfWrath] = new SimulatorAbility( 6 * timeUnitsPerSecond, meleeAbilityGcd); int gcdFinishTime = 0; int currentTime = 0; while (currentTime < fightLength) { if (currentTime >= gcdFinishTime) { foreach (Ability ability in rot.Priorities) { if (abilities[(int)ability].ShouldAbilityBeUsedNext(currentTime)) { if (abilities[(int)ability].CanAbilityBeUsedNow(currentTime)) { gcdFinishTime = abilities[(int)ability].UseAbility(currentTime); } break; } } } int nextTime = fightLength; if (currentTime >= gcdFinishTime) { foreach (Ability ability in rot.Priorities) { int nextUseTime = abilities[(int)ability].GetNextUseTime(currentTime); if (nextUseTime > currentTime) { nextTime = Math.Min(nextTime, nextUseTime); } } } else { nextTime = Math.Min(nextTime, gcdFinishTime); } currentTime = nextTime; } float fightLengthInSeconds = ((float)fightLength) / timeUnitsPerSecond; RotationSolution solution = new RotationSolution(); foreach (Ability ability in rot.Priorities) { solution.SetAbilityUsagePerSecond( ability, abilities[(int)ability].Uses / fightLengthInSeconds); solution.SetAbilityEffectiveCooldown( ability, abilities[(int)ability].EffectiveCooldown() / timeUnitsPerSecond); } return(solution); }