Remove abstract ScoreProcessor class

This commit is contained in:
smoogipoo 2019-12-11 17:25:06 +09:00
parent c126cd8b01
commit 48f1dad4aa
11 changed files with 165 additions and 192 deletions

View File

@ -2,23 +2,21 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Catch.Scoring
{
public class CatchScoreProcessor : ScoreProcessor<CatchHitObject>
public class CatchScoreProcessor : ScoreProcessor
{
public CatchScoreProcessor(DrawableRuleset<CatchHitObject> drawableRuleset)
: base(drawableRuleset)
public CatchScoreProcessor(IBeatmap beatmap)
: base(beatmap)
{
}
private float hpDrainRate;
protected override void ApplyBeatmap(Beatmap<CatchHitObject> beatmap)
protected override void ApplyBeatmap(IBeatmap beatmap)
{
base.ApplyBeatmap(beatmap);

View File

@ -32,7 +32,7 @@ public DrawableCatchRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyL
TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450);
}
public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this);
public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(Beatmap);
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);

View File

@ -3,13 +3,11 @@
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania.Scoring
{
internal class ManiaScoreProcessor : ScoreProcessor<ManiaHitObject>
internal class ManiaScoreProcessor : ScoreProcessor
{
/// <summary>
/// The hit HP multiplier at OD = 0.
@ -51,12 +49,12 @@ internal class ManiaScoreProcessor : ScoreProcessor<ManiaHitObject>
/// </summary>
private double hpMultiplier = 1;
public ManiaScoreProcessor(DrawableRuleset<ManiaHitObject> drawableRuleset)
: base(drawableRuleset)
public ManiaScoreProcessor(IBeatmap beatmap)
: base(beatmap)
{
}
protected override void ApplyBeatmap(Beatmap<ManiaHitObject> beatmap)
protected override void ApplyBeatmap(IBeatmap beatmap)
{
base.ApplyBeatmap(beatmap);
@ -65,7 +63,7 @@ protected override void ApplyBeatmap(Beatmap<ManiaHitObject> beatmap)
hpMissMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_miss_min, hp_multiplier_miss_mid, hp_multiplier_miss_max);
}
protected override void SimulateAutoplay(Beatmap<ManiaHitObject> beatmap)
protected override void SimulateAutoplay(IBeatmap beatmap)
{
while (true)
{

View File

@ -67,7 +67,7 @@ private void load()
protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages);
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this);
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(Beatmap);
public override int Variant => (int)(Beatmap.Stages.Count == 1 ? PlayfieldType.Single : PlayfieldType.Dual) + Beatmap.TotalColumns;

View File

@ -5,22 +5,20 @@
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu.Scoring
{
internal class OsuScoreProcessor : ScoreProcessor<OsuHitObject>
internal class OsuScoreProcessor : ScoreProcessor
{
public OsuScoreProcessor(DrawableRuleset<OsuHitObject> drawableRuleset)
: base(drawableRuleset)
public OsuScoreProcessor(IBeatmap beatmap)
: base(beatmap)
{
}
private float hpDrainRate;
protected override void ApplyBeatmap(Beatmap<OsuHitObject> beatmap)
protected override void ApplyBeatmap(IBeatmap beatmap)
{
base.ApplyBeatmap(beatmap);

View File

@ -33,7 +33,7 @@ public DrawableOsuRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyLis
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // always show the gameplay cursor
public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(this);
public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(Beatmap);
protected override Playfield CreatePlayfield() => new OsuPlayfield();

View File

@ -1,15 +1,15 @@
// 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.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Taiko.Scoring
{
internal class TaikoScoreProcessor : ScoreProcessor<TaikoHitObject>
internal class TaikoScoreProcessor : ScoreProcessor
{
/// <summary>
/// A value used for calculating <see cref="hpMultiplier"/>.
@ -31,16 +31,16 @@ internal class TaikoScoreProcessor : ScoreProcessor<TaikoHitObject>
/// </summary>
private double hpMissMultiplier;
public TaikoScoreProcessor(DrawableRuleset<TaikoHitObject> drawableRuleset)
: base(drawableRuleset)
public TaikoScoreProcessor(IBeatmap beatmap)
: base(beatmap)
{
}
protected override void ApplyBeatmap(Beatmap<TaikoHitObject> beatmap)
protected override void ApplyBeatmap(IBeatmap beatmap)
{
base.ApplyBeatmap(beatmap);
hpMultiplier = 1 / (object_count_factor * beatmap.HitObjects.FindAll(o => o is Hit).Count * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98));
hpMultiplier = 1 / (object_count_factor * beatmap.HitObjects.OfType<Hit>().Count() * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98));
hpMissMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120);
}

View File

@ -40,7 +40,7 @@ private void load()
new BarLineGenerator<BarLine>(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar)));
}
public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this);
public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(Beatmap);
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer();

