1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-28 02:02:53 +08:00

Merge pull request #10106 from smoogipoo/score-recalc

This commit is contained in:
Dean Herbert 2020-09-10 19:05:42 +09:00 committed by GitHub
commit 23b51e8f7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 207 additions and 26 deletions

View File

@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Catch.Difficulty
public class CatchDifficultyAttributes : DifficultyAttributes
{
public double ApproachRate;
public int MaxCombo;
}
}

View File

@ -11,6 +11,7 @@ using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
using osu.Game.Rulesets.Mania.Difficulty.Skills;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
@ -43,6 +44,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
Mods = mods,
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
GreatHitWindow = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate,
MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1),
Skills = skills
};
}

View File

@ -11,6 +11,5 @@ namespace osu.Game.Rulesets.Osu.Difficulty
public double SpeedStrain;
public double ApproachRate;
public double OverallDifficulty;
public int MaxCombo;
}
}

View File

@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
public class TaikoDifficultyAttributes : DifficultyAttributes
{
public double GreatHitWindow;
public int MaxCombo;
}
}

View File

@ -207,11 +207,11 @@ namespace osu.Game.Beatmaps
var calculator = ruleset.CreateDifficultyCalculator(beatmapManager.GetWorkingBeatmap(beatmapInfo));
var attributes = calculator.Calculate(key.Mods);
return difficultyCache[key] = new StarDifficulty(attributes.StarRating);
return difficultyCache[key] = new StarDifficulty(attributes.StarRating, attributes.MaxCombo);
}
catch
{
return difficultyCache[key] = new StarDifficulty(0);
return difficultyCache[key] = new StarDifficulty();
}
}
@ -233,7 +233,7 @@ namespace osu.Game.Beatmaps
if (beatmapInfo.ID == 0 || rulesetInfo.ID == null)
{
// If not, fall back to the existing star difficulty (e.g. from an online source).
existingDifficulty = new StarDifficulty(beatmapInfo.StarDifficulty);
existingDifficulty = new StarDifficulty(beatmapInfo.StarDifficulty, beatmapInfo.MaxCombo ?? 0);
key = default;
return true;
@ -298,10 +298,12 @@ namespace osu.Game.Beatmaps
public readonly struct StarDifficulty
{
public readonly double Stars;
public readonly int MaxCombo;
public StarDifficulty(double stars)
public StarDifficulty(double stars, int maxCombo)
{
Stars = stars;
MaxCombo = maxCombo;
// Todo: Add more members (BeatmapInfo.DifficultyRating? Attributes? Etc...)
}

View File

@ -1,6 +1,7 @@
// 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.
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
@ -55,6 +56,12 @@ namespace osu.Game.Graphics.Sprites
set => spriteText.UseFullGlyphHeight = blurredText.UseFullGlyphHeight = value;
}
public Bindable<string> Current
{
get => spriteText.Current;
set => spriteText.Current = value;
}
public GlowingSpriteText()
{
AutoSizeAxes = Axes.Both;

View File

@ -72,7 +72,7 @@ namespace osu.Game.Online.Leaderboards
}
[BackgroundDependencyLoader]
private void load(IAPIProvider api, OsuColour colour)
private void load(IAPIProvider api, OsuColour colour, ScoreManager scoreManager)
{
var user = score.User;
@ -194,7 +194,7 @@ namespace osu.Game.Online.Leaderboards
{
TextColour = Color4.White,
GlowColour = Color4Extensions.FromHex(@"83ccfa"),
Text = score.TotalScore.ToString(@"N0"),
Current = scoreManager.GetBindableTotalScoreString(score),
Font = OsuFont.Numeric.With(size: 23),
},
RankContainer = new Container

View File

@ -58,6 +58,8 @@ namespace osu.Game
protected ScoreManager ScoreManager;
protected BeatmapDifficultyManager DifficultyManager;
protected SkinManager SkinManager;
protected RulesetStore RulesetStore;
@ -197,7 +199,7 @@ namespace osu.Game
dependencies.Cache(FileStore = new FileStore(contextFactory, Storage));
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host));
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host, () => DifficultyManager, LocalConfig));
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Host, defaultBeatmap, true));
// this should likely be moved to ArchiveModelManager when another case appers where it is necessary
@ -221,9 +223,8 @@ namespace osu.Game
ScoreManager.Undelete(getBeatmapScores(item), true);
});
var difficultyManager = new BeatmapDifficultyManager();
dependencies.Cache(difficultyManager);
AddInternal(difficultyManager);
dependencies.Cache(DifficultyManager = new BeatmapDifficultyManager());
AddInternal(DifficultyManager);
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory));

View File

@ -25,6 +25,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
private const float row_height = 22;
private const int text_size = 12;
[Resolved]
private ScoreManager scoreManager { get; set; }
private readonly FillFlowContainer backgroundFlow;
private Color4 highAccuracyColour;
@ -121,7 +124,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
new OsuSpriteText
{
Margin = new MarginPadding { Right = horizontal_inset },
Text = $@"{score.TotalScore:N0}",
Current = scoreManager.GetBindableTotalScoreString(score),
Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium)
},
new OsuSpriteText

