1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 10:12:54 +08:00

Merge pull request #14919 from smoogipoo/realtime-pp-display

Implement real-time PP counter
This commit is contained in:
Dean Herbert 2021-10-05 20:50:03 +09:00 committed by GitHub
commit 6f7b8293af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 540 additions and 59 deletions

View File

@ -49,7 +49,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)Math.Ceiling(getHitWindow300(mods) / clockRate),
ScoreMultiplier = getScoreMultiplier(beatmap, mods),
ScoreMultiplier = getScoreMultiplier(mods),
MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1),
Skills = skills
};
@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[]
{
new Strain(mods, ((ManiaBeatmap)beatmap).TotalColumns)
new Strain(mods, ((ManiaBeatmap)Beatmap).TotalColumns)
};
protected override Mod[] DifficultyAdjustmentMods
@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
}
}
private double getScoreMultiplier(IBeatmap beatmap, Mod[] mods)
private double getScoreMultiplier(Mod[] mods)
{
double scoreMultiplier = 1;
@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
}
}
var maniaBeatmap = (ManiaBeatmap)beatmap;
var maniaBeatmap = (ManiaBeatmap)Beatmap;
int diff = maniaBeatmap.TotalColumns - maniaBeatmap.OriginalTotalColumns;
if (diff > 0)

View File

@ -42,6 +42,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestEmptyLegacyBeatmapSkinFallsBack()
{
CreateSkinTest(SkinInfo.Default, () => new LegacyBeatmapSkin(new BeatmapInfo(), null, null));
AddUntilStep("wait for hud load", () => Player.ChildrenOfType<SkinnableTargetContainer>().All(c => c.ComponentsLoaded));
AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value));
}
@ -84,18 +85,18 @@ namespace osu.Game.Tests.Visual.Gameplay
Remove(expectedComponentsAdjustmentContainer);
return almostEqual(actualInfo, expectedInfo);
static bool almostEqual(SkinnableInfo info, SkinnableInfo other) =>
other != null
&& info.Type == other.Type
&& info.Anchor == other.Anchor
&& info.Origin == other.Origin
&& Precision.AlmostEquals(info.Position, other.Position)
&& Precision.AlmostEquals(info.Scale, other.Scale)
&& Precision.AlmostEquals(info.Rotation, other.Rotation)
&& info.Children.SequenceEqual(other.Children, new FuncEqualityComparer<SkinnableInfo>(almostEqual));
}
private static bool almostEqual(SkinnableInfo info, SkinnableInfo other) =>
other != null
&& info.Type == other.Type
&& info.Anchor == other.Anchor
&& info.Origin == other.Origin
&& Precision.AlmostEquals(info.Position, other.Position, 1)
&& Precision.AlmostEquals(info.Scale, other.Scale)
&& Precision.AlmostEquals(info.Rotation, other.Rotation)
&& info.Children.SequenceEqual(other.Children, new FuncEqualityComparer<SkinnableInfo>(almostEqual));
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
=> new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, Audio, currentBeatmapSkin);

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.Diagnostics;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestScenePerformancePointsCounter : OsuTestScene
{
[Cached]
private GameplayState gameplayState;
[Cached]
private ScoreProcessor scoreProcessor;
private int iteration;
private PerformancePointsCounter counter;
public TestScenePerformancePointsCounter()
{
var ruleset = CreateRuleset();
Debug.Assert(ruleset != null);
var beatmap = CreateWorkingBeatmap(ruleset.RulesetInfo)
.GetPlayableBeatmap(ruleset.RulesetInfo);
gameplayState = new GameplayState(beatmap, ruleset);
scoreProcessor = new ScoreProcessor();
}
protected override Ruleset CreateRuleset() => new OsuRuleset();
[SetUpSteps]
public void SetUpSteps()
{
AddStep("Create counter", () =>
{
iteration = 0;
Child = counter = new PerformancePointsCounter
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(5),
};
});
}
[Test]
public void TestBasicCounting()
{
int previousValue = 0;
AddAssert("counter displaying zero", () => counter.Current.Value == 0);
AddRepeatStep("Add judgement", applyOneJudgement, 10);
AddUntilStep("counter non-zero", () => counter.Current.Value > 0);
AddUntilStep("counter opaque", () => counter.Child.Alpha == 1);
AddStep("Revert judgement", () =>
{
previousValue = counter.Current.Value;
scoreProcessor.RevertResult(new JudgementResult(new HitObject(), new OsuJudgement()));
});
AddUntilStep("counter decreased", () => counter.Current.Value < previousValue);
AddStep("Add judgement", applyOneJudgement);
AddUntilStep("counter non-zero", () => counter.Current.Value > 0);
}
private void applyOneJudgement()
{
var scoreInfo = gameplayState.Score.ScoreInfo;
scoreInfo.MaxCombo = iteration * 1000;
scoreInfo.Accuracy = 1;
scoreInfo.Statistics[HitResult.Great] = iteration * 1000;
scoreProcessor.ApplyResult(new OsuJudgementResult(new HitObject
{
StartTime = iteration * 10000,
}, new OsuJudgement())
{
Type = HitResult.Perfect,
});
iteration++;
}
}
}

