diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/SampleLookups/taiko-hitobject-beatmap-custom-sample-bank.osu b/osu.Game.Rulesets.Taiko.Tests/Resources/SampleLookups/taiko-hitobject-beatmap-custom-sample-bank.osu new file mode 100644 index 0000000000..f9755782c2 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Resources/SampleLookups/taiko-hitobject-beatmap-custom-sample-bank.osu @@ -0,0 +1,10 @@ +osu file format v14 + +[General] +Mode: 1 + +[TimingPoints] +0,300,4,1,2,100,1,0 + +[HitObjects] +444,320,1000,5,0,0:0:0:0: diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs new file mode 100644 index 0000000000..7089ea6619 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Reflection; +using NUnit.Framework; +using osu.Framework.IO.Stores; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public class TestSceneTaikoHitObjectSamples : HitObjectSampleTest + { + protected override Ruleset CreatePlayerRuleset() => new TaikoRuleset(); + + protected override IResourceStore Resources => new DllResourceStore(Assembly.GetAssembly(typeof(TestSceneTaikoHitObjectSamples))); + + [TestCase("taiko-normal-hitnormal")] + [TestCase("normal-hitnormal")] + [TestCase("hitnormal")] + public void TestDefaultCustomSampleFromBeatmap(string expectedSample) + { + SetupSkins(expectedSample, expectedSample); + + CreateTestWithBeatmap("taiko-hitobject-beatmap-custom-sample-bank.osu"); + + AssertBeatmapLookup(expectedSample); + } + + [TestCase("taiko-normal-hitnormal")] + [TestCase("normal-hitnormal")] + [TestCase("hitnormal")] + public void TestDefaultCustomSampleFromUserSkinFallback(string expectedSample) + { + SetupSkins(string.Empty, expectedSample); + + CreateTestWithBeatmap("taiko-hitobject-beatmap-custom-sample-bank.osu"); + + AssertUserLookup(expectedSample); + } + + [TestCase("taiko-normal-hitnormal2")] + [TestCase("normal-hitnormal2")] + public void TestUserSkinLookupIgnoresSampleBank(string unwantedSample) + { + SetupSkins(string.Empty, unwantedSample); + + CreateTestWithBeatmap("taiko-hitobject-beatmap-custom-sample-bank.osu"); + + AssertNoLookup(unwantedSample); + } + } +} diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs index 737946e1e0..c3acc2ebe7 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs @@ -67,9 +67,11 @@ namespace osu.Game.Tests.Gameplay /// Tests that a hitobject which provides a custom sample set of 2 retrieves the following samples from the beatmap skin: /// normal-hitnormal2 /// normal-hitnormal + /// hitnormal /// [TestCase("normal-hitnormal2")] [TestCase("normal-hitnormal")] + [TestCase("hitnormal")] public void TestDefaultCustomSampleFromBeatmap(string expectedSample) { SetupSkins(expectedSample, expectedSample); @@ -80,12 +82,13 @@ namespace osu.Game.Tests.Gameplay } /// - /// Tests that a hitobject which provides a custom sample set of 2 retrieves the following samples from the user skin when the beatmap does not contain the sample: - /// normal-hitnormal2 + /// Tests that a hitobject which provides a custom sample set of 2 retrieves the following samples from the user skin + /// (ignoring the custom sample set index) when the beatmap skin does not contain the sample: /// normal-hitnormal + /// hitnormal /// - [TestCase("normal-hitnormal2")] [TestCase("normal-hitnormal")] + [TestCase("hitnormal")] public void TestDefaultCustomSampleFromUserSkinFallback(string expectedSample) { SetupSkins(string.Empty, expectedSample); @@ -95,6 +98,23 @@ namespace osu.Game.Tests.Gameplay AssertUserLookup(expectedSample); } + /// + /// Tests that a hitobject which provides a custom sample set of 2 does not retrieve a normal-hitnormal2 sample from the user skin + /// if the beatmap skin does not contain the sample. + /// User skins in stable ignore the custom sample set index when performing lookups. + /// + [Test] + public void TestUserSkinLookupIgnoresSampleBank() + { + const string unwanted_sample = "normal-hitnormal2"; + + SetupSkins(string.Empty, unwanted_sample); + + CreateTestWithBeatmap("hitobject-beatmap-custom-sample.osu"); + + AssertNoLookup(unwanted_sample); + } + /// /// Tests that a hitobject which provides a sample file retrieves the sample file from the beatmap skin. /// @@ -145,6 +165,7 @@ namespace osu.Game.Tests.Gameplay /// [TestCase("normal-hitnormal2")] [TestCase("normal-hitnormal")] + [TestCase("hitnormal")] public void TestControlPointCustomSampleFromBeatmap(string sampleName) { SetupSkins(sampleName, sampleName); @@ -178,7 +199,7 @@ namespace osu.Game.Tests.Gameplay string[] expectedSamples = { "normal-hitnormal2", - "normal-hitwhistle2" + "normal-hitwhistle" // user skin lookups ignore custom sample set index }; SetupSkins(expectedSamples[0], expectedSamples[1]); diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 87bca856a3..d647bc4a2d 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -14,6 +14,7 @@ namespace osu.Game.Skinning public class LegacyBeatmapSkin : LegacySkin { protected override bool AllowManiaSkin => false; + protected override bool UseCustomSampleBanks => true; public LegacyBeatmapSkin(BeatmapInfo beatmap, IResourceStore storage, AudioManager audioManager) : base(createSkinInfo(beatmap), new LegacySkinResourceStore(beatmap.BeatmapSet, storage), audioManager, beatmap.Path) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 3bbeff9918..fc04383a64 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -38,6 +38,12 @@ namespace osu.Game.Skinning protected virtual bool AllowManiaSkin => hasKeyTexture.Value; + /// + /// Whether this skin can use samples with a custom bank (custom sample set in stable terminology). + /// Added in order to match sample lookup logic from stable (in stable, only the beatmap skin could use samples with a custom sample bank). + /// + protected virtual bool UseCustomSampleBanks => false; + public new LegacySkinConfiguration Configuration { get => base.Configuration as LegacySkinConfiguration; @@ -337,7 +343,12 @@ namespace osu.Game.Skinning public override SampleChannel GetSample(ISampleInfo sampleInfo) { - foreach (var lookup in sampleInfo.LookupNames) + var lookupNames = sampleInfo.LookupNames; + + if (sampleInfo is HitSampleInfo hitSample) + lookupNames = getLegacyLookupNames(hitSample); + + foreach (var lookup in lookupNames) { var sample = Samples?.Get(lookup); @@ -345,10 +356,6 @@ namespace osu.Game.Skinning return sample; } - if (sampleInfo is HitSampleInfo hsi) - // Try fallback to non-bank samples. - return Samples?.Get(hsi.Name); - return null; } @@ -361,5 +368,23 @@ namespace osu.Game.Skinning string lastPiece = componentName.Split('/').Last(); yield return componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece; } + + private IEnumerable getLegacyLookupNames(HitSampleInfo hitSample) + { + var lookupNames = hitSample.LookupNames; + + if (!UseCustomSampleBanks && !string.IsNullOrEmpty(hitSample.Suffix)) + // for compatibility with stable, exclude the lookup names with the custom sample bank suffix, if they are not valid for use in this skin. + // using .EndsWith() is intentional as it ensures parity in all edge cases + // (see LegacyTaikoSampleInfo for an example of one - prioritising the taiko prefix should still apply, but the sample bank should not). + lookupNames = hitSample.LookupNames.Where(name => !name.EndsWith(hitSample.Suffix)); + + // also for compatibility, try falling back to non-bank samples (so-called "universal" samples) as the last resort. + // going forward specifying banks shall always be required, even for elements that wouldn't require it on stable, + // which is why this is done locally here. + lookupNames = lookupNames.Append(hitSample.Name); + + return lookupNames; + } } }