2019-01-24 16:43:03 +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.
2018-11-28 15:47:10 +08:00
2022-06-17 15:37:17 +08:00
#nullable disable
2018-11-28 16:19:58 +08:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Linq.Expressions ;
2020-09-09 16:37:11 +08:00
using System.Threading ;
2021-06-27 12:06:20 +08:00
using System.Threading.Tasks ;
2020-08-28 18:16:46 +08:00
using JetBrains.Annotations ;
2020-08-28 20:34:34 +08:00
using osu.Framework.Bindables ;
2018-11-28 15:47:10 +08:00
using osu.Framework.Platform ;
using osu.Game.Beatmaps ;
2020-08-28 20:34:34 +08:00
using osu.Game.Configuration ;
2018-11-28 15:47:10 +08:00
using osu.Game.Database ;
using osu.Game.IO.Archives ;
2021-09-30 17:21:16 +08:00
using osu.Game.Overlays.Notifications ;
2018-11-28 15:47:10 +08:00
using osu.Game.Rulesets ;
2020-08-28 18:16:46 +08:00
using osu.Game.Rulesets.Scoring ;
2022-07-08 11:16:06 +08:00
using osu.Game.Online.API ;
2018-11-28 15:47:10 +08:00
namespace osu.Game.Scoring
{
2022-06-16 17:53:13 +08:00
public class ScoreManager : ModelManager < ScoreInfo > , IModelImporter < ScoreInfo >
2018-11-28 15:47:10 +08:00
{
2020-08-28 20:34:34 +08:00
private readonly OsuConfigManager configManager ;
2022-06-16 17:11:50 +08:00
private readonly ScoreImporter scoreImporter ;
2022-12-11 17:30:24 +08:00
private readonly LegacyScoreExporter scoreExporter ;
2020-08-28 20:34:34 +08:00
2022-09-08 21:06:44 +08:00
public ScoreManager ( RulesetStore rulesets , Func < BeatmapManager > beatmaps , Storage storage , RealmAccess realm , IAPIProvider api ,
2022-09-09 12:57:01 +08:00
OsuConfigManager configManager = null )
2022-06-16 17:53:13 +08:00
: base ( storage , realm )
2018-11-28 15:47:10 +08:00
{
2020-08-28 20:34:34 +08:00
this . configManager = configManager ;
2018-11-28 16:19:58 +08:00
2022-09-09 12:57:01 +08:00
scoreImporter = new ScoreImporter ( rulesets , beatmaps , storage , realm , api )
2022-06-16 18:48:18 +08:00
{
PostNotification = obj = > PostNotification ? . Invoke ( obj )
} ;
2022-12-11 17:30:24 +08:00
scoreExporter = new LegacyScoreExporter ( storage , realm )
{
PostNotification = obj = > PostNotification ? . Invoke ( obj )
} ;
2018-11-28 15:47:10 +08:00
}
2018-11-28 16:19:58 +08:00
2022-06-16 17:11:50 +08:00
public Score GetScore ( ScoreInfo score ) = > scoreImporter . GetScore ( score ) ;
2021-06-27 12:06:20 +08:00
2021-12-13 18:01:20 +08:00
/// <summary>
/// Perform a lookup query on available <see cref="ScoreInfo"/>s.
/// </summary>
/// <param name="query">The query.</param>
/// <returns>The first result for the provided query, or null if no results were found.</returns>
2022-01-07 23:40:14 +08:00
public ScoreInfo Query ( Expression < Func < ScoreInfo , bool > > query )
2021-12-13 18:01:20 +08:00
{
2022-06-16 17:56:53 +08:00
return Realm . Run ( r = > r . All < ScoreInfo > ( ) . FirstOrDefault ( query ) ? . Detach ( ) ) ;
2021-12-13 18:01:20 +08:00
}
2020-08-28 18:16:46 +08:00
2021-09-01 14:41:52 +08:00
/// <summary>
/// Orders an array of <see cref="ScoreInfo"/>s by total score.
/// </summary>
/// <param name="scores">The array of <see cref="ScoreInfo"/>s to reorder.</param>
/// <returns>The given <paramref name="scores"/> ordered by decreasing total score.</returns>
2022-08-22 20:31:30 +08:00
public IEnumerable < ScoreInfo > OrderByTotalScore ( IEnumerable < ScoreInfo > scores )
2022-09-14 13:19:53 +08:00
= > scores . OrderByDescending ( s = > GetTotalScore ( s ) )
. ThenBy ( s = > s . OnlineID )
// Local scores may not have an online ID. Fall back to date in these cases.
. ThenBy ( s = > s . Date ) ;
2021-08-31 20:36:31 +08:00
2020-09-09 16:04:02 +08:00
/// <summary>
/// Retrieves a bindable that represents the total score of a <see cref="ScoreInfo"/>.
/// </summary>
/// <remarks>
/// Responds to changes in the currently-selected <see cref="ScoringMode"/>.
/// </remarks>
/// <param name="score">The <see cref="ScoreInfo"/> to retrieve the bindable for.</param>
/// <returns>The bindable containing the total score.</returns>
2022-03-30 12:15:41 +08:00
public Bindable < long > GetBindableTotalScore ( [ NotNull ] ScoreInfo score ) = > new TotalScoreBindable ( score , this , configManager ) ;
2020-08-28 18:16:46 +08:00
2020-09-09 16:04:02 +08:00
/// <summary>
/// Retrieves a bindable that represents the formatted total score string of a <see cref="ScoreInfo"/>.
/// </summary>
/// <remarks>
/// Responds to changes in the currently-selected <see cref="ScoringMode"/>.
/// </remarks>
/// <param name="score">The <see cref="ScoreInfo"/> to retrieve the bindable for.</param>
/// <returns>The bindable containing the formatted total score string.</returns>
2021-09-01 19:56:23 +08:00
public Bindable < string > GetBindableTotalScoreString ( [ NotNull ] ScoreInfo score ) = > new TotalScoreStringBindable ( GetBindableTotalScore ( score ) ) ;
2020-09-09 16:04:02 +08:00
2021-09-01 14:41:52 +08:00
/// <summary>
/// Retrieves the total score of a <see cref="ScoreInfo"/> in the given <see cref="ScoringMode"/>.
/// </summary>
/// <param name="score">The <see cref="ScoreInfo"/> to calculate the total score of.</param>
/// <param name="mode">The <see cref="ScoringMode"/> to return the total score as.</param>
/// <returns>The total score.</returns>
2022-08-22 20:31:30 +08:00
public long GetTotalScore ( [ NotNull ] ScoreInfo score , ScoringMode mode = ScoringMode . Standardised )
2021-08-30 18:33:09 +08:00
{
2022-01-17 12:11:43 +08:00
// TODO: This is required for playlist aggregate scores. They should likely not be getting here in the first place.
2022-03-08 18:07:39 +08:00
if ( string . IsNullOrEmpty ( score . BeatmapInfo . MD5Hash ) )
2022-01-17 12:11:43 +08:00
return score . TotalScore ;
2021-08-30 18:33:09 +08:00
2022-03-20 10:28:07 +08:00
var ruleset = score . Ruleset . CreateInstance ( ) ;
var scoreProcessor = ruleset . CreateScoreProcessor ( ) ;
scoreProcessor . Mods . Value = score . Mods ;
2022-11-07 06:59:27 +08:00
return scoreProcessor . ComputeScore ( mode , score ) ;
2022-03-20 10:28:07 +08:00
}
/// <summary>
2022-03-20 21:18:53 +08:00
/// Retrieves the maximum achievable combo for the provided score.
2022-03-20 10:28:07 +08:00
/// </summary>
/// <param name="score">The <see cref="ScoreInfo"/> to compute the maximum achievable combo for.</param>
2022-08-29 14:36:10 +08:00
/// <returns>The maximum achievable combo.</returns>
public int GetMaximumAchievableCombo ( [ NotNull ] ScoreInfo score ) = > score . MaximumStatistics . Where ( kvp = > kvp . Key . AffectsCombo ( ) ) . Sum ( kvp = > kvp . Value ) ;
2021-08-30 18:33:09 +08:00
2020-09-09 16:04:02 +08:00
/// <summary>
/// Provides the total score of a <see cref="ScoreInfo"/>. Responds to changes in the currently-selected <see cref="ScoringMode"/>.
/// </summary>
2020-08-28 21:51:19 +08:00
private class TotalScoreBindable : Bindable < long >
2020-08-28 20:34:34 +08:00
{
2022-03-30 12:15:41 +08:00
private readonly Bindable < ScoringMode > scoringMode = new Bindable < ScoringMode > ( ) ;
2021-09-01 19:56:23 +08:00
2020-09-09 16:04:02 +08:00
/// <summary>
/// Creates a new <see cref="TotalScoreBindable"/>.
/// </summary>
/// <param name="score">The <see cref="ScoreInfo"/> to provide the total score of.</param>
2021-08-30 18:33:09 +08:00
/// <param name="scoreManager">The <see cref="ScoreManager"/>.</param>
2022-03-30 12:15:41 +08:00
/// <param name="configManager">The config.</param>
public TotalScoreBindable ( ScoreInfo score , ScoreManager scoreManager , OsuConfigManager configManager )
2020-08-28 18:16:46 +08:00
{
2022-03-30 12:15:41 +08:00
configManager ? . BindWith ( OsuSetting . ScoreDisplayMode , scoringMode ) ;
2022-08-22 20:31:30 +08:00
scoringMode . BindValueChanged ( mode = > Value = scoreManager . GetTotalScore ( score , mode . NewValue ) , true ) ;
2020-08-28 21:51:19 +08:00
}
}
2020-09-09 16:04:02 +08:00
/// <summary>
/// Provides the total score of a <see cref="ScoreInfo"/> as a formatted string. Responds to changes in the currently-selected <see cref="ScoringMode"/>.
/// </summary>
2020-08-28 21:51:19 +08:00
private class TotalScoreStringBindable : Bindable < string >
{
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable (need to hold a reference)
private readonly IBindable < long > totalScore ;
public TotalScoreStringBindable ( IBindable < long > totalScore )
{
this . totalScore = totalScore ;
this . totalScore . BindValueChanged ( v = > Value = v . NewValue . ToString ( "N0" ) , true ) ;
2020-08-28 20:34:34 +08:00
}
2020-08-28 18:16:46 +08:00
}
2021-09-30 17:21:16 +08:00
2021-12-13 18:01:20 +08:00
public void Delete ( [ CanBeNull ] Expression < Func < ScoreInfo , bool > > filter = null , bool silent = false )
{
2022-06-16 17:56:53 +08:00
Realm . Run ( r = >
2021-12-13 18:01:20 +08:00
{
2022-01-25 12:04:05 +08:00
var items = r . All < ScoreInfo > ( )
. Where ( s = > ! s . DeletePending ) ;
2021-12-13 18:01:20 +08:00
if ( filter ! = null )
items = items . Where ( filter ) ;
2022-06-16 17:53:13 +08:00
Delete ( items . ToList ( ) , silent ) ;
2022-01-21 16:08:20 +08:00
} ) ;
2021-12-13 18:01:20 +08:00
}
2022-01-28 14:08:39 +08:00
public void Delete ( BeatmapInfo beatmap , bool silent = false )
{
2022-06-16 17:56:53 +08:00
Realm . Run ( r = >
2022-01-28 14:08:39 +08:00
{
var beatmapScores = r . Find < BeatmapInfo > ( beatmap . ID ) . Scores . ToList ( ) ;
2022-06-16 17:53:13 +08:00
Delete ( beatmapScores , silent ) ;
2022-01-28 14:08:39 +08:00
} ) ;
}
2022-06-16 17:11:50 +08:00
public Task Import ( params string [ ] paths ) = > scoreImporter . Import ( paths ) ;
2021-09-30 17:21:16 +08:00
2022-06-16 17:11:50 +08:00
public Task Import ( params ImportTask [ ] tasks ) = > scoreImporter . Import ( tasks ) ;
2021-09-30 17:21:16 +08:00
2022-06-16 18:05:25 +08:00
public override bool IsAvailableLocally ( ScoreInfo model ) = > Realm . Run ( realm = > realm . All < ScoreInfo > ( ) . Any ( s = > s . OnlineID = = model . OnlineID ) ) ;
2022-06-16 17:11:50 +08:00
public IEnumerable < string > HandledExtensions = > scoreImporter . HandledExtensions ;
2021-09-30 17:21:16 +08:00
2022-06-16 17:11:50 +08:00
public Task < IEnumerable < Live < ScoreInfo > > > Import ( ProgressNotification notification , params ImportTask [ ] tasks ) = > scoreImporter . Import ( notification , tasks ) ;
2021-09-30 17:21:16 +08:00
2022-12-15 19:59:11 +08:00
public Task Export ( ScoreInfo score ) = > scoreExporter . ExportAsync ( score ) ;
2022-12-11 17:30:24 +08:00
2022-07-26 14:46:29 +08:00
public Task < Live < ScoreInfo > > ImportAsUpdate ( ProgressNotification notification , ImportTask task , ScoreInfo original ) = > scoreImporter . ImportAsUpdate ( notification , task , original ) ;
2022-06-16 18:05:25 +08:00
public Live < ScoreInfo > Import ( ScoreInfo item , ArchiveReader archive = null , bool batchImport = false , CancellationToken cancellationToken = default ) = >
2022-06-20 14:18:07 +08:00
scoreImporter . ImportModel ( item , archive , batchImport , cancellationToken ) ;
2021-09-30 17:21:16 +08:00
2022-09-08 21:06:44 +08:00
/// <summary>
/// Populates the <see cref="ScoreInfo.MaximumStatistics"/> for a given <see cref="ScoreInfo"/>.
/// </summary>
/// <param name="score">The score to populate the statistics of.</param>
2022-09-09 12:57:01 +08:00
public void PopulateMaximumStatistics ( ScoreInfo score ) = > scoreImporter . PopulateMaximumStatistics ( score ) ;
2022-09-08 21:06:44 +08:00
2021-09-30 17:21:16 +08:00
#region Implementation of IPresentImports < ScoreInfo >
2022-06-20 17:21:37 +08:00
public Action < IEnumerable < Live < ScoreInfo > > > PresentImport
2021-09-30 17:21:16 +08:00
{
2022-06-20 17:21:37 +08:00
set = > scoreImporter . PresentImport = value ;
2021-09-30 17:21:16 +08:00
}
#endregion
2018-11-28 15:47:10 +08:00
}
}