mirror of
https://github.com/osukey/osukey.git
synced 2025-08-05 07:33:55 +09:00
Merge remote-tracking branch 'refs/remotes/ppy/master' into overlay_headers_update
This commit is contained in:
@ -9,6 +9,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@ -31,6 +32,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
protected readonly Nub Nub;
|
||||
private readonly Box leftBox;
|
||||
private readonly Box rightBox;
|
||||
private readonly Container nubContainer;
|
||||
|
||||
public virtual string TooltipText { get; private set; }
|
||||
|
||||
@ -72,10 +74,15 @@ namespace osu.Game.Graphics.UserInterface
|
||||
Origin = Anchor.CentreRight,
|
||||
Alpha = 0.5f,
|
||||
},
|
||||
Nub = new Nub
|
||||
nubContainer = new Container
|
||||
{
|
||||
Origin = Anchor.TopCentre,
|
||||
Expanded = true,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = Nub = new Nub
|
||||
{
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativePositionAxes = Axes.X,
|
||||
Expanded = true,
|
||||
},
|
||||
},
|
||||
new HoverClickSounds()
|
||||
};
|
||||
@ -90,6 +97,13 @@ namespace osu.Game.Graphics.UserInterface
|
||||
AccentColour = colours.Pink;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
nubContainer.Padding = new MarginPadding { Horizontal = RangePadding };
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
@ -176,14 +190,14 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
leftBox.Scale = new Vector2(Math.Clamp(
|
||||
Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1);
|
||||
RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1);
|
||||
rightBox.Scale = new Vector2(Math.Clamp(
|
||||
DrawWidth - Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1);
|
||||
DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2, 0, DrawWidth), 1);
|
||||
}
|
||||
|
||||
protected override void UpdateValue(float value)
|
||||
{
|
||||
Nub.MoveToX(RangePadding + UsableWidth * value, 250, Easing.OutQuint);
|
||||
Nub.MoveToX(value, 250, Easing.OutQuint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
15
osu.Game/Rulesets/Mods/IApplicableToHealthProcessor.cs
Normal file
15
osu.Game/Rulesets/Mods/IApplicableToHealthProcessor.cs
Normal file
@ -0,0 +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 osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
public interface IApplicableToHealthProcessor : IApplicableMod
|
||||
{
|
||||
/// <summary>
|
||||
/// Provide a <see cref="HealthProcessor"/> to a mod. Called once on initialisation of a play instance.
|
||||
/// </summary>
|
||||
void ApplyToHealthProcessor(HealthProcessor healthProcessor);
|
||||
}
|
||||
}
|
@ -8,11 +8,10 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
public abstract class ModEasy : Mod, IApplicableToDifficulty, IApplicableFailOverride, IApplicableToScoreProcessor
|
||||
public abstract class ModEasy : Mod, IApplicableToDifficulty, IApplicableFailOverride, IApplicableToHealthProcessor
|
||||
{
|
||||
public override string Name => "Easy";
|
||||
public override string Acronym => "EZ";
|
||||
@ -59,11 +58,9 @@ namespace osu.Game.Rulesets.Mods
|
||||
|
||||
public bool RestartOnFail => false;
|
||||
|
||||
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
||||
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
|
||||
{
|
||||
health = scoreProcessor.Health.GetBoundCopy();
|
||||
health = healthProcessor.Health.GetBoundCopy();
|
||||
}
|
||||
|
||||
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,6 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override IconUsage Icon => OsuIcon.ModPerfect;
|
||||
public override string Description => "SS or quit.";
|
||||
|
||||
protected override bool FailCondition(ScoreProcessor scoreProcessor, JudgementResult result) => scoreProcessor.Accuracy.Value != 1;
|
||||
protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) => result.Type != result.Judgement.MaxResult;
|
||||
}
|
||||
}
|
||||
|
@ -6,11 +6,10 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
public abstract class ModSuddenDeath : Mod, IApplicableToScoreProcessor, IApplicableFailOverride
|
||||
public abstract class ModSuddenDeath : Mod, IApplicableToHealthProcessor, IApplicableFailOverride
|
||||
{
|
||||
public override string Name => "Sudden Death";
|
||||
public override string Acronym => "SD";
|
||||
@ -24,13 +23,11 @@ namespace osu.Game.Rulesets.Mods
|
||||
public bool AllowFail => true;
|
||||
public bool RestartOnFail => true;
|
||||
|
||||
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
||||
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
|
||||
{
|
||||
scoreProcessor.FailConditions += FailCondition;
|
||||
healthProcessor.FailConditions += FailCondition;
|
||||
}
|
||||
|
||||
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
|
||||
|
||||
protected virtual bool FailCondition(ScoreProcessor scoreProcessor, JudgementResult result) => scoreProcessor.Combo.Value == 0 && result.Judgement.AffectsCombo;
|
||||
protected virtual bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) => !result.IsHit && result.Judgement.AffectsCombo;
|
||||
}
|
||||
}
|
||||
|
@ -76,6 +76,12 @@ namespace osu.Game.Rulesets
|
||||
/// <returns>The score processor.</returns>
|
||||
public virtual ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new ScoreProcessor(beatmap);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="HealthProcessor"/> for a beatmap converted to this ruleset.
|
||||
/// </summary>
|
||||
/// <returns>The health processor.</returns>
|
||||
public virtual HealthProcessor CreateHealthProcessor(IBeatmap beatmap) => new HealthProcessor(beatmap);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="IBeatmapConverter"/> to convert a <see cref="IBeatmap"/> to one that is applicable for this <see cref="Ruleset"/>.
|
||||
/// </summary>
|
||||
|
84
osu.Game/Rulesets/Scoring/HealthProcessor.cs
Normal file
84
osu.Game/Rulesets/Scoring/HealthProcessor.cs
Normal file
@ -0,0 +1,84 @@
|
||||
// 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 osu.Framework.Bindables;
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
|
||||
namespace osu.Game.Rulesets.Scoring
|
||||
{
|
||||
public class HealthProcessor : JudgementProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Invoked when the <see cref="ScoreProcessor"/> is in a failed state.
|
||||
/// Return true if the fail was permitted.
|
||||
/// </summary>
|
||||
public event Func<bool> Failed;
|
||||
|
||||
/// <summary>
|
||||
/// Additional conditions on top of <see cref="DefaultFailCondition"/> that cause a failing state.
|
||||
/// </summary>
|
||||
public event Func<HealthProcessor, JudgementResult, bool> FailConditions;
|
||||
|
||||
/// <summary>
|
||||
/// The current health.
|
||||
/// </summary>
|
||||
public readonly BindableDouble Health = new BindableDouble(1) { MinValue = 0, MaxValue = 1 };
|
||||
|
||||
/// <summary>
|
||||
/// Whether this ScoreProcessor has already triggered the failed state.
|
||||
/// </summary>
|
||||
public bool HasFailed { get; private set; }
|
||||
|
||||
public HealthProcessor(IBeatmap beatmap)
|
||||
: base(beatmap)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void ApplyResultInternal(JudgementResult result)
|
||||
{
|
||||
result.HealthAtJudgement = Health.Value;
|
||||
result.FailedAtJudgement = HasFailed;
|
||||
|
||||
if (HasFailed)
|
||||
return;
|
||||
|
||||
Health.Value += HealthAdjustmentFactorFor(result) * result.Judgement.HealthIncreaseFor(result);
|
||||
|
||||
if (!DefaultFailCondition && FailConditions?.Invoke(this, result) != true)
|
||||
return;
|
||||
|
||||
if (Failed?.Invoke() != false)
|
||||
HasFailed = true;
|
||||
}
|
||||
|
||||
protected override void RevertResultInternal(JudgementResult result)
|
||||
{
|
||||
Health.Value = result.HealthAtJudgement;
|
||||
|
||||
// Todo: Revert HasFailed state with proper player support
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An adjustment factor which is multiplied into the health increase provided by a <see cref="JudgementResult"/>.
|
||||
/// </summary>
|
||||
/// <param name="result">The <see cref="JudgementResult"/> for which the adjustment should apply.</param>
|
||||
/// <returns>The adjustment factor.</returns>
|
||||
protected virtual double HealthAdjustmentFactorFor(JudgementResult result) => 1;
|
||||
|
||||
/// <summary>
|
||||
/// The default conditions for failing.
|
||||
/// </summary>
|
||||
protected virtual bool DefaultFailCondition => Precision.AlmostBigger(Health.MinValue, Health.Value);
|
||||
|
||||
protected override void Reset(bool storeResults)
|
||||
{
|
||||
base.Reset(storeResults);
|
||||
|
||||
Health.Value = 1;
|
||||
HasFailed = false;
|
||||
}
|
||||
}
|
||||
}
|
146
osu.Game/Rulesets/Scoring/JudgementProcessor.cs
Normal file
146
osu.Game/Rulesets/Scoring/JudgementProcessor.cs
Normal file
@ -0,0 +1,146 @@
|
||||
// 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 osu.Framework.Extensions.TypeExtensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Scoring
|
||||
{
|
||||
public abstract class JudgementProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Invoked when all <see cref="HitObject"/>s have been judged by this <see cref="JudgementProcessor"/>.
|
||||
/// </summary>
|
||||
public event Action AllJudged;
|
||||
|
||||
/// <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>
|
||||
/// 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; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether all <see cref="Judgement"/>s have been processed.
|
||||
/// </summary>
|
||||
public bool HasCompleted => JudgedHits == MaxHits;
|
||||
|
||||
protected JudgementProcessor(IBeatmap beatmap)
|
||||
{
|
||||
ApplyBeatmap(beatmap);
|
||||
|
||||
Reset(false);
|
||||
SimulateAutoplay(beatmap);
|
||||
Reset(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies any properties of the <see cref="IBeatmap"/> which affect scoring to this <see cref="ScoreProcessor"/>.
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The <see cref="IBeatmap"/> to read properties from.</param>
|
||||
protected virtual void ApplyBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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++;
|
||||
|
||||
ApplyResultInternal(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>
|
||||
public void RevertResult(JudgementResult result)
|
||||
{
|
||||
JudgedHits--;
|
||||
|
||||
RevertResultInternal(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>
|
||||
/// 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();
|
||||
if (judgement == null)
|
||||
return;
|
||||
|
||||
var result = CreateResult(obj, judgement);
|
||||
if (result == null)
|
||||
throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}.");
|
||||
|
||||
result.Type = judgement.MaxResult;
|
||||
|
||||
ApplyResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,44 +7,19 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.TypeExtensions;
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Scoring
|
||||
{
|
||||
public class ScoreProcessor
|
||||
public class ScoreProcessor : JudgementProcessor
|
||||
{
|
||||
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.
|
||||
/// Return true if the fail was permitted.
|
||||
/// </summary>
|
||||
public event Func<bool> Failed;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when all <see cref="HitObject"/>s have been judged.
|
||||
/// </summary>
|
||||
public event Action AllJudged;
|
||||
|
||||
/// <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<JudgementResult> NewJudgement;
|
||||
|
||||
/// <summary>
|
||||
/// Additional conditions on top of <see cref="DefaultFailCondition"/> that cause a failing state.
|
||||
/// </summary>
|
||||
public event Func<ScoreProcessor, JudgementResult, bool> FailConditions;
|
||||
|
||||
/// <summary>
|
||||
/// The current total score.
|
||||
/// </summary>
|
||||
@ -55,11 +30,6 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// </summary>
|
||||
public readonly BindableDouble Accuracy = new BindableDouble(1) { MinValue = 0, MaxValue = 1 };
|
||||
|
||||
/// <summary>
|
||||
/// The current health.
|
||||
/// </summary>
|
||||
public readonly BindableDouble Health = new BindableDouble(1) { MinValue = 0, MaxValue = 1 };
|
||||
|
||||
/// <summary>
|
||||
/// The current combo.
|
||||
/// </summary>
|
||||
@ -85,26 +55,6 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// </summary>
|
||||
public readonly Bindable<ScoringMode> Mode = new Bindable<ScoringMode>();
|
||||
|
||||
/// <summary>
|
||||
/// Whether all <see cref="Judgement"/>s have been processed.
|
||||
/// </summary>
|
||||
public bool HasCompleted => JudgedHits == MaxHits;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this ScoreProcessor has already triggered the failed state.
|
||||
/// </summary>
|
||||
public bool HasFailed { get; private set; }
|
||||
|
||||
/// <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; }
|
||||
|
||||
private double maxHighestCombo;
|
||||
|
||||
private double maxBaseScore;
|
||||
@ -115,8 +65,14 @@ namespace osu.Game.Rulesets.Scoring
|
||||
private double scoreMultiplier = 1;
|
||||
|
||||
public ScoreProcessor(IBeatmap beatmap)
|
||||
: base(beatmap)
|
||||
{
|
||||
Debug.Assert(base_portion + combo_portion == 1.0);
|
||||
}
|
||||
|
||||
protected override void ApplyBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
base.ApplyBeatmap(beatmap);
|
||||
|
||||
Combo.ValueChanged += combo => HighestCombo.Value = Math.Max(HighestCombo.Value, combo.NewValue);
|
||||
Accuracy.ValueChanged += accuracy =>
|
||||
@ -126,12 +82,6 @@ namespace osu.Game.Rulesets.Scoring
|
||||
Rank.Value = mod.AdjustRank(Rank.Value, accuracy.NewValue);
|
||||
};
|
||||
|
||||
ApplyBeatmap(beatmap);
|
||||
|
||||
Reset(false);
|
||||
SimulateAutoplay(beatmap);
|
||||
Reset(true);
|
||||
|
||||
if (maxBaseScore == 0 || maxHighestCombo == 0)
|
||||
{
|
||||
Mode.Value = ScoringMode.Classic;
|
||||
@ -150,91 +100,16 @@ namespace osu.Game.Rulesets.Scoring
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies any properties of the <see cref="IBeatmap"/> which affect scoring to this <see cref="ScoreProcessor"/>.
|
||||
/// </summary>
|
||||
/// <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="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();
|
||||
if (judgement == null)
|
||||
return;
|
||||
|
||||
var result = CreateResult(obj, judgement);
|
||||
if (result == null)
|
||||
throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}.");
|
||||
|
||||
result.Type = judgement.MaxResult;
|
||||
|
||||
ApplyResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
ApplyResultInternal(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>
|
||||
public void RevertResult(JudgementResult result)
|
||||
{
|
||||
RevertResultInternal(result);
|
||||
updateScore();
|
||||
}
|
||||
|
||||
private readonly Dictionary<HitResult, int> scoreResultCounts = new Dictionary<HitResult, int>();
|
||||
|
||||
/// <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 virtual void ApplyResultInternal(JudgementResult result)
|
||||
protected sealed override void ApplyResultInternal(JudgementResult result)
|
||||
{
|
||||
result.ComboAtJudgement = Combo.Value;
|
||||
result.HighestComboAtJudgement = HighestCombo.Value;
|
||||
result.HealthAtJudgement = Health.Value;
|
||||
result.FailedAtJudgement = HasFailed;
|
||||
|
||||
if (HasFailed)
|
||||
if (result.FailedAtJudgement)
|
||||
return;
|
||||
|
||||
JudgedHits++;
|
||||
|
||||
if (result.Judgement.AffectsCombo)
|
||||
{
|
||||
switch (result.Type)
|
||||
@ -266,26 +141,17 @@ namespace osu.Game.Rulesets.Scoring
|
||||
rollingMaxBaseScore += result.Judgement.MaxNumericResult;
|
||||
}
|
||||
|
||||
Health.Value += HealthAdjustmentFactorFor(result) * result.Judgement.HealthIncreaseFor(result);
|
||||
updateScore();
|
||||
}
|
||||
|
||||
/// <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 virtual void RevertResultInternal(JudgementResult result)
|
||||
protected sealed override void RevertResultInternal(JudgementResult result)
|
||||
{
|
||||
Combo.Value = result.ComboAtJudgement;
|
||||
HighestCombo.Value = result.HighestComboAtJudgement;
|
||||
Health.Value = result.HealthAtJudgement;
|
||||
|
||||
// Todo: Revert HasFailed state with proper player support
|
||||
|
||||
if (result.FailedAtJudgement)
|
||||
return;
|
||||
|
||||
JudgedHits--;
|
||||
|
||||
if (result.Judgement.IsBonus)
|
||||
{
|
||||
if (result.IsHit)
|
||||
@ -299,14 +165,9 @@ namespace osu.Game.Rulesets.Scoring
|
||||
baseScore -= result.Judgement.NumericResultFor(result);
|
||||
rollingMaxBaseScore -= result.Judgement.MaxNumericResult;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An adjustment factor which is multiplied into the health increase provided by a <see cref="JudgementResult"/>.
|
||||
/// </summary>
|
||||
/// <param name="result">The <see cref="JudgementResult"/> for which the adjustment should apply.</param>
|
||||
/// <returns>The adjustment factor.</returns>
|
||||
protected virtual double HealthAdjustmentFactorFor(JudgementResult result) => 1;
|
||||
updateScore();
|
||||
}
|
||||
|
||||
private void updateScore()
|
||||
{
|
||||
@ -330,24 +191,6 @@ namespace osu.Game.Rulesets.Scoring
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
|
||||
if (!DefaultFailCondition && FailConditions?.Invoke(this, result) != true)
|
||||
return;
|
||||
|
||||
if (Failed?.Invoke() != false)
|
||||
HasFailed = true;
|
||||
}
|
||||
|
||||
private ScoreRank rankFrom(double acc)
|
||||
{
|
||||
if (acc == 1)
|
||||
@ -372,30 +215,27 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// 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)
|
||||
protected override void Reset(bool storeResults)
|
||||
{
|
||||
base.Reset(storeResults);
|
||||
|
||||
scoreResultCounts.Clear();
|
||||
|
||||
if (storeResults)
|
||||
{
|
||||
MaxHits = JudgedHits;
|
||||
maxHighestCombo = HighestCombo.Value;
|
||||
maxBaseScore = baseScore;
|
||||
}
|
||||
|
||||
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>
|
||||
@ -416,22 +256,10 @@ namespace osu.Game.Rulesets.Scoring
|
||||
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>
|
||||
/// <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);
|
||||
}
|
||||
|
||||
public enum ScoringMode
|
||||
|
@ -159,7 +159,7 @@ namespace osu.Game.Rulesets.UI
|
||||
dependencies.Cache(textureStore);
|
||||
|
||||
localSampleStore = dependencies.Get<AudioManager>().GetSampleStore(new NamespacedResourceStore<byte[]>(resources, "Samples"));
|
||||
dependencies.CacheAs(new FallbackSampleStore(localSampleStore, dependencies.Get<ISampleStore>()));
|
||||
dependencies.CacheAs<ISampleStore>(new FallbackSampleStore(localSampleStore, dependencies.Get<ISampleStore>()));
|
||||
}
|
||||
|
||||
onScreenDisplay = dependencies.Get<OnScreenDisplay>();
|
||||
|
@ -41,6 +41,7 @@ namespace osu.Game.Screens.Play
|
||||
public Bindable<bool> ShowHealthbar = new Bindable<bool>(true);
|
||||
|
||||
private readonly ScoreProcessor scoreProcessor;
|
||||
private readonly HealthProcessor healthProcessor;
|
||||
private readonly DrawableRuleset drawableRuleset;
|
||||
private readonly IReadOnlyList<Mod> mods;
|
||||
|
||||
@ -63,9 +64,10 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private IEnumerable<Drawable> hideTargets => new Drawable[] { visibilityContainer, KeyCounter };
|
||||
|
||||
public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, IReadOnlyList<Mod> mods)
|
||||
public HUDOverlay(ScoreProcessor scoreProcessor, HealthProcessor healthProcessor, DrawableRuleset drawableRuleset, IReadOnlyList<Mod> mods)
|
||||
{
|
||||
this.scoreProcessor = scoreProcessor;
|
||||
this.healthProcessor = healthProcessor;
|
||||
this.drawableRuleset = drawableRuleset;
|
||||
this.mods = mods;
|
||||
|
||||
@ -119,7 +121,10 @@ namespace osu.Game.Screens.Play
|
||||
private void load(OsuConfigManager config, NotificationOverlay notificationOverlay)
|
||||
{
|
||||
if (scoreProcessor != null)
|
||||
BindProcessor(scoreProcessor);
|
||||
BindScoreProcessor(scoreProcessor);
|
||||
|
||||
if (healthProcessor != null)
|
||||
BindHealthProcessor(healthProcessor);
|
||||
|
||||
if (drawableRuleset != null)
|
||||
{
|
||||
@ -288,15 +293,19 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay();
|
||||
|
||||
protected virtual void BindProcessor(ScoreProcessor processor)
|
||||
protected virtual void BindScoreProcessor(ScoreProcessor processor)
|
||||
{
|
||||
ScoreCounter?.Current.BindTo(processor.TotalScore);
|
||||
AccuracyCounter?.Current.BindTo(processor.Accuracy);
|
||||
ComboCounter?.Current.BindTo(processor.Combo);
|
||||
HealthDisplay?.Current.BindTo(processor.Health);
|
||||
|
||||
if (HealthDisplay is StandardHealthDisplay shd)
|
||||
processor.NewJudgement += shd.Flash;
|
||||
}
|
||||
|
||||
protected virtual void BindHealthProcessor(HealthProcessor processor)
|
||||
{
|
||||
HealthDisplay?.Current.BindTo(processor.Health);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,6 +72,9 @@ namespace osu.Game.Screens.Play
|
||||
public BreakOverlay BreakOverlay;
|
||||
|
||||
protected ScoreProcessor ScoreProcessor { get; private set; }
|
||||
|
||||
protected HealthProcessor HealthProcessor { get; private set; }
|
||||
|
||||
protected DrawableRuleset DrawableRuleset { get; private set; }
|
||||
|
||||
protected HUDOverlay HUDOverlay { get; private set; }
|
||||
@ -131,6 +134,8 @@ namespace osu.Game.Screens.Play
|
||||
ScoreProcessor = ruleset.CreateScoreProcessor(playableBeatmap);
|
||||
ScoreProcessor.Mods.BindTo(Mods);
|
||||
|
||||
HealthProcessor = ruleset.CreateHealthProcessor(playableBeatmap);
|
||||
|
||||
if (!ScoreProcessor.Mode.Disabled)
|
||||
config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode);
|
||||
|
||||
@ -145,15 +150,28 @@ namespace osu.Game.Screens.Play
|
||||
// bind clock into components that require it
|
||||
DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused);
|
||||
|
||||
DrawableRuleset.OnNewResult += ScoreProcessor.ApplyResult;
|
||||
DrawableRuleset.OnRevertResult += ScoreProcessor.RevertResult;
|
||||
DrawableRuleset.OnNewResult += r =>
|
||||
{
|
||||
HealthProcessor.ApplyResult(r);
|
||||
ScoreProcessor.ApplyResult(r);
|
||||
};
|
||||
|
||||
// Bind ScoreProcessor to ourselves
|
||||
DrawableRuleset.OnRevertResult += r =>
|
||||
{
|
||||
HealthProcessor.RevertResult(r);
|
||||
ScoreProcessor.RevertResult(r);
|
||||
};
|
||||
|
||||
// Bind the judgement processors to ourselves
|
||||
ScoreProcessor.AllJudged += onCompletion;
|
||||
ScoreProcessor.Failed += onFail;
|
||||
HealthProcessor.Failed += onFail;
|
||||
|
||||
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
|
||||
mod.ApplyToScoreProcessor(ScoreProcessor);
|
||||
|
||||
foreach (var mod in Mods.Value.OfType<IApplicableToHealthProcessor>())
|
||||
mod.ApplyToHealthProcessor(HealthProcessor);
|
||||
|
||||
BreakOverlay.IsBreakTime.ValueChanged += _ => updatePauseOnFocusLostState();
|
||||
}
|
||||
|
||||
@ -197,7 +215,7 @@ namespace osu.Game.Screens.Play
|
||||
// display the cursor above some HUD elements.
|
||||
DrawableRuleset.Cursor?.CreateProxy() ?? new Container(),
|
||||
DrawableRuleset.ResumeOverlay?.CreateProxy() ?? new Container(),
|
||||
HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, Mods.Value)
|
||||
HUDOverlay = new HUDOverlay(ScoreProcessor, HealthProcessor, DrawableRuleset, Mods.Value)
|
||||
{
|
||||
HoldToQuit =
|
||||
{
|
||||
@ -342,7 +360,7 @@ namespace osu.Game.Screens.Play
|
||||
private void onCompletion()
|
||||
{
|
||||
// Only show the completion screen if the player hasn't failed
|
||||
if (ScoreProcessor.HasFailed || completionProgressDelegate != null)
|
||||
if (HealthProcessor.HasFailed || completionProgressDelegate != null)
|
||||
return;
|
||||
|
||||
ValidForResume = false;
|
||||
@ -350,18 +368,7 @@ namespace osu.Game.Screens.Play
|
||||
if (!showResults) return;
|
||||
|
||||
using (BeginDelayedSequence(1000))
|
||||
{
|
||||
completionProgressDelegate = Schedule(delegate
|
||||
{
|
||||
if (!this.IsCurrentScreen()) return;
|
||||
|
||||
var score = CreateScore();
|
||||
if (DrawableRuleset.ReplayScore == null)
|
||||
scoreManager.Import(score).Wait();
|
||||
|
||||
this.Push(CreateResults(score));
|
||||
});
|
||||
}
|
||||
scheduleGotoRanking();
|
||||
}
|
||||
|
||||
protected virtual ScoreInfo CreateScore()
|
||||
@ -542,7 +549,7 @@ namespace osu.Game.Screens.Play
|
||||
if (completionProgressDelegate != null && !completionProgressDelegate.Cancelled && !completionProgressDelegate.Completed)
|
||||
{
|
||||
// proceed to result screen if beatmap already finished playing
|
||||
completionProgressDelegate.RunTask();
|
||||
scheduleGotoRanking();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -577,6 +584,19 @@ namespace osu.Game.Screens.Play
|
||||
storyboardReplacesBackground.Value = false;
|
||||
}
|
||||
|
||||
private void scheduleGotoRanking()
|
||||
{
|
||||
completionProgressDelegate?.Cancel();
|
||||
completionProgressDelegate = Schedule(delegate
|
||||
{
|
||||
var score = CreateScore();
|
||||
if (DrawableRuleset.ReplayScore == null)
|
||||
scoreManager.Import(score).Wait();
|
||||
|
||||
this.Push(CreateResults(score));
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user