Initial PP counter implementation

This commit is contained in:
smoogipoo 2021-09-30 17:00:15 +09:00
parent 8bbd8cd948
commit 84bddf0885
4 changed files with 218 additions and 26 deletions

View File

@ -81,7 +81,7 @@ namespace osu.Game.Beatmaps
/// <returns>The applicable <see cref="IBeatmapConverter"/>.</returns>
protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap);
public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList<Mod> mods = null, TimeSpan? timeout = null)
public virtual IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList<Mod> mods = null, TimeSpan? timeout = null)
{
using (var cancellationSource = createCancellationTokenSource(timeout))
{

View File

@ -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
/// <returns>A structure describing the difficulty of the beatmap.</returns>
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<IApplicableToTrack>().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<TimedDifficultyAttributes> 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));
}
}
/// <summary>
@ -57,24 +90,23 @@ namespace osu.Game.Rulesets.Difficulty
}
}
private DifficultyAttributes calculate(IBeatmap beatmap, Mod[] mods, double clockRate)
/// <summary>
/// Retrieves the <see cref="DifficultyHitObject"/>s to calculate against.
/// </summary>
private IEnumerable<DifficultyHitObject> getDifficultyHitObjects() => SortObjects(CreateDifficultyHitObjects(playableBeatmap, clockRate));
/// <summary>
/// Performs required tasks before every calculation.
/// </summary>
/// <param name="mods">The original list of <see cref="Mod"/>s.</param>
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<IApplicableToTrack>().ForEach(m => m.ApplyToTrack(track));
clockRate = track.Rate;
}
/// <summary>
@ -183,5 +215,57 @@ namespace osu.Game.Rulesets.Difficulty
/// <param name="clockRate">Clockrate to calculate difficulty with.</param>
/// <returns>The <see cref="Skill"/>s.</returns>
protected abstract Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate);
public class TimedDifficultyAttributes : IComparable<TimedDifficultyAttributes>
{
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<BreakPeriod> Breaks => baseBeatmap.Breaks;
public double TotalBreakTime => baseBeatmap.TotalBreakTime;
public readonly List<HitObject> HitObjects = new List<HitObject>();
IReadOnlyList<HitObject> IBeatmap.HitObjects => HitObjects;
public IEnumerable<BeatmapStatistic> GetStatistics() => baseBeatmap.GetStatistics();
public double GetMostCommonBeatLength() => baseBeatmap.GetMostCommonBeatLength();
public IBeatmap Clone() => new ProgressiveCalculationBeatmap(baseBeatmap.Clone());
}
}
}

View File

@ -0,0 +1,108 @@
// 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 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<int>, 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<Mod> 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();
}
}
}

View File

@ -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<IReadOnlyList<Mod>>))]
protected new readonly Bindable<IReadOnlyList<Mod>> Mods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
public new readonly Bindable<IReadOnlyList<Mod>> Mods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
/// <summary>
/// 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; }
/// <summary>
/// Create a new player instance.