mirror of
https://github.com/ppy/osu.git
synced 2024-11-16 14:17:27 +08:00
214 lines
8.6 KiB
C#
214 lines
8.6 KiB
C#
// 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.Framework.Extensions.ObjectExtensions;
|
|
using osu.Framework.Extensions.TypeExtensions;
|
|
using osu.Framework.Graphics;
|
|
using osu.Game.Beatmaps;
|
|
using osu.Game.Rulesets.Judgements;
|
|
using osu.Game.Rulesets.Objects;
|
|
using osu.Game.Rulesets.Replays;
|
|
|
|
namespace osu.Game.Rulesets.Scoring
|
|
{
|
|
public abstract partial class JudgementProcessor : Component
|
|
{
|
|
/// <summary>
|
|
/// Invoked when a new judgement has occurred. This occurs after the judgement has been processed by this <see cref="JudgementProcessor"/>.
|
|
/// </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>
|
|
protected int MaxHits { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Whether <see cref="SimulateAutoplay"/> is currently running.
|
|
/// </summary>
|
|
protected bool IsSimulating { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The total number of judged <see cref="HitObject"/>s at the current point in time.
|
|
/// </summary>
|
|
public int JudgedHits { get; private set; }
|
|
|
|
private JudgementResult? lastAppliedResult;
|
|
|
|
private readonly BindableBool hasCompleted = new BindableBool();
|
|
|
|
/// <summary>
|
|
/// Whether all <see cref="Judgement"/>s have been processed.
|
|
/// </summary>
|
|
public IBindable<bool> HasCompleted => hasCompleted;
|
|
|
|
/// <summary>
|
|
/// Applies a <see cref="IBeatmap"/> to this <see cref="ScoreProcessor"/>.
|
|
/// </summary>
|
|
/// <param name="beatmap">The <see cref="IBeatmap"/> to read properties from.</param>
|
|
public virtual void ApplyBeatmap(IBeatmap beatmap)
|
|
{
|
|
Reset(false);
|
|
SimulateAutoplay(beatmap);
|
|
Reset(true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies the score change of a <see cref="JudgementResult"/> to this <see cref="ScoreProcessor"/>.
|
|
/// </summary>
|
|
/// <param name="result">The <see cref="JudgementResult"/> to apply.</param>
|
|
public void ApplyResult(JudgementResult result)
|
|
{
|
|
#pragma warning disable CS0618
|
|
if (result.Type == HitResult.LegacyComboIncrease)
|
|
throw new ArgumentException(@$"A {nameof(HitResult.LegacyComboIncrease)} hit result cannot be applied.");
|
|
#pragma warning restore CS0618
|
|
|
|
JudgedHits++;
|
|
lastAppliedResult = result;
|
|
|
|
ApplyResultInternal(result);
|
|
|
|
NewJudgement?.Invoke(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reverts the score change of a <see cref="JudgementResult"/> that was applied to this <see cref="ScoreProcessor"/>.
|
|
/// </summary>
|
|
/// <param name="result">The judgement scoring result.</param>
|
|
public void RevertResult(JudgementResult result)
|
|
{
|
|
JudgedHits--;
|
|
|
|
RevertResultInternal(result);
|
|
|
|
JudgementReverted?.Invoke(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies the score change of a <see cref="JudgementResult"/> to this <see cref="ScoreProcessor"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Any changes applied via this method can be reverted via <see cref="RevertResultInternal"/>.
|
|
/// </remarks>
|
|
/// <param name="result">The <see cref="JudgementResult"/> to apply.</param>
|
|
protected abstract void ApplyResultInternal(JudgementResult result);
|
|
|
|
/// <summary>
|
|
/// Reverts the score change of a <see cref="JudgementResult"/> that was applied to this <see cref="ScoreProcessor"/> via <see cref="ApplyResultInternal"/>.
|
|
/// </summary>
|
|
/// <param name="result">The judgement scoring result.</param>
|
|
protected abstract void RevertResultInternal(JudgementResult result);
|
|
|
|
/// <summary>
|
|
/// Resets this <see cref="JudgementProcessor"/> to a default state.
|
|
/// </summary>
|
|
/// <param name="storeResults">Whether to store the current state of the <see cref="JudgementProcessor"/> for future use.</param>
|
|
protected virtual void Reset(bool storeResults)
|
|
{
|
|
if (storeResults)
|
|
MaxHits = JudgedHits;
|
|
|
|
JudgedHits = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reset all statistics based on header information contained within a replay frame.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// If the provided replay frame does not have any header information, this will be a noop.
|
|
/// </remarks>
|
|
/// <param name="frame">The replay frame to read header statistics from.</param>
|
|
public virtual void ResetFromReplayFrame(ReplayFrame frame)
|
|
{
|
|
if (frame.Header == null)
|
|
return;
|
|
|
|
JudgedHits = 0;
|
|
|
|
foreach ((_, int count) in frame.Header.Statistics)
|
|
JudgedHits += count;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Simulates an autoplay of the <see cref="IBeatmap"/> to determine scoring values.
|
|
/// </summary>
|
|
/// <remarks>This provided temporarily. DO NOT USE.</remarks>
|
|
/// <param name="beatmap">The <see cref="IBeatmap"/> to simulate.</param>
|
|
protected void SimulateAutoplay(IBeatmap beatmap)
|
|
{
|
|
IsSimulating = true;
|
|
|
|
foreach (var obj in EnumerateHitObjects(beatmap))
|
|
{
|
|
var judgement = obj.Judgement;
|
|
|
|
var result = CreateResult(obj, judgement);
|
|
if (result == null)
|
|
throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}.");
|
|
|
|
result.Type = GetSimulatedHitResult(judgement);
|
|
ApplyResult(result);
|
|
}
|
|
|
|
IsSimulating = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enumerates all <see cref="HitObject"/>s in the given <paramref name="beatmap"/> in the order in which they are to be judged.
|
|
/// Used in <see cref="SimulateAutoplay"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// In Score V2, the score awarded for each object includes a component based on the combo value after the judgement of that object.
|
|
/// This means that the score is dependent on the order of evaluation of judgements.
|
|
/// This method is provided so that rulesets can specify custom ordering that is correct for them and matches processing order during actual gameplay.
|
|
/// </remarks>
|
|
protected virtual IEnumerable<HitObject> EnumerateHitObjects(IBeatmap beatmap)
|
|
=> enumerateRecursively(beatmap.HitObjects);
|
|
|
|
private IEnumerable<HitObject> enumerateRecursively(IEnumerable<HitObject> hitObjects)
|
|
{
|
|
foreach (var hitObject in hitObjects)
|
|
{
|
|
foreach (var nested in enumerateRecursively(hitObject.NestedHitObjects))
|
|
yield return nested;
|
|
|
|
yield return hitObject;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates the <see cref="JudgementResult"/> that represents the scoring result for a <see cref="HitObject"/>.
|
|
/// </summary>
|
|
/// <param name="hitObject">The <see cref="HitObject"/> which was judged.</param>
|
|
/// <param name="judgement">The <see cref="Judgement"/> that provides the scoring information.</param>
|
|
protected virtual JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new JudgementResult(hitObject, judgement);
|
|
|
|
/// <summary>
|
|
/// Gets a simulated <see cref="HitResult"/> for a judgement. Used during <see cref="SimulateAutoplay"/> to simulate a "perfect" play.
|
|
/// </summary>
|
|
/// <param name="judgement">The judgement to simulate a <see cref="HitResult"/> for.</param>
|
|
/// <returns>The simulated <see cref="HitResult"/> for the judgement.</returns>
|
|
protected virtual HitResult GetSimulatedHitResult(Judgement judgement) => judgement.MaxResult;
|
|
|
|
protected override void Update()
|
|
{
|
|
base.Update();
|
|
|
|
hasCompleted.Value =
|
|
JudgedHits == MaxHits
|
|
&& (JudgedHits == 0
|
|
// Last applied result is guaranteed to be non-null when JudgedHits > 0.
|
|
|| lastAppliedResult.AsNonNull().TimeAbsolute < Clock.CurrentTime);
|
|
}
|
|
}
|
|
}
|