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.