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 ;
2020-04-19 10:36:04 +08:00
using osu.Framework.Bindables ;
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 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>
public event Action < JudgementResult > NewJudgement ;
2021-10-05 14:39:29 +08:00
/// <summary>
/// Invoked when a judgement is reverted, usually due to rewinding gameplay.
/// </summary>
public event Action < JudgementResult > JudgementReverted ;
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 ; }
/// <summary>
/// The total number of judged <see cref="HitObject"/>s at the current point in time.
/// </summary>
public int JudgedHits { get ; private set ; }
2021-04-13 13:29:47 +08:00
private JudgementResult lastAppliedResult ;
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 )
{
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="ruleset">The ruleset to be used for retrieving statistics.</param>
/// <param name="frame">The replay frame to read header statistics from.</param>
2022-01-31 17:54:23 +08:00
public virtual void ResetFromReplayFrame ( Ruleset ruleset , ReplayFrame frame )
{
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>
/// 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>
/// 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 virtual void SimulateAutoplay ( IBeatmap beatmap )
{
foreach ( var obj in beatmap . HitObjects )
simulate ( obj ) ;
void simulate ( HitObject obj )
{
foreach ( var nested in obj . NestedHitObjects )
simulate ( nested ) ;
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 ) ;
}
}
2020-04-20 11:40:51 +08:00
2021-04-13 13:29:47 +08:00
protected override void Update ( )
{
base . Update ( ) ;
hasCompleted . Value = JudgedHits = = MaxHits & & ( JudgedHits = = 0 | | lastAppliedResult . TimeAbsolute < Clock . CurrentTime ) ;
}
2022-01-12 17:29:23 +08:00
/// <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 ;
2019-12-19 18:02:11 +08:00
}
}