View File

@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -38,6 +39,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
private readonly FillFlowContainer<InfoColumn> statisticsColumns;
private readonly ModsInfoColumn modsColumn;
[Resolved]
private ScoreManager scoreManager { get; set; }
public TopScoreStatisticsSection()
{
RelativeSizeAxes = Axes.X;
@ -87,6 +91,15 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
};
}
[BackgroundDependencyLoader]
private void load()
{
if (score != null)
totalScoreColumn.Current = scoreManager.GetBindableTotalScoreString(score);
}
private ScoreInfo score;
/// <summary>
/// Sets the score to be displayed.
/// </summary>
@ -94,7 +107,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
{
set
{
totalScoreColumn.Text = $@"{value.TotalScore:N0}";
if (score == value)
return;
score = value;
accuracyColumn.Text = value.DisplayAccuracy;
maxComboColumn.Text = $@"{value.MaxCombo:N0}x";
ppColumn.Alpha = value.Beatmap?.Status == BeatmapSetOnlineStatus.Ranked ? 1 : 0;
@ -102,6 +119,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
statisticsColumns.ChildrenEnumerable = value.SortedStatistics.Select(kvp => createStatisticsColumn(kvp.Key, kvp.Value));
modsColumn.Mods = value.Mods;
if (scoreManager != null)
totalScoreColumn.Current = scoreManager.GetBindableTotalScoreString(value);
}
}
@ -190,6 +210,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
{
set => text.Text = value;
}
public Bindable<string> Current
{
get => text.Current;
set => text.Current = value;
}
}
private class ModsInfoColumn : InfoColumn

View File

@ -12,6 +12,7 @@ namespace osu.Game.Rulesets.Difficulty
public Skill[] Skills;
public double StarRating;
public int MaxCombo;
public DifficultyAttributes()
{

View File

@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Scoring
private readonly double accuracyPortion;
private readonly double comboPortion;
private double maxHighestCombo;
private int maxHighestCombo;
private double maxBaseScore;
private double rollingMaxBaseScore;
private double baseScore;
@ -202,20 +202,31 @@ namespace osu.Game.Rulesets.Scoring
TotalScore.Value = getScore(Mode.Value);
}
private double getScore(ScoringMode mode)
private double getScore(ScoringMode mode) => GetScore(mode, maxHighestCombo, baseScore / maxBaseScore, (double)HighestCombo.Value / maxHighestCombo, bonusScore);
/// <summary>
/// Computes the total score.
/// </summary>
/// <param name="mode">The <see cref="ScoringMode"/> to compute the total score in.</param>
/// <param name="maxCombo">The maximum combo achievable in the beatmap.</param>
/// <param name="accuracyRatio">The accuracy percentage achieved by the player.</param>
/// <param name="comboRatio">The proportion of <paramref name="maxCombo"/> achieved by the player.</param>
/// <param name="bonusScore">Any bonus score to be added.</param>
/// <returns>The total score.</returns>
public double GetScore(ScoringMode mode, int maxCombo, double accuracyRatio, double comboRatio, double bonusScore)
{
switch (mode)
{
default:
case ScoringMode.Standardised:
double accuracyScore = accuracyPortion * baseScore / maxBaseScore;
double comboScore = comboPortion * HighestCombo.Value / maxHighestCombo;
double accuracyScore = accuracyPortion * accuracyRatio;
double comboScore = comboPortion * comboRatio;
return (max_score * (accuracyScore + comboScore) + bonusScore) * scoreMultiplier;
case ScoringMode.Classic:
// should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1)
return bonusScore + baseScore * (1 + Math.Max(0, HighestCombo.Value - 1) * scoreMultiplier / 25);
return bonusScore + (accuracyRatio * maxCombo * 300) * (1 + Math.Max(0, (comboRatio * maxCombo) - 1) * scoreMultiplier / 25);
}
}

View File

