Split out judgement definition from judgement result

This commit is contained in:
smoogipoo 2018-08-02 20:35:54 +09:00
parent cd70e5e30b
commit 3619290c34
7 changed files with 161 additions and 111 deletions

View File

@ -25,7 +25,7 @@ public class DrawableJudgement : Container
private OsuColour colours;
protected readonly Judgement Judgement;
protected readonly JudgementResult Result;
public readonly DrawableHitObject JudgedObject;
@ -34,11 +34,11 @@ public class DrawableJudgement : Container
/// <summary>
/// Creates a drawable which visualises a <see cref="Judgements.Judgement"/>.
/// </summary>
/// <param name="judgement">The judgement to visualise.</param>
/// <param name="result">The judgement to visualise.</param>
/// <param name="judgedObject">The object which was judged.</param>
public DrawableJudgement(Judgement judgement, DrawableHitObject judgedObject)
public DrawableJudgement(JudgementResult result, DrawableHitObject judgedObject)
{
Judgement = judgement;
Result = result;
JudgedObject = judgedObject;
Size = new Vector2(judgement_size);
@ -49,11 +49,11 @@ private void load(OsuColour colours)
{
this.colours = colours;
Child = new SkinnableDrawable($"Play/{Judgement.Result}", _ => JudgementText = new OsuSpriteText
Child = new SkinnableDrawable($"Play/{Result.Type}", _ => JudgementText = new OsuSpriteText
{
Text = Judgement.Result.GetDescription().ToUpperInvariant(),
Text = Result.Type.GetDescription().ToUpperInvariant(),
Font = @"Venera",
Colour = judgementColour(Judgement.Result),
Colour = judgementColour(Result.Type),
Scale = new Vector2(0.85f, 1),
TextSize = 12
}, restrictSize: false);
@ -65,7 +65,7 @@ protected override void LoadComplete()
this.FadeInFromZero(100, Easing.OutQuint);
switch (Judgement.Result)
switch (Result.Type)
{
case HitResult.None:
break;

View File

@ -1,74 +1,44 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Judgements
{
public class Judgement
{
/// <summary>
/// Whether this judgement is the result of a hit or a miss.
/// </summary>
public HitResult Result;
/// <summary>
/// The maximum <see cref="HitResult"/> that can be achieved.
/// </summary>
public virtual HitResult MaxResult => HitResult.Perfect;
/// <summary>
/// The combo prior to this judgement occurring.
/// </summary>
public int ComboAtJudgement;
/// <summary>
/// The highest combo achieved prior to this judgement occurring.
/// </summary>
public int HighestComboAtJudgement;
/// <summary>
/// Whether this <see cref="Judgement"/> has a result.
/// </summary>
public bool HasResult => Result > HitResult.None;
/// <summary>
/// Whether a successful hit occurred.
/// </summary>
public bool IsHit => Result > HitResult.Miss;
/// <summary>
/// The offset from a perfect hit at which this judgement occurred.
/// Populated when added via <see cref="DrawableHitObject.ApplyJudgement"/>.
/// </summary>
public double TimeOffset { get; set; }
/// <summary>
/// Whether the <see cref="Result"/> should affect the current combo.
/// Whether this <see cref="Judgement"/> should affect the current combo.
/// </summary>
public virtual bool AffectsCombo => true;
/// <summary>
/// Whether the <see cref="Result"/> should be counted as base (combo) or bonus score.
/// Whether this <see cref="Judgement"/> should be counted as base (combo) or bonus score.
/// </summary>
public virtual bool IsBonus => !AffectsCombo;
/// <summary>
/// The numeric representation for the result achieved.
/// </summary>
public int NumericResult => NumericResultFor(Result);
/// <summary>
/// The numeric representation for the maximum achievable result.
/// The numeric score representation for the maximum achievable result.
/// </summary>
public int MaxNumericResult => NumericResultFor(MaxResult);
/// <summary>
/// Convert a <see cref="HitResult"/> to a numeric score representation.
/// Retrieves the numeric score representation of a <see cref="HitResult"/>.
/// </summary>
/// <param name="result">The value to convert.</param>
/// <returns>The number.</returns>
/// <param name="result">The <see cref="HitResult"/> to find the numeric score representation for.</param>
/// <returns>The numeric score representation of <paramref name="result"/>.</returns>
protected virtual int NumericResultFor(HitResult result) => result > HitResult.Miss ? 1 : 0;
/// <summary>
/// Retrieves the numeric score representation of a <see cref="JudgementResult"/>.
/// </summary>
/// <param name="result">The <see cref="JudgementResult"/> to find the numeric score representation for.</param>
/// <returns>The numeric score representation of <paramref name="result"/>.</returns>
public int NumericResultFor(JudgementResult result) => NumericResultFor(result.Type);
}
}

View File

@ -0,0 +1,51 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Judgements
{
public class JudgementResult
{
/// <summary>
/// Whether this <see cref="JudgementResult"/> is the result of a hit or a miss.
/// </summary>
public HitResult Type;
/// <summary>
/// The <see cref="Judgement"/> which this <see cref="JudgementResult"/> applies for.
/// </summary>
public readonly Judgement Judgement;
/// <summary>
/// The offset from a perfect hit at which this <see cref="JudgementResult"/> occurred.
/// Populated when added via <see cref="DrawableHitObject.ApplyJudgement"/>.
/// </summary>
public double TimeOffset { get; internal set; }
/// <summary>
/// The combo prior to this judgement occurring.
/// </summary>
public int ComboAtJudgement { get; internal set; }
/// <summary>
/// The highest combo achieved prior to this judgement occurring.
/// </summary>
public int HighestComboAtJudgement { get; internal set; }
/// <summary>
/// Whether this <see cref="Judgement"/> has a result.
/// </summary>
public bool HasResult => Type > HitResult.None;
/// <summary>
/// Whether a successful hit occurred.
/// </summary>
public bool IsHit => Type > HitResult.Miss;
public JudgementResult(Judgement judgement)
{
Judgement = judgement;
}
}
}

View File

@ -35,8 +35,8 @@ public abstract class DrawableHitObject : SkinReloadableDrawable, IHasAccentColo
private readonly Lazy<List<DrawableHitObject>> nestedHitObjects = new Lazy<List<DrawableHitObject>>();
public IEnumerable<DrawableHitObject> NestedHitObjects => nestedHitObjects.IsValueCreated ? nestedHitObjects.Value : Enumerable.Empty<DrawableHitObject>();
public event Action<DrawableHitObject, Judgement> OnJudgement;
public event Action<DrawableHitObject, Judgement> OnJudgementRemoved;
public event Action<DrawableHitObject, JudgementResult> OnJudgement;
public event Action<DrawableHitObject, JudgementResult> OnJudgementRemoved;
/// <summary>
/// Whether a visible judgement should be displayed when this representation is hit.
@ -46,7 +46,7 @@ public abstract class DrawableHitObject : SkinReloadableDrawable, IHasAccentColo
/// <summary>
/// Whether this <see cref="DrawableHitObject"/> and all of its nested <see cref="DrawableHitObject"/>s have been hit.
/// </summary>
public bool IsHit => HitObject.Judgements.All(j => j.IsHit) && NestedHitObjects.All(n => n.IsHit);
public bool IsHit => Results.All(j => j.IsHit) && NestedHitObjects.All(n => n.IsHit);
/// <summary>
/// Whether this <see cref="DrawableHitObject"/> and all of its nested <see cref="DrawableHitObject"/>s have been judged.
@ -57,7 +57,10 @@ public abstract class DrawableHitObject : SkinReloadableDrawable, IHasAccentColo
/// Whether this <see cref="DrawableHitObject"/> has been judged.
/// Note: This does NOT include nested hitobjects.
/// </summary>
public bool Judged => HitObject.Judgements.All(h => h.HasResult);
public bool Judged => Results.All(h => h.HasResult);
private readonly List<JudgementResult> results = new List<JudgementResult>();
public IReadOnlyList<JudgementResult> Results => results;
private bool judgementOccurred;
@ -74,6 +77,9 @@ public abstract class DrawableHitObject : SkinReloadableDrawable, IHasAccentColo
protected DrawableHitObject(HitObject hitObject)
{
HitObject = hitObject;
foreach (var j in hitObject.Judgements)
results.Add(CreateJudgementResult(j));
}
[BackgroundDependencyLoader]
@ -135,17 +141,17 @@ protected override void Update()
{
var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
for (int i = HitObject.Judgements.Count - 1; i >= 0; i--)
for (int i = Results.Count - 1; i >= 0; i--)
{
var judgement = HitObject.Judgements[i];
var judgement = Results[i];
if (judgement.TimeOffset + endTime <= Time.Current)
break;
judgement.Result = HitResult.None;
State.Value = ArmedState.Idle;
OnJudgementRemoved?.Invoke(this, judgement);
judgement.Type = HitResult.None;
State.Value = ArmedState.Idle;
}
}
@ -161,8 +167,8 @@ protected override void UpdateAfterChildren()
protected virtual void AddNested(DrawableHitObject h)
{
h.OnJudgement += (d, j) => OnJudgement?.Invoke(d, j);
h.OnJudgementRemoved += (d, j) => OnJudgementRemoved?.Invoke(d, j);
h.OnJudgement += (d, r) => OnJudgement?.Invoke(d, r);
h.OnJudgementRemoved += (d, r) => OnJudgementRemoved?.Invoke(d, r);
h.ApplyCustomUpdateState += (d, j) => ApplyCustomUpdateState?.Invoke(d, j);
nestedHitObjects.Value.Add(h);
@ -172,18 +178,21 @@ protected virtual void AddNested(DrawableHitObject h)
/// Notifies that a new judgement has occurred for this <see cref="DrawableHitObject"/>.
/// </summary>
/// <param name="judgement">The <see cref="Judgement"/>.</param>
protected void ApplyJudgement<T>(T judgement, Action<T> application)
where T : Judgement
protected void ApplyResult(JudgementResult result, Action<JudgementResult> application)
{
judgementOccurred = true;
// Todo: Unsure if we want to keep this
if (!Results.Contains(result))
throw new ArgumentException($"The applied judgement result must be a part of {Results}.");
application?.Invoke(judgement);
application?.Invoke(result);
judgementOccurred = true;
// Ensure that the judgement is given a valid time offset, because this may not get set by the caller
var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
judgement.TimeOffset = Time.Current - endTime;
result.TimeOffset = Time.Current - endTime;
switch (judgement.Result)
switch (result.Type)
{
case HitResult.None:
break;
@ -195,7 +204,7 @@ protected void ApplyJudgement<T>(T judgement, Action<T> application)
break;
}
OnJudgement?.Invoke(this, judgement);
OnJudgement?.Invoke(this, result);
}
/// <summary>
@ -224,7 +233,7 @@ protected bool UpdateJudgement(bool userTriggered)
/// <summary>
/// Checks if any judgements have occurred for this <see cref="DrawableHitObject"/>. This method must construct
/// all <see cref="Judgement"/>s and notify of them through <see cref="ApplyJudgement"/>.
/// all <see cref="Judgement"/>s and notify of them through <see cref="ApplyResult{T}"/>.
/// </summary>
/// <param name="userTriggered">Whether the user triggered this check.</param>
/// <param name="timeOffset">The offset from the <see cref="HitObject"/> end time at which this check occurred. A <paramref name="timeOffset"/> &gt; 0
@ -232,6 +241,8 @@ protected bool UpdateJudgement(bool userTriggered)
protected virtual void CheckForJudgements(bool userTriggered, double timeOffset)
{
}
protected virtual JudgementResult CreateJudgementResult(Judgement judgement) => new JudgementResult(judgement);
}
public abstract class DrawableHitObject<TObject> : DrawableHitObject

View File

@ -28,7 +28,7 @@ public abstract class ScoreProcessor
/// <summary>
/// Invoked when a new judgement has occurred. This occurs after the judgement has been processed by the <see cref="ScoreProcessor"/>.
/// </summary>
public event Action<Judgement> NewJudgement;
public event Action<JudgementResult> NewJudgement;
/// <summary>
/// Additional conditions on top of <see cref="DefaultFailCondition"/> that cause a failing state.
@ -144,9 +144,10 @@ protected void UpdateFailed()
/// Notifies subscribers of <see cref="NewJudgement"/> that a new judgement has occurred.
/// </summary>
/// <param name="judgement">The judgement to notify subscribers of.</param>
protected void NotifyNewJudgement(Judgement judgement)
/// <param name="result">The judgement scoring result to notify subscribers of.</param>
protected void NotifyNewJudgement(JudgementResult result)
{
NewJudgement?.Invoke(judgement);
NewJudgement?.Invoke(result);
if (HasCompleted)
AllJudged?.Invoke();
@ -209,32 +210,47 @@ public ScoreProcessor(RulesetContainer<TObject> rulesetContainer)
Mode.ValueChanged += _ => updateScore();
}
/// <summary>
/// Simulates an autoplay of <see cref="HitObject"/>s that will be judged by this <see cref="ScoreProcessor{TObject}"/>
/// by adding <see cref="Judgement"/>s for each <see cref="HitObject"/> in the <see cref="Beatmap{TObject}"/>.
/// <para>
/// This is required for <see cref="ScoringMode.Standardised"/> to work, otherwise <see cref="ScoringMode.Classic"/> will be used.
/// </para>
/// </summary>
/// <param name="beatmap">The <see cref="Beatmap{TObject}"/> containing the <see cref="HitObject"/>s that will be judged by this <see cref="ScoreProcessor{TObject}"/>.</param>
protected virtual void SimulateAutoplay(Beatmap<TObject> beatmap) { }
protected virtual void ApplyBeatmap(Beatmap<TObject> beatmap)
{
}
protected virtual void SimulateAutoplay(Beatmap<TObject> beatmap)
{
foreach (var obj in beatmap.HitObjects)
simulate(obj);
void simulate(HitObject obj)
{
foreach (var nested in obj.NestedHitObjects)
simulate(nested);
foreach (var judgement in obj.Judgements)
AddJudgement(new JudgementResult(judgement) { Type = judgement.MaxResult });
}
}
/// <summary>
/// Adds a judgement to this ScoreProcessor.
/// </summary>
/// <param name="judgement">The judgement to add.</param>
protected void AddJudgement(Judgement judgement)
/// <param name="result">The judgement scoring result.</param>
protected void AddJudgement(JudgementResult result)
{
OnNewJudgement(judgement);
OnNewJudgement(result);
updateScore();
UpdateFailed();
NotifyNewJudgement(judgement);
NotifyNewJudgement(result);
}
protected void RemoveJudgement(Judgement judgement)
/// <summary>
/// Removes a judgement from this ScoreProcessor.
/// </summary>
/// <param name="judgement">The judgement to remove.</param>
/// <param name="result">The judgement scoring result.</param>
protected void RemoveJudgement(JudgementResult result)
{
OnJudgementRemoved(judgement);
OnJudgementRemoved(result);
updateScore();
}
@ -242,16 +258,17 @@ protected void RemoveJudgement(Judgement judgement)
/// Applies a judgement.
/// </summary>
/// <param name="judgement">The judgement to apply/</param>
protected virtual void OnNewJudgement(Judgement judgement)
/// <param name="result">The judgement scoring result.</param>
protected virtual void OnNewJudgement(JudgementResult result)
{
judgement.ComboAtJudgement = Combo;
judgement.HighestComboAtJudgement = HighestCombo;
result.ComboAtJudgement = Combo;
result.HighestComboAtJudgement = HighestCombo;
JudgedHits++;
if (judgement.AffectsCombo)
if (result.Judgement.AffectsCombo)
{
switch (judgement.Result)
switch (result.Type)
{
case HitResult.None:
break;
@ -264,15 +281,15 @@ protected virtual void OnNewJudgement(Judgement judgement)
}
}
if (judgement.IsBonus)
if (result.Judgement.IsBonus)
{
if (judgement.IsHit)
bonusScore += judgement.NumericResult;
if (result.IsHit)
bonusScore += result.Judgement.NumericResultFor(result);
}
else
{
baseScore += judgement.NumericResult;
rollingMaxBaseScore += judgement.MaxNumericResult;
baseScore += result.Judgement.NumericResultFor(result);
rollingMaxBaseScore += result.Judgement.MaxNumericResult;
}
}
@ -280,22 +297,23 @@ protected virtual void OnNewJudgement(Judgement judgement)
/// Removes a judgement. This should reverse everything in <see cref="OnNewJudgement(Judgement)"/>.
/// </summary>
/// <param name="judgement">The judgement to remove.</param>
protected virtual void OnJudgementRemoved(Judgement judgement)
/// <param name="result">The judgement scoring result.</param>
protected virtual void OnJudgementRemoved(JudgementResult result)
{
Combo.Value = judgement.ComboAtJudgement;
HighestCombo.Value = judgement.HighestComboAtJudgement;
Combo.Value = result.ComboAtJudgement;
HighestCombo.Value = result.HighestComboAtJudgement;
JudgedHits--;
if (judgement.IsBonus)
if (result.Judgement.IsBonus)
{
if (judgement.IsHit)
bonusScore -= judgement.NumericResult;
if (result.IsHit)
bonusScore -= result.Judgement.NumericResultFor(result);
}
else
{
baseScore -= judgement.NumericResult;
rollingMaxBaseScore -= judgement.MaxNumericResult;
baseScore -= result.Judgement.NumericResultFor(result);
rollingMaxBaseScore -= result.Judgement.MaxNumericResult;
}
}

View File

@ -182,8 +182,8 @@ protected override void Dispose(bool isDisposing)
public abstract class RulesetContainer<TObject> : RulesetContainer
where TObject : HitObject
{
public event Action<Judgement> OnJudgement;
public event Action<Judgement> OnJudgementRemoved;
public event Action<JudgementResult> OnJudgement;
public event Action<JudgementResult> OnJudgementRemoved;
/// <summary>
/// The Beatmap
@ -290,8 +290,8 @@ private void loadObjects()
if (drawableObject == null)
continue;
drawableObject.OnJudgement += (d, j) => OnJudgement?.Invoke(j);
drawableObject.OnJudgementRemoved += (d, j) => OnJudgementRemoved?.Invoke(j);
drawableObject.OnJudgement += (_, r) => OnJudgement?.Invoke(r);
drawableObject.OnJudgementRemoved += (_, r) => OnJudgementRemoved?.Invoke(r);
Playfield.Add(drawableObject);
}

View File

@ -92,9 +92,9 @@ public StandardHealthDisplay()
};
}
public void Flash(Judgement judgement)
public void Flash(JudgementResult result)
{
if (judgement.Result == HitResult.Miss)
if (result.Type == HitResult.Miss)
return;
fill.FadeEdgeEffectTo(Math.Min(1, fill.EdgeEffect.Colour.Linear.A + (1f - base_glow_opacity) / glow_max_hits), 50, Easing.OutQuint)