diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs index f305b7255e..a5e2f02f31 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Ranking } } }, - new AccuracyCircle(score) + new AccuracyCircle(score, true) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/SoundDesign/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/SoundDesign/TestSceneAccuracyCircle.cs new file mode 100644 index 0000000000..c7ff7f9760 --- /dev/null +++ b/osu.Game.Tests/Visual/SoundDesign/TestSceneAccuracyCircle.cs @@ -0,0 +1,969 @@ +// 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.Collections.Generic; +using System.IO; +using Newtonsoft.Json; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Platform; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking.Expanded.Accuracy; +using osu.Game.Tests.Beatmaps; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Tests.Visual.SoundDesign +{ + [Serializable] + public class TestSceneAccuracyCircle : OsuTestScene + { + [Resolved] + private AudioManager audioManager { get; set; } + + [Resolved] + private OsuColour colours { get; set; } + + private DrawableSample previewSampleChannel; + private AccuracyCircleAudioSettings settings = new AccuracyCircleAudioSettings(); + private OsuTextBox saveFilename; + + private Storage presetStorage; + private FileSelector presetFileSelector; + + private Bindable sampleLoadTarget = new Bindable(); + private Bindable selectedSampleName = new Bindable(); + + private Container accuracyCircle; + + private enum SampleLoadTarget + { + ScoreTick, + BadgeDink, + BadgeDinkMax, + Swoosh, + ImpactD, + ImpactC, + ImpactB, + ImpactA, + ImpactS, + ImpactSS, + ApplauseD, + ApplauseC, + ApplauseB, + ApplauseA, + ApplauseS, + ApplauseSS, + }; + + private enum SectionTabs + { + [System.ComponentModel.Description("Score Ticks")] + ScoreTicks, + + [System.ComponentModel.Description("Badge Dinks")] + BadgeDinks, + + [System.ComponentModel.Description("Swoosh")] + Swoosh, + + [System.ComponentModel.Description("Impact")] + Impact, + + [System.ComponentModel.Description("Applause")] + Applause, + + [System.ComponentModel.Description("Preset")] + Preset + } + + private OsuTabControl tabSelector; + + private Dictionary tabContainers = new Dictionary(); + private FillFlowContainer sampleSelectContainer; + + private FileSelector sampleFileSelector; + + [BackgroundDependencyLoader] + private void load(GameHost host) + { + presetStorage = host.Storage.GetStorageForDirectory("presets"); + + Children = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("222") + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Padding = new MarginPadding(10), + Children = new Drawable[] + { + tabSelector = new OsuTabControl + { + RelativeSizeAxes = Axes.X, + Width = 1f, + Height = 24, + }, + + #region score ticks + + // ==================== SCORE TICKS ==================== + tabContainers[SectionTabs.ScoreTicks] = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Width = 1f, + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = "Play Ticks", + Current = { BindTarget = settings.PlayTicks } + }, + new SettingsSlider + { + LabelText = "Tick Volume (Start)", + Current = { BindTarget = settings.TickVolumeStart } + }, + new SettingsSlider + { + LabelText = "Tick Volume (End)", + Current = { BindTarget = settings.TickVolumeEnd } + }, + new SettingsSlider + { + LabelText = "ScoreTick Start Debounce Rate", + Current = { BindTarget = settings.TickDebounceStart } + }, + new SettingsSlider + { + LabelText = "ScoreTick End Debounce Rate", + Current = { BindTarget = settings.TickDebounceEnd } + }, + new OsuSpriteText + { + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + Text = "ScoreTick Rate Easing:" + }, + new SettingsEnumDropdown + { + Current = { BindTarget = settings.TickRateEasing } + }, + new SettingsSlider + { + LabelText = "ScoreTick Pitch Factor", + Current = { BindTarget = settings.TickPitchFactor } + }, + new OsuSpriteText + { + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + Text = "Pitch Easing:" + }, + new SettingsEnumDropdown + { + Current = { BindTarget = settings.TickPitchEasing } + }, + new OsuSpriteText + { + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + Text = "Volume Easing:" + }, + new SettingsEnumDropdown + { + Current = { BindTarget = settings.TickVolumeEasing } + }, + new OsuSpriteText + { + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + Text = "Tick Sample:" + }, + new OsuSpriteText + { + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2 }, + Current = { BindTarget = settings.TickSampleName } + } + } + }, + + #endregion + + #region badge dinks + + // ==================== BADGE DINKS ==================== + tabContainers[SectionTabs.BadgeDinks] = new FillFlowContainer + { + Alpha = 0, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Width = 1f, + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = "Play BadgeSounds", + Current = { BindTarget = settings.PlayBadgeSounds } + }, + new SettingsSlider + { + LabelText = "Badge Dink Volume", + Current = { BindTarget = settings.BadgeDinkVolume } + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Badge Dink Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.BadgeSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Badge Max Dink Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.BadgeMaxSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + } + } + }, + + #endregion + + #region swoosh + + // ==================== SWOOSHES ==================== + tabContainers[SectionTabs.Swoosh] = new FillFlowContainer + { + Alpha = 0, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Width = 1f, + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = "Play Swoosh", + Current = { BindTarget = settings.PlaySwooshSound } + }, + new SettingsSlider + { + LabelText = "Swoosh Volume", + Current = { BindTarget = settings.SwooshVolume } + }, + new SettingsSlider + { + LabelText = "Swoosh Pre-Delay (ms)", + Current = { BindTarget = settings.SwooshPreDelay } + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Swoosh Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.SwooshSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + } + } + }, + + #endregion + + #region impact + + // ==================== IMPACT ==================== + tabContainers[SectionTabs.Impact] = new FillFlowContainer + { + Alpha = 0, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Width = 1f, + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = "Play Impact", + Current = { BindTarget = settings.PlayImpact } + }, + new SettingsSlider + { + LabelText = "Impact Volume", + Current = { BindTarget = settings.ImpactVolume } + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Grade D Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.ImpactGradeDSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Grade C Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.ImpactGradeCSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Grade B Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.ImpactGradeBSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Grade A Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.ImpactGradeASampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Grade S Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.ImpactGradeSSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Grade SS Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.ImpactGradeSSSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + } + } + }, + + #endregion + + #region applause + + // ==================== APPLAUSE ==================== + tabContainers[SectionTabs.Applause] = new FillFlowContainer + { + Alpha = 0, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Width = 1f, + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = "Play Applause", + Current = { BindTarget = settings.PlayApplause } + }, + new SettingsSlider + { + LabelText = "Applause Volume", + Current = { BindTarget = settings.ApplauseVolume } + }, + new SettingsSlider + { + LabelText = "Applause Delay (ms)", + Current = { BindTarget = settings.ApplauseDelay } + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Grade D Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.ApplauseGradeDSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Grade C Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.ApplauseGradeCSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Grade B Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.ApplauseGradeBSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Grade A Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.ApplauseGradeASampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Grade S Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.ApplauseGradeSSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(), + Text = "Grade SS Sample:", + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + }, + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Current = { BindTarget = settings.ApplauseGradeSSSampleName }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS * 2f }, + } + } + }, + + #endregion + + #region preset + + // ==================== PRESET ==================== + tabContainers[SectionTabs.Preset] = new FillFlowContainer + { + Alpha = 0, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Width = 1f, + Children = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.Default.With(size: 24), + Text = "Load", + Colour = colours.Yellow + }, + presetFileSelector = new FileSelector(presetStorage.GetFullPath(string.Empty)) + { + RelativeSizeAxes = Axes.X, + Height = 300, + }, + new OsuSpriteText + { + Font = OsuFont.Default.With(size: 24), + Text = "Save", + Colour = colours.Yellow + }, + saveFilename = new OsuTextBox + { + PlaceholderText = "New preset filename", + RelativeSizeAxes = Axes.X, + }, + new TriangleButton + { + Text = "Save", + Action = savePreset, + RelativeSizeAxes = Axes.X, + }, + } + }, + + #endregion + + #region fileselector + + // ==================== SAMPLE SELECTOR ==================== + sampleSelectContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(10) + { + Top = 20, + }, + Width = 1f, + Children = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.Default.With(size: 20), + Text = "Load Sample", + Colour = colours.Yellow + }, + sampleFileSelector = new FileSelector("/Users/jamie/Sandbox/derp/Samples/Results") + { + RelativeSizeAxes = Axes.X, + Height = 300, + }, + new TriangleButton + { + Text = "Refresh", + Action = refreshSampleBrowser, + RelativeSizeAxes = Axes.X, + }, + new SettingsEnumDropdown + { + Current = { BindTarget = sampleLoadTarget } + }, + new TriangleButton + { + Text = "Load Sample", + Action = loadSample, + RelativeSizeAxes = Axes.X, + } + } + } + + #endregion + } + } + } + } + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Children = new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#555"), Color4Extensions.FromHex("#333")) + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Height = 0.5f, + Children = new[] + { + new TriangleButton + { + Text = "Low D Rank", + Action = CreateLowRankDCircle, + RelativeSizeAxes = Axes.X, + Width = 0.25f, + }, + new TriangleButton + { + Text = "D Rank", + Action = CreateDRankCircle, + RelativeSizeAxes = Axes.X, + Width = 0.25f, + }, + new TriangleButton + { + Text = "C Rank", + Action = CreateCRankCircle, + RelativeSizeAxes = Axes.X, + Width = 0.25f, + }, + new TriangleButton + { + Text = "B Rank", + Action = CreateBRankCircle, + RelativeSizeAxes = Axes.X, + Width = 0.25f, + }, + new TriangleButton + { + Text = "A Rank", + Action = CreateARankCircle, + RelativeSizeAxes = Axes.X, + Width = 0.25f, + }, + new TriangleButton + { + Text = "S Rank", + Action = CreateSRankCircle, + RelativeSizeAxes = Axes.X, + Width = 0.25f, + }, + new TriangleButton + { + Text = "Almost SS Rank", + Action = CreateAlmostSSRankCircle, + RelativeSizeAxes = Axes.X, + Width = 0.25f, + }, + new TriangleButton + { + Text = "SS Rank", + Action = CreateSSRankCircle, + RelativeSizeAxes = Axes.X, + Width = 0.25f, + }, + } + }, + accuracyCircle = new Container + { + RelativeSizeAxes = Axes.Both, + // Child = CreateRankDCircle() + } + } + } + } + }, + }; + + presetFileSelector.CurrentFile.ValueChanged += value => + { + string path = value.NewValue.FullName; + + loadPreset(path); + saveFilename.Text = Path.GetFileNameWithoutExtension(path); + }; + + sampleFileSelector.CurrentFile.ValueChanged += value => + { + var sample = Path.GetFileNameWithoutExtension(value.NewValue.Name); + + previewSampleChannel?.Dispose(); + previewSampleChannel = new DrawableSample(audioManager.Samples.Get($"Results/{sample}")); + previewSampleChannel?.Play(); + + selectedSampleName.Value = sample; + }; + + tabSelector.Current.ValueChanged += tab => + { + tabContainers[tab.OldValue].Hide(); + tabContainers[tab.NewValue].Show(); + + switch (tab.NewValue) + { + case SectionTabs.Preset: + sampleSelectContainer.Hide(); + break; + + case SectionTabs.Impact: + sampleLoadTarget.Value = SampleLoadTarget.ImpactD; + sampleSelectContainer.Show(); + break; + + case SectionTabs.Swoosh: + sampleLoadTarget.Value = SampleLoadTarget.Swoosh; + sampleSelectContainer.Show(); + break; + + case SectionTabs.BadgeDinks: + sampleLoadTarget.Value = SampleLoadTarget.BadgeDink; + sampleSelectContainer.Show(); + break; + + case SectionTabs.ScoreTicks: + sampleLoadTarget.Value = SampleLoadTarget.ScoreTick; + sampleSelectContainer.Show(); + break; + + case SectionTabs.Applause: + sampleLoadTarget.Value = SampleLoadTarget.ApplauseD; + sampleSelectContainer.Show(); + break; + } + }; + } + + #region rank scenarios + + [Test] + public void TestDoNothing() => AddStep("show", () => + { + /* do nothing */ + }); + + [Test] + public void TestLowDRank() => AddStep("show", CreateLowRankDCircle); + + [Test] + public void TestDRank() => AddStep("show", CreateDRankCircle); + + [Test] + public void TestCRank() => AddStep("show", CreateCRankCircle); + + [Test] + public void TestBRank() => AddStep("show", CreateBRankCircle); + + [Test] + public void TestARank() => AddStep("show", CreateARankCircle); + + [Test] + public void TestSRank() => AddStep("show", CreateSRankCircle); + + [Test] + public void TestAlmostSSRank() => AddStep("show", CreateAlmostSSRankCircle); + + [Test] + public void TestSSRank() => AddStep("show", CreateSSRankCircle); + + #endregion + + public void CreateLowRankDCircle() => + accuracyCircle.Child = CreateAccuracyCircle(createScore(0.2, ScoreRank.D)); + + public void CreateDRankCircle() => + accuracyCircle.Child = CreateAccuracyCircle(createScore(0.5, ScoreRank.D)); + + public void CreateCRankCircle() => + accuracyCircle.Child = CreateAccuracyCircle(createScore(0.75, ScoreRank.C)); + + public void CreateBRankCircle() => + accuracyCircle.Child = CreateAccuracyCircle(createScore(0.85, ScoreRank.B)); + + public void CreateARankCircle() => + accuracyCircle.Child = CreateAccuracyCircle(createScore(0.925, ScoreRank.A)); + + public void CreateSRankCircle() => + accuracyCircle.Child = CreateAccuracyCircle(createScore(0.975, ScoreRank.S)); + + public void CreateAlmostSSRankCircle() => + accuracyCircle.Child = CreateAccuracyCircle(createScore(0.9999, ScoreRank.S)); + + public void CreateSSRankCircle() => + accuracyCircle.Child = CreateAccuracyCircle(createScore(1, ScoreRank.X)); + + public AccuracyCircle CreateAccuracyCircle(ScoreInfo score) + { + var newAccuracyCircle = new AccuracyCircle(score, true) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(230), + }; + + newAccuracyCircle.BindAudioSettings(settings); + + return newAccuracyCircle; + } + + private void savePreset() + { + string path = presetStorage.GetFullPath($"{saveFilename.Text}.json", true); + File.WriteAllText(path, JsonConvert.SerializeObject(settings)); + presetFileSelector.CurrentFile.Value = new FileInfo(path); + } + + private void loadPreset(string filename) + { + var saved = JsonConvert.DeserializeObject(File.ReadAllText(presetStorage.GetFullPath(filename))); + + foreach (var (_, prop) in saved.GetSettingsSourceProperties()) + { + var targetBindable = (IBindable)prop.GetValue(settings); + var sourceBindable = (IBindable)prop.GetValue(saved); + + ((IParseable)targetBindable)?.Parse(sourceBindable); + } + } + + private void refreshSampleBrowser() => + sampleFileSelector.CurrentPath.Value = new DirectoryInfo(sampleFileSelector.CurrentPath.Value.FullName); + + private void loadSample() + { + switch (sampleLoadTarget.Value) + { + case SampleLoadTarget.Swoosh: + settings.SwooshSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ScoreTick: + settings.TickSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.BadgeDink: + settings.BadgeSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.BadgeDinkMax: + settings.BadgeMaxSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ImpactD: + settings.ImpactGradeDSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ImpactC: + settings.ImpactGradeCSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ImpactB: + settings.ImpactGradeBSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ImpactA: + settings.ImpactGradeASampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ImpactS: + settings.ImpactGradeSSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ImpactSS: + settings.ImpactGradeSSSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ApplauseD: + settings.ApplauseGradeDSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ApplauseC: + settings.ApplauseGradeCSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ApplauseB: + settings.ApplauseGradeBSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ApplauseA: + settings.ApplauseGradeASampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ApplauseS: + settings.ApplauseGradeSSampleName.Value = selectedSampleName.Value; + break; + + case SampleLoadTarget.ApplauseSS: + settings.ApplauseGradeSSSampleName.Value = selectedSampleName.Value; + break; + } + } + + private ScoreInfo createScore(double accuracy = 0.95, ScoreRank rank = ScoreRank.S) => new ScoreInfo + { + User = new User + { + Id = 2, + Username = "peppy", + }, + Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, + Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, + TotalScore = 2845370, + Accuracy = accuracy, + MaxCombo = 999, + Rank = rank, + Date = DateTimeOffset.Now, + Statistics = + { + { HitResult.Miss, 1 }, + { HitResult.Meh, 50 }, + { HitResult.Good, 100 }, + { HitResult.Great, 300 }, + } + }; + } +} diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index c70b4dd35b..82f2bc8c29 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -3,13 +3,18 @@ using System; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Platform; using osu.Framework.Utils; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; @@ -79,14 +84,69 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private Container badges; private RankText rankText; - public AccuracyCircle(ScoreInfo score) + private DrawableSample scoreTickSound; + private DrawableSample badgeTickSound; + private DrawableSample badgeMaxSound; + private DrawableSample swooshUpSound; + private DrawableSample rankDImpactSound; + private DrawableSample rankBImpactSound; + private DrawableSample rankCImpactSound; + private DrawableSample rankAImpactSound; + private DrawableSample rankSImpactSound; + private DrawableSample rankSSImpactSound; + private DrawableSample rankDApplauseSound; + private DrawableSample rankBApplauseSound; + private DrawableSample rankCApplauseSound; + private DrawableSample rankAApplauseSound; + private DrawableSample rankSApplauseSound; + private DrawableSample rankSSApplauseSound; + + private Bindable tickPlaybackRate = new Bindable(); + private double lastTickPlaybackTime; + private bool isTicking; + + private AudioManager audioManager; + + public AccuracyCircleAudioSettings AudioSettings = new AccuracyCircleAudioSettings(); + + private readonly bool withFlair; + + public AccuracyCircle(ScoreInfo score, bool withFlair) { this.score = score; + this.withFlair = withFlair; + } + + public void BindAudioSettings(AccuracyCircleAudioSettings audioSettings) + { + foreach (var (_, prop) in audioSettings.GetSettingsSourceProperties()) + { + var targetBindable = (IBindable)prop.GetValue(AudioSettings); + var sourceBindable = (IBindable)prop.GetValue(audioSettings); + + targetBindable?.BindTo(sourceBindable); + } + } + + private void loadSample(ref DrawableSample target, string sampleName, [CanBeNull] BindableDouble volumeBindable = null) + { + if (IsDisposed) return; + + target?.Expire(); + AddInternal(target = new DrawableSample(audioManager.Samples.Get($"Results/{sampleName}")) + { + Frequency = { Value = 1.0 } + }); + + if (volumeBindable != null) + target.Volume.BindTarget = volumeBindable; } [BackgroundDependencyLoader] - private void load(AudioManager audio) + private void load(AudioManager audio, GameHost host) { + audioManager = audio; + InternalChildren = new Drawable[] { new SmoothCircularProgress @@ -204,6 +264,35 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy }, rankText = new RankText(score.Rank) }; + + if (withFlair) + { + tickPlaybackRate = new Bindable(AudioSettings.TickDebounceStart.Value); + + // score ticks + AudioSettings.TickSampleName.BindValueChanged(sample => loadSample(ref scoreTickSound, sample.NewValue), true); + AudioSettings.SwooshSampleName.BindValueChanged(sample => loadSample(ref swooshUpSound, sample.NewValue, AudioSettings.SwooshVolume), true); + + // badge sounds + AudioSettings.BadgeSampleName.BindValueChanged(sample => loadSample(ref badgeTickSound, sample.NewValue, AudioSettings.BadgeDinkVolume), true); + AudioSettings.BadgeMaxSampleName.BindValueChanged(sample => loadSample(ref badgeMaxSound, sample.NewValue, AudioSettings.BadgeDinkVolume), true); + + // impacts + AudioSettings.ImpactGradeDSampleName.BindValueChanged(sample => loadSample(ref rankDImpactSound, sample.NewValue, AudioSettings.ImpactVolume), true); + AudioSettings.ImpactGradeCSampleName.BindValueChanged(sample => loadSample(ref rankCImpactSound, sample.NewValue, AudioSettings.ImpactVolume), true); + AudioSettings.ImpactGradeBSampleName.BindValueChanged(sample => loadSample(ref rankBImpactSound, sample.NewValue, AudioSettings.ImpactVolume), true); + AudioSettings.ImpactGradeASampleName.BindValueChanged(sample => loadSample(ref rankAImpactSound, sample.NewValue, AudioSettings.ImpactVolume), true); + AudioSettings.ImpactGradeSSampleName.BindValueChanged(sample => loadSample(ref rankSImpactSound, sample.NewValue, AudioSettings.ImpactVolume), true); + AudioSettings.ImpactGradeSSSampleName.BindValueChanged(sample => loadSample(ref rankSSImpactSound, sample.NewValue, AudioSettings.ImpactVolume), true); + + // applause + AudioSettings.ApplauseGradeDSampleName.BindValueChanged(sample => loadSample(ref rankDApplauseSound, sample.NewValue, AudioSettings.ApplauseVolume), true); + AudioSettings.ApplauseGradeCSampleName.BindValueChanged(sample => loadSample(ref rankCApplauseSound, sample.NewValue, AudioSettings.ApplauseVolume), true); + AudioSettings.ApplauseGradeBSampleName.BindValueChanged(sample => loadSample(ref rankBApplauseSound, sample.NewValue, AudioSettings.ApplauseVolume), true); + AudioSettings.ApplauseGradeASampleName.BindValueChanged(sample => loadSample(ref rankAApplauseSound, sample.NewValue, AudioSettings.ApplauseVolume), true); + AudioSettings.ApplauseGradeSSampleName.BindValueChanged(sample => loadSample(ref rankSApplauseSound, sample.NewValue, AudioSettings.ApplauseVolume), true); + AudioSettings.ApplauseGradeSSSampleName.BindValueChanged(sample => loadSample(ref rankSSApplauseSound, sample.NewValue, AudioSettings.ApplauseVolume), true); + } } private ScoreRank getRank(ScoreRank rank) @@ -214,12 +303,29 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy return rank; } + protected override void Update() + { + base.Update(); + + if (!AudioSettings.PlayTicks.Value || !isTicking) return; + + bool enoughTimePassedSinceLastPlayback = Clock.CurrentTime - lastTickPlaybackTime >= tickPlaybackRate.Value; + + if (!enoughTimePassedSinceLastPlayback) return; + + scoreTickSound?.Play(); + lastTickPlaybackTime = Clock.CurrentTime; + } + protected override void LoadComplete() { base.LoadComplete(); this.ScaleTo(0).Then().ScaleTo(1, APPEAR_DURATION, Easing.OutQuint); + if (AudioSettings.PlaySwooshSound.Value) + this.Delay(AudioSettings.SwooshPreDelay.Value).Schedule(() => swooshUpSound?.Play()); + using (BeginDelayedSequence(RANK_CIRCLE_TRANSFORM_DELAY, true)) innerMask.FillTo(1f, RANK_CIRCLE_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING); @@ -229,6 +335,22 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy accuracyCircle.FillTo(targetAccuracy, ACCURACY_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING); + if (AudioSettings.PlayTicks.Value) + { + scoreTickSound?.FrequencyTo(1 + (targetAccuracy * AudioSettings.TickPitchFactor.Value), ACCURACY_TRANSFORM_DURATION, AudioSettings.TickPitchEasing.Value); + scoreTickSound?.VolumeTo(AudioSettings.TickVolumeStart.Value).Then().VolumeTo(AudioSettings.TickVolumeEnd.Value, ACCURACY_TRANSFORM_DURATION, AudioSettings.TickVolumeEasing.Value); + this.TransformBindableTo(tickPlaybackRate, AudioSettings.TickDebounceEnd.Value, ACCURACY_TRANSFORM_DURATION, AudioSettings.TickRateEasing.Value); + } + + Schedule(() => + { + if (!AudioSettings.PlayTicks.Value) return; + + isTicking = true; + }); + + int badgeNum = 0; + foreach (var badge in badges) { if (badge.Accuracy > score.Accuracy) @@ -237,12 +359,100 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy using (BeginDelayedSequence(inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(1 - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION, true)) { badge.Appear(); + Schedule(() => + { + if (badgeTickSound == null || badgeMaxSound == null || !AudioSettings.PlayBadgeSounds.Value) return; + + if (badgeNum < (badges.Count - 1)) + { + badgeTickSound.Frequency.Value = 1 + (badgeNum++ * 0.05); + badgeTickSound?.Play(); + } + else + { + badgeMaxSound.Frequency.Value = 1 + (badgeNum++ * 0.05); + badgeMaxSound?.Play(); + isTicking = false; + } + }); } } using (BeginDelayedSequence(TEXT_APPEAR_DELAY, true)) { rankText.Appear(); + Schedule(() => + { + isTicking = false; + + if (!AudioSettings.PlayImpact.Value) return; + + switch (score.Rank) + { + case ScoreRank.D: + rankDImpactSound?.Play(); + break; + + case ScoreRank.C: + rankCImpactSound?.Play(); + break; + + case ScoreRank.B: + rankBImpactSound?.Play(); + break; + + case ScoreRank.A: + rankAImpactSound?.Play(); + break; + + case ScoreRank.S: + case ScoreRank.SH: + rankSImpactSound?.Play(); + break; + + case ScoreRank.X: + case ScoreRank.XH: + rankSSImpactSound?.Play(); + break; + } + }); + + using (BeginDelayedSequence(AudioSettings.ApplauseDelay.Value)) + { + if (!AudioSettings.PlayApplause.Value) return; + + Schedule(() => + { + switch (score.Rank) + { + case ScoreRank.D: + rankDApplauseSound?.Play(); + break; + + case ScoreRank.C: + rankCApplauseSound?.Play(); + break; + + case ScoreRank.B: + rankBApplauseSound?.Play(); + break; + + case ScoreRank.A: + rankAApplauseSound?.Play(); + break; + + case ScoreRank.S: + case ScoreRank.SH: + rankSApplauseSound?.Play(); + break; + + case ScoreRank.X: + case ScoreRank.XH: + rankSSApplauseSound?.Play(); + break; + } + }); + } } } } @@ -266,4 +476,164 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy return test; } } + + public class AccuracyCircleAudioSettings + { + [SettingSource("setting")] + public Bindable PlayTicks { get; } = new Bindable(true); + + [SettingSource("setting")] + public Bindable TickSampleName { get; } = new Bindable("badge-dink-2"); + + [SettingSource("setting")] + public Bindable PlayBadgeSounds { get; } = new Bindable(true); + + [SettingSource("setting")] + public Bindable BadgeSampleName { get; } = new Bindable("badge-dink-3"); + + [SettingSource("setting")] + public Bindable BadgeMaxSampleName { get; } = new Bindable("badge-dink-8"); + + [SettingSource("setting")] + public Bindable PlaySwooshSound { get; } = new Bindable(true); + + [SettingSource("setting")] + public Bindable SwooshSampleName { get; } = new Bindable("swoosh-up-2"); + + [SettingSource("setting")] + public Bindable PlayImpact { get; } = new Bindable(true); + + [SettingSource("setting")] + public Bindable ImpactGradeDSampleName { get; } = new Bindable("rank-impact-d-1"); + + [SettingSource("setting")] + public Bindable ImpactGradeCSampleName { get; } = new Bindable("rank-impact-c-3"); + + [SettingSource("setting")] + public Bindable ImpactGradeBSampleName { get; } = new Bindable("rank-impact-b-3"); + + [SettingSource("setting")] + public Bindable ImpactGradeASampleName { get; } = new Bindable("rank-impact-a-3"); + + [SettingSource("setting")] + public Bindable ImpactGradeSSampleName { get; } = new Bindable("rank-impact-s-3"); + + [SettingSource("setting")] + public Bindable ImpactGradeSSSampleName { get; } = new Bindable("rank-impact-s-3"); + + [SettingSource("setting")] + public Bindable PlayApplause { get; } = new Bindable(true); + + [SettingSource("setting")] + public BindableDouble ApplauseVolume { get; } = new BindableDouble(1) + { + MinValue = 0, + MaxValue = 1, + Precision = 0.1, + }; + + [SettingSource("setting")] + public BindableDouble ApplauseDelay { get; } = new BindableDouble(545) + { + MinValue = 0, + MaxValue = 10000, + Precision = 1, + }; + + [SettingSource("setting")] + public Bindable ApplauseGradeDSampleName { get; } = new Bindable("rank-applause-d-1"); + + [SettingSource("setting")] + public Bindable ApplauseGradeCSampleName { get; } = new Bindable("rank-applause-c-1"); + + [SettingSource("setting")] + public Bindable ApplauseGradeBSampleName { get; } = new Bindable("rank-applause-b-1"); + + [SettingSource("setting")] + public Bindable ApplauseGradeASampleName { get; } = new Bindable("rank-applause-a-1"); + + [SettingSource("setting")] + public Bindable ApplauseGradeSSampleName { get; } = new Bindable("rank-applause-s-1"); + + [SettingSource("setting")] + public Bindable ApplauseGradeSSSampleName { get; } = new Bindable("rank-applause-s-1"); + + [SettingSource("setting")] + public BindableDouble TickPitchFactor { get; } = new BindableDouble(1) + { + MinValue = 0, + MaxValue = 3, + Precision = 0.1, + }; + + [SettingSource("setting")] + public BindableDouble TickDebounceStart { get; } = new BindableDouble(10) + { + MinValue = 1, + MaxValue = 100, + }; + + [SettingSource("setting")] + public BindableDouble TickDebounceEnd { get; } = new BindableDouble(400) + { + MinValue = 100, + MaxValue = 1000, + }; + + [SettingSource("setting")] + public BindableDouble SwooshPreDelay { get; } = new BindableDouble(450) + { + MinValue = -1000, + MaxValue = 1000, + }; + + [SettingSource("setting")] + public Bindable TickRateEasing { get; } = new Bindable(Easing.None); + + [SettingSource("setting")] + public Bindable TickPitchEasing { get; } = new Bindable(Easing.None); + + [SettingSource("setting")] + public Bindable TickVolumeEasing { get; } = new Bindable(Easing.OutSine); + + [SettingSource("setting")] + public BindableDouble TickVolumeStart { get; } = new BindableDouble(0.6) + { + MinValue = 0, + MaxValue = 1, + Precision = 0.1, + }; + + [SettingSource("setting")] + public BindableDouble TickVolumeEnd { get; } = new BindableDouble(1.0) + { + MinValue = 0, + MaxValue = 1, + Precision = 0.1, + }; + + [SettingSource("setting")] + public BindableDouble ImpactVolume { get; } = new BindableDouble(1.0) + { + MinValue = 0, + MaxValue = 1, + Precision = 0.1, + }; + + [SettingSource("setting")] + public BindableDouble BadgeDinkVolume { get; } = new BindableDouble(0.5) + { + MinValue = 0, + MaxValue = 1, + Precision = 0.1, + }; + + [SettingSource("setting")] + public BindableDouble SwooshVolume { get; } = new BindableDouble(0.5) + { + MinValue = 0, + MaxValue = 1, + Precision = 0.1, + }; + } } diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 4895240314..6a6b39b61c 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -122,7 +122,7 @@ namespace osu.Game.Screens.Ranking.Expanded Margin = new MarginPadding { Top = 40 }, RelativeSizeAxes = Axes.X, Height = 230, - Child = new AccuracyCircle(score) + Child = new AccuracyCircle(score, withFlair) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index a0ea27b640..95dd9f72a8 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -156,13 +156,6 @@ namespace osu.Game.Screens.Ranking bool shouldFlair = player != null && !Score.Mods.Any(m => m is ModAutoplay); ScorePanelList.AddScore(Score, shouldFlair); - - if (shouldFlair) - { - AddInternal(applauseSound = Score.Rank >= ScoreRank.A - ? new SkinnableSound(new SampleInfo("Results/rankpass", "applause")) - : new SkinnableSound(new SampleInfo("Results/rankfail"))); - } } if (allowWatchingReplay)