1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-21 07:43:01 +08:00
osu-lazer/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs

175 lines
7.4 KiB
C#
Raw Normal View History

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
2018-04-13 17:19:50 +08:00
2018-06-06 15:20:17 +08:00
using System;
2018-04-13 17:19:50 +08:00
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Audio.Track;
2019-02-21 12:12:37 +08:00
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
2019-02-21 12:12:37 +08:00
using osu.Game.Rulesets.Objects;
2018-04-13 17:19:50 +08:00
namespace osu.Game.Rulesets.Difficulty
2018-04-13 17:19:50 +08:00
{
2019-02-21 12:12:37 +08:00
public abstract class DifficultyCalculator
2018-04-13 17:19:50 +08:00
{
/// <summary>
/// The length of each strain section.
/// </summary>
protected virtual int SectionLength => 400;
2018-04-13 17:19:50 +08:00
2019-02-21 12:12:37 +08:00
private readonly Ruleset ruleset;
private readonly WorkingBeatmap beatmap;
protected DifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
2018-04-13 17:19:50 +08:00
{
2019-02-21 12:12:37 +08:00
this.ruleset = ruleset;
this.beatmap = beatmap;
}
/// <summary>
/// Calculates the difficulty of the beatmap using a specific mod combination.
/// </summary>
/// <param name="mods">The mods that should be applied to the beatmap.</param>
/// <returns>A structure describing the difficulty of the beatmap.</returns>
public DifficultyAttributes Calculate(params Mod[] mods)
{
mods = mods.Select(m => m.CreateCopy()).ToArray();
2019-04-08 17:32:05 +08:00
IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods);
2019-02-21 12:12:37 +08:00
var track = new TrackVirtual(10000);
mods.OfType<IApplicableToTrack>().ForEach(m => m.ApplyToTrack(track));
2019-02-21 12:12:37 +08:00
return calculate(playableBeatmap, mods, track.Rate);
2019-02-21 12:12:37 +08:00
}
/// <summary>
/// Calculates the difficulty of the beatmap using all mod combinations applicable to the beatmap.
/// </summary>
/// <returns>A collection of structures describing the difficulty of the beatmap for each mod combination.</returns>
public IEnumerable<DifficultyAttributes> CalculateAll()
{
foreach (var combination in CreateDifficultyAdjustmentModCombinations())
{
if (combination is MultiMod multi)
2019-03-20 15:46:16 +08:00
yield return Calculate(multi.Mods);
2019-02-21 12:12:37 +08:00
else
2019-03-20 15:46:16 +08:00
yield return Calculate(combination);
2019-02-21 12:12:37 +08:00
}
2018-04-13 17:19:50 +08:00
}
2019-02-21 12:12:37 +08:00
private DifficultyAttributes calculate(IBeatmap beatmap, Mod[] mods, double clockRate)
2018-04-13 17:19:50 +08:00
{
2019-02-19 16:52:59 +08:00
var skills = CreateSkills(beatmap);
if (!beatmap.HitObjects.Any())
2019-02-19 16:36:33 +08:00
return CreateDifficultyAttributes(beatmap, mods, skills, clockRate);
2019-02-19 13:29:23 +08:00
var difficultyHitObjects = CreateDifficultyHitObjects(beatmap, clockRate).OrderBy(h => h.BaseObject.StartTime).ToList();
2019-02-19 13:29:23 +08:00
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();
2019-02-19 16:36:33 +08:00
return CreateDifficultyAttributes(beatmap, mods, skills, clockRate);
2018-04-13 17:19:50 +08:00
}
2018-06-06 15:20:17 +08:00
/// <summary>
/// Creates all <see cref="Mod"/> combinations which adjust the <see cref="Beatmap"/> difficulty.
/// </summary>
public Mod[] CreateDifficultyAdjustmentModCombinations()
{
return createDifficultyAdjustmentModCombinations(Array.Empty<Mod>(), DifficultyAdjustmentMods).ToArray();
2018-06-06 15:20:17 +08:00
IEnumerable<Mod> createDifficultyAdjustmentModCombinations(IEnumerable<Mod> currentSet, Mod[] adjustmentSet, int currentSetCount = 0, int adjustmentSetStart = 0)
{
switch (currentSetCount)
{
case 0:
// Initial-case: Empty current set
2018-11-30 16:35:13 +08:00
yield return new ModNoMod();
2019-02-28 12:31:40 +08:00
break;
2019-04-01 11:44:46 +08:00
case 1:
yield return currentSet.Single();
2019-02-28 12:31:40 +08:00
break;
2019-04-01 11:44:46 +08:00
2018-07-17 15:33:08 +08:00
default:
yield return new MultiMod(currentSet.ToArray());
2019-02-28 12:31:40 +08:00
2018-07-17 15:33:08 +08:00
break;
}
2018-06-06 15:20:17 +08:00
// 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;
}
}
}
/// <summary>
/// Retrieves all <see cref="Mod"/>s which adjust the <see cref="Beatmap"/> difficulty.
/// </summary>
protected virtual Mod[] DifficultyAdjustmentMods => Array.Empty<Mod>();
/// <summary>
2019-02-19 16:36:33 +08:00
/// Creates <see cref="DifficultyAttributes"/> to describe beatmap's calculated difficulty.
/// </summary>
2019-02-19 16:52:59 +08:00
/// <param name="beatmap">The <see cref="IBeatmap"/> whose difficulty was calculated.</param>
/// <param name="mods">The <see cref="Mod"/>s that difficulty was calculated with.</param>
/// <param name="skills">The skills which processed the beatmap.</param>
2019-02-19 13:29:23 +08:00
/// <param name="clockRate">The rate at which the gameplay clock is run at.</param>
2019-02-19 16:36:33 +08:00
protected abstract DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate);
/// <summary>
/// Enumerates <see cref="DifficultyHitObject"/>s to be processed from <see cref="HitObject"/>s in the <see cref="IBeatmap"/>.
/// </summary>
/// <param name="beatmap">The <see cref="IBeatmap"/> providing the <see cref="HitObject"/>s to enumerate.</param>
2019-02-19 13:29:23 +08:00
/// <param name="clockRate">The rate at which the gameplay clock is run at.</param>
/// <returns>The enumerated <see cref="DifficultyHitObject"/>s.</returns>
2019-02-19 13:29:23 +08:00
protected abstract IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate);
/// <summary>
2019-02-19 16:52:59 +08:00
/// Creates the <see cref="Skill"/>s to calculate the difficulty of an <see cref="IBeatmap"/>.
/// </summary>
2019-04-25 16:36:17 +08:00
/// <param name="beatmap">The <see cref="IBeatmap"/> whose difficulty will be calculated.</param>
/// <returns>The <see cref="Skill"/>s.</returns>
2019-02-19 16:52:59 +08:00
protected abstract Skill[] CreateSkills(IBeatmap beatmap);
2018-04-13 17:19:50 +08:00
}
}