From 84bddf0885893d700addf2cbc00900dc408a0f8e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 30 Sep 2021 17:00:15 +0900 Subject: [PATCH] Initial PP counter implementation --- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- .../Difficulty/DifficultyCalculator.cs | 126 +++++++++++++++--- .../HUD/DefaultPerformancePointsCounter.cs | 108 +++++++++++++++ osu.Game/Screens/Play/Player.cs | 8 +- 4 files changed, 218 insertions(+), 26 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 61760e69b0..c4c5c89f28 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -81,7 +81,7 @@ namespace osu.Game.Beatmaps /// The applicable . protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap); - public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null) + public virtual IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null) { using (var cancellationSource = createCancellationTokenSource(timeout)) { diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 224c9178ae..f38949d982 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -7,6 +7,8 @@ using System.Linq; using osu.Framework.Audio.Track; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; @@ -19,6 +21,10 @@ namespace osu.Game.Rulesets.Difficulty private readonly Ruleset ruleset; private readonly WorkingBeatmap beatmap; + private IBeatmap playableBeatmap; + private Mod[] playableMods; + private double clockRate; + protected DifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) { this.ruleset = ruleset; @@ -32,14 +38,41 @@ namespace osu.Game.Rulesets.Difficulty /// A structure describing the difficulty of the beatmap. public DifficultyAttributes Calculate(params Mod[] mods) { - mods = mods.Select(m => m.DeepClone()).ToArray(); + preProcess(mods); - IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods); + var skills = CreateSkills(playableBeatmap, playableMods, clockRate); - var track = new TrackVirtual(10000); - mods.OfType().ForEach(m => m.ApplyToTrack(track)); + if (!playableBeatmap.HitObjects.Any()) + return CreateDifficultyAttributes(playableBeatmap, playableMods, skills, clockRate); - return calculate(playableBeatmap, mods, track.Rate); + foreach (var hitObject in getDifficultyHitObjects()) + { + foreach (var skill in skills) + skill.ProcessInternal(hitObject); + } + + return CreateDifficultyAttributes(playableBeatmap, playableMods, skills, clockRate); + } + + public IEnumerable CalculateTimed(params Mod[] mods) + { + preProcess(mods); + + if (!playableBeatmap.HitObjects.Any()) + yield break; + + var skills = CreateSkills(playableBeatmap, playableMods, clockRate); + var progressiveBeatmap = new ProgressiveCalculationBeatmap(playableBeatmap); + + foreach (var hitObject in getDifficultyHitObjects()) + { + progressiveBeatmap.HitObjects.Add(hitObject.BaseObject); + + foreach (var skill in skills) + skill.ProcessInternal(hitObject); + + yield return new TimedDifficultyAttributes(hitObject.EndTime, CreateDifficultyAttributes(progressiveBeatmap, playableMods, skills, clockRate)); + } } /// @@ -57,24 +90,23 @@ namespace osu.Game.Rulesets.Difficulty } } - private DifficultyAttributes calculate(IBeatmap beatmap, Mod[] mods, double clockRate) + /// + /// Retrieves the s to calculate against. + /// + private IEnumerable getDifficultyHitObjects() => SortObjects(CreateDifficultyHitObjects(playableBeatmap, clockRate)); + + /// + /// Performs required tasks before every calculation. + /// + /// The original list of s. + private void preProcess(Mod[] mods) { - var skills = CreateSkills(beatmap, mods, clockRate); + playableMods = mods.Select(m => m.DeepClone()).ToArray(); + playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods); - if (!beatmap.HitObjects.Any()) - return CreateDifficultyAttributes(beatmap, mods, skills, clockRate); - - var difficultyHitObjects = SortObjects(CreateDifficultyHitObjects(beatmap, clockRate)).ToList(); - - foreach (var hitObject in difficultyHitObjects) - { - foreach (var skill in skills) - { - skill.ProcessInternal(hitObject); - } - } - - return CreateDifficultyAttributes(beatmap, mods, skills, clockRate); + var track = new TrackVirtual(10000); + mods.OfType().ForEach(m => m.ApplyToTrack(track)); + clockRate = track.Rate; } /// @@ -183,5 +215,57 @@ namespace osu.Game.Rulesets.Difficulty /// Clockrate to calculate difficulty with. /// The s. protected abstract Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate); + + public class TimedDifficultyAttributes : IComparable + { + public readonly double Time; + public readonly DifficultyAttributes Attributes; + + public TimedDifficultyAttributes(double time, DifficultyAttributes attributes) + { + Time = time; + Attributes = attributes; + } + + public int CompareTo(TimedDifficultyAttributes other) => Time.CompareTo(other.Time); + } + + private class ProgressiveCalculationBeatmap : IBeatmap + { + private readonly IBeatmap baseBeatmap; + + public ProgressiveCalculationBeatmap(IBeatmap baseBeatmap) + { + this.baseBeatmap = baseBeatmap; + } + + public BeatmapInfo BeatmapInfo + { + get => baseBeatmap.BeatmapInfo; + set => baseBeatmap.BeatmapInfo = value; + } + + public BeatmapMetadata Metadata => baseBeatmap.Metadata; + + public ControlPointInfo ControlPointInfo + { + get => baseBeatmap.ControlPointInfo; + set => baseBeatmap.ControlPointInfo = value; + } + + public List Breaks => baseBeatmap.Breaks; + + public double TotalBreakTime => baseBeatmap.TotalBreakTime; + + public readonly List HitObjects = new List(); + + IReadOnlyList IBeatmap.HitObjects => HitObjects; + + public IEnumerable GetStatistics() => baseBeatmap.GetStatistics(); + + public double GetMostCommonBeatLength() => baseBeatmap.GetMostCommonBeatLength(); + + public IBeatmap Clone() => new ProgressiveCalculationBeatmap(baseBeatmap.Clone()); + } } } diff --git a/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs new file mode 100644 index 0000000000..18bb621dd1 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs @@ -0,0 +1,108 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics; +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.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.Skinning; + +namespace osu.Game.Screens.Play.HUD +{ + public class DefaultPerformancePointsCounter : RollingCounter, ISkinnableDrawable + { + public bool UsesFixedAnchor { get; set; } + + [Resolved] + private ScoreProcessor scoreProcessor { get; set; } + + [Resolved] + private Player player { get; set; } + + private DifficultyCalculator.TimedDifficultyAttributes[] timedAttributes; + private Ruleset gameplayRuleset; + + public DefaultPerformancePointsCounter() + { + Current.Value = DisplayedCount = 0; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.BlueLighter; + + gameplayRuleset = player.GameplayRuleset; + timedAttributes = gameplayRuleset.CreateDifficultyCalculator(new GameplayWorkingBeatmap(player.GameplayBeatmap)).CalculateTimed(player.Mods.Value.ToArray()).ToArray(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + scoreProcessor.NewJudgement += onNewJudgement; + } + + private void onNewJudgement(JudgementResult judgement) + { + var attribIndex = Array.BinarySearch(timedAttributes, 0, timedAttributes.Length, new DifficultyCalculator.TimedDifficultyAttributes(judgement.HitObject.GetEndTime(), null)); + if (attribIndex < 0) + attribIndex = ~attribIndex - 1; + attribIndex = Math.Clamp(attribIndex, 0, timedAttributes.Length - 1); + + var ppProcessor = gameplayRuleset.CreatePerformanceCalculator(timedAttributes[attribIndex].Attributes, player.Score.ScoreInfo); + Current.Value = (int)(ppProcessor?.Calculate() ?? 0); + } + + protected override LocalisableString FormatCount(int count) => $@"{count}pp"; + + protected override OsuSpriteText CreateSpriteText() + => base.CreateSpriteText().With(s => s.Font = s.Font.With(size: 20f)); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (scoreProcessor != null) + scoreProcessor.NewJudgement -= onNewJudgement; + } + + private class GameplayWorkingBeatmap : WorkingBeatmap + { + private readonly GameplayBeatmap gameplayBeatmap; + + public GameplayWorkingBeatmap(GameplayBeatmap gameplayBeatmap) + : base(gameplayBeatmap.BeatmapInfo, null) + { + this.gameplayBeatmap = gameplayBeatmap; + } + + public override IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null) + => gameplayBeatmap; + + protected override IBeatmap GetBeatmap() => gameplayBeatmap; + + protected 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/Player.cs b/osu.Game/Screens/Play/Player.cs index 9927467bd6..4018650093 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -93,9 +93,9 @@ namespace osu.Game.Screens.Play [Resolved] private SpectatorClient spectatorClient { get; set; } - protected Ruleset GameplayRuleset { get; private set; } + public Ruleset GameplayRuleset { get; private set; } - protected GameplayBeatmap GameplayBeatmap { get; private set; } + public GameplayBeatmap GameplayBeatmap { get; private set; } private Sample sampleRestart; @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Play [Cached] [Cached(Type = typeof(IBindable>))] - protected new readonly Bindable> Mods = new Bindable>(Array.Empty()); + public new readonly Bindable> Mods = new Bindable>(Array.Empty()); /// /// Whether failing should be allowed. @@ -137,7 +137,7 @@ namespace osu.Game.Screens.Play public readonly PlayerConfiguration Configuration; - protected Score Score { get; private set; } + public Score Score { get; private set; } /// /// Create a new player instance.