2019-12-19 18:02:11 +08:00
// 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 ;
2023-06-15 02:15:43 +08:00
using System.Collections.Generic ;
2020-04-19 10:36:04 +08:00
using osu.Framework.Bindables ;
2022-03-14 16:10:37 +08:00
using osu.Framework.Extensions.ObjectExtensions ;
2019-12-19 18:02:11 +08:00
using osu.Framework.Extensions.TypeExtensions ;
2019-12-25 13:35:32 +08:00
using osu.Framework.Graphics ;
2019-12-19 18:02:11 +08:00
using osu.Game.Beatmaps ;
using osu.Game.Rulesets.Judgements ;
using osu.Game.Rulesets.Objects ;
2022-01-31 17:54:23 +08:00
using osu.Game.Rulesets.Replays ;
2019-12-19 18:02:11 +08:00
namespace osu.Game.Rulesets.Scoring
{
2019-12-25 13:35:32 +08:00
public abstract partial class JudgementProcessor : Component
2019-12-19 18:02:11 +08:00
{
2019-12-19 19:18:17 +08:00
/// <summary>
/// Invoked when a new judgement has occurred. This occurs after the judgement has been processed by this <see cref="JudgementProcessor"/>.
/// </summary>
2022-03-14 14:38:00 +08:00
public event Action < JudgementResult > ? NewJudgement ;
2019-12-19 19:18:17 +08:00
2021-10-05 14:39:29 +08:00
/// <summary>
/// Invoked when a judgement is reverted, usually due to rewinding gameplay.
/// </summary>
2022-03-14 14:38:00 +08:00
public event Action < JudgementResult > ? JudgementReverted ;
2021-10-05 14:39:29 +08:00
2019-12-19 18:02:11 +08:00
/// <summary>
/// The maximum number of hits that can be judged.
/// </summary>
protected int MaxHits { get ; private set ; }
2023-06-13 01:05:11 +08:00
/// <summary>
/// Whether <see cref="SimulateAutoplay"/> is currently running.
/// </summary>
protected bool IsSimulating { get ; private set ; }
2019-12-19 18:02:11 +08:00
/// <summary>
/// The total number of judged <see cref="HitObject"/>s at the current point in time.
/// </summary>
public int JudgedHits { get ; private set ; }
2022-03-14 14:38:00 +08:00
private JudgementResult ? lastAppliedResult ;
2021-04-13 13:29:47 +08:00
2020-04-19 10:36:04 +08:00
private readonly BindableBool hasCompleted = new BindableBool ( ) ;
2019-12-19 19:18:17 +08:00
/// <summary>
/// Whether all <see cref="Judgement"/>s have been processed.
/// </summary>
2020-04-19 10:36:04 +08:00
public IBindable < bool > HasCompleted = > hasCompleted ;
2019-12-19 19:18:17 +08:00
2019-12-19 18:02:11 +08:00
/// <summary>
2019-12-24 16:01:17 +08:00
/// Applies a <see cref="IBeatmap"/> to this <see cref="ScoreProcessor"/>.
2019-12-19 18:02:11 +08:00
/// </summary>
/// <param name="beatmap">The <see cref="IBeatmap"/> to read properties from.</param>
2019-12-25 12:32:58 +08:00
public virtual void ApplyBeatmap ( IBeatmap beatmap )
2019-12-19 18:02:11 +08:00
{
2019-12-24 16:01:17 +08:00
Reset ( false ) ;
SimulateAutoplay ( beatmap ) ;
Reset ( true ) ;
2019-12-19 18:02:11 +08:00
}
/// <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 )
{
2022-08-23 20:00:30 +08:00
#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
2019-12-19 18:02:11 +08:00
JudgedHits + + ;
2021-04-13 13:29:47 +08:00
lastAppliedResult = result ;
2019-12-19 18:02:11 +08:00
ApplyResultInternal ( result ) ;
2019-12-19 19:18:17 +08:00
NewJudgement ? . Invoke ( result ) ;
2019-12-19 18:02:11 +08:00
}
/// <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 ) ;
2021-10-05 14:39:29 +08:00
JudgementReverted ? . Invoke ( result ) ;
2019-12-19 18:02:11 +08:00
}
/// <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 ;
}
2022-02-01 15:52:53 +08:00
/// <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>
2022-05-31 16:16:23 +08:00
public virtual void ResetFromReplayFrame ( ReplayFrame frame )
2022-01-31 17:54:23 +08:00
{
if ( frame . Header = = null )
return ;
JudgedHits = 0 ;
foreach ( ( _ , int count ) in frame . Header . Statistics )
JudgedHits + = count ;
}
2019-12-19 18:02:11 +08:00
/// <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>
2023-06-15 02:08:14 +08:00
protected void SimulateAutoplay ( IBeatmap beatmap )
2019-12-19 18:02:11 +08:00
{
2023-06-13 01:05:11 +08:00
IsSimulating = true ;
2023-06-15 02:15:43 +08:00
foreach ( var obj in EnumerateHitObjects ( beatmap ) )
2019-12-19 18:02:11 +08:00
simulate ( obj ) ;
void simulate ( HitObject obj )
{
var judgement = obj . CreateJudgement ( ) ;
var result = CreateResult ( obj , judgement ) ;
if ( result = = null )
throw new InvalidOperationException ( $"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}." ) ;
2022-01-12 17:29:23 +08:00
result . Type = GetSimulatedHitResult ( judgement ) ;
2019-12-19 18:02:11 +08:00
ApplyResult ( result ) ;
}
2023-06-13 01:05:11 +08:00
IsSimulating = false ;
2019-12-19 18:02:11 +08:00
}
2020-04-20 11:40:51 +08:00
2023-06-15 02:15:43 +08:00
/// <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 ;
}
}
2023-06-15 02:10:34 +08:00
/// <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 ;
2021-04-13 13:29:47 +08:00
protected override void Update ( )
{
base . Update ( ) ;
2022-03-14 16:10:37 +08:00
hasCompleted . Value =
JudgedHits = = MaxHits
& & ( JudgedHits = = 0
// Last applied result is guaranteed to be non-null when JudgedHits > 0.
| | lastAppliedResult . AsNonNull ( ) . TimeAbsolute < Clock . CurrentTime ) ;
2021-04-13 13:29:47 +08:00
}
2019-12-19 18:02:11 +08:00
}
}