From daf4a03fd092ac84955a8f63fa3c45200a28cf04 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 26 Oct 2023 08:59:47 +0300 Subject: [PATCH] Abstractify PP counter logic from the "Triangles" implementation --- .../HUD/GameplayPerformancePointsCounter.cs | 186 ++++++++++++++++++ .../Play/HUD/PerformancePointsCounter.cs | 177 +---------------- 2 files changed, 190 insertions(+), 173 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/GameplayPerformancePointsCounter.cs diff --git a/osu.Game/Screens/Play/HUD/GameplayPerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/GameplayPerformancePointsCounter.cs new file mode 100644 index 0000000000..a812a3e043 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/GameplayPerformancePointsCounter.cs @@ -0,0 +1,186 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Audio.Track; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Textures; +using osu.Framework.Localisation; +using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Skinning; + +namespace osu.Game.Screens.Play.HUD +{ + public abstract partial class GameplayPerformancePointsCounter : RollingCounter + { + protected override bool IsRollingProportional => true; + + protected override double RollingDuration => 1000; + + private const float alpha_when_invalid = 0.3f; + + [Resolved] + private ScoreProcessor scoreProcessor { get; set; } + + [Resolved] + private GameplayState gameplayState { get; set; } + + [CanBeNull] + private List timedAttributes; + + private readonly CancellationTokenSource loadCancellationSource = new CancellationTokenSource(); + + private JudgementResult lastJudgement; + private PerformanceCalculator performanceCalculator; + private ScoreInfo scoreInfo; + + protected GameplayPerformancePointsCounter() + { + Current.Value = DisplayedCount = 0; + } + + private Mod[] clonedMods; + + [BackgroundDependencyLoader] + private void load(BeatmapDifficultyCache difficultyCache) + { + DrawableCount.Alpha = alpha_when_invalid; + + if (gameplayState != null) + { + performanceCalculator = gameplayState.Ruleset.CreatePerformanceCalculator(); + clonedMods = gameplayState.Mods.Select(m => m.DeepClone()).ToArray(); + + scoreInfo = new ScoreInfo(gameplayState.Score.ScoreInfo.BeatmapInfo, gameplayState.Score.ScoreInfo.Ruleset) { Mods = clonedMods }; + + var gameplayWorkingBeatmap = new GameplayWorkingBeatmap(gameplayState.Beatmap); + difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, clonedMods, loadCancellationSource.Token) + .ContinueWith(task => Schedule(() => + { + timedAttributes = task.GetResultSafely(); + + IsValid = true; + + if (lastJudgement != null) + onJudgementChanged(lastJudgement); + }), TaskContinuationOptions.OnlyOnRanToCompletion); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (scoreProcessor != null) + { + scoreProcessor.NewJudgement += onJudgementChanged; + scoreProcessor.JudgementReverted += onJudgementChanged; + } + + if (gameplayState?.LastJudgementResult.Value != null) + onJudgementChanged(gameplayState.LastJudgementResult.Value); + } + + private bool isValid; + + protected bool IsValid + { + set + { + if (value == isValid) + return; + + isValid = value; + DrawableCount.FadeTo(isValid ? 1 : alpha_when_invalid, 1000, Easing.OutQuint); + } + } + + private void onJudgementChanged(JudgementResult judgement) + { + lastJudgement = judgement; + + var attrib = getAttributeAtTime(judgement); + + if (gameplayState == null || attrib == null || scoreProcessor == null) + { + IsValid = false; + return; + } + + scoreProcessor.PopulateScore(scoreInfo); + Current.Value = (int)Math.Round(performanceCalculator?.Calculate(scoreInfo, attrib).Total ?? 0, MidpointRounding.AwayFromZero); + IsValid = true; + } + + [CanBeNull] + private DifficultyAttributes getAttributeAtTime(JudgementResult judgement) + { + if (timedAttributes == null || timedAttributes.Count == 0) + return null; + + int attribIndex = timedAttributes.BinarySearch(new TimedDifficultyAttributes(judgement.HitObject.GetEndTime(), null)); + if (attribIndex < 0) + attribIndex = ~attribIndex - 1; + + return timedAttributes[Math.Clamp(attribIndex, 0, timedAttributes.Count - 1)].Attributes; + } + + protected override LocalisableString FormatCount(int count) => count.ToString(@"D"); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (scoreProcessor != null) + { + scoreProcessor.NewJudgement -= onJudgementChanged; + scoreProcessor.JudgementReverted -= onJudgementChanged; + } + + loadCancellationSource?.Cancel(); + } + + // TODO: This class shouldn't exist, but requires breaking changes to allow DifficultyCalculator to receive an IBeatmap. + private class GameplayWorkingBeatmap : WorkingBeatmap + { + private readonly IBeatmap gameplayBeatmap; + + public GameplayWorkingBeatmap(IBeatmap gameplayBeatmap) + : base(gameplayBeatmap.BeatmapInfo, null) + { + this.gameplayBeatmap = gameplayBeatmap; + } + + public override IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList mods, CancellationToken cancellationToken) + => gameplayBeatmap; + + protected override IBeatmap GetBeatmap() => gameplayBeatmap; + + public override Texture GetBackground() => throw new NotImplementedException(); + + protected override Track GetBeatmapTrack() => throw new NotImplementedException(); + + protected internal override ISkin GetSkin() => throw new NotImplementedException(); + + public override Stream GetStream(string storagePath) => throw new NotImplementedException(); + } + } +} diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 82f116b4ae..76feae88af 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -1,175 +1,31 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Audio.Track; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osu.Framework.Localisation; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Resources.Localisation.Web; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; using osu.Game.Skinning; using osuTK; namespace osu.Game.Screens.Play.HUD { - public partial class PerformancePointsCounter : RollingCounter, ISerialisableDrawable + // todo: this should be renamed to DefaultPerformancePointsCounter, or probably even TrianglesPerformancePointsCounter once all other triangles components are renamed accordingly. + public partial class PerformancePointsCounter : GameplayPerformancePointsCounter, ISerialisableDrawable { public bool UsesFixedAnchor { get; set; } - protected override bool IsRollingProportional => true; - - protected override double RollingDuration => 1000; - - private const float alpha_when_invalid = 0.3f; - - [Resolved] - private ScoreProcessor scoreProcessor { get; set; } - - [Resolved] - private GameplayState gameplayState { get; set; } - - [CanBeNull] - private List timedAttributes; - - private readonly CancellationTokenSource loadCancellationSource = new CancellationTokenSource(); - - private JudgementResult lastJudgement; - private PerformanceCalculator performanceCalculator; - private ScoreInfo scoreInfo; - - public PerformancePointsCounter() - { - Current.Value = DisplayedCount = 0; - } - - private Mod[] clonedMods; - [BackgroundDependencyLoader] - private void load(OsuColour colours, BeatmapDifficultyCache difficultyCache) + private void load(OsuColour colours) { Colour = colours.BlueLighter; - - if (gameplayState != null) - { - performanceCalculator = gameplayState.Ruleset.CreatePerformanceCalculator(); - clonedMods = gameplayState.Mods.Select(m => m.DeepClone()).ToArray(); - - scoreInfo = new ScoreInfo(gameplayState.Score.ScoreInfo.BeatmapInfo, gameplayState.Score.ScoreInfo.Ruleset) { Mods = clonedMods }; - - var gameplayWorkingBeatmap = new GameplayWorkingBeatmap(gameplayState.Beatmap); - difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, clonedMods, loadCancellationSource.Token) - .ContinueWith(task => Schedule(() => - { - timedAttributes = task.GetResultSafely(); - - IsValid = true; - - if (lastJudgement != null) - onJudgementChanged(lastJudgement); - }), TaskContinuationOptions.OnlyOnRanToCompletion); - } } - protected override void LoadComplete() - { - base.LoadComplete(); - - if (scoreProcessor != null) - { - scoreProcessor.NewJudgement += onJudgementChanged; - scoreProcessor.JudgementReverted += onJudgementChanged; - } - - if (gameplayState?.LastJudgementResult.Value != null) - onJudgementChanged(gameplayState.LastJudgementResult.Value); - } - - private bool isValid; - - protected bool IsValid - { - set - { - if (value == isValid) - return; - - isValid = value; - DrawableCount.FadeTo(isValid ? 1 : alpha_when_invalid, 1000, Easing.OutQuint); - } - } - - private void onJudgementChanged(JudgementResult judgement) - { - lastJudgement = judgement; - - var attrib = getAttributeAtTime(judgement); - - if (gameplayState == null || attrib == null || scoreProcessor == null) - { - IsValid = false; - return; - } - - scoreProcessor.PopulateScore(scoreInfo); - Current.Value = (int)Math.Round(performanceCalculator?.Calculate(scoreInfo, attrib).Total ?? 0, MidpointRounding.AwayFromZero); - IsValid = true; - } - - [CanBeNull] - private DifficultyAttributes getAttributeAtTime(JudgementResult judgement) - { - if (timedAttributes == null || timedAttributes.Count == 0) - return null; - - int attribIndex = timedAttributes.BinarySearch(new TimedDifficultyAttributes(judgement.HitObject.GetEndTime(), null)); - if (attribIndex < 0) - attribIndex = ~attribIndex - 1; - - return timedAttributes[Math.Clamp(attribIndex, 0, timedAttributes.Count - 1)].Attributes; - } - - protected override LocalisableString FormatCount(int count) => count.ToString(@"D"); - - protected override IHasText CreateText() => new TextComponent - { - Alpha = alpha_when_invalid - }; - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (scoreProcessor != null) - { - scoreProcessor.NewJudgement -= onJudgementChanged; - scoreProcessor.JudgementReverted -= onJudgementChanged; - } - - loadCancellationSource?.Cancel(); - } + protected override IHasText CreateText() => new TextComponent(); private partial class TextComponent : CompositeDrawable, IHasText { @@ -209,30 +65,5 @@ namespace osu.Game.Screens.Play.HUD }; } } - - // TODO: This class shouldn't exist, but requires breaking changes to allow DifficultyCalculator to receive an IBeatmap. - private class GameplayWorkingBeatmap : WorkingBeatmap - { - private readonly IBeatmap gameplayBeatmap; - - public GameplayWorkingBeatmap(IBeatmap gameplayBeatmap) - : base(gameplayBeatmap.BeatmapInfo, null) - { - this.gameplayBeatmap = gameplayBeatmap; - } - - public override IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList mods, CancellationToken cancellationToken) - => gameplayBeatmap; - - protected override IBeatmap GetBeatmap() => gameplayBeatmap; - - public override Texture GetBackground() => throw new NotImplementedException(); - - protected override Track GetBeatmapTrack() => throw new NotImplementedException(); - - protected internal override ISkin GetSkin() => throw new NotImplementedException(); - - public override Stream GetStream(string storagePath) => throw new NotImplementedException(); - } } }