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

Move hit events to the ScoreProcessor

This commit is contained in:
smoogipoo 2020-06-19 19:58:35 +09:00
parent ef56225d9a
commit eab00ec9d9
12 changed files with 106 additions and 124 deletions

View File

@ -10,10 +10,15 @@ namespace osu.Game.Rulesets.Osu.Judgements
{ {
public class OsuHitCircleJudgementResult : OsuJudgementResult public class OsuHitCircleJudgementResult : OsuJudgementResult
{ {
/// <summary>
/// The <see cref="HitCircle"/>.
/// </summary>
public HitCircle HitCircle => (HitCircle)HitObject; public HitCircle HitCircle => (HitCircle)HitObject;
public Vector2? HitPosition; /// <summary>
public float? Radius; /// The position of the player's cursor when <see cref="HitCircle"/> was hit.
/// </summary>
public Vector2? CursorPositionAtHit;
public OsuHitCircleJudgementResult(HitObject hitObject, Judgement judgement) public OsuHitCircleJudgementResult(HitObject hitObject, Judgement judgement)
: base(hitObject, judgement) : base(hitObject, judgement)

View File

@ -142,11 +142,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
var circleResult = (OsuHitCircleJudgementResult)r; var circleResult = (OsuHitCircleJudgementResult)r;
// Todo: This should also consider misses, but they're a little more interesting to handle, since we don't necessarily know the position at the time of a miss.
if (result != HitResult.Miss) if (result != HitResult.Miss)
{ {
var localMousePosition = ToLocalSpace(inputManager.CurrentState.Mouse.Position); var localMousePosition = ToLocalSpace(inputManager.CurrentState.Mouse.Position);
circleResult.HitPosition = HitObject.StackedPosition + (localMousePosition - DrawSize / 2); circleResult.CursorPositionAtHit = HitObject.StackedPosition + (localMousePosition - DrawSize / 2);
circleResult.Radius = (float)HitObject.Radius;
} }
circleResult.Type = result; circleResult.Type = result;

View File

@ -29,7 +29,6 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
using System; using System;
using System.Linq;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Rulesets.Osu.Statistics;
using osu.Game.Screens.Ranking.Statistics; using osu.Game.Screens.Ranking.Statistics;
@ -201,7 +200,7 @@ namespace osu.Game.Rulesets.Osu
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = 130, Height = 130,
Child = new TimingDistributionGraph(score.HitEvents.Cast<HitEvent>().ToList()) Child = new TimingDistributionGraph(score)
{ {
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both
} }
@ -209,7 +208,7 @@ namespace osu.Game.Rulesets.Osu
new StatisticContainer("Accuracy Heatmap") new StatisticContainer("Accuracy Heatmap")
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Child = new Heatmap(score.Beatmap, score.HitEvents.Cast<HitEvent>().ToList()) Child = new Heatmap(score)
{ {
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both
} }

View File

@ -1,54 +1,16 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osuTK;
namespace osu.Game.Rulesets.Osu.Scoring namespace osu.Game.Rulesets.Osu.Scoring
{ {
public class OsuScoreProcessor : ScoreProcessor public class OsuScoreProcessor : ScoreProcessor
{ {
private readonly List<HitEvent> hitEvents = new List<HitEvent>();
private HitObject lastHitObject;
protected override void OnResultApplied(JudgementResult result)
{
base.OnResultApplied(result);
hitEvents.Add(new HitEvent(result.TimeOffset, result.Type, result.HitObject, lastHitObject, (result as OsuHitCircleJudgementResult)?.HitPosition));
lastHitObject = result.HitObject;
}
protected override void OnResultReverted(JudgementResult result)
{
base.OnResultReverted(result);
hitEvents.RemoveAt(hitEvents.Count - 1);
}
protected override void Reset(bool storeResults)
{
base.Reset(storeResults);
hitEvents.Clear();
lastHitObject = null;
}
public override void PopulateScore(ScoreInfo score)
{
base.PopulateScore(score);
score.HitEvents.AddRange(hitEvents.Select(e => e).Cast<object>());
}
protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement)
{ {
switch (hitObject) switch (hitObject)
@ -63,42 +25,4 @@ namespace osu.Game.Rulesets.Osu.Scoring
public override HitWindows CreateHitWindows() => new OsuHitWindows(); public override HitWindows CreateHitWindows() => new OsuHitWindows();
} }
public readonly struct HitEvent
{
/// <summary>
/// The time offset from the end of <see cref="HitObject"/> at which the event occurred.
/// </summary>
public readonly double TimeOffset;
/// <summary>
/// The hit result.
/// </summary>
public readonly HitResult Result;
/// <summary>
/// The <see cref="HitObject"/> on which the result occurred.
/// </summary>
public readonly HitObject HitObject;
/// <summary>
/// The <see cref="HitObject"/> occurring prior to <see cref="HitObject"/>.
/// </summary>
[CanBeNull]
public readonly HitObject LastHitObject;
/// <summary>
/// The player's position offset, if available, at the time of the event.
/// </summary>
public readonly Vector2? PositionOffset;
public HitEvent(double timeOffset, HitResult result, HitObject hitObject, [CanBeNull] HitObject lastHitObject, Vector2? positionOffset)
{
TimeOffset = timeOffset;
Result = result;
HitObject = hitObject;
LastHitObject = lastHitObject;
PositionOffset = positionOffset;
}
}
} }

View File

@ -2,17 +2,14 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Layout;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Scoring;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -36,16 +33,11 @@ namespace osu.Game.Rulesets.Osu.Statistics
private GridContainer pointGrid; private GridContainer pointGrid;
private readonly BeatmapInfo beatmap; private readonly ScoreInfo score;
private readonly IReadOnlyList<HitEvent> hitEvents;
private readonly LayoutValue sizeLayout = new LayoutValue(Invalidation.DrawSize);
public Heatmap(BeatmapInfo beatmap, IReadOnlyList<HitEvent> hitEvents) public Heatmap(ScoreInfo score)
{ {
this.beatmap = beatmap; this.score = score;
this.hitEvents = hitEvents;
AddLayout(sizeLayout);
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -149,12 +141,13 @@ namespace osu.Game.Rulesets.Osu.Statistics
pointGrid.Content = points; pointGrid.Content = points;
if (hitEvents.Count > 0) if (score.HitEvents == null || score.HitEvents.Count == 0)
{ return;
// Todo: This should probably not be done like this.
float radius = OsuHitObject.OBJECT_RADIUS * (1.0f - 0.7f * (beatmap.BaseDifficulty.CircleSize - 5) / 5) / 2;
foreach (var e in hitEvents) // Todo: This should probably not be done like this.
float radius = OsuHitObject.OBJECT_RADIUS * (1.0f - 0.7f * (score.Beatmap.BaseDifficulty.CircleSize - 5) / 5) / 2;
foreach (var e in score.HitEvents)
{ {
if (e.LastHitObject == null || e.PositionOffset == null) if (e.LastHitObject == null || e.PositionOffset == null)
continue; continue;
@ -162,7 +155,6 @@ namespace osu.Game.Rulesets.Osu.Statistics
AddPoint(((OsuHitObject)e.LastHitObject).StackedEndPosition, ((OsuHitObject)e.HitObject).StackedEndPosition, e.PositionOffset.Value, radius); AddPoint(((OsuHitObject)e.LastHitObject).StackedEndPosition, ((OsuHitObject)e.HitObject).StackedEndPosition, e.PositionOffset.Value, radius);
} }
} }
}
protected void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius) protected void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius)
{ {

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
@ -11,7 +10,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Scoring;
namespace osu.Game.Rulesets.Osu.Statistics namespace osu.Game.Rulesets.Osu.Statistics
{ {
@ -37,23 +36,23 @@ namespace osu.Game.Rulesets.Osu.Statistics
/// </summary> /// </summary>
private const float axis_points = 5; private const float axis_points = 5;
private readonly List<HitEvent> hitEvents; private readonly ScoreInfo score;
public TimingDistributionGraph(List<HitEvent> hitEvents) public TimingDistributionGraph(ScoreInfo score)
{ {
this.hitEvents = hitEvents; this.score = score;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
if (hitEvents.Count == 0) if (score.HitEvents == null || score.HitEvents.Count == 0)
return; return;
int[] bins = new int[total_timing_distribution_bins]; int[] bins = new int[total_timing_distribution_bins];
double binSize = hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins; double binSize = score.HitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins;
foreach (var e in hitEvents) foreach (var e in score.HitEvents)
{ {
int binOffset = (int)(e.TimeOffset / binSize); int binOffset = (int)(e.TimeOffset / binSize);
bins[timing_distribution_centre_bin_index + binOffset]++; bins[timing_distribution_centre_bin_index + binOffset]++;

View File

@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -10,10 +9,9 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Rulesets.Osu.Statistics;
using osu.Game.Scoring;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -50,7 +48,7 @@ namespace osu.Game.Tests.Visual.Ranking
{ {
Position = new Vector2(100, 300), Position = new Vector2(100, 300),
}, },
heatmap = new TestHeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, new List<HitEvent>()) heatmap = new TestHeatmap(new ScoreInfo { Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo })
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
@ -93,8 +91,8 @@ namespace osu.Game.Tests.Visual.Ranking
private class TestHeatmap : Heatmap private class TestHeatmap : Heatmap
{ {
public TestHeatmap(BeatmapInfo beatmap, List<HitEvent> events) public TestHeatmap(ScoreInfo score)
: base(beatmap, events) : base(score)
{ {
} }

View File

@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -18,7 +17,7 @@ namespace osu.Game.Tests.Visual.Ranking
{ {
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) var score = new TestScoreInfo(new OsuRuleset().RulesetInfo)
{ {
HitEvents = TestSceneTimingDistributionGraph.CreateDistributedHitEvents().Cast<object>().ToList(), HitEvents = TestSceneTimingDistributionGraph.CreateDistributedHitEvents()
}; };
loadPanel(score); loadPanel(score);

View File

@ -7,9 +7,9 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Rulesets.Osu.Statistics;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osuTK; using osuTK;
namespace osu.Game.Tests.Visual.Ranking namespace osu.Game.Tests.Visual.Ranking
@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Ranking
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#333") Colour = Color4Extensions.FromHex("#333")
}, },
new TimingDistributionGraph(CreateDistributedHitEvents()) new TimingDistributionGraph(new ScoreInfo { HitEvents = CreateDistributedHitEvents() })
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,

View File

@ -0,0 +1,48 @@
// 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 JetBrains.Annotations;
using osu.Game.Rulesets.Objects;
using osuTK;
namespace osu.Game.Rulesets.Scoring
{
public readonly struct HitEvent
{
/// <summary>
/// The time offset from the end of <see cref="HitObject"/> at which the event occurred.
/// </summary>
public readonly double TimeOffset;
/// <summary>
/// The hit result.
/// </summary>
public readonly HitResult Result;
/// <summary>
/// The <see cref="HitObject"/> on which the result occurred.
/// </summary>
public readonly HitObject HitObject;
/// <summary>
/// The <see cref="HitObject"/> occurring prior to <see cref="HitObject"/>.
/// </summary>
[CanBeNull]
public readonly HitObject LastHitObject;
/// <summary>
/// The player's position offset, if available, at the time of the event.
/// </summary>
[CanBeNull]
public readonly Vector2? PositionOffset;
public HitEvent(double timeOffset, HitResult result, HitObject hitObject, [CanBeNull] HitObject lastHitObject, [CanBeNull] Vector2? positionOffset)
{
TimeOffset = timeOffset;
Result = result;
HitObject = hitObject;
LastHitObject = lastHitObject;
PositionOffset = positionOffset;
}
}
}

View File

@ -9,6 +9,7 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Scoring; using osu.Game.Scoring;
namespace osu.Game.Rulesets.Scoring namespace osu.Game.Rulesets.Scoring
@ -61,6 +62,9 @@ namespace osu.Game.Rulesets.Scoring
private double baseScore; private double baseScore;
private double bonusScore; private double bonusScore;
private readonly List<HitEvent> hitEvents = new List<HitEvent>();
private HitObject lastHitObject;
private double scoreMultiplier = 1; private double scoreMultiplier = 1;
public ScoreProcessor() public ScoreProcessor()
@ -128,6 +132,9 @@ namespace osu.Game.Rulesets.Scoring
rollingMaxBaseScore += result.Judgement.MaxNumericResult; rollingMaxBaseScore += result.Judgement.MaxNumericResult;
} }
hitEvents.Add(CreateHitEvent(result));
lastHitObject = result.HitObject;
updateScore(); updateScore();
OnResultApplied(result); OnResultApplied(result);
@ -137,6 +144,9 @@ namespace osu.Game.Rulesets.Scoring
{ {
} }
protected virtual HitEvent CreateHitEvent(JudgementResult result)
=> new HitEvent(result.TimeOffset, result.Type, result.HitObject, lastHitObject, null);
protected sealed override void RevertResultInternal(JudgementResult result) protected sealed override void RevertResultInternal(JudgementResult result)
{ {
Combo.Value = result.ComboAtJudgement; Combo.Value = result.ComboAtJudgement;
@ -159,6 +169,10 @@ namespace osu.Game.Rulesets.Scoring
rollingMaxBaseScore -= result.Judgement.MaxNumericResult; rollingMaxBaseScore -= result.Judgement.MaxNumericResult;
} }
Debug.Assert(hitEvents.Count > 0);
lastHitObject = hitEvents[^1].LastHitObject;
hitEvents.RemoveAt(hitEvents.Count - 1);
updateScore(); updateScore();
OnResultReverted(result); OnResultReverted(result);
@ -219,6 +233,8 @@ namespace osu.Game.Rulesets.Scoring
base.Reset(storeResults); base.Reset(storeResults);
scoreResultCounts.Clear(); scoreResultCounts.Clear();
hitEvents.Clear();
lastHitObject = null;
if (storeResults) if (storeResults)
{ {
@ -259,6 +275,8 @@ namespace osu.Game.Rulesets.Scoring
foreach (var result in Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) foreach (var result in Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r)))
score.Statistics[result] = GetStatistic(result); score.Statistics[result] = GetStatistic(result);
score.HitEvents = new List<HitEvent>(hitEvents);
} }
/// <summary> /// <summary>

View File

@ -168,7 +168,7 @@ namespace osu.Game.Scoring
[NotMapped] [NotMapped]
[JsonIgnore] [JsonIgnore]
public List<object> HitEvents = new List<object>(); public List<HitEvent> HitEvents { get; set; }
[JsonIgnore] [JsonIgnore]
public List<ScoreFileInfo> Files { get; set; } public List<ScoreFileInfo> Files { get; set; }