diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/SampleLookups/mania-hitobject-beatmap-custom-sample-bank.osu b/osu.Game.Rulesets.Mania.Tests/Resources/SampleLookups/mania-hitobject-beatmap-custom-sample-bank.osu new file mode 100644 index 0000000000..4f8e1b68dd --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Resources/SampleLookups/mania-hitobject-beatmap-custom-sample-bank.osu @@ -0,0 +1,10 @@ +osu file format v14 + +[General] +Mode: 3 + +[TimingPoints] +0,300,4,0,2,100,1,0 + +[HitObjects] +444,320,1000,5,2,0:0:0:0: diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/SampleLookups/mania-hitobject-beatmap-normal-sample-bank.osu b/osu.Game.Rulesets.Mania.Tests/Resources/SampleLookups/mania-hitobject-beatmap-normal-sample-bank.osu new file mode 100644 index 0000000000..f22901e304 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Resources/SampleLookups/mania-hitobject-beatmap-normal-sample-bank.osu @@ -0,0 +1,10 @@ +osu file format v14 + +[General] +Mode: 3 + +[TimingPoints] +0,300,4,0,2,100,1,0 + +[HitObjects] +444,320,1000,5,1,0:0:0:0: diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectSamples.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectSamples.cs new file mode 100644 index 0000000000..0d726e1a50 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectSamples.cs @@ -0,0 +1,49 @@ +// 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.Mania.Tests +{ + public class TestSceneManiaHitObjectSamples : HitObjectSampleTest + { + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + protected override IResourceStore Resources => new DllResourceStore(Assembly.GetAssembly(typeof(TestSceneManiaHitObjectSamples))); + + /// + /// Tests that when a normal sample bank is used, the normal hitsound will be looked up. + /// + [Test] + public void TestManiaHitObjectNormalSampleBank() + { + const string expected_sample = "normal-hitnormal2"; + + SetupSkins(expected_sample, expected_sample); + + CreateTestWithBeatmap("mania-hitobject-beatmap-normal-sample-bank.osu"); + + AssertBeatmapLookup(expected_sample); + } + + /// + /// Tests that when a custom sample bank is used, layered hitsounds are not played + /// (only the sample from the custom bank is looked up). + /// + [Test] + public void TestManiaHitObjectCustomSampleBank() + { + const string expected_sample = "normal-hitwhistle2"; + const string unwanted_sample = "normal-hitnormal2"; + + SetupSkins(expected_sample, unwanted_sample); + + CreateTestWithBeatmap("mania-hitobject-beatmap-custom-sample-bank.osu"); + + AssertBeatmapLookup(expected_sample); + AssertNoLookup(unwanted_sample); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index 84e88a10be..e167135556 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -9,6 +9,9 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Skinning; using System.Collections.Generic; +using osu.Framework.Audio.Sample; +using osu.Game.Audio; +using osu.Game.Rulesets.Objects.Legacy; namespace osu.Game.Rulesets.Mania.Skinning { @@ -129,6 +132,15 @@ namespace osu.Game.Rulesets.Mania.Skinning return this.GetAnimation(filename, true, true); } + public override SampleChannel GetSample(ISampleInfo sampleInfo) + { + // layered hit sounds never play in mania + if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample && legacySample.IsLayered) + return new SampleChannelVirtual(); + + return Source.GetSample(sampleInfo); + } + public override IBindable GetConfig(TLookup lookup) { if (lookup is ManiaSkinConfigurationLookup maniaLookup) diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs index ef6efb7fec..737946e1e0 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs @@ -6,6 +6,7 @@ using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Skinning; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; @@ -167,5 +168,64 @@ namespace osu.Game.Tests.Gameplay AssertBeatmapLookup(expected_sample); } + + /// + /// Tests that when a custom sample bank is used, both the normal and additional sounds will be looked up. + /// + [Test] + public void TestHitObjectCustomSampleBank() + { + string[] expectedSamples = + { + "normal-hitnormal2", + "normal-hitwhistle2" + }; + + SetupSkins(expectedSamples[0], expectedSamples[1]); + + CreateTestWithBeatmap("hitobject-beatmap-custom-sample-bank.osu"); + + AssertBeatmapLookup(expectedSamples[0]); + AssertUserLookup(expectedSamples[1]); + } + + /// + /// Tests that when a custom sample bank is used, but is disabled, + /// only the additional sound will be looked up. + /// + [Test] + public void TestHitObjectCustomSampleBankWithoutLayered() + { + const string expected_sample = "normal-hitwhistle2"; + const string unwanted_sample = "normal-hitnormal2"; + + SetupSkins(expected_sample, unwanted_sample); + disableLayeredHitSounds(); + + CreateTestWithBeatmap("hitobject-beatmap-custom-sample-bank.osu"); + + AssertBeatmapLookup(expected_sample); + AssertNoLookup(unwanted_sample); + } + + /// + /// Tests that when a normal sample bank is used and is disabled, + /// the normal sound will be looked up anyway. + /// + [Test] + public void TestHitObjectNormalSampleBankWithoutLayered() + { + const string expected_sample = "normal-hitnormal"; + + SetupSkins(expected_sample, expected_sample); + disableLayeredHitSounds(); + + CreateTestWithBeatmap("hitobject-beatmap-sample.osu"); + + AssertBeatmapLookup(expected_sample); + } + + private void disableLayeredHitSounds() + => AddStep("set LayeredHitSounds to false", () => Skin.Configuration.ConfigDictionary[GlobalSkinConfiguration.LayeredHitSounds.ToString()] = "0"); } } diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 552d163b2f..b30870d057 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -11,8 +11,10 @@ using osu.Framework.Audio.Sample; using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Game.Audio; +using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Play; using osu.Game.Skinning; using osu.Game.Storyboards; @@ -70,6 +72,50 @@ namespace osu.Game.Tests.Gameplay AddUntilStep("sample playback succeeded", () => sample.LifetimeEnd < double.MaxValue); } + [TestCase(typeof(OsuModDoubleTime), 1.5)] + [TestCase(typeof(OsuModHalfTime), 0.75)] + [TestCase(typeof(ModWindUp), 1.5)] + [TestCase(typeof(ModWindDown), 0.75)] + [TestCase(typeof(OsuModDoubleTime), 2)] + [TestCase(typeof(OsuModHalfTime), 0.5)] + [TestCase(typeof(ModWindUp), 2)] + [TestCase(typeof(ModWindDown), 0.5)] + public void TestSamplePlaybackWithRateMods(Type expectedMod, double expectedRate) + { + GameplayClockContainer gameplayContainer = null; + TestDrawableStoryboardSample sample = null; + + Mod testedMod = Activator.CreateInstance(expectedMod) as Mod; + + switch (testedMod) + { + case ModRateAdjust m: + m.SpeedChange.Value = expectedRate; + break; + + case ModTimeRamp m: + m.InitialRate.Value = m.FinalRate.Value = expectedRate; + break; + } + + AddStep("setup storyboard sample", () => + { + Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, Audio); + SelectedMods.Value = new[] { testedMod }; + + Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, SelectedMods.Value, 0)); + + gameplayContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1)) + { + Clock = gameplayContainer.GameplayClock + }); + }); + + AddStep("start", () => gameplayContainer.Start()); + + AddAssert("sample playback rate matches mod rates", () => sample.Channel.AggregateFrequency.Value == expectedRate); + } + private class TestSkin : LegacySkin { public TestSkin(string resourceName, AudioManager audioManager) @@ -99,5 +145,28 @@ namespace osu.Game.Tests.Gameplay { } } + + private class TestCustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap + { + private readonly AudioManager audio; + + public TestCustomSkinWorkingBeatmap(RulesetInfo ruleset, AudioManager audio) + : base(ruleset, null, audio) + { + this.audio = audio; + } + + protected override ISkin GetSkin() => new TestSkin("test-sample", audio); + } + + private class TestDrawableStoryboardSample : DrawableStoryboardSample + { + public TestDrawableStoryboardSample(StoryboardSampleInfo sampleInfo) + : base(sampleInfo) + { + } + + public new SampleChannel Channel => base.Channel; + } } } diff --git a/osu.Game.Tests/Resources/SampleLookups/hitobject-beatmap-custom-sample-bank.osu b/osu.Game.Tests/Resources/SampleLookups/hitobject-beatmap-custom-sample-bank.osu new file mode 100644 index 0000000000..c50c921839 --- /dev/null +++ b/osu.Game.Tests/Resources/SampleLookups/hitobject-beatmap-custom-sample-bank.osu @@ -0,0 +1,7 @@ +osu file format v14 + +[TimingPoints] +0,300,4,0,2,100,1,0 + +[HitObjects] +444,320,1000,5,2,0:0:0:0: diff --git a/osu.Game.Tests/Resources/skin-zero-alpha-colour.ini b/osu.Game.Tests/Resources/skin-zero-alpha-colour.ini new file mode 100644 index 0000000000..3c0dae6b13 --- /dev/null +++ b/osu.Game.Tests/Resources/skin-zero-alpha-colour.ini @@ -0,0 +1,5 @@ +[General] +Version: latest + +[Colours] +Combo1: 255,255,255,0 \ No newline at end of file diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs index aedf26ee75..c408d2f182 100644 --- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs @@ -108,5 +108,15 @@ namespace osu.Game.Tests.Skins using (var stream = new LineBufferedReader(resStream)) Assert.That(decoder.Decode(stream).LegacyVersion, Is.EqualTo(1.0m)); } + + [Test] + public void TestDecodeColourWithZeroAlpha() + { + var decoder = new LegacySkinDecoder(); + + using (var resStream = TestResources.OpenResource("skin-zero-alpha-colour.ini")) + using (var stream = new LineBufferedReader(resStream)) + Assert.That(decoder.Decode(stream).ComboColours[0].A, Is.EqualTo(1.0f)); + } } } diff --git a/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs index 77119f7a60..acd5d53310 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs @@ -9,7 +9,7 @@ using osu.Game.Tournament.Screens.Gameplay.Components; namespace osu.Game.Tournament.Tests.Components { - public class TestSceneMatchScoreDisplay : LadderTestScene + public class TestSceneMatchScoreDisplay : TournamentTestScene { [Cached(Type = typeof(MatchIPCInfo))] private MatchIPCInfo matchInfo = new MatchIPCInfo(); diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs index 77fa411058..bc32a12ab7 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs @@ -8,12 +8,11 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; -using osu.Game.Tests.Visual; using osu.Game.Tournament.Components; namespace osu.Game.Tournament.Tests.Components { - public class TestSceneTournamentBeatmapPanel : OsuTestScene + public class TestSceneTournamentBeatmapPanel : TournamentTestScene { [Resolved] private IAPIProvider api { get; set; } diff --git a/osu.Game.Tournament.Tests/LadderTestScene.cs b/osu.Game.Tournament.Tests/LadderTestScene.cs deleted file mode 100644 index 2f4373679c..0000000000 --- a/osu.Game.Tournament.Tests/LadderTestScene.cs +++ /dev/null @@ -1,146 +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.Utils; -using osu.Game.Beatmaps; -using osu.Game.Rulesets; -using osu.Game.Tournament.Models; -using osu.Game.Users; - -namespace osu.Game.Tournament.Tests -{ - [TestFixture] - public abstract class LadderTestScene : TournamentTestScene - { - [Cached] - protected LadderInfo Ladder { get; private set; } = new LadderInfo(); - - [Resolved] - private RulesetStore rulesetStore { get; set; } - - [BackgroundDependencyLoader] - private void load() - { - Ladder.Ruleset.Value ??= rulesetStore.AvailableRulesets.First(); - - Ruleset.BindTo(Ladder.Ruleset); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - TournamentMatch match = CreateSampleMatch(); - - Ladder.Rounds.Add(match.Round.Value); - Ladder.Matches.Add(match); - Ladder.Teams.Add(match.Team1.Value); - Ladder.Teams.Add(match.Team2.Value); - - Ladder.CurrentMatch.Value = match; - } - - public static TournamentMatch CreateSampleMatch() => new TournamentMatch - { - Team1 = - { - Value = new TournamentTeam - { - FlagName = { Value = "JP" }, - FullName = { Value = "Japan" }, - LastYearPlacing = { Value = 10 }, - Seed = { Value = "Low" }, - SeedingResults = - { - new SeedingResult - { - Mod = { Value = "NM" }, - Seed = { Value = 10 }, - Beatmaps = - { - new SeedingBeatmap - { - BeatmapInfo = CreateSampleBeatmapInfo(), - Score = 12345672, - Seed = { Value = 24 }, - }, - new SeedingBeatmap - { - BeatmapInfo = CreateSampleBeatmapInfo(), - Score = 1234567, - Seed = { Value = 12 }, - }, - new SeedingBeatmap - { - BeatmapInfo = CreateSampleBeatmapInfo(), - Score = 1234567, - Seed = { Value = 16 }, - } - } - }, - new SeedingResult - { - Mod = { Value = "DT" }, - Seed = { Value = 5 }, - Beatmaps = - { - new SeedingBeatmap - { - BeatmapInfo = CreateSampleBeatmapInfo(), - Score = 234567, - Seed = { Value = 3 }, - }, - new SeedingBeatmap - { - BeatmapInfo = CreateSampleBeatmapInfo(), - Score = 234567, - Seed = { Value = 6 }, - }, - new SeedingBeatmap - { - BeatmapInfo = CreateSampleBeatmapInfo(), - Score = 234567, - Seed = { Value = 12 }, - } - } - } - }, - Players = - { - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 12 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 16 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 20 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 24 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 30 } } }, - } - } - }, - Team2 = - { - Value = new TournamentTeam - { - FlagName = { Value = "US" }, - FullName = { Value = "United States" }, - Players = - { - new User { Username = "Hello" }, - new User { Username = "Hello" }, - new User { Username = "Hello" }, - new User { Username = "Hello" }, - new User { Username = "Hello" }, - } - } - }, - Round = - { - Value = new TournamentRound { Name = { Value = "Quarterfinals" } } - } - }; - - public static BeatmapInfo CreateSampleBeatmapInfo() => - new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist", ID = RNG.Next(0, 1000000) } }; - } -} diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs index a45c5de2bd..bceb3e6b74 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs @@ -8,7 +8,7 @@ using osu.Game.Tournament.Screens.Editors; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneLadderEditorScreen : LadderTestScene + public class TestSceneLadderEditorScreen : TournamentTestScene { [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneLadderScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneLadderScreen.cs index 2be0564c82..c4c100d506 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneLadderScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneLadderScreen.cs @@ -8,7 +8,7 @@ using osu.Game.Tournament.Screens.Ladder; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneLadderScreen : LadderTestScene + public class TestSceneLadderScreen : TournamentTestScene { [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs index a4538be384..f4032fdd54 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs @@ -12,7 +12,7 @@ using osu.Game.Tournament.Screens.MapPool; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneMapPoolScreen : LadderTestScene + public class TestSceneMapPoolScreen : TournamentTestScene { private MapPoolScreen screen; diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneRoundEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneRoundEditorScreen.cs index e15ac416b0..5c2b59df3a 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneRoundEditorScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneRoundEditorScreen.cs @@ -5,7 +5,7 @@ using osu.Game.Tournament.Screens.Editors; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneRoundEditorScreen : LadderTestScene + public class TestSceneRoundEditorScreen : TournamentTestScene { public TestSceneRoundEditorScreen() { diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs index 8d12d5393d..2722021216 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs @@ -7,7 +7,7 @@ using osu.Game.Tournament.Screens.Editors; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneSeedingEditorScreen : LadderTestScene + public class TestSceneSeedingEditorScreen : TournamentTestScene { [Cached] private readonly LadderInfo ladder = new LadderInfo(); diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs index 4269f8f56a..d414d8e36e 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs @@ -8,7 +8,7 @@ using osu.Game.Tournament.Screens.TeamIntro; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneSeedingScreen : LadderTestScene + public class TestSceneSeedingScreen : TournamentTestScene { [Cached] private readonly LadderInfo ladder = new LadderInfo(); diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamEditorScreen.cs index 097bad4a02..fc6574ec8a 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneTeamEditorScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamEditorScreen.cs @@ -5,7 +5,7 @@ using osu.Game.Tournament.Screens.Editors; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneTeamEditorScreen : LadderTestScene + public class TestSceneTeamEditorScreen : TournamentTestScene { public TestSceneTeamEditorScreen() { diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs index e36b594ff2..b3f78c92d9 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs @@ -9,7 +9,7 @@ using osu.Game.Tournament.Screens.TeamIntro; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneTeamIntroScreen : LadderTestScene + public class TestSceneTeamIntroScreen : TournamentTestScene { [Cached] private readonly LadderInfo ladder = new LadderInfo(); diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs index 1a2faa76c1..3ca58dcaf4 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs @@ -4,25 +4,19 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens.TeamWin; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneTeamWinScreen : LadderTestScene + public class TestSceneTeamWinScreen : TournamentTestScene { - [Cached] - private readonly LadderInfo ladder = new LadderInfo(); - [BackgroundDependencyLoader] private void load() { - var match = new TournamentMatch(); - match.Team1.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "USA"); - match.Team2.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "JPN"); + var match = Ladder.CurrentMatch.Value; + match.Round.Value = Ladder.Rounds.FirstOrDefault(g => g.Name.Value == "Finals"); match.Completed.Value = true; - ladder.CurrentMatch.Value = match; Add(new TeamWinScreen { diff --git a/osu.Game.Tournament.Tests/TournamentTestScene.cs b/osu.Game.Tournament.Tests/TournamentTestScene.cs index 18ac3230da..d22da25f9d 100644 --- a/osu.Game.Tournament.Tests/TournamentTestScene.cs +++ b/osu.Game.Tournament.Tests/TournamentTestScene.cs @@ -1,13 +1,151 @@ // 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.Allocation; +using osu.Framework.Platform; using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; using osu.Game.Tests.Visual; +using osu.Game.Tournament.IPC; +using osu.Game.Tournament.Models; +using osu.Game.Users; namespace osu.Game.Tournament.Tests { public abstract class TournamentTestScene : OsuTestScene { + [Cached] + protected LadderInfo Ladder { get; private set; } = new LadderInfo(); + + [Resolved] + private RulesetStore rulesetStore { get; set; } + + [Cached] + protected MatchIPCInfo IPCInfo { get; private set; } = new MatchIPCInfo(); + + [BackgroundDependencyLoader] + private void load(Storage storage) + { + Ladder.Ruleset.Value ??= rulesetStore.AvailableRulesets.First(); + + TournamentMatch match = CreateSampleMatch(); + + Ladder.Rounds.Add(match.Round.Value); + Ladder.Matches.Add(match); + Ladder.Teams.Add(match.Team1.Value); + Ladder.Teams.Add(match.Team2.Value); + + Ladder.CurrentMatch.Value = match; + + Ruleset.BindTo(Ladder.Ruleset); + Dependencies.CacheAs(new StableInfo(storage)); + } + + public static TournamentMatch CreateSampleMatch() => new TournamentMatch + { + Team1 = + { + Value = new TournamentTeam + { + Acronym = { Value = "JPN" }, + FlagName = { Value = "JP" }, + FullName = { Value = "Japan" }, + LastYearPlacing = { Value = 10 }, + Seed = { Value = "Low" }, + SeedingResults = + { + new SeedingResult + { + Mod = { Value = "NM" }, + Seed = { Value = 10 }, + Beatmaps = + { + new SeedingBeatmap + { + BeatmapInfo = CreateSampleBeatmapInfo(), + Score = 12345672, + Seed = { Value = 24 }, + }, + new SeedingBeatmap + { + BeatmapInfo = CreateSampleBeatmapInfo(), + Score = 1234567, + Seed = { Value = 12 }, + }, + new SeedingBeatmap + { + BeatmapInfo = CreateSampleBeatmapInfo(), + Score = 1234567, + Seed = { Value = 16 }, + } + } + }, + new SeedingResult + { + Mod = { Value = "DT" }, + Seed = { Value = 5 }, + Beatmaps = + { + new SeedingBeatmap + { + BeatmapInfo = CreateSampleBeatmapInfo(), + Score = 234567, + Seed = { Value = 3 }, + }, + new SeedingBeatmap + { + BeatmapInfo = CreateSampleBeatmapInfo(), + Score = 234567, + Seed = { Value = 6 }, + }, + new SeedingBeatmap + { + BeatmapInfo = CreateSampleBeatmapInfo(), + Score = 234567, + Seed = { Value = 12 }, + } + } + } + }, + Players = + { + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 12 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 16 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 20 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 24 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 30 } } }, + } + } + }, + Team2 = + { + Value = new TournamentTeam + { + Acronym = { Value = "USA" }, + FlagName = { Value = "US" }, + FullName = { Value = "United States" }, + Players = + { + new User { Username = "Hello" }, + new User { Username = "Hello" }, + new User { Username = "Hello" }, + new User { Username = "Hello" }, + new User { Username = "Hello" }, + } + } + }, + Round = + { + Value = new TournamentRound { Name = { Value = "Quarterfinals" } } + } + }; + + public static BeatmapInfo CreateSampleBeatmapInfo() => + new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist", ID = RNG.Next(0, 1000000) } }; + protected override ITestSceneTestRunner CreateRunner() => new TournamentTestSceneTestRunner(); public class TournamentTestSceneTestRunner : TournamentGameBase, ITestSceneTestRunner diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index 23fcb01db7..2c539cdd43 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tournament public const float STREAM_AREA_WIDTH = 1366; - public const double REQUIRED_WIDTH = TournamentSceneManager.CONTROL_AREA_WIDTH * 2 + TournamentSceneManager.STREAM_AREA_WIDTH; + public const double REQUIRED_WIDTH = CONTROL_AREA_WIDTH * 2 + STREAM_AREA_WIDTH; [Cached] private TournamentMatchChatDisplay chat = new TournamentMatchChatDisplay(); diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 6406bd88a5..a0e83554a3 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -103,7 +103,12 @@ namespace osu.Game.Beatmaps.Formats try { - colour = new Color4(byte.Parse(split[0]), byte.Parse(split[1]), byte.Parse(split[2]), split.Length == 4 ? byte.Parse(split[3]) : (byte)255); + byte alpha = split.Length == 4 ? byte.Parse(split[3]) : (byte)255; + + if (alpha == 0) + alpha = 255; + + colour = new Color4(byte.Parse(split[0]), byte.Parse(split[1]), byte.Parse(split[2]), alpha); } catch { diff --git a/osu.Game/Beatmaps/Formats/Parsing.cs b/osu.Game/Beatmaps/Formats/Parsing.cs index c3efb8c760..c4795a6931 100644 --- a/osu.Game/Beatmaps/Formats/Parsing.cs +++ b/osu.Game/Beatmaps/Formats/Parsing.cs @@ -11,7 +11,7 @@ namespace osu.Game.Beatmaps.Formats /// public static class Parsing { - public const int MAX_COORDINATE_VALUE = 65536; + public const int MAX_COORDINATE_VALUE = 131072; public const double MAX_PARSE_VALUE = int.MaxValue; diff --git a/osu.Game/Rulesets/Mods/IApplicableToAudio.cs b/osu.Game/Rulesets/Mods/IApplicableToAudio.cs new file mode 100644 index 0000000000..901da7af55 --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableToAudio.cs @@ -0,0 +1,9 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Mods +{ + public interface IApplicableToAudio : IApplicableToTrack, IApplicableToSample + { + } +} diff --git a/osu.Game/Rulesets/Mods/IApplicableToSample.cs b/osu.Game/Rulesets/Mods/IApplicableToSample.cs new file mode 100644 index 0000000000..559d127cfc --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableToSample.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Audio.Sample; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// An interface for mods that make adjustments to a sample. + /// + public interface IApplicableToSample : IApplicableMod + { + void ApplyToSample(SampleChannel sample); + } +} diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index cb2ff149f1..874384686f 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -2,12 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; using osu.Framework.Bindables; namespace osu.Game.Rulesets.Mods { - public abstract class ModRateAdjust : Mod, IApplicableToTrack + public abstract class ModRateAdjust : Mod, IApplicableToAudio { public abstract BindableNumber SpeedChange { get; } @@ -16,6 +17,11 @@ namespace osu.Game.Rulesets.Mods track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange); } + public virtual void ApplyToSample(SampleChannel sample) + { + sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); + } + public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N2}x"; } } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index df059eef7d..cbd07efa97 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -10,10 +10,11 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Objects; +using osu.Framework.Audio.Sample; namespace osu.Game.Rulesets.Mods { - public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToBeatmap, IApplicableToTrack + public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToBeatmap, IApplicableToAudio { /// /// The point in the beatmap at which the final ramping rate should be reached. @@ -58,6 +59,11 @@ namespace osu.Game.Rulesets.Mods AdjustPitch.TriggerChange(); } + public void ApplyToSample(SampleChannel sample) + { + sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); + } + public virtual void ApplyToBeatmap(IBeatmap beatmap) { HitObject lastObject = beatmap.HitObjects.LastOrDefault(); diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 9e936c7717..77075b2abe 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -12,6 +12,7 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework.Utils; using osu.Game.Beatmaps.Legacy; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Objects.Legacy { @@ -356,7 +357,10 @@ namespace osu.Game.Rulesets.Objects.Legacy Bank = bankInfo.Normal, Name = HitSampleInfo.HIT_NORMAL, Volume = bankInfo.Volume, - CustomSampleBank = bankInfo.CustomSampleBank + CustomSampleBank = bankInfo.CustomSampleBank, + // if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample. + // None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds + IsLayered = type != LegacyHitSoundType.None && !type.HasFlag(LegacyHitSoundType.Normal) } }; @@ -409,7 +413,7 @@ namespace osu.Game.Rulesets.Objects.Legacy public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone(); } - internal class LegacyHitSampleInfo : HitSampleInfo + public class LegacyHitSampleInfo : HitSampleInfo { private int customSampleBank; @@ -424,6 +428,15 @@ namespace osu.Game.Rulesets.Objects.Legacy Suffix = value.ToString(); } } + + /// + /// Whether this hit sample is layered. + /// + /// + /// Layered hit samples are automatically added in all modes (except osu!mania), but can be disabled + /// using the skin config option. + /// + public bool IsLayered { get; set; } } private class FileHitSampleInfo : LegacyHitSampleInfo diff --git a/osu.Game/Screens/Select/ImportFromStablePopup.cs b/osu.Game/Screens/Select/ImportFromStablePopup.cs index 20494829ae..272f9566d5 100644 --- a/osu.Game/Screens/Select/ImportFromStablePopup.cs +++ b/osu.Game/Screens/Select/ImportFromStablePopup.cs @@ -12,7 +12,7 @@ namespace osu.Game.Screens.Select public ImportFromStablePopup(Action importFromStable) { HeaderText = @"You have no beatmaps!"; - BodyText = "An existing copy of osu! was found, though.\nWould you like to import your beatmaps, skins and scores?"; + BodyText = "An existing copy of osu! was found, though.\nWould you like to import your beatmaps, skins and scores?\nThis will create a second copy of all files on disk."; Icon = FontAwesome.Solid.Plane; diff --git a/osu.Game/Skinning/GlobalSkinConfiguration.cs b/osu.Game/Skinning/GlobalSkinConfiguration.cs index 8774fe5a97..d405702ea5 100644 --- a/osu.Game/Skinning/GlobalSkinConfiguration.cs +++ b/osu.Game/Skinning/GlobalSkinConfiguration.cs @@ -5,6 +5,7 @@ namespace osu.Game.Skinning { public enum GlobalSkinConfiguration { - AnimationFramerate + AnimationFramerate, + LayeredHitSounds, } } diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index 1131c93288..94a7a32f05 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Game.Audio; +using osu.Game.Rulesets.Objects.Legacy; namespace osu.Game.Skinning { @@ -28,7 +29,17 @@ namespace osu.Game.Skinning public Texture GetTexture(string componentName) => Source.GetTexture(componentName); - public virtual SampleChannel GetSample(ISampleInfo sampleInfo) => Source.GetSample(sampleInfo); + public virtual SampleChannel GetSample(ISampleInfo sampleInfo) + { + if (!(sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample)) + return Source.GetSample(sampleInfo); + + var playLayeredHitSounds = GetConfig(GlobalSkinConfiguration.LayeredHitSounds); + if (legacySample.IsLayered && playLayeredHitSounds?.Value == false) + return new SampleChannelVirtual(); + + return Source.GetSample(sampleInfo); + } public abstract IBindable GetConfig(TLookup lookup); } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 8292b02068..8eaf9ac652 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -1,11 +1,14 @@ // Copyright (c) ppy Pty Ltd . 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.Framework.Allocation; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; namespace osu.Game.Storyboards.Drawables { @@ -17,7 +20,8 @@ namespace osu.Game.Storyboards.Drawables private const double allowable_late_start = 100; private readonly StoryboardSampleInfo sampleInfo; - private SampleChannel channel; + + protected SampleChannel Channel { get; private set; } public override bool RemoveWhenNotAlive => false; @@ -28,12 +32,16 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(IBindable beatmap) + private void load(IBindable beatmap, IBindable> mods) { - channel = beatmap.Value.Skin.GetSample(sampleInfo); + Channel = beatmap.Value.Skin.GetSample(sampleInfo); + if (Channel == null) + return; - if (channel != null) - channel.Volume.Value = sampleInfo.Volume / 100.0; + Channel.Volume.Value = sampleInfo.Volume / 100.0; + + foreach (var mod in mods.Value.OfType()) + mod.ApplyToSample(Channel); } protected override void Update() @@ -44,7 +52,7 @@ namespace osu.Game.Storyboards.Drawables if (Time.Current < sampleInfo.StartTime) { // We've rewound before the start time of the sample - channel?.Stop(); + Channel?.Stop(); // In the case that the user fast-forwards to a point far beyond the start time of the sample, // we want to be able to fall into the if-conditional below (therefore we must not have a life time end) @@ -56,7 +64,7 @@ namespace osu.Game.Storyboards.Drawables // We've passed the start time of the sample. We only play the sample if we're within an allowable range // from the sample's start, to reduce layering if we've been fast-forwarded far into the future if (Time.Current - sampleInfo.StartTime < allowable_late_start) - channel?.Play(); + Channel?.Play(); // In the case that the user rewinds to a point far behind the start time of the sample, // we want to be able to fall into the if-conditional above (therefore we must not have a life time start) @@ -67,7 +75,7 @@ namespace osu.Game.Storyboards.Drawables protected override void Dispose(bool isDisposing) { - channel?.Stop(); + Channel?.Stop(); base.Dispose(isDisposing); } } diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index b4ce322165..ab4fb38657 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -24,6 +24,10 @@ namespace osu.Game.Tests.Beatmaps public abstract class HitObjectSampleTest : PlayerTestScene { protected abstract IResourceStore Resources { get; } + protected LegacySkin Skin { get; private set; } + + [Resolved] + private RulesetStore rulesetStore { get; set; } private readonly SkinInfo userSkinInfo = new SkinInfo(); @@ -64,6 +68,9 @@ namespace osu.Game.Tests.Beatmaps { using (var reader = new LineBufferedReader(Resources.GetStream($"Resources/SampleLookups/{filename}"))) currentTestBeatmap = Decoder.GetDecoder(reader).Decode(reader); + + // populate ruleset for beatmap converters that require it to be present. + currentTestBeatmap.BeatmapInfo.Ruleset = rulesetStore.GetRuleset(currentTestBeatmap.BeatmapInfo.RulesetID); }); }); } @@ -91,7 +98,7 @@ namespace osu.Game.Tests.Beatmaps }; // Need to refresh the cached skin source to refresh the skin resource store. - dependencies.SkinSource = new SkinProvidingContainer(new LegacySkin(userSkinInfo, userSkinResourceStore, Audio)); + dependencies.SkinSource = new SkinProvidingContainer(Skin = new LegacySkin(userSkinInfo, userSkinResourceStore, Audio)); }); } @@ -101,6 +108,9 @@ namespace osu.Game.Tests.Beatmaps protected void AssertUserLookup(string name) => AddAssert($"\"{name}\" looked up from user skin", () => !beatmapSkinResourceStore.PerformedLookups.Contains(name) && userSkinResourceStore.PerformedLookups.Contains(name)); + protected void AssertNoLookup(string name) => AddAssert($"\"{name}\" not looked up", + () => !beatmapSkinResourceStore.PerformedLookups.Contains(name) && !userSkinResourceStore.PerformedLookups.Contains(name)); + private class SkinSourceDependencyContainer : IReadOnlyDependencyContainer { public ISkinSource SkinSource;