diff --git a/osu.Android.props b/osu.Android.props
index e7f90af5fd..a43c834406 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -53,7 +53,7 @@
-
-
+
+
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index 58212e29ef..f0e50c5ba5 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -30,7 +30,10 @@ namespace osu.Game.Rulesets.Catch
public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new CatchScoreProcessor(beatmap);
+ public override HealthProcessor CreateHealthProcessor(IBeatmap beatmap) => new CatchHealthProcessor(beatmap);
+
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap, this);
+
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap);
public const string SHORT_NAME = "fruits";
diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs
new file mode 100644
index 0000000000..49ba0f6122
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Scoring/CatchHealthProcessor.cs
@@ -0,0 +1,38 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Catch.Scoring
+{
+ public class CatchHealthProcessor : HealthProcessor
+ {
+ public CatchHealthProcessor(IBeatmap beatmap)
+ : base(beatmap)
+ {
+ }
+
+ private float hpDrainRate;
+
+ protected override void ApplyBeatmap(IBeatmap beatmap)
+ {
+ base.ApplyBeatmap(beatmap);
+
+ hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate;
+ }
+
+ protected override double HealthAdjustmentFactorFor(JudgementResult result)
+ {
+ switch (result.Type)
+ {
+ case HitResult.Miss:
+ return hpDrainRate;
+
+ default:
+ return 10.2 - hpDrainRate; // Award less HP as drain rate is increased
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
index f67ca1213e..ad7520d57d 100644
--- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Scoring
@@ -14,27 +13,6 @@ namespace osu.Game.Rulesets.Catch.Scoring
{
}
- private float hpDrainRate;
-
- protected override void ApplyBeatmap(IBeatmap beatmap)
- {
- base.ApplyBeatmap(beatmap);
-
- hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate;
- }
-
- protected override double HealthAdjustmentFactorFor(JudgementResult result)
- {
- switch (result.Type)
- {
- case HitResult.Miss:
- return hpDrainRate;
-
- default:
- return 10.2 - hpDrainRate; // Award less HP as drain rate is increased
- }
- }
-
public override HitWindows CreateHitWindows() => new CatchHitWindows();
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/SkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/SkinnableTestScene.cs
new file mode 100644
index 0000000000..80b1b3df8e
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/SkinnableTestScene.cs
@@ -0,0 +1,46 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Skinning;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ public abstract class SkinnableTestScene : OsuGridTestScene
+ {
+ private Skin defaultSkin;
+
+ protected SkinnableTestScene()
+ : base(1, 2)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(AudioManager audio, SkinManager skinManager)
+ {
+ defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info);
+ }
+
+ public void SetContents(Func creationFunction)
+ {
+ Cell(0).Child = createProvider(null, creationFunction);
+ Cell(1).Child = createProvider(defaultSkin, creationFunction);
+ }
+
+ private Drawable createProvider(Skin skin, Func creationFunction)
+ {
+ var mainProvider = new SkinProvidingContainer(skin);
+
+ return mainProvider
+ .WithChild(new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider))
+ {
+ Child = creationFunction()
+ });
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs
new file mode 100644
index 0000000000..eea1a36a19
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs
@@ -0,0 +1,37 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Extensions;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ public class TestSceneDrawableJudgement : SkinnableTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DrawableJudgement),
+ typeof(DrawableManiaJudgement)
+ };
+
+ public TestSceneDrawableJudgement()
+ {
+ foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1))
+ {
+ AddStep("Show " + result.GetDescription(), () => SetContents(() =>
+ new DrawableManiaJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }));
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index b07e1d8f54..3c2fbd1548 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -26,7 +26,9 @@ using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Difficulty;
using osu.Game.Rulesets.Mania.Edit;
using osu.Game.Rulesets.Mania.Scoring;
+using osu.Game.Rulesets.Mania.Skinning;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Skinning;
using osu.Game.Scoring;
namespace osu.Game.Rulesets.Mania
@@ -37,6 +39,8 @@ namespace osu.Game.Rulesets.Mania
public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new ManiaScoreProcessor(beatmap);
+ public override HealthProcessor CreateHealthProcessor(IBeatmap beatmap) => new ManiaHealthProcessor(beatmap);
+
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score);
@@ -45,6 +49,8 @@ namespace osu.Game.Rulesets.Mania
public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this);
+ public override ISkin CreateLegacySkinProvider(ISkinSource source) => new ManiaLegacySkinTransformer(source);
+
public override IEnumerable ConvertLegacyMods(LegacyMods mods)
{
if (mods.HasFlag(LegacyMods.Nightcore))
diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs
new file mode 100644
index 0000000000..c362c906a4
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaHealthProcessor.cs
@@ -0,0 +1,69 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Mania.Scoring
+{
+ public class ManiaHealthProcessor : HealthProcessor
+ {
+ ///
+ /// The hit HP multiplier at OD = 0.
+ ///
+ private const double hp_multiplier_min = 0.75;
+
+ ///
+ /// The hit HP multiplier at OD = 0.
+ ///
+ private const double hp_multiplier_mid = 0.85;
+
+ ///
+ /// The hit HP multiplier at OD = 0.
+ ///
+ private const double hp_multiplier_max = 1;
+
+ ///
+ /// The MISS HP multiplier at OD = 0.
+ ///
+ private const double hp_multiplier_miss_min = 0.5;
+
+ ///
+ /// The MISS HP multiplier at OD = 5.
+ ///
+ private const double hp_multiplier_miss_mid = 0.75;
+
+ ///
+ /// The MISS HP multiplier at OD = 10.
+ ///
+ private const double hp_multiplier_miss_max = 1;
+
+ ///
+ /// The MISS HP multiplier. This is multiplied to the miss hp increase.
+ ///
+ private double hpMissMultiplier = 1;
+
+ ///
+ /// The HIT HP multiplier. This is multiplied to hit hp increases.
+ ///
+ private double hpMultiplier = 1;
+
+ public ManiaHealthProcessor(IBeatmap beatmap)
+ : base(beatmap)
+ {
+ }
+
+ protected override void ApplyBeatmap(IBeatmap beatmap)
+ {
+ base.ApplyBeatmap(beatmap);
+
+ BeatmapDifficulty difficulty = beatmap.BeatmapInfo.BaseDifficulty;
+ hpMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_min, hp_multiplier_mid, hp_multiplier_max);
+ hpMissMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_miss_min, hp_multiplier_miss_mid, hp_multiplier_miss_max);
+ }
+
+ protected override double HealthAdjustmentFactorFor(JudgementResult result)
+ => result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier;
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
index a678ef60e7..97f1ea721c 100644
--- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
@@ -2,86 +2,17 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Scoring
{
internal class ManiaScoreProcessor : ScoreProcessor
{
- ///
- /// The hit HP multiplier at OD = 0.
- ///
- private const double hp_multiplier_min = 0.75;
-
- ///
- /// The hit HP multiplier at OD = 0.
- ///
- private const double hp_multiplier_mid = 0.85;
-
- ///
- /// The hit HP multiplier at OD = 0.
- ///
- private const double hp_multiplier_max = 1;
-
- ///
- /// The MISS HP multiplier at OD = 0.
- ///
- private const double hp_multiplier_miss_min = 0.5;
-
- ///
- /// The MISS HP multiplier at OD = 5.
- ///
- private const double hp_multiplier_miss_mid = 0.75;
-
- ///
- /// The MISS HP multiplier at OD = 10.
- ///
- private const double hp_multiplier_miss_max = 1;
-
- ///
- /// The MISS HP multiplier. This is multiplied to the miss hp increase.
- ///
- private double hpMissMultiplier = 1;
-
- ///
- /// The HIT HP multiplier. This is multiplied to hit hp increases.
- ///
- private double hpMultiplier = 1;
-
public ManiaScoreProcessor(IBeatmap beatmap)
: base(beatmap)
{
}
- protected override void ApplyBeatmap(IBeatmap beatmap)
- {
- base.ApplyBeatmap(beatmap);
-
- BeatmapDifficulty difficulty = beatmap.BeatmapInfo.BaseDifficulty;
- hpMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_min, hp_multiplier_mid, hp_multiplier_max);
- hpMissMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_miss_min, hp_multiplier_miss_mid, hp_multiplier_miss_max);
- }
-
- protected override void SimulateAutoplay(IBeatmap beatmap)
- {
- while (true)
- {
- base.SimulateAutoplay(beatmap);
-
- if (!HasFailed)
- break;
-
- hpMultiplier *= 1.01;
- hpMissMultiplier *= 0.98;
-
- Reset(false);
- }
- }
-
- protected override double HealthAdjustmentFactorFor(JudgementResult result)
- => result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier;
-
public override HitWindows CreateHitWindows() => new ManiaHitWindows();
}
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs
new file mode 100644
index 0000000000..f3739ce7c2
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs
@@ -0,0 +1,67 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Audio;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Mania.Skinning
+{
+ public class ManiaLegacySkinTransformer : ISkin
+ {
+ private readonly ISkin source;
+
+ public ManiaLegacySkinTransformer(ISkin source)
+ {
+ this.source = source;
+ }
+
+ public Drawable GetDrawableComponent(ISkinComponent component)
+ {
+ switch (component)
+ {
+ case GameplaySkinComponent resultComponent:
+ return getResult(resultComponent);
+ }
+
+ return null;
+ }
+
+ private Drawable getResult(GameplaySkinComponent resultComponent)
+ {
+ switch (resultComponent.Component)
+ {
+ case HitResult.Miss:
+ return this.GetAnimation("mania-hit0", true, true);
+
+ case HitResult.Meh:
+ return this.GetAnimation("mania-hit50", true, true);
+
+ case HitResult.Ok:
+ return this.GetAnimation("mania-hit100", true, true);
+
+ case HitResult.Good:
+ return this.GetAnimation("mania-hit200", true, true);
+
+ case HitResult.Great:
+ return this.GetAnimation("mania-hit300", true, true);
+
+ case HitResult.Perfect:
+ return this.GetAnimation("mania-hit300g", true, true);
+ }
+
+ return null;
+ }
+
+ public Texture GetTexture(string componentName) => source.GetTexture(componentName);
+
+ public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample);
+
+ public IBindable GetConfig(TLookup lookup) =>
+ source.GetConfig(lookup);
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
index 63110b2797..831e4a700f 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
@@ -18,7 +18,7 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Mods
{
- public class OsuModBlinds : Mod, IApplicableToDrawableRuleset, IApplicableToScoreProcessor
+ public class OsuModBlinds : Mod, IApplicableToDrawableRuleset, IApplicableToHealthProcessor
{
public override string Name => "Blinds";
public override string Description => "Play with blinds on your screen.";
@@ -37,9 +37,9 @@ namespace osu.Game.Rulesets.Osu.Mods
drawableRuleset.Overlays.Add(blinds = new DrawableOsuBlinds(drawableRuleset.Playfield.HitObjectContainer, drawableRuleset.Beatmap));
}
- public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
+ public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
{
- scoreProcessor.Health.ValueChanged += health => { blinds.AnimateClosedness((float)health.NewValue); };
+ healthProcessor.Health.ValueChanged += health => { blinds.AnimateClosedness((float)health.NewValue); };
}
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs
index adca95cf8a..9bf7525d33 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs
@@ -5,7 +5,7 @@ using osu.Framework.Graphics.Sprites;
namespace osu.Game.Rulesets.Osu.Mods
{
- public class OsuModDeflate : OsuModeObjectScaleTween
+ public class OsuModDeflate : OsuModObjectScaleTween
{
public override string Name => "Deflate";
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
index 3c81203ad7..76676ce888 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
@@ -5,7 +5,7 @@ using osu.Framework.Graphics.Sprites;
namespace osu.Game.Rulesets.Osu.Mods
{
- internal class OsuModGrow : OsuModeObjectScaleTween
+ internal class OsuModGrow : OsuModObjectScaleTween
{
public override string Name => "Grow";
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs
similarity index 96%
rename from osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs
rename to osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs
index 923278f484..42ddddc4dd 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods
///
/// Adjusts the size of hit objects during their fade in animation.
///
- public abstract class OsuModeObjectScaleTween : Mod, IReadFromConfig, IApplicableToDrawableHitObjects
+ public abstract class OsuModObjectScaleTween : Mod, IReadFromConfig, IApplicableToDrawableHitObjects
{
public override ModType Type => ModType.Fun;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs
index e786ec86f9..eae218509e 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override double ScoreMultiplier => 1;
// todo: this mod should be able to be compatible with hidden with a bit of further implementation.
- public override Type[] IncompatibleMods => new[] { typeof(OsuModeObjectScaleTween), typeof(OsuModHidden), typeof(OsuModTraceable) };
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModObjectScaleTween), typeof(OsuModHidden), typeof(OsuModTraceable) };
private const int rotate_offset = 360;
private const float rotate_starting_width = 2;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
index cf1ce517e8..dff9a77807 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Description => "Put your faith in the approach circles...";
public override double ScoreMultiplier => 1;
- public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden), typeof(OsuModSpinIn), typeof(OsuModeObjectScaleTween) };
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden), typeof(OsuModSpinIn), typeof(OsuModObjectScaleTween) };
private Bindable increaseFirstObjectVisibility = new Bindable();
public void ReadFromConfig(OsuConfigManager config)
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index c8a156dc57..c9ea5e6cf1 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -38,6 +38,8 @@ namespace osu.Game.Rulesets.Osu
public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new OsuScoreProcessor(beatmap);
+ public override HealthProcessor CreateHealthProcessor(IBeatmap beatmap) => new OsuHealthProcessor(beatmap);
+
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap, this);
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap);
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs
new file mode 100644
index 0000000000..36ccc80af6
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs
@@ -0,0 +1,54 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu.Judgements;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Osu.Scoring
+{
+ public class OsuHealthProcessor : HealthProcessor
+ {
+ public OsuHealthProcessor(IBeatmap beatmap)
+ : base(beatmap)
+ {
+ }
+
+ private float hpDrainRate;
+
+ protected override void ApplyBeatmap(IBeatmap beatmap)
+ {
+ base.ApplyBeatmap(beatmap);
+
+ hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate;
+ }
+
+ protected override double HealthAdjustmentFactorFor(JudgementResult result)
+ {
+ switch (result.Type)
+ {
+ case HitResult.Great:
+ return 10.2 - hpDrainRate;
+
+ case HitResult.Good:
+ return 8 - hpDrainRate;
+
+ case HitResult.Meh:
+ return 4 - hpDrainRate;
+
+ // case HitResult.SliderTick:
+ // return Math.Max(7 - hpDrainRate, 0) * 0.01;
+
+ case HitResult.Miss:
+ return hpDrainRate;
+
+ default:
+ return 0;
+ }
+ }
+
+ protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new OsuJudgementResult(hitObject, judgement);
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
index 6779271cb3..4593364e42 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
@@ -16,39 +16,6 @@ namespace osu.Game.Rulesets.Osu.Scoring
{
}
- private float hpDrainRate;
-
- protected override void ApplyBeatmap(IBeatmap beatmap)
- {
- base.ApplyBeatmap(beatmap);
-
- hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate;
- }
-
- protected override double HealthAdjustmentFactorFor(JudgementResult result)
- {
- switch (result.Type)
- {
- case HitResult.Great:
- return 10.2 - hpDrainRate;
-
- case HitResult.Good:
- return 8 - hpDrainRate;
-
- case HitResult.Meh:
- return 4 - hpDrainRate;
-
- // case HitResult.SliderTick:
- // return Math.Max(7 - hpDrainRate, 0) * 0.01;
-
- case HitResult.Miss:
- return hpDrainRate;
-
- default:
- return 0;
- }
- }
-
protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new OsuJudgementResult(hitObject, judgement);
public override HitWindows CreateHitWindows() => new OsuHitWindows();
diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs
new file mode 100644
index 0000000000..c8aa32a678
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs
@@ -0,0 +1,58 @@
+// Copyright (c) ppy Pty Ltd . 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;
+
+namespace osu.Game.Rulesets.Taiko.Scoring
+{
+ public class TaikoHealthProcessor : HealthProcessor
+ {
+ ///
+ /// A value used for calculating .
+ ///
+ private const double object_count_factor = 3;
+
+ ///
+ /// Taiko fails at the end of the map if the player has not half-filled their HP bar.
+ ///
+ protected override bool DefaultFailCondition => JudgedHits == MaxHits && Health.Value <= 0.5;
+
+ ///
+ /// HP multiplier for a successful .
+ ///
+ private double hpMultiplier;
+
+ ///
+ /// HP multiplier for a .
+ ///
+ private double hpMissMultiplier;
+
+ public TaikoHealthProcessor(IBeatmap beatmap)
+ : base(beatmap)
+ {
+ }
+
+ protected override void ApplyBeatmap(IBeatmap beatmap)
+ {
+ base.ApplyBeatmap(beatmap);
+
+ hpMultiplier = 1 / (object_count_factor * beatmap.HitObjects.OfType().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);
+ }
+
+ protected override double HealthAdjustmentFactorFor(JudgementResult result)
+ => result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier;
+
+ protected override void Reset(bool storeResults)
+ {
+ base.Reset(storeResults);
+
+ Health.Value = 0;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
index ae593d2e3a..10011d2669 100644
--- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
+++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
@@ -1,60 +1,18 @@
// Copyright (c) ppy Pty Ltd . 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;
namespace osu.Game.Rulesets.Taiko.Scoring
{
internal class TaikoScoreProcessor : ScoreProcessor
{
- ///
- /// A value used for calculating .
- ///
- private const double object_count_factor = 3;
-
- ///
- /// Taiko fails at the end of the map if the player has not half-filled their HP bar.
- ///
- protected override bool DefaultFailCondition => JudgedHits == MaxHits && Health.Value <= 0.5;
-
- ///
- /// HP multiplier for a successful .
- ///
- private double hpMultiplier;
-
- ///
- /// HP multiplier for a .
- ///
- private double hpMissMultiplier;
-
public TaikoScoreProcessor(IBeatmap beatmap)
: base(beatmap)
{
}
- protected override void ApplyBeatmap(IBeatmap beatmap)
- {
- base.ApplyBeatmap(beatmap);
-
- hpMultiplier = 1 / (object_count_factor * beatmap.HitObjects.OfType().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);
- }
-
- protected override double HealthAdjustmentFactorFor(JudgementResult result)
- => result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier;
-
- protected override void Reset(bool storeResults)
- {
- base.Reset(storeResults);
-
- Health.Value = 0;
- }
-
public override HitWindows CreateHitWindows() => new TaikoHitWindows();
}
}
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index 5890ed2976..d713a4145d 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Taiko
public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new TaikoScoreProcessor(beatmap);
+ public override HealthProcessor CreateHealthProcessor(IBeatmap beatmap) => new TaikoHealthProcessor(beatmap);
+
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap, this);
public const string SHORT_NAME = "taiko";
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
index 992c47f856..81050b1637 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
@@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override void LoadComplete()
{
base.LoadComplete();
- ScoreProcessor.FailConditions += (_, __) => true;
+ HealthProcessor.FailConditions += (_, __) => true;
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
index 1580aac8c5..2045072c79 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
@@ -22,12 +22,12 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddUntilStep("wait for fail", () => Player.HasFailed);
AddUntilStep("wait for multiple judged objects", () => ((FailPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.Count(h => h.AllJudged) > 1);
- AddAssert("total judgements == 1", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits == 1);
+ AddAssert("total judgements == 1", () => ((FailPlayer)Player).HealthProcessor.JudgedHits >= 1);
}
private class FailPlayer : TestPlayer
{
- public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
+ public new HealthProcessor HealthProcessor => base.HealthProcessor;
public FailPlayer()
: base(false, false)
@@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override void LoadComplete()
{
base.LoadComplete();
- ScoreProcessor.FailConditions += (_, __) => true;
+ HealthProcessor.FailConditions += (_, __) => true;
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
index 39c42980ab..ee58219cd3 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
@@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddStep("create overlay", () =>
{
- Child = hudOverlay = new HUDOverlay(null, null, Array.Empty());
+ Child = hudOverlay = new HUDOverlay(null, null, null, Array.Empty());
action?.Invoke(hudOverlay);
});
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
index e04315894e..1a83e35e4f 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
@@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestResumeWithResumeOverlay()
{
AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre));
- AddUntilStep("wait for hitobjects", () => Player.ScoreProcessor.Health.Value < 1);
+ AddUntilStep("wait for hitobjects", () => Player.HealthProcessor.Health.Value < 1);
pauseAndConfirm();
resume();
@@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestPauseWithResumeOverlay()
{
AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre));
- AddUntilStep("wait for hitobjects", () => Player.ScoreProcessor.Health.Value < 1);
+ AddUntilStep("wait for hitobjects", () => Player.HealthProcessor.Health.Value < 1);
pauseAndConfirm();
@@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddStep("move cursor to button", () =>
InputManager.MoveMouseTo(Player.HUDOverlay.HoldToQuit.Children.OfType().First().ScreenSpaceDrawQuad.Centre));
- AddUntilStep("wait for hitobjects", () => Player.ScoreProcessor.Health.Value < 1);
+ AddUntilStep("wait for hitobjects", () => Player.HealthProcessor.Health.Value < 1);
pauseAndConfirm();
resumeAndConfirm();
@@ -285,7 +285,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected class PausePlayer : TestPlayer
{
- public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
+ public new HealthProcessor HealthProcessor => base.HealthProcessor;
public new HUDOverlay HUDOverlay => base.HUDOverlay;
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs
index d8a4514df1..35e5f9719c 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs
@@ -67,9 +67,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddRepeatStep(@"add many simple", sendManyNotifications, 3);
- AddWaitStep("wait some", 5);
-
- checkProgressingCount(0);
+ waitForCompletion();
AddStep(@"progress #3", sendUploadProgress);
@@ -77,9 +75,7 @@ namespace osu.Game.Tests.Visual.UserInterface
checkDisplayedCount(33);
- AddWaitStep("wait some", 10);
-
- checkProgressingCount(0);
+ waitForCompletion();
}
[Test]
@@ -109,9 +105,9 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep(@"background progress #1", sendBackgroundUploadProgress);
- AddWaitStep("wait some", 5);
+ checkProgressingCount(1);
- checkProgressingCount(0);
+ waitForCompletion();
checkDisplayedCount(2);
@@ -190,6 +186,8 @@ namespace osu.Game.Tests.Visual.UserInterface
private void checkProgressingCount(int expected) => AddAssert($"progressing count is {expected}", () => progressingNotifications.Count == expected);
+ private void waitForCompletion() => AddUntilStep("wait for notification progress completion", () => progressingNotifications.Count == 0);
+
private void sendBarrage()
{
switch (RNG.Next(0, 4))
diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs
index fd455d7cd5..45fe034a70 100644
--- a/osu.Game/Database/ArchiveModelManager.cs
+++ b/osu.Game/Database/ArchiveModelManager.cs
@@ -259,6 +259,9 @@ namespace osu.Game.Database
///
/// Create a SHA-2 hash from the provided archive based on file content of all files matching .
///
+ ///
+ /// In the case of no matching files, a hash will be generated from the passed archive's .
+ ///
private string computeHash(ArchiveReader reader)
{
// for now, concatenate all .osu files in the set to create a unique hash.
@@ -270,7 +273,7 @@ namespace osu.Game.Database
s.CopyTo(hashable);
}
- return hashable.Length > 0 ? hashable.ComputeSHA2Hash() : null;
+ return hashable.Length > 0 ? hashable.ComputeSHA2Hash() : reader.Name.ComputeSHA2Hash();
}
///
diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs
index 563dc2dad9..958390d5d2 100644
--- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs
+++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs
@@ -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);
}
///
diff --git a/osu.Game/Graphics/UserInterface/ScreenTitle.cs b/osu.Game/Graphics/UserInterface/ScreenTitle.cs
index 10fc312d8b..ecd0508258 100644
--- a/osu.Game/Graphics/UserInterface/ScreenTitle.cs
+++ b/osu.Game/Graphics/UserInterface/ScreenTitle.cs
@@ -4,6 +4,7 @@
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Sprites;
using osuTK;
@@ -13,15 +14,15 @@ namespace osu.Game.Graphics.UserInterface
{
public abstract class ScreenTitle : CompositeDrawable, IHasAccentColour
{
- public const float ICON_WIDTH = ICON_SIZE + icon_spacing;
+ public const float ICON_WIDTH = ICON_SIZE + spacing;
public const float ICON_SIZE = 25;
+ private const float spacing = 6;
+ private const int text_offset = 2;
private SpriteIcon iconSprite;
private readonly OsuSpriteText titleText, pageText;
- private const float icon_spacing = 10;
-
protected IconUsage Icon
{
set
@@ -63,26 +64,35 @@ namespace osu.Game.Graphics.UserInterface
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
- Spacing = new Vector2(icon_spacing, 0),
+ Spacing = new Vector2(spacing, 0),
+ Direction = FillDirection.Horizontal,
Children = new[]
{
- CreateIcon(),
- new FillFlowContainer
+ CreateIcon().With(t =>
{
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(6, 0),
- Children = new[]
- {
- titleText = new OsuSpriteText
- {
- Font = OsuFont.GetFont(size: 30, weight: FontWeight.Light),
- },
- pageText = new OsuSpriteText
- {
- Font = OsuFont.GetFont(size: 30, weight: FontWeight.Light),
- }
- }
+ t.Anchor = Anchor.Centre;
+ t.Origin = Anchor.Centre;
+ }),
+ titleText = new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold),
+ Margin = new MarginPadding { Bottom = text_offset }
+ },
+ new Circle
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(4),
+ Colour = Color4.Gray,
+ },
+ pageText = new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Font = OsuFont.GetFont(size: 20),
+ Margin = new MarginPadding { Bottom = text_offset }
}
}
},
diff --git a/osu.Game/Graphics/UserInterface/ScreenTitleTextureIcon.cs b/osu.Game/Graphics/UserInterface/ScreenTitleTextureIcon.cs
index f590e7e357..c2a13970de 100644
--- a/osu.Game/Graphics/UserInterface/ScreenTitleTextureIcon.cs
+++ b/osu.Game/Graphics/UserInterface/ScreenTitleTextureIcon.cs
@@ -4,7 +4,6 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osuTK;
@@ -16,8 +15,6 @@ namespace osu.Game.Graphics.UserInterface
///
public class ScreenTitleTextureIcon : CompositeDrawable
{
- private const float circle_allowance = 0.8f;
-
private readonly string textureName;
public ScreenTitleTextureIcon(string textureName)
@@ -26,38 +23,17 @@ namespace osu.Game.Graphics.UserInterface
}
[BackgroundDependencyLoader]
- private void load(TextureStore textures, OsuColour colours)
+ private void load(TextureStore textures)
{
- Size = new Vector2(ScreenTitle.ICON_SIZE / circle_allowance);
+ Size = new Vector2(ScreenTitle.ICON_SIZE);
- InternalChildren = new Drawable[]
+ InternalChild = new Sprite
{
- new CircularContainer
- {
- Masking = true,
- BorderColour = colours.Violet,
- BorderThickness = 3,
- MaskingSmoothness = 1,
- RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
- {
- new Sprite
- {
- RelativeSizeAxes = Axes.Both,
- Texture = textures.Get(textureName),
- Size = new Vector2(circle_allowance),
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- },
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = colours.Violet,
- Alpha = 0,
- AlwaysPresent = true,
- },
- }
- },
+ RelativeSizeAxes = Axes.Both,
+ Texture = textures.Get(textureName),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ FillMode = FillMode.Fit
};
}
}
diff --git a/osu.Game/Overlays/Changelog/ChangelogHeader.cs b/osu.Game/Overlays/Changelog/ChangelogHeader.cs
index b2e9be24b3..3b6f0d778d 100644
--- a/osu.Game/Overlays/Changelog/ChangelogHeader.cs
+++ b/osu.Game/Overlays/Changelog/ChangelogHeader.cs
@@ -47,6 +47,8 @@ namespace osu.Game.Overlays.Changelog
private void load(OsuColour colours)
{
TabControl.AccentColour = colours.Violet;
+ TitleBackgroundColour = colours.GreyVioletDarker;
+ ControlBackgroundColour = colours.GreyVioletDark;
}
private ChangelogHeaderTitle title;
@@ -111,7 +113,7 @@ namespace osu.Game.Overlays.Changelog
public ChangelogHeaderTitle()
{
- Title = "Changelog";
+ Title = "changelog";
Version = null;
}
diff --git a/osu.Game/Overlays/News/NewsHeader.cs b/osu.Game/Overlays/News/NewsHeader.cs
index 27620ab523..e3cf58ed0a 100644
--- a/osu.Game/Overlays/News/NewsHeader.cs
+++ b/osu.Game/Overlays/News/NewsHeader.cs
@@ -4,7 +4,6 @@
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Graphics;
@@ -15,7 +14,7 @@ namespace osu.Game.Overlays.News
{
public class NewsHeader : OverlayHeader
{
- private const string front_page_string = "Front Page";
+ private const string front_page_string = "frontpage";
private NewsHeaderTitle title;
@@ -33,16 +32,18 @@ namespace osu.Game.Overlays.News
ShowFrontPage?.Invoke();
};
- Current.ValueChanged += showArticle;
+ Current.ValueChanged += showPost;
}
[BackgroundDependencyLoader]
- private void load(OsuColour colour)
+ private void load(OsuColour colours)
{
- TabControl.AccentColour = colour.Violet;
+ TabControl.AccentColour = colours.Violet;
+ TitleBackgroundColour = colours.GreyVioletDarker;
+ ControlBackgroundColour = colours.GreyVioletDark;
}
- private void showArticle(ValueChangedEvent e)
+ private void showPost(ValueChangedEvent e)
{
if (e.OldValue != null)
TabControl.RemoveItem(e.OldValue);
@@ -52,19 +53,17 @@ namespace osu.Game.Overlays.News
TabControl.AddItem(e.NewValue);
TabControl.Current.Value = e.NewValue;
- title.IsReadingArticle = true;
+ title.IsReadingPost = true;
}
else
{
TabControl.Current.Value = front_page_string;
- title.IsReadingArticle = false;
+ title.IsReadingPost = false;
}
}
protected override Drawable CreateBackground() => new NewsHeaderBackground();
- protected override Drawable CreateContent() => new Container();
-
protected override ScreenTitle CreateTitle() => title = new NewsHeaderTitle();
private class NewsHeaderBackground : Sprite
@@ -84,17 +83,17 @@ namespace osu.Game.Overlays.News
private class NewsHeaderTitle : ScreenTitle
{
- private const string article_string = "Article";
+ private const string post_string = "post";
- public bool IsReadingArticle
+ public bool IsReadingPost
{
- set => Section = value ? article_string : front_page_string;
+ set => Section = value ? post_string : front_page_string;
}
public NewsHeaderTitle()
{
- Title = "News";
- IsReadingArticle = false;
+ Title = "news";
+ IsReadingPost = false;
}
protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/news");
diff --git a/osu.Game/Overlays/OverlayHeader.cs b/osu.Game/Overlays/OverlayHeader.cs
index 2e032db2ba..7a397d10c6 100644
--- a/osu.Game/Overlays/OverlayHeader.cs
+++ b/osu.Game/Overlays/OverlayHeader.cs
@@ -1,9 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using JetBrains.Annotations;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.UserInterface;
+using osuTK.Graphics;
namespace osu.Game.Overlays
{
@@ -11,59 +14,96 @@ namespace osu.Game.Overlays
{
protected readonly OverlayHeaderTabControl TabControl;
- private const float cover_height = 150;
- private const float cover_info_height = 75;
+ private readonly Box titleBackground;
+ private readonly Box controlBackground;
+ private readonly Container background;
+
+ protected Color4 TitleBackgroundColour
+ {
+ set => titleBackground.Colour = value;
+ }
+
+ protected Color4 ControlBackgroundColour
+ {
+ set => controlBackground.Colour = value;
+ }
+
+ protected float BackgroundHeight
+ {
+ set => background.Height = value;
+ }
protected OverlayHeader()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
- Children = new Drawable[]
+ Add(new FillFlowContainer
{
- new Container
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Children = new[]
{
- RelativeSizeAxes = Axes.X,
- Height = cover_height,
- Masking = true,
- Child = CreateBackground()
- },
- new Container
- {
- Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN },
- Y = cover_height,
- Height = cover_info_height,
- RelativeSizeAxes = Axes.X,
- Anchor = Anchor.TopLeft,
- Origin = Anchor.BottomLeft,
- Depth = -float.MaxValue,
- Children = new Drawable[]
+ background = new Container
{
- CreateTitle().With(t => t.X = -ScreenTitle.ICON_WIDTH),
- TabControl = new OverlayHeaderTabControl
+ RelativeSizeAxes = Axes.X,
+ Height = 80,
+ Masking = true,
+ Child = CreateBackground()
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Children = new Drawable[]
{
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- RelativeSizeAxes = Axes.X,
- Height = cover_info_height - 30,
- Margin = new MarginPadding { Left = -UserProfileOverlay.CONTENT_X_MARGIN },
- Padding = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }
+ titleBackground = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Gray,
+ },
+ CreateTitle().With(title =>
+ {
+ title.Margin = new MarginPadding
+ {
+ Vertical = 10,
+ Left = UserProfileOverlay.CONTENT_X_MARGIN
+ };
+ })
}
- }
- },
- new Container
- {
- Margin = new MarginPadding { Top = cover_height },
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Child = CreateContent()
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Depth = -float.MaxValue,
+ Children = new Drawable[]
+ {
+ controlBackground = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Gray,
+ },
+ TabControl = new OverlayHeaderTabControl
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ RelativeSizeAxes = Axes.X,
+ Height = 30,
+ Padding = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN },
+ }
+ }
+ },
+ CreateContent()
}
- };
+ });
}
protected abstract Drawable CreateBackground();
- protected abstract Drawable CreateContent();
+ [NotNull]
+ protected virtual Drawable CreateContent() => new Container();
protected abstract ScreenTitle CreateTitle();
}
diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs
index 76613c156d..4a792f7375 100644
--- a/osu.Game/Overlays/Profile/ProfileHeader.cs
+++ b/osu.Game/Overlays/Profile/ProfileHeader.cs
@@ -26,6 +26,8 @@ namespace osu.Game.Overlays.Profile
public ProfileHeader()
{
+ BackgroundHeight = 150;
+
User.ValueChanged += e => updateDisplay(e.NewValue);
TabControl.AddItem("Info");
@@ -38,6 +40,8 @@ namespace osu.Game.Overlays.Profile
private void load(OsuColour colours)
{
TabControl.AccentColour = colours.Seafoam;
+ TitleBackgroundColour = colours.GreySeafoamDarker;
+ ControlBackgroundColour = colours.GreySeafoam;
}
protected override Drawable CreateBackground() =>
@@ -101,8 +105,8 @@ namespace osu.Game.Overlays.Profile
{
public ProfileHeaderTitle()
{
- Title = "Player";
- Section = "Info";
+ Title = "player";
+ Section = "info";
}
[BackgroundDependencyLoader]
@@ -110,6 +114,8 @@ namespace osu.Game.Overlays.Profile
{
AccentColour = colours.Seafoam;
}
+
+ protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/profile");
}
}
}
diff --git a/osu.Game/Rulesets/Mods/IApplicableToHealthProcessor.cs b/osu.Game/Rulesets/Mods/IApplicableToHealthProcessor.cs
new file mode 100644
index 0000000000..a181955653
--- /dev/null
+++ b/osu.Game/Rulesets/Mods/IApplicableToHealthProcessor.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . 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
+ {
+ ///
+ /// Provide a to a mod. Called once on initialisation of a play instance.
+ ///
+ void ApplyToHealthProcessor(HealthProcessor healthProcessor);
+ }
+}
diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs
index a8058961ee..ec0f50c0be 100644
--- a/osu.Game/Rulesets/Mods/ModEasy.cs
+++ b/osu.Game/Rulesets/Mods/ModEasy.cs
@@ -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";
@@ -61,11 +60,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;
}
}
diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs
index 0994d1f7d3..afa263f1c9 100644
--- a/osu.Game/Rulesets/Mods/ModPerfect.cs
+++ b/osu.Game/Rulesets/Mods/ModPerfect.cs
@@ -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;
}
}
diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs
index c4c4ab1f04..a4d0631d8c 100644
--- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs
+++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs
@@ -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;
}
}
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index 107633194f..bfd6a16729 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -76,6 +76,12 @@ namespace osu.Game.Rulesets
/// The score processor.
public virtual ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new ScoreProcessor(beatmap);
+ ///
+ /// Creates a for a beatmap converted to this ruleset.
+ ///
+ /// The health processor.
+ public virtual HealthProcessor CreateHealthProcessor(IBeatmap beatmap) => new HealthProcessor(beatmap);
+
///
/// Creates a to convert a to one that is applicable for this .
///
diff --git a/osu.Game/Rulesets/Scoring/HealthProcessor.cs b/osu.Game/Rulesets/Scoring/HealthProcessor.cs
new file mode 100644
index 0000000000..d05e2d7b6b
--- /dev/null
+++ b/osu.Game/Rulesets/Scoring/HealthProcessor.cs
@@ -0,0 +1,84 @@
+// Copyright (c) ppy Pty Ltd . 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
+ {
+ ///
+ /// Invoked when the is in a failed state.
+ /// Return true if the fail was permitted.
+ ///
+ public event Func Failed;
+
+ ///
+ /// Additional conditions on top of that cause a failing state.
+ ///
+ public event Func FailConditions;
+
+ ///
+ /// The current health.
+ ///
+ public readonly BindableDouble Health = new BindableDouble(1) { MinValue = 0, MaxValue = 1 };
+
+ ///
+ /// Whether this ScoreProcessor has already triggered the failed state.
+ ///
+ 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
+ }
+
+ ///
+ /// An adjustment factor which is multiplied into the health increase provided by a .
+ ///
+ /// The for which the adjustment should apply.
+ /// The adjustment factor.
+ protected virtual double HealthAdjustmentFactorFor(JudgementResult result) => 1;
+
+ ///
+ /// The default conditions for failing.
+ ///
+ protected virtual bool DefaultFailCondition => Precision.AlmostBigger(Health.MinValue, Health.Value);
+
+ protected override void Reset(bool storeResults)
+ {
+ base.Reset(storeResults);
+
+ Health.Value = 1;
+ HasFailed = false;
+ }
+ }
+}
diff --git a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs
new file mode 100644
index 0000000000..c7ac466eb0
--- /dev/null
+++ b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs
@@ -0,0 +1,146 @@
+// Copyright (c) ppy Pty Ltd . 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
+ {
+ ///
+ /// Invoked when all s have been judged by this .
+ ///
+ public event Action AllJudged;
+
+ ///
+ /// Invoked when a new judgement has occurred. This occurs after the judgement has been processed by this .
+ ///
+ public event Action NewJudgement;
+
+ ///
+ /// The maximum number of hits that can be judged.
+ ///
+ protected int MaxHits { get; private set; }
+
+ ///
+ /// The total number of judged s at the current point in time.
+ ///
+ public int JudgedHits { get; private set; }
+
+ ///
+ /// Whether all s have been processed.
+ ///
+ public bool HasCompleted => JudgedHits == MaxHits;
+
+ protected JudgementProcessor(IBeatmap beatmap)
+ {
+ ApplyBeatmap(beatmap);
+
+ Reset(false);
+ SimulateAutoplay(beatmap);
+ Reset(true);
+ }
+
+ ///
+ /// Applies any properties of the which affect scoring to this .
+ ///
+ /// The to read properties from.
+ protected virtual void ApplyBeatmap(IBeatmap beatmap)
+ {
+ }
+
+ ///
+ /// Applies the score change of a to this .
+ ///
+ /// The to apply.
+ public void ApplyResult(JudgementResult result)
+ {
+ JudgedHits++;
+
+ ApplyResultInternal(result);
+
+ NewJudgement?.Invoke(result);
+
+ if (HasCompleted)
+ AllJudged?.Invoke();
+ }
+
+ ///
+ /// Reverts the score change of a that was applied to this .
+ ///
+ /// The judgement scoring result.
+ public void RevertResult(JudgementResult result)
+ {
+ JudgedHits--;
+
+ RevertResultInternal(result);
+ }
+
+ ///
+ /// Applies the score change of a to this .
+ ///
+ ///
+ /// Any changes applied via this method can be reverted via .
+ ///
+ /// The to apply.
+ protected abstract void ApplyResultInternal(JudgementResult result);
+
+ ///
+ /// Reverts the score change of a that was applied to this via .
+ ///
+ /// The judgement scoring result.
+ protected abstract void RevertResultInternal(JudgementResult result);
+
+ ///
+ /// Resets this to a default state.
+ ///
+ /// Whether to store the current state of the for future use.
+ protected virtual void Reset(bool storeResults)
+ {
+ if (storeResults)
+ MaxHits = JudgedHits;
+
+ JudgedHits = 0;
+ }
+
+ ///
+ /// Creates the that represents the scoring result for a .
+ ///
+ /// The which was judged.
+ /// The that provides the scoring information.
+ protected virtual JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new JudgementResult(hitObject, judgement);
+
+ ///
+ /// Simulates an autoplay of the to determine scoring values.
+ ///
+ /// This provided temporarily. DO NOT USE.
+ /// The to simulate.
+ 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);
+ }
+ }
+ }
+}
diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
index a8a2294498..acd394d955 100644
--- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
+++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
@@ -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;
- ///
- /// Invoked when the is in a failed state.
- /// This may occur regardless of whether an event is invoked.
- /// Return true if the fail was permitted.
- ///
- public event Func Failed;
-
- ///
- /// Invoked when all s have been judged.
- ///
- public event Action AllJudged;
-
- ///
- /// Invoked when a new judgement has occurred. This occurs after the judgement has been processed by the .
- ///
- public event Action NewJudgement;
-
- ///
- /// Additional conditions on top of that cause a failing state.
- ///
- public event Func FailConditions;
-
///
/// The current total score.
///
@@ -55,11 +30,6 @@ namespace osu.Game.Rulesets.Scoring
///
public readonly BindableDouble Accuracy = new BindableDouble(1) { MinValue = 0, MaxValue = 1 };
- ///
- /// The current health.
- ///
- public readonly BindableDouble Health = new BindableDouble(1) { MinValue = 0, MaxValue = 1 };
-
///
/// The current combo.
///
@@ -85,26 +55,6 @@ namespace osu.Game.Rulesets.Scoring
///
public readonly Bindable Mode = new Bindable();
- ///
- /// Whether all s have been processed.
- ///
- public bool HasCompleted => JudgedHits == MaxHits;
-
- ///
- /// Whether this ScoreProcessor has already triggered the failed state.
- ///
- public bool HasFailed { get; private set; }
-
- ///
- /// The maximum number of hits that can be judged.
- ///
- protected int MaxHits { get; private set; }
-
- ///
- /// The total number of judged s at the current point in time.
- ///
- 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
};
}
- ///
- /// Applies any properties of the which affect scoring to this .
- ///
- /// The to read properties from.
- protected virtual void ApplyBeatmap(IBeatmap beatmap)
- {
- }
-
- ///
- /// Simulates an autoplay of the to determine scoring values.
- ///
- /// This provided temporarily. DO NOT USE.
- /// The to simulate.
- 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);
- }
- }
-
- ///
- /// Applies the score change of a to this .
- ///
- /// The to apply.
- public void ApplyResult(JudgementResult result)
- {
- ApplyResultInternal(result);
-
- updateScore();
- updateFailed(result);
-
- NewJudgement?.Invoke(result);
-
- if (HasCompleted)
- AllJudged?.Invoke();
- }
-
- ///
- /// Reverts the score change of a that was applied to this .
- ///
- /// The judgement scoring result.
- public void RevertResult(JudgementResult result)
- {
- RevertResultInternal(result);
- updateScore();
- }
-
private readonly Dictionary scoreResultCounts = new Dictionary();
- ///
- /// Applies the score change of a to this .
- ///
- ///
- /// Any changes applied via this method can be reverted via .
- ///
- /// The to apply.
- 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();
}
- ///
- /// Reverts the score change of a that was applied to this via .
- ///
- /// The judgement scoring result.
- 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;
}
- }
- ///
- /// An adjustment factor which is multiplied into the health increase provided by a .
- ///
- /// The for which the adjustment should apply.
- /// The adjustment factor.
- protected virtual double HealthAdjustmentFactorFor(JudgementResult result) => 1;
+ updateScore();
+ }
private void updateScore()
{
@@ -330,24 +191,6 @@ namespace osu.Game.Rulesets.Scoring
}
}
- ///
- /// Checks if the score is in a failed state and notifies subscribers.
- ///
- /// This can only ever notify subscribers once.
- ///
- ///
- 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.
///
/// Whether to store the current state of the for future use.
- 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;
}
///
@@ -416,22 +256,10 @@ namespace osu.Game.Rulesets.Scoring
score.Statistics[result] = GetStatistic(result);
}
- ///
- /// The default conditions for failing.
- ///
- protected virtual bool DefaultFailCondition => Precision.AlmostBigger(Health.MinValue, Health.Value);
-
///
/// Create a for this processor.
///
public virtual HitWindows CreateHitWindows() => new HitWindows();
-
- ///
- /// Creates the that represents the scoring result for a .
- ///
- /// The which was judged.
- /// The that provides the scoring information.
- protected virtual JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new JudgementResult(hitObject, judgement);
}
public enum ScoringMode
diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs
index e2f362780d..236bdc8442 100644
--- a/osu.Game/Screens/Play/HUDOverlay.cs
+++ b/osu.Game/Screens/Play/HUDOverlay.cs
@@ -41,6 +41,7 @@ namespace osu.Game.Screens.Play
public Bindable ShowHealthbar = new Bindable(true);
private readonly ScoreProcessor scoreProcessor;
+ private readonly HealthProcessor healthProcessor;
private readonly DrawableRuleset drawableRuleset;
private readonly IReadOnlyList mods;
@@ -63,9 +64,10 @@ namespace osu.Game.Screens.Play
private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter };
- public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods)
+ public HUDOverlay(ScoreProcessor scoreProcessor, HealthProcessor healthProcessor, DrawableRuleset drawableRuleset, IReadOnlyList 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);
+ }
}
}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 8970f9ac88..f0960371e3 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -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())
mod.ApplyToScoreProcessor(ScoreProcessor);
+
+ foreach (var mod in Mods.Value.OfType())
+ 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
}
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 0c0a58d533..3f64ce8dfd 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -22,8 +22,8 @@
-
-
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index edeeea239e..c2d4e3ad8e 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -73,8 +73,8 @@
-
-
+
+
@@ -82,7 +82,7 @@
-
+