1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 12:47:25 +08:00
osu-lazer/osu.Game/Scoring/ScoreManager.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

199 lines
8.9 KiB
C#
Raw Normal View History

// 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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
2020-09-09 16:37:11 +08:00
using System.Threading;
using System.Threading.Tasks;
2020-08-28 18:16:46 +08:00
using JetBrains.Annotations;
using osu.Framework.Bindables;
using osu.Framework.Platform;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.IO.Archives;
using osu.Game.Overlays.Notifications;
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;
namespace osu.Game.Scoring
{
public class ScoreManager : ModelManager<ScoreInfo>, IModelImporter<ScoreInfo>
{
private readonly OsuConfigManager configManager;
private readonly ScoreImporter scoreImporter;
2022-08-22 20:31:30 +08:00
public ScoreManager(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, RealmAccess realm, IAPIProvider api, OsuConfigManager configManager = null)
: base(storage, realm)
{
this.configManager = configManager;
2022-07-08 11:16:06 +08:00
scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm, api)
2022-06-16 18:48:18 +08:00
{
PostNotification = obj => PostNotification?.Invoke(obj)
};
}
public Score GetScore(ScoreInfo score) => scoreImporter.GetScore(score);
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>
public ScoreInfo Query(Expression<Func<ScoreInfo, bool>> query)
2021-12-13 18:01:20 +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)
2021-08-31 20:36:31 +08:00
{
2022-08-22 20:31:30 +08:00
return scores.Select((score, index) => (score, totalScore: GetTotalScore(score)))
.OrderByDescending(g => g.totalScore)
.ThenBy(g => g.score.OnlineID)
2022-08-22 20:31:30 +08:00
.Select(g => g.score);
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>
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
{
// TODO: This is required for playlist aggregate scores. They should likely not be getting here in the first place.
if (string.IsNullOrEmpty(score.BeatmapInfo.MD5Hash))
return score.TotalScore;
2021-08-30 18:33:09 +08:00
var ruleset = score.Ruleset.CreateInstance();
var scoreProcessor = ruleset.CreateScoreProcessor();
scoreProcessor.Mods.Value = score.Mods;
return (long)Math.Round(scoreProcessor.ComputeScore(mode, score));
}
/// <summary>
/// Retrieves the maximum achievable combo for the provided score.
/// </summary>
/// <param name="score">The <see cref="ScoreInfo"/> to compute the maximum achievable combo for.</param>
2022-03-20 21:24:29 +08:00
/// <returns>The maximum achievable combo. A <see langword="null"/> return value indicates the difficulty cache has failed to retrieve the combo.</returns>
2022-08-22 20:31:30 +08:00
public int GetMaximumAchievableCombo([NotNull] ScoreInfo score)
{
return Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Where(r => r.AffectsCombo()).Select(r => score.Statistics.GetValueOrDefault(r)).Sum();
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>
{
2022-03-30 12:15:41 +08:00
private readonly Bindable<ScoringMode> scoringMode = new Bindable<ScoringMode>();
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 18:16:46 +08:00
}
2021-12-13 18:01:20 +08:00
public void Delete([CanBeNull] Expression<Func<ScoreInfo, bool>> filter = null, bool silent = false)
{
Realm.Run(r =>
2021-12-13 18:01:20 +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);
Delete(items.ToList(), silent);
});
2021-12-13 18:01:20 +08:00
}
public void Delete(BeatmapInfo beatmap, bool silent = false)
{
Realm.Run(r =>
{
var beatmapScores = r.Find<BeatmapInfo>(beatmap.ID).Scores.ToList();
Delete(beatmapScores, silent);
});
}
public Task Import(params string[] paths) => scoreImporter.Import(paths);
public Task Import(params ImportTask[] tasks) => scoreImporter.Import(tasks);
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));
public IEnumerable<string> HandledExtensions => scoreImporter.HandledExtensions;
public Task<IEnumerable<Live<ScoreInfo>>> Import(ProgressNotification notification, params ImportTask[] tasks) => scoreImporter.Import(notification, tasks);
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) =>
scoreImporter.ImportModel(item, archive, batchImport, cancellationToken);
#region Implementation of IPresentImports<ScoreInfo>
2022-06-20 17:21:37 +08:00
public Action<IEnumerable<Live<ScoreInfo>>> PresentImport
{
2022-06-20 17:21:37 +08:00
set => scoreImporter.PresentImport = value;
}
#endregion
}
}