mirror of
https://github.com/ppy/osu.git
synced 2026-05-17 17:33:02 +08:00
Merge pull request #34181 from Hiviexd/verify/check-lowest-diff-drain
Add verify check for lowest diff drain/play time requirements
This commit is contained in:
@@ -18,6 +18,9 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
new CheckBananaShowerGap(),
|
||||
new CheckConcurrentObjects(),
|
||||
|
||||
// Spread
|
||||
new CheckCatchLowestDiffDrainTime(),
|
||||
|
||||
// Settings
|
||||
new CheckCatchAbnormalDifficultySettings(),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// 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;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit.Checks;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Edit.Checks
|
||||
{
|
||||
public class CheckCatchLowestDiffDrainTime : CheckLowestDiffDrainTime
|
||||
{
|
||||
protected override IEnumerable<(DifficultyRating rating, double thresholdMs, string name)> GetThresholds()
|
||||
{
|
||||
// See lowest difficulty requirements in https://osu.ppy.sh/wiki/en/Ranking_criteria/osu%21catch#general
|
||||
yield return (DifficultyRating.Hard, new TimeSpan(0, 2, 30).TotalMilliseconds, "Platter");
|
||||
yield return (DifficultyRating.Insane, new TimeSpan(0, 3, 15).TotalMilliseconds, "Rain");
|
||||
yield return (DifficultyRating.Expert, new TimeSpan(0, 4, 0).TotalMilliseconds, "Overdose");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// 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;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit.Checks;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Edit.Checks
|
||||
{
|
||||
public class CheckManiaLowestDiffDrainTime : CheckLowestDiffDrainTime
|
||||
{
|
||||
protected override IEnumerable<(DifficultyRating rating, double thresholdMs, string name)> GetThresholds()
|
||||
{
|
||||
// See lowest difficulty requirements in https://osu.ppy.sh/wiki/en/Ranking_criteria/osu%21mania#rules
|
||||
yield return (DifficultyRating.Hard, new TimeSpan(0, 2, 30).TotalMilliseconds, "Hard");
|
||||
yield return (DifficultyRating.Insane, new TimeSpan(0, 2, 45).TotalMilliseconds, "Insane");
|
||||
yield return (DifficultyRating.Expert, new TimeSpan(0, 3, 30).TotalMilliseconds, "Expert");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,9 @@ namespace osu.Game.Rulesets.Mania.Edit
|
||||
// Compose
|
||||
new CheckManiaConcurrentObjects(),
|
||||
|
||||
// Spread
|
||||
new CheckManiaLowestDiffDrainTime(),
|
||||
|
||||
// Settings
|
||||
new CheckKeyCount(),
|
||||
new CheckManiaAbnormalDifficultySettings(),
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// 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;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit.Checks;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit.Checks
|
||||
{
|
||||
public class CheckOsuLowestDiffDrainTime : CheckLowestDiffDrainTime
|
||||
{
|
||||
protected override IEnumerable<(DifficultyRating rating, double thresholdMs, string name)> GetThresholds()
|
||||
{
|
||||
// See lowest difficulty requirements in https://osu.ppy.sh/wiki/en/Ranking_criteria/osu%21#general
|
||||
yield return (DifficultyRating.Hard, new TimeSpan(0, 3, 30).TotalMilliseconds, "Hard");
|
||||
yield return (DifficultyRating.Insane, new TimeSpan(0, 4, 15).TotalMilliseconds, "Insane");
|
||||
yield return (DifficultyRating.Expert, new TimeSpan(0, 5, 0).TotalMilliseconds, "Expert");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
new CheckTimeDistanceEquality(),
|
||||
new CheckLowDiffOverlaps(),
|
||||
new CheckTooShortSliders(),
|
||||
new CheckOsuLowestDiffDrainTime(),
|
||||
|
||||
// Settings
|
||||
new CheckOsuAbnormalDifficultySettings(),
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// 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;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit.Checks;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Edit.Checks
|
||||
{
|
||||
public class CheckTaikoLowestDiffDrainTime : CheckLowestDiffDrainTime
|
||||
{
|
||||
protected override IEnumerable<(DifficultyRating rating, double thresholdMs, string name)> GetThresholds()
|
||||
{
|
||||
// See lowest difficulty requirements in https://osu.ppy.sh/wiki/en/Ranking_criteria/osu%21taiko#general
|
||||
yield return (DifficultyRating.Hard, new TimeSpan(0, 3, 30).TotalMilliseconds, "Muzukashii");
|
||||
yield return (DifficultyRating.Insane, new TimeSpan(0, 4, 15).TotalMilliseconds, "Oni");
|
||||
yield return (DifficultyRating.Expert, new TimeSpan(0, 5, 0).TotalMilliseconds, "Inner Oni");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,9 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
||||
// Compose
|
||||
new CheckConcurrentObjects(),
|
||||
|
||||
// Spread
|
||||
new CheckTaikoLowestDiffDrainTime(),
|
||||
|
||||
// Settings
|
||||
new CheckTaikoAbnormalDifficultySettings(),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
// 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;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Checks;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Tests.Editing.Checks
|
||||
{
|
||||
[TestFixture]
|
||||
public class CheckLowestDiffDrainTimeTest
|
||||
{
|
||||
private TestCheckLowestDiffDrainTime check = null!;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
check = new TestCheckLowestDiffDrainTime();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSingleDifficultyMeetsRequirement()
|
||||
{
|
||||
var beatmap = createBeatmapWithDrainTime(4 * 60 * 1000, 3.5, "Hard"); // 4 minutes
|
||||
assertOk(beatmap);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSingleDifficultyTooShort()
|
||||
{
|
||||
var beatmap = createBeatmapWithDrainTime(2 * 60 * 1000, 3.5, "Hard"); // 2 minutes - too short for Hard
|
||||
assertTooShort(beatmap);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHardDifficultyAtThreshold()
|
||||
{
|
||||
var beatmap = createBeatmapWithDrainTime((3 * 60 + 30) * 1000, 3.5, "Hard"); // Exactly 3:30
|
||||
assertOk(beatmap);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHardDifficultyJustUnderThreshold()
|
||||
{
|
||||
var beatmap = createBeatmapWithDrainTime((3 * 60 + 29) * 1000, 3.5, "Hard"); // 3:29 - just under threshold
|
||||
assertTooShort(beatmap);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInsaneDifficultyAtThreshold()
|
||||
{
|
||||
var beatmap = createBeatmapWithDrainTime((4 * 60 + 15) * 1000, 4.5, "Insane"); // Exactly 4:15
|
||||
assertOk(beatmap);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInsaneDifficultyTooShort()
|
||||
{
|
||||
var beatmap = createBeatmapWithDrainTime(4 * 60 * 1000, 4.5, "Insane"); // 4:00 - too short for Insane
|
||||
assertTooShort(beatmap);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExpertDifficultyAtThreshold()
|
||||
{
|
||||
var beatmap = createBeatmapWithDrainTime(5 * 60 * 1000, 5.5, "Expert"); // Exactly 5:00
|
||||
assertOk(beatmap);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExpertDifficultyTooShort()
|
||||
{
|
||||
var beatmap = createBeatmapWithDrainTime((4 * 60 + 30) * 1000, 5.5, "Expert"); // 4:30 - too short for Expert
|
||||
assertTooShort(beatmap);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEasyDifficultyMeetsRequirement()
|
||||
{
|
||||
var beatmap = createBeatmapWithDrainTime(2 * 60 * 1000, 1.5, "Easy"); // 2 minutes - should be ok for Easy
|
||||
assertOk(beatmap);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNormalDifficultyMeetsRequirement()
|
||||
{
|
||||
var beatmap = createBeatmapWithDrainTime(2 * 60 * 1000, 2.5, "Normal"); // 2 minutes - should be ok for Normal
|
||||
assertOk(beatmap);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleDifficultiesMeetsRequirement()
|
||||
{
|
||||
var difficulties = new List<IBeatmap>
|
||||
{
|
||||
createBeatmapWithDrainTime((3 * 60 + 30) * 1000, 3.5, "Hard"), // Hard - lowest difficulty, 3:30
|
||||
createBeatmapWithDrainTime((3 * 60 + 30) * 1000, 4.5, "Insane"),
|
||||
createBeatmapWithDrainTime((3 * 60 + 30) * 1000, 5.5, "Expert")
|
||||
};
|
||||
|
||||
// All should be ok because lowest difficulty is Hard and drain time meets Hard requirement
|
||||
assertOkWithMultipleDifficulties(difficulties[0], difficulties);
|
||||
assertOkWithMultipleDifficulties(difficulties[1], difficulties);
|
||||
assertOkWithMultipleDifficulties(difficulties[2], difficulties);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleDifficultiesTooShort()
|
||||
{
|
||||
var difficulties = new List<IBeatmap>
|
||||
{
|
||||
createBeatmapWithDrainTime(4 * 60 * 1000, 4.5, "Insane"), // Insane - lowest difficulty, 4:00
|
||||
createBeatmapWithDrainTime(4 * 60 * 1000, 5.5, "Expert") // Same drain time
|
||||
};
|
||||
|
||||
// Should be too short because lowest difficulty is Insane and requires 4:15
|
||||
assertTooShortWithMultipleDifficulties(difficulties[0], difficulties);
|
||||
assertTooShortWithMultipleDifficulties(difficulties[1], difficulties);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlayTimeVsDrainTimeNotHighestDifficulty()
|
||||
{
|
||||
var expertBeatmap = createBeatmapWithPlayTime(5 * 60 * 1000, 5.5, "Expert"); // 5:00 play time
|
||||
expertBeatmap.Breaks.Add(new BreakPeriod(60000, 100000)); // 40-second break
|
||||
|
||||
var difficulties = new List<IBeatmap>
|
||||
{
|
||||
expertBeatmap, // Expert - 5:00 play, 4:20 drain
|
||||
createBeatmapWithPlayTime(5 * 60 * 1000, 6.5, "ExpertPlus") // ExpertPlus - highest difficulty
|
||||
};
|
||||
|
||||
// The Expert difficulty (not highest) should use play time (5:00) and pass the Expert requirement
|
||||
assertOkWithMultipleDifficulties(difficulties[0], difficulties);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlayTimeVsDrainTimeHighestDifficulty()
|
||||
{
|
||||
var expertBeatmap = createBeatmapWithPlayTime(5 * 60 * 1000, 5.5, "Expert"); // 5:00 play time
|
||||
expertBeatmap.Breaks.Add(new BreakPeriod(60000, 100000)); // 40-second break
|
||||
|
||||
// As the highest difficulty with breaks > 30s, it should use drain time and fail
|
||||
assertTooShort(expertBeatmap);
|
||||
}
|
||||
|
||||
private IBeatmap createBeatmapWithDrainTime(double drainTimeMs, double starRating = 3.5, string difficultyName = "Default")
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
StarRating = starRating,
|
||||
DifficultyName = difficultyName,
|
||||
Ruleset = new OsuRuleset().RulesetInfo
|
||||
},
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HitObject { StartTime = 0 },
|
||||
new HitObject { StartTime = drainTimeMs } // Last object at drain time
|
||||
}
|
||||
};
|
||||
|
||||
return beatmap;
|
||||
}
|
||||
|
||||
private IBeatmap createBeatmapWithPlayTime(double playTimeMs, double starRating = 3.5, string difficultyName = "Default")
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject>
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
StarRating = starRating,
|
||||
DifficultyName = difficultyName,
|
||||
Ruleset = new OsuRuleset().RulesetInfo
|
||||
},
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HitObject { StartTime = 0 },
|
||||
new HitObject { StartTime = playTimeMs } // Last object at play time
|
||||
}
|
||||
};
|
||||
|
||||
return beatmap;
|
||||
}
|
||||
|
||||
private void assertOk(IBeatmap beatmap)
|
||||
{
|
||||
var difficultyRating = StarDifficulty.GetDifficultyRating(beatmap.BeatmapInfo.StarRating);
|
||||
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), difficultyRating);
|
||||
|
||||
Assert.That(check.Run(context), Is.Empty);
|
||||
}
|
||||
|
||||
private void assertTooShort(IBeatmap beatmap)
|
||||
{
|
||||
var difficultyRating = StarDifficulty.GetDifficultyRating(beatmap.BeatmapInfo.StarRating);
|
||||
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap), difficultyRating);
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.First().Template is CheckLowestDiffDrainTime.IssueTemplateTooShort);
|
||||
}
|
||||
|
||||
private void assertOkWithMultipleDifficulties(IBeatmap currentBeatmap, IEnumerable<IBeatmap> allDifficulties)
|
||||
{
|
||||
var context = createContextWithMultipleDifficulties(currentBeatmap, allDifficulties);
|
||||
|
||||
Assert.That(check.Run(context), Is.Empty);
|
||||
}
|
||||
|
||||
private void assertTooShortWithMultipleDifficulties(IBeatmap currentBeatmap, IEnumerable<IBeatmap> allDifficulties)
|
||||
{
|
||||
var context = createContextWithMultipleDifficulties(currentBeatmap, allDifficulties);
|
||||
var issues = check.Run(context).ToList();
|
||||
|
||||
Assert.That(issues, Has.Count.EqualTo(1));
|
||||
Assert.That(issues.First().Template is CheckLowestDiffDrainTime.IssueTemplateTooShort);
|
||||
}
|
||||
|
||||
private BeatmapVerifierContext createContextWithMultipleDifficulties(IBeatmap currentBeatmap, IEnumerable<IBeatmap> allDifficulties)
|
||||
{
|
||||
var beatmapSet = new BeatmapSetInfo();
|
||||
var beatmapInfos = allDifficulties.Select(d => d.BeatmapInfo).ToList();
|
||||
|
||||
// Set up the beatmapset with all difficulties
|
||||
beatmapSet.Beatmaps.AddRange(beatmapInfos);
|
||||
currentBeatmap.BeatmapInfo.BeatmapSet = beatmapSet;
|
||||
|
||||
// Create a resolver that returns the appropriate working beatmap for each difficulty
|
||||
var difficultyDict = allDifficulties.ToDictionary(d => d.BeatmapInfo, d => new TestWorkingBeatmap(d));
|
||||
|
||||
// Use the current beatmap's star rating to determine its difficulty rating
|
||||
var currentDifficultyRating = StarDifficulty.GetDifficultyRating(currentBeatmap.BeatmapInfo.StarRating);
|
||||
|
||||
return new BeatmapVerifierContext(
|
||||
currentBeatmap,
|
||||
new TestWorkingBeatmap(currentBeatmap),
|
||||
currentDifficultyRating,
|
||||
beatmapInfo => difficultyDict.TryGetValue(beatmapInfo, out var workingBeatmap) ? workingBeatmap.Beatmap : null
|
||||
);
|
||||
}
|
||||
|
||||
private class TestCheckLowestDiffDrainTime : CheckLowestDiffDrainTime
|
||||
{
|
||||
protected override IEnumerable<(DifficultyRating rating, double thresholdMs, string name)> GetThresholds()
|
||||
{
|
||||
// Same thresholds as `CheckOsuLowestDiffDrainTime` for testing
|
||||
yield return (DifficultyRating.Hard, new TimeSpan(0, 3, 30).TotalMilliseconds, "Hard");
|
||||
yield return (DifficultyRating.Insane, new TimeSpan(0, 4, 15).TotalMilliseconds, "Insane");
|
||||
yield return (DifficultyRating.Expert, new TimeSpan(0, 5, 0).TotalMilliseconds, "Expert");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
// 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;
|
||||
using osu.Game.Beatmaps;
|
||||
|
||||
namespace osu.Game.Rulesets.Edit
|
||||
@@ -26,11 +28,44 @@ namespace osu.Game.Rulesets.Edit
|
||||
/// </summary>
|
||||
public DifficultyRating InterpretedDifficulty;
|
||||
|
||||
public BeatmapVerifierContext(IBeatmap beatmap, IWorkingBeatmap workingBeatmap, DifficultyRating difficultyRating = DifficultyRating.ExpertPlus)
|
||||
/// <summary>
|
||||
/// All beatmap difficulties in the same beatmapset, including the current beatmap.
|
||||
/// </summary>
|
||||
public readonly IReadOnlyList<IBeatmap> BeatmapsetDifficulties;
|
||||
|
||||
// TODO: Refactor this to have a simple constructor that only stores data and move the beatmap resolution logic to a static factory method.
|
||||
public BeatmapVerifierContext(IBeatmap beatmap, IWorkingBeatmap workingBeatmap, DifficultyRating difficultyRating = DifficultyRating.ExpertPlus, Func<BeatmapInfo, IBeatmap?>? beatmapResolver = null)
|
||||
{
|
||||
Beatmap = beatmap;
|
||||
WorkingBeatmap = workingBeatmap;
|
||||
InterpretedDifficulty = difficultyRating;
|
||||
|
||||
var beatmapSet = beatmap.BeatmapInfo.BeatmapSet;
|
||||
|
||||
if (beatmapSet?.Beatmaps == null)
|
||||
{
|
||||
BeatmapsetDifficulties = new[] { beatmap };
|
||||
return;
|
||||
}
|
||||
|
||||
var difficulties = new List<IBeatmap>();
|
||||
|
||||
foreach (var beatmapInfo in beatmapSet.Beatmaps)
|
||||
{
|
||||
// Use the current beatmap if it matches this BeatmapInfo
|
||||
if (beatmapInfo.Equals(beatmap.BeatmapInfo))
|
||||
{
|
||||
difficulties.Add(beatmap);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to resolve other difficulties using the provided resolver
|
||||
var resolvedBeatmap = beatmapResolver?.Invoke(beatmapInfo);
|
||||
if (resolvedBeatmap != null)
|
||||
difficulties.Add(resolvedBeatmap);
|
||||
}
|
||||
|
||||
BeatmapsetDifficulties = difficulties;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
// 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;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||
|
||||
namespace osu.Game.Rulesets.Edit.Checks
|
||||
{
|
||||
public abstract class CheckLowestDiffDrainTime : ICheck
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the minimum drain time thresholds for different difficulty ratings.
|
||||
/// </summary>
|
||||
protected abstract IEnumerable<(DifficultyRating rating, double thresholdMs, string name)> GetThresholds();
|
||||
|
||||
private const double break_time_leniency = 30 * 1000;
|
||||
|
||||
public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Spread, "Lowest difficulty too difficult for the given drain/play time(s)");
|
||||
|
||||
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||
{
|
||||
new IssueTemplateTooShort(this)
|
||||
};
|
||||
|
||||
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||
{
|
||||
IReadOnlyList<IBeatmap> difficulties = context.BeatmapsetDifficulties
|
||||
.Where(d => d.BeatmapInfo.Ruleset.Equals(context.Beatmap.BeatmapInfo.Ruleset))
|
||||
.ToList();
|
||||
|
||||
if (difficulties.Count == 0)
|
||||
yield break;
|
||||
|
||||
var lowestDifficulty = difficulties.OrderBy(b => b.BeatmapInfo.StarRating).First();
|
||||
|
||||
// Get difficulty rating for the lowest difficulty
|
||||
DifficultyRating lowestDifficultyRating = lowestDifficulty == context.Beatmap
|
||||
? context.InterpretedDifficulty
|
||||
: StarDifficulty.GetDifficultyRating(lowestDifficulty.BeatmapInfo.StarRating);
|
||||
|
||||
double drainTime = context.Beatmap.CalculateDrainLength();
|
||||
double playTime = context.Beatmap.CalculatePlayableLength();
|
||||
|
||||
bool isHighestDifficulty = difficulties.OrderByDescending(b => b.BeatmapInfo.StarRating).First() == context.Beatmap;
|
||||
|
||||
// Use play time unless it's the highest difficulty and has significant breaks
|
||||
bool canUsePlayTime = !isHighestDifficulty || context.Beatmap.TotalBreakTime < break_time_leniency;
|
||||
|
||||
double effectiveTime = canUsePlayTime ? playTime : drainTime;
|
||||
double thresholdReduction = canUsePlayTime ? 0 : break_time_leniency;
|
||||
|
||||
// Check against thresholds based on the lowest difficulty's rating in the beatmapset
|
||||
// Find the most appropriate threshold (highest rating that applies)
|
||||
var applicableThreshold = GetThresholds()
|
||||
.Where(t => lowestDifficultyRating >= t.rating)
|
||||
.OrderByDescending(t => t.rating)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (applicableThreshold != default && effectiveTime < applicableThreshold.thresholdMs - thresholdReduction)
|
||||
{
|
||||
yield return new IssueTemplateTooShort(this).Create(
|
||||
applicableThreshold.name,
|
||||
canUsePlayTime ? "play" : "drain",
|
||||
applicableThreshold.thresholdMs - thresholdReduction,
|
||||
effectiveTime
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public class IssueTemplateTooShort : IssueTemplate
|
||||
{
|
||||
public IssueTemplateTooShort(ICheck check)
|
||||
: base(check, IssueType.Problem, "With the lowest difficulty being \"{0}\", the {1} time of this difficulty must be at least {2}, currently {3}.")
|
||||
{
|
||||
}
|
||||
|
||||
public Issue Create(string lowestDiffLevel, string timeType, double requiredTime, double currentTime)
|
||||
=> new Issue(this,
|
||||
lowestDiffLevel,
|
||||
timeType,
|
||||
TimeSpan.FromMilliseconds(requiredTime).ToString(@"m\:ss"),
|
||||
TimeSpan.FromMilliseconds(currentTime).ToString(@"m\:ss"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,9 @@ namespace osu.Game.Screens.Edit.Verify
|
||||
[Resolved]
|
||||
private VerifyScreen verify { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmapManager { get; set; }
|
||||
|
||||
private IBeatmapVerifier rulesetVerifier;
|
||||
private BeatmapVerifier generalVerifier;
|
||||
private BeatmapVerifierContext context;
|
||||
@@ -43,7 +46,13 @@ namespace osu.Game.Screens.Edit.Verify
|
||||
generalVerifier = new BeatmapVerifier();
|
||||
rulesetVerifier = beatmap.BeatmapInfo.Ruleset.CreateInstance().CreateBeatmapVerifier();
|
||||
|
||||
context = new BeatmapVerifierContext(beatmap, workingBeatmap.Value, verify.InterpretedDifficulty.Value);
|
||||
context = new BeatmapVerifierContext(
|
||||
beatmap,
|
||||
workingBeatmap.Value,
|
||||
verify.InterpretedDifficulty.Value,
|
||||
beatmapInfo => beatmapManager.GetWorkingBeatmap(beatmapInfo).GetPlayableBeatmap(beatmapInfo.Ruleset)
|
||||
);
|
||||
|
||||
verify.InterpretedDifficulty.BindValueChanged(difficulty => context.InterpretedDifficulty = difficulty.NewValue);
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
Reference in New Issue
Block a user