// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Difficulty { public abstract class DifficultyCalculator : LegacyDifficultyCalculator { /// /// The length of each strain section. /// protected virtual int SectionLength => 400; protected DifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { } protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double clockRate) { var skills = CreateSkills(beatmap); if (!beatmap.HitObjects.Any()) return CreateDifficultyAttributes(beatmap, mods, skills, clockRate); var difficultyHitObjects = CreateDifficultyHitObjects(beatmap, clockRate).OrderBy(h => h.BaseObject.StartTime).ToList(); double sectionLength = SectionLength * clockRate; // The first object doesn't generate a strain, so we begin with an incremented section end double currentSectionEnd = Math.Ceiling(beatmap.HitObjects.First().StartTime / sectionLength) * sectionLength; foreach (DifficultyHitObject h in difficultyHitObjects) { while (h.BaseObject.StartTime > currentSectionEnd) { foreach (Skill s in skills) { s.SaveCurrentPeak(); s.StartNewSectionFrom(currentSectionEnd); } currentSectionEnd += sectionLength; } foreach (Skill s in skills) s.Process(h); } // The peak strain will not be saved for the last section in the above loop foreach (Skill s in skills) s.SaveCurrentPeak(); return CreateDifficultyAttributes(beatmap, mods, skills, clockRate); } /// /// Creates all combinations which adjust the difficulty. /// public Mod[] CreateDifficultyAdjustmentModCombinations() { return createDifficultyAdjustmentModCombinations(Enumerable.Empty(), DifficultyAdjustmentMods).ToArray(); IEnumerable createDifficultyAdjustmentModCombinations(IEnumerable currentSet, Mod[] adjustmentSet, int currentSetCount = 0, int adjustmentSetStart = 0) { switch (currentSetCount) { case 0: // Initial-case: Empty current set yield return new ModNoMod(); break; case 1: yield return currentSet.Single(); break; default: yield return new MultiMod(currentSet.ToArray()); break; } // Apply mods in the adjustment set recursively. Using the entire adjustment set would result in duplicate multi-mod mod // combinations in further recursions, so a moving subset is used to eliminate this effect for (int i = adjustmentSetStart; i < adjustmentSet.Length; i++) { var adjustmentMod = adjustmentSet[i]; if (currentSet.Any(c => c.IncompatibleMods.Any(m => m.IsInstanceOfType(adjustmentMod)))) continue; foreach (var combo in createDifficultyAdjustmentModCombinations(currentSet.Append(adjustmentMod), adjustmentSet, currentSetCount + 1, i + 1)) yield return combo; } } } /// /// Retrieves all s which adjust the difficulty. /// protected virtual Mod[] DifficultyAdjustmentMods => Array.Empty(); /// /// Creates to describe beatmap's calculated difficulty. /// /// The whose difficulty was calculated. /// The s that difficulty was calculated with. /// The skills which processed the beatmap. /// The rate at which the gameplay clock is run at. protected abstract DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate); /// /// Enumerates s to be processed from s in the . /// /// The providing the s to enumerate. /// The rate at which the gameplay clock is run at. /// The enumerated s. protected abstract IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate); /// /// Creates the s to calculate the difficulty of an . /// /// The whose difficulty will be calculated.The s. protected abstract Skill[] CreateSkills(IBeatmap beatmap); } }