// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Overlays.Dialog; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit; using osu.Game.Storyboards; using osu.Game.Tests.Beatmaps.IO; namespace osu.Game.Tests.Visual.Editing { public partial class TestSceneDifficultySwitching : EditorTestScene { protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); protected override bool IsolateSavingFromDatabase => false; [Resolved] private OsuGameBase game { get; set; } [Resolved] private BeatmapManager beatmaps { get; set; } private BeatmapSetInfo importedBeatmapSet; public override void SetUpSteps() { AddStep("import test beatmap", () => importedBeatmapSet = BeatmapImportHelper.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely()); base.SetUpSteps(); } protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First()); [Test] public void TestBasicSwitch() { BeatmapInfo targetDifficulty = null; AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo))); switchToDifficulty(() => targetDifficulty); confirmEditingBeatmap(() => targetDifficulty); AddStep("exit editor", () => Stack.Exit()); // ensure editor loader didn't resume. AddAssert("stack empty", () => Stack.CurrentScreen == null); } [Test] public void TestClockPositionPreservedBetweenSwitches() { BeatmapInfo targetDifficulty = null; AddStep("seek editor to 00:05:00", () => EditorClock.Seek(5000)); AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo))); switchToDifficulty(() => targetDifficulty); confirmEditingBeatmap(() => targetDifficulty); AddAssert("editor clock at 00:05:00", () => EditorClock.CurrentTime == 5000); AddStep("exit editor", () => Stack.Exit()); // ensure editor loader didn't resume. AddAssert("stack empty", () => Stack.CurrentScreen == null); } [Test] public void TestClipboardPreservedAfterSwitch([Values] bool sameRuleset) { BeatmapInfo targetDifficulty = null; AddStep("select first object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.First())); AddStep("copy object", () => Editor.Copy()); AddStep("set target difficulty", () => { targetDifficulty = sameRuleset ? importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.Ruleset.ShortName == Beatmap.Value.BeatmapInfo.Ruleset.ShortName) : importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.Ruleset.ShortName != Beatmap.Value.BeatmapInfo.Ruleset.ShortName); }); switchToDifficulty(() => targetDifficulty); confirmEditingBeatmap(() => targetDifficulty); AddAssert("no objects selected", () => !EditorBeatmap.SelectedHitObjects.Any()); AddUntilStep("wait for drawable ruleset", () => Editor.ChildrenOfType().SingleOrDefault()?.IsLoaded == true); AddStep("paste object", () => Editor.Paste()); if (sameRuleset) AddAssert("object was pasted", () => EditorBeatmap.SelectedHitObjects.Any()); else AddAssert("object was not pasted", () => !EditorBeatmap.SelectedHitObjects.Any()); AddStep("exit editor", () => Stack.Exit()); if (sameRuleset) { AddUntilStep("prompt for save dialog shown", () => DialogOverlay.CurrentDialog is PromptForSaveDialog); AddStep("discard changes", () => ((PromptForSaveDialog)DialogOverlay.CurrentDialog)?.PerformOkAction()); } // ensure editor loader didn't resume. AddAssert("stack empty", () => Stack.CurrentScreen == null); } [Test] public void TestPreventSwitchDueToUnsavedChanges() { BeatmapInfo targetDifficulty = null; PromptForSaveDialog saveDialog = null; AddStep("remove first hitobject", () => EditorBeatmap.RemoveAt(0)); AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo))); switchToDifficulty(() => targetDifficulty); AddUntilStep("prompt for save dialog shown", () => { saveDialog = this.ChildrenOfType().Single(); return saveDialog != null; }); AddStep("continue editing", () => { var continueButton = saveDialog.ChildrenOfType().Last(); continueButton.TriggerClick(); }); confirmEditingBeatmap(() => importedBeatmapSet.Beatmaps.First()); AddRepeatStep("exit editor forcefully", () => Stack.Exit(), 2); // ensure editor loader didn't resume. AddAssert("stack empty", () => Stack.CurrentScreen == null); } [Test] public void TestAllowSwitchAfterDiscardingUnsavedChanges() { BeatmapInfo targetDifficulty = null; PromptForSaveDialog saveDialog = null; AddStep("remove first hitobject", () => EditorBeatmap.RemoveAt(0)); AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo))); switchToDifficulty(() => targetDifficulty); AddUntilStep("prompt for save dialog shown", () => { saveDialog = this.ChildrenOfType().Single(); return saveDialog != null; }); AddStep("discard changes", () => { var continueButton = saveDialog.ChildrenOfType().Single(); continueButton.TriggerClick(); }); confirmEditingBeatmap(() => targetDifficulty); AddStep("exit editor forcefully", () => Stack.Exit()); // ensure editor loader didn't resume. AddAssert("stack empty", () => Stack.CurrentScreen == null); } [Test] public void TestSwitchToDifficultyOfAnotherRuleset() { BeatmapInfo targetDifficulty = null; AddAssert("ruleset is catch", () => Ruleset.Value.CreateInstance() is CatchRuleset); AddStep("set taiko difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == 1)); switchToDifficulty(() => targetDifficulty); confirmEditingBeatmap(() => targetDifficulty); AddAssert("ruleset switched to taiko", () => Ruleset.Value.CreateInstance() is TaikoRuleset); AddStep("exit editor forcefully", () => Stack.Exit()); // ensure editor loader didn't resume. AddAssert("stack empty", () => Stack.CurrentScreen == null); } private void switchToDifficulty(Func difficulty) => AddStep("switch to difficulty", () => Editor.SwitchToDifficulty(difficulty.Invoke())); private void confirmEditingBeatmap(Func targetDifficulty) { AddUntilStep("current beatmap is correct", () => Beatmap.Value.BeatmapInfo.Equals(targetDifficulty.Invoke())); AddUntilStep("current screen is editor", () => Stack.CurrentScreen == Editor && Editor?.IsLoaded == true); } } }