// 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 timeRate) { var attributes = CreateDifficultyAttributes(mods); if (!beatmap.HitObjects.Any()) return attributes; var difficultyHitObjects = CreateDifficultyHitObjects(beatmap, timeRate).OrderBy(h => h.BaseObject.StartTime).ToList(); var skills = CreateSkills(); double sectionLength = SectionLength * timeRate; // 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(); PopulateAttributes(attributes, beatmap, skills, timeRate); return attributes; } /// /// 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(); /// /// Populates after difficulty has been processed. /// /// The to populate with information about the difficulty of . /// The whose difficulty was processed. /// The skills which processed the difficulty. /// The rate of time in . protected abstract void PopulateAttributes(DifficultyAttributes attributes, IBeatmap beatmap, Skill[] skills, double timeRate); /// /// Enumerates s to be processed from s in the . /// /// The providing the s to enumerate. /// The rate of time in . /// The enumerated s. protected abstract IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double timeRate); /// /// Creates the s to calculate the difficulty of s. /// /// The s. protected abstract Skill[] CreateSkills(); /// /// Creates an empty . /// /// The s which difficulty is being processed with. /// The empty . protected abstract DifficultyAttributes CreateDifficultyAttributes(Mod[] mods); } }