View File

@ -17,6 +17,7 @@ using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
@ -147,6 +148,14 @@ namespace osu.Game.Beatmaps
}, token, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler);
}
public Task<List<TimedDifficultyAttributes>> GetTimedDifficultyAttributesAsync(WorkingBeatmap beatmap, Ruleset ruleset, Mod[] mods, CancellationToken token = default)
{
return Task.Factory.StartNew(() => ruleset.CreateDifficultyCalculator(beatmap).CalculateTimed(mods),
token,
TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously,
updateScheduler);
}
/// <summary>
/// Retrieves the <see cref="DifficultyRating"/> that describes a star rating.
/// </summary>

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

@ -25,7 +25,9 @@ namespace osu.Game.Graphics.UserInterface
set => current.Current = value;
}
private SpriteText displayedCountSpriteText;
private IHasText displayedCountText;
public Drawable DrawableCount { get; private set; }
/// <summary>
/// If true, the roll-up duration will be proportional to change in value.
@ -72,16 +74,16 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader]
private void load()
{
displayedCountSpriteText = CreateSpriteText();
displayedCountText = CreateText();
UpdateDisplay();
Child = displayedCountSpriteText;
Child = DrawableCount = (Drawable)displayedCountText;
}
protected void UpdateDisplay()
{
if (displayedCountSpriteText != null)
displayedCountSpriteText.Text = FormatCount(DisplayedCount);
if (displayedCountText != null)
displayedCountText.Text = FormatCount(DisplayedCount);
}
protected override void LoadComplete()
@ -160,6 +162,15 @@ namespace osu.Game.Graphics.UserInterface
this.TransformTo(nameof(DisplayedCount), newValue, rollingTotalDuration, RollingEasing);
}
/// <summary>
/// Creates the text. Delegates to <see cref="CreateSpriteText"/> by default.
/// </summary>
protected virtual IHasText CreateText() => CreateSpriteText();
/// <summary>
/// Creates an <see cref="OsuSpriteText"/> which may be used to display this counter's text.
/// May not be called if <see cref="CreateText"/> is overridden.
/// </summary>
protected virtual OsuSpriteText CreateSpriteText() => new OsuSpriteText
{
Font = OsuFont.Numeric.With(size: 40f),

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;
@ -16,6 +18,14 @@ namespace osu.Game.Rulesets.Difficulty
{
public abstract class DifficultyCalculator
{
/// <summary>
/// The beatmap for which difficulty will be calculated.
/// </summary>
protected IBeatmap Beatmap { get; private set; }
private Mod[] playableMods;
private double clockRate;
private readonly Ruleset ruleset;
private readonly WorkingBeatmap beatmap;
@ -32,14 +42,45 @@ 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(Beatmap, playableMods, clockRate);
var track = new TrackVirtual(10000);
mods.OfType<IApplicableToTrack>().ForEach(m => m.ApplyToTrack(track));
if (!Beatmap.HitObjects.Any())
return CreateDifficultyAttributes(Beatmap, playableMods, skills, clockRate);
return calculate(playableBeatmap, mods, track.Rate);
foreach (var hitObject in getDifficultyHitObjects())
{
foreach (var skill in skills)
skill.ProcessInternal(hitObject);
}
return CreateDifficultyAttributes(Beatmap, playableMods, skills, clockRate);
}
public List<TimedDifficultyAttributes> CalculateTimed(params Mod[] mods)
{
preProcess(mods);
var attribs = new List<TimedDifficultyAttributes>();
if (!Beatmap.HitObjects.Any())
return attribs;
var skills = CreateSkills(Beatmap, playableMods, clockRate);
var progressiveBeatmap = new ProgressiveCalculationBeatmap(Beatmap);
foreach (var hitObject in getDifficultyHitObjects())
{
progressiveBeatmap.HitObjects.Add(hitObject.BaseObject);
foreach (var skill in skills)
skill.ProcessInternal(hitObject);
attribs.Add(new TimedDifficultyAttributes(hitObject.EndTime, CreateDifficultyAttributes(progressiveBeatmap, playableMods, skills, clockRate)));
}
return attribs;
}
/// <summary>
@ -57,24 +98,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(Beatmap, 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();
Beatmap = 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>
@ -86,7 +126,7 @@ namespace osu.Game.Rulesets.Difficulty
=> input.OrderBy(h => h.BaseObject.StartTime);
/// <summary>
/// Creates all <see cref="Mod"/> combinations which adjust the <see cref="Beatmap"/> difficulty.
/// Creates all <see cref="Mod"/> combinations which adjust the <see cref="Beatmaps.Beatmap"/> difficulty.
/// </summary>
public Mod[] CreateDifficultyAdjustmentModCombinations()
{
@ -154,14 +194,15 @@ namespace osu.Game.Rulesets.Difficulty
}
/// <summary>
/// Retrieves all <see cref="Mod"/>s which adjust the <see cref="Beatmap"/> difficulty.
/// Retrieves all <see cref="Mod"/>s which adjust the <see cref="Beatmaps.Beatmap"/> difficulty.
/// </summary>
protected virtual Mod[] DifficultyAdjustmentMods => Array.Empty<Mod>();
/// <summary>
/// Creates <see cref="DifficultyAttributes"/> to describe beatmap's calculated difficulty.
/// </summary>
/// <param name="beatmap">The <see cref="IBeatmap"/> whose difficulty was calculated.</param>
/// <param name="beatmap">The <see cref="IBeatmap"/> whose difficulty was calculated.
/// This may differ from <see cref="Beatmap"/> in the case of timed calculation.</param>
/// <param name="mods">The <see cref="Mod"/>s that difficulty was calculated with.</param>
/// <param name="skills">The skills which processed the beatmap.</param>
/// <param name="clockRate">The rate at which the gameplay clock is run at.</param>
@ -178,10 +219,51 @@ namespace osu.Game.Rulesets.Difficulty
/// <summary>
/// Creates the <see cref="Skill"/>s to calculate the difficulty of an <see cref="IBeatmap"/>.
/// </summary>
/// <param name="beatmap">The <see cref="IBeatmap"/> whose difficulty will be calculated.</param>
/// <param name="beatmap">The <see cref="IBeatmap"/> whose difficulty will be calculated.
/// This may differ from <see cref="Beatmap"/> in the case of timed calculation.</param>
/// <param name="mods">Mods to calculate difficulty with.</param>
/// <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);
/// <summary>
/// Used to calculate timed difficulty attributes, where only a subset of hitobjects should be visible at any point in time.
/// </summary>
private class ProgressiveCalculationBeatmap : IBeatmap
{
private readonly IBeatmap baseBeatmap;
public ProgressiveCalculationBeatmap(IBeatmap baseBeatmap)
{
this.baseBeatmap = baseBeatmap;
}
public readonly List<HitObject> HitObjects = new List<HitObject>();
IReadOnlyList<HitObject> IBeatmap.HitObjects => HitObjects;
#region Delegated IBeatmap implementation
public BeatmapInfo BeatmapInfo
{
get => baseBeatmap.BeatmapInfo;
set => baseBeatmap.BeatmapInfo = value;
}
public ControlPointInfo ControlPointInfo
{
get => baseBeatmap.ControlPointInfo;
set => baseBeatmap.ControlPointInfo = value;
}
public BeatmapMetadata Metadata => baseBeatmap.Metadata;
public List<BreakPeriod> Breaks => baseBeatmap.Breaks;
public double TotalBreakTime => baseBeatmap.TotalBreakTime;
public IEnumerable<BeatmapStatistic> GetStatistics() => baseBeatmap.GetStatistics();
public double GetMostCommonBeatLength() => baseBeatmap.GetMostCommonBeatLength();
public IBeatmap Clone() => new ProgressiveCalculationBeatmap(baseBeatmap.Clone());
#endregion
}
}
}

View File

@ -0,0 +1,25 @@
// 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;
namespace osu.Game.Rulesets.Difficulty
{
/// <summary>
/// Wraps a <see cref="DifficultyAttributes"/> object and adds a time value for which the attribute is valid.
/// Output by <see cref="DifficultyCalculator.CalculateTimed"/>.
/// </summary>
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);
}
}

