diff --git a/osu.Android.props b/osu.Android.props
index dd11804b90..e7f90af5fd 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -54,6 +54,6 @@
-
+
diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs
index 8818cef8eb..80bb82c769 100644
--- a/osu.Desktop/DiscordRichPresence.cs
+++ b/osu.Desktop/DiscordRichPresence.cs
@@ -1,6 +1,8 @@
// 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.Text;
using DiscordRPC;
using DiscordRPC.Message;
using osu.Framework.Allocation;
@@ -43,6 +45,10 @@ namespace osu.Desktop
};
client.OnReady += onReady;
+
+ // safety measure for now, until we performance test / improve backoff for failed connections.
+ client.OnConnectionFailed += (_, __) => client.Deinitialize();
+
client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network);
(user = provider.LocalUser.GetBoundCopy()).BindValueChanged(u =>
@@ -77,8 +83,8 @@ namespace osu.Desktop
if (status.Value is UserStatusOnline && activity.Value != null)
{
- presence.State = activity.Value.Status;
- presence.Details = getDetails(activity.Value);
+ presence.State = truncate(activity.Value.Status);
+ presence.Details = truncate(getDetails(activity.Value));
}
else
{
@@ -96,6 +102,27 @@ namespace osu.Desktop
client.SetPresence(presence);
}
+ private static readonly int ellipsis_length = Encoding.UTF8.GetByteCount(new[] { '…' });
+
+ private string truncate(string str)
+ {
+ if (Encoding.UTF8.GetByteCount(str) <= 128)
+ return str;
+
+ ReadOnlyMemory strMem = str.AsMemory();
+
+ do
+ {
+ strMem = strMem[..^1];
+ } while (Encoding.UTF8.GetByteCount(strMem.Span) + ellipsis_length > 128);
+
+ return string.Create(strMem.Length + 1, strMem, (span, mem) =>
+ {
+ mem.Span.CopyTo(span);
+ span[^1] = '…';
+ });
+ }
+
private string getDetails(UserActivity activity)
{
switch (activity)
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
index 088f187621..90a6e609f0 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
@@ -14,8 +14,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
{
public class CatchBeatmapConverter : BeatmapConverter
{
- public CatchBeatmapConverter(IBeatmap beatmap)
- : base(beatmap)
+ public CatchBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
+ : base(beatmap, ruleset)
{
}
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index b8a844cb86..f0e50c5ba5 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -24,13 +24,16 @@ using System;
namespace osu.Game.Rulesets.Catch
{
- public class CatchRuleset : Ruleset
+ public class CatchRuleset : Ruleset, ILegacyRuleset
{
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableCatchRuleset(this, beatmap, mods);
public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new CatchScoreProcessor(beatmap);
- public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(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";
@@ -106,6 +109,12 @@ namespace osu.Game.Rulesets.Catch
new CatchModFlashlight(),
};
+ case ModType.Conversion:
+ return new Mod[]
+ {
+ new CatchModDifficultyAdjust(),
+ };
+
case ModType.Automation:
return new Mod[]
{
@@ -134,7 +143,7 @@ namespace osu.Game.Rulesets.Catch
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new CatchPerformanceCalculator(this, beatmap, score);
- public override int? LegacyID => 2;
+ public int LegacyID => 2;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame();
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
new file mode 100644
index 0000000000..4c0f5d510e
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
@@ -0,0 +1,49 @@
+// 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.Bindables;
+using osu.Game.Beatmaps;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Mods;
+
+namespace osu.Game.Rulesets.Catch.Mods
+{
+ public class CatchModDifficultyAdjust : ModDifficultyAdjust
+ {
+ [SettingSource("Fruit Size", "Override a beatmap's set CS.")]
+ public BindableNumber CircleSize { get; } = new BindableFloat
+ {
+ Precision = 0.1f,
+ MinValue = 1,
+ MaxValue = 10,
+ Default = 5,
+ Value = 5,
+ };
+
+ [SettingSource("Approach Rate", "Override a beatmap's set AR.")]
+ public BindableNumber ApproachRate { get; } = new BindableFloat
+ {
+ Precision = 0.1f,
+ MinValue = 1,
+ MaxValue = 10,
+ Default = 5,
+ Value = 5,
+ };
+
+ protected override void TransferSettings(BeatmapDifficulty difficulty)
+ {
+ base.TransferSettings(difficulty);
+
+ CircleSize.Value = CircleSize.Default = difficulty.CircleSize;
+ ApproachRate.Value = ApproachRate.Default = difficulty.ApproachRate;
+ }
+
+ protected override void ApplySettings(BeatmapDifficulty difficulty)
+ {
+ base.ApplySettings(difficulty);
+
+ difficulty.CircleSize = CircleSize.Value;
+ difficulty.ApproachRate = ApproachRate.Value;
+ }
+ }
+}
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/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index c2f92090a1..1a77a4944b 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -35,10 +35,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
private ManiaBeatmap beatmap;
- public ManiaBeatmapConverter(IBeatmap beatmap)
- : base(beatmap)
+ public ManiaBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
+ : base(beatmap, ruleset)
{
- IsForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(new ManiaRuleset().RulesetInfo);
+ IsForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
var roundedCircleSize = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.CircleSize);
var roundedOverallDifficulty = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index bf630cf892..a579a7d07e 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -31,13 +31,15 @@ using osu.Game.Scoring;
namespace osu.Game.Rulesets.Mania
{
- public class ManiaRuleset : Ruleset
+ public class ManiaRuleset : Ruleset, ILegacyRuleset
{
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableManiaRuleset(this, beatmap, mods);
public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new ManiaScoreProcessor(beatmap);
- public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(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);
@@ -151,6 +153,7 @@ namespace osu.Game.Rulesets.Mania
new ManiaModRandom(),
new ManiaModDualStages(),
new ManiaModMirror(),
+ new ManiaModDifficultyAdjust(),
};
case ModType.Automation:
@@ -178,7 +181,7 @@ namespace osu.Game.Rulesets.Mania
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(this, beatmap);
- public override int? LegacyID => 3;
+ public int LegacyID => 3;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame();
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs
new file mode 100644
index 0000000000..0817f8f9fc
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs
@@ -0,0 +1,11 @@
+// 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.Mods;
+
+namespace osu.Game.Rulesets.Mania.Mods
+{
+ public class ManiaModDifficultyAdjust : ModDifficultyAdjust
+ {
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
index 70ba5cd938..877a9ee410 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Replays
public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
{
// We don't need to fully convert, just create the converter
- var converter = new ManiaBeatmapConverter(beatmap);
+ var converter = new ManiaBeatmapConverter(beatmap, new ManiaRuleset());
// NB: Via co-op mod, osu-stable can have two stages with floor(col/2) and ceil(col/2) columns. This will need special handling
// elsewhere in the game if we do choose to support the old co-op mod anyway. For now, assume that there is only one stage.
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.Osu.Tests/TestSceneLegacyBeatmapSkin.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs
new file mode 100644
index 0000000000..4676f14655
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs
@@ -0,0 +1,155 @@
+// 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 NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Graphics;
+using osu.Framework.IO.Stores;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Screens;
+using osu.Game.Screens.Play;
+using osu.Game.Skinning;
+using osu.Game.Tests.Visual;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestSceneLegacyBeatmapSkin : OsuTestScene
+ {
+ [Resolved]
+ private AudioManager audio { get; set; }
+
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestBeatmapComboColours(bool customSkinColoursPresent)
+ {
+ ExposedPlayer player = null;
+
+ AddStep("load coloured beatmap", () => player = loadBeatmap(customSkinColoursPresent, true));
+ AddUntilStep("wait for player", () => player.IsLoaded);
+
+ AddAssert("is beatmap skin colours", () => player.UsableComboColours.SequenceEqual(TestBeatmapSkin.Colours));
+ }
+
+ [Test]
+ public void TestBeatmapNoComboColours()
+ {
+ ExposedPlayer player = null;
+
+ AddStep("load no-colour beatmap", () => player = loadBeatmap(false, false));
+ AddUntilStep("wait for player", () => player.IsLoaded);
+
+ AddAssert("is default user skin colours", () => player.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
+ }
+
+ [Test]
+ public void TestBeatmapNoComboColoursSkinOverride()
+ {
+ ExposedPlayer player = null;
+
+ AddStep("load custom-skin colour", () => player = loadBeatmap(true, false));
+ AddUntilStep("wait for player", () => player.IsLoaded);
+
+ AddAssert("is custom user skin colours", () => player.UsableComboColours.SequenceEqual(TestSkin.Colours));
+ }
+
+ private ExposedPlayer loadBeatmap(bool userHasCustomColours, bool beatmapHasColours)
+ {
+ ExposedPlayer player;
+
+ Beatmap.Value = new CustomSkinWorkingBeatmap(audio, beatmapHasColours);
+ Child = new OsuScreenStack(player = new ExposedPlayer(userHasCustomColours)) { RelativeSizeAxes = Axes.Both };
+
+ return player;
+ }
+
+ private class ExposedPlayer : Player
+ {
+ private readonly bool userHasCustomColours;
+
+ public ExposedPlayer(bool userHasCustomColours)
+ : base(false, false)
+ {
+ this.userHasCustomColours = userHasCustomColours;
+ }
+
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
+ {
+ var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+ dependencies.CacheAs(new TestSkin(userHasCustomColours));
+ return dependencies;
+ }
+
+ public IReadOnlyList UsableComboColours =>
+ GameplayClockContainer.ChildrenOfType()
+ .First()
+ .GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value;
+ }
+
+ private class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap
+ {
+ private readonly bool hasColours;
+
+ public CustomSkinWorkingBeatmap(AudioManager audio, bool hasColours)
+ : base(new Beatmap
+ {
+ BeatmapInfo =
+ {
+ BeatmapSet = new BeatmapSetInfo(),
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
+ HitObjects = { new HitCircle { Position = new Vector2(256, 192) } }
+ }, null, null, audio)
+ {
+ this.hasColours = hasColours;
+ }
+
+ protected override ISkin GetSkin() => new TestBeatmapSkin(BeatmapInfo, hasColours);
+ }
+
+ private class TestBeatmapSkin : LegacyBeatmapSkin
+ {
+ public static Color4[] Colours { get; } =
+ {
+ new Color4(50, 100, 150, 255),
+ new Color4(40, 80, 120, 255),
+ };
+
+ public TestBeatmapSkin(BeatmapInfo beatmap, bool hasColours)
+ : base(beatmap, new ResourceStore(), null)
+ {
+ if (hasColours)
+ Configuration.AddComboColours(Colours);
+ }
+ }
+
+ private class TestSkin : LegacySkin, ISkinSource
+ {
+ public static Color4[] Colours { get; } =
+ {
+ new Color4(150, 100, 50, 255),
+ new Color4(20, 20, 20, 255),
+ };
+
+ public TestSkin(bool hasCustomColours)
+ : base(new SkinInfo(), null, null, string.Empty)
+ {
+ if (hasCustomColours)
+ Configuration.AddComboColours(Colours);
+ }
+
+ public event Action SourceChanged
+ {
+ add { }
+ remove { }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
index 02ce77e707..bd9d948782 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
@@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Tests
EndTime = 6000,
},
// placeholder object to avoid hitting the results screen
- new HitObject
+ new HitCircle
{
StartTime = 99999,
}
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
index a05fb5e9de..147d74c929 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
@@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
{
public class OsuBeatmapConverter : BeatmapConverter
{
- public OsuBeatmapConverter(IBeatmap beatmap)
- : base(beatmap)
+ public OsuBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
+ : base(beatmap, ruleset)
{
}
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/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
new file mode 100644
index 0000000000..0514e2ab34
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
@@ -0,0 +1,49 @@
+// 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.Bindables;
+using osu.Game.Beatmaps;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Mods;
+
+namespace osu.Game.Rulesets.Osu.Mods
+{
+ public class OsuModDifficultyAdjust : ModDifficultyAdjust
+ {
+ [SettingSource("Circle Size", "Override a beatmap's set CS.")]
+ public BindableNumber CircleSize { get; } = new BindableFloat
+ {
+ Precision = 0.1f,
+ MinValue = 1,
+ MaxValue = 10,
+ Default = 5,
+ Value = 5,
+ };
+
+ [SettingSource("Approach Rate", "Override a beatmap's set AR.")]
+ public BindableNumber ApproachRate { get; } = new BindableFloat
+ {
+ Precision = 0.1f,
+ MinValue = 1,
+ MaxValue = 10,
+ Default = 5,
+ Value = 5,
+ };
+
+ protected override void TransferSettings(BeatmapDifficulty difficulty)
+ {
+ base.TransferSettings(difficulty);
+
+ CircleSize.Value = CircleSize.Default = difficulty.CircleSize;
+ ApproachRate.Value = ApproachRate.Default = difficulty.ApproachRate;
+ }
+
+ protected override void ApplySettings(BeatmapDifficulty difficulty)
+ {
+ base.ApplySettings(difficulty);
+
+ difficulty.CircleSize = CircleSize.Value;
+ difficulty.ApproachRate = ApproachRate.Value;
+ }
+ }
+}
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 27af615935..c9ea5e6cf1 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -32,13 +32,15 @@ using System;
namespace osu.Game.Rulesets.Osu
{
- public class OsuRuleset : Ruleset
+ public class OsuRuleset : Ruleset, ILegacyRuleset
{
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableOsuRuleset(this, beatmap, mods);
public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new OsuScoreProcessor(beatmap);
- public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(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);
@@ -130,6 +132,7 @@ namespace osu.Game.Rulesets.Osu
return new Mod[]
{
new OsuModTarget(),
+ new OsuModDifficultyAdjust(),
};
case ModType.Automation:
@@ -178,7 +181,7 @@ namespace osu.Game.Rulesets.Osu
public override ISkin CreateLegacySkinProvider(ISkinSource source) => new OsuLegacySkinTransformer(source);
- public override int? LegacyID => 0;
+ public int LegacyID => 0;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame();
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/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index f506f31831..cc9d6e4470 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -39,10 +39,10 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
private readonly bool isForCurrentRuleset;
- public TaikoBeatmapConverter(IBeatmap beatmap)
- : base(beatmap)
+ public TaikoBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
+ : base(beatmap, ruleset)
{
- isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(new TaikoRuleset().RulesetInfo);
+ isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
}
public override bool CanConvert() => true;
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs
new file mode 100644
index 0000000000..56a73ad7df
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs
@@ -0,0 +1,11 @@
+// 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.Mods;
+
+namespace osu.Game.Rulesets.Taiko.Mods
+{
+ public class TaikoModDifficultyAdjust : ModDifficultyAdjust
+ {
+ }
+}
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 ca7ab30867..d713a4145d 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -24,13 +24,15 @@ using System;
namespace osu.Game.Rulesets.Taiko
{
- public class TaikoRuleset : Ruleset
+ public class TaikoRuleset : Ruleset, ILegacyRuleset
{
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableTaikoRuleset(this, beatmap, mods);
public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new TaikoScoreProcessor(beatmap);
- public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(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";
@@ -105,6 +107,12 @@ namespace osu.Game.Rulesets.Taiko
new TaikoModFlashlight(),
};
+ case ModType.Conversion:
+ return new Mod[]
+ {
+ new TaikoModDifficultyAdjust(),
+ };
+
case ModType.Automation:
return new Mod[]
{
@@ -133,7 +141,7 @@ namespace osu.Game.Rulesets.Taiko
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new TaikoPerformanceCalculator(this, beatmap, score);
- public override int? LegacyID => 1;
+ public int LegacyID => 1;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame();
}
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index 26e70f19e4..33f484a9aa 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -14,6 +14,7 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.Timing;
using osu.Game.IO;
+using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
@@ -313,7 +314,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
{
var beatmap = decoder.Decode(stream);
- var converted = new OsuBeatmapConverter(beatmap).Convert();
+ var converted = new OsuBeatmapConverter(beatmap, new OsuRuleset()).Convert();
new OsuBeatmapProcessor(converted).PreProcess();
new OsuBeatmapProcessor(converted).PostProcess();
@@ -336,7 +337,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
{
var beatmap = decoder.Decode(stream);
- var converted = new CatchBeatmapConverter(beatmap).Convert();
+ var converted = new CatchBeatmapConverter(beatmap, new CatchRuleset()).Convert();
new CatchBeatmapProcessor(converted).PreProcess();
new CatchBeatmapProcessor(converted).PostProcess();
diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
index 6d7159a825..c6d1f9da29 100644
--- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
@@ -130,7 +130,7 @@ namespace osu.Game.Tests.Gameplay
switch (global)
{
case GlobalSkinConfiguration.ComboColours:
- return SkinUtils.As(new Bindable>(ComboColours));
+ return SkinUtils.As(new Bindable>(ComboColours));
}
break;
diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
index f68d49dd3e..cef38bbbb8 100644
--- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
+++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
@@ -13,31 +13,22 @@ namespace osu.Game.Tests.Skins
[TestFixture]
public class LegacySkinDecoderTest
{
- [TestCase(true)]
- [TestCase(false)]
- public void TestDecodeSkinColours(bool hasColours)
+ [Test]
+ public void TestDecodeSkinColours()
{
var decoder = new LegacySkinDecoder();
- using (var resStream = TestResources.OpenResource(hasColours ? "skin.ini" : "skin-empty.ini"))
+ using (var resStream = TestResources.OpenResource("skin.ini"))
using (var stream = new LineBufferedReader(resStream))
{
var comboColors = decoder.Decode(stream).ComboColours;
-
- List expectedColors;
-
- if (hasColours)
+ var expectedColors = new List
{
- expectedColors = new List
- {
- new Color4(142, 199, 255, 255),
- new Color4(255, 128, 128, 255),
- new Color4(128, 255, 255, 255),
- new Color4(100, 100, 100, 100),
- };
- }
- else
- expectedColors = new DefaultSkin().Configuration.ComboColours;
+ new Color4(142, 199, 255, 255),
+ new Color4(255, 128, 128, 255),
+ new Color4(128, 255, 255, 255),
+ new Color4(100, 100, 100, 100),
+ };
Assert.AreEqual(expectedColors.Count, comboColors.Count);
for (int i = 0; i < expectedColors.Count; i++)
@@ -45,6 +36,37 @@ namespace osu.Game.Tests.Skins
}
}
+ [Test]
+ public void TestDecodeEmptySkinColours()
+ {
+ var decoder = new LegacySkinDecoder();
+
+ using (var resStream = TestResources.OpenResource("skin-empty.ini"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var comboColors = decoder.Decode(stream).ComboColours;
+ var expectedColors = SkinConfiguration.DefaultComboColours;
+
+ Assert.AreEqual(expectedColors.Count, comboColors.Count);
+ for (int i = 0; i < expectedColors.Count; i++)
+ Assert.AreEqual(expectedColors[i], comboColors[i]);
+ }
+ }
+
+ [Test]
+ public void TestDecodeEmptySkinColoursNoFallback()
+ {
+ var decoder = new LegacySkinDecoder();
+
+ using (var resStream = TestResources.OpenResource("skin-empty.ini"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var skinConfiguration = decoder.Decode(stream);
+ skinConfiguration.AllowDefaultComboColoursFallback = false;
+ Assert.IsNull(skinConfiguration.ComboColours);
+ }
+ }
+
[Test]
public void TestDecodeGeneral()
{
diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
index 8b9c648442..ed54cc982d 100644
--- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
+++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
@@ -21,8 +22,8 @@ namespace osu.Game.Tests.Skins
[HeadlessTest]
public class TestSceneSkinConfigurationLookup : OsuTestScene
{
- private LegacySkin source1;
- private LegacySkin source2;
+ private SkinSource source1;
+ private SkinSource source2;
private SkinRequester requester;
[SetUp]
@@ -94,7 +95,7 @@ namespace osu.Game.Tests.Skins
[Test]
public void TestGlobalLookup()
{
- AddAssert("Check combo colours", () => requester.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value?.Count > 0);
+ AddAssert("Check combo colours", () => requester.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value?.Count > 0);
}
[Test]
@@ -116,6 +117,28 @@ namespace osu.Game.Tests.Skins
});
}
+ [Test]
+ public void TestEmptyComboColours()
+ {
+ AddAssert("Check retrieved combo colours is skin default colours", () =>
+ requester.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value?.SequenceEqual(SkinConfiguration.DefaultComboColours) ?? false);
+ }
+
+ [Test]
+ public void TestEmptyComboColoursNoFallback()
+ {
+ AddStep("Add custom combo colours to source1", () => source1.Configuration.AddComboColours(
+ new Color4(100, 150, 200, 255),
+ new Color4(55, 110, 166, 255),
+ new Color4(75, 125, 175, 255)
+ ));
+
+ AddStep("Disallow default colours fallback in source2", () => source2.Configuration.AllowDefaultComboColoursFallback = false);
+
+ AddAssert("Check retrieved combo colours from source1", () =>
+ requester.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value?.SequenceEqual(source1.Configuration.ComboColours) ?? false);
+ }
+
[Test]
public void TestLegacyVersionLookup()
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
index f0363d2e6d..36235a4418 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
@@ -198,7 +198,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new TestDrawableScrollingRuleset(this, beatmap, mods);
- public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TestBeatmapConverter(beatmap);
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TestBeatmapConverter(beatmap, null);
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException();
@@ -268,8 +268,8 @@ namespace osu.Game.Tests.Visual.Gameplay
private class TestBeatmapConverter : BeatmapConverter
{
- public TestBeatmapConverter(IBeatmap beatmap)
- : base(beatmap)
+ public TestBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
+ : base(beatmap, ruleset)
{
}
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/SongSelect/TestSceneBeatmapDetailArea.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs
deleted file mode 100644
index 66144cbfe4..0000000000
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs
+++ /dev/null
@@ -1,237 +0,0 @@
-// 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 NUnit.Framework;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets;
-using osu.Game.Rulesets.Mods;
-using osu.Game.Screens.Play.HUD;
-using osu.Game.Screens.Select;
-using osu.Game.Tests.Beatmaps;
-using osuTK;
-
-namespace osu.Game.Tests.Visual.SongSelect
-{
- [TestFixture]
- [System.ComponentModel.Description("PlaySongSelect leaderboard/details area")]
- public class TestSceneBeatmapDetailArea : OsuTestScene
- {
- public override IReadOnlyList RequiredTypes => new[] { typeof(BeatmapDetails) };
-
- private ModDisplay modDisplay;
-
- [BackgroundDependencyLoader]
- private void load(OsuGameBase game, RulesetStore rulesets)
- {
- BeatmapDetailArea detailsArea;
- Add(detailsArea = new BeatmapDetailArea
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(550f, 450f),
- });
-
- Add(modDisplay = new ModDisplay
- {
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- AutoSizeAxes = Axes.Both,
- Position = new Vector2(0, 25),
- });
-
- modDisplay.Current.BindTo(SelectedMods);
-
- AddStep("all metrics", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
- {
- BeatmapInfo =
- {
- BeatmapSet = new BeatmapSetInfo
- {
- Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
- },
- Version = "All Metrics",
- Metadata = new BeatmapMetadata
- {
- Source = "osu!lazer",
- Tags = "this beatmap has all the metrics",
- },
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 7,
- DrainRate = 1,
- OverallDifficulty = 5.7f,
- ApproachRate = 3.5f,
- },
- StarDifficulty = 5.3f,
- Metrics = new BeatmapMetrics
- {
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
- },
- }
- }));
-
- AddStep("all except source", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
- {
- BeatmapInfo =
- {
- BeatmapSet = new BeatmapSetInfo
- {
- Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
- },
- Version = "All Metrics",
- Metadata = new BeatmapMetadata
- {
- Tags = "this beatmap has all the metrics",
- },
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 7,
- DrainRate = 1,
- OverallDifficulty = 5.7f,
- ApproachRate = 3.5f,
- },
- StarDifficulty = 5.3f,
- Metrics = new BeatmapMetrics
- {
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
- },
- }
- }));
-
- AddStep("ratings", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
- {
- BeatmapInfo =
- {
- BeatmapSet = new BeatmapSetInfo
- {
- Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
- },
- Version = "Only Ratings",
- Metadata = new BeatmapMetadata
- {
- Source = "osu!lazer",
- Tags = "this beatmap has ratings metrics but not retries or fails",
- },
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 6,
- DrainRate = 9,
- OverallDifficulty = 6,
- ApproachRate = 6,
- },
- StarDifficulty = 4.8f
- }
- }));
-
- AddStep("fails+retries", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
- {
- BeatmapInfo =
- {
- Version = "Only Retries and Fails",
- Metadata = new BeatmapMetadata
- {
- Source = "osu!lazer",
- Tags = "this beatmap has retries and fails but no ratings",
- },
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 3.7f,
- DrainRate = 6,
- OverallDifficulty = 6,
- ApproachRate = 7,
- },
- StarDifficulty = 2.91f,
- Metrics = new BeatmapMetrics
- {
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
- },
- }
- }));
-
- AddStep("null metrics", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
- {
- BeatmapInfo =
- {
- Version = "No Metrics",
- Metadata = new BeatmapMetadata
- {
- Source = "osu!lazer",
- Tags = "this beatmap has no metrics",
- },
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 5,
- DrainRate = 5,
- OverallDifficulty = 5.5f,
- ApproachRate = 6.5f,
- },
- StarDifficulty = 1.97f,
- }
- }));
-
- AddStep("null beatmap", () => detailsArea.Beatmap = null);
-
- Ruleset ruleset = rulesets.AvailableRulesets.First().CreateInstance();
-
- AddStep("with EZ mod", () =>
- {
- detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
- {
- BeatmapInfo =
- {
- Version = "Has Easy Mod",
- Metadata = new BeatmapMetadata
- {
- Source = "osu!lazer",
- Tags = "this beatmap has the easy mod enabled",
- },
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 3,
- DrainRate = 3,
- OverallDifficulty = 3,
- ApproachRate = 3,
- },
- StarDifficulty = 1f,
- }
- });
-
- SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModEasy) };
- });
-
- AddStep("with HR mod", () =>
- {
- detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
- {
- BeatmapInfo =
- {
- Version = "Has Hard Rock Mod",
- Metadata = new BeatmapMetadata
- {
- Source = "osu!lazer",
- Tags = "this beatmap has the hard rock mod enabled",
- },
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 3,
- DrainRate = 3,
- OverallDifficulty = 3,
- ApproachRate = 3,
- },
- StarDifficulty = 1f,
- }
- });
-
- SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModHardRock) };
- });
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs
index acf037198f..6aa5a76490 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs
@@ -3,8 +3,14 @@
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Framework.Testing;
using osu.Game.Beatmaps;
+using osu.Game.Graphics;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Select;
namespace osu.Game.Tests.Visual.SongSelect
@@ -174,5 +180,27 @@ namespace osu.Game.Tests.Visual.SongSelect
OnlineBeatmapID = 162,
});
}
+
+ [Resolved]
+ private RulesetStore rulesets { get; set; }
+
+ [Resolved]
+ private OsuColour colours { get; set; }
+
+ [Test]
+ public void TestModAdjustments()
+ {
+ TestAllMetrics();
+
+ Ruleset ruleset = rulesets.AvailableRulesets.First().CreateInstance();
+
+ AddStep("with EZ mod", () => SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModEasy) });
+
+ AddAssert("first bar coloured blue", () => details.ChildrenOfType().Skip(1).First().AccentColour == colours.BlueDark);
+
+ AddStep("with HR mod", () => SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModHardRock) });
+
+ AddAssert("first bar coloured red", () => details.ChildrenOfType().Skip(1).First().AccentColour == colours.Red);
+ }
}
}
diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs
index 0964f3f1ed..99e0bf4e33 100644
--- a/osu.Game/Beatmaps/BeatmapConverter.cs
+++ b/osu.Game/Beatmaps/BeatmapConverter.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Beatmaps
@@ -25,7 +26,7 @@ namespace osu.Game.Beatmaps
public IBeatmap Beatmap { get; }
- protected BeatmapConverter(IBeatmap beatmap)
+ protected BeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
{
Beatmap = beatmap;
}
diff --git a/osu.Game/Beatmaps/Formats/IHasComboColours.cs b/osu.Game/Beatmaps/Formats/IHasComboColours.cs
index 4c15cb96d1..41c85db063 100644
--- a/osu.Game/Beatmaps/Formats/IHasComboColours.cs
+++ b/osu.Game/Beatmaps/Formats/IHasComboColours.cs
@@ -8,6 +8,14 @@ namespace osu.Game.Beatmaps.Formats
{
public interface IHasComboColours
{
- List ComboColours { get; set; }
+ ///
+ /// Retrieves the list of combo colours for presentation only.
+ ///
+ IReadOnlyList ComboColours { get; }
+
+ ///
+ /// Adds combo colours to the list.
+ ///
+ void AddComboColours(params Color4[] colours);
}
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
index b1585d04c5..f55e24245b 100644
--- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
@@ -77,8 +77,6 @@ namespace osu.Game.Beatmaps.Formats
return line;
}
- private bool hasComboColours;
-
private void handleColours(T output, string line)
{
var pair = SplitKeyVal(line);
@@ -105,14 +103,7 @@ namespace osu.Game.Beatmaps.Formats
{
if (!(output is IHasComboColours tHasComboColours)) return;
- if (!hasComboColours)
- {
- // remove default colours.
- tHasComboColours.ComboColours.Clear();
- hasComboColours = true;
- }
-
- tHasComboColours.ComboColours.Add(colour);
+ tHasComboColours.AddComboColours(colour);
}
else
{
diff --git a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs
index d1e55fee24..93ea6bbbe6 100644
--- a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs
+++ b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs
@@ -52,7 +52,6 @@ namespace osu.Game.Graphics.UserInterface
public override bool HandleNonPositionalInput => State == Visibility.Visible;
public override bool HandlePositionalInput => State == Visibility.Visible;
- public override bool IsRemovable => true;
private Visibility state;
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/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs
index 1cac4d76ab..f5b7bc3073 100644
--- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs
+++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs
@@ -12,10 +12,12 @@ using osu.Framework.Input.Events;
namespace osu.Game.Graphics.UserInterface
{
- public class OsuTextBox : TextBox
+ public class OsuTextBox : BasicTextBox
{
protected override float LeftRightPadding => 10;
+ protected override float CaretWidth => 3;
+
protected override SpriteText CreatePlaceholder() => new OsuSpriteText
{
Font = OsuFont.GetFont(italics: true),
@@ -41,6 +43,8 @@ namespace osu.Game.Graphics.UserInterface
BackgroundCommit = BorderColour = colour.Yellow;
}
+ protected override Color4 SelectionColour => new Color4(249, 90, 255, 255);
+
protected override void OnFocus(FocusEvent e)
{
BorderThickness = 3;
diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs
index 33bcc4c139..e80c51d82a 100644
--- a/osu.Game/Overlays/ChatOverlay.cs
+++ b/osu.Game/Overlays/ChatOverlay.cs
@@ -171,6 +171,7 @@ namespace osu.Game.Overlays
d.Origin = Anchor.BottomLeft;
d.RelativeSizeAxes = Axes.Both;
d.OnRequestLeave = channelManager.LeaveChannel;
+ d.IsSwitchable = true;
}),
}
},
diff --git a/osu.Game/Overlays/Settings/ISettingsItem.cs b/osu.Game/Overlays/Settings/ISettingsItem.cs
new file mode 100644
index 0000000000..e7afa48502
--- /dev/null
+++ b/osu.Game/Overlays/Settings/ISettingsItem.cs
@@ -0,0 +1,13 @@
+// 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.Graphics;
+
+namespace osu.Game.Overlays.Settings
+{
+ public interface ISettingsItem : IDrawable, IDisposable
+ {
+ event Action SettingChanged;
+ }
+}
diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs
index 31fcb7abd8..35f28ab1b2 100644
--- a/osu.Game/Overlays/Settings/SettingsItem.cs
+++ b/osu.Game/Overlays/Settings/SettingsItem.cs
@@ -1,6 +1,7 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -20,7 +21,7 @@ using osuTK;
namespace osu.Game.Overlays.Settings
{
- public abstract class SettingsItem : Container, IFilterable
+ public abstract class SettingsItem : Container, IFilterable, ISettingsItem
{
protected abstract Drawable CreateControl();
@@ -34,8 +35,6 @@ namespace osu.Game.Overlays.Settings
private SpriteText text;
- private readonly RestoreDefaultValueButton restoreDefaultButton;
-
public bool ShowsDefaultIndicator = true;
public virtual string LabelText
@@ -70,8 +69,12 @@ namespace osu.Game.Overlays.Settings
public bool FilteringActive { get; set; }
+ public event Action SettingChanged;
+
protected SettingsItem()
{
+ RestoreDefaultValueButton restoreDefaultButton;
+
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Padding = new MarginPadding { Right = SettingsPanel.CONTENT_MARGINS };
@@ -87,13 +90,12 @@ namespace osu.Game.Overlays.Settings
Child = Control = CreateControl()
},
};
- }
- [BackgroundDependencyLoader]
- private void load()
- {
+ // all bindable logic is in constructor intentionally to support "CreateSettingsControls" being used in a context it is
+ // never loaded, but requires bindable storage.
if (controlWithCurrent != null)
{
+ controlWithCurrent.Current.ValueChanged += _ => SettingChanged?.Invoke();
controlWithCurrent.Current.DisabledChanged += disabled => { Colour = disabled ? Color4.Gray : Color4.White; };
if (ShowsDefaultIndicator)
diff --git a/osu.Game/Rulesets/ILegacyRuleset.cs b/osu.Game/Rulesets/ILegacyRuleset.cs
new file mode 100644
index 0000000000..06a85b5261
--- /dev/null
+++ b/osu.Game/Rulesets/ILegacyRuleset.cs
@@ -0,0 +1,13 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets
+{
+ public interface ILegacyRuleset
+ {
+ ///
+ /// Identifies the server-side ID of a legacy ruleset.
+ ///
+ int LegacyID { get; }
+ }
+}
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/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs
new file mode 100644
index 0000000000..224fc78508
--- /dev/null
+++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs
@@ -0,0 +1,81 @@
+// 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.Framework.Bindables;
+using osu.Framework.Graphics.Sprites;
+using System;
+using osu.Game.Configuration;
+
+namespace osu.Game.Rulesets.Mods
+{
+ public abstract class ModDifficultyAdjust : Mod, IApplicableToDifficulty
+ {
+ public override string Name => @"Difficulty Adjust";
+
+ public override string Description => @"Override a beatmap's difficulty settings.";
+
+ public override string Acronym => "DA";
+
+ public override ModType Type => ModType.Conversion;
+
+ public override IconUsage Icon => FontAwesome.Solid.Hammer;
+
+ public override double ScoreMultiplier => 1.0;
+
+ public override Type[] IncompatibleMods => new[] { typeof(ModEasy), typeof(ModHardRock) };
+
+ [SettingSource("Drain Rate", "Override a beatmap's set HP.")]
+ public BindableNumber DrainRate { get; } = new BindableFloat
+ {
+ Precision = 0.1f,
+ MinValue = 1,
+ MaxValue = 10,
+ Default = 5,
+ Value = 5,
+ };
+
+ [SettingSource("Overall Difficulty", "Override a beatmap's set OD.")]
+ public BindableNumber OverallDifficulty { get; } = new BindableFloat
+ {
+ Precision = 0.1f,
+ MinValue = 1,
+ MaxValue = 10,
+ Default = 5,
+ Value = 5,
+ };
+
+ private BeatmapDifficulty difficulty;
+
+ public void ApplyToDifficulty(BeatmapDifficulty difficulty)
+ {
+ if (this.difficulty == null || this.difficulty.ID != difficulty.ID)
+ {
+ this.difficulty = difficulty;
+ TransferSettings(difficulty);
+ }
+ else
+ ApplySettings(difficulty);
+ }
+
+ ///
+ /// Transfer initial settings from the beatmap to settings.
+ ///
+ /// The beatmap's initial values.
+ protected virtual void TransferSettings(BeatmapDifficulty difficulty)
+ {
+ DrainRate.Value = DrainRate.Default = difficulty.DrainRate;
+ OverallDifficulty.Value = OverallDifficulty.Default = difficulty.OverallDifficulty;
+ }
+
+ ///
+ /// Apply all custom settings to the provided beatmap.
+ ///
+ /// The beatmap to have settings applied.
+ protected virtual void ApplySettings(BeatmapDifficulty difficulty)
+ {
+ difficulty.DrainRate = DrainRate.Value;
+ difficulty.OverallDifficulty = OverallDifficulty.Value;
+ }
+ }
+}
diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs
index a55ebc51d6..f2da70d046 100644
--- a/osu.Game/Rulesets/Mods/ModEasy.cs
+++ b/osu.Game/Rulesets/Mods/ModEasy.cs
@@ -5,13 +5,13 @@ using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
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";
@@ -19,9 +19,16 @@ namespace osu.Game.Rulesets.Mods
public override ModType Type => ModType.DifficultyReduction;
public override double ScoreMultiplier => 0.5;
public override bool Ranked => true;
- public override Type[] IncompatibleMods => new[] { typeof(ModHardRock) };
+ public override Type[] IncompatibleMods => new[] { typeof(ModHardRock), typeof(ModDifficultyAdjust) };
- private int retries = 2;
+ [SettingSource("Extra Lives", "Number of extra lives")]
+ public Bindable Retries { get; } = new BindableInt(2)
+ {
+ MinValue = 0,
+ MaxValue = 10
+ };
+
+ private int retries;
private BindableNumber health;
@@ -32,6 +39,8 @@ namespace osu.Game.Rulesets.Mods
difficulty.ApproachRate *= ratio;
difficulty.DrainRate *= ratio;
difficulty.OverallDifficulty *= ratio;
+
+ retries = Retries.Value;
}
public bool AllowFail
@@ -49,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;
}
}
diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs
index 2044cbeae2..2bcac3e4a9 100644
--- a/osu.Game/Rulesets/Mods/ModHardRock.cs
+++ b/osu.Game/Rulesets/Mods/ModHardRock.cs
@@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mods
public override IconUsage Icon => OsuIcon.ModHardrock;
public override ModType Type => ModType.DifficultyIncrease;
public override string Description => "Everything just got a bit harder...";
- public override Type[] IncompatibleMods => new[] { typeof(ModEasy) };
+ public override Type[] IncompatibleMods => new[] { typeof(ModEasy), typeof(ModDifficultyAdjust) };
public void ApplyToDifficulty(BeatmapDifficulty difficulty)
{
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/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index 386805d7e5..a959fee9be 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -356,7 +356,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
{
if (HitObject is IHasComboInformation combo)
{
- var comboColours = CurrentSkin.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value;
+ var comboColours = CurrentSkin.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value;
AccentColour.Value = comboColours?.Count > 0 ? comboColours[combo.ComboIndex % comboColours.Count] : Color4.White;
}
}
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index 7ad93379f0..bfd6a16729 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets
{
public abstract class Ruleset
{
- public readonly RulesetInfo RulesetInfo;
+ public RulesetInfo RulesetInfo { get; internal set; }
public IEnumerable GetAllMods() => Enum.GetValues(typeof(ModType)).Cast()
// Confine all mods of each mod type into a single IEnumerable
@@ -51,7 +51,14 @@ namespace osu.Game.Rulesets
protected Ruleset()
{
- RulesetInfo = createRulesetInfo();
+ RulesetInfo = new RulesetInfo
+ {
+ Name = Description,
+ ShortName = ShortName,
+ ID = (this as ILegacyRuleset)?.LegacyID,
+ InstantiationInfo = GetType().AssemblyQualifiedName,
+ Available = true
+ };
}
///
@@ -69,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 .
///
@@ -103,11 +116,6 @@ namespace osu.Game.Rulesets
/// The to store the settings.
public virtual IRulesetConfigManager CreateConfig(SettingsStore settings) => null;
- ///
- /// Do not override this unless you are a legacy mode.
- ///
- public virtual int? LegacyID => null;
-
///
/// A unique short name to reference this ruleset in online requests.
///
@@ -138,18 +146,5 @@ namespace osu.Game.Rulesets
///
/// An empty frame for the current ruleset, or null if unsupported.
public virtual IConvertibleReplayFrame CreateConvertibleReplayFrame() => null;
-
- ///
- /// Create a ruleset info based on this ruleset.
- ///
- /// A filled .
- private RulesetInfo createRulesetInfo() => new RulesetInfo
- {
- Name = Description,
- ShortName = ShortName,
- InstantiationInfo = GetType().AssemblyQualifiedName,
- ID = LegacyID,
- Available = true
- };
}
}
diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs
index d695e0b56d..ececc18c96 100644
--- a/osu.Game/Rulesets/RulesetInfo.cs
+++ b/osu.Game/Rulesets/RulesetInfo.cs
@@ -20,11 +20,17 @@ namespace osu.Game.Rulesets
[JsonIgnore]
public bool Available { get; set; }
+ // TODO: this should probably be moved to RulesetStore.
public virtual Ruleset CreateInstance()
{
if (!Available) return null;
- return (Ruleset)Activator.CreateInstance(Type.GetType(InstantiationInfo));
+ var ruleset = (Ruleset)Activator.CreateInstance(Type.GetType(InstantiationInfo));
+
+ // overwrite the pre-populated RulesetInfo with a potentially database attached copy.
+ ruleset.RulesetInfo = this;
+
+ return ruleset;
}
public bool Equals(RulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo;
diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs
index 5d0c5c7ccf..a389d4ff75 100644
--- a/osu.Game/Rulesets/RulesetStore.cs
+++ b/osu.Game/Rulesets/RulesetStore.cs
@@ -58,17 +58,17 @@ namespace osu.Game.Rulesets
var instances = loadedAssemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r)).ToList();
- //add all legacy modes in correct order
- foreach (var r in instances.Where(r => r.LegacyID != null).OrderBy(r => r.LegacyID))
+ //add all legacy rulesets first to ensure they have exclusive choice of primary key.
+ foreach (var r in instances.Where(r => r is ILegacyRuleset))
{
- if (context.RulesetInfo.SingleOrDefault(rsi => rsi.ID == r.RulesetInfo.ID) == null)
+ if (context.RulesetInfo.SingleOrDefault(dbRuleset => dbRuleset.ID == r.RulesetInfo.ID) == null)
context.RulesetInfo.Add(r.RulesetInfo);
}
context.SaveChanges();
//add any other modes
- foreach (var r in instances.Where(r => r.LegacyID == null))
+ foreach (var r in instances.Where(r => !(r is ILegacyRuleset)))
{
if (context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo == r.RulesetInfo.InstantiationInfo) == null)
context.RulesetInfo.Add(r.RulesetInfo);
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/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs
index df1b8078a6..f318539bb7 100644
--- a/osu.Game/Rulesets/UI/DrawableRuleset.cs
+++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs
@@ -159,7 +159,7 @@ namespace osu.Game.Rulesets.UI
dependencies.Cache(textureStore);
localSampleStore = dependencies.Get().GetSampleStore(new NamespacedResourceStore(resources, "Samples"));
- dependencies.CacheAs(new FallbackSampleStore(localSampleStore, dependencies.Get()));
+ dependencies.CacheAs(new FallbackSampleStore(localSampleStore, dependencies.Get()));
}
onScreenDisplay = dependencies.Get();
diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs
index 231115d1e1..b28d572b5c 100644
--- a/osu.Game/Screens/Menu/MainMenu.cs
+++ b/osu.Game/Screens/Menu/MainMenu.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Linq;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Allocation;
@@ -132,6 +133,8 @@ namespace osu.Game.Screens.Menu
private void confirmAndExit()
{
+ if (exitConfirmed) return;
+
exitConfirmed = true;
this.Exit();
}
@@ -244,10 +247,18 @@ namespace osu.Game.Screens.Menu
public override bool OnExiting(IScreen next)
{
- if (!exitConfirmed && dialogOverlay != null && !(dialogOverlay.CurrentDialog is ConfirmExitDialog))
+ if (!exitConfirmed && dialogOverlay != null)
{
- dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort()));
- return true;
+ if (dialogOverlay.CurrentDialog is ConfirmExitDialog exitDialog)
+ {
+ exitConfirmed = true;
+ exitDialog.Buttons.First().Click();
+ }
+ else
+ {
+ dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort()));
+ return true;
+ }
}
buttons.State = ButtonSystemState.Exit;
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 5dfdeb5ebc..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;
}
@@ -562,7 +569,7 @@ namespace osu.Game.Screens.Play
// GameplayClockContainer performs seeks / start / stop operations on the beatmap's track.
// as we are no longer the current screen, we cannot guarantee the track is still usable.
- GameplayClockContainer.StopUsingBeatmapClock();
+ GameplayClockContainer?.StopUsingBeatmapClock();
fadeOut();
return base.OnExiting(next);
@@ -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/Screens/Select/BeatmapDetailAreaTabControl.cs b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs
index 433e8ee398..19ecdb6dbf 100644
--- a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs
+++ b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs
@@ -48,6 +48,7 @@ namespace osu.Game.Screens.Select
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Both,
+ IsSwitchable = true,
},
},
modsCheckbox = new OsuTabControlCheckbox
diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs
index 9c9c33274f..a147527f6c 100644
--- a/osu.Game/Screens/Select/Details/AdvancedStats.cs
+++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs
@@ -16,6 +16,9 @@ using osu.Framework.Bindables;
using System.Collections.Generic;
using osu.Game.Rulesets.Mods;
using System.Linq;
+using osu.Framework.Threading;
+using osu.Game.Configuration;
+using osu.Game.Overlays.Settings;
namespace osu.Game.Screens.Select.Details
{
@@ -69,7 +72,37 @@ namespace osu.Game.Screens.Select.Details
{
base.LoadComplete();
- mods.BindValueChanged(_ => updateStatistics(), true);
+ mods.BindValueChanged(modsChanged, true);
+ }
+
+ private readonly List references = new List();
+
+ private void modsChanged(ValueChangedEvent> mods)
+ {
+ // TODO: find a more permanent solution for this if/when it is needed in other components.
+ // this is generating drawables for the only purpose of storing bindable references.
+ foreach (var r in references)
+ r.Dispose();
+
+ references.Clear();
+
+ ScheduledDelegate debounce = null;
+
+ foreach (var mod in mods.NewValue.OfType())
+ {
+ foreach (var setting in mod.CreateSettingsControls().OfType())
+ {
+ setting.SettingChanged += () =>
+ {
+ debounce?.Cancel();
+ debounce = Scheduler.AddDelayed(updateStatistics, 100);
+ };
+
+ references.Add(setting);
+ }
+ }
+
+ updateStatistics();
}
private void updateStatistics()
diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs
index 0caf2d19e9..1929a7e5d2 100644
--- a/osu.Game/Skinning/DefaultLegacySkin.cs
+++ b/osu.Game/Skinning/DefaultLegacySkin.cs
@@ -13,13 +13,12 @@ namespace osu.Game.Skinning
: base(Info, storage, audioManager, string.Empty)
{
Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255);
- Configuration.ComboColours.AddRange(new[]
- {
+ Configuration.AddComboColours(
new Color4(255, 192, 0, 255),
new Color4(0, 202, 0, 255),
new Color4(18, 124, 255, 255),
- new Color4(242, 24, 57, 255),
- });
+ new Color4(242, 24, 57, 255)
+ );
Configuration.LegacyVersion = 2.0m;
}
diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs
index 529c1afca5..2a065ea3d7 100644
--- a/osu.Game/Skinning/DefaultSkin.cs
+++ b/osu.Game/Skinning/DefaultSkin.cs
@@ -35,7 +35,7 @@ namespace osu.Game.Skinning
switch (global)
{
case GlobalSkinConfiguration.ComboColours:
- return SkinUtils.As(new Bindable>(Configuration.ComboColours));
+ return SkinUtils.As(new Bindable>(Configuration.ComboColours));
}
break;
diff --git a/osu.Game/Skinning/DefaultSkinConfiguration.cs b/osu.Game/Skinning/DefaultSkinConfiguration.cs
index cd5975edac..5842ee82ee 100644
--- a/osu.Game/Skinning/DefaultSkinConfiguration.cs
+++ b/osu.Game/Skinning/DefaultSkinConfiguration.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osuTK.Graphics;
-
namespace osu.Game.Skinning
{
///
@@ -10,15 +8,5 @@ namespace osu.Game.Skinning
///
public class DefaultSkinConfiguration : SkinConfiguration
{
- public DefaultSkinConfiguration()
- {
- ComboColours.AddRange(new[]
- {
- new Color4(255, 192, 0, 255),
- new Color4(0, 202, 0, 255),
- new Color4(18, 124, 255, 255),
- new Color4(242, 24, 57, 255),
- });
- }
}
}
diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs
index 6770da3c66..fa7e895a28 100644
--- a/osu.Game/Skinning/LegacyBeatmapSkin.cs
+++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs
@@ -12,6 +12,8 @@ namespace osu.Game.Skinning
public LegacyBeatmapSkin(BeatmapInfo beatmap, IResourceStore storage, AudioManager audioManager)
: base(createSkinInfo(beatmap), new LegacySkinResourceStore(beatmap.BeatmapSet, storage), audioManager, beatmap.Path)
{
+ // Disallow default colours fallback on beatmap skins to allow using parent skin combo colours. (via SkinProvidingContainer)
+ Configuration.AllowDefaultComboColoursFallback = false;
}
private static SkinInfo createSkinInfo(BeatmapInfo beatmap) =>
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index 868e3921bb..48c520986a 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -72,7 +72,11 @@ namespace osu.Game.Skinning
switch (global)
{
case GlobalSkinConfiguration.ComboColours:
- return SkinUtils.As(new Bindable>(Configuration.ComboColours));
+ var comboColours = Configuration.ComboColours;
+ if (comboColours != null)
+ return SkinUtils.As(new Bindable>(comboColours));
+
+ break;
}
break;
diff --git a/osu.Game/Skinning/LegacySkinConfiguration.cs b/osu.Game/Skinning/LegacySkinConfiguration.cs
index b1679bd464..027f5b8883 100644
--- a/osu.Game/Skinning/LegacySkinConfiguration.cs
+++ b/osu.Game/Skinning/LegacySkinConfiguration.cs
@@ -3,7 +3,7 @@
namespace osu.Game.Skinning
{
- public class LegacySkinConfiguration : DefaultSkinConfiguration
+ public class LegacySkinConfiguration : SkinConfiguration
{
public const decimal LATEST_VERSION = 2.7m;
diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs
index 54aac86e3c..a55870aa6d 100644
--- a/osu.Game/Skinning/SkinConfiguration.cs
+++ b/osu.Game/Skinning/SkinConfiguration.cs
@@ -14,7 +14,36 @@ namespace osu.Game.Skinning
{
public readonly SkinInfo SkinInfo = new SkinInfo();
- public List ComboColours { get; set; } = new List();
+ ///
+ /// Whether to allow as a fallback list for when no combo colours are provided.
+ ///
+ internal bool AllowDefaultComboColoursFallback = true;
+
+ public static List DefaultComboColours { get; } = new List
+ {
+ new Color4(255, 192, 0, 255),
+ new Color4(0, 202, 0, 255),
+ new Color4(18, 124, 255, 255),
+ new Color4(242, 24, 57, 255),
+ };
+
+ private readonly List comboColours = new List();
+
+ public IReadOnlyList ComboColours
+ {
+ get
+ {
+ if (comboColours.Count > 0)
+ return comboColours;
+
+ if (AllowDefaultComboColoursFallback)
+ return DefaultComboColours;
+
+ return null;
+ }
+ }
+
+ public void AddComboColours(params Color4[] colours) => comboColours.AddRange(colours);
public Dictionary CustomColours { get; set; } = new Dictionary();
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 757e0e11fa..0c0a58d533 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -23,7 +23,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 0dba92b975..edeeea239e 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -74,7 +74,7 @@
-
+
@@ -82,7 +82,7 @@
-
+