View File

@ -13,13 +13,16 @@
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
namespace osu.Game.Rulesets.Scoring
{
public abstract class ScoreProcessor
public class ScoreProcessor
{
private const double base_portion = 0.3;
private const double combo_portion = 0.7;
private const double max_score = 1000000;
/// <summary>
/// Invoked when the <see cref="ScoreProcessor"/> is in a failed state.
/// This may occur regardless of whether an <see cref="AllJudged"/> event is invoked.
@ -67,11 +70,6 @@ public abstract class ScoreProcessor
/// </summary>
public readonly Bindable<IReadOnlyList<Mod>> Mods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
/// <summary>
/// Create a <see cref="HitWindows"/> for this processor.
/// </summary>
public virtual HitWindows CreateHitWindows() => new HitWindows();
/// <summary>
/// The current rank.
/// </summary>
@ -90,132 +88,23 @@ public abstract class ScoreProcessor
/// <summary>
/// Whether all <see cref="Judgement"/>s have been processed.
/// </summary>
public virtual bool HasCompleted => false;
/// <summary>
/// The total number of judged <see cref="HitObject"/>s at the current point in time.
/// </summary>
public int JudgedHits { get; protected set; }
public bool HasCompleted => JudgedHits == MaxHits;
/// <summary>
/// Whether this ScoreProcessor has already triggered the failed state.
/// </summary>
public virtual bool HasFailed { get; private set; }
public bool HasFailed { get; private set; }
/// <summary>
/// The default conditions for failing.
/// The maximum number of hits that can be judged.
/// </summary>
protected virtual bool DefaultFailCondition => Precision.AlmostBigger(Health.MinValue, Health.Value);
protected ScoreProcessor()
{
Combo.ValueChanged += delegate { HighestCombo.Value = Math.Max(HighestCombo.Value, Combo.Value); };
Accuracy.ValueChanged += delegate
{
Rank.Value = rankFrom(Accuracy.Value);
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
Rank.Value = mod.AdjustRank(Rank.Value, Accuracy.Value);
};
}
private ScoreRank rankFrom(double acc)
{
if (acc == 1)
return ScoreRank.X;
if (acc > 0.95)
return ScoreRank.S;
if (acc > 0.9)
return ScoreRank.A;
if (acc > 0.8)
return ScoreRank.B;
if (acc > 0.7)
return ScoreRank.C;
return ScoreRank.D;
}
/// <summary>
/// Resets this ScoreProcessor to a default state.
/// </summary>
/// <param name="storeResults">Whether to store the current state of the <see cref="ScoreProcessor"/> for future use.</param>
protected virtual void Reset(bool storeResults)
{
TotalScore.Value = 0;
Accuracy.Value = 1;
Health.Value = 1;
Combo.Value = 0;
Rank.Value = ScoreRank.X;
HighestCombo.Value = 0;
JudgedHits = 0;
HasFailed = false;
}
/// <summary>
/// Checks if the score is in a failed state and notifies subscribers.
/// <para>
/// This can only ever notify subscribers once.
/// </para>
/// </summary>
protected void UpdateFailed(JudgementResult result)
{
if (HasFailed)
return;
if (!DefaultFailCondition && FailConditions?.Invoke(this, result) != true)
return;
if (Failed?.Invoke() != false)
HasFailed = true;
}
/// <summary>
/// Notifies subscribers of <see cref="NewJudgement"/> that a new judgement has occurred.
/// </summary>
/// <param name="result">The judgement scoring result to notify subscribers of.</param>
protected void NotifyNewJudgement(JudgementResult result)
{
NewJudgement?.Invoke(result);
if (HasCompleted)
AllJudged?.Invoke();
}
/// <summary>
/// Retrieve a score populated with data for the current play this processor is responsible for.
/// </summary>
public virtual void PopulateScore(ScoreInfo score)
{
score.TotalScore = (long)Math.Round(TotalScore.Value);
score.Combo = Combo.Value;
score.MaxCombo = HighestCombo.Value;
score.Accuracy = Math.Round(Accuracy.Value, 4);
score.Rank = Rank.Value;
score.Date = DateTimeOffset.Now;
var hitWindows = CreateHitWindows();
foreach (var result in Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r)))
score.Statistics[result] = GetStatistic(result);
}
public abstract int GetStatistic(HitResult result);
public abstract double GetStandardisedScore();
}
public class ScoreProcessor<TObject> : ScoreProcessor
where TObject : HitObject
{
private const double base_portion = 0.3;
private const double combo_portion = 0.7;
private const double max_score = 1000000;
public sealed override bool HasCompleted => JudgedHits == MaxHits;
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; }
private double maxHighestCombo;
private double maxBaseScore;
@ -225,17 +114,22 @@ public class ScoreProcessor<TObject> : ScoreProcessor
private double scoreMultiplier = 1;
public ScoreProcessor(DrawableRuleset<TObject> drawableRuleset)
public ScoreProcessor(IBeatmap beatmap)
{
Debug.Assert(base_portion + combo_portion == 1.0);
drawableRuleset.OnNewResult += applyResult;
drawableRuleset.OnRevertResult += revertResult;
Combo.ValueChanged += combo => HighestCombo.Value = Math.Max(HighestCombo.Value, combo.NewValue);
Accuracy.ValueChanged += accuracy =>
{
Rank.Value = rankFrom(accuracy.NewValue);
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
Rank.Value = mod.AdjustRank(Rank.Value, accuracy.NewValue);
};
ApplyBeatmap(drawableRuleset.Beatmap);
ApplyBeatmap(beatmap);
Reset(false);
SimulateAutoplay(drawableRuleset.Beatmap);
SimulateAutoplay(beatmap);
Reset(true);
if (maxBaseScore == 0 || maxHighestCombo == 0)
@ -257,19 +151,19 @@ public ScoreProcessor(DrawableRuleset<TObject> drawableRuleset)
}
/// <summary>
/// Applies any properties of the <see cref="Beatmap{TObject}"/> which affect scoring to this <see cref="ScoreProcessor{TObject}"/>.
/// Applies any properties of the <see cref="IBeatmap"/> which affect scoring to this <see cref="ScoreProcessor"/>.
/// </summary>
/// <param name="beatmap">The <see cref="Beatmap{TObject}"/> to read properties from.</param>
protected virtual void ApplyBeatmap(Beatmap<TObject> beatmap)
/// <param name="beatmap">The <see cref="IBeatmap"/> to read properties from.</param>
protected virtual void ApplyBeatmap(IBeatmap beatmap)
{
}
/// <summary>
/// Simulates an autoplay of the <see cref="Beatmap{TObject}"/> to determine scoring values.
/// 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="Beatmap{TObject}"/> to simulate.</param>
protected virtual void SimulateAutoplay(Beatmap<TObject> beatmap)
/// <param name="beatmap">The <see cref="IBeatmap"/> to simulate.</param>
protected virtual void SimulateAutoplay(IBeatmap beatmap)
{
foreach (var obj in beatmap.HitObjects)
simulate(obj);
@ -289,7 +183,7 @@ void simulate(HitObject obj)
result.Type = judgement.MaxResult;
applyResult(result);
ApplyResult(result);
}
}
@ -297,22 +191,26 @@ void simulate(HitObject obj)
/// 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>
private void applyResult(JudgementResult result)
public void ApplyResult(JudgementResult result)
{
ApplyResult(result);
updateScore();
ApplyResultInternal(result);
UpdateFailed(result);
NotifyNewJudgement(result);
updateScore();
updateFailed(result);
NewJudgement?.Invoke(result);
if (HasCompleted)
AllJudged?.Invoke();
}
/// <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>
private void revertResult(JudgementResult result)
public void RevertResult(JudgementResult result)
{
RevertResult(result);
RevertResultInternal(result);
updateScore();
}
@ -322,10 +220,10 @@ private void revertResult(JudgementResult result)
/// 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="RevertResult"/>.
/// 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 virtual void ApplyResult(JudgementResult result)
protected virtual void ApplyResultInternal(JudgementResult result)
{
result.ComboAtJudgement = Combo.Value;
result.HighestComboAtJudgement = HighestCombo.Value;
@ -372,10 +270,10 @@ protected virtual void ApplyResult(JudgementResult result)
}
/// <summary>
/// Reverts the score change of a <see cref="JudgementResult"/> that was applied to this <see cref="ScoreProcessor"/> via <see cref="ApplyResult"/>.
/// 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 virtual void RevertResult(JudgementResult result)
protected virtual void RevertResultInternal(JudgementResult result)
{
Combo.Value = result.ComboAtJudgement;
HighestCombo.Value = result.HighestComboAtJudgement;
@ -432,11 +330,49 @@ private double getScore(ScoringMode mode)
}
}
public override int GetStatistic(HitResult result) => scoreResultCounts.GetOrDefault(result);
/// <summary>
/// Checks if the score is in a failed state and notifies subscribers.
/// <para>
/// This can only ever notify subscribers once.
/// </para>
/// </summary>
private void updateFailed(JudgementResult result)
{
if (HasFailed)
return;
public override double GetStandardisedScore() => getScore(ScoringMode.Standardised);
if (!DefaultFailCondition && FailConditions?.Invoke(this, result) != true)
return;
protected override void Reset(bool storeResults)
if (Failed?.Invoke() != false)
HasFailed = true;
}
private ScoreRank rankFrom(double acc)
{
if (acc == 1)
return ScoreRank.X;
if (acc > 0.95)
return ScoreRank.S;
if (acc > 0.9)
return ScoreRank.A;
if (acc > 0.8)
return ScoreRank.B;
if (acc > 0.7)
return ScoreRank.C;
return ScoreRank.D;
}
public int GetStatistic(HitResult result) => scoreResultCounts.GetOrDefault(result);
public double GetStandardisedScore() => getScore(ScoringMode.Standardised);
/// <summary>
/// Resets this ScoreProcessor to a default state.
/// </summary>
/// <param name="storeResults">Whether to store the current state of the <see cref="ScoreProcessor"/> for future use.</param>
protected virtual void Reset(bool storeResults)
{
scoreResultCounts.Clear();
@ -447,13 +383,49 @@ protected override void Reset(bool storeResults)
maxBaseScore = baseScore;
}
base.Reset(storeResults);
JudgedHits = 0;
baseScore = 0;
rollingMaxBaseScore = 0;
bonusScore = 0;
TotalScore.Value = 0;
Accuracy.Value = 1;
Health.Value = 1;
Combo.Value = 0;
Rank.Value = ScoreRank.X;
HighestCombo.Value = 0;
HasFailed = false;
}
/// <summary>
/// Retrieve a score populated with data for the current play this processor is responsible for.
/// </summary>
public virtual void PopulateScore(ScoreInfo score)
{
score.TotalScore = (long)Math.Round(TotalScore.Value);
score.Combo = Combo.Value;
score.MaxCombo = HighestCombo.Value;
score.Accuracy = Math.Round(Accuracy.Value, 4);
score.Rank = Rank.Value;
score.Date = DateTimeOffset.Now;
var hitWindows = CreateHitWindows();
foreach (var result in Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r)))
score.Statistics[result] = GetStatistic(result);
}
/// <summary>
/// The default conditions for failing.
/// </summary>
protected virtual bool DefaultFailCondition => Precision.AlmostBigger(Health.MinValue, Health.Value);
/// <summary>
/// Create a <see cref="HitWindows"/> for this processor.
/// </summary>
public virtual HitWindows CreateHitWindows() => new HitWindows();
/// <summary>
/// Creates the <see cref="JudgementResult"/> that represents the scoring result for a <see cref="HitObject"/>.
/// </summary>