View File

@ -18,6 +18,11 @@ namespace osu.Game.Rulesets.Scoring
/// </summary>
public event Action<JudgementResult> NewJudgement;
/// <summary>
/// Invoked when a judgement is reverted, usually due to rewinding gameplay.
/// </summary>
public event Action<JudgementResult> JudgementReverted;
/// <summary>
/// The maximum number of hits that can be judged.
/// </summary>
@ -71,6 +76,8 @@ namespace osu.Game.Rulesets.Scoring
JudgedHits--;
RevertResultInternal(result);
JudgementReverted?.Invoke(result);
}
/// <summary>

View File

@ -1,12 +1,14 @@
// 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 osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
#nullable enable
@ -30,7 +32,12 @@ namespace osu.Game.Screens.Play
/// <summary>
/// The mods applied to the gameplay.
/// </summary>
public IReadOnlyList<Mod> Mods;
public readonly IReadOnlyList<Mod> Mods;
/// <summary>
/// The gameplay score.
/// </summary>
public readonly Score Score;
/// <summary>
/// A bindable tracking the last judgement result applied to any hit object.
@ -39,11 +46,12 @@ namespace osu.Game.Screens.Play
private readonly Bindable<JudgementResult> lastJudgementResult = new Bindable<JudgementResult>();
public GameplayState(IBeatmap beatmap, Ruleset ruleset, IReadOnlyList<Mod> mods)
public GameplayState(IBeatmap beatmap, Ruleset ruleset, IReadOnlyList<Mod>? mods = null, Score? score = null)
{
Beatmap = beatmap;
Ruleset = ruleset;
Mods = mods;
Score = score ?? new Score();
Mods = mods ?? ArraySegment<Mod>.Empty;
}
/// <summary>

