2020-04-16 01:19:17 +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
2020-04-16 01:19:17 +08:00
using System ;
using System.Collections.Generic ;
2022-07-01 16:10:04 +08:00
using System.Diagnostics ;
2020-04-16 01:19:17 +08:00
using System.Linq ;
using NUnit.Framework ;
2024-11-07 16:25:42 +08:00
using osu.Framework.Platform ;
2020-04-16 01:19:17 +08:00
using osu.Framework.Testing ;
using osu.Game.Beatmaps ;
2024-11-07 16:25:42 +08:00
using osu.Game.Database ;
2024-12-11 10:18:34 +08:00
using osu.Game.Graphics.Sprites ;
2024-11-07 16:25:42 +08:00
using osu.Game.Online.API ;
2024-11-18 08:13:23 +08:00
using osu.Game.Online.API.Requests ;
using osu.Game.Online.API.Requests.Responses ;
2024-12-11 10:18:34 +08:00
using osu.Game.Overlays ;
using osu.Game.Overlays.BeatmapListing ;
2020-04-16 01:19:17 +08:00
using osu.Game.Rulesets ;
2020-05-01 22:59:45 +08:00
using osu.Game.Rulesets.Catch ;
using osu.Game.Rulesets.Mania ;
using osu.Game.Rulesets.Osu ;
using osu.Game.Rulesets.Taiko ;
2021-11-24 17:29:33 +08:00
using osu.Game.Tests.Resources ;
2020-04-16 01:19:17 +08:00
using osu.Game.Users ;
2024-12-11 10:18:34 +08:00
using osu.Game.Utils ;
using osuTK.Input ;
2020-04-16 01:19:17 +08:00
namespace osu.Game.Tests.Visual.SongSelect
{
public partial class TestSceneBeatmapRecommendations : OsuGameTestScene
{
[SetUpSteps]
2020-05-01 22:59:45 +08:00
public override void SetUpSteps ( )
2020-04-16 01:19:17 +08:00
{
2022-06-08 22:44:57 +08:00
AddStep ( "populate ruleset statistics" , ( ) = >
2020-04-25 15:21:01 +08:00
{
2024-11-18 09:35:59 +08:00
( ( DummyAPIAccess ) API ) . HandleRequest = r = >
2020-04-25 15:21:01 +08:00
{
2024-11-18 08:13:23 +08:00
switch ( r )
2020-04-25 15:21:01 +08:00
{
2024-11-18 08:13:23 +08:00
case GetUserRequest userRequest :
userRequest . TriggerSuccess ( new APIUser
{
Id = 99 ,
Statistics = new UserStatistics
{
PP = getNecessaryPP ( userRequest . Ruleset ? . OnlineID ? ? 0 )
}
} ) ;
return true ;
2022-06-08 22:44:57 +08:00
2024-11-18 08:13:23 +08:00
default :
return false ;
}
} ;
2022-06-08 22:44:57 +08:00
} ) ;
2020-04-25 15:21:01 +08:00
decimal getNecessaryPP ( int? rulesetID )
{
switch ( rulesetID )
{
case 0 :
2020-05-02 00:46:49 +08:00
return 336 ; // recommended star rating of 2
2020-04-25 15:21:01 +08:00
case 1 :
2024-12-13 16:41:55 +08:00
return 973 ; // SR 3
2020-04-25 15:21:01 +08:00
case 2 :
2020-05-02 00:46:49 +08:00
return 1905 ; // SR 4
2020-04-25 15:21:01 +08:00
case 3 :
2020-05-02 00:46:49 +08:00
return 3329 ; // SR 5
2020-04-25 15:21:01 +08:00
default :
return 0 ;
}
}
2024-08-13 16:28:16 +08:00
base . SetUpSteps ( ) ;
2020-04-16 01:19:17 +08:00
}
[Test]
2025-01-07 16:57:09 +08:00
[FlakyTest]
2020-04-16 01:19:17 +08:00
public void TestPresentedBeatmapIsRecommended ( )
{
2020-05-01 22:59:45 +08:00
List < BeatmapSetInfo > beatmapSets = null ;
const int import_count = 5 ;
2020-04-18 00:55:51 +08:00
2020-05-01 22:59:45 +08:00
AddStep ( "import 5 maps" , ( ) = >
2020-04-18 00:55:51 +08:00
{
2020-05-01 22:59:45 +08:00
beatmapSets = new List < BeatmapSetInfo > ( ) ;
2020-04-16 01:19:17 +08:00
2020-05-01 22:59:45 +08:00
for ( int i = 0 ; i < import_count ; + + i )
{
2021-11-24 17:29:33 +08:00
beatmapSets . Add ( importBeatmapSet ( Enumerable . Repeat ( new OsuRuleset ( ) . RulesetInfo , 5 ) ) ) ;
2020-05-01 22:59:45 +08:00
}
} ) ;
AddAssert ( "all sets imported" , ( ) = > ensureAllBeatmapSetsImported ( beatmapSets ) ) ;
presentAndConfirm ( ( ) = > beatmapSets [ 3 ] , 2 ) ;
2020-04-16 01:19:17 +08:00
}
2020-12-04 02:07:42 +08:00
[Test]
2025-01-07 16:57:09 +08:00
[FlakyTest]
2020-12-04 02:07:42 +08:00
public void TestCurrentRulesetIsRecommended ( )
{
BeatmapSetInfo catchSet = null , mixedSet = null ;
2021-11-24 17:29:33 +08:00
AddStep ( "create catch beatmapset" , ( ) = > catchSet = importBeatmapSet ( new [ ] { new CatchRuleset ( ) . RulesetInfo } ) ) ;
AddStep ( "create mixed beatmapset" , ( ) = > mixedSet = importBeatmapSet ( new [ ] { new TaikoRuleset ( ) . RulesetInfo , new CatchRuleset ( ) . RulesetInfo , new ManiaRuleset ( ) . RulesetInfo } ) ) ;
2020-12-04 02:07:42 +08:00
AddAssert ( "all sets imported" , ( ) = > ensureAllBeatmapSetsImported ( new [ ] { catchSet , mixedSet } ) ) ;
// Switch to catch
presentAndConfirm ( ( ) = > catchSet , 1 ) ;
2022-01-10 15:34:16 +08:00
AddAssert ( "game-wide ruleset changed" , ( ) = > Game . Ruleset . Value . Equals ( catchSet . Beatmaps . First ( ) . Ruleset ) ) ;
2020-12-04 02:07:42 +08:00
// Present mixed difficulty set, expect current ruleset to be selected
presentAndConfirm ( ( ) = > mixedSet , 2 ) ;
}
2020-04-18 00:55:51 +08:00
[Test]
public void TestBestRulesetIsRecommended ( )
2020-04-16 01:19:17 +08:00
{
2020-05-01 22:59:45 +08:00
BeatmapSetInfo osuSet = null , mixedSet = null ;
2021-11-24 17:29:33 +08:00
AddStep ( "create osu! beatmapset" , ( ) = > osuSet = importBeatmapSet ( new [ ] { new OsuRuleset ( ) . RulesetInfo } ) ) ;
AddStep ( "create mixed beatmapset" , ( ) = > mixedSet = importBeatmapSet ( new [ ] { new TaikoRuleset ( ) . RulesetInfo , new CatchRuleset ( ) . RulesetInfo , new ManiaRuleset ( ) . RulesetInfo } ) ) ;
2020-04-16 01:19:17 +08:00
2020-05-01 22:59:45 +08:00
AddAssert ( "all sets imported" , ( ) = > ensureAllBeatmapSetsImported ( new [ ] { osuSet , mixedSet } ) ) ;
2020-04-18 00:55:51 +08:00
// Make sure we are on standard ruleset
2020-05-01 22:59:45 +08:00
presentAndConfirm ( ( ) = > osuSet , 1 ) ;
2020-04-18 00:55:51 +08:00
// Present mixed difficulty set, expect ruleset with highest star difficulty
2020-05-01 22:59:45 +08:00
presentAndConfirm ( ( ) = > mixedSet , 3 ) ;
2020-04-18 00:55:51 +08:00
}
[Test]
2025-01-07 16:57:09 +08:00
[FlakyTest]
2020-04-18 00:55:51 +08:00
public void TestSecondBestRulesetIsRecommended ( )
{
2020-05-01 22:59:45 +08:00
BeatmapSetInfo osuSet = null , mixedSet = null ;
2020-04-18 00:55:51 +08:00
2021-11-24 17:29:33 +08:00
AddStep ( "create osu! beatmapset" , ( ) = > osuSet = importBeatmapSet ( new [ ] { new OsuRuleset ( ) . RulesetInfo } ) ) ;
AddStep ( "create mixed beatmapset" , ( ) = > mixedSet = importBeatmapSet ( new [ ] { new TaikoRuleset ( ) . RulesetInfo , new CatchRuleset ( ) . RulesetInfo , new TaikoRuleset ( ) . RulesetInfo } ) ) ;
2020-05-01 22:59:45 +08:00
AddAssert ( "all sets imported" , ( ) = > ensureAllBeatmapSetsImported ( new [ ] { osuSet , mixedSet } ) ) ;
2020-04-18 00:55:51 +08:00
// Make sure we are on standard ruleset
2020-05-01 22:59:45 +08:00
presentAndConfirm ( ( ) = > osuSet , 1 ) ;
2020-04-16 01:19:17 +08:00
2020-04-25 15:37:18 +08:00
// Present mixed difficulty set, expect ruleset with second highest star difficulty
2020-05-01 22:59:45 +08:00
presentAndConfirm ( ( ) = > mixedSet , 2 ) ;
2020-04-16 01:19:17 +08:00
}
2020-05-02 00:08:56 +08:00
[Test]
2025-01-07 16:57:09 +08:00
[FlakyTest]
2020-05-02 00:08:56 +08:00
public void TestCorrectStarRatingIsUsed ( )
{
BeatmapSetInfo osuSet = null , maniaSet = null ;
2021-11-24 17:29:33 +08:00
AddStep ( "create osu! beatmapset" , ( ) = > osuSet = importBeatmapSet ( new [ ] { new OsuRuleset ( ) . RulesetInfo } ) ) ;
AddStep ( "create mania beatmapset" , ( ) = > maniaSet = importBeatmapSet ( Enumerable . Repeat ( new ManiaRuleset ( ) . RulesetInfo , 10 ) ) ) ;
2020-05-02 00:08:56 +08:00
AddAssert ( "all sets imported" , ( ) = > ensureAllBeatmapSetsImported ( new [ ] { osuSet , maniaSet } ) ) ;
// Make sure we are on standard ruleset
presentAndConfirm ( ( ) = > osuSet , 1 ) ;
// Present mania set, expect the difficulty that matches recommended mania star rating
presentAndConfirm ( ( ) = > maniaSet , 5 ) ;
}
2024-12-11 10:18:34 +08:00
[Test]
2025-01-07 16:57:09 +08:00
[FlakyTest]
2024-12-11 10:18:34 +08:00
public void TestBeatmapListingFilter ( )
{
AddStep ( "set playmode to taiko" , ( ) = > ( ( DummyAPIAccess ) API ) . LocalUser . Value . PlayMode = "taiko" ) ;
AddStep ( "open beatmap listing" , ( ) = >
{
InputManager . PressKey ( Key . ControlLeft ) ;
InputManager . PressKey ( Key . B ) ;
InputManager . ReleaseKey ( Key . B ) ;
InputManager . ReleaseKey ( Key . ControlLeft ) ;
} ) ;
AddUntilStep ( "wait for load" , ( ) = > Game . ChildrenOfType < BeatmapListingOverlay > ( ) . SingleOrDefault ( ) ? . IsLoaded , ( ) = > Is . True ) ;
checkRecommendedDifficulty ( 3 ) ;
AddStep ( "change mode filter to osu!" , ( ) = > Game . ChildrenOfType < BeatmapSearchRulesetFilterRow > ( ) . Single ( ) . ChildrenOfType < FilterTabItem < RulesetInfo > > ( ) . ElementAt ( 1 ) . TriggerClick ( ) ) ;
checkRecommendedDifficulty ( 2 ) ;
AddStep ( "change mode filter to osu!taiko" , ( ) = > Game . ChildrenOfType < BeatmapSearchRulesetFilterRow > ( ) . Single ( ) . ChildrenOfType < FilterTabItem < RulesetInfo > > ( ) . ElementAt ( 2 ) . TriggerClick ( ) ) ;
checkRecommendedDifficulty ( 3 ) ;
AddStep ( "change mode filter to osu!catch" , ( ) = > Game . ChildrenOfType < BeatmapSearchRulesetFilterRow > ( ) . Single ( ) . ChildrenOfType < FilterTabItem < RulesetInfo > > ( ) . ElementAt ( 3 ) . TriggerClick ( ) ) ;
checkRecommendedDifficulty ( 4 ) ;
AddStep ( "change mode filter to osu!mania" , ( ) = > Game . ChildrenOfType < BeatmapSearchRulesetFilterRow > ( ) . Single ( ) . ChildrenOfType < FilterTabItem < RulesetInfo > > ( ) . ElementAt ( 4 ) . TriggerClick ( ) ) ;
checkRecommendedDifficulty ( 5 ) ;
void checkRecommendedDifficulty ( double starRating )
= > AddAssert ( $"recommended difficulty is {starRating}" ,
( ) = > Game . ChildrenOfType < BeatmapSearchGeneralFilterRow > ( ) . Single ( ) . ChildrenOfType < OsuSpriteText > ( ) . ElementAt ( 1 ) . Text . ToString ( ) ,
( ) = > Is . EqualTo ( $"Recommended difficulty ({starRating.FormatStarRating()})" ) ) ;
}
2021-11-24 17:29:33 +08:00
private BeatmapSetInfo importBeatmapSet ( IEnumerable < RulesetInfo > difficultyRulesets )
2020-04-16 01:19:17 +08:00
{
2021-11-24 17:29:33 +08:00
var rulesets = difficultyRulesets . ToArray ( ) ;
2020-04-16 01:19:17 +08:00
2021-11-24 17:29:33 +08:00
var beatmapSet = TestResources . CreateTestBeatmapSetInfo ( rulesets . Length , rulesets ) ;
2020-04-16 01:19:17 +08:00
2022-07-01 16:10:04 +08:00
var importedBeatmapSet = Game . BeatmapManager . Import ( beatmapSet ) ;
Debug . Assert ( importedBeatmapSet ! = null ) ;
importedBeatmapSet . PerformWrite ( s = >
2021-11-24 12:25:26 +08:00
{
2022-07-01 16:10:04 +08:00
for ( int i = 0 ; i < rulesets . Length ; i + + )
{
var beatmap = s . Beatmaps [ i ] ;
2021-11-24 17:29:33 +08:00
2022-07-01 16:10:04 +08:00
beatmap . StarRating = i + 1 ;
beatmap . DifficultyName = $"SR{i + 1}" ;
}
} ) ;
2021-11-24 12:25:26 +08:00
2022-07-01 16:10:04 +08:00
return importedBeatmapSet . Value ;
2020-04-16 01:19:17 +08:00
}
2020-05-01 22:59:45 +08:00
private bool ensureAllBeatmapSetsImported ( IEnumerable < BeatmapSetInfo > beatmapSets ) = > beatmapSets . All ( set = > set ! = null ) ;
2020-04-25 15:36:19 +08:00
private void presentAndConfirm ( Func < BeatmapSetInfo > getImport , int expectedDiff )
2020-04-16 01:19:17 +08:00
{
AddStep ( "present beatmap" , ( ) = > Game . PresentBeatmap ( getImport ( ) ) ) ;
2024-11-06 19:39:10 +08:00
AddUntilStep ( "wait for song select" , ( ) = > Game . ScreenStack . CurrentScreen is Screens . Select . SongSelect select & & select . BeatmapSetsLoaded ) ;
2025-01-07 16:57:09 +08:00
AddUntilStep ( "recommended beatmap displayed" , ( ) = > Game . Beatmap . Value . BeatmapInfo . OnlineID , ( ) = > Is . EqualTo ( getImport ( ) . Beatmaps [ expectedDiff - 1 ] . OnlineID ) ) ;
2020-04-16 01:19:17 +08:00
}
2024-11-07 16:25:42 +08:00
protected override TestOsuGame CreateTestGame ( ) = > new NoBeatmapUpdateGame ( LocalStorage , API ) ;
2024-11-07 16:28:31 +08:00
private partial class NoBeatmapUpdateGame : TestOsuGame
2024-11-07 16:25:42 +08:00
{
public NoBeatmapUpdateGame ( Storage storage , IAPIProvider api , string [ ] args = null )
: base ( storage , api , args )
{
}
protected override IBeatmapUpdater CreateBeatmapUpdater ( ) = > new TestBeatmapUpdater ( ) ;
private class TestBeatmapUpdater : IBeatmapUpdater
{
public void Queue ( Live < BeatmapSetInfo > beatmapSet , MetadataLookupScope lookupScope = MetadataLookupScope . LocalCacheFirst )
{
}
public void Process ( BeatmapSetInfo beatmapSet , MetadataLookupScope lookupScope = MetadataLookupScope . LocalCacheFirst )
{
}
public void ProcessObjectCounts ( BeatmapInfo beatmapInfo , MetadataLookupScope lookupScope = MetadataLookupScope . LocalCacheFirst )
{
}
public void Dispose ( )
{
}
}
}
2020-04-16 01:19:17 +08:00
}
}