View File

@ -45,6 +45,10 @@ namespace osu.Game.Rulesets.UI
public abstract class DrawableRuleset<TObject> : DrawableRuleset, IProvideCursor, ICanAttachKeyCounter
where TObject : HitObject
{
public override event Action<JudgementResult> OnNewResult;
public override event Action<JudgementResult> OnRevertResult;
/// <summary>
/// The selected variant.
/// </summary>
@ -91,16 +95,6 @@ internal bool FrameStablePlayback
}
}
/// <summary>
/// Invoked when a <see cref="JudgementResult"/> has been applied by a <see cref="DrawableHitObject"/>.
/// </summary>
public event Action<JudgementResult> OnNewResult;
/// <summary>
/// Invoked when a <see cref="JudgementResult"/> is being reverted by a <see cref="DrawableHitObject"/>.
/// </summary>
public event Action<JudgementResult> OnRevertResult;
/// <summary>
/// The beatmap.
/// </summary>
@ -309,7 +303,7 @@ public void Attach(KeyCounterDisplay keyCounter) =>
/// <returns>The Playfield.</returns>
protected abstract Playfield CreatePlayfield();
public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor<TObject>(this);
public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(Beatmap);
/// <summary>
/// Applies the active mods to this DrawableRuleset.
@ -366,6 +360,16 @@ protected override void Dispose(bool isDisposing)
/// </summary>
public abstract class DrawableRuleset : CompositeDrawable
{
/// <summary>
/// Invoked when a <see cref="JudgementResult"/> has been applied by a <see cref="DrawableHitObject"/>.
/// </summary>
public abstract event Action<JudgementResult> OnNewResult;
/// <summary>
/// Invoked when a <see cref="JudgementResult"/> is being reverted by a <see cref="DrawableHitObject"/>.
/// </summary>
public abstract event Action<JudgementResult> OnRevertResult;
/// <summary>
/// Whether a replay is currently loaded.
/// </summary>

View File

@ -140,6 +140,9 @@ private void load(AudioManager audio, IAPIProvider api, OsuConfigManager config)
// bind clock into components that require it
DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused);
DrawableRuleset.OnNewResult += ScoreProcessor.ApplyResult;
DrawableRuleset.OnRevertResult += ScoreProcessor.RevertResult;
// Bind ScoreProcessor to ourselves
ScoreProcessor.AllJudged += onCompletion;
ScoreProcessor.Failed += onFail;