From 10af64234275b47fb26543519eee0fba220db68b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 11 Jun 2024 11:30:20 +0200 Subject: [PATCH 1/5] Split mania difficulty section implementation off completely from base - "Circle size" / key count needs completely different handling. - Approach rate does not exist in mania. --- .../Edit/Setup/ManiaDifficultySection.cs | 120 +++++++++++++++++- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 2 +- 3 files changed, 117 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs index 4f983debea..0df6f3a1f7 100644 --- a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs +++ b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs @@ -3,20 +3,130 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Localisation; +using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Localisation; using osu.Game.Resources.Localisation.Web; +using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Setup; namespace osu.Game.Rulesets.Mania.Edit.Setup { - public partial class ManiaDifficultySection : DifficultySection + public partial class ManiaDifficultySection : SetupSection { + public override LocalisableString Title => EditorSetupStrings.DifficultyHeader; + + private LabelledSliderBar keyCountSlider { get; set; } = null!; + private LabelledSliderBar healthDrainSlider { get; set; } = null!; + private LabelledSliderBar overallDifficultySlider { get; set; } = null!; + private LabelledSliderBar baseVelocitySlider { get; set; } = null!; + private LabelledSliderBar tickRateSlider { get; set; } = null!; + + [Resolved] + private Editor editor { get; set; } = null!; + + [Resolved] + private IEditorChangeHandler changeHandler { get; set; } = null!; + [BackgroundDependencyLoader] private void load() { - CircleSizeSlider.Label = BeatmapsetsStrings.ShowStatsCsMania; - CircleSizeSlider.Description = "The number of columns in the beatmap"; - if (CircleSizeSlider.Current is BindableNumber circleSizeFloat) - circleSizeFloat.Precision = 1; + Children = new Drawable[] + { + keyCountSlider = new LabelledSliderBar + { + Label = BeatmapsetsStrings.ShowStatsCsMania, + FixedLabelWidth = LABEL_WIDTH, + Description = "The number of columns in the beatmap", + Current = new BindableFloat(Beatmap.Difficulty.CircleSize) + { + Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, + MinValue = 0, + MaxValue = 10, + Precision = 1, + } + }, + healthDrainSlider = new LabelledSliderBar + { + Label = BeatmapsetsStrings.ShowStatsDrain, + FixedLabelWidth = LABEL_WIDTH, + Description = EditorSetupStrings.DrainRateDescription, + Current = new BindableFloat(Beatmap.Difficulty.DrainRate) + { + Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, + MinValue = 0, + MaxValue = 10, + Precision = 0.1f, + } + }, + overallDifficultySlider = new LabelledSliderBar + { + Label = BeatmapsetsStrings.ShowStatsAccuracy, + FixedLabelWidth = LABEL_WIDTH, + Description = EditorSetupStrings.OverallDifficultyDescription, + Current = new BindableFloat(Beatmap.Difficulty.OverallDifficulty) + { + Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, + MinValue = 0, + MaxValue = 10, + Precision = 0.1f, + } + }, + baseVelocitySlider = new LabelledSliderBar + { + Label = EditorSetupStrings.BaseVelocity, + FixedLabelWidth = LABEL_WIDTH, + Description = EditorSetupStrings.BaseVelocityDescription, + Current = new BindableDouble(Beatmap.Difficulty.SliderMultiplier) + { + Default = 1.4, + MinValue = 0.4, + MaxValue = 3.6, + Precision = 0.01f, + } + }, + tickRateSlider = new LabelledSliderBar + { + Label = EditorSetupStrings.TickRate, + FixedLabelWidth = LABEL_WIDTH, + Description = EditorSetupStrings.TickRateDescription, + Current = new BindableDouble(Beatmap.Difficulty.SliderTickRate) + { + Default = 1, + MinValue = 1, + MaxValue = 4, + Precision = 1, + } + }, + }; + + keyCountSlider.Current.BindValueChanged(updateKeyCount); + healthDrainSlider.Current.BindValueChanged(_ => updateValues()); + overallDifficultySlider.Current.BindValueChanged(_ => updateValues()); + baseVelocitySlider.Current.BindValueChanged(_ => updateValues()); + tickRateSlider.Current.BindValueChanged(_ => updateValues()); + } + + private void updateKeyCount(ValueChangedEvent keyCount) + { + updateValues(); + } + + private void updateValues() + { + // for now, update these on commit rather than making BeatmapMetadata bindables. + // after switching database engines we can reconsider if switching to bindables is a good direction. + Beatmap.Difficulty.CircleSize = keyCountSlider.Current.Value; + Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value; + Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value; + Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value; + Beatmap.Difficulty.SliderTickRate = tickRateSlider.Current.Value; + + Beatmap.UpdateAllHitObjects(); + Beatmap.SaveState(); } } } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index b5614e2b56..40eb44944c 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -421,7 +421,7 @@ namespace osu.Game.Rulesets.Mania public override RulesetSetupSection CreateEditorSetupSection() => new ManiaSetupSection(); - public override DifficultySection CreateEditorDifficultySection() => new ManiaDifficultySection(); + public override SetupSection CreateEditorDifficultySection() => new ManiaDifficultySection(); public int GetKeyCount(IBeatmapInfo beatmapInfo, IReadOnlyList? mods = null) => ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo), mods); diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 37a35fd3ae..cae2ce610e 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -401,6 +401,6 @@ namespace osu.Game.Rulesets /// /// Can be overridden to alter the difficulty section to the editor beatmap setup screen. /// - public virtual DifficultySection? CreateEditorDifficultySection() => null; + public virtual SetupSection? CreateEditorDifficultySection() => null; } } From 3afe98612c4020fe833d410ad4de2d85d5036cd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 11 Jun 2024 11:31:30 +0200 Subject: [PATCH 2/5] Add `RestoreState()` to `IEditorChangeHandler` --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 2 ++ osu.Game/Screens/Edit/EditorChangeHandler.cs | 4 ---- osu.Game/Screens/Edit/IEditorChangeHandler.cs | 6 ++++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 67fd6a9550..41fd701a09 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -540,6 +540,8 @@ namespace osu.Game.Overlays.SkinEditor protected void Redo() => changeHandler?.RestoreState(1); + void IEditorChangeHandler.RestoreState(int direction) => changeHandler?.RestoreState(direction); + public void Save(bool userTriggered = true) => save(currentSkin.Value, userTriggered); private void save(Skin skin, bool userTriggered = true) diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index 0bb17e4c5d..f8ef133549 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -83,10 +83,6 @@ namespace osu.Game.Screens.Edit } } - /// - /// Restores an older or newer state. - /// - /// The direction to restore in. If less than 0, an older state will be used. If greater than 0, a newer state will be used. public void RestoreState(int direction) { if (TransactionActive) diff --git a/osu.Game/Screens/Edit/IEditorChangeHandler.cs b/osu.Game/Screens/Edit/IEditorChangeHandler.cs index 9fe40ba1b1..2259b52ea8 100644 --- a/osu.Game/Screens/Edit/IEditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/IEditorChangeHandler.cs @@ -43,5 +43,11 @@ namespace osu.Game.Screens.Edit /// Note that this will be a no-op if there is a change in progress via . /// void SaveState(); + + /// + /// Restores an older or newer state. + /// + /// The direction to restore in. If less than 0, an older state will be used. If greater than 0, a newer state will be used. + void RestoreState(int direction); } } From da53a11d3ca009670b2f0d04aec887b0e8f70d14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 11 Jun 2024 11:31:49 +0200 Subject: [PATCH 3/5] Attempt full editor reload on key count change --- .../Edit/Setup/ManiaDifficultySection.cs | 22 ++++++++++++ osu.Game/Localisation/EditorDialogsStrings.cs | 5 +++ osu.Game/Screens/Edit/Editor.cs | 21 ++++++++++++ osu.Game/Screens/Edit/ReloadEditorDialog.cs | 34 +++++++++++++++++++ 4 files changed, 82 insertions(+) create mode 100644 osu.Game/Screens/Edit/ReloadEditorDialog.cs diff --git a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs index 0df6f3a1f7..f62c63bf8e 100644 --- a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs +++ b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs @@ -110,9 +110,31 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup tickRateSlider.Current.BindValueChanged(_ => updateValues()); } + private bool updatingKeyCount; + private void updateKeyCount(ValueChangedEvent keyCount) { + if (updatingKeyCount) return; + + updatingKeyCount = true; + updateValues(); + editor.Reload().ContinueWith(t => + { + if (!t.GetResultSafely()) + { + Schedule(() => + { + changeHandler.RestoreState(-1); + Beatmap.Difficulty.CircleSize = keyCountSlider.Current.Value = keyCount.OldValue; + updatingKeyCount = false; + }); + } + else + { + updatingKeyCount = false; + } + }); } private void updateValues() diff --git a/osu.Game/Localisation/EditorDialogsStrings.cs b/osu.Game/Localisation/EditorDialogsStrings.cs index fc4c2b7f2a..94f28c617c 100644 --- a/osu.Game/Localisation/EditorDialogsStrings.cs +++ b/osu.Game/Localisation/EditorDialogsStrings.cs @@ -49,6 +49,11 @@ namespace osu.Game.Localisation /// public static LocalisableString ContinueEditing => new TranslatableString(getKey(@"continue_editing"), @"Oops, continue editing"); + /// + /// "The editor must be reloaded to apply this change. The beatmap will be saved." + /// + public static LocalisableString EditorReloadDialogHeader => new TranslatableString(getKey(@"editor_reload_dialog_header"), @"The editor must be reloaded to apply this change. The beatmap will be saved."); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 3e3e772810..a630a5df41 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1231,6 +1231,27 @@ namespace osu.Game.Screens.Edit loader?.CancelPendingDifficultySwitch(); } + public Task Reload() + { + var tcs = new TaskCompletionSource(); + + dialogOverlay.Push(new ReloadEditorDialog( + reload: () => + { + bool reloadedSuccessfully = attemptMutationOperation(() => + { + if (!Save()) + return false; + + SwitchToDifficulty(editorBeatmap.BeatmapInfo); + return true; + }); + tcs.SetResult(reloadedSuccessfully); + }, + cancel: () => tcs.SetResult(false))); + return tcs.Task; + } + public void HandleTimestamp(string timestamp) { if (!EditorTimestampParser.TryParse(timestamp, out var timeSpan, out string selection)) diff --git a/osu.Game/Screens/Edit/ReloadEditorDialog.cs b/osu.Game/Screens/Edit/ReloadEditorDialog.cs new file mode 100644 index 0000000000..72a9f81347 --- /dev/null +++ b/osu.Game/Screens/Edit/ReloadEditorDialog.cs @@ -0,0 +1,34 @@ +// 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.Graphics.Sprites; +using osu.Game.Overlays.Dialog; +using osu.Game.Localisation; + +namespace osu.Game.Screens.Edit +{ + public partial class ReloadEditorDialog : PopupDialog + { + public ReloadEditorDialog(Action reload, Action cancel) + { + HeaderText = EditorDialogsStrings.EditorReloadDialogHeader; + + Icon = FontAwesome.Solid.Sync; + + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = DialogStrings.Confirm, + Action = reload + }, + new PopupDialogCancelButton + { + Text = DialogStrings.Cancel, + Action = cancel + } + }; + } + } +} From e67d73be7dd1ffa6eb7fd8089c801206c889536a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 11 Jun 2024 12:00:02 +0200 Subject: [PATCH 4/5] Add test coverage --- .../Editor/TestSceneManiaEditorSaving.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaEditorSaving.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaEditorSaving.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaEditorSaving.cs new file mode 100644 index 0000000000..9765648f44 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaEditorSaving.cs @@ -0,0 +1,45 @@ +// 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.Graphics.UserInterfaceV2; +using osu.Game.Overlays; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Setup; +using osu.Game.Tests.Visual; +using osuTK.Input; + +namespace osu.Game.Rulesets.Mania.Tests.Editor +{ + public partial class TestSceneManiaEditorSaving : EditorSavingTestScene + { + protected override Ruleset CreateRuleset() => new ManiaRuleset(); + + [Test] + public void TestKeyCountChange() + { + LabelledSliderBar keyCount = null!; + + AddStep("go to setup screen", () => InputManager.Key(Key.F4)); + AddUntilStep("retrieve key count slider", () => keyCount = Editor.ChildrenOfType().Single().ChildrenOfType>().First(), () => Is.Not.Null); + AddAssert("key count is 5", () => keyCount.Current.Value, () => Is.EqualTo(5)); + AddStep("change key count to 8", () => + { + keyCount.Current.Value = 8; + }); + AddUntilStep("dialog visible", () => Game.ChildrenOfType().SingleOrDefault()?.CurrentDialog, Is.InstanceOf); + AddStep("refuse", () => InputManager.Key(Key.Number2)); + AddAssert("key count is 5", () => keyCount.Current.Value, () => Is.EqualTo(5)); + + AddStep("change key count to 8 again", () => + { + keyCount.Current.Value = 8; + }); + AddUntilStep("dialog visible", () => Game.ChildrenOfType().Single().CurrentDialog, Is.InstanceOf); + AddStep("acquiesce", () => InputManager.Key(Key.Number1)); + AddUntilStep("beatmap became 8K", () => Game.Beatmap.Value.BeatmapInfo.Difficulty.CircleSize, () => Is.EqualTo(8)); + } + } +} From 2a8bd8d9686fe3cd6474b8a939eb6123a7858f75 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jun 2024 11:23:56 +0800 Subject: [PATCH 5/5] Fix failing tests due to missing DI pieces --- .../Edit/Setup/ManiaDifficultySection.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs index f62c63bf8e..62b54a7215 100644 --- a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs +++ b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs @@ -26,10 +26,10 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup private LabelledSliderBar tickRateSlider { get; set; } = null!; [Resolved] - private Editor editor { get; set; } = null!; + private Editor? editor { get; set; } [Resolved] - private IEditorChangeHandler changeHandler { get; set; } = null!; + private IEditorChangeHandler? changeHandler { get; set; } [BackgroundDependencyLoader] private void load() @@ -116,16 +116,19 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup { if (updatingKeyCount) return; + updateValues(); + + if (editor == null) return; + updatingKeyCount = true; - updateValues(); editor.Reload().ContinueWith(t => { if (!t.GetResultSafely()) { Schedule(() => { - changeHandler.RestoreState(-1); + changeHandler!.RestoreState(-1); Beatmap.Difficulty.CircleSize = keyCountSlider.Current.Value = keyCount.OldValue; updatingKeyCount = false; });