@ -6,15 +6,20 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using osu.Framework.Bindables;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.IO.Archives;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring.Legacy;
namespace osu.Game.Scoring
@ -30,11 +35,20 @@ namespace osu.Game.Scoring
private readonly RulesetStore rulesets;
private readonly Func<BeatmapManager> beatmaps;
public ScoreManager(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, IAPIProvider api, IDatabaseContextFactory contextFactory, IIpcHost importHost = null)
[CanBeNull]
private readonly Func<BeatmapDifficultyManager> difficulties;
[CanBeNull]
private readonly OsuConfigManager configManager;
public ScoreManager(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, IAPIProvider api, IDatabaseContextFactory contextFactory, IIpcHost importHost = null,
Func<BeatmapDifficultyManager> difficulties = null, OsuConfigManager configManager = null)
: base(storage, contextFactory, api, new ScoreStore(contextFactory, storage), importHost)
{
this.rulesets = rulesets;
this.beatmaps = beatmaps;
this.difficulties = difficulties;
this.configManager = configManager;
}
protected override ScoreInfo CreateModel(ArchiveReader archive)
@ -72,5 +86,118 @@ namespace osu.Game.Scoring
protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable<ScoreInfo> items)
=> base.CheckLocalAvailability(model, items)
|| (model.OnlineScoreID != null && items.Any(i => i.OnlineScoreID == model.OnlineScoreID));
/// <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>
public Bindable<long> GetBindableTotalScore(ScoreInfo score)
{
var bindable = new TotalScoreBindable(score, difficulties);
configManager?.BindWith(OsuSetting.ScoreDisplayMode, bindable.ScoringMode);
return bindable;
}
/// <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(ScoreInfo score) => new TotalScoreStringBindable(GetBindableTotalScore(score));
/// <summary>
/// Provides the total score of a <see cref="ScoreInfo"/>. Responds to changes in the currently-selected <see cref="ScoringMode"/>.
/// </summary>
private class TotalScoreBindable : Bindable<long>
{
public readonly Bindable<ScoringMode> ScoringMode = new Bindable<ScoringMode>();
private readonly ScoreInfo score;
private readonly Func<BeatmapDifficultyManager> difficulties;
/// <summary>
/// Creates a new <see cref="TotalScoreBindable"/>.
/// </summary>
/// <param name="score">The <see cref="ScoreInfo"/> to provide the total score of.</param>
/// <param name="difficulties">A function to retrieve the <see cref="BeatmapDifficultyManager"/>.</param>
public TotalScoreBindable(ScoreInfo score, Func<BeatmapDifficultyManager> difficulties)
{
this.score = score;
this.difficulties = difficulties;
ScoringMode.BindValueChanged(onScoringModeChanged, true);
}
private IBindable<StarDifficulty> difficultyBindable;
private CancellationTokenSource difficultyCancellationSource;
private void onScoringModeChanged(ValueChangedEvent<ScoringMode> mode)
{
difficultyCancellationSource?.Cancel();
difficultyCancellationSource = null;
if (score.Beatmap == null)
{
Value = score.TotalScore;
return;
}
int? beatmapMaxCombo = score.Beatmap.MaxCombo;
if (beatmapMaxCombo == null)
{
if (score.Beatmap.ID == 0 || difficulties == null)
{
// We don't have enough information (max combo) to compute the score, so let's use the provided score.
Value = score.TotalScore;
return;
}
// We can compute the max combo locally after the async beatmap difficulty computation.
difficultyBindable = difficulties().GetBindableDifficulty(score.Beatmap, score.Ruleset, score.Mods, (difficultyCancellationSource = new CancellationTokenSource()).Token);
difficultyBindable.BindValueChanged(d => updateScore(d.NewValue.MaxCombo), true);
}
else
updateScore(beatmapMaxCombo.Value);
}
private void updateScore(int beatmapMaxCombo)
{
if (beatmapMaxCombo == 0)
{
Value = 0;
return;
}
var ruleset = score.Ruleset.CreateInstance();
var scoreProcessor = ruleset.CreateScoreProcessor();
scoreProcessor.Mods.Value = score.Mods;
Value = (long)Math.Round(scoreProcessor.GetScore(ScoringMode.Value, beatmapMaxCombo, score.Accuracy, (double)score.MaxCombo / beatmapMaxCombo, 0));
}
}
/// <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>
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);
}
}
}
}

View File

@ -30,6 +30,9 @@ namespace osu.Game.Screens.Ranking.Contracted
{
private readonly ScoreInfo score;
[Resolved]
private ScoreManager scoreManager { get; set; }
/// <summary>
/// Creates a new <see cref="ContractedPanelMiddleContent"/>.
/// </summary>
@ -160,7 +163,7 @@ namespace osu.Game.Screens.Ranking.Contracted
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = score.TotalScore.ToString("N0"),
Current = scoreManager.GetBindableTotalScoreString(score),
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, fixedWidth: true),
Spacing = new Vector2(-1, 0)
},

View File

@ -25,15 +25,16 @@ namespace osu.Game.Screens.Ranking.Expanded
/// </summary>
public class ExpandedPanelMiddleContent : CompositeDrawable
{
private readonly ScoreInfo score;
private const float padding = 10;
private readonly ScoreInfo score;
private readonly List<StatisticDisplay> statisticDisplays = new List<StatisticDisplay>();
private FillFlowContainer starAndModDisplay;
private RollingCounter<long> scoreCounter;
private const float padding = 10;
[Resolved]
private ScoreManager scoreManager { get; set; }
/// <summary>
/// Creates a new <see cref="ExpandedPanelMiddleContent"/>.
@ -238,7 +239,7 @@ namespace osu.Game.Screens.Ranking.Expanded
using (BeginDelayedSequence(AccuracyCircle.ACCURACY_TRANSFORM_DELAY, true))
{
scoreCounter.FadeIn();
scoreCounter.Current.Value = score.TotalScore;
scoreCounter.Current = scoreManager.GetBindableTotalScore(score);
double delay = 0;