From a4aa501bb540cd83f988febd90d128f1c890b0b9 Mon Sep 17 00:00:00 2001 From: Spooghetti420 Date: Fri, 28 Jan 2022 21:59:53 +0000 Subject: [PATCH] Change threshold from ms to beat-based, add tests --- .../Mods/TestSceneManiaModHoldOff.cs | 130 ++++++++++++++++++ .../Mods/TestSceneManiaModNoHolds.cs | 51 ------- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- ...{ManiaModNoHolds.cs => ManiaModHoldOff.cs} | 34 +++-- .../Mods/ManiaModNoLongNotes.cs | 86 ------------ 5 files changed, 155 insertions(+), 148 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs delete mode 100644 osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs rename osu.Game.Rulesets.Mania/Mods/{ManiaModNoHolds.cs => ManiaModHoldOff.cs} (66%) delete mode 100644 osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs new file mode 100644 index 0000000000..e157cb50b1 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs @@ -0,0 +1,130 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Tests.Visual; +using System.Collections.Generic; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Difficulty; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Mania.Tests.Mods +{ + public class TestSceneManiaModHoldOff : ModTestScene + { + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + + [Test] + public void TestMapHasNoHoldNotes() + { + var testBeatmap = createModdedBeatmap(); + Assert.False(testBeatmap.HitObjects.OfType().Any()); + } + + [Test] + public void TestCorrectNoteValues() + { + var testBeatmap = createRawBeatmap(); + var noteValues = new List(testBeatmap.HitObjects.OfType().Count()); + foreach (HoldNote h in testBeatmap.HitObjects.OfType()) { + noteValues.Add(ManiaModHoldOff.getNoteValue(h, (ManiaBeatmap)testBeatmap)); + } + noteValues.Sort(); + Assert.AreEqual(noteValues, new List { 0.125, 0.250, 0.500, 1.000, 2.000 }); + } + + [TestCase(ManiaModHoldOff.BeatDivisors.Whole)] + [TestCase(ManiaModHoldOff.BeatDivisors.Half)] + [TestCase(ManiaModHoldOff.BeatDivisors.Quarter)] + [TestCase(ManiaModHoldOff.BeatDivisors.Eighth)] + public void TestCorrectObjectCount(ManiaModHoldOff.BeatDivisors minBeatSnap) { + /* + This test is to ensure that, given that end notes are enabled, + the mod produces the expected number of objects when the mod is applied. + */ + + // Mod settings will be set to include the correct beat snap value + var rawBeatmap = createRawBeatmap(); + var testBeatmap = createModdedBeatmap(minBeatSnap); + + // Calculate expected number of objects + int expectedObjectCount = 0; + double beatSnapValue = 1/(Math.Pow(2, (int)minBeatSnap)); + + foreach (ManiaHitObject h in rawBeatmap.HitObjects) { + // Both notes and hold notes account for at least one object + expectedObjectCount++; + if (h.GetType() == typeof(HoldNote)) { + var noteValue = ManiaModHoldOff.getNoteValue((HoldNote)h, (ManiaBeatmap)rawBeatmap); + if (noteValue >= beatSnapValue) { + // Should generate an end note if it's longer than the minimum note value + expectedObjectCount++; + } + } + } + + Assert.That(testBeatmap.HitObjects.Count() == expectedObjectCount); + } + + [Test] + public void TestDifficultyIncrease() { + // A lower minimum beat snap divisor should only make the map harder, never easier + // (as more notes can be spawned) + var beatmaps = new ManiaBeatmap[] { + createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Whole), + createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Half), + createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Quarter), + createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Eighth), + createModdedBeatmap(ManiaModHoldOff.BeatDivisors.Sixteenth) + }; + + var mapDifficulties = new double[beatmaps.Length]; + for (int i = 0; i < mapDifficulties.Length; i++) { + var workingBeatmap = new TestWorkingBeatmap(beatmaps[i]); + var difficultyCalculator = new ManiaDifficultyCalculator(new ManiaRuleset().RulesetInfo, workingBeatmap); + mapDifficulties[i] = difficultyCalculator.Calculate().StarRating; + if (i > 0) { + Assert.LessOrEqual(mapDifficulties[i-1], mapDifficulties[i]); + Assert.LessOrEqual(beatmaps[i-1].HitObjects.Count, beatmaps[i].HitObjects.Count); + } + } + } + + private static ManiaBeatmap createModdedBeatmap(ManiaModHoldOff.BeatDivisors minBeatSnap=ManiaModHoldOff.BeatDivisors.Whole) + { + var beatmap = createRawBeatmap(); + var holdOffMod = new ManiaModHoldOff(); + holdOffMod.MinBeatSnap.Value = minBeatSnap; // Set the specified beat snap setting + Assert.AreEqual(holdOffMod.MinBeatSnap.Value, minBeatSnap); + + foreach (var hitObject in beatmap.HitObjects) + hitObject.ApplyDefaults(beatmap.ControlPointInfo, new BeatmapDifficulty()); + + holdOffMod.ApplyToBeatmap(beatmap); + + return (ManiaBeatmap)beatmap; + } + private static ManiaBeatmap createRawBeatmap() + { + var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); + beatmap.ControlPointInfo.Add(0.0, new TimingControlPoint { BeatLength = 1000 } ); // Set BPM to 60 + + // Add test hit objects + beatmap.HitObjects.Add(new Note { StartTime = 4000 }); + beatmap.HitObjects.Add(new Note { StartTime = 4500 }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 125 }); // 1/8 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 250 }); // 1/4 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 500 }); // 1/2 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 1000 }); // 1/1 note + beatmap.HitObjects.Add(new HoldNote { StartTime = 0, EndTime = 2000 }); // 2/1 note + + return beatmap; + } + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs deleted file mode 100644 index f22b724c99..0000000000 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoHolds.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Linq; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Testing; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mania.Mods; -using osu.Game.Tests.Visual; -using osu.Game.Rulesets.Mania.Tests; -using System.Collections.Generic; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Objects; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Mania.Beatmaps; - -namespace osu.Game.Rulesets.Mania.Tests.Mods -{ - public class TestSceneManiaModNoHolds : ModTestScene - { - protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); - - [Test] - public void TestMapHasNoHeldNotes() - { - var testBeatmap = createBeatmap(); - Assert.That(!testBeatmap.HitObjects.OfType().Any()); - } - - private static IBeatmap createBeatmap() - { - var beatmap = createRawBeatmap(); - var noHoldsMod = new ManiaModNoHolds(); - - foreach (var hitObject in beatmap.HitObjects) - hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - - noHoldsMod.ApplyToBeatmap(beatmap); - - return beatmap; - } - private static IBeatmap createRawBeatmap() - { - var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); - beatmap.HitObjects.Add(new Note { StartTime = 1000 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, EndTime = 3000 }); - return beatmap; - } - } -} diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 55ca1a465e..2098c7f5d8 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -244,7 +244,7 @@ namespace osu.Game.Rulesets.Mania new ManiaModClassic(), new ManiaModInvert(), new ManiaModConstantSpeed(), - new ManiaModNoHolds() + new ManiaModHoldOff() }; case ModType.Automation: diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs similarity index 66% rename from osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs rename to osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index a18dbad4a1..cfdb58ee67 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNoHolds.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -2,28 +2,23 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; using osu.Framework.Graphics.Sprites; using System; using System.Collections.Generic; -using osu.Game.Audio; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Framework.Utils; -using osu.Game.Overlays.Settings; using osu.Framework.Bindables; using osu.Game.Configuration; -using osu.Game.Graphics; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModNoHolds : Mod, IApplicableAfterBeatmapConversion + public class ManiaModHoldOff : Mod, IApplicableAfterBeatmapConversion { - public override string Name => "No Holds"; + public override string Name => "Hold Off"; - public override string Acronym => "NH"; + public override string Acronym => "HO"; public override double ScoreMultiplier => 1; @@ -40,11 +35,15 @@ namespace osu.Game.Rulesets.Mania.Mods Value = true }; + [SettingSource("Minimum end note beat snap", "Don't add end notes for hold notes shorter than this beat division")] + public Bindable MinBeatSnap { get; } = new Bindable(defaultValue: BeatDivisors.Half); + public void ApplyToBeatmap(IBeatmap beatmap) { var maniaBeatmap = (ManiaBeatmap)beatmap; var newObjects = new List(); + var beatSnap = 1/(Math.Pow(2, (double)MinBeatSnap.Value)); foreach (var h in beatmap.HitObjects.OfType()) { // Add a note for the beginning of the hold note @@ -56,7 +55,8 @@ namespace osu.Game.Rulesets.Mania.Mods }); // Don't add an end note if the duration is shorter than some threshold, or end notes are disabled - if (AddEndNotes.Value && h.Duration > 200) + var noteValue = getNoteValue(h, maniaBeatmap); // 1/1, 1/2, 1/4, etc. + if (AddEndNotes.Value && noteValue >= beatSnap) { newObjects.Add(new Note { @@ -66,8 +66,22 @@ namespace osu.Game.Rulesets.Mania.Mods }); } } - maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList(); } + + public static double getNoteValue(HoldNote holdNote, ManiaBeatmap beatmap) { + var bpmAtNoteTime = beatmap.ControlPointInfo.TimingPointAt(holdNote.StartTime).BPM; + var noteValue = (60*holdNote.Duration)/(1000*bpmAtNoteTime); + return noteValue; + } + + public enum BeatDivisors + { + Whole, + Half, + Quarter, + Eighth, + Sixteenth + } } } \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs deleted file mode 100644 index d10003b783..0000000000 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNoLongNotes.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mods; -using osu.Framework.Graphics.Sprites; -using System; -using System.Collections.Generic; -using osu.Game.Audio; -using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Framework.Utils; -using osu.Game.Overlays.Settings; -using osu.Framework.Bindables; -using osu.Game.Configuration; -using osu.Game.Graphics; - - - -namespace osu.Game.Rulesets.Mania.Mods -{ - public class ManiaModNoLongNotes : Mod, IApplicableAfterBeatmapConversion - { - - public override string Name => "No Long Notes"; - - public override string Acronym => "NL"; - - public override double ScoreMultiplier => 1; - - public override string Description => @"Turns all held notes into tap notes. No coordination required."; - - public override IconUsage? Icon => FontAwesome.Solid.DotCircle; - - public override ModType Type => ModType.Conversion; - - [SettingSource("Add end notes", "Also add a note at the end of a held note")] - public BindableBool AddEndNotes { get; } = new BindableBool - { - Default = true, - Value = true - }; - - [SettingSource("Length threshold", "Only add an end note for held notes longer than this threshold (in milliseconds)")] - public BindableNumber Threshold { get; } = new BindableDouble - { - MinValue = 1.0, - MaxValue = 1990.0, - Default = 200.0, - Value = 200.0, - Precision = 1.0, - }; - - public void ApplyToBeatmap(IBeatmap beatmap) - { - var maniaBeatmap = (ManiaBeatmap)beatmap; - - var newObjects = new List(); - beatmap.HitObjects.OfType().ForEach(h => - { - // Add a note for the beginning of the hold note - newObjects.Add(new Note - { - Column = h.Column, - StartTime = h.StartTime, - Samples = h.Samples - }); - - // Don't add an end note if the duration is below the threshold, or end notes are disabled - if (AddEndNotes.Value && h.Duration > Threshold.Value) - { - newObjects.Add(new Note - { - Column = h.Column, - StartTime = h.EndTime, - Samples = h.Samples - }); - } - }); - - maniaBeatmap.HitObjects = maniaBeatmap.HitObjects.OfType().Concat(newObjects).OrderBy(h => h.StartTime).ToList(); - } - } -}