From ea77ea4a08757dc77cf2d1470666395efe79f349 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 17:24:05 +0900 Subject: [PATCH 01/53] Add basic testing of new beatmaps persistence --- .../Editing/TestSceneEditorBeatmapCreation.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs new file mode 100644 index 0000000000..3cc95dec9e --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Storyboards; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneEditorBeatmapCreation : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new DummyWorkingBeatmap(Audio, null); + + [Test] + public void TestCreateNewBeatmap() + { + AddStep("save beatmap", () => Editor.Save()); + AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.ID > 0); + } + } +} From 4b9581bca0cbab33263239a8204dece936de3faa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 18:18:50 +0900 Subject: [PATCH 02/53] Add audio selection to song setup screen --- .../UserInterfaceV2/LabelledTextBox.cs | 9 ++- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 80 +++++++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index 290aba3468..72d25a7836 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -44,12 +44,17 @@ namespace osu.Game.Graphics.UserInterfaceV2 Component.BorderColour = colours.Blue; } - protected override OsuTextBox CreateComponent() => new OsuTextBox + protected virtual OsuTextBox CreateTextBox() => new OsuTextBox { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, CornerRadius = CORNER_RADIUS, - }.With(t => t.OnCommit += (sender, newText) => OnCommit?.Invoke(sender, newText)); + }; + + protected override OsuTextBox CreateComponent() => CreateTextBox().With(t => + { + t.OnCommit += (sender, newText) => OnCommit?.Invoke(sender, newText); + }); } } diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index a2c8f19016..b0de9e6674 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -1,16 +1,21 @@ // 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.IO; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; using osu.Game.Beatmaps.Drawables; 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 osuTK; @@ -23,10 +28,17 @@ namespace osu.Game.Screens.Edit.Setup private LabelledTextBox titleTextBox; private LabelledTextBox creatorTextBox; private LabelledTextBox difficultyTextBox; + private LabelledTextBox audioTrackTextBox; [BackgroundDependencyLoader] private void load(OsuColour colours) { + Container audioTrackFileChooserContainer = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }; + Child = new Container { RelativeSizeAxes = Axes.Both, @@ -70,6 +82,18 @@ namespace osu.Game.Screens.Edit.Setup }, }, new OsuSpriteText + { + Text = "Resources" + }, + audioTrackTextBox = new FileChooserLabelledTextBox + { + Label = "Audio Track", + Current = { Value = Beatmap.Value.Metadata.AudioFile ?? "Click to select a track" }, + Target = audioTrackFileChooserContainer, + TabbableContentContainer = this + }, + audioTrackFileChooserContainer, + new OsuSpriteText { Text = "Beatmap metadata" }, @@ -120,4 +144,60 @@ namespace osu.Game.Screens.Edit.Setup Beatmap.Value.BeatmapInfo.Version = difficultyTextBox.Current.Value; } } + + internal class FileChooserLabelledTextBox : LabelledTextBox + { + public Container Target; + + private readonly IBindable currentFile = new Bindable(); + + public FileChooserLabelledTextBox() + { + currentFile.BindValueChanged(onFileSelected); + } + + private void onFileSelected(ValueChangedEvent file) + { + if (file.NewValue == null) + return; + + Target.Clear(); + Current.Value = file.NewValue.FullName; + } + + protected override OsuTextBox CreateTextBox() => + new FileChooserOsuTextBox + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + CornerRadius = CORNER_RADIUS, + OnFocused = DisplayFileChooser + }; + + public void DisplayFileChooser() + { + Target.Child = new FileSelector("/Users/Dean/.osu/Songs", new[] { ".mp3", ".ogg" }) + { + RelativeSizeAxes = Axes.X, + Height = 400, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + CurrentFile = { BindTarget = currentFile } + }; + } + + internal class FileChooserOsuTextBox : OsuTextBox + { + public Action OnFocused; + + protected override void OnFocus(FocusEvent e) + { + OnFocused?.Invoke(); + base.OnFocus(e); + + GetContainingInputManager().TriggerFocusContention(this); + } + } + } } From 4d714866cdba4d3ef7c171c6d3f9316eb8cd5d43 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 18:30:02 +0900 Subject: [PATCH 03/53] Add ability to actually import a new audio file to the beatmap / database --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 30 +++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index b0de9e6674..02b8afffe9 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.IO; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -11,13 +10,16 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; 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.IO; using osuTK; +using FileInfo = System.IO.FileInfo; namespace osu.Game.Screens.Edit.Setup { @@ -128,10 +130,36 @@ namespace osu.Game.Screens.Edit.Setup } }; + audioTrackTextBox.Current.BindValueChanged(audioTrackChanged); + foreach (var item in flow.OfType()) item.OnCommit += onCommit; } + [Resolved] + private FileStore files { get; set; } + + private void audioTrackChanged(ValueChangedEvent filePath) + { + var info = new FileInfo(filePath.NewValue); + + if (!info.Exists) + audioTrackTextBox.Current.Value = filePath.OldValue; + + IO.FileInfo osuFileInfo; + + using (var stream = info.OpenRead()) + osuFileInfo = files.Add(stream); + + Beatmap.Value.BeatmapSetInfo.Files.Add(new BeatmapSetFileInfo + { + FileInfo = osuFileInfo, + Filename = info.Name + }); + + Beatmap.Value.Metadata.AudioFile = info.Name; + } + private void onCommit(TextBox sender, bool newText) { if (!newText) return; From 65e6dd2ac3e01f205b9d4b838696bd49acf2a7d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 18:34:17 +0900 Subject: [PATCH 04/53] Remove the previous audio file before adding a new one --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 02b8afffe9..ce201e544a 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -144,14 +144,29 @@ namespace osu.Game.Screens.Edit.Setup var info = new FileInfo(filePath.NewValue); if (!info.Exists) + { audioTrackTextBox.Current.Value = filePath.OldValue; + return; + } + var beatmapFiles = Beatmap.Value.BeatmapSetInfo.Files; + + // remove the old file + var oldFile = beatmapFiles.FirstOrDefault(f => f.Filename == filePath.OldValue); + + if (oldFile != null) + { + beatmapFiles.Remove(oldFile); + files.Dereference(oldFile.FileInfo); + } + + // add the new file IO.FileInfo osuFileInfo; using (var stream = info.OpenRead()) osuFileInfo = files.Add(stream); - Beatmap.Value.BeatmapSetInfo.Files.Add(new BeatmapSetFileInfo + beatmapFiles.Add(new BeatmapSetFileInfo { FileInfo = osuFileInfo, Filename = info.Name From 978f6edf38488d7299f6e6df27a2887c74a077a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 18:55:49 +0900 Subject: [PATCH 05/53] Add basic track reloading support while inside the editor --- osu.Game/Overlays/MusicController.cs | 5 +++++ osu.Game/Screens/Edit/Editor.cs | 17 +++++++++++++++-- osu.Game/Screens/Edit/EditorClock.cs | 9 +++++++-- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 11 +++++++++++ 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index b568e4d02b..66c9b15c0a 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -81,6 +81,11 @@ namespace osu.Game.Overlays mods.BindValueChanged(_ => ResetTrackAdjustments(), true); } + /// + /// Forcefully reload the current 's track from disk. + /// + public void ForceReloadCurrentBeatmap() => changeTrack(); + /// /// Change the position of a in the current playlist. /// diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b7a59bc2e2..74b92f3168 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -43,6 +43,7 @@ using osuTK.Input; namespace osu.Game.Screens.Edit { [Cached(typeof(IBeatSnapProvider))] + [Cached] public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler, IKeyBindingHandler, IBeatSnapProvider { public override float BackgroundParallaxAmount => 0.1f; @@ -91,6 +92,9 @@ namespace osu.Game.Screens.Edit [Resolved] private IAPIProvider api { get; set; } + [Resolved] + private MusicController music { get; set; } + [BackgroundDependencyLoader] private void load(OsuColour colours, GameHost host) { @@ -98,9 +102,9 @@ namespace osu.Game.Screens.Edit beatDivisor.BindValueChanged(divisor => Beatmap.Value.BeatmapInfo.BeatDivisor = divisor.NewValue); // Todo: should probably be done at a DrawableRuleset level to share logic with Player. - var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock(); clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false }; - clock.ChangeSource(sourceClock); + + UpdateClockSource(); dependencies.CacheAs(clock); AddInternal(clock); @@ -271,6 +275,15 @@ namespace osu.Game.Screens.Edit bottomBackground.Colour = colours.Gray2; } + /// + /// If the beatmap's track has changed, this method must be called to keep the editor in a valid state. + /// + public void UpdateClockSource() + { + var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock(); + clock.ChangeSource(sourceClock); + } + protected void Save() { // apply any set-level metadata changes. diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index d4d0feb813..a829f23697 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using osu.Framework.Audio.Track; using osu.Framework.Graphics; using osu.Framework.Graphics.Transforms; using osu.Framework.Utils; @@ -17,7 +18,7 @@ namespace osu.Game.Screens.Edit /// public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock { - public readonly double TrackLength; + public double TrackLength; public ControlPointInfo ControlPointInfo; @@ -190,7 +191,11 @@ namespace osu.Game.Screens.Edit public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo; - public void ChangeSource(IClock source) => underlyingClock.ChangeSource(source); + public void ChangeSource(IClock source) + { + underlyingClock.ChangeSource(source); + TrackLength = (source as Track)?.Length ?? 60000; + } public IClock Source => underlyingClock.Source; diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index ce201e544a..075815203c 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -18,6 +18,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.IO; +using osu.Game.Overlays; using osuTK; using FileInfo = System.IO.FileInfo; @@ -139,6 +140,12 @@ namespace osu.Game.Screens.Edit.Setup [Resolved] private FileStore files { get; set; } + [Resolved] + private MusicController music { get; set; } + + [Resolved] + private Editor editor { get; set; } + private void audioTrackChanged(ValueChangedEvent filePath) { var info = new FileInfo(filePath.NewValue); @@ -173,6 +180,10 @@ namespace osu.Game.Screens.Edit.Setup }); Beatmap.Value.Metadata.AudioFile = info.Name; + + music.ForceReloadCurrentBeatmap(); + + editor.UpdateClockSource(); } private void onCommit(TextBox sender, bool newText) From 7e7e2fd64a6c9e95919028e838fd5439971d83b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 19:01:28 +0900 Subject: [PATCH 06/53] Use bindable for track to fix rate adjustments not applying correctly --- osu.Game/Screens/Edit/Components/BottomBarContainer.cs | 7 +++++-- osu.Game/Screens/Edit/Components/PlaybackControl.cs | 4 ++-- osu.Game/Screens/Edit/EditorClock.cs | 10 +++++++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/BottomBarContainer.cs b/osu.Game/Screens/Edit/Components/BottomBarContainer.cs index cb5078a479..08091fc3f7 100644 --- a/osu.Game/Screens/Edit/Components/BottomBarContainer.cs +++ b/osu.Game/Screens/Edit/Components/BottomBarContainer.cs @@ -18,7 +18,8 @@ namespace osu.Game.Screens.Edit.Components private const float contents_padding = 15; protected readonly IBindable Beatmap = new Bindable(); - protected Track Track => Beatmap.Value.Track; + + protected readonly IBindable Track = new Bindable(); private readonly Drawable background; private readonly Container content; @@ -42,9 +43,11 @@ namespace osu.Game.Screens.Edit.Components } [BackgroundDependencyLoader] - private void load(IBindable beatmap, OsuColour colours) + private void load(IBindable beatmap, OsuColour colours, EditorClock clock) { Beatmap.BindTo(beatmap); + Track.BindTo(clock.Track); + background.Colour = colours.Gray1; } } diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 59b3d1c565..9739f2876a 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -62,12 +62,12 @@ namespace osu.Game.Screens.Edit.Components } }; - Track?.AddAdjustment(AdjustableProperty.Tempo, tempo); + Track.BindValueChanged(tr => tr.NewValue?.AddAdjustment(AdjustableProperty.Tempo, tempo), true); } protected override void Dispose(bool isDisposing) { - Track?.RemoveAdjustment(AdjustableProperty.Tempo, tempo); + Track.Value?.RemoveAdjustment(AdjustableProperty.Tempo, tempo); base.Dispose(isDisposing); } diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index a829f23697..ec203df064 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using osu.Framework.Audio.Track; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Transforms; using osu.Framework.Utils; @@ -18,7 +19,11 @@ namespace osu.Game.Screens.Edit /// public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock { - public double TrackLength; + public IBindable Track => track; + + private readonly Bindable track = new Bindable(); + + public double TrackLength => track.Value?.Length ?? 60000; public ControlPointInfo ControlPointInfo; @@ -36,7 +41,6 @@ namespace osu.Game.Screens.Edit this.beatDivisor = beatDivisor; ControlPointInfo = controlPointInfo; - TrackLength = trackLength; underlyingClock = new DecoupleableInterpolatingFramedClock(); } @@ -193,8 +197,8 @@ namespace osu.Game.Screens.Edit public void ChangeSource(IClock source) { + track.Value = source as Track; underlyingClock.ChangeSource(source); - TrackLength = (source as Track)?.Length ?? 60000; } public IClock Source => underlyingClock.Source; From 833ff1c1d77df62adf003cfa30c1753699e59a77 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 19:12:12 +0900 Subject: [PATCH 07/53] Fix test failures due to editor dependency --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 075815203c..7090987093 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -143,7 +143,7 @@ namespace osu.Game.Screens.Edit.Setup [Resolved] private MusicController music { get; set; } - [Resolved] + [Resolved(canBeNull: true)] private Editor editor { get; set; } private void audioTrackChanged(ValueChangedEvent filePath) From cc9ae328116900fee199aff64e87d44d32f5be7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 21:05:29 +0900 Subject: [PATCH 08/53] Fix summary timeline not updating to new track length correctly --- .../Components/Timelines/Summary/Parts/TimelinePart.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs index 4a7c3f26bc..5b8f7c747b 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osuTK; using osu.Framework.Graphics; @@ -22,6 +23,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { protected readonly IBindable Beatmap = new Bindable(); + protected readonly IBindable Track = new Bindable(); + private readonly Container content; protected override Container Content => content; @@ -35,12 +38,15 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts updateRelativeChildSize(); LoadBeatmap(b.NewValue); }; + + Track.ValueChanged += _ => updateRelativeChildSize(); } [BackgroundDependencyLoader] - private void load(IBindable beatmap) + private void load(IBindable beatmap, EditorClock clock) { Beatmap.BindTo(beatmap); + Track.BindTo(clock.Track); } private void updateRelativeChildSize() From 011b17624429504950f3befcf6159a931a806f0d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 22:00:13 +0900 Subject: [PATCH 09/53] Add test coverage of audio track changing --- .../Editing/TestSceneEditorBeatmapCreation.cs | 35 ++++++++++++++++++ osu.Game/Screens/Edit/Setup/SetupScreen.cs | 36 ++++++++++--------- osu.Game/Tests/Visual/EditorTestScene.cs | 6 ++-- 3 files changed, 59 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 3cc95dec9e..8ba172fc70 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -1,11 +1,18 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.IO; +using System.Linq; using NUnit.Framework; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Edit.Setup; using osu.Game.Storyboards; +using osu.Game.Tests.Resources; +using SharpCompress.Archives; +using SharpCompress.Archives.Zip; namespace osu.Game.Tests.Visual.Editing { @@ -13,6 +20,8 @@ namespace osu.Game.Tests.Visual.Editing { protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + protected override bool EditorComponentsReady => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true; + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new DummyWorkingBeatmap(Audio, null); [Test] @@ -21,5 +30,31 @@ namespace osu.Game.Tests.Visual.Editing AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.ID > 0); } + + [Test] + public void TestAddAudioTrack() + { + AddAssert("switch track to real track", () => + { + var setup = Editor.ChildrenOfType().First(); + + var temp = TestResources.GetTestBeatmapForImport(); + + string extractedFolder = $"{temp}_extracted"; + Directory.CreateDirectory(extractedFolder); + + using (var zip = ZipArchive.Open(temp)) + zip.WriteToDirectory(extractedFolder); + + bool success = setup.ChangeAudioTrack(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3")); + + File.Delete(temp); + Directory.Delete(extractedFolder, true); + + return success; + }); + + AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000); + } } } diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 7090987093..e238e1fcf0 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -33,6 +33,15 @@ namespace osu.Game.Screens.Edit.Setup private LabelledTextBox difficultyTextBox; private LabelledTextBox audioTrackTextBox; + [Resolved] + private FileStore files { get; set; } + + [Resolved] + private MusicController music { get; set; } + + [Resolved(canBeNull: true)] + private Editor editor { get; set; } + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -137,29 +146,17 @@ namespace osu.Game.Screens.Edit.Setup item.OnCommit += onCommit; } - [Resolved] - private FileStore files { get; set; } - - [Resolved] - private MusicController music { get; set; } - - [Resolved(canBeNull: true)] - private Editor editor { get; set; } - - private void audioTrackChanged(ValueChangedEvent filePath) + public bool ChangeAudioTrack(string path) { - var info = new FileInfo(filePath.NewValue); + var info = new FileInfo(path); if (!info.Exists) - { - audioTrackTextBox.Current.Value = filePath.OldValue; - return; - } + return false; var beatmapFiles = Beatmap.Value.BeatmapSetInfo.Files; // remove the old file - var oldFile = beatmapFiles.FirstOrDefault(f => f.Filename == filePath.OldValue); + var oldFile = beatmapFiles.FirstOrDefault(f => f.Filename == Beatmap.Value.Metadata.AudioFile); if (oldFile != null) { @@ -184,6 +181,13 @@ namespace osu.Game.Screens.Edit.Setup music.ForceReloadCurrentBeatmap(); editor.UpdateClockSource(); + return true; + } + + private void audioTrackChanged(ValueChangedEvent filePath) + { + if (!ChangeAudioTrack(filePath.NewValue)) + audioTrackTextBox.Current.Value = filePath.OldValue; } private void onCommit(TextBox sender, bool newText) diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 8f76f247cf..a9ee8e2668 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -26,13 +26,15 @@ namespace osu.Game.Tests.Visual Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); } + protected virtual bool EditorComponentsReady => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true + && Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true; + public override void SetUpSteps() { base.SetUpSteps(); AddStep("load editor", () => LoadScreen(Editor = CreateEditor())); - AddUntilStep("wait for editor to load", () => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true - && Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + AddUntilStep("wait for editor to load", () => EditorComponentsReady); AddStep("get beatmap", () => EditorBeatmap = Editor.ChildrenOfType().Single()); AddStep("get clock", () => EditorClock = Editor.ChildrenOfType().Single()); } From 94c1cc8ffa4b8eefb0c7bd498084e0d2dbee9c6f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 22:25:04 +0900 Subject: [PATCH 10/53] Fix test runs under headless --- .../Visual/Editing/TestSceneEditorBeatmapCreation.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 8ba172fc70..7215b80a97 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -1,6 +1,7 @@ // 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.IO; using System.Linq; using NUnit.Framework; @@ -22,11 +23,17 @@ namespace osu.Game.Tests.Visual.Editing protected override bool EditorComponentsReady => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true; - protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new DummyWorkingBeatmap(Audio, null); + public override void SetUpSteps() + { + AddStep("set dummy", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null)); + + base.SetUpSteps(); + } [Test] public void TestCreateNewBeatmap() { + AddStep("add random hitobject", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString()); AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.ID > 0); } From dbc522aedea4e0cf3e5b36b07e4e5f04d36f4b0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Sep 2020 22:41:52 +0900 Subject: [PATCH 11/53] Remove weird using --- osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 7215b80a97..df2fdfa79d 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -10,7 +10,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit.Setup; -using osu.Game.Storyboards; using osu.Game.Tests.Resources; using SharpCompress.Archives; using SharpCompress.Archives.Zip; From c3df7e1fa8c897971e9662f909ada3444e203930 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 01:05:12 +0900 Subject: [PATCH 12/53] Fix scroll container's scrollbar not respecting minimum size on first resize --- osu.Game/Graphics/Containers/OsuScrollContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs index d504a11b22..ed5c73bee6 100644 --- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs +++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs @@ -112,6 +112,9 @@ namespace osu.Game.Graphics.Containers CornerRadius = 5; + // needs to be set initially for the ResizeTo to respect minimum size + Size = new Vector2(SCROLL_BAR_HEIGHT); + const float margin = 3; Margin = new MarginPadding From d602072ee3ddb834899295a7c664b6f170d73175 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 12:24:41 +0900 Subject: [PATCH 13/53] Use SingleOrDefault where feasible --- osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index df2fdfa79d..d4976c3d48 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Editing { protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); - protected override bool EditorComponentsReady => Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true; + protected override bool EditorComponentsReady => Editor.ChildrenOfType().SingleOrDefault()?.IsLoaded == true; public override void SetUpSteps() { From 9846d87eb0787ec65f0df9ec6558448dbb18f0b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 12:25:50 +0900 Subject: [PATCH 14/53] Fix misleading step name (and add comment as to its purpose) --- .../Visual/Editing/TestSceneEditorBeatmapCreation.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index d4976c3d48..ceacbd51a2 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -32,7 +32,10 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestCreateNewBeatmap() { - AddStep("add random hitobject", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString()); + // if we save a beatmap with a hash collision, things fall over. + // probably needs a more solid resolution in the future but this will do for now. + AddStep("make new beatmap unique", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString()); + AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.ID > 0); } From a17eac3692441e5664bc98c31c6b1c71a5c8ece2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 12:27:08 +0900 Subject: [PATCH 15/53] Rename reload method to not mention beatmap unnecessarily --- osu.Game/Overlays/MusicController.cs | 2 +- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 66c9b15c0a..0764f34697 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -84,7 +84,7 @@ namespace osu.Game.Overlays /// /// Forcefully reload the current 's track from disk. /// - public void ForceReloadCurrentBeatmap() => changeTrack(); + public void ReloadCurrentTrack() => changeTrack(); /// /// Change the position of a in the current playlist. diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index e238e1fcf0..9c72268eea 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -178,7 +178,7 @@ namespace osu.Game.Screens.Edit.Setup Beatmap.Value.Metadata.AudioFile = info.Name; - music.ForceReloadCurrentBeatmap(); + music.ReloadCurrentTrack(); editor.UpdateClockSource(); return true; From b1e72c311edc68d3c98136349b4597418ba5e6c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 12:28:41 +0900 Subject: [PATCH 16/53] Add null check because we can --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 9c72268eea..4d553756b8 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -180,7 +180,7 @@ namespace osu.Game.Screens.Edit.Setup music.ReloadCurrentTrack(); - editor.UpdateClockSource(); + editor?.UpdateClockSource(); return true; } From f047ff10bfcb616b236b864cd5ae663d74875100 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 12:30:05 +0900 Subject: [PATCH 17/53] Remove local specification for file selector search path --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 4d553756b8..edbe75448b 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -235,7 +235,7 @@ namespace osu.Game.Screens.Edit.Setup public void DisplayFileChooser() { - Target.Child = new FileSelector("/Users/Dean/.osu/Songs", new[] { ".mp3", ".ogg" }) + Target.Child = new FileSelector(validFileExtensions: new[] { ".mp3", ".ogg" }) { RelativeSizeAxes = Axes.X, Height = 400, From eff6af3111eba46adb782205eca4f2b7a40347c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 12:45:13 +0900 Subject: [PATCH 18/53] Add "bindables" to dictionary --- osu.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 29ca385275..64f3d41acb 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -909,6 +909,7 @@ private void load() True True True + True True True True From 50ba320a5118e3bcd6c5d40022ca2df22ac4ab89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 13:10:04 +0900 Subject: [PATCH 19/53] Expand available file operations in ArchiveModelManager --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/Database/ArchiveModelManager.cs | 44 +++++++++++++++++++----- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index e9f41f6bff..b48ab6112e 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -260,7 +260,7 @@ namespace osu.Game.Beatmaps fileInfo.Filename = beatmapInfo.Path; stream.Seek(0, SeekOrigin.Begin); - UpdateFile(setInfo, fileInfo, stream); + ReplaceFile(setInfo, fileInfo, stream); } } diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 76bc4f7755..c39b71b058 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -401,29 +401,55 @@ namespace osu.Game.Database } /// - /// Update an existing file, or create a new entry if not already part of the 's files. + /// Replace an existing file with a new version. /// /// The item to operate on. - /// The file model to be updated or added. + /// The existing file to be replaced. /// The new file contents. - public void UpdateFile(TModel model, TFileModel file, Stream contents) + /// An optional filename for the new file. Will use the previous filename if not specified. + public void ReplaceFile(TModel model, TFileModel file, Stream contents, string filename = null) + { + using (ContextFactory.GetForWrite()) + { + DeleteFile(model, file); + AddFile(model, contents, filename ?? file.Filename); + } + } + + /// + /// Delete new file. + /// + /// The item to operate on. + /// The existing file to be deleted. + public void DeleteFile(TModel model, TFileModel file) { using (var usage = ContextFactory.GetForWrite()) { // Dereference the existing file info, since the file model will be removed. if (file.FileInfo != null) - { Files.Dereference(file.FileInfo); - // Remove the file model. - usage.Context.Set().Remove(file); - } + // This shouldn't be required, but here for safety in case the provided TModel is not being change tracked + // Definitely can be removed once we rework the database backend. + usage.Context.Set().Remove(file); - // Add the new file info and containing file model. model.Files.Remove(file); + } + } + + /// + /// Add a new file. + /// + /// The item to operate on. + /// The new file contents. + /// The filename for the new file. + public void AddFile(TModel model, Stream contents, string filename) + { + using (ContextFactory.GetForWrite()) + { model.Files.Add(new TFileModel { - Filename = file.Filename, + Filename = filename, FileInfo = Files.Add(contents) }); From ea971ecb9008f2b09cee1fe546cf8a6ce4c65c81 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 13:11:34 +0900 Subject: [PATCH 20/53] Remove local file handling from SetupScreen --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 34 ++++++++-------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index edbe75448b..802f304835 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.IO; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -17,10 +18,8 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; -using osu.Game.IO; using osu.Game.Overlays; using osuTK; -using FileInfo = System.IO.FileInfo; namespace osu.Game.Screens.Edit.Setup { @@ -34,10 +33,10 @@ namespace osu.Game.Screens.Edit.Setup private LabelledTextBox audioTrackTextBox; [Resolved] - private FileStore files { get; set; } + private MusicController music { get; set; } [Resolved] - private MusicController music { get; set; } + private BeatmapManager beatmaps { get; set; } [Resolved(canBeNull: true)] private Editor editor { get; set; } @@ -153,28 +152,19 @@ namespace osu.Game.Screens.Edit.Setup if (!info.Exists) return false; - var beatmapFiles = Beatmap.Value.BeatmapSetInfo.Files; + var set = Beatmap.Value.BeatmapSetInfo; - // remove the old file - var oldFile = beatmapFiles.FirstOrDefault(f => f.Filename == Beatmap.Value.Metadata.AudioFile); - - if (oldFile != null) - { - beatmapFiles.Remove(oldFile); - files.Dereference(oldFile.FileInfo); - } - - // add the new file - IO.FileInfo osuFileInfo; + // remove the previous audio track for now. + // in the future we probably want to check if this is being used elsewhere (other difficulties?) + var oldFile = set.Files.FirstOrDefault(f => f.Filename == Beatmap.Value.Metadata.AudioFile); using (var stream = info.OpenRead()) - osuFileInfo = files.Add(stream); - - beatmapFiles.Add(new BeatmapSetFileInfo { - FileInfo = osuFileInfo, - Filename = info.Name - }); + if (oldFile != null) + beatmaps.ReplaceFile(set, oldFile, stream, info.Name); + else + beatmaps.AddFile(set, stream, info.Name); + } Beatmap.Value.Metadata.AudioFile = info.Name; From ae68dcd962090efe418228f4c11c09d166a12af1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 16:33:22 +0900 Subject: [PATCH 21/53] Add ternary toggle buttons to editor toolbox selection --- .../Edit/OsuHitObjectComposer.cs | 11 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 14 +-- .../TernaryButtons/DrawableTernaryButton.cs | 112 ++++++++++++++++++ .../TernaryButtons/TernaryButton.cs | 44 +++++++ .../Components/ComposeBlueprintContainer.cs | 29 ++--- .../Compose/Components/SelectionHandler.cs | 22 ++-- 6 files changed, 194 insertions(+), 38 deletions(-) create mode 100644 osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs create mode 100644 osu.Game/Screens/Edit/Components/TernaryButtons/TernaryButton.cs diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 0d0a139a8a..49af80dd63 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -9,7 +9,9 @@ using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; @@ -17,6 +19,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit.Components.TernaryButtons; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -39,11 +42,11 @@ namespace osu.Game.Rulesets.Osu.Edit new SpinnerCompositionTool() }; - private readonly BindableBool distanceSnapToggle = new BindableBool(true) { Description = "Distance Snap" }; + private readonly Bindable distanceSnapToggle = new Bindable(); - protected override IEnumerable> Toggles => base.Toggles.Concat(new[] + protected override IEnumerable Toggles => base.Toggles.Concat(new[] { - distanceSnapToggle + new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler }) }); private BindableList selectedHitObjects; @@ -156,7 +159,7 @@ namespace osu.Game.Rulesets.Osu.Edit distanceSnapGridCache.Invalidate(); distanceSnapGrid = null; - if (!distanceSnapToggle.Value) + if (distanceSnapToggle.Value != TernaryState.True) return; switch (BlueprintContainer.CurrentTool) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index b2d7c40a22..afef542e36 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -14,7 +14,6 @@ using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; @@ -24,6 +23,7 @@ using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; +using osu.Game.Screens.Edit.Components.TernaryButtons; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -124,11 +124,7 @@ namespace osu.Game.Rulesets.Edit new ToolboxGroup("toolbox") { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } }, togglesCollection = new ToolboxGroup("toggles") { - ChildrenEnumerable = Toggles.Select(b => new SettingsCheckbox - { - Bindable = b, - LabelText = b?.Description ?? "unknown" - }) + ChildrenEnumerable = Toggles.Select(b => new DrawableTernaryButton(b)) } } }, @@ -170,7 +166,7 @@ namespace osu.Game.Rulesets.Edit /// A collection of toggles which will be displayed to the user. /// The display name will be decided by . /// - protected virtual IEnumerable> Toggles => BlueprintContainer.Toggles; + protected virtual IEnumerable Toggles => BlueprintContainer.Toggles; /// /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. @@ -212,9 +208,9 @@ namespace osu.Game.Rulesets.Edit { var item = togglesCollection.Children[rightIndex]; - if (item is SettingsCheckbox checkbox) + if (item is DrawableTernaryButton button) { - checkbox.Bindable.Value = !checkbox.Bindable.Value; + button.Button.Toggle(); return true; } } diff --git a/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs b/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs new file mode 100644 index 0000000000..c72fff5c91 --- /dev/null +++ b/osu.Game/Screens/Edit/Components/TernaryButtons/DrawableTernaryButton.cs @@ -0,0 +1,112 @@ +// 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.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Components.TernaryButtons +{ + internal class DrawableTernaryButton : TriangleButton + { + private Color4 defaultBackgroundColour; + private Color4 defaultBubbleColour; + private Color4 selectedBackgroundColour; + private Color4 selectedBubbleColour; + + private Drawable icon; + + public readonly TernaryButton Button; + + public DrawableTernaryButton(TernaryButton button) + { + Button = button; + + Text = button.Description; + + RelativeSizeAxes = Axes.X; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + defaultBackgroundColour = colours.Gray3; + defaultBubbleColour = defaultBackgroundColour.Darken(0.5f); + selectedBackgroundColour = colours.BlueDark; + selectedBubbleColour = selectedBackgroundColour.Lighten(0.5f); + + Triangles.Alpha = 0; + + Content.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = 2, + Offset = new Vector2(0, 1), + Colour = Color4.Black.Opacity(0.5f) + }; + + Add(icon = (Button.CreateIcon?.Invoke() ?? new Circle()).With(b => + { + b.Blending = BlendingParameters.Additive; + b.Anchor = Anchor.CentreLeft; + b.Origin = Anchor.CentreLeft; + b.Size = new Vector2(20); + b.X = 10; + })); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Button.Bindable.BindValueChanged(selected => updateSelectionState(), true); + + Action = onAction; + } + + private void onAction() + { + Button.Toggle(); + } + + private void updateSelectionState() + { + if (!IsLoaded) + return; + + switch (Button.Bindable.Value) + { + case TernaryState.Indeterminate: + icon.Colour = selectedBubbleColour.Darken(0.5f); + BackgroundColour = selectedBackgroundColour.Darken(0.5f); + break; + + case TernaryState.False: + icon.Colour = defaultBubbleColour; + BackgroundColour = defaultBackgroundColour; + break; + + case TernaryState.True: + icon.Colour = selectedBubbleColour; + BackgroundColour = selectedBackgroundColour; + break; + } + } + + protected override SpriteText CreateText() => new OsuSpriteText + { + Depth = -1, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + X = 40f + }; + } +} diff --git a/osu.Game/Screens/Edit/Components/TernaryButtons/TernaryButton.cs b/osu.Game/Screens/Edit/Components/TernaryButtons/TernaryButton.cs new file mode 100644 index 0000000000..7f64695bde --- /dev/null +++ b/osu.Game/Screens/Edit/Components/TernaryButtons/TernaryButton.cs @@ -0,0 +1,44 @@ +// 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 osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit.Components.TernaryButtons +{ + public class TernaryButton + { + public readonly Bindable Bindable; + + public readonly string Description; + + /// + /// A function which creates a drawable icon to represent this item. If null, a sane default should be used. + /// + public readonly Func CreateIcon; + + public TernaryButton(Bindable bindable, string description, Func createIcon = null) + { + Bindable = bindable; + Description = description; + CreateIcon = createIcon; + } + + public void Toggle() + { + switch (Bindable.Value) + { + case TernaryState.False: + case TernaryState.Indeterminate: + Bindable.Value = TernaryState.True; + break; + + case TernaryState.True: + Bindable.Value = TernaryState.False; + break; + } + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 6f66c1bd6f..91eab18acb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -7,12 +7,16 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Screens.Edit.Components.TernaryButtons; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components @@ -57,35 +61,26 @@ namespace osu.Game.Screens.Edit.Compose.Components inputManager = GetContainingInputManager(); - Beatmap.SelectedHitObjects.CollectionChanged += (_, __) => updateTogglesFromSelection(); - - // the updated object may be in the selection - Beatmap.HitObjectUpdated += _ => updateTogglesFromSelection(); + // updates to selected are handled for us by SelectionHandler. + NewCombo.BindTo(SelectionHandler.SelectionNewComboState); + // we are responsible for current placement blueprint updated based on state changes. NewCombo.ValueChanged += combo => { - if (Beatmap.SelectedHitObjects.Count > 0) + if (currentPlacement != null) { - SelectionHandler.SetNewCombo(combo.NewValue); - } - else if (currentPlacement != null) - { - // update placement object from toggle if (currentPlacement.HitObject is IHasComboInformation c) - c.NewCombo = combo.NewValue; + c.NewCombo = combo.NewValue == TernaryState.True; } }; } - private void updateTogglesFromSelection() => - NewCombo.Value = Beatmap.SelectedHitObjects.OfType().All(c => c.NewCombo); + public readonly Bindable NewCombo = new Bindable { Description = "New Combo" }; - public readonly Bindable NewCombo = new Bindable { Description = "New Combo" }; - - public virtual IEnumerable> Toggles => new[] + public virtual IEnumerable Toggles => new[] { //TODO: this should only be enabled (visible?) for rulesets that provide combo-supporting HitObjects. - NewCombo + new TernaryButton(NewCombo, "New combo", () => new SpriteIcon { Icon = FontAwesome.Regular.DotCircle }) }; #region Placement diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index a316f34ad0..ed956357b6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -316,9 +316,15 @@ namespace osu.Game.Screens.Edit.Compose.Components #region Selection State - private readonly Bindable selectionNewComboState = new Bindable(); + /// + /// The state of "new combo" for all selected hitobjects. + /// + public readonly Bindable SelectionNewComboState = new Bindable(); - private readonly Dictionary> selectionSampleStates = new Dictionary>(); + /// + /// The state of each sample type for all selected hitobjects. Keys match with constant specifications. + /// + public readonly Dictionary> SelectionSampleStates = new Dictionary>(); /// /// Set up ternary state bindables and bind them to selection/hitobject changes (in both directions) @@ -349,11 +355,11 @@ namespace osu.Game.Screens.Edit.Compose.Components } }; - selectionSampleStates[sampleName] = bindable; + SelectionSampleStates[sampleName] = bindable; } // new combo - selectionNewComboState.ValueChanged += state => + SelectionNewComboState.ValueChanged += state => { switch (state.NewValue) { @@ -377,9 +383,9 @@ namespace osu.Game.Screens.Edit.Compose.Components /// protected virtual void UpdateTernaryStates() { - selectionNewComboState.Value = GetStateFromSelection(SelectedHitObjects.OfType(), h => h.NewCombo); + SelectionNewComboState.Value = GetStateFromSelection(SelectedHitObjects.OfType(), h => h.NewCombo); - foreach (var (sampleName, bindable) in selectionSampleStates) + foreach (var (sampleName, bindable) in SelectionSampleStates) { bindable.Value = GetStateFromSelection(SelectedHitObjects, h => h.Samples.Any(s => s.Name == sampleName)); } @@ -413,7 +419,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (selectedBlueprints.All(b => b.HitObject is IHasComboInformation)) { - items.Add(new TernaryStateMenuItem("New combo") { State = { BindTarget = selectionNewComboState } }); + items.Add(new TernaryStateMenuItem("New combo") { State = { BindTarget = SelectionNewComboState } }); } if (selectedBlueprints.Count == 1) @@ -423,7 +429,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { new OsuMenuItem("Sound") { - Items = selectionSampleStates.Select(kvp => + Items = SelectionSampleStates.Select(kvp => new TernaryStateMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray() }, new OsuMenuItem("Delete", MenuItemType.Destructive, deleteSelected), From a6298c60eb30d5c7f0bf5dc8422148f531c315ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 16:44:37 +0900 Subject: [PATCH 22/53] Fix button spacing --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index afef542e36..f823d37060 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Edit private RadioButtonCollection toolboxCollection; - private ToolboxGroup togglesCollection; + private FillFlowContainer togglesCollection; protected HitObjectComposer(Ruleset ruleset) { @@ -121,10 +121,20 @@ namespace osu.Game.Rulesets.Edit Spacing = new Vector2(10), Children = new Drawable[] { - new ToolboxGroup("toolbox") { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } }, - togglesCollection = new ToolboxGroup("toggles") + new ToolboxGroup("toolbox") { - ChildrenEnumerable = Toggles.Select(b => new DrawableTernaryButton(b)) + Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } + }, + new ToolboxGroup("toggles") + { + Child = togglesCollection = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + ChildrenEnumerable = Toggles.Select(b => new DrawableTernaryButton(b)) + }, } } }, From da820c815e5fc97a350669e9904d2700c25fbe44 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 16:46:06 +0900 Subject: [PATCH 23/53] Add shortcut keys to toolbox gorup titles --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index f823d37060..a592500a87 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -121,11 +121,11 @@ namespace osu.Game.Rulesets.Edit Spacing = new Vector2(10), Children = new Drawable[] { - new ToolboxGroup("toolbox") + new ToolboxGroup("toolbox (1-9)") { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } }, - new ToolboxGroup("toggles") + new ToolboxGroup("toggles (Q~P)") { Child = togglesCollection = new FillFlowContainer { From 98c6027352deb80fd0d80e9bc5abcb12ead00552 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 17:07:58 +0900 Subject: [PATCH 24/53] Remove unused using --- .../Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 91eab18acb..cab277f10b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; From b8e9f19b92195f8c6e270540c9d77f8a17b5e2c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 17:30:31 +0900 Subject: [PATCH 25/53] Move common HitSampleInfo lookup to static method --- osu.Game/Audio/HitSampleInfo.cs | 5 +++++ osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 5 +---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index f6b0107bd2..8b1f5a366a 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -17,6 +17,11 @@ namespace osu.Game.Audio public const string HIT_NORMAL = @"hitnormal"; public const string HIT_CLAP = @"hitclap"; + /// + /// All valid sample addition constants. + /// + public static IEnumerable AllAdditions => new[] { HIT_WHISTLE, HIT_CLAP, HIT_FINISH }; + /// /// The bank to load the sample from. /// diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index ed956357b6..6ca85fe026 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -331,10 +331,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// private void createStateBindables() { - // hit samples - var sampleTypes = new[] { HitSampleInfo.HIT_WHISTLE, HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_FINISH }; - - foreach (var sampleName in sampleTypes) + foreach (var sampleName in HitSampleInfo.AllAdditions) { var bindable = new Bindable { From 22511c36c3b0d306418b55adbb61376796f85187 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 17:40:43 +0900 Subject: [PATCH 26/53] Ensure toggles are not instantiated more than once for safety --- .../Edit/OsuHitObjectComposer.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 8 ++++++-- .../Components/ComposeBlueprintContainer.cs | 19 +++++++++++-------- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 49af80dd63..a4dd463ea5 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Edit private readonly Bindable distanceSnapToggle = new Bindable(); - protected override IEnumerable Toggles => base.Toggles.Concat(new[] + protected override IEnumerable CreateToggles() => base.CreateToggles().Concat(new[] { new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler }) }); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index a592500a87..e692f8d606 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -133,7 +133,6 @@ namespace osu.Game.Rulesets.Edit AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 5), - ChildrenEnumerable = Toggles.Select(b => new DrawableTernaryButton(b)) }, } } @@ -145,6 +144,9 @@ namespace osu.Game.Rulesets.Edit .Select(t => new RadioButton(t.Name, () => toolSelected(t), t.CreateIcon)) .ToList(); + Toggles = CreateToggles().ToArray(); + togglesCollection.AddRange(Toggles.Select(b => new DrawableTernaryButton(b))); + setSelectTool(); EditorBeatmap.SelectedHitObjects.CollectionChanged += selectionChanged; @@ -176,7 +178,9 @@ namespace osu.Game.Rulesets.Edit /// A collection of toggles which will be displayed to the user. /// The display name will be decided by . /// - protected virtual IEnumerable Toggles => BlueprintContainer.Toggles; + public TernaryButton[] Toggles { get; private set; } + + protected virtual IEnumerable CreateToggles() => BlueprintContainer.Toggles; /// /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index cab277f10b..0f4bab8b33 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -51,6 +51,8 @@ namespace osu.Game.Screens.Edit.Compose.Components [BackgroundDependencyLoader] private void load() { + Toggles = CreateToggles().ToArray(); + AddInternal(placementBlueprintContainer); } @@ -66,21 +68,22 @@ namespace osu.Game.Screens.Edit.Compose.Components // we are responsible for current placement blueprint updated based on state changes. NewCombo.ValueChanged += combo => { - if (currentPlacement != null) - { - if (currentPlacement.HitObject is IHasComboInformation c) - c.NewCombo = combo.NewValue == TernaryState.True; - } + if (currentPlacement == null) return; + + if (currentPlacement.HitObject is IHasComboInformation c) + c.NewCombo = combo.NewValue == TernaryState.True; }; } public readonly Bindable NewCombo = new Bindable { Description = "New Combo" }; - public virtual IEnumerable Toggles => new[] + public TernaryButton[] Toggles { get; private set; } + + protected virtual IEnumerable CreateToggles() { //TODO: this should only be enabled (visible?) for rulesets that provide combo-supporting HitObjects. - new TernaryButton(NewCombo, "New combo", () => new SpriteIcon { Icon = FontAwesome.Regular.DotCircle }) - }; + yield return new TernaryButton(NewCombo, "New combo", () => new SpriteIcon { Icon = FontAwesome.Regular.DotCircle }); + } #region Placement From 346d14d40bfc09d0da125c0850dafe587eccb376 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 17:45:19 +0900 Subject: [PATCH 27/53] Rename variables to match --- .../Edit/OsuHitObjectComposer.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 15 ++++++++------- .../Components/ComposeBlueprintContainer.cs | 12 +++++++++--- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index a4dd463ea5..912a705d16 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Edit private readonly Bindable distanceSnapToggle = new Bindable(); - protected override IEnumerable CreateToggles() => base.CreateToggles().Concat(new[] + protected override IEnumerable CreateTernaryButtons() => base.CreateTernaryButtons().Concat(new[] { new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler }) }); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index e692f8d606..3ad2394420 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; @@ -144,8 +143,8 @@ namespace osu.Game.Rulesets.Edit .Select(t => new RadioButton(t.Name, () => toolSelected(t), t.CreateIcon)) .ToList(); - Toggles = CreateToggles().ToArray(); - togglesCollection.AddRange(Toggles.Select(b => new DrawableTernaryButton(b))); + TernaryStates = CreateTernaryButtons().ToArray(); + togglesCollection.AddRange(TernaryStates.Select(b => new DrawableTernaryButton(b))); setSelectTool(); @@ -175,12 +174,14 @@ namespace osu.Game.Rulesets.Edit protected abstract IReadOnlyList CompositionTools { get; } /// - /// A collection of toggles which will be displayed to the user. - /// The display name will be decided by . + /// A collection of states which will be displayed to the user in the toolbox. /// - public TernaryButton[] Toggles { get; private set; } + public TernaryButton[] TernaryStates { get; private set; } - protected virtual IEnumerable CreateToggles() => BlueprintContainer.Toggles; + /// + /// Create all ternary states required to be displayed to the user. + /// + protected virtual IEnumerable CreateTernaryButtons() => BlueprintContainer.TernaryStates; /// /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 0f4bab8b33..88c3170c34 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Edit.Compose.Components [BackgroundDependencyLoader] private void load() { - Toggles = CreateToggles().ToArray(); + TernaryStates = CreateTernaryButtons().ToArray(); AddInternal(placementBlueprintContainer); } @@ -77,9 +77,15 @@ namespace osu.Game.Screens.Edit.Compose.Components public readonly Bindable NewCombo = new Bindable { Description = "New Combo" }; - public TernaryButton[] Toggles { get; private set; } + /// + /// A collection of states which will be displayed to the user in the toolbox. + /// + public TernaryButton[] TernaryStates { get; private set; } - protected virtual IEnumerable CreateToggles() + /// + /// Create all ternary states required to be displayed to the user. + /// + protected virtual IEnumerable CreateTernaryButtons() { //TODO: this should only be enabled (visible?) for rulesets that provide combo-supporting HitObjects. yield return new TernaryButton(NewCombo, "New combo", () => new SpriteIcon { Icon = FontAwesome.Regular.DotCircle }); From b561429f920d52c53e5ae33f1efbd1e1e9f0f1be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 17:53:41 +0900 Subject: [PATCH 28/53] Add toolbar toggle buttons for hit samples --- .../Components/ComposeBlueprintContainer.cs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 88c3170c34..a83977c15f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -3,12 +3,14 @@ using System.Collections.Generic; using System.Linq; +using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; +using osu.Game.Audio; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; @@ -73,6 +75,34 @@ namespace osu.Game.Screens.Edit.Compose.Components if (currentPlacement.HitObject is IHasComboInformation c) c.NewCombo = combo.NewValue == TernaryState.True; }; + + // we own SelectionHandler so don't need to worry about making bindable copies (for simplicity) + foreach (var kvp in SelectionHandler.SelectionSampleStates) + { + kvp.Value.BindValueChanged(c => sampleChanged(kvp.Key, c.NewValue)); + } + } + + private void sampleChanged(string sampleName, TernaryState state) + { + if (currentPlacement == null) return; + + var samples = currentPlacement.HitObject.Samples; + + var existingSample = samples.FirstOrDefault(s => s.Name == sampleName); + + switch (state) + { + case TernaryState.False: + if (existingSample != null) + samples.Remove(existingSample); + break; + + case TernaryState.True: + if (existingSample == null) + samples.Add(new HitSampleInfo { Name = sampleName }); + break; + } } public readonly Bindable NewCombo = new Bindable { Description = "New Combo" }; @@ -89,6 +119,26 @@ namespace osu.Game.Screens.Edit.Compose.Components { //TODO: this should only be enabled (visible?) for rulesets that provide combo-supporting HitObjects. yield return new TernaryButton(NewCombo, "New combo", () => new SpriteIcon { Icon = FontAwesome.Regular.DotCircle }); + + foreach (var kvp in SelectionHandler.SelectionSampleStates) + yield return new TernaryButton(kvp.Value, kvp.Key.Replace("hit", string.Empty).Titleize(), () => getIconForSample(kvp.Key)); + } + + private Drawable getIconForSample(string sampleName) + { + switch (sampleName) + { + case HitSampleInfo.HIT_CLAP: + return new SpriteIcon { Icon = FontAwesome.Solid.Hands }; + + case HitSampleInfo.HIT_WHISTLE: + return new SpriteIcon { Icon = FontAwesome.Solid.Bullhorn }; + + case HitSampleInfo.HIT_FINISH: + return new SpriteIcon { Icon = FontAwesome.Solid.DrumSteelpan }; + } + + return null; } #region Placement From dbfa05d3b34339ecb139de74a4c3c2aa24f48342 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 18:00:17 +0900 Subject: [PATCH 29/53] Fix placement object not getting updated with initial state --- .../Components/ComposeBlueprintContainer.cs | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index a83977c15f..81d7fa4b32 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -68,21 +68,31 @@ namespace osu.Game.Screens.Edit.Compose.Components NewCombo.BindTo(SelectionHandler.SelectionNewComboState); // we are responsible for current placement blueprint updated based on state changes. - NewCombo.ValueChanged += combo => - { - if (currentPlacement == null) return; - - if (currentPlacement.HitObject is IHasComboInformation c) - c.NewCombo = combo.NewValue == TernaryState.True; - }; + NewCombo.ValueChanged += _ => updatePlacementNewCombo(); // we own SelectionHandler so don't need to worry about making bindable copies (for simplicity) foreach (var kvp in SelectionHandler.SelectionSampleStates) { - kvp.Value.BindValueChanged(c => sampleChanged(kvp.Key, c.NewValue)); + kvp.Value.BindValueChanged(_ => updatePlacementSamples()); } } + private void updatePlacementNewCombo() + { + if (currentPlacement == null) return; + + if (currentPlacement.HitObject is IHasComboInformation c) + c.NewCombo = NewCombo.Value == TernaryState.True; + } + + private void updatePlacementSamples() + { + if (currentPlacement == null) return; + + foreach (var kvp in SelectionHandler.SelectionSampleStates) + sampleChanged(kvp.Key, kvp.Value.Value); + } + private void sampleChanged(string sampleName, TernaryState state) { if (currentPlacement == null) return; @@ -206,6 +216,10 @@ namespace osu.Game.Screens.Edit.Compose.Components // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame updatePlacementPosition(); + + updatePlacementSamples(); + + updatePlacementNewCombo(); } } From 8b255f457971745411f896764889edc92a650a98 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 18:40:20 +0900 Subject: [PATCH 30/53] Fix test failures The issue was the ArchiveModelManager change; the test local change is just there because it makes more sense to run for every test in that scene. --- .../Visual/Editing/TestSceneEditorBeatmapCreation.cs | 8 ++++---- osu.Game/Database/ArchiveModelManager.cs | 8 +++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index ceacbd51a2..720cf51f2c 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -27,15 +27,15 @@ namespace osu.Game.Tests.Visual.Editing AddStep("set dummy", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null)); base.SetUpSteps(); + + // if we save a beatmap with a hash collision, things fall over. + // probably needs a more solid resolution in the future but this will do for now. + AddStep("make new beatmap unique", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString()); } [Test] public void TestCreateNewBeatmap() { - // if we save a beatmap with a hash collision, things fall over. - // probably needs a more solid resolution in the future but this will do for now. - AddStep("make new beatmap unique", () => EditorBeatmap.Metadata.Title = Guid.NewGuid().ToString()); - AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.ID > 0); } diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index c39b71b058..bbe2604216 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -427,11 +427,13 @@ namespace osu.Game.Database { // Dereference the existing file info, since the file model will be removed. if (file.FileInfo != null) + { Files.Dereference(file.FileInfo); - // This shouldn't be required, but here for safety in case the provided TModel is not being change tracked - // Definitely can be removed once we rework the database backend. - usage.Context.Set().Remove(file); + // This shouldn't be required, but here for safety in case the provided TModel is not being change tracked + // Definitely can be removed once we rework the database backend. + usage.Context.Set().Remove(file); + } model.Files.Remove(file); } From c41fb67e730cba4dc9c8b706a511acf430b3938a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 18:48:04 +0900 Subject: [PATCH 31/53] Move all ruleset editor tests to their own namespace --- .../{ => Editor}/ManiaPlacementBlueprintTestScene.cs | 2 +- .../{ => Editor}/ManiaSelectionBlueprintTestScene.cs | 2 +- osu.Game.Rulesets.Mania.Tests/{ => Editor}/TestSceneEditor.cs | 2 +- .../{ => Editor}/TestSceneHoldNotePlacementBlueprint.cs | 2 +- .../{ => Editor}/TestSceneHoldNoteSelectionBlueprint.cs | 2 +- .../{ => Editor}/TestSceneManiaBeatSnapGrid.cs | 2 +- .../{ => Editor}/TestSceneManiaHitObjectComposer.cs | 2 +- .../{ => Editor}/TestSceneNotePlacementBlueprint.cs | 2 +- .../{ => Editor}/TestSceneNoteSelectionBlueprint.cs | 2 +- .../{ => Editor}/TestSceneHitCirclePlacementBlueprint.cs | 2 +- .../{ => Editor}/TestSceneHitCircleSelectionBlueprint.cs | 2 +- .../{ => Editor}/TestSceneOsuDistanceSnapGrid.cs | 2 +- .../{ => Editor}/TestScenePathControlPointVisualiser.cs | 2 +- .../{ => Editor}/TestSceneSliderPlacementBlueprint.cs | 2 +- .../{ => Editor}/TestSceneSliderSelectionBlueprint.cs | 2 +- .../{ => Editor}/TestSceneSpinnerPlacementBlueprint.cs | 2 +- .../{ => Editor}/TestSceneSpinnerSelectionBlueprint.cs | 2 +- osu.Game.Rulesets.Taiko.Tests/{ => Editor}/TestSceneEditor.cs | 2 +- .../{ => Editor}/TestSceneTaikoHitObjectComposer.cs | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) rename osu.Game.Rulesets.Mania.Tests/{ => Editor}/ManiaPlacementBlueprintTestScene.cs (97%) rename osu.Game.Rulesets.Mania.Tests/{ => Editor}/ManiaSelectionBlueprintTestScene.cs (95%) rename osu.Game.Rulesets.Mania.Tests/{ => Editor}/TestSceneEditor.cs (96%) rename osu.Game.Rulesets.Mania.Tests/{ => Editor}/TestSceneHoldNotePlacementBlueprint.cs (93%) rename osu.Game.Rulesets.Mania.Tests/{ => Editor}/TestSceneHoldNoteSelectionBlueprint.cs (97%) rename osu.Game.Rulesets.Mania.Tests/{ => Editor}/TestSceneManiaBeatSnapGrid.cs (98%) rename osu.Game.Rulesets.Mania.Tests/{ => Editor}/TestSceneManiaHitObjectComposer.cs (99%) rename osu.Game.Rulesets.Mania.Tests/{ => Editor}/TestSceneNotePlacementBlueprint.cs (97%) rename osu.Game.Rulesets.Mania.Tests/{ => Editor}/TestSceneNoteSelectionBlueprint.cs (96%) rename osu.Game.Rulesets.Osu.Tests/{ => Editor}/TestSceneHitCirclePlacementBlueprint.cs (94%) rename osu.Game.Rulesets.Osu.Tests/{ => Editor}/TestSceneHitCircleSelectionBlueprint.cs (98%) rename osu.Game.Rulesets.Osu.Tests/{ => Editor}/TestSceneOsuDistanceSnapGrid.cs (99%) rename osu.Game.Rulesets.Osu.Tests/{ => Editor}/TestScenePathControlPointVisualiser.cs (97%) rename osu.Game.Rulesets.Osu.Tests/{ => Editor}/TestSceneSliderPlacementBlueprint.cs (99%) rename osu.Game.Rulesets.Osu.Tests/{ => Editor}/TestSceneSliderSelectionBlueprint.cs (99%) rename osu.Game.Rulesets.Osu.Tests/{ => Editor}/TestSceneSpinnerPlacementBlueprint.cs (94%) rename osu.Game.Rulesets.Osu.Tests/{ => Editor}/TestSceneSpinnerSelectionBlueprint.cs (96%) rename osu.Game.Rulesets.Taiko.Tests/{ => Editor}/TestSceneEditor.cs (88%) rename osu.Game.Rulesets.Taiko.Tests/{ => Editor}/TestSceneTaikoHitObjectComposer.cs (97%) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs similarity index 97% rename from osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs rename to osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs index 0fe4a3c669..ece523e84c 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs @@ -16,7 +16,7 @@ using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; using osuTK.Graphics; -namespace osu.Game.Rulesets.Mania.Tests +namespace osu.Game.Rulesets.Mania.Tests.Editor { public abstract class ManiaPlacementBlueprintTestScene : PlacementBlueprintTestScene { diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs similarity index 95% rename from osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs rename to osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs index 149f6582ab..176fbba921 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs @@ -8,7 +8,7 @@ using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; using osuTK.Graphics; -namespace osu.Game.Rulesets.Mania.Tests +namespace osu.Game.Rulesets.Mania.Tests.Editor { public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene { diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneEditor.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneEditor.cs similarity index 96% rename from osu.Game.Rulesets.Mania.Tests/TestSceneEditor.cs rename to osu.Game.Rulesets.Mania.Tests/Editor/TestSceneEditor.cs index 3b9c03b86a..d3afbc63eb 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneEditor.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneEditor.cs @@ -8,7 +8,7 @@ using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; -namespace osu.Game.Rulesets.Mania.Tests +namespace osu.Game.Rulesets.Mania.Tests.Editor { [TestFixture] public class TestSceneEditor : EditorTestScene diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs similarity index 93% rename from osu.Game.Rulesets.Mania.Tests/TestSceneHoldNotePlacementBlueprint.cs rename to osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs index b4332264b9..87c74a12cf 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNotePlacementBlueprint.cs @@ -8,7 +8,7 @@ using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; -namespace osu.Game.Rulesets.Mania.Tests +namespace osu.Game.Rulesets.Mania.Tests.Editor { public class TestSceneHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestScene { diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs similarity index 97% rename from osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs rename to osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs index 90394f3d1b..24f4c6858e 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneHoldNoteSelectionBlueprint.cs @@ -12,7 +12,7 @@ using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; -namespace osu.Game.Rulesets.Mania.Tests +namespace osu.Game.Rulesets.Mania.Tests.Editor { public class TestSceneHoldNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene { diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs similarity index 98% rename from osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs rename to osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs index 639be0bc11..654b752001 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs @@ -20,7 +20,7 @@ using osu.Game.Screens.Edit; using osu.Game.Tests.Visual; using osuTK; -namespace osu.Game.Rulesets.Mania.Tests +namespace osu.Game.Rulesets.Mania.Tests.Editor { public class TestSceneManiaBeatSnapGrid : EditorClockTestScene { diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs similarity index 99% rename from osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs rename to osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs index 1a3fa29d4a..c9551ee79e 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs @@ -23,7 +23,7 @@ using osu.Game.Tests.Visual; using osuTK; using osuTK.Input; -namespace osu.Game.Rulesets.Mania.Tests +namespace osu.Game.Rulesets.Mania.Tests.Editor { public class TestSceneManiaHitObjectComposer : EditorClockTestScene { diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs similarity index 97% rename from osu.Game.Rulesets.Mania.Tests/TestSceneNotePlacementBlueprint.cs rename to osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs index 2d97e61aa5..36c34a8fb9 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNotePlacementBlueprint.cs @@ -18,7 +18,7 @@ using osu.Game.Tests.Visual; using osuTK; using osuTK.Input; -namespace osu.Game.Rulesets.Mania.Tests +namespace osu.Game.Rulesets.Mania.Tests.Editor { public class TestSceneNotePlacementBlueprint : ManiaPlacementBlueprintTestScene { diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs similarity index 96% rename from osu.Game.Rulesets.Mania.Tests/TestSceneNoteSelectionBlueprint.cs rename to osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs index 1514bdf0bd..0e47a12a8e 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneNoteSelectionBlueprint.cs @@ -12,7 +12,7 @@ using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; using osuTK; -namespace osu.Game.Rulesets.Mania.Tests +namespace osu.Game.Rulesets.Mania.Tests.Editor { public class TestSceneNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCirclePlacementBlueprint.cs similarity index 94% rename from osu.Game.Rulesets.Osu.Tests/TestSceneHitCirclePlacementBlueprint.cs rename to osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCirclePlacementBlueprint.cs index 4c6abc45f7..7bccec6c97 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCirclePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCirclePlacementBlueprint.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Tests.Visual; -namespace osu.Game.Rulesets.Osu.Tests +namespace osu.Game.Rulesets.Osu.Tests.Editor { public class TestSceneHitCirclePlacementBlueprint : PlacementBlueprintTestScene { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCircleSelectionBlueprint.cs similarity index 98% rename from osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs rename to osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCircleSelectionBlueprint.cs index 0ecce42e88..66cd405195 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneHitCircleSelectionBlueprint.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Tests.Visual; using osuTK; -namespace osu.Game.Rulesets.Osu.Tests +namespace osu.Game.Rulesets.Osu.Tests.Editor { public class TestSceneHitCircleSelectionBlueprint : SelectionBlueprintTestScene { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs similarity index 99% rename from osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs rename to osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs index 0d0be2953b..1232369a0b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs @@ -19,7 +19,7 @@ using osu.Game.Tests.Visual; using osuTK; using osuTK.Graphics; -namespace osu.Game.Rulesets.Osu.Tests +namespace osu.Game.Rulesets.Osu.Tests.Editor { public class TestSceneOsuDistanceSnapGrid : OsuManualInputManagerTestScene { diff --git a/osu.Game.Rulesets.Osu.Tests/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs similarity index 97% rename from osu.Game.Rulesets.Osu.Tests/TestScenePathControlPointVisualiser.cs rename to osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs index 21fa283b6d..738a21b17e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestScenePathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs @@ -12,7 +12,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Visual; using osuTK; -namespace osu.Game.Rulesets.Osu.Tests +namespace osu.Game.Rulesets.Osu.Tests.Editor { public class TestScenePathControlPointVisualiser : OsuTestScene { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs similarity index 99% rename from osu.Game.Rulesets.Osu.Tests/TestSceneSliderPlacementBlueprint.cs rename to osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index fe9973f4d8..49d7d9249c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -14,7 +14,7 @@ using osu.Game.Tests.Visual; using osuTK; using osuTK.Input; -namespace osu.Game.Rulesets.Osu.Tests +namespace osu.Game.Rulesets.Osu.Tests.Editor { public class TestSceneSliderPlacementBlueprint : PlacementBlueprintTestScene { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs similarity index 99% rename from osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs rename to osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs index d5be538d94..f6e1be693b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs @@ -16,7 +16,7 @@ using osu.Game.Tests.Visual; using osuTK; using osuTK.Input; -namespace osu.Game.Rulesets.Osu.Tests +namespace osu.Game.Rulesets.Osu.Tests.Editor { public class TestSceneSliderSelectionBlueprint : SelectionBlueprintTestScene { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerPlacementBlueprint.cs similarity index 94% rename from osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerPlacementBlueprint.cs rename to osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerPlacementBlueprint.cs index d74d072857..fa6c660b01 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerPlacementBlueprint.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Tests.Visual; -namespace osu.Game.Rulesets.Osu.Tests +namespace osu.Game.Rulesets.Osu.Tests.Editor { public class TestSceneSpinnerPlacementBlueprint : PlacementBlueprintTestScene { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerSelectionBlueprint.cs similarity index 96% rename from osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs rename to osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerSelectionBlueprint.cs index 011463ab14..4248f68a60 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSpinnerSelectionBlueprint.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Tests.Visual; using osuTK; -namespace osu.Game.Rulesets.Osu.Tests +namespace osu.Game.Rulesets.Osu.Tests.Editor { public class TestSceneSpinnerSelectionBlueprint : SelectionBlueprintTestScene { diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditor.cs similarity index 88% rename from osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs rename to osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditor.cs index 411fe08bcf..e3c1613bd9 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditor.cs @@ -4,7 +4,7 @@ using NUnit.Framework; using osu.Game.Tests.Visual; -namespace osu.Game.Rulesets.Taiko.Tests +namespace osu.Game.Rulesets.Taiko.Tests.Editor { [TestFixture] public class TestSceneEditor : EditorTestScene diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs similarity index 97% rename from osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs rename to osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs index 34d5fdf857..626537053a 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs @@ -12,7 +12,7 @@ using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Screens.Edit; using osu.Game.Tests.Visual; -namespace osu.Game.Rulesets.Taiko.Tests +namespace osu.Game.Rulesets.Taiko.Tests.Editor { public class TestSceneTaikoHitObjectComposer : EditorClockTestScene { From acfa62bb50e669cdcfdc45f24f67339df7799eba Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Sep 2020 19:25:58 +0900 Subject: [PATCH 32/53] Fix potential taiko crash on rewind --- osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs | 13 +++++++++---- .../DrawableTestStrongHit.cs | 15 +++------------ .../Skinning/TestSceneHitExplosion.cs | 10 ++++------ osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs | 12 +++++++++--- osu.Game.Rulesets.Taiko/UI/HitExplosion.cs | 11 +++++------ osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 8 ++++---- 6 files changed, 34 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs index 1db07b3244..3ffc6187b7 100644 --- a/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs @@ -2,26 +2,31 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects.Drawables; namespace osu.Game.Rulesets.Taiko.Tests { - internal class DrawableTestHit : DrawableTaikoHitObject + public class DrawableTestHit : DrawableHit { - private readonly HitResult type; + public readonly HitResult Type; public DrawableTestHit(Hit hit, HitResult type = HitResult.Great) : base(hit) { - this.type = type; + Type = type; + + // in order to create nested strong hit + HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); } [BackgroundDependencyLoader] private void load() { - Result.Type = type; + Result.Type = Type; } public override bool OnPressed(TaikoAction action) => false; diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTestStrongHit.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTestStrongHit.cs index 7cb984b254..829bcf34a1 100644 --- a/osu.Game.Rulesets.Taiko.Tests/DrawableTestStrongHit.cs +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTestStrongHit.cs @@ -2,17 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects.Drawables; namespace osu.Game.Rulesets.Taiko.Tests { - public class DrawableTestStrongHit : DrawableHit + public class DrawableTestStrongHit : DrawableTestHit { - private readonly HitResult type; private readonly bool hitBoth; public DrawableTestStrongHit(double startTime, HitResult type = HitResult.Great, bool hitBoth = true) @@ -20,12 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Tests { IsStrong = true, StartTime = startTime, - }) + }, type) { - // in order to create nested strong hit - HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - - this.type = type; this.hitBoth = hitBoth; } @@ -33,10 +26,8 @@ namespace osu.Game.Rulesets.Taiko.Tests { base.LoadAsyncComplete(); - Result.Type = type; - var nestedStrongHit = (DrawableStrongNestedHit)NestedHitObjects.Single(); - nestedStrongHit.Result.Type = hitBoth ? type : HitResult.Miss; + nestedStrongHit.Result.Type = hitBoth ? Type : HitResult.Miss; } public override bool OnPressed(TaikoAction action) => false; diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs index 48969e0f5a..19cc56527e 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.Taiko.UI; namespace osu.Game.Rulesets.Taiko.Tests.Skinning @@ -29,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning AddStep("Good", () => SetContents(() => getContentFor(createStrongHit(HitResult.Good, hitBoth)))); } - private Drawable getContentFor(DrawableTaikoHitObject hit) + private Drawable getContentFor(DrawableTestHit hit) { return new Container { @@ -37,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning Children = new Drawable[] { hit, - new HitExplosion(hit) + new HitExplosion(hit, hit.Type) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -46,9 +45,8 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning }; } - private DrawableTaikoHitObject createHit(HitResult type) => new DrawableTestHit(new Hit { StartTime = Time.Current }, type); + private DrawableTestHit createHit(HitResult type) => new DrawableTestHit(new Hit { StartTime = Time.Current }, type); - private DrawableTaikoHitObject createStrongHit(HitResult type, bool hitBoth) - => new DrawableTestStrongHit(Time.Current, type, hitBoth); + private DrawableTestHit createStrongHit(HitResult type, bool hitBoth) => new DrawableTestStrongHit(Time.Current, type, hitBoth); } } diff --git a/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs index 9943a58e3e..7b8ab89233 100644 --- a/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs @@ -15,8 +15,14 @@ namespace osu.Game.Rulesets.Taiko.UI { internal class DefaultHitExplosion : CircularContainer { - [Resolved] - private DrawableHitObject judgedObject { get; set; } + private readonly DrawableHitObject judgedObject; + private readonly HitResult result; + + public DefaultHitExplosion(DrawableHitObject judgedObject, HitResult result) + { + this.judgedObject = judgedObject; + this.result = result; + } [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -31,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.UI Alpha = 0.15f; Masking = true; - if (judgedObject.Result.Type == HitResult.Miss) + if (result == HitResult.Miss) return; bool isRim = (judgedObject.HitObject as Hit)?.Type == HitType.Rim; diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs index e3eabbf88f..16300d5715 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs @@ -22,8 +22,8 @@ namespace osu.Game.Rulesets.Taiko.UI { public override bool RemoveWhenNotAlive => true; - [Cached(typeof(DrawableHitObject))] public readonly DrawableHitObject JudgedObject; + private readonly HitResult result; private SkinnableDrawable skinnable; @@ -31,9 +31,10 @@ namespace osu.Game.Rulesets.Taiko.UI public override double LifetimeEnd => skinnable.Drawable.LifetimeEnd; - public HitExplosion(DrawableHitObject judgedObject) + public HitExplosion(DrawableHitObject judgedObject, HitResult result) { JudgedObject = judgedObject; + this.result = result; Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -47,14 +48,12 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load() { - Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(JudgedObject)), _ => new DefaultHitExplosion()); + Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(JudgedObject)), _ => new DefaultHitExplosion(JudgedObject, result)); } private TaikoSkinComponents getComponentName(DrawableHitObject judgedObject) { - var resultType = judgedObject.Result?.Type ?? HitResult.Great; - - switch (resultType) + switch (result) { case HitResult.Miss: return TaikoSkinComponents.TaikoExplosionMiss; diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 7976d5bc6d..7b3fbb1faf 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -9,6 +9,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.Taiko.Objects.Drawables; @@ -206,8 +207,7 @@ namespace osu.Game.Rulesets.Taiko.UI }); var type = (judgedObject.HitObject as Hit)?.Type ?? HitType.Centre; - - addExplosion(judgedObject, type); + addExplosion(judgedObject, result.Type, type); break; } } @@ -219,9 +219,9 @@ namespace osu.Game.Rulesets.Taiko.UI /// As legacy skins have different explosions for singular and double strong hits, /// explosion addition is scheduled to ensure that both hits are processed if they occur on the same frame. /// - private void addExplosion(DrawableHitObject drawableObject, HitType type) => Schedule(() => + private void addExplosion(DrawableHitObject drawableObject, HitResult result, HitType type) => Schedule(() => { - hitExplosionContainer.Add(new HitExplosion(drawableObject)); + hitExplosionContainer.Add(new HitExplosion(drawableObject, result)); if (drawableObject.HitObject.Kiai) kiaiExplosionContainer.Add(new KiaiHitExplosion(drawableObject, type)); }); From 480eeb5fbee83ce1ac7eb5c28fb55e71b78c4640 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Sep 2020 19:37:34 +0900 Subject: [PATCH 33/53] Add back caching --- osu.Game.Rulesets.Taiko/UI/HitExplosion.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs index 16300d5715..efd1b25046 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs @@ -22,7 +22,9 @@ namespace osu.Game.Rulesets.Taiko.UI { public override bool RemoveWhenNotAlive => true; + [Cached(typeof(DrawableHitObject))] public readonly DrawableHitObject JudgedObject; + private readonly HitResult result; private SkinnableDrawable skinnable; From 0853f0e128dc14a880bc8d7ae0c2553fd67b4b9f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Sep 2020 19:38:23 +0900 Subject: [PATCH 34/53] Remove comment --- osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs index 3ffc6187b7..e0af973b53 100644 --- a/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs @@ -19,7 +19,6 @@ namespace osu.Game.Rulesets.Taiko.Tests { Type = type; - // in order to create nested strong hit HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); } From 9a24346a008ed8c730bea94adb9271324c5a79a7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Sep 2020 23:29:40 +0900 Subject: [PATCH 35/53] Fix HP drain edgecase potentially causing insta-fails --- .../TestSceneDrainingHealthProcessor.cs | 39 +++++++++++++++++++ .../Scoring/DrainingHealthProcessor.cs | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs index 460ad1b898..4876d051aa 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs @@ -1,15 +1,18 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Threading; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Tests.Visual; @@ -175,6 +178,24 @@ namespace osu.Game.Tests.Gameplay assertHealthNotEqualTo(0); } + [Test] + public void TestSingleLongObjectDoesNotDrain() + { + var beatmap = new Beatmap + { + HitObjects = { new JudgeableLongHitObject() } + }; + + beatmap.HitObjects[0].ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + createProcessor(beatmap); + setTime(0); + assertHealthEqualTo(1); + + setTime(5000); + assertHealthEqualTo(1); + } + private Beatmap createBeatmap(double startTime, double endTime, params BreakPeriod[] breaks) { var beatmap = new Beatmap @@ -235,5 +256,23 @@ namespace osu.Game.Tests.Gameplay } } } + + private class JudgeableLongHitObject : JudgeableHitObject, IHasDuration + { + public double EndTime => StartTime + Duration; + public double Duration { get; set; } = 5000; + + public JudgeableLongHitObject() + : base(false) + { + } + + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) + { + base.CreateNestedHitObjects(cancellationToken); + + AddNested(new JudgeableHitObject()); + } + } } } diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index 130907b242..ba9bbb055f 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Scoring private double computeDrainRate() { - if (healthIncreases.Count == 0) + if (healthIncreases.Count <= 1) return 0; int adjustment = 1; From b64e69fabd46f83e64853e8630ac06090032f904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Sep 2020 17:18:50 +0200 Subject: [PATCH 36/53] Add test hits to playfields directly where possible --- .../Skinning/TestSceneDrawableTaikoMascot.cs | 2 +- osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs index 47d8a5c012..36289bda93 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs @@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning foreach (var playfield in playfields) { var hit = new DrawableTestHit(new Hit(), judgementResult.Type); - Add(hit); + playfield.Add(hit); playfield.OnNewResult(hit, judgementResult); } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs index 99d1b72ea4..9fc07340c6 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs @@ -149,7 +149,7 @@ namespace osu.Game.Rulesets.Taiko.Tests var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; - Add(h); + drawableRuleset.Playfield.Add(h); ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); } @@ -166,7 +166,7 @@ namespace osu.Game.Rulesets.Taiko.Tests var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; - Add(h); + drawableRuleset.Playfield.Add(h); ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great }); @@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Taiko.Tests private void addMissJudgement() { DrawableTestHit h; - Add(h = new DrawableTestHit(new Hit(), HitResult.Miss)); + drawableRuleset.Playfield.Add(h = new DrawableTestHit(new Hit(), HitResult.Miss)); ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss }); } From 095686a320823d80be303f2d6028a0da1bf4bb8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Sep 2020 17:26:26 +0200 Subject: [PATCH 37/53] Hide test hit directly in explosion scene --- osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs | 6 ++++++ .../Skinning/TestSceneHitExplosion.cs | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs index e0af973b53..4eeb4a1475 100644 --- a/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs @@ -22,6 +22,12 @@ namespace osu.Game.Rulesets.Taiko.Tests HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); } + protected override void UpdateInitialTransforms() + { + // base implementation in DrawableHitObject forces alpha to 1. + // suppress locally to allow hiding the visuals wherever necessary. + } + [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs index 19cc56527e..45c94a8a86 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs @@ -35,7 +35,9 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - hit, + // the hit needs to be added to hierarchy in order for nested objects to be created correctly. + // setting zero alpha is supposed to prevent the test from looking broken. + hit.With(h => h.Alpha = 0), new HitExplosion(hit, hit.Type) { Anchor = Anchor.Centre, From b1e02db8742e0510d88c68cbe0e15823abee86e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Sep 2020 20:36:38 +0200 Subject: [PATCH 38/53] Extract base taiko drawable ruleset scene --- .../DrawableTaikoRulesetTestScene.cs | 55 ++++++++++++++ .../TestSceneHits.cs | 74 +++++-------------- 2 files changed, 75 insertions(+), 54 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs new file mode 100644 index 0000000000..d1c4a1c56d --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs @@ -0,0 +1,55 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.UI; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public abstract class DrawableTaikoRulesetTestScene : OsuTestScene + { + protected DrawableTaikoRuleset DrawableRuleset { get; private set; } + protected Container PlayfieldContainer { get; private set; } + + [BackgroundDependencyLoader] + private void load() + { + var controlPointInfo = new ControlPointInfo(); + controlPointInfo.Add(0, new TimingControlPoint()); + + WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap + { + HitObjects = new List { new Hit { Type = HitType.Centre } }, + BeatmapInfo = new BeatmapInfo + { + BaseDifficulty = new BeatmapDifficulty(), + Metadata = new BeatmapMetadata + { + Artist = @"Unknown", + Title = @"Sample Beatmap", + AuthorString = @"peppy", + }, + Ruleset = new TaikoRuleset().RulesetInfo + }, + ControlPointInfo = controlPointInfo + }); + + Add(PlayfieldContainer = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = 768, + Children = new[] { DrawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(new TaikoRuleset().RulesetInfo)) } + }); + } + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs index 9fc07340c6..b6cfe368f7 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs @@ -2,11 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -18,13 +16,12 @@ using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.Taiko.UI; -using osu.Game.Tests.Visual; using osuTK; namespace osu.Game.Rulesets.Taiko.Tests { [TestFixture] - public class TestSceneHits : OsuTestScene + public class TestSceneHits : DrawableTaikoRulesetTestScene { private const double default_duration = 3000; private const float scroll_time = 1000; @@ -32,8 +29,6 @@ namespace osu.Game.Rulesets.Taiko.Tests protected override double TimePerAction => default_duration * 2; private readonly Random rng = new Random(1337); - private DrawableTaikoRuleset drawableRuleset; - private Container playfieldContainer; [BackgroundDependencyLoader] private void load() @@ -64,35 +59,6 @@ namespace osu.Game.Rulesets.Taiko.Tests AddStep("Height test 4", () => changePlayfieldSize(4)); AddStep("Height test 5", () => changePlayfieldSize(5)); AddStep("Reset height", () => changePlayfieldSize(6)); - - var controlPointInfo = new ControlPointInfo(); - controlPointInfo.Add(0, new TimingControlPoint()); - - WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap - { - HitObjects = new List { new Hit { Type = HitType.Centre } }, - BeatmapInfo = new BeatmapInfo - { - BaseDifficulty = new BeatmapDifficulty(), - Metadata = new BeatmapMetadata - { - Artist = @"Unknown", - Title = @"Sample Beatmap", - AuthorString = @"peppy", - }, - Ruleset = new TaikoRuleset().RulesetInfo - }, - ControlPointInfo = controlPointInfo - }); - - Add(playfieldContainer = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = 768, - Children = new[] { drawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(new TaikoRuleset().RulesetInfo)) } - }); } private void changePlayfieldSize(int step) @@ -128,11 +94,11 @@ namespace osu.Game.Rulesets.Taiko.Tests switch (step) { default: - playfieldContainer.Delay(delay).ResizeTo(new Vector2(1, rng.Next(25, 400)), 500); + PlayfieldContainer.Delay(delay).ResizeTo(new Vector2(1, rng.Next(25, 400)), 500); break; case 6: - playfieldContainer.Delay(delay).ResizeTo(new Vector2(1, TaikoPlayfield.DEFAULT_HEIGHT), 500); + PlayfieldContainer.Delay(delay).ResizeTo(new Vector2(1, TaikoPlayfield.DEFAULT_HEIGHT), 500); break; } } @@ -149,9 +115,9 @@ namespace osu.Game.Rulesets.Taiko.Tests var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; - drawableRuleset.Playfield.Add(h); + DrawableRuleset.Playfield.Add(h); - ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); } private void addStrongHitJudgement(bool kiai) @@ -166,37 +132,37 @@ namespace osu.Game.Rulesets.Taiko.Tests var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; - drawableRuleset.Playfield.Add(h); + DrawableRuleset.Playfield.Add(h); - ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); - ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great }); + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great }); } private void addMissJudgement() { DrawableTestHit h; - drawableRuleset.Playfield.Add(h = new DrawableTestHit(new Hit(), HitResult.Miss)); - ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss }); + DrawableRuleset.Playfield.Add(h = new DrawableTestHit(new Hit(), HitResult.Miss)); + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss }); } private void addBarLine(bool major, double delay = scroll_time) { - BarLine bl = new BarLine { StartTime = drawableRuleset.Playfield.Time.Current + delay }; + BarLine bl = new BarLine { StartTime = DrawableRuleset.Playfield.Time.Current + delay }; - drawableRuleset.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl)); + DrawableRuleset.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl)); } private void addSwell(double duration = default_duration) { var swell = new Swell { - StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, + StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time, Duration = duration, }; swell.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - drawableRuleset.Playfield.Add(new DrawableSwell(swell)); + DrawableRuleset.Playfield.Add(new DrawableSwell(swell)); } private void addDrumRoll(bool strong, double duration = default_duration, bool kiai = false) @@ -206,7 +172,7 @@ namespace osu.Game.Rulesets.Taiko.Tests var d = new DrumRoll { - StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, + StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time, IsStrong = strong, Duration = duration, TickRate = 8, @@ -217,33 +183,33 @@ namespace osu.Game.Rulesets.Taiko.Tests d.ApplyDefaults(cpi, new BeatmapDifficulty()); - drawableRuleset.Playfield.Add(new DrawableDrumRoll(d)); + DrawableRuleset.Playfield.Add(new DrawableDrumRoll(d)); } private void addCentreHit(bool strong) { Hit h = new Hit { - StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, + StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time, IsStrong = strong }; h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - drawableRuleset.Playfield.Add(new DrawableHit(h)); + DrawableRuleset.Playfield.Add(new DrawableHit(h)); } private void addRimHit(bool strong) { Hit h = new Hit { - StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, + StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time, IsStrong = strong }; h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - drawableRuleset.Playfield.Add(new DrawableHit(h)); + DrawableRuleset.Playfield.Add(new DrawableHit(h)); } private class TestStrongNestedHit : DrawableStrongNestedHit From 0563a488f479083260753908b2ca8c140d3d98b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Sep 2020 20:37:23 +0200 Subject: [PATCH 39/53] Add failing test case --- .../TestSceneFlyingHits.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs new file mode 100644 index 0000000000..7492a76a67 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs @@ -0,0 +1,48 @@ +// 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.Testing; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Judgements; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Objects.Drawables; +using osu.Game.Rulesets.Taiko.UI; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + [TestFixture] + public class TestSceneFlyingHits : DrawableTaikoRulesetTestScene + { + [TestCase(HitType.Centre)] + [TestCase(HitType.Rim)] + public void TestFlyingHits(HitType hitType) + { + DrawableFlyingHit flyingHit = null; + + AddStep("add flying hit", () => + { + addFlyingHit(hitType); + + // flying hits all land in one common scrolling container (and stay there for rewind purposes), + // so we need to manually get the latest one. + flyingHit = this.ChildrenOfType() + .OrderByDescending(h => h.HitObject.StartTime) + .FirstOrDefault(); + }); + + AddAssert("hit type is correct", () => flyingHit.HitObject.Type == hitType); + } + + private void addFlyingHit(HitType hitType) + { + var tick = new DrumRollTick { HitWindows = HitWindows.Empty, StartTime = DrawableRuleset.Playfield.Time.Current }; + + DrawableDrumRollTick h; + DrawableRuleset.Playfield.Add(h = new DrawableDrumRollTick(tick) { JudgementType = hitType }); + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(tick, new TaikoDrumRollTickJudgement()) { Type = HitResult.Perfect }); + } + } +} From d61a8327da94243b65d7761ee23d9a5f4649a5fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Sep 2020 20:59:55 +0200 Subject: [PATCH 40/53] Fix rim flying hits changing colour --- .../Objects/Drawables/DrawableFlyingHit.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs index 460e760629..3253c1ce5a 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs @@ -27,5 +27,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables base.LoadComplete(); ApplyResult(r => r.Type = r.Judgement.MaxResult); } + + protected override void LoadSamples() + { + // block base call - flying hits are not supposed to play samples + // the base call could overwrite the type of this hit + } } } From 00aea7748970cb3d29bde9f7a171d6ba5a545480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 27 Sep 2020 11:18:13 +0200 Subject: [PATCH 41/53] Fix potential instability of overlay activation tests --- .../Visual/Gameplay/TestSceneOverlayActivation.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs index 3ee0f4e720..e36cc6861d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs @@ -12,6 +12,14 @@ namespace osu.Game.Tests.Visual.Gameplay { protected new OverlayTestPlayer Player => base.Player as OverlayTestPlayer; + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddUntilStep("gameplay has started", + () => Player.GameplayClockContainer.GameplayClock.CurrentTime > Player.DrawableRuleset.GameplayStartTime); + } + [Test] public void TestGameplayOverlayActivation() { From 8d9945dea895ab03f7fcb9562a1d533ebc502d81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 27 Sep 2020 11:28:20 +0200 Subject: [PATCH 42/53] Change until step to assert for consistency --- osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs index e36cc6861d..ce04b940e7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestGameplayOverlayActivationPaused() { - AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); + AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddStep("pause gameplay", () => Player.Pause()); AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); } From 3cf430f49434ed3883ca1c5dc06844ca3d287327 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 15:30:18 +0900 Subject: [PATCH 43/53] Avoid saving state changes if nothing has changed (via binary comparison) --- .../Editing/EditorChangeHandlerTest.cs | 57 ++++++++++++++++++- osu.Game/Screens/Edit/EditorChangeHandler.cs | 28 +++++---- 2 files changed, 71 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs index feda1ae0e9..ff2c9fb1a9 100644 --- a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs +++ b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; namespace osu.Game.Tests.Editing @@ -13,11 +15,12 @@ namespace osu.Game.Tests.Editing [Test] public void TestSaveRestoreState() { - var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap())); + var (handler, beatmap) = createChangeHandler(); Assert.That(handler.CanUndo.Value, Is.False); Assert.That(handler.CanRedo.Value, Is.False); + addArbitraryChange(beatmap); handler.SaveState(); Assert.That(handler.CanUndo.Value, Is.True); @@ -29,15 +32,48 @@ namespace osu.Game.Tests.Editing Assert.That(handler.CanRedo.Value, Is.True); } + [Test] + public void TestSaveSameStateDoesNotSave() + { + var (handler, beatmap) = createChangeHandler(); + + Assert.That(handler.CanUndo.Value, Is.False); + Assert.That(handler.CanRedo.Value, Is.False); + + addArbitraryChange(beatmap); + handler.SaveState(); + + Assert.That(handler.CanUndo.Value, Is.True); + Assert.That(handler.CanRedo.Value, Is.False); + + string hash = handler.CurrentStateHash; + + // save a save without making any changes + handler.SaveState(); + + Assert.That(hash, Is.EqualTo(handler.CurrentStateHash)); + + handler.RestoreState(-1); + + Assert.That(hash, Is.Not.EqualTo(handler.CurrentStateHash)); + + // we should only be able to restore once even though we saved twice. + Assert.That(handler.CanUndo.Value, Is.False); + Assert.That(handler.CanRedo.Value, Is.True); + } + [Test] public void TestMaxStatesSaved() { - var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap())); + var (handler, beatmap) = createChangeHandler(); Assert.That(handler.CanUndo.Value, Is.False); for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES; i++) + { + addArbitraryChange(beatmap); handler.SaveState(); + } Assert.That(handler.CanUndo.Value, Is.True); @@ -53,12 +89,15 @@ namespace osu.Game.Tests.Editing [Test] public void TestMaxStatesExceeded() { - var handler = new EditorChangeHandler(new EditorBeatmap(new Beatmap())); + var (handler, beatmap) = createChangeHandler(); Assert.That(handler.CanUndo.Value, Is.False); for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES * 2; i++) + { + addArbitraryChange(beatmap); handler.SaveState(); + } Assert.That(handler.CanUndo.Value, Is.True); @@ -70,5 +109,17 @@ namespace osu.Game.Tests.Editing Assert.That(handler.CanUndo.Value, Is.False); } + + private (EditorChangeHandler, EditorBeatmap) createChangeHandler() + { + var beatmap = new EditorBeatmap(new Beatmap()); + + return (new EditorChangeHandler(beatmap), beatmap); + } + + private void addArbitraryChange(EditorBeatmap beatmap) + { + beatmap.Add(new HitCircle { StartTime = RNG.Next(0, 100000) }); + } } } diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index aa0f89912a..286fdbb020 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -4,9 +4,11 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Framework.Logging; using osu.Game.Beatmaps.Formats; using osu.Game.Rulesets.Objects; @@ -89,23 +91,27 @@ namespace osu.Game.Screens.Edit if (isRestoring) return; - if (currentState < savedStates.Count - 1) - savedStates.RemoveRange(currentState + 1, savedStates.Count - currentState - 1); - - if (savedStates.Count > MAX_SAVED_STATES) - savedStates.RemoveAt(0); - using (var stream = new MemoryStream()) { using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) new LegacyBeatmapEncoder(editorBeatmap, editorBeatmap.BeatmapSkin).Encode(sw); - savedStates.Add(stream.ToArray()); + var newState = stream.ToArray(); + + // if the previous state is binary equal we don't need to push a new one, unless this is the initial state. + if (savedStates.Count > 0 && newState.SequenceEqual(savedStates.Last())) return; + + if (currentState < savedStates.Count - 1) + savedStates.RemoveRange(currentState + 1, savedStates.Count - currentState - 1); + + if (savedStates.Count > MAX_SAVED_STATES) + savedStates.RemoveAt(0); + + savedStates.Add(newState); + + currentState = savedStates.Count - 1; + updateBindables(); } - - currentState = savedStates.Count - 1; - - updateBindables(); } /// From 1aa8b400d432b17031d4d5a37898dc0a3ac66d2c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 14:45:36 +0900 Subject: [PATCH 44/53] Avoid unnecessary object updates from SelectionHandlers --- osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs | 8 +++++++- .../Screens/Edit/Compose/Components/SelectionHandler.cs | 3 +-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index a3ecf7ed95..d5dd758e10 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -57,7 +57,13 @@ namespace osu.Game.Rulesets.Taiko.Edit ChangeHandler.BeginChange(); foreach (var h in hits) - h.IsStrong = state; + { + if (h.IsStrong != state) + { + h.IsStrong = state; + EditorBeatmap.UpdateHitObject(h); + } + } ChangeHandler.EndChange(); } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 6ca85fe026..a0220cf987 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -288,8 +288,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { var comboInfo = h as IHasComboInformation; - if (comboInfo == null) - continue; + if (comboInfo == null || comboInfo.NewCombo == state) continue; comboInfo.NewCombo = state; EditorBeatmap?.UpdateHitObject(h); From 0ae2266b8229ba5c8192385230c4506b2bf3e5a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 14:51:28 +0900 Subject: [PATCH 45/53] Fix new placement hitobjects in the editor not getting the default sample added --- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 81d7fa4b32..9b3314e2ad 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -212,6 +212,9 @@ namespace osu.Game.Screens.Edit.Compose.Components if (blueprint != null) { + // doing this post-creations as adding the default hit sample should be the case regardless of the ruleset. + blueprint.HitObject.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_NORMAL }); + placementBlueprintContainer.Child = currentPlacement = blueprint; // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame From a4e9c85333b06d293cc88a921ac38612490b1c04 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 14:37:55 +0900 Subject: [PATCH 46/53] Trigger a hitobject update after blueprint drag ends --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 8908520cd7..970e16d1c3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -201,6 +201,10 @@ namespace osu.Game.Screens.Edit.Compose.Components if (isDraggingBlueprint) { + // handle positional change etc. + foreach (var obj in selectedHitObjects) + Beatmap.UpdateHitObject(obj); + changeHandler?.EndChange(); isDraggingBlueprint = false; } From 6095446f10df0a34b1676aa1bf46deb9cceb7a26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 14:15:26 +0900 Subject: [PATCH 47/53] Fix autoplay generators failing on empty hitobjects lists --- osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs | 3 +++ osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs | 3 +++ osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 3 +++ osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs | 3 +++ 4 files changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index 5d11c574b1..a4f54bfe82 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -31,6 +31,9 @@ namespace osu.Game.Rulesets.Catch.Replays public override Replay Generate() { + if (Beatmap.HitObjects.Count == 0) + return Replay; + // todo: add support for HT DT const double dash_speed = Catcher.BASE_SPEED; const double movement_speed = dash_speed / 2; diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 483327d5b3..3ebbe5af8e 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -46,6 +46,9 @@ namespace osu.Game.Rulesets.Mania.Replays public override Replay Generate() { + if (Beatmap.HitObjects.Count == 0) + return Replay; + var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time); var actions = new List(); diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 76b2631894..9b350278f3 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -72,6 +72,9 @@ namespace osu.Game.Rulesets.Osu.Replays public override Replay Generate() { + if (Beatmap.HitObjects.Count == 0) + return Replay; + buttonIndex = 0; AddFrameToReplay(new OsuReplayFrame(-100000, new Vector2(256, 500))); diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index 273f4e4105..db2e5948f5 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -30,6 +30,9 @@ namespace osu.Game.Rulesets.Taiko.Replays public override Replay Generate() { + if (Beatmap.HitObjects.Count == 0) + return Replay; + bool hitButton = true; Frames.Add(new TaikoReplayFrame(-100000)); From e8220cf1b62dd7fcdd2afa13bb39b53b8a3771a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 14:03:56 +0900 Subject: [PATCH 48/53] Allow attaching a replay to a FrameStabilityContainer when FrameStablePlayback is off --- .../Rulesets/UI/FrameStabilityContainer.cs | 72 ++++++++++++------- 1 file changed, 48 insertions(+), 24 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index d574991fa0..a4af92749f 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -123,39 +123,63 @@ namespace osu.Game.Rulesets.UI try { - if (!FrameStablePlayback) - return; - - if (firstConsumption) + if (FrameStablePlayback) { - // On the first update, frame-stability seeking would result in unexpected/unwanted behaviour. - // Instead we perform an initial seek to the proposed time. + if (firstConsumption) + { + // On the first update, frame-stability seeking would result in unexpected/unwanted behaviour. + // Instead we perform an initial seek to the proposed time. - // process frame (in addition to finally clause) to clear out ElapsedTime - manualClock.CurrentTime = newProposedTime; - framedClock.ProcessFrame(); + // process frame (in addition to finally clause) to clear out ElapsedTime + manualClock.CurrentTime = newProposedTime; + framedClock.ProcessFrame(); - firstConsumption = false; - } - else if (manualClock.CurrentTime < gameplayStartTime) - manualClock.CurrentTime = newProposedTime = Math.Min(gameplayStartTime, newProposedTime); - else if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) - { - newProposedTime = newProposedTime > manualClock.CurrentTime - ? Math.Min(newProposedTime, manualClock.CurrentTime + sixty_frame_time) - : Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time); + firstConsumption = false; + } + else if (manualClock.CurrentTime < gameplayStartTime) + manualClock.CurrentTime = newProposedTime = Math.Min(gameplayStartTime, newProposedTime); + else if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) + { + newProposedTime = newProposedTime > manualClock.CurrentTime + ? Math.Min(newProposedTime, manualClock.CurrentTime + sixty_frame_time) + : Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time); + } } if (isAttached) { - double? newTime = ReplayInputHandler.SetFrameFromTime(newProposedTime); + double? newTime; - if (newTime == null) + if (FrameStablePlayback) { - // we shouldn't execute for this time value. probably waiting on more replay data. - validState = false; - requireMoreUpdateLoops = true; - return; + // when stability is turned on, we shouldn't execute for time values the replay is unable to satisfy. + if ((newTime = ReplayInputHandler.SetFrameFromTime(newProposedTime)) == null) + { + // setting invalid state here ensures that gameplay will not continue (ie. our child + // hierarchy won't be updated). + validState = false; + + // potentially loop to catch-up playback. + requireMoreUpdateLoops = true; + + return; + } + } + else + { + // when stability is disabled, we don't really care about accuracy. + // looping over the replay will allow it to catch up and feed out the required values + // for the current time. + while ((newTime = ReplayInputHandler.SetFrameFromTime(newProposedTime)) != newProposedTime) + { + if (newTime == null) + { + // special case for when the replay actually can't arrive at the required time. + // protects from potential endless loop. + validState = false; + return; + } + } } newProposedTime = newTime.Value; From ff7c904996083e985dd41b389656b190f357b202 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 14:03:37 +0900 Subject: [PATCH 49/53] Add autoplay mod in editor specific ruleset construction --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index b9b7c1ef54..6e377ff207 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Edit try { - drawableRulesetWrapper = new DrawableEditRulesetWrapper(CreateDrawableRuleset(Ruleset, EditorBeatmap.PlayableBeatmap)) + drawableRulesetWrapper = new DrawableEditRulesetWrapper(CreateDrawableRuleset(Ruleset, EditorBeatmap.PlayableBeatmap, new[] { Ruleset.GetAutoplayMod() })) { Clock = EditorClock, ProcessCustomClock = false From 524c2b678c68b71d604342b32e5274fbb7684607 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 14:15:54 +0900 Subject: [PATCH 50/53] Forcefully regenerate autoplay on editor changes --- .../Rulesets/Edit/DrawableEditRulesetWrapper.cs | 8 ++++++++ osu.Game/Rulesets/UI/DrawableRuleset.cs | 14 ++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs index 89e7866707..1070b8cbd2 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs @@ -45,15 +45,21 @@ namespace osu.Game.Rulesets.Edit base.LoadComplete(); beatmap.HitObjectAdded += addHitObject; + beatmap.HitObjectUpdated += updateReplay; beatmap.HitObjectRemoved += removeHitObject; } + private void updateReplay(HitObject obj = null) => + drawableRuleset.RegenerateAutoplay(); + private void addHitObject(HitObject hitObject) { var drawableObject = drawableRuleset.CreateDrawableRepresentation((TObject)hitObject); drawableRuleset.Playfield.Add(drawableObject); drawableRuleset.Playfield.PostProcess(); + + updateReplay(); } private void removeHitObject(HitObject hitObject) @@ -62,6 +68,8 @@ namespace osu.Game.Rulesets.Edit drawableRuleset.Playfield.Remove(drawableObject); drawableRuleset.Playfield.PostProcess(); + + drawableRuleset.RegenerateAutoplay(); } public override bool PropagatePositionalInputSubTree => false; diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index fbb9acfe90..50e9a93e22 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -151,8 +151,11 @@ namespace osu.Game.Rulesets.UI public virtual PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new PlayfieldAdjustmentContainer(); + [Resolved] + private OsuConfigManager config { get; set; } + [BackgroundDependencyLoader] - private void load(OsuConfigManager config, CancellationToken? cancellationToken) + private void load(CancellationToken? cancellationToken) { InternalChildren = new Drawable[] { @@ -178,11 +181,18 @@ namespace osu.Game.Rulesets.UI .WithChild(ResumeOverlay))); } - applyRulesetMods(Mods, config); + RegenerateAutoplay(); loadObjects(cancellationToken); } + public void RegenerateAutoplay() + { + // for now this is applying mods which aren't just autoplay. + // we'll need to reconsider this flow in the future. + applyRulesetMods(Mods, config); + } + /// /// Creates and adds drawable representations of hit objects to the play field. /// From 7949eabaac45fe2a67ab5a4f283c8851e314300c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 15:49:45 +0900 Subject: [PATCH 51/53] Remove left-over using --- osu.Game/Screens/Edit/EditorChangeHandler.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index 286fdbb020..617c436ee0 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Text; using osu.Framework.Bindables; using osu.Framework.Extensions; -using osu.Framework.Logging; using osu.Game.Beatmaps.Formats; using osu.Game.Rulesets.Objects; From 467a16bf750600b0edc47674acd022334c632d89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 16:21:20 +0900 Subject: [PATCH 52/53] Fix fade out extension logic (and make it generally look better for sliders) --- .../Edit/DrawableOsuEditRuleset.cs | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index a8719e0aa8..01e59c9598 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -8,6 +8,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osuTK; @@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Edit /// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay. /// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points. /// - private const double editor_hit_object_fade_out_extension = 500; + private const double editor_hit_object_fade_out_extension = 700; public DrawableOsuEditRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) @@ -32,20 +33,37 @@ namespace osu.Game.Rulesets.Osu.Edit private void updateState(DrawableHitObject hitObject, ArmedState state) { - switch (state) + if (state == ArmedState.Idle) + return; + + // adjust the visuals of certain object types to make them stay on screen for longer than usual. + switch (hitObject) { - case ArmedState.Miss: - // Get the existing fade out transform - var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha)); - if (existing == null) - return; - - hitObject.RemoveTransform(existing); - - using (hitObject.BeginAbsoluteSequence(existing.StartTime)) - hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire(); + case DrawableSlider slider: + // no specifics to sliders but let them fade slower below. break; + + case DrawableHitCircle circle: // also handles slider heads + circle.ApproachCircle + .FadeOutFromOne(editor_hit_object_fade_out_extension) + .Expire(); + break; + + default: + // there are quite a few drawable hit types we don't want to extent (spinners, ticks etc.) + return; } + + // Get the existing fade out transform + var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha)); + + if (existing == null) + return; + + hitObject.RemoveTransform(existing); + + using (hitObject.BeginAbsoluteSequence(existing.StartTime)) + hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire(); } protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor(); From 5237fa7bf24cdaca9e2a8c2e24bdceff5906ff84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 16:37:54 +0900 Subject: [PATCH 53/53] Remove unused local in case statement --- osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index 01e59c9598..746ff4ac19 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -39,7 +39,11 @@ namespace osu.Game.Rulesets.Osu.Edit // adjust the visuals of certain object types to make them stay on screen for longer than usual. switch (hitObject) { - case DrawableSlider slider: + default: + // there are quite a few drawable hit types we don't want to extent (spinners, ticks etc.) + return; + + case DrawableSlider _: // no specifics to sliders but let them fade slower below. break; @@ -48,10 +52,6 @@ namespace osu.Game.Rulesets.Osu.Edit .FadeOutFromOne(editor_hit_object_fade_out_extension) .Expire(); break; - - default: - // there are quite a few drawable hit types we don't want to extent (spinners, ticks etc.) - return; } // Get the existing fade out transform