2022-04-24 04:15:31 +08:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
2022-06-17 15:37:17 +08:00
#nullable disable
2024-04-29 20:06:02 +08:00
using System ;
2022-04-24 04:15:31 +08:00
using System.Linq ;
using NUnit.Framework ;
2023-10-27 20:23:41 +08:00
using osu.Framework.Allocation ;
2022-04-24 04:15:31 +08:00
using osu.Framework.Extensions ;
2022-05-08 04:47:19 +08:00
using osu.Framework.Graphics.Containers ;
2022-04-24 04:15:31 +08:00
using osu.Framework.Graphics.UserInterface ;
2024-02-19 16:34:52 +08:00
using osu.Framework.Input ;
2022-06-20 12:45:50 +08:00
using osu.Framework.Screens ;
2022-04-24 04:15:31 +08:00
using osu.Framework.Testing ;
2022-06-20 12:45:50 +08:00
using osu.Framework.Threading ;
2023-12-04 20:50:52 +08:00
using osu.Game.Online.API ;
2023-12-04 20:58:07 +08:00
using osu.Game.Beatmaps ;
2022-04-24 04:15:31 +08:00
using osu.Game.Overlays.Settings ;
2023-01-26 17:21:04 +08:00
using osu.Game.Overlays.SkinEditor ;
2022-04-24 04:15:31 +08:00
using osu.Game.Rulesets.Mods ;
2023-12-04 20:50:52 +08:00
using osu.Game.Rulesets.Osu ;
2022-04-24 04:15:31 +08:00
using osu.Game.Rulesets.Osu.Mods ;
2023-03-04 21:07:35 +08:00
using osu.Game.Screens.Edit.Components ;
2022-04-24 04:15:31 +08:00
using osu.Game.Screens.Play ;
using osu.Game.Screens.Play.HUD.HitErrorMeters ;
2023-10-27 20:23:41 +08:00
using osu.Game.Skinning ;
2022-04-24 04:15:31 +08:00
using osu.Game.Tests.Beatmaps.IO ;
2022-06-20 12:45:50 +08:00
using osuTK ;
2022-04-24 04:15:31 +08:00
using osuTK.Input ;
using static osu . Game . Tests . Visual . Navigation . TestSceneScreenNavigation ;
namespace osu.Game.Tests.Visual.Navigation
{
2022-11-24 13:32:20 +08:00
public partial class TestSceneSkinEditorNavigation : OsuGameTestScene
2022-04-24 04:15:31 +08:00
{
2022-05-08 04:41:48 +08:00
private TestPlaySongSelect songSelect ;
private SkinEditor skinEditor = > Game . ChildrenOfType < SkinEditor > ( ) . FirstOrDefault ( ) ;
2022-04-24 04:15:31 +08:00
[Test]
2023-10-27 20:15:30 +08:00
public void TestEditComponentFromGameplayScene ( )
2022-04-24 04:15:31 +08:00
{
2022-05-08 04:41:48 +08:00
advanceToSongSelect ( ) ;
openSkinEditor ( ) ;
2023-12-04 20:34:36 +08:00
AddStep ( "import beatmap" , ( ) = > BeatmapImportHelper . LoadQuickOszIntoOsu ( Game ) . WaitSafely ( ) ) ;
AddUntilStep ( "wait for selected" , ( ) = > ! Game . Beatmap . IsDefault ) ;
2022-04-24 04:15:31 +08:00
switchToGameplayScene ( ) ;
BarHitErrorMeter hitErrorMeter = null ;
AddUntilStep ( "select bar hit error blueprint" , ( ) = >
{
var blueprint = skinEditor . ChildrenOfType < SkinBlueprint > ( ) . FirstOrDefault ( b = > b . Item is BarHitErrorMeter ) ;
if ( blueprint = = null )
return false ;
hitErrorMeter = ( BarHitErrorMeter ) blueprint . Item ;
skinEditor . SelectedComponents . Clear ( ) ;
skinEditor . SelectedComponents . Add ( blueprint . Item ) ;
return true ;
} ) ;
AddAssert ( "value is default" , ( ) = > hitErrorMeter . JudgementLineThickness . IsDefault ) ;
AddStep ( "hover first slider" , ( ) = >
{
InputManager . MoveMouseTo (
skinEditor . ChildrenOfType < SkinSettingsToolbox > ( ) . First ( )
. ChildrenOfType < SettingsSlider < float > > ( ) . First ( )
. ChildrenOfType < SliderBar < float > > ( ) . First ( )
) ;
} ) ;
AddStep ( "adjust slider via keyboard" , ( ) = > InputManager . Key ( Key . Left ) ) ;
AddAssert ( "value is less than default" , ( ) = > hitErrorMeter . JudgementLineThickness . Value < hitErrorMeter . JudgementLineThickness . Default ) ;
}
2023-10-27 20:23:41 +08:00
[Test]
public void TestMutateProtectedSkinDuringGameplay ( )
{
advanceToSongSelect ( ) ;
AddStep ( "set default skin" , ( ) = > Game . Dependencies . Get < SkinManager > ( ) . CurrentSkinInfo . SetDefault ( ) ) ;
AddStep ( "import beatmap" , ( ) = > BeatmapImportHelper . LoadQuickOszIntoOsu ( Game ) . WaitSafely ( ) ) ;
AddUntilStep ( "wait for selected" , ( ) = > ! Game . Beatmap . IsDefault ) ;
AddStep ( "enable NF" , ( ) = > Game . SelectedMods . Value = new [ ] { new OsuModNoFail ( ) } ) ;
AddStep ( "enter gameplay" , ( ) = > InputManager . Key ( Key . Enter ) ) ;
AddUntilStep ( "wait for player" , ( ) = >
{
DismissAnyNotifications ( ) ;
return Game . ScreenStack . CurrentScreen is Player ;
} ) ;
openSkinEditor ( ) ;
AddUntilStep ( "current skin is mutable" , ( ) = > ! Game . Dependencies . Get < SkinManager > ( ) . CurrentSkin . Value . SkinInfo . Value . Protected ) ;
}
2022-06-20 02:35:05 +08:00
[Test]
2022-06-20 12:45:50 +08:00
public void TestComponentsDeselectedOnSkinEditorHide ( )
2022-06-20 02:35:05 +08:00
{
advanceToSongSelect ( ) ;
openSkinEditor ( ) ;
2023-12-04 20:34:36 +08:00
AddStep ( "import beatmap" , ( ) = > BeatmapImportHelper . LoadQuickOszIntoOsu ( Game ) . WaitSafely ( ) ) ;
AddUntilStep ( "wait for selected" , ( ) = > ! Game . Beatmap . IsDefault ) ;
2022-06-20 02:35:05 +08:00
switchToGameplayScene ( ) ;
2022-06-20 12:45:50 +08:00
AddUntilStep ( "wait for components" , ( ) = > skinEditor . ChildrenOfType < SkinBlueprint > ( ) . Any ( ) ) ;
2022-06-20 02:35:05 +08:00
AddStep ( "select all components" , ( ) = >
{
InputManager . PressKey ( Key . ControlLeft ) ;
InputManager . Key ( Key . A ) ;
InputManager . ReleaseKey ( Key . ControlLeft ) ;
} ) ;
2022-06-20 12:45:50 +08:00
AddUntilStep ( "components selected" , ( ) = > skinEditor . SelectedComponents . Count > 0 ) ;
2022-06-20 12:29:43 +08:00
toggleSkinEditor ( ) ;
AddUntilStep ( "no components selected" , ( ) = > skinEditor . SelectedComponents . Count = = 0 ) ;
}
[Test]
2022-06-20 12:45:50 +08:00
public void TestSwitchScreenWhileDraggingComponent ( )
2022-06-20 12:29:43 +08:00
{
2022-06-20 12:45:50 +08:00
Vector2 firstBlueprintCentre = Vector2 . Zero ;
ScheduledDelegate movementDelegate = null ;
2022-06-20 12:29:43 +08:00
advanceToSongSelect ( ) ;
2022-06-20 12:45:50 +08:00
2022-06-20 12:29:43 +08:00
openSkinEditor ( ) ;
2022-06-20 12:45:50 +08:00
AddStep ( "add skinnable component" , ( ) = >
2022-06-20 02:35:05 +08:00
{
2022-06-20 12:45:50 +08:00
skinEditor . ChildrenOfType < SkinComponentToolbox . ToolboxComponentButton > ( ) . First ( ) . TriggerClick ( ) ;
} ) ;
AddUntilStep ( "newly added component selected" , ( ) = > skinEditor . SelectedComponents . Count = = 1 ) ;
AddStep ( "start drag" , ( ) = >
{
firstBlueprintCentre = skinEditor . ChildrenOfType < SkinBlueprint > ( ) . First ( ) . ScreenSpaceDrawQuad . Centre ;
InputManager . MoveMouseTo ( firstBlueprintCentre ) ;
InputManager . PressButton ( MouseButton . Left ) ;
2022-06-20 02:35:05 +08:00
} ) ;
2022-06-20 12:45:50 +08:00
AddStep ( "start movement" , ( ) = > movementDelegate = Scheduler . AddDelayed ( ( ) = > { InputManager . MoveMouseTo ( firstBlueprintCentre + = new Vector2 ( 1 ) ) ; } , 10 , true ) ) ;
2022-06-20 12:29:43 +08:00
toggleSkinEditor ( ) ;
2022-06-20 12:45:50 +08:00
AddStep ( "exit song select" , ( ) = > songSelect . Exit ( ) ) ;
2022-06-20 12:29:43 +08:00
2022-06-20 12:45:50 +08:00
AddUntilStep ( "wait for blueprints removed" , ( ) = > ! skinEditor . ChildrenOfType < SkinBlueprint > ( ) . Any ( ) ) ;
AddStep ( "stop drag" , ( ) = >
{
InputManager . ReleaseButton ( MouseButton . Left ) ;
movementDelegate ? . Cancel ( ) ;
} ) ;
2022-06-20 02:35:05 +08:00
}
2022-04-24 04:16:06 +08:00
[Test]
public void TestAutoplayCompatibleModsRetainedOnEnteringGameplay ( )
{
2022-05-08 04:41:48 +08:00
advanceToSongSelect ( ) ;
openSkinEditor ( ) ;
2022-04-24 04:16:06 +08:00
AddStep ( "select DT" , ( ) = > Game . SelectedMods . Value = new Mod [ ] { new OsuModDoubleTime ( ) } ) ;
2023-12-04 20:34:36 +08:00
AddStep ( "import beatmap" , ( ) = > BeatmapImportHelper . LoadQuickOszIntoOsu ( Game ) . WaitSafely ( ) ) ;
AddUntilStep ( "wait for selected" , ( ) = > ! Game . Beatmap . IsDefault ) ;
2022-04-24 04:16:06 +08:00
switchToGameplayScene ( ) ;
AddAssert ( "DT still selected" , ( ) = > ( ( Player ) Game . ScreenStack . CurrentScreen ) . Mods . Value . Single ( ) is OsuModDoubleTime ) ;
}
[Test]
public void TestAutoplayIncompatibleModsRemovedOnEnteringGameplay ( )
{
2022-05-08 04:41:48 +08:00
advanceToSongSelect ( ) ;
openSkinEditor ( ) ;
2023-07-14 03:22:48 +08:00
AddStep ( "select relax and spun out" , ( ) = > Game . SelectedMods . Value = new Mod [ ] { new OsuModRelax ( ) , new OsuModSpunOut ( ) } ) ;
2022-04-24 04:16:06 +08:00
2023-12-04 20:34:36 +08:00
AddStep ( "import beatmap" , ( ) = > BeatmapImportHelper . LoadQuickOszIntoOsu ( Game ) . WaitSafely ( ) ) ;
AddUntilStep ( "wait for selected" , ( ) = > ! Game . Beatmap . IsDefault ) ;
2022-04-24 04:16:06 +08:00
switchToGameplayScene ( ) ;
AddAssert ( "no mod selected" , ( ) = > ! ( ( Player ) Game . ScreenStack . CurrentScreen ) . Mods . Value . Any ( ) ) ;
}
[Test]
public void TestDuplicateAutoplayModRemovedOnEnteringGameplay ( )
{
2022-05-08 04:41:48 +08:00
advanceToSongSelect ( ) ;
openSkinEditor ( ) ;
2022-04-24 04:16:06 +08:00
AddStep ( "select autoplay" , ( ) = > Game . SelectedMods . Value = new Mod [ ] { new OsuModAutoplay ( ) } ) ;
2023-12-04 20:34:36 +08:00
AddStep ( "import beatmap" , ( ) = > BeatmapImportHelper . LoadQuickOszIntoOsu ( Game ) . WaitSafely ( ) ) ;
AddUntilStep ( "wait for selected" , ( ) = > ! Game . Beatmap . IsDefault ) ;
2022-04-24 04:16:06 +08:00
switchToGameplayScene ( ) ;
AddAssert ( "no mod selected" , ( ) = > ! ( ( Player ) Game . ScreenStack . CurrentScreen ) . Mods . Value . Any ( ) ) ;
}
[Test]
public void TestCinemaModRemovedOnEnteringGameplay ( )
{
2022-05-08 04:41:48 +08:00
advanceToSongSelect ( ) ;
openSkinEditor ( ) ;
2022-04-24 04:16:06 +08:00
AddStep ( "select cinema" , ( ) = > Game . SelectedMods . Value = new Mod [ ] { new OsuModCinema ( ) } ) ;
2023-12-04 20:34:36 +08:00
AddStep ( "import beatmap" , ( ) = > BeatmapImportHelper . LoadQuickOszIntoOsu ( Game ) . WaitSafely ( ) ) ;
AddUntilStep ( "wait for selected" , ( ) = > ! Game . Beatmap . IsDefault ) ;
2022-04-24 04:16:06 +08:00
switchToGameplayScene ( ) ;
AddAssert ( "no mod selected" , ( ) = > ! ( ( Player ) Game . ScreenStack . CurrentScreen ) . Mods . Value . Any ( ) ) ;
}
2022-05-08 04:47:19 +08:00
[Test]
public void TestModOverlayClosesOnOpeningSkinEditor ( )
{
advanceToSongSelect ( ) ;
AddStep ( "open mod overlay" , ( ) = > songSelect . ModSelectOverlay . Show ( ) ) ;
openSkinEditor ( ) ;
AddUntilStep ( "mod overlay closed" , ( ) = > songSelect . ModSelectOverlay . State . Value = = Visibility . Hidden ) ;
}
2023-03-04 20:17:54 +08:00
[Test]
public void TestChangeToNonSkinnableScreen ( )
{
advanceToSongSelect ( ) ;
openSkinEditor ( ) ;
AddAssert ( "blueprint container present" , ( ) = > skinEditor . ChildrenOfType < SkinBlueprintContainer > ( ) . Count ( ) , ( ) = > Is . EqualTo ( 1 ) ) ;
2023-03-04 20:22:43 +08:00
AddAssert ( "placeholder not present" , ( ) = > skinEditor . ChildrenOfType < NonSkinnableScreenPlaceholder > ( ) . Count ( ) , ( ) = > Is . Zero ) ;
2023-03-04 21:07:35 +08:00
AddAssert ( "editor sidebars not empty" , ( ) = > skinEditor . ChildrenOfType < EditorSidebar > ( ) . SelectMany ( sidebar = > sidebar . Children ) . Count ( ) , ( ) = > Is . GreaterThan ( 0 ) ) ;
2023-03-04 20:17:54 +08:00
AddStep ( "add skinnable component" , ( ) = >
{
skinEditor . ChildrenOfType < SkinComponentToolbox . ToolboxComponentButton > ( ) . First ( ) . TriggerClick ( ) ;
} ) ;
AddUntilStep ( "newly added component selected" , ( ) = > skinEditor . SelectedComponents , ( ) = > Has . Count . EqualTo ( 1 ) ) ;
AddStep ( "exit to main menu" , ( ) = > Game . ScreenStack . CurrentScreen . Exit ( ) ) ;
AddAssert ( "selection cleared" , ( ) = > skinEditor . SelectedComponents , ( ) = > Has . Count . Zero ) ;
AddAssert ( "blueprint container not present" , ( ) = > skinEditor . ChildrenOfType < SkinBlueprintContainer > ( ) . Count ( ) , ( ) = > Is . Zero ) ;
2023-03-04 20:22:43 +08:00
AddAssert ( "placeholder present" , ( ) = > skinEditor . ChildrenOfType < NonSkinnableScreenPlaceholder > ( ) . Count ( ) , ( ) = > Is . EqualTo ( 1 ) ) ;
2023-03-04 21:07:35 +08:00
AddAssert ( "editor sidebars empty" , ( ) = > skinEditor . ChildrenOfType < EditorSidebar > ( ) . SelectMany ( sidebar = > sidebar . Children ) . Count ( ) , ( ) = > Is . Zero ) ;
2023-03-04 20:17:54 +08:00
advanceToSongSelect ( ) ;
AddAssert ( "blueprint container present" , ( ) = > skinEditor . ChildrenOfType < SkinBlueprintContainer > ( ) . Count ( ) , ( ) = > Is . EqualTo ( 1 ) ) ;
2023-03-04 20:22:43 +08:00
AddAssert ( "placeholder not present" , ( ) = > skinEditor . ChildrenOfType < NonSkinnableScreenPlaceholder > ( ) . Count ( ) , ( ) = > Is . Zero ) ;
2023-03-04 21:07:35 +08:00
AddAssert ( "editor sidebars not empty" , ( ) = > skinEditor . ChildrenOfType < EditorSidebar > ( ) . SelectMany ( sidebar = > sidebar . Children ) . Count ( ) , ( ) = > Is . GreaterThan ( 0 ) ) ;
2023-03-04 20:17:54 +08:00
}
2023-12-04 20:50:52 +08:00
[Test]
public void TestOpenSkinEditorGameplaySceneOnBeatmapWithNoObjects ( )
{
AddStep ( "set dummy beatmap" , ( ) = > Game . Beatmap . SetDefault ( ) ) ;
advanceToSongSelect ( ) ;
AddStep ( "create empty beatmap" , ( ) = > Game . BeatmapManager . CreateNew ( new OsuRuleset ( ) . RulesetInfo , new GuestUser ( ) ) ) ;
AddUntilStep ( "wait for selected" , ( ) = > ! Game . Beatmap . IsDefault ) ;
openSkinEditor ( ) ;
switchToGameplayScene ( ) ;
}
2023-12-04 20:52:41 +08:00
[Test]
public void TestOpenSkinEditorGameplaySceneWhenDummyBeatmapActive ( )
{
AddStep ( "set dummy beatmap" , ( ) = > Game . Beatmap . SetDefault ( ) ) ;
openSkinEditor ( ) ;
}
2023-12-06 17:11:41 +08:00
[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
public void TestOpenSkinEditorGameplaySceneWhenDifferentRulesetActive ( int rulesetId )
2023-12-04 20:58:07 +08:00
{
BeatmapSetInfo beatmapSet = null ! ;
AddStep ( "import beatmap" , ( ) = > beatmapSet = BeatmapImportHelper . LoadQuickOszIntoOsu ( Game ) . GetResultSafely ( ) ) ;
2023-12-06 17:11:41 +08:00
AddStep ( $"select difficulty for ruleset w/ ID {rulesetId}" , ( ) = >
2023-12-04 20:58:07 +08:00
{
2023-12-06 17:11:41 +08:00
var beatmap = beatmapSet . Beatmaps . First ( b = > b . Ruleset . OnlineID = = rulesetId ) ;
2023-12-04 20:58:07 +08:00
Game . Beatmap . Value = Game . BeatmapManager . GetWorkingBeatmap ( beatmap ) ;
} ) ;
openSkinEditor ( ) ;
switchToGameplayScene ( ) ;
}
2024-02-19 16:34:52 +08:00
[Test]
public void TestRulesetInputDisabledWhenSkinEditorOpen ( )
{
advanceToSongSelect ( ) ;
openSkinEditor ( ) ;
AddStep ( "import beatmap" , ( ) = > BeatmapImportHelper . LoadQuickOszIntoOsu ( Game ) . WaitSafely ( ) ) ;
AddUntilStep ( "wait for selected" , ( ) = > ! Game . Beatmap . IsDefault ) ;
switchToGameplayScene ( ) ;
AddUntilStep ( "nested input disabled" , ( ) = > ( ( Player ) Game . ScreenStack . CurrentScreen ) . ChildrenOfType < PassThroughInputManager > ( ) . All ( manager = > ! manager . UseParentInput ) ) ;
toggleSkinEditor ( ) ;
AddUntilStep ( "nested input enabled" , ( ) = > ( ( Player ) Game . ScreenStack . CurrentScreen ) . ChildrenOfType < PassThroughInputManager > ( ) . Any ( manager = > manager . UseParentInput ) ) ;
toggleSkinEditor ( ) ;
AddUntilStep ( "nested input disabled" , ( ) = > ( ( Player ) Game . ScreenStack . CurrentScreen ) . ChildrenOfType < PassThroughInputManager > ( ) . All ( manager = > ! manager . UseParentInput ) ) ;
}
2024-04-29 20:06:02 +08:00
[Test]
public void TestSkinSavesOnChange ( )
{
advanceToSongSelect ( ) ;
openSkinEditor ( ) ;
Guid editedSkinId = Guid . Empty ;
AddStep ( "save skin id" , ( ) = > editedSkinId = Game . Dependencies . Get < SkinManager > ( ) . CurrentSkinInfo . Value . ID ) ;
AddStep ( "add skinnable component" , ( ) = >
{
skinEditor . ChildrenOfType < SkinComponentToolbox . ToolboxComponentButton > ( ) . First ( ) . TriggerClick ( ) ;
} ) ;
AddStep ( "change to triangles skin" , ( ) = > Game . Dependencies . Get < SkinManager > ( ) . SetSkinFromConfiguration ( SkinInfo . TRIANGLES_SKIN . ToString ( ) ) ) ;
2024-08-22 16:14:35 +08:00
AddUntilStep ( "components loaded" , ( ) = > Game . ChildrenOfType < SkinnableContainer > ( ) . All ( c = > c . ComponentsLoaded ) ) ;
2024-04-29 20:06:02 +08:00
// sort of implicitly relies on song select not being skinnable.
// TODO: revisit if the above ever changes
AddUntilStep ( "skin changed" , ( ) = > ! skinEditor . ChildrenOfType < SkinBlueprint > ( ) . Any ( ) ) ;
AddStep ( "change back to modified skin" , ( ) = > Game . Dependencies . Get < SkinManager > ( ) . SetSkinFromConfiguration ( editedSkinId . ToString ( ) ) ) ;
2024-08-22 16:14:35 +08:00
AddUntilStep ( "components loaded" , ( ) = > Game . ChildrenOfType < SkinnableContainer > ( ) . All ( c = > c . ComponentsLoaded ) ) ;
2024-04-29 20:06:02 +08:00
AddUntilStep ( "changes saved" , ( ) = > skinEditor . ChildrenOfType < SkinBlueprint > ( ) . Any ( ) ) ;
}
2022-06-20 12:29:43 +08:00
private void advanceToSongSelect ( )
{
PushAndConfirm ( ( ) = > songSelect = new TestPlaySongSelect ( ) ) ;
AddUntilStep ( "wait for song select" , ( ) = > songSelect . BeatmapSetsLoaded ) ;
}
private void openSkinEditor ( )
{
toggleSkinEditor ( ) ;
AddUntilStep ( "skin editor loaded" , ( ) = > skinEditor ! = null ) ;
}
private void toggleSkinEditor ( )
{
AddStep ( "toggle skin editor" , ( ) = >
{
InputManager . PressKey ( Key . ControlLeft ) ;
InputManager . PressKey ( Key . ShiftLeft ) ;
InputManager . Key ( Key . S ) ;
InputManager . ReleaseKey ( Key . ControlLeft ) ;
InputManager . ReleaseKey ( Key . ShiftLeft ) ;
} ) ;
}
2022-04-24 04:15:31 +08:00
private void switchToGameplayScene ( )
{
2022-06-18 23:32:02 +08:00
AddStep ( "Click gameplay scene button" , ( ) = >
{
2023-01-17 10:51:17 +08:00
InputManager . MoveMouseTo ( skinEditor . ChildrenOfType < SkinEditorSceneLibrary . SceneButton > ( ) . First ( b = > b . Text . ToString ( ) = = "Gameplay" ) ) ;
2022-06-18 23:32:02 +08:00
InputManager . Click ( MouseButton . Left ) ;
} ) ;
2022-04-24 04:15:31 +08:00
AddUntilStep ( "wait for player" , ( ) = >
{
DismissAnyNotifications ( ) ;
return Game . ScreenStack . CurrentScreen is Player ;
} ) ;
}
}
}