View File

@ -0,0 +1,219 @@
// 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 System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
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.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;
using osuTK;
namespace osu.Game.Screens.Play.HUD
{
public class PerformancePointsCounter : RollingCounter<int>, ISkinnableDrawable
{
public bool UsesFixedAnchor { get; set; }
protected override bool IsRollingProportional => true;
protected override double RollingDuration => 1000;
private const float alpha_when_invalid = 0.3f;
[CanBeNull]
[Resolved(CanBeNull = true)]
private ScoreProcessor scoreProcessor { get; set; }
[Resolved(CanBeNull = true)]
[CanBeNull]
private GameplayState gameplayState { get; set; }
[CanBeNull]
private List<TimedDifficultyAttributes> timedAttributes;
private readonly CancellationTokenSource loadCancellationSource = new CancellationTokenSource();
private JudgementResult lastJudgement;
public PerformancePointsCounter()
{
Current.Value = DisplayedCount = 0;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours, BeatmapDifficultyCache difficultyCache)
{
Colour = colours.BlueLighter;
if (gameplayState != null)
{
var gameplayWorkingBeatmap = new GameplayWorkingBeatmap(gameplayState.Beatmap);
difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, gameplayState.Mods.ToArray(), loadCancellationSource.Token)
.ContinueWith(r => Schedule(() =>
{
timedAttributes = r.Result;
IsValid = true;
if (lastJudgement != null)
onJudgementChanged(lastJudgement);
}), TaskContinuationOptions.OnlyOnRanToCompletion);
}
}
protected override void LoadComplete()
{
base.LoadComplete();
if (scoreProcessor != null)
{
scoreProcessor.NewJudgement += onJudgementChanged;
scoreProcessor.JudgementReverted += onJudgementChanged;
}
}
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)
{
IsValid = false;
return;
}
var calculator = gameplayState.Ruleset.CreatePerformanceCalculator(attrib, gameplayState.Score.ScoreInfo);
Current.Value = (int)Math.Round(calculator?.Calculate() ?? 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;
loadCancellationSource?.Cancel();
}
private class TextComponent : CompositeDrawable, IHasText
{
public LocalisableString Text
{
get => text.Text;
set => text.Text = value;
}
private readonly OsuSpriteText text;
public TextComponent()
{
AutoSizeAxes = Axes.Both;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(2),
Children = new Drawable[]
{
text = new OsuSpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Font = OsuFont.Numeric.With(size: 16)
},
new OsuSpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Text = @"pp",
Font = OsuFont.Numeric.With(size: 8),
Padding = new MarginPadding { Bottom = 1.5f }, // align baseline better
}
}
};
}
}
// 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(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

@ -161,13 +161,6 @@ namespace osu.Game.Screens.Play
if (!LoadedBeatmapSuccessfully)
return;
Score = CreateScore();
// ensure the score is in a consistent state with the current player.
Score.ScoreInfo.BeatmapInfo = Beatmap.Value.BeatmapInfo;
Score.ScoreInfo.Ruleset = ruleset.RulesetInfo;
Score.ScoreInfo.Mods = Mods.Value.ToArray();
PrepareReplay();
ScoreProcessor.NewJudgement += result => ScoreProcessor.PopulateScore(Score.ScoreInfo);
@ -225,7 +218,14 @@ namespace osu.Game.Screens.Play
InternalChild = GameplayClockContainer = CreateGameplayClockContainer(Beatmap.Value, DrawableRuleset.GameplayStartTime);
dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, Mods.Value));
Score = CreateScore(playableBeatmap);
// ensure the score is in a consistent state with the current player.
Score.ScoreInfo.BeatmapInfo = Beatmap.Value.BeatmapInfo;
Score.ScoreInfo.Ruleset = ruleset.RulesetInfo;
Score.ScoreInfo.Mods = Mods.Value.ToArray();
dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, Mods.Value, Score));
AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer));
@ -988,8 +988,9 @@ namespace osu.Game.Screens.Play
/// <summary>
/// Creates the player's <see cref="Scoring.Score"/>.
/// </summary>
/// <param name="beatmap"></param>
/// <returns>The <see cref="Scoring.Score"/>.</returns>
protected virtual Score CreateScore() => new Score
protected virtual Score CreateScore(IBeatmap beatmap) => new Score
{
ScoreInfo = new ScoreInfo { User = api.LocalUser.Value },
};

View File

@ -41,7 +41,7 @@ namespace osu.Game.Screens.Play
DrawableRuleset?.SetReplayScore(Score);
}
protected override Score CreateScore() => createScore(GameplayState.Beatmap, Mods.Value);
protected override Score CreateScore(IBeatmap beatmap) => createScore(beatmap, Mods.Value);
// Don't re-import replay scores as they're already present in the database.
protected override Task ImportScore(Score score) => Task.CompletedTask;

View File

@ -4,6 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Spectator;
@ -79,7 +80,7 @@ namespace osu.Game.Screens.Play
NonFrameStableSeek(score.Replay.Frames[0].Time);
}
protected override Score CreateScore() => score;
protected override Score CreateScore(IBeatmap beatmap) => score;
protected override ResultsScreen CreateResults(ScoreInfo score)
=> new SpectatorResultsScreen(score);

View File

@ -68,6 +68,7 @@ namespace osu.Game.Skinning
var score = container.OfType<DefaultScoreCounter>().FirstOrDefault();
var accuracy = container.OfType<DefaultAccuracyCounter>().FirstOrDefault();
var combo = container.OfType<DefaultComboCounter>().FirstOrDefault();
var ppCounter = container.OfType<PerformancePointsCounter>().FirstOrDefault();
if (score != null)
{
@ -81,6 +82,13 @@ namespace osu.Game.Skinning
score.Position = new Vector2(0, vertical_offset);
if (ppCounter != null)
{
ppCounter.Y = score.Position.Y + ppCounter.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).Y - 4;
ppCounter.Origin = Anchor.TopCentre;
ppCounter.Anchor = Anchor.TopCentre;
}
if (accuracy != null)
{
accuracy.Position = new Vector2(-accuracy.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 - horizontal_padding, vertical_offset + 5);
@ -123,6 +131,7 @@ namespace osu.Game.Skinning
new SongProgress(),
new BarHitErrorMeter(),
new BarHitErrorMeter(),
new PerformancePointsCounter()
}
};