diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs new file mode 100644 index 0000000000..db42667033 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -0,0 +1,123 @@ +// 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.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Screens; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Components.Timelines.Summary; +using osu.Game.Tests.Beatmaps.IO; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneEditorTestGameplay : EditorTestScene + { + protected override bool IsolateSavingFromDatabase => false; + + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + [Resolved] + private OsuGameBase game { get; set; } + + [Resolved] + private BeatmapManager beatmaps { get; set; } + + private BeatmapSetInfo importedBeatmapSet; + + public override void SetUpSteps() + { + AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game).Result); + base.SetUpSteps(); + } + + protected override void LoadEditor() + { + Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0)); + base.LoadEditor(); + } + + [Test] + public void TestBasicGameplayTest() + { + AddStep("click test gameplay button", () => + { + var button = Editor.ChildrenOfType().Single(); + + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + + EditorPlayer editorPlayer = null; + AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null); + AddStep("exit player", () => editorPlayer.Exit()); + AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); + } + + [Test] + public void TestCancelGameplayTestWithUnsavedChanges() + { + AddStep("delete all but first object", () => EditorBeatmap.RemoveRange(EditorBeatmap.HitObjects.Skip(1).ToList())); + + AddStep("click test gameplay button", () => + { + var button = Editor.ChildrenOfType().Single(); + + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("save prompt shown", () => DialogOverlay.CurrentDialog is SaveBeforeGameplayTestDialog); + + AddStep("dismiss prompt", () => + { + var button = DialogOverlay.CurrentDialog.Buttons.Last(); + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + + AddWaitStep("wait some", 3); + AddAssert("stayed in editor", () => Stack.CurrentScreen is Editor); + } + + [Test] + public void TestSaveChangesBeforeGameplayTest() + { + AddStep("delete all but first object", () => EditorBeatmap.RemoveRange(EditorBeatmap.HitObjects.Skip(1).ToList())); + // bit of a hack to ensure this test can be ran multiple times without running into UNIQUE constraint failures + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = Guid.NewGuid().ToString()); + + AddStep("click test gameplay button", () => + { + var button = Editor.ChildrenOfType().Single(); + + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("save prompt shown", () => DialogOverlay.CurrentDialog is SaveBeforeGameplayTestDialog); + + AddStep("save changes", () => DialogOverlay.CurrentDialog.PerformOkAction()); + + EditorPlayer editorPlayer = null; + AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null); + AddAssert("beatmap has 1 object", () => editorPlayer.Beatmap.Value.Beatmap.HitObjects.Count == 1); + + AddUntilStep("wait for return to editor", () => Stack.CurrentScreen is Editor); + AddAssert("track stopped", () => !Beatmap.Value.Track.IsRunning); + } + + public override void TearDownSteps() + { + base.TearDownSteps(); + AddStep("delete imported", () => + { + beatmaps.Delete(importedBeatmapSet); + }); + } + } +} diff --git a/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs index c3c803ff23..03e78ce854 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Edit; @@ -23,6 +24,9 @@ namespace osu.Game.Tests.Visual.Editing [Cached(typeof(IBeatSnapProvider))] private readonly EditorBeatmap editorBeatmap; + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + public TestSceneSetupScreen() { editorBeatmap = new EditorBeatmap(new OsuBeatmap()); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs index f961fff1e5..4bbffbdc7a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; @@ -18,6 +19,9 @@ namespace osu.Game.Tests.Visual.Editing [Cached(typeof(IBeatSnapProvider))] private readonly EditorBeatmap editorBeatmap; + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + protected override bool ScrollUsingMouseWheel => false; public TestSceneTimingScreen() diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 22446634c1..c71cb6a00a 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -76,6 +76,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.J }, GlobalAction.EditorNudgeLeft), new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight), new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode), + new KeyBinding(new[] { InputKey.F5 }, GlobalAction.EditorTestGameplay), }; public IEnumerable InGameKeyBindings => new[] @@ -288,6 +289,9 @@ namespace osu.Game.Input.Bindings ToggleChatFocus, [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridDisplayMode))] - EditorCycleGridDisplayMode + EditorCycleGridDisplayMode, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestGameplay))] + EditorTestGameplay } } diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 06f1b094bf..35a0c2ae74 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -169,6 +169,11 @@ namespace osu.Game.Localisation /// public static LocalisableString EditorCycleGridDisplayMode => new TranslatableString(getKey(@"editor_cycle_grid_display_mode"), @"Cycle grid display mode"); + /// + /// "Test gameplay" + /// + public static LocalisableString EditorTestGameplay => new TranslatableString(getKey(@"editor_test_gameplay"), @"Test gameplay"); + /// /// "Hold for HUD" /// diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/TestGameplayButton.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/TestGameplayButton.cs new file mode 100644 index 0000000000..0d7a4ad057 --- /dev/null +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/TestGameplayButton.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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary +{ + public class TestGameplayButton : OsuButton + { + protected override SpriteText CreateText() => new OsuSpriteText + { + Depth = -1, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Font = OsuFont.TorusAlternate.With(weight: FontWeight.Light, size: 24), + Shadow = false + }; + + [BackgroundDependencyLoader] + private void load(OsuColour colours, OverlayColourProvider colourProvider) + { + BackgroundColour = colours.Orange1; + SpriteText.Colour = colourProvider.Background6; + + Text = "Test!"; + } + } +} diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 0ca7038580..738f607cc8 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -36,6 +36,7 @@ using osu.Game.Screens.Edit.Timing; using osu.Game.Screens.Edit.Verify; using osu.Game.Screens.Play; using osu.Game.Users; +using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -90,6 +91,8 @@ namespace osu.Game.Screens.Edit private DependencyContainer dependencies; + private TestGameplayButton testGameplayButton; + private bool isNewBeatmap; protected override UserActivity InitialActivity => new UserActivity.Editing(Beatmap.Value.BeatmapInfo); @@ -106,6 +109,9 @@ namespace osu.Game.Screens.Edit [Cached] public readonly EditorClipboard Clipboard = new EditorClipboard(); + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + public Editor(EditorLoader loader = null) { this.loader = loader; @@ -262,7 +268,8 @@ namespace osu.Game.Screens.Edit { new Dimension(GridSizeMode.Absolute, 220), new Dimension(), - new Dimension(GridSizeMode.Absolute, 220) + new Dimension(GridSizeMode.Absolute, 220), + new Dimension(GridSizeMode.Absolute, 120), }, Content = new[] { @@ -283,6 +290,13 @@ namespace osu.Game.Screens.Edit RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Left = 10 }, Child = new PlaybackControl { RelativeSizeAxes = Axes.Both }, + }, + testGameplayButton = new TestGameplayButton + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = 10 }, + Size = new Vector2(1), + Action = testGameplay } }, } @@ -456,6 +470,10 @@ namespace osu.Game.Screens.Edit menuBar.Mode.Value = EditorScreenMode.Verify; return true; + case GlobalAction.EditorTestGameplay: + testGameplayButton.TriggerClick(); + return true; + default: return false; } @@ -510,7 +528,21 @@ namespace osu.Game.Screens.Edit ApplyToBackground(b => b.FadeColour(Color4.White, 500)); resetTrack(); - // To update the game-wide beatmap with any changes, perform a re-fetch on exit. + refetchBeatmap(); + + return base.OnExiting(next); + } + + public override void OnSuspending(IScreen next) + { + refetchBeatmap(); + + base.OnSuspending(next); + } + + private void refetchBeatmap() + { + // To update the game-wide beatmap with any changes, perform a re-fetch on exit/suspend. // This is required as the editor makes its local changes via EditorBeatmap // (which are not propagated outwards to a potentially cached WorkingBeatmap). var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(Beatmap.Value.BeatmapInfo); @@ -520,8 +552,6 @@ namespace osu.Game.Screens.Edit Logger.Log("Editor providing re-fetched beatmap post edit session"); Beatmap.Value = refetchedBeatmap; } - - return base.OnExiting(next); } private void confirmExitWithSave() @@ -752,6 +782,24 @@ namespace osu.Game.Screens.Edit loader?.CancelPendingDifficultySwitch(); } + private void testGameplay() + { + if (HasUnsavedChanges) + { + dialogOverlay.Push(new SaveBeforeGameplayTestDialog(() => + { + Save(); + pushEditorPlayer(); + })); + } + else + { + pushEditorPlayer(); + } + + void pushEditorPlayer() => this.Push(new PlayerLoader(() => new EditorPlayer())); + } + public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); public double GetBeatLengthAtTime(double referenceTime) => editorBeatmap.GetBeatLengthAtTime(referenceTime); diff --git a/osu.Game/Screens/Edit/EditorPlayer.cs b/osu.Game/Screens/Edit/EditorPlayer.cs new file mode 100644 index 0000000000..b2fab3fefc --- /dev/null +++ b/osu.Game/Screens/Edit/EditorPlayer.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 osu.Framework.Allocation; +using osu.Framework.Screens; +using osu.Game.Overlays; +using osu.Game.Screens.Play; + +namespace osu.Game.Screens.Edit +{ + public class EditorPlayer : Player + { + public EditorPlayer() + : base(new PlayerConfiguration { ShowResults = false }) + { + } + + [Resolved] + private MusicController musicController { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + ScoreProcessor.HasCompleted.BindValueChanged(completed => + { + if (completed.NewValue) + Scheduler.AddDelayed(this.Exit, RESULTS_DISPLAY_DELAY); + }); + } + + protected override void PrepareReplay() + { + // don't record replays. + } + + protected override bool CheckModsAllowFailure() => false; // never fail. + + public override bool OnExiting(IScreen next) + { + musicController.Stop(); + return base.OnExiting(next); + } + } +} diff --git a/osu.Game/Screens/Edit/EditorRoundedScreen.cs b/osu.Game/Screens/Edit/EditorRoundedScreen.cs index 508663224d..7f7b3abc2a 100644 --- a/osu.Game/Screens/Edit/EditorRoundedScreen.cs +++ b/osu.Game/Screens/Edit/EditorRoundedScreen.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Overlays; namespace osu.Game.Screens.Edit { @@ -26,7 +27,7 @@ namespace osu.Game.Screens.Edit } [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider colourProvider) { base.Content.Add(new Container { @@ -41,7 +42,7 @@ namespace osu.Game.Screens.Edit { new Box { - Colour = ColourProvider.Background3, + Colour = colourProvider.Background3, RelativeSizeAxes = Axes.Both, }, roundedContent = new Container diff --git a/osu.Game/Screens/Edit/EditorScreen.cs b/osu.Game/Screens/Edit/EditorScreen.cs index 516d7a23e0..2837cdcd9a 100644 --- a/osu.Game/Screens/Edit/EditorScreen.cs +++ b/osu.Game/Screens/Edit/EditorScreen.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Game.Overlays; namespace osu.Game.Screens.Edit { @@ -18,9 +17,6 @@ namespace osu.Game.Screens.Edit [Resolved] protected EditorBeatmap EditorBeatmap { get; private set; } - [Cached] - protected readonly OverlayColourProvider ColourProvider; - protected override Container Content => content; private readonly Container content; @@ -34,8 +30,6 @@ namespace osu.Game.Screens.Edit Origin = Anchor.Centre; RelativeSizeAxes = Axes.Both; - ColourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); - InternalChild = content = new PopoverContainer { RelativeSizeAxes = Axes.Both }; } diff --git a/osu.Game/Screens/Edit/SaveBeforeGameplayTestDialog.cs b/osu.Game/Screens/Edit/SaveBeforeGameplayTestDialog.cs new file mode 100644 index 0000000000..7c03664c66 --- /dev/null +++ b/osu.Game/Screens/Edit/SaveBeforeGameplayTestDialog.cs @@ -0,0 +1,32 @@ +// 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; + +namespace osu.Game.Screens.Edit +{ + public class SaveBeforeGameplayTestDialog : PopupDialog + { + public SaveBeforeGameplayTestDialog(Action saveAndPreview) + { + HeaderText = "The beatmap will be saved in order to test it."; + + Icon = FontAwesome.Regular.Save; + + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = "Sounds good, let's go!", + Action = saveAndPreview + }, + new PopupDialogCancelButton + { + Text = "Oops, continue editing", + }, + }; + } + } +}