mirror of
https://github.com/ppy/osu.git
synced 2025-02-08 07:02:54 +08:00
Merge pull request #31636 from tsunyoku/taiko-cleanup
This commit is contained in:
commit
2593946f69
@ -10,32 +10,8 @@ using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour.Data;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
|
namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
|
||||||
{
|
{
|
||||||
public class ColourEvaluator
|
public static class ColourEvaluator
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Evaluate the difficulty of the first note of a <see cref="MonoStreak"/>.
|
|
||||||
/// </summary>
|
|
||||||
public static double EvaluateDifficultyOf(MonoStreak monoStreak)
|
|
||||||
{
|
|
||||||
return DifficultyCalculationUtils.Logistic(exponent: Math.E * monoStreak.Index - 2 * Math.E) * EvaluateDifficultyOf(monoStreak.Parent) * 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Evaluate the difficulty of the first note of a <see cref="AlternatingMonoPattern"/>.
|
|
||||||
/// </summary>
|
|
||||||
public static double EvaluateDifficultyOf(AlternatingMonoPattern alternatingMonoPattern)
|
|
||||||
{
|
|
||||||
return DifficultyCalculationUtils.Logistic(exponent: Math.E * alternatingMonoPattern.Index - 2 * Math.E) * EvaluateDifficultyOf(alternatingMonoPattern.Parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Evaluate the difficulty of the first note of a <see cref="RepeatingHitPatterns"/>.
|
|
||||||
/// </summary>
|
|
||||||
public static double EvaluateDifficultyOf(RepeatingHitPatterns repeatingHitPattern)
|
|
||||||
{
|
|
||||||
return 2 * (1 - DifficultyCalculationUtils.Logistic(exponent: Math.E * repeatingHitPattern.RepetitionInterval - 2 * Math.E));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates a consistency penalty based on the number of consecutive consistent intervals,
|
/// Calculates a consistency penalty based on the number of consecutive consistent intervals,
|
||||||
/// considering the delta time between each colour sequence.
|
/// considering the delta time between each colour sequence.
|
||||||
@ -58,8 +34,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
|
|||||||
|
|
||||||
var previousHitObject = (TaikoDifficultyHitObject)current.Previous(1);
|
var previousHitObject = (TaikoDifficultyHitObject)current.Previous(1);
|
||||||
|
|
||||||
double currentRatio = current.Rhythm.Ratio;
|
double currentRatio = current.RhythmData.Ratio;
|
||||||
double previousRatio = previousHitObject.Rhythm.Ratio;
|
double previousRatio = previousHitObject.RhythmData.Ratio;
|
||||||
|
|
||||||
// A consistent interval is defined as the percentage difference between the two rhythmic ratios with the margin of error.
|
// A consistent interval is defined as the percentage difference between the two rhythmic ratios with the margin of error.
|
||||||
if (Math.Abs(1 - currentRatio / previousRatio) <= threshold)
|
if (Math.Abs(1 - currentRatio / previousRatio) <= threshold)
|
||||||
@ -85,22 +61,31 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
|
|||||||
public static double EvaluateDifficultyOf(DifficultyHitObject hitObject)
|
public static double EvaluateDifficultyOf(DifficultyHitObject hitObject)
|
||||||
{
|
{
|
||||||
var taikoObject = (TaikoDifficultyHitObject)hitObject;
|
var taikoObject = (TaikoDifficultyHitObject)hitObject;
|
||||||
TaikoDifficultyHitObjectColour colour = taikoObject.Colour;
|
TaikoColourData colourData = taikoObject.ColourData;
|
||||||
double difficulty = 0.0d;
|
double difficulty = 0.0d;
|
||||||
|
|
||||||
if (colour.MonoStreak?.FirstHitObject == hitObject) // Difficulty for MonoStreak
|
if (colourData.MonoStreak?.FirstHitObject == hitObject) // Difficulty for MonoStreak
|
||||||
difficulty += EvaluateDifficultyOf(colour.MonoStreak);
|
difficulty += evaluateMonoStreakDifficulty(colourData.MonoStreak);
|
||||||
|
|
||||||
if (colour.AlternatingMonoPattern?.FirstHitObject == hitObject) // Difficulty for AlternatingMonoPattern
|
if (colourData.AlternatingMonoPattern?.FirstHitObject == hitObject) // Difficulty for AlternatingMonoPattern
|
||||||
difficulty += EvaluateDifficultyOf(colour.AlternatingMonoPattern);
|
difficulty += evaluateAlternatingMonoPatternDifficulty(colourData.AlternatingMonoPattern);
|
||||||
|
|
||||||
if (colour.RepeatingHitPattern?.FirstHitObject == hitObject) // Difficulty for RepeatingHitPattern
|
if (colourData.RepeatingHitPattern?.FirstHitObject == hitObject) // Difficulty for RepeatingHitPattern
|
||||||
difficulty += EvaluateDifficultyOf(colour.RepeatingHitPattern);
|
difficulty += evaluateRepeatingHitPatternsDifficulty(colourData.RepeatingHitPattern);
|
||||||
|
|
||||||
double consistencyPenalty = consistentRatioPenalty(taikoObject);
|
double consistencyPenalty = consistentRatioPenalty(taikoObject);
|
||||||
difficulty *= consistencyPenalty;
|
difficulty *= consistencyPenalty;
|
||||||
|
|
||||||
return difficulty;
|
return difficulty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static double evaluateMonoStreakDifficulty(MonoStreak monoStreak) =>
|
||||||
|
DifficultyCalculationUtils.Logistic(exponent: Math.E * monoStreak.Index - 2 * Math.E) * evaluateAlternatingMonoPatternDifficulty(monoStreak.Parent) * 0.5;
|
||||||
|
|
||||||
|
private static double evaluateAlternatingMonoPatternDifficulty(AlternatingMonoPattern alternatingMonoPattern) =>
|
||||||
|
DifficultyCalculationUtils.Logistic(exponent: Math.E * alternatingMonoPattern.Index - 2 * Math.E) * evaluateRepeatingHitPatternsDifficulty(alternatingMonoPattern.Parent);
|
||||||
|
|
||||||
|
private static double evaluateRepeatingHitPatternsDifficulty(RepeatingHitPatterns repeatingHitPattern) =>
|
||||||
|
2 * (1 - DifficultyCalculationUtils.Logistic(exponent: Math.E * repeatingHitPattern.RepetitionInterval - 2 * Math.E));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,8 +47,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
|
|||||||
// High density is penalised at high velocity as it is generally considered easier to read. See https://www.desmos.com/calculator/u63f3ntdsi
|
// High density is penalised at high velocity as it is generally considered easier to read. See https://www.desmos.com/calculator/u63f3ntdsi
|
||||||
double densityPenalty = DifficultyCalculationUtils.Logistic(objectDensity, 0.925, 15);
|
double densityPenalty = DifficultyCalculationUtils.Logistic(objectDensity, 0.925, 15);
|
||||||
|
|
||||||
double highVelocityDifficulty = (1.0 - 0.33 * densityPenalty) * DifficultyCalculationUtils.Logistic
|
double highVelocityDifficulty = (1.0 - 0.33 * densityPenalty)
|
||||||
(effectiveBPM, highVelocity.Center + 8 * densityPenalty, (1.0 + 0.5 * densityPenalty) / (highVelocity.Range / 10));
|
* DifficultyCalculationUtils.Logistic(effectiveBPM, highVelocity.Center + 8 * densityPenalty, (1.0 + 0.5 * densityPenalty) / (highVelocity.Range / 10));
|
||||||
|
|
||||||
return midVelocityDifficulty + highVelocityDifficulty;
|
return midVelocityDifficulty + highVelocityDifficulty;
|
||||||
}
|
}
|
||||||
|
@ -18,39 +18,39 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static double EvaluateDifficultyOf(DifficultyHitObject hitObject, double hitWindow)
|
public static double EvaluateDifficultyOf(DifficultyHitObject hitObject, double hitWindow)
|
||||||
{
|
{
|
||||||
TaikoDifficultyHitObjectRhythm rhythm = ((TaikoDifficultyHitObject)hitObject).Rhythm;
|
TaikoRhythmData rhythmData = ((TaikoDifficultyHitObject)hitObject).RhythmData;
|
||||||
double difficulty = 0.0d;
|
double difficulty = 0.0d;
|
||||||
|
|
||||||
double sameRhythm = 0;
|
double sameRhythm = 0;
|
||||||
double samePattern = 0;
|
double samePattern = 0;
|
||||||
double intervalPenalty = 0;
|
double intervalPenalty = 0;
|
||||||
|
|
||||||
if (rhythm.SameRhythmHitObjects?.FirstHitObject == hitObject) // Difficulty for SameRhythmHitObjects
|
if (rhythmData.SameRhythmGroupedHitObjects?.FirstHitObject == hitObject) // Difficulty for SameRhythmGroupedHitObjects
|
||||||
{
|
{
|
||||||
sameRhythm += 10.0 * evaluateDifficultyOf(rhythm.SameRhythmHitObjects, hitWindow);
|
sameRhythm += 10.0 * evaluateDifficultyOf(rhythmData.SameRhythmGroupedHitObjects, hitWindow);
|
||||||
intervalPenalty = repeatedIntervalPenalty(rhythm.SameRhythmHitObjects, hitWindow);
|
intervalPenalty = repeatedIntervalPenalty(rhythmData.SameRhythmGroupedHitObjects, hitWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rhythm.SamePatterns?.FirstHitObject == hitObject) // Difficulty for SamePatterns
|
if (rhythmData.SamePatternsGroupedHitObjects?.FirstHitObject == hitObject) // Difficulty for SamePatternsGroupedHitObjects
|
||||||
samePattern += 1.15 * ratioDifficulty(rhythm.SamePatterns.IntervalRatio);
|
samePattern += 1.15 * ratioDifficulty(rhythmData.SamePatternsGroupedHitObjects.IntervalRatio);
|
||||||
|
|
||||||
difficulty += Math.Max(sameRhythm, samePattern) * intervalPenalty;
|
difficulty += Math.Max(sameRhythm, samePattern) * intervalPenalty;
|
||||||
|
|
||||||
return difficulty;
|
return difficulty;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double evaluateDifficultyOf(SameRhythmHitObjects sameRhythmHitObjects, double hitWindow)
|
private static double evaluateDifficultyOf(SameRhythmHitObjectGrouping sameRhythmGroupedHitObjects, double hitWindow)
|
||||||
{
|
{
|
||||||
double intervalDifficulty = ratioDifficulty(sameRhythmHitObjects.HitObjectIntervalRatio);
|
double intervalDifficulty = ratioDifficulty(sameRhythmGroupedHitObjects.HitObjectIntervalRatio);
|
||||||
double? previousInterval = sameRhythmHitObjects.Previous?.HitObjectInterval;
|
double? previousInterval = sameRhythmGroupedHitObjects.Previous?.HitObjectInterval;
|
||||||
|
|
||||||
intervalDifficulty *= repeatedIntervalPenalty(sameRhythmHitObjects, hitWindow);
|
intervalDifficulty *= repeatedIntervalPenalty(sameRhythmGroupedHitObjects, hitWindow);
|
||||||
|
|
||||||
// If a previous interval exists and there are multiple hit objects in the sequence:
|
// If a previous interval exists and there are multiple hit objects in the sequence:
|
||||||
if (previousInterval != null && sameRhythmHitObjects.Children.Count > 1)
|
if (previousInterval != null && sameRhythmGroupedHitObjects.HitObjects.Count > 1)
|
||||||
{
|
{
|
||||||
double expectedDurationFromPrevious = (double)previousInterval * sameRhythmHitObjects.Children.Count;
|
double expectedDurationFromPrevious = (double)previousInterval * sameRhythmGroupedHitObjects.HitObjects.Count;
|
||||||
double durationDifference = sameRhythmHitObjects.Duration - expectedDurationFromPrevious;
|
double durationDifference = sameRhythmGroupedHitObjects.Duration - expectedDurationFromPrevious;
|
||||||
|
|
||||||
if (durationDifference > 0)
|
if (durationDifference > 0)
|
||||||
{
|
{
|
||||||
@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
|
|||||||
|
|
||||||
// Penalise patterns that can be hit within a single hit window.
|
// Penalise patterns that can be hit within a single hit window.
|
||||||
intervalDifficulty *= DifficultyCalculationUtils.Logistic(
|
intervalDifficulty *= DifficultyCalculationUtils.Logistic(
|
||||||
sameRhythmHitObjects.Duration / hitWindow,
|
sameRhythmGroupedHitObjects.Duration / hitWindow,
|
||||||
midpointOffset: 0.6,
|
midpointOffset: 0.6,
|
||||||
multiplier: 1,
|
multiplier: 1,
|
||||||
maxValue: 1);
|
maxValue: 1);
|
||||||
@ -75,20 +75,20 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines if the changes in hit object intervals is consistent based on a given threshold.
|
/// Determines if the changes in hit object intervals is consistent based on a given threshold.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static double repeatedIntervalPenalty(SameRhythmHitObjects sameRhythmHitObjects, double hitWindow, double threshold = 0.1)
|
private static double repeatedIntervalPenalty(SameRhythmHitObjectGrouping sameRhythmGroupedHitObjects, double hitWindow, double threshold = 0.1)
|
||||||
{
|
{
|
||||||
double longIntervalPenalty = sameInterval(sameRhythmHitObjects, 3);
|
double longIntervalPenalty = sameInterval(sameRhythmGroupedHitObjects, 3);
|
||||||
|
|
||||||
double shortIntervalPenalty = sameRhythmHitObjects.Children.Count < 6
|
double shortIntervalPenalty = sameRhythmGroupedHitObjects.HitObjects.Count < 6
|
||||||
? sameInterval(sameRhythmHitObjects, 4)
|
? sameInterval(sameRhythmGroupedHitObjects, 4)
|
||||||
: 1.0; // Returns a non-penalty if there are 6 or more notes within an interval.
|
: 1.0; // Returns a non-penalty if there are 6 or more notes within an interval.
|
||||||
|
|
||||||
// The duration penalty is based on hit object duration relative to hitWindow.
|
// The duration penalty is based on hit object duration relative to hitWindow.
|
||||||
double durationPenalty = Math.Max(1 - sameRhythmHitObjects.Duration * 2 / hitWindow, 0.5);
|
double durationPenalty = Math.Max(1 - sameRhythmGroupedHitObjects.Duration * 2 / hitWindow, 0.5);
|
||||||
|
|
||||||
return Math.Min(longIntervalPenalty, shortIntervalPenalty) * durationPenalty;
|
return Math.Min(longIntervalPenalty, shortIntervalPenalty) * durationPenalty;
|
||||||
|
|
||||||
double sameInterval(SameRhythmHitObjects startObject, int intervalCount)
|
double sameInterval(SameRhythmHitObjectGrouping startObject, int intervalCount)
|
||||||
{
|
{
|
||||||
List<double?> intervals = new List<double?>();
|
List<double?> intervals = new List<double?>();
|
||||||
var currentObject = startObject;
|
var currentObject = startObject;
|
||||||
|
@ -8,43 +8,8 @@ using osu.Game.Rulesets.Taiko.Objects;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
|
namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
|
||||||
{
|
{
|
||||||
public class StaminaEvaluator
|
public static class StaminaEvaluator
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Applies a speed bonus dependent on the time since the last hit performed using this finger.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="interval">The interval between the current and previous note hit using the same finger.</param>
|
|
||||||
private static double speedBonus(double interval)
|
|
||||||
{
|
|
||||||
// Interval is capped at a very small value to prevent infinite values.
|
|
||||||
interval = Math.Max(interval, 1);
|
|
||||||
|
|
||||||
return 20 / interval;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines the number of fingers available to hit the current <see cref="TaikoDifficultyHitObject"/>.
|
|
||||||
/// Any mono notes that is more than 300ms apart from a colour change will be considered to have more than 2
|
|
||||||
/// fingers available, since players can hit the same key with multiple fingers.
|
|
||||||
/// </summary>
|
|
||||||
private static int availableFingersFor(TaikoDifficultyHitObject hitObject)
|
|
||||||
{
|
|
||||||
DifficultyHitObject? previousColourChange = hitObject.Colour.PreviousColourChange;
|
|
||||||
DifficultyHitObject? nextColourChange = hitObject.Colour.NextColourChange;
|
|
||||||
|
|
||||||
if (previousColourChange != null && hitObject.StartTime - previousColourChange.StartTime < 300)
|
|
||||||
{
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextColourChange != null && nextColourChange.StartTime - hitObject.StartTime < 300)
|
|
||||||
{
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Evaluates the minimum mechanical stamina required to play the current object. This is calculated using the
|
/// Evaluates the minimum mechanical stamina required to play the current object. This is calculated using the
|
||||||
/// maximum possible interval between two hits using the same key, by alternating available fingers for each colour.
|
/// maximum possible interval between two hits using the same key, by alternating available fingers for each colour.
|
||||||
@ -70,5 +35,40 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
|
|||||||
|
|
||||||
return objectStrain;
|
return objectStrain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies a speed bonus dependent on the time since the last hit performed using this finger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="interval">The interval between the current and previous note hit using the same finger.</param>
|
||||||
|
private static double speedBonus(double interval)
|
||||||
|
{
|
||||||
|
// Interval is capped at a very small value to prevent infinite values.
|
||||||
|
interval = Math.Max(interval, 1);
|
||||||
|
|
||||||
|
return 20 / interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines the number of fingers available to hit the current <see cref="TaikoDifficultyHitObject"/>.
|
||||||
|
/// Any mono notes that is more than 300ms apart from a colour change will be considered to have more than 2
|
||||||
|
/// fingers available, since players can hit the same key with multiple fingers.
|
||||||
|
/// </summary>
|
||||||
|
private static int availableFingersFor(TaikoDifficultyHitObject hitObject)
|
||||||
|
{
|
||||||
|
DifficultyHitObject? previousColourChange = hitObject.ColourData.PreviousColourChange;
|
||||||
|
DifficultyHitObject? nextColourChange = hitObject.ColourData.NextColourChange;
|
||||||
|
|
||||||
|
if (previousColourChange != null && hitObject.StartTime - previousColourChange.StartTime < 300)
|
||||||
|
{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextColourChange != null && nextColourChange.StartTime - hitObject.StartTime < 300)
|
||||||
|
{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stores colour compression information for a <see cref="TaikoDifficultyHitObject"/>.
|
/// Stores colour compression information for a <see cref="TaikoDifficultyHitObject"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TaikoDifficultyHitObjectColour
|
public class TaikoColourData
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The <see cref="MonoStreak"/> that encodes this note.
|
/// The <see cref="MonoStreak"/> that encodes this note.
|
@ -14,8 +14,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour
|
|||||||
public static class TaikoColourDifficultyPreprocessor
|
public static class TaikoColourDifficultyPreprocessor
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes and encodes a list of <see cref="TaikoDifficultyHitObject"/>s into a list of <see cref="TaikoDifficultyHitObjectColour"/>s,
|
/// Processes and encodes a list of <see cref="TaikoDifficultyHitObject"/>s into a list of <see cref="TaikoColourData"/>s,
|
||||||
/// assigning the appropriate <see cref="TaikoDifficultyHitObjectColour"/>s to each <see cref="TaikoDifficultyHitObject"/>.
|
/// assigning the appropriate <see cref="TaikoColourData"/>s to each <see cref="TaikoDifficultyHitObject"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void ProcessAndAssign(List<DifficultyHitObject> hitObjects)
|
public static void ProcessAndAssign(List<DifficultyHitObject> hitObjects)
|
||||||
{
|
{
|
||||||
@ -41,9 +41,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour
|
|||||||
|
|
||||||
foreach (var hitObject in monoStreak.HitObjects)
|
foreach (var hitObject in monoStreak.HitObjects)
|
||||||
{
|
{
|
||||||
hitObject.Colour.RepeatingHitPattern = repeatingHitPattern;
|
hitObject.ColourData.RepeatingHitPattern = repeatingHitPattern;
|
||||||
hitObject.Colour.AlternatingMonoPattern = monoPattern;
|
hitObject.ColourData.AlternatingMonoPattern = monoPattern;
|
||||||
hitObject.Colour.MonoStreak = monoStreak;
|
hitObject.ColourData.MonoStreak = monoStreak;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
// 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.
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Reading
|
|
||||||
{
|
|
||||||
public class EffectiveBPMPreprocessor
|
|
||||||
{
|
|
||||||
private readonly IList<TaikoDifficultyHitObject> noteObjects;
|
|
||||||
private readonly double globalSliderVelocity;
|
|
||||||
|
|
||||||
public EffectiveBPMPreprocessor(IBeatmap beatmap, List<TaikoDifficultyHitObject> noteObjects)
|
|
||||||
{
|
|
||||||
this.noteObjects = noteObjects;
|
|
||||||
globalSliderVelocity = beatmap.Difficulty.SliderMultiplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calculates and sets the effective BPM and slider velocity for each note object, considering clock rate and scroll speed.
|
|
||||||
/// </summary>
|
|
||||||
public void ProcessEffectiveBPM(ControlPointInfo controlPointInfo, double clockRate)
|
|
||||||
{
|
|
||||||
foreach (var currentNoteObject in noteObjects)
|
|
||||||
{
|
|
||||||
double startTime = currentNoteObject.StartTime * clockRate;
|
|
||||||
|
|
||||||
// Retrieve the timing point at the note's start time
|
|
||||||
TimingControlPoint currentControlPoint = controlPointInfo.TimingPointAt(startTime);
|
|
||||||
|
|
||||||
// Calculate the slider velocity at the note's start time.
|
|
||||||
double currentSliderVelocity = calculateSliderVelocity(controlPointInfo, startTime, clockRate);
|
|
||||||
currentNoteObject.CurrentSliderVelocity = currentSliderVelocity;
|
|
||||||
|
|
||||||
currentNoteObject.EffectiveBPM = currentControlPoint.BPM * currentSliderVelocity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calculates the slider velocity based on control point info and clock rate.
|
|
||||||
/// </summary>
|
|
||||||
private double calculateSliderVelocity(ControlPointInfo controlPointInfo, double startTime, double clockRate)
|
|
||||||
{
|
|
||||||
var activeEffectControlPoint = controlPointInfo.EffectPointAt(startTime);
|
|
||||||
return globalSliderVelocity * (activeEffectControlPoint.ScrollSpeed) * clockRate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
// 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.
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents <see cref="SameRhythmHitObjects"/> grouped by their <see cref="SameRhythmHitObjects.StartTime"/>'s interval.
|
|
||||||
/// </summary>
|
|
||||||
public class SamePatterns : SameRhythm<SameRhythmHitObjects>
|
|
||||||
{
|
|
||||||
public SamePatterns? Previous { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The <see cref="SameRhythmHitObjects.Interval"/> between children <see cref="SameRhythmHitObjects"/> within this group.
|
|
||||||
/// If there is only one child, this will have the value of the first child's <see cref="SameRhythmHitObjects.Interval"/>.
|
|
||||||
/// </summary>
|
|
||||||
public double ChildrenInterval => Children.Count > 1 ? Children[1].Interval : Children[0].Interval;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The ratio of <see cref="ChildrenInterval"/> between this and the previous <see cref="SamePatterns"/>. In the
|
|
||||||
/// case where there is no previous <see cref="SamePatterns"/>, this will have a value of 1.
|
|
||||||
/// </summary>
|
|
||||||
public double IntervalRatio => ChildrenInterval / Previous?.ChildrenInterval ?? 1.0d;
|
|
||||||
|
|
||||||
public TaikoDifficultyHitObject FirstHitObject => Children[0].FirstHitObject;
|
|
||||||
|
|
||||||
public IEnumerable<TaikoDifficultyHitObject> AllHitObjects => Children.SelectMany(child => child.Children);
|
|
||||||
|
|
||||||
private SamePatterns(SamePatterns? previous, List<SameRhythmHitObjects> data, ref int i)
|
|
||||||
: base(data, ref i, 5)
|
|
||||||
{
|
|
||||||
Previous = previous;
|
|
||||||
|
|
||||||
foreach (TaikoDifficultyHitObject hitObject in AllHitObjects)
|
|
||||||
{
|
|
||||||
hitObject.Rhythm.SamePatterns = this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void GroupPatterns(List<SameRhythmHitObjects> data)
|
|
||||||
{
|
|
||||||
List<SamePatterns> samePatterns = new List<SamePatterns>();
|
|
||||||
|
|
||||||
// Index does not need to be incremented, as it is handled within the SameRhythm constructor.
|
|
||||||
for (int i = 0; i < data.Count;)
|
|
||||||
{
|
|
||||||
SamePatterns? previous = samePatterns.Count > 0 ? samePatterns[^1] : null;
|
|
||||||
samePatterns.Add(new SamePatterns(previous, data, ref i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,40 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents <see cref="SameRhythmHitObjectGrouping"/> grouped by their <see cref="SameRhythmHitObjectGrouping.StartTime"/>'s interval.
|
||||||
|
/// </summary>
|
||||||
|
public class SamePatternsGroupedHitObjects
|
||||||
|
{
|
||||||
|
public IReadOnlyList<SameRhythmHitObjectGrouping> Groups { get; }
|
||||||
|
|
||||||
|
public SamePatternsGroupedHitObjects? Previous { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="SameRhythmHitObjectGrouping.Interval"/> between groups <see cref="SameRhythmHitObjectGrouping"/>.
|
||||||
|
/// If there is only one group, this will have the value of the first group's <see cref="SameRhythmHitObjectGrouping.Interval"/>.
|
||||||
|
/// </summary>
|
||||||
|
public double GroupInterval => Groups.Count > 1 ? Groups[1].Interval : Groups[0].Interval;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ratio of <see cref="GroupInterval"/> between this and the previous <see cref="SamePatternsGroupedHitObjects"/>. In the
|
||||||
|
/// case where there is no previous <see cref="SamePatternsGroupedHitObjects"/>, this will have a value of 1.
|
||||||
|
/// </summary>
|
||||||
|
public double IntervalRatio => GroupInterval / Previous?.GroupInterval ?? 1.0d;
|
||||||
|
|
||||||
|
public TaikoDifficultyHitObject FirstHitObject => Groups[0].FirstHitObject;
|
||||||
|
|
||||||
|
public IEnumerable<TaikoDifficultyHitObject> AllHitObjects => Groups.SelectMany(hitObject => hitObject.HitObjects);
|
||||||
|
|
||||||
|
public SamePatternsGroupedHitObjects(SamePatternsGroupedHitObjects? previous, List<SameRhythmHitObjectGrouping> groups)
|
||||||
|
{
|
||||||
|
Previous = previous;
|
||||||
|
Groups = groups;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,73 +0,0 @@
|
|||||||
// 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.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A base class for grouping <see cref="IHasInterval"/>s by their interval. In edges where an interval change
|
|
||||||
/// occurs, the <see cref="IHasInterval"/> is added to the group with the smaller interval.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class SameRhythm<ChildType>
|
|
||||||
where ChildType : IHasInterval
|
|
||||||
{
|
|
||||||
public IReadOnlyList<ChildType> Children { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines if the intervals between two child objects are within a specified margin of error,
|
|
||||||
/// indicating that the intervals are effectively "flat" or consistent.
|
|
||||||
/// </summary>
|
|
||||||
private bool isFlat(ChildType current, ChildType previous, double marginOfError)
|
|
||||||
{
|
|
||||||
return Math.Abs(current.Interval - previous.Interval) <= marginOfError;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new <see cref="SameRhythm{ChildType}"/> from a list of <see cref="IHasInterval"/>s, and add
|
|
||||||
/// them to the <see cref="Children"/> list until the end of the group.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="data">The list of <see cref="IHasInterval"/>s.</param>
|
|
||||||
/// <param name="i">
|
|
||||||
/// Index in <paramref name="data"/> to start adding children. This will be modified and should be passed into
|
|
||||||
/// the next <see cref="SameRhythm{ChildType}"/>'s constructor.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="marginOfError">
|
|
||||||
/// The margin of error for the interval, within of which no interval change is considered to have occured.
|
|
||||||
/// </param>
|
|
||||||
protected SameRhythm(List<ChildType> data, ref int i, double marginOfError)
|
|
||||||
{
|
|
||||||
List<ChildType> children = new List<ChildType>();
|
|
||||||
Children = children;
|
|
||||||
children.Add(data[i]);
|
|
||||||
i++;
|
|
||||||
|
|
||||||
for (; i < data.Count - 1; i++)
|
|
||||||
{
|
|
||||||
// An interval change occured, add the current data if the next interval is larger.
|
|
||||||
if (!isFlat(data[i], data[i + 1], marginOfError))
|
|
||||||
{
|
|
||||||
if (data[i + 1].Interval > data[i].Interval + marginOfError)
|
|
||||||
{
|
|
||||||
children.Add(data[i]);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No interval change occured
|
|
||||||
children.Add(data[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the last two objects in the data form a "flat" rhythm pattern within the specified margin of error.
|
|
||||||
// If true, add the current object to the group and increment the index to process the next object.
|
|
||||||
if (data.Count > 2 && isFlat(data[^1], data[^2], marginOfError))
|
|
||||||
{
|
|
||||||
children.Add(data[i]);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,63 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
|
using osu.Game.Rulesets.Taiko.Difficulty.Utils;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a group of <see cref="TaikoDifficultyHitObject"/>s with no rhythm variation.
|
||||||
|
/// </summary>
|
||||||
|
public class SameRhythmHitObjectGrouping : IHasInterval
|
||||||
|
{
|
||||||
|
public readonly List<TaikoDifficultyHitObject> HitObjects;
|
||||||
|
|
||||||
|
public TaikoDifficultyHitObject FirstHitObject => HitObjects[0];
|
||||||
|
|
||||||
|
public readonly SameRhythmHitObjectGrouping? Previous;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DifficultyHitObject.StartTime"/> of the first hit object.
|
||||||
|
/// </summary>
|
||||||
|
public double StartTime => HitObjects[0].StartTime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The interval between the first and final hit object within this group.
|
||||||
|
/// </summary>
|
||||||
|
public double Duration => HitObjects[^1].StartTime - HitObjects[0].StartTime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The interval in ms of each hit object in this <see cref="SameRhythmHitObjectGrouping"/>. This is only defined if there is
|
||||||
|
/// more than two hit objects in this <see cref="SameRhythmHitObjectGrouping"/>.
|
||||||
|
/// </summary>
|
||||||
|
public readonly double? HitObjectInterval;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ratio of <see cref="HitObjectInterval"/> between this and the previous <see cref="SameRhythmHitObjectGrouping"/>. In the
|
||||||
|
/// case where one or both of the <see cref="HitObjectInterval"/> is undefined, this will have a value of 1.
|
||||||
|
/// </summary>
|
||||||
|
public readonly double HitObjectIntervalRatio;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public double Interval { get; }
|
||||||
|
|
||||||
|
public SameRhythmHitObjectGrouping(SameRhythmHitObjectGrouping? previous, List<TaikoDifficultyHitObject> hitObjects)
|
||||||
|
{
|
||||||
|
Previous = previous;
|
||||||
|
HitObjects = hitObjects;
|
||||||
|
|
||||||
|
// Calculate the average interval between hitobjects, or null if there are fewer than two
|
||||||
|
HitObjectInterval = HitObjects.Count < 2 ? null : Duration / (HitObjects.Count - 1);
|
||||||
|
|
||||||
|
// Calculate the ratio between this group's interval and the previous group's interval
|
||||||
|
HitObjectIntervalRatio = Previous?.HitObjectInterval != null && HitObjectInterval != null
|
||||||
|
? HitObjectInterval.Value / Previous.HitObjectInterval.Value
|
||||||
|
: 1;
|
||||||
|
|
||||||
|
// Calculate the interval from the previous group's start time
|
||||||
|
Interval = Previous != null ? StartTime - Previous.StartTime : double.PositiveInfinity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,94 +0,0 @@
|
|||||||
// 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.
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a group of <see cref="TaikoDifficultyHitObject"/>s with no rhythm variation.
|
|
||||||
/// </summary>
|
|
||||||
public class SameRhythmHitObjects : SameRhythm<TaikoDifficultyHitObject>, IHasInterval
|
|
||||||
{
|
|
||||||
public TaikoDifficultyHitObject FirstHitObject => Children[0];
|
|
||||||
|
|
||||||
public SameRhythmHitObjects? Previous;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// <see cref="DifficultyHitObject.StartTime"/> of the first hit object.
|
|
||||||
/// </summary>
|
|
||||||
public double StartTime => Children[0].StartTime;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The interval between the first and final hit object within this group.
|
|
||||||
/// </summary>
|
|
||||||
public double Duration => Children[^1].StartTime - Children[0].StartTime;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The interval in ms of each hit object in this <see cref="SameRhythmHitObjects"/>. This is only defined if there is
|
|
||||||
/// more than two hit objects in this <see cref="SameRhythmHitObjects"/>.
|
|
||||||
/// </summary>
|
|
||||||
public double? HitObjectInterval;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The ratio of <see cref="HitObjectInterval"/> between this and the previous <see cref="SameRhythmHitObjects"/>. In the
|
|
||||||
/// case where one or both of the <see cref="HitObjectInterval"/> is undefined, this will have a value of 1.
|
|
||||||
/// </summary>
|
|
||||||
public double HitObjectIntervalRatio = 1;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The interval between the <see cref="StartTime"/> of this and the previous <see cref="SameRhythmHitObjects"/>.
|
|
||||||
/// </summary>
|
|
||||||
public double Interval { get; private set; } = double.PositiveInfinity;
|
|
||||||
|
|
||||||
public SameRhythmHitObjects(SameRhythmHitObjects? previous, List<TaikoDifficultyHitObject> data, ref int i)
|
|
||||||
: base(data, ref i, 5)
|
|
||||||
{
|
|
||||||
Previous = previous;
|
|
||||||
|
|
||||||
foreach (var hitObject in Children)
|
|
||||||
{
|
|
||||||
hitObject.Rhythm.SameRhythmHitObjects = this;
|
|
||||||
|
|
||||||
// Pass the HitObjectInterval to each child.
|
|
||||||
hitObject.HitObjectInterval = HitObjectInterval;
|
|
||||||
}
|
|
||||||
|
|
||||||
calculateIntervals();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<SameRhythmHitObjects> GroupHitObjects(List<TaikoDifficultyHitObject> data)
|
|
||||||
{
|
|
||||||
List<SameRhythmHitObjects> flatPatterns = new List<SameRhythmHitObjects>();
|
|
||||||
|
|
||||||
// Index does not need to be incremented, as it is handled within SameRhythm's constructor.
|
|
||||||
for (int i = 0; i < data.Count;)
|
|
||||||
{
|
|
||||||
SameRhythmHitObjects? previous = flatPatterns.Count > 0 ? flatPatterns[^1] : null;
|
|
||||||
flatPatterns.Add(new SameRhythmHitObjects(previous, data, ref i));
|
|
||||||
}
|
|
||||||
|
|
||||||
return flatPatterns;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void calculateIntervals()
|
|
||||||
{
|
|
||||||
// Calculate the average interval between hitobjects, or null if there are fewer than two.
|
|
||||||
HitObjectInterval = Children.Count < 2 ? null : (Children[^1].StartTime - Children[0].StartTime) / (Children.Count - 1);
|
|
||||||
|
|
||||||
// If both the current and previous intervals are available, calculate the ratio.
|
|
||||||
if (Previous?.HitObjectInterval != null && HitObjectInterval != null)
|
|
||||||
{
|
|
||||||
HitObjectIntervalRatio = HitObjectInterval.Value / Previous.HitObjectInterval.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Previous == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Interval = StartTime - Previous.StartTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,98 +0,0 @@
|
|||||||
// 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.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Stores rhythm data for a <see cref="TaikoDifficultyHitObject"/>.
|
|
||||||
/// </summary>
|
|
||||||
public class TaikoDifficultyHitObjectRhythm
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The group of hit objects with consistent rhythm that this object belongs to.
|
|
||||||
/// </summary>
|
|
||||||
public SameRhythmHitObjects? SameRhythmHitObjects;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The larger pattern of rhythm groups that this object is part of.
|
|
||||||
/// </summary>
|
|
||||||
public SamePatterns? SamePatterns;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The ratio of current <see cref="Rulesets.Difficulty.Preprocessing.DifficultyHitObject.DeltaTime"/>
|
|
||||||
/// to previous <see cref="Rulesets.Difficulty.Preprocessing.DifficultyHitObject.DeltaTime"/> for the rhythm change.
|
|
||||||
/// A <see cref="Ratio"/> above 1 indicates a slow-down; a <see cref="Ratio"/> below 1 indicates a speed-up.
|
|
||||||
/// </summary>
|
|
||||||
public readonly double Ratio;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// List of most common rhythm changes in taiko maps. Based on how each object's interval compares to the previous object.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// The general guidelines for the values are:
|
|
||||||
/// <list type="bullet">
|
|
||||||
/// <item>rhythm changes with ratio closer to 1 (that are <i>not</i> 1) are harder to play,</item>
|
|
||||||
/// <item>speeding up is <i>generally</i> harder than slowing down (with exceptions of rhythm changes requiring a hand switch).</item>
|
|
||||||
/// </list>
|
|
||||||
/// </remarks>
|
|
||||||
private static readonly TaikoDifficultyHitObjectRhythm[] common_rhythms =
|
|
||||||
{
|
|
||||||
new TaikoDifficultyHitObjectRhythm(1, 1),
|
|
||||||
new TaikoDifficultyHitObjectRhythm(2, 1),
|
|
||||||
new TaikoDifficultyHitObjectRhythm(1, 2),
|
|
||||||
new TaikoDifficultyHitObjectRhythm(3, 1),
|
|
||||||
new TaikoDifficultyHitObjectRhythm(1, 3),
|
|
||||||
new TaikoDifficultyHitObjectRhythm(3, 2),
|
|
||||||
new TaikoDifficultyHitObjectRhythm(2, 3),
|
|
||||||
new TaikoDifficultyHitObjectRhythm(5, 4),
|
|
||||||
new TaikoDifficultyHitObjectRhythm(4, 5)
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initialises a new instance of <see cref="TaikoDifficultyHitObjectRhythm"/>s,
|
|
||||||
/// calculating the closest rhythm change and its associated difficulty for the current hit object.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="current">The current <see cref="TaikoDifficultyHitObject"/> being processed.</param>
|
|
||||||
public TaikoDifficultyHitObjectRhythm(TaikoDifficultyHitObject current)
|
|
||||||
{
|
|
||||||
var previous = current.Previous(0);
|
|
||||||
|
|
||||||
if (previous == null)
|
|
||||||
{
|
|
||||||
Ratio = 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TaikoDifficultyHitObjectRhythm closestRhythm = getClosestRhythm(current.DeltaTime, previous.DeltaTime);
|
|
||||||
Ratio = closestRhythm.Ratio;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates an object representing a rhythm change.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="numerator">The numerator for <see cref="Ratio"/>.</param>
|
|
||||||
/// <param name="denominator">The denominator for <see cref="Ratio"/></param>
|
|
||||||
private TaikoDifficultyHitObjectRhythm(int numerator, int denominator)
|
|
||||||
{
|
|
||||||
Ratio = numerator / (double)denominator;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines the closest rhythm change from <see cref="common_rhythms"/> that matches the timing ratio
|
|
||||||
/// between the current and previous intervals.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="currentDeltaTime">The time difference between the current hit object and the previous one.</param>
|
|
||||||
/// <param name="previousDeltaTime">The time difference between the previous hit object and the one before it.</param>
|
|
||||||
/// <returns>The closest matching rhythm from <see cref="common_rhythms"/>.</returns>
|
|
||||||
private TaikoDifficultyHitObjectRhythm getClosestRhythm(double currentDeltaTime, double previousDeltaTime)
|
|
||||||
{
|
|
||||||
double ratio = currentDeltaTime / previousDeltaTime;
|
|
||||||
return common_rhythms.OrderBy(x => Math.Abs(x.Ratio - ratio)).First();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Stores rhythm data for a <see cref="TaikoDifficultyHitObject"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class TaikoRhythmData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The group of hit objects with consistent rhythm that this object belongs to.
|
||||||
|
/// </summary>
|
||||||
|
public SameRhythmHitObjectGrouping? SameRhythmGroupedHitObjects;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The larger pattern of rhythm groups that this object is part of.
|
||||||
|
/// </summary>
|
||||||
|
public SamePatternsGroupedHitObjects? SamePatternsGroupedHitObjects;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ratio of current <see cref="Rulesets.Difficulty.Preprocessing.DifficultyHitObject.DeltaTime"/>
|
||||||
|
/// to previous <see cref="Rulesets.Difficulty.Preprocessing.DifficultyHitObject.DeltaTime"/> for the rhythm change.
|
||||||
|
/// A <see cref="Ratio"/> above 1 indicates a slow-down; a <see cref="Ratio"/> below 1 indicates a speed-up.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is snapped to the closest matching <see cref="common_ratios"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public readonly double Ratio;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialises a new instance of <see cref="TaikoRhythmData"/>s,
|
||||||
|
/// calculating the closest rhythm change and its associated difficulty for the current hit object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="current">The current <see cref="TaikoDifficultyHitObject"/> being processed.</param>
|
||||||
|
public TaikoRhythmData(TaikoDifficultyHitObject current)
|
||||||
|
{
|
||||||
|
var previous = current.Previous(0);
|
||||||
|
|
||||||
|
if (previous == null)
|
||||||
|
{
|
||||||
|
Ratio = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
double actualRatio = current.DeltaTime / previous.DeltaTime;
|
||||||
|
double closestRatio = common_ratios.OrderBy(r => Math.Abs(r - actualRatio)).First();
|
||||||
|
|
||||||
|
Ratio = closestRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of most common rhythm changes in taiko maps. Based on how each object's interval compares to the previous object.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The general guidelines for the values are:
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>rhythm changes with ratio closer to 1 (that are <i>not</i> 1) are harder to play,</item>
|
||||||
|
/// <item>speeding up is <i>generally</i> harder than slowing down (with exceptions of rhythm changes requiring a hand switch).</item>
|
||||||
|
/// </list>
|
||||||
|
/// </remarks>
|
||||||
|
private static readonly double[] common_ratios = new[]
|
||||||
|
{
|
||||||
|
1.0 / 1,
|
||||||
|
2.0 / 1,
|
||||||
|
1.0 / 2,
|
||||||
|
3.0 / 1,
|
||||||
|
1.0 / 3,
|
||||||
|
3.0 / 2,
|
||||||
|
2.0 / 3,
|
||||||
|
5.0 / 4,
|
||||||
|
4.0 / 5
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data;
|
||||||
|
using osu.Game.Rulesets.Taiko.Difficulty.Utils;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm
|
||||||
|
{
|
||||||
|
public static class TaikoRhythmDifficultyPreprocessor
|
||||||
|
{
|
||||||
|
public static void ProcessAndAssign(List<TaikoDifficultyHitObject> hitObjects)
|
||||||
|
{
|
||||||
|
var rhythmGroups = createSameRhythmGroupedHitObjects(hitObjects);
|
||||||
|
|
||||||
|
foreach (var rhythmGroup in rhythmGroups)
|
||||||
|
{
|
||||||
|
foreach (var hitObject in rhythmGroup.HitObjects)
|
||||||
|
hitObject.RhythmData.SameRhythmGroupedHitObjects = rhythmGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
var patternGroups = createSamePatternGroupedHitObjects(rhythmGroups);
|
||||||
|
|
||||||
|
foreach (var patternGroup in patternGroups)
|
||||||
|
{
|
||||||
|
foreach (var hitObject in patternGroup.AllHitObjects)
|
||||||
|
hitObject.RhythmData.SamePatternsGroupedHitObjects = patternGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<SameRhythmHitObjectGrouping> createSameRhythmGroupedHitObjects(List<TaikoDifficultyHitObject> hitObjects)
|
||||||
|
{
|
||||||
|
var rhythmGroups = new List<SameRhythmHitObjectGrouping>();
|
||||||
|
|
||||||
|
foreach (var grouped in IntervalGroupingUtils.GroupByInterval(hitObjects))
|
||||||
|
rhythmGroups.Add(new SameRhythmHitObjectGrouping(rhythmGroups.LastOrDefault(), grouped));
|
||||||
|
|
||||||
|
return rhythmGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<SamePatternsGroupedHitObjects> createSamePatternGroupedHitObjects(List<SameRhythmHitObjectGrouping> rhythmGroups)
|
||||||
|
{
|
||||||
|
var patternGroups = new List<SamePatternsGroupedHitObjects>();
|
||||||
|
|
||||||
|
foreach (var grouped in IntervalGroupingUtils.GroupByInterval(rhythmGroups))
|
||||||
|
patternGroups.Add(new SamePatternsGroupedHitObjects(patternGroups.LastOrDefault(), grouped));
|
||||||
|
|
||||||
|
return patternGroups;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,11 +3,14 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Taiko.Difficulty.Evaluators;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour;
|
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour;
|
||||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm;
|
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm;
|
||||||
|
using osu.Game.Rulesets.Taiko.Difficulty.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
||||||
{
|
{
|
||||||
@ -37,61 +40,52 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
|||||||
public readonly int NoteIndex;
|
public readonly int NoteIndex;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The rhythm required to hit this hit object.
|
/// Rhythm data used by <see cref="RhythmEvaluator"/>.
|
||||||
|
/// This is populated via <see cref="TaikoRhythmDifficultyPreprocessor"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly TaikoDifficultyHitObjectRhythm Rhythm;
|
public readonly TaikoRhythmData RhythmData;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The interval between this hit object and the surrounding hit objects in its rhythm group.
|
/// Colour data used by <see cref="ColourEvaluator"/> and <see cref="StaminaEvaluator"/>.
|
||||||
|
/// This is populated via <see cref="TaikoColourDifficultyPreprocessor"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double? HitObjectInterval { get; set; }
|
public readonly TaikoColourData ColourData;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Colour data for this hit object. This is used by colour evaluator to calculate colour difficulty, but can be used
|
|
||||||
/// by other skills in the future.
|
|
||||||
/// </summary>
|
|
||||||
public readonly TaikoDifficultyHitObjectColour Colour;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The adjusted BPM of this hit object, based on its slider velocity and scroll speed.
|
/// The adjusted BPM of this hit object, based on its slider velocity and scroll speed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double EffectiveBPM;
|
public double EffectiveBPM;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The current slider velocity of this hit object.
|
|
||||||
/// </summary>
|
|
||||||
public double CurrentSliderVelocity;
|
|
||||||
|
|
||||||
public double Interval => DeltaTime;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new difficulty hit object.
|
/// Creates a new difficulty hit object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="hitObject">The gameplay <see cref="HitObject"/> associated with this difficulty object.</param>
|
/// <param name="hitObject">The gameplay <see cref="HitObject"/> associated with this difficulty object.</param>
|
||||||
/// <param name="lastObject">The gameplay <see cref="HitObject"/> preceding <paramref name="hitObject"/>.</param>
|
/// <param name="lastObject">The gameplay <see cref="HitObject"/> preceding <paramref name="hitObject"/>.</param>
|
||||||
/// <param name="lastLastObject">The gameplay <see cref="HitObject"/> preceding <paramref name="lastObject"/>.</param>
|
|
||||||
/// <param name="clockRate">The rate of the gameplay clock. Modified by speed-changing mods.</param>
|
/// <param name="clockRate">The rate of the gameplay clock. Modified by speed-changing mods.</param>
|
||||||
/// <param name="objects">The list of all <see cref="DifficultyHitObject"/>s in the current beatmap.</param>
|
/// <param name="objects">The list of all <see cref="DifficultyHitObject"/>s in the current beatmap.</param>
|
||||||
/// <param name="centreHitObjects">The list of centre (don) <see cref="DifficultyHitObject"/>s in the current beatmap.</param>
|
/// <param name="centreHitObjects">The list of centre (don) <see cref="DifficultyHitObject"/>s in the current beatmap.</param>
|
||||||
/// <param name="rimHitObjects">The list of rim (kat) <see cref="DifficultyHitObject"/>s in the current beatmap.</param>
|
/// <param name="rimHitObjects">The list of rim (kat) <see cref="DifficultyHitObject"/>s in the current beatmap.</param>
|
||||||
/// <param name="noteObjects">The list of <see cref="DifficultyHitObject"/>s that is a hit (i.e. not a drumroll or swell) in the current beatmap.</param>
|
/// <param name="noteObjects">The list of <see cref="DifficultyHitObject"/>s that is a hit (i.e. not a drumroll or swell) in the current beatmap.</param>
|
||||||
/// <param name="index">The position of this <see cref="DifficultyHitObject"/> in the <paramref name="objects"/> list.</param>
|
/// <param name="index">The position of this <see cref="DifficultyHitObject"/> in the <paramref name="objects"/> list.</param>
|
||||||
public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, HitObject lastLastObject, double clockRate,
|
/// <param name="controlPointInfo">The control point info of the beatmap.</param>
|
||||||
|
/// <param name="globalSliderVelocity">The global slider velocity of the beatmap.</param>
|
||||||
|
public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate,
|
||||||
List<DifficultyHitObject> objects,
|
List<DifficultyHitObject> objects,
|
||||||
List<TaikoDifficultyHitObject> centreHitObjects,
|
List<TaikoDifficultyHitObject> centreHitObjects,
|
||||||
List<TaikoDifficultyHitObject> rimHitObjects,
|
List<TaikoDifficultyHitObject> rimHitObjects,
|
||||||
List<TaikoDifficultyHitObject> noteObjects, int index)
|
List<TaikoDifficultyHitObject> noteObjects, int index,
|
||||||
|
ControlPointInfo controlPointInfo,
|
||||||
|
double globalSliderVelocity)
|
||||||
: base(hitObject, lastObject, clockRate, objects, index)
|
: base(hitObject, lastObject, clockRate, objects, index)
|
||||||
{
|
{
|
||||||
noteDifficultyHitObjects = noteObjects;
|
noteDifficultyHitObjects = noteObjects;
|
||||||
|
|
||||||
// Create the Colour object, its properties should be filled in by TaikoDifficultyPreprocessor
|
ColourData = new TaikoColourData();
|
||||||
Colour = new TaikoDifficultyHitObjectColour();
|
RhythmData = new TaikoRhythmData(this);
|
||||||
|
|
||||||
// Create a Rhythm object, its properties are filled in by TaikoDifficultyHitObjectRhythm
|
if (hitObject is Hit hit)
|
||||||
Rhythm = new TaikoDifficultyHitObjectRhythm(this);
|
{
|
||||||
|
switch (hit.Type)
|
||||||
switch ((hitObject as Hit)?.Type)
|
|
||||||
{
|
{
|
||||||
case HitType.Centre:
|
case HitType.Centre:
|
||||||
MonoIndex = centreHitObjects.Count;
|
MonoIndex = centreHitObjects.Count;
|
||||||
@ -106,11 +100,29 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hitObject is Hit)
|
|
||||||
{
|
|
||||||
NoteIndex = noteObjects.Count;
|
NoteIndex = noteObjects.Count;
|
||||||
noteObjects.Add(this);
|
noteObjects.Add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Using `hitObject.StartTime` causes floating point error differences
|
||||||
|
double normalisedStartTime = StartTime * clockRate;
|
||||||
|
|
||||||
|
// Retrieve the timing point at the note's start time
|
||||||
|
TimingControlPoint currentControlPoint = controlPointInfo.TimingPointAt(normalisedStartTime);
|
||||||
|
|
||||||
|
// Calculate the slider velocity at the note's start time.
|
||||||
|
double currentSliderVelocity = calculateSliderVelocity(controlPointInfo, globalSliderVelocity, normalisedStartTime, clockRate);
|
||||||
|
|
||||||
|
EffectiveBPM = currentControlPoint.BPM * currentSliderVelocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the slider velocity based on control point info and clock rate.
|
||||||
|
/// </summary>
|
||||||
|
private static double calculateSliderVelocity(ControlPointInfo controlPointInfo, double globalSliderVelocity, double startTime, double clockRate)
|
||||||
|
{
|
||||||
|
var activeEffectControlPoint = controlPointInfo.EffectPointAt(startTime);
|
||||||
|
return globalSliderVelocity * (activeEffectControlPoint.ScrollSpeed) * clockRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TaikoDifficultyHitObject? PreviousMono(int backwardsIndex) => monoDifficultyHitObjects?.ElementAtOrDefault(MonoIndex - (backwardsIndex + 1));
|
public TaikoDifficultyHitObject? PreviousMono(int backwardsIndex) => monoDifficultyHitObjects?.ElementAtOrDefault(MonoIndex - (backwardsIndex + 1));
|
||||||
@ -120,5 +132,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
|||||||
public TaikoDifficultyHitObject? PreviousNote(int backwardsIndex) => noteDifficultyHitObjects.ElementAtOrDefault(NoteIndex - (backwardsIndex + 1));
|
public TaikoDifficultyHitObject? PreviousNote(int backwardsIndex) => noteDifficultyHitObjects.ElementAtOrDefault(NoteIndex - (backwardsIndex + 1));
|
||||||
|
|
||||||
public TaikoDifficultyHitObject? NextNote(int forwardsIndex) => noteDifficultyHitObjects.ElementAtOrDefault(NoteIndex + (forwardsIndex + 1));
|
public TaikoDifficultyHitObject? NextNote(int forwardsIndex) => noteDifficultyHitObjects.ElementAtOrDefault(NoteIndex + (forwardsIndex + 1));
|
||||||
|
|
||||||
|
public double Interval => DeltaTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
|||||||
}
|
}
|
||||||
|
|
||||||
var taikoObject = (TaikoDifficultyHitObject)current;
|
var taikoObject = (TaikoDifficultyHitObject)current;
|
||||||
int index = taikoObject.Colour.MonoStreak?.HitObjects.IndexOf(taikoObject) ?? 0;
|
int index = taikoObject.ColourData.MonoStreak?.HitObjects.IndexOf(taikoObject) ?? 0;
|
||||||
|
|
||||||
currentStrain *= DifficultyCalculationUtils.Logistic(index, 4, -1 / 25.0, 0.5) + 0.5;
|
currentStrain *= DifficultyCalculationUtils.Logistic(index, 4, -1 / 25.0, 0.5) + 0.5;
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
|||||||
|
|
||||||
// Safely prevents previous strains from shifting as new notes are added.
|
// Safely prevents previous strains from shifting as new notes are added.
|
||||||
var currentObject = current as TaikoDifficultyHitObject;
|
var currentObject = current as TaikoDifficultyHitObject;
|
||||||
int index = currentObject?.Colour.MonoStreak?.HitObjects.IndexOf(currentObject) ?? 0;
|
int index = currentObject?.ColourData.MonoStreak?.HitObjects.IndexOf(currentObject) ?? 0;
|
||||||
|
|
||||||
double monolengthBonus = isConvert ? 1 : 1 + Math.Min(Math.Max((index - 5) / 50.0, 0), 0.30);
|
double monolengthBonus = isConvert ? 1 : 1 + Math.Min(Math.Max((index - 5) / 50.0, 0), 0.30);
|
||||||
|
|
||||||
|
@ -13,8 +13,7 @@ using osu.Game.Rulesets.Mods;
|
|||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour;
|
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour;
|
||||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Reading;
|
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm;
|
||||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data;
|
|
||||||
using osu.Game.Rulesets.Taiko.Difficulty.Skills;
|
using osu.Game.Rulesets.Taiko.Difficulty.Skills;
|
||||||
using osu.Game.Rulesets.Taiko.Mods;
|
using osu.Game.Rulesets.Taiko.Mods;
|
||||||
using osu.Game.Rulesets.Taiko.Scoring;
|
using osu.Game.Rulesets.Taiko.Scoring;
|
||||||
@ -72,7 +71,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
var centreObjects = new List<TaikoDifficultyHitObject>();
|
var centreObjects = new List<TaikoDifficultyHitObject>();
|
||||||
var rimObjects = new List<TaikoDifficultyHitObject>();
|
var rimObjects = new List<TaikoDifficultyHitObject>();
|
||||||
var noteObjects = new List<TaikoDifficultyHitObject>();
|
var noteObjects = new List<TaikoDifficultyHitObject>();
|
||||||
EffectiveBPMPreprocessor bpmLoader = new EffectiveBPMPreprocessor(beatmap, noteObjects);
|
|
||||||
|
|
||||||
// Generate TaikoDifficultyHitObjects from the beatmap's hit objects.
|
// Generate TaikoDifficultyHitObjects from the beatmap's hit objects.
|
||||||
for (int i = 2; i < beatmap.HitObjects.Count; i++)
|
for (int i = 2; i < beatmap.HitObjects.Count; i++)
|
||||||
@ -80,21 +78,19 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
difficultyHitObjects.Add(new TaikoDifficultyHitObject(
|
difficultyHitObjects.Add(new TaikoDifficultyHitObject(
|
||||||
beatmap.HitObjects[i],
|
beatmap.HitObjects[i],
|
||||||
beatmap.HitObjects[i - 1],
|
beatmap.HitObjects[i - 1],
|
||||||
beatmap.HitObjects[i - 2],
|
|
||||||
clockRate,
|
clockRate,
|
||||||
difficultyHitObjects,
|
difficultyHitObjects,
|
||||||
centreObjects,
|
centreObjects,
|
||||||
rimObjects,
|
rimObjects,
|
||||||
noteObjects,
|
noteObjects,
|
||||||
difficultyHitObjects.Count
|
difficultyHitObjects.Count,
|
||||||
|
beatmap.ControlPointInfo,
|
||||||
|
beatmap.Difficulty.SliderMultiplier
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
var groupedHitObjects = SameRhythmHitObjects.GroupHitObjects(noteObjects);
|
|
||||||
|
|
||||||
TaikoColourDifficultyPreprocessor.ProcessAndAssign(difficultyHitObjects);
|
TaikoColourDifficultyPreprocessor.ProcessAndAssign(difficultyHitObjects);
|
||||||
SamePatterns.GroupPatterns(groupedHitObjects);
|
TaikoRhythmDifficultyPreprocessor.ProcessAndAssign(noteObjects);
|
||||||
bpmLoader.ProcessEffectiveBPM(beatmap.ControlPointInfo, clockRate);
|
|
||||||
|
|
||||||
return difficultyHitObjects;
|
return difficultyHitObjects;
|
||||||
}
|
}
|
||||||
@ -204,9 +200,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
/// Applies a final re-scaling of the star rating.
|
/// Applies a final re-scaling of the star rating.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sr">The raw star rating value before re-scaling.</param>
|
/// <param name="sr">The raw star rating value before re-scaling.</param>
|
||||||
private double rescale(double sr)
|
private static double rescale(double sr)
|
||||||
{
|
{
|
||||||
if (sr < 0) return sr;
|
if (sr < 0)
|
||||||
|
return sr;
|
||||||
|
|
||||||
return 10.43 * Math.Log(sr / 8 + 1);
|
return 10.43 * Math.Log(sr / 8 + 1);
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm
|
namespace osu.Game.Rulesets.Taiko.Difficulty.Utils
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The interface for hitobjects that provide an interval value.
|
/// The interface for objects that provide an interval value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IHasInterval
|
public interface IHasInterval
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The interval – ie delta time – between this object and a known previous object.
|
||||||
|
/// </summary>
|
||||||
double Interval { get; }
|
double Interval { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Difficulty.Utils
|
||||||
|
{
|
||||||
|
public static class IntervalGroupingUtils
|
||||||
|
{
|
||||||
|
public static List<List<T>> GroupByInterval<T>(IReadOnlyList<T> objects) where T : IHasInterval
|
||||||
|
{
|
||||||
|
var groups = new List<List<T>>();
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (i < objects.Count)
|
||||||
|
groups.Add(createNextGroup(objects, ref i));
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<T> createNextGroup<T>(IReadOnlyList<T> objects, ref int i) where T : IHasInterval
|
||||||
|
{
|
||||||
|
const double margin_of_error = 5;
|
||||||
|
|
||||||
|
var groupedObjects = new List<T> { objects[i] };
|
||||||
|
i++;
|
||||||
|
|
||||||
|
for (; i < objects.Count - 1; i++)
|
||||||
|
{
|
||||||
|
if (!Precision.AlmostEquals(objects[i].Interval, objects[i + 1].Interval, margin_of_error))
|
||||||
|
{
|
||||||
|
// When an interval change occurs, include the object with the differing interval in the case it increased
|
||||||
|
// See https://github.com/ppy/osu/pull/31636#discussion_r1942368372 for rationale.
|
||||||
|
if (objects[i + 1].Interval > objects[i].Interval + margin_of_error)
|
||||||
|
{
|
||||||
|
groupedObjects.Add(objects[i]);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupedObjects;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No interval change occurred
|
||||||
|
groupedObjects.Add(objects[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the last two objects in the object form a "flat" rhythm pattern within the specified margin of error.
|
||||||
|
// If true, add the current object to the group and increment the index to process the next object.
|
||||||
|
if (objects.Count > 2 && i < objects.Count && Precision.AlmostEquals(objects[^1].Interval, objects[^2].Interval, margin_of_error))
|
||||||
|
{
|
||||||
|
groupedObjects.Add(objects[i]);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupedObjects;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user