Merge remote-tracking branch 'refs/remotes/ppy/master' into overlay_headers_update

This commit is contained in:
Andrei Zavatski
2019-12-26 21:02:14 +03:00
34 changed files with 593 additions and 415 deletions

View File

@ -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>

View 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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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>

View 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;
}
}
}

View 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);
}
}
}
}

View File

@ -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

View File

@ -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>();

View File

@ -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);
}
}
}

View File

@ -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
}
}