diff --git a/osu.Android.props b/osu.Android.props
index d684b73d8d..0b41d5cda4 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -53,7 +53,7 @@
-
-
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
index 9559d13328..0b2862e5bb 100644
--- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
+++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
@@ -4,7 +4,7 @@
-
+
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
index b5497ea89f..90a6e609f0 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
@@ -4,7 +4,7 @@
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using System.Collections.Generic;
-using System;
+using System.Linq;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Objects;
@@ -14,12 +14,12 @@ 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)
{
}
- protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
+ public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition);
protected override IEnumerable ConvertHitObject(HitObject obj, IBeatmap beatmap)
{
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index b8a844cb86..e5c3647f99 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -24,13 +24,14 @@ 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 ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor();
+
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap, this);
- public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap);
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap);
public const string SHORT_NAME = "fruits";
@@ -106,6 +107,12 @@ namespace osu.Game.Rulesets.Catch
new CatchModFlashlight(),
};
+ case ModType.Conversion:
+ return new Mod[]
+ {
+ new CatchModDifficultyAdjust(),
+ };
+
case ModType.Automation:
return new Mod[]
{
@@ -128,13 +135,15 @@ namespace osu.Game.Rulesets.Catch
public override string ShortName => SHORT_NAME;
+ public override string PlayingVerb => "Catching fruit";
+
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch };
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap);
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..8377b3786a
--- /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);
+
+ TransferSetting(CircleSize, difficulty.CircleSize);
+ TransferSetting(ApproachRate, 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/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
index f67ca1213e..4c7bc4ab73 100644
--- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
@@ -1,40 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Scoring
{
public class CatchScoreProcessor : ScoreProcessor
{
- public CatchScoreProcessor(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
- }
- }
-
public override HitWindows CreateHitWindows() => new CatchHitWindows();
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/SkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/SkinnableTestScene.cs
new file mode 100644
index 0000000000..80b1b3df8e
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/SkinnableTestScene.cs
@@ -0,0 +1,46 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Skinning;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ public abstract class SkinnableTestScene : OsuGridTestScene
+ {
+ private Skin defaultSkin;
+
+ protected SkinnableTestScene()
+ : base(1, 2)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(AudioManager audio, SkinManager skinManager)
+ {
+ defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info);
+ }
+
+ public void SetContents(Func creationFunction)
+ {
+ Cell(0).Child = createProvider(null, creationFunction);
+ Cell(1).Child = createProvider(defaultSkin, creationFunction);
+ }
+
+ private Drawable createProvider(Skin skin, Func creationFunction)
+ {
+ var mainProvider = new SkinProvidingContainer(skin);
+
+ return mainProvider
+ .WithChild(new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider))
+ {
+ Child = creationFunction()
+ });
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs
new file mode 100644
index 0000000000..eea1a36a19
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs
@@ -0,0 +1,37 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Extensions;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ public class TestSceneDrawableJudgement : SkinnableTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DrawableJudgement),
+ typeof(DrawableManiaJudgement)
+ };
+
+ public TestSceneDrawableJudgement()
+ {
+ foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1))
+ {
+ AddStep("Show " + result.GetDescription(), () => SetContents(() =>
+ new DrawableManiaJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }));
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
new file mode 100644
index 0000000000..7b0cf40d45
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
@@ -0,0 +1,314 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Screens;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Replays;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Replays;
+using osu.Game.Rulesets.Replays;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
+using osu.Game.Screens.Play;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ public class TestSceneHoldNoteInput : RateAdjustedBeatmapTestScene
+ {
+ private const double time_before_head = 250;
+ private const double time_head = 1500;
+ private const double time_during_hold_1 = 2500;
+ private const double time_tail = 4000;
+ private const double time_after_tail = 5250;
+
+ private List judgementResults;
+ private bool allJudgedFired;
+
+ ///
+ /// -----[ ]-----
+ /// o o
+ ///
+ [Test]
+ public void TestNoInput()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_before_head),
+ new ManiaReplayFrame(time_after_tail),
+ });
+
+ assertHeadJudgement(HitResult.Miss);
+ assertTickJudgement(HitResult.Miss);
+ assertTailJudgement(HitResult.Miss);
+ assertNoteJudgement(HitResult.Perfect);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// x o
+ ///
+ [Test]
+ public void TestPressTooEarlyAndReleaseAfterTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_after_tail, ManiaAction.Key1),
+ });
+
+ assertHeadJudgement(HitResult.Miss);
+ assertTickJudgement(HitResult.Miss);
+ assertTailJudgement(HitResult.Miss);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// x o
+ ///
+ [Test]
+ public void TestPressTooEarlyAndReleaseAtTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_tail),
+ });
+
+ assertHeadJudgement(HitResult.Miss);
+ assertTickJudgement(HitResult.Miss);
+ assertTailJudgement(HitResult.Miss);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// xo x o
+ ///
+ [Test]
+ public void TestPressTooEarlyThenPressAtStartAndReleaseAfterTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_before_head + 10),
+ new ManiaReplayFrame(time_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_after_tail),
+ });
+
+ assertHeadJudgement(HitResult.Perfect);
+ assertTickJudgement(HitResult.Perfect);
+ assertTailJudgement(HitResult.Miss);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// xo x o
+ ///
+ [Test]
+ public void TestPressTooEarlyThenPressAtStartAndReleaseAtTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_before_head + 10),
+ new ManiaReplayFrame(time_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_tail),
+ });
+
+ assertHeadJudgement(HitResult.Perfect);
+ assertTickJudgement(HitResult.Perfect);
+ assertTailJudgement(HitResult.Perfect);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// xo o
+ ///
+ [Test]
+ public void TestPressAtStartAndBreak()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_head + 10),
+ new ManiaReplayFrame(time_after_tail),
+ });
+
+ assertHeadJudgement(HitResult.Perfect);
+ assertTickJudgement(HitResult.Miss);
+ assertTailJudgement(HitResult.Miss);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// xo x o
+ ///
+ [Test]
+ public void TestPressAtStartThenBreakThenRepressAndReleaseAfterTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_head + 10),
+ new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
+ new ManiaReplayFrame(time_after_tail),
+ });
+
+ assertHeadJudgement(HitResult.Perfect);
+ assertTickJudgement(HitResult.Perfect);
+ assertTailJudgement(HitResult.Miss);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// xo x o o
+ ///
+ [Test]
+ public void TestPressAtStartThenBreakThenRepressAndReleaseAtTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_head + 10),
+ new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
+ new ManiaReplayFrame(time_tail),
+ });
+
+ assertHeadJudgement(HitResult.Perfect);
+ assertTickJudgement(HitResult.Perfect);
+ assertTailJudgement(HitResult.Meh);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// x o
+ ///
+ [Test]
+ public void TestPressDuringNoteAndReleaseAfterTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
+ new ManiaReplayFrame(time_after_tail),
+ });
+
+ assertHeadJudgement(HitResult.Miss);
+ assertTickJudgement(HitResult.Perfect);
+ assertTailJudgement(HitResult.Miss);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// x o o
+ ///
+ [Test]
+ public void TestPressDuringNoteAndReleaseAtTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
+ new ManiaReplayFrame(time_tail),
+ });
+
+ assertHeadJudgement(HitResult.Miss);
+ assertTickJudgement(HitResult.Perfect);
+ assertTailJudgement(HitResult.Meh);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// xo o
+ ///
+ [Test]
+ public void TestPressAndReleaseAtTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_tail, ManiaAction.Key1),
+ new ManiaReplayFrame(time_tail + 10),
+ });
+
+ assertHeadJudgement(HitResult.Miss);
+ assertTickJudgement(HitResult.Miss);
+ assertTailJudgement(HitResult.Meh);
+ }
+
+ private void assertHeadJudgement(HitResult result)
+ => AddAssert($"head judged as {result}", () => judgementResults[0].Type == result);
+
+ private void assertTailJudgement(HitResult result)
+ => AddAssert($"tail judged as {result}", () => judgementResults[^2].Type == result);
+
+ private void assertNoteJudgement(HitResult result)
+ => AddAssert($"hold note judged as {result}", () => judgementResults[^1].Type == result);
+
+ private void assertTickJudgement(HitResult result)
+ => AddAssert($"tick judged as {result}", () => judgementResults[6].Type == result); // arbitrary tick
+
+ private ScoreAccessibleReplayPlayer currentPlayer;
+
+ private void performTest(List frames)
+ {
+ AddStep("load player", () =>
+ {
+ Beatmap.Value = CreateWorkingBeatmap(new Beatmap
+ {
+ HitObjects =
+ {
+ new HoldNote
+ {
+ StartTime = time_head,
+ Duration = time_tail - time_head,
+ Column = 0,
+ }
+ },
+ BeatmapInfo =
+ {
+ BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 4 },
+ Ruleset = new ManiaRuleset().RulesetInfo
+ },
+ });
+
+ Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
+
+ var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
+
+ p.OnLoadComplete += _ =>
+ {
+ p.ScoreProcessor.NewJudgement += result =>
+ {
+ if (currentPlayer == p) judgementResults.Add(result);
+ };
+ p.ScoreProcessor.AllJudged += () =>
+ {
+ if (currentPlayer == p) allJudgedFired = true;
+ };
+ };
+
+ LoadScreen(currentPlayer = p);
+ allJudgedFired = false;
+ judgementResults = new List();
+ });
+
+ AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
+ AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
+ AddUntilStep("Wait for all judged", () => allJudgedFired);
+ }
+
+ private class ScoreAccessibleReplayPlayer : ReplayPlayer
+ {
+ public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
+
+ protected override bool PauseOnFocusLost => false;
+
+ public ScoreAccessibleReplayPlayer(Score score)
+ : base(score, false, false)
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestScenePlayer.cs b/osu.Game.Rulesets.Mania.Tests/TestScenePlayer.cs
new file mode 100644
index 0000000000..cd25d162d0
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestScenePlayer.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.Tests.Visual;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ public class TestScenePlayer : PlayerTestScene
+ {
+ public TestScenePlayer()
+ : base(new ManiaRuleset())
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
index dea6e6c0fb..9d362e5819 100644
--- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
+++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
@@ -4,7 +4,7 @@
-
+
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 6e4491de94..1a77a4944b 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -24,8 +24,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
///
private const int max_notes_for_density = 7;
- protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
-
public int TargetColumns;
public bool Dual;
public readonly bool IsForCurrentRuleset;
@@ -37,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);
@@ -69,6 +67,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
}
}
+ public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition || h is ManiaHitObject);
+
protected override Beatmap ConvertBeatmap(IBeatmap original)
{
BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty;
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs
index acce41db6f..4e73883de0 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
// Todo: This shouldn't exist, mania should not reference the drawable hitobject directly.
if (DrawableObject.IsLoaded)
{
- DrawableNote note = position == HoldNotePosition.Start ? DrawableObject.Head : DrawableObject.Tail;
+ DrawableNote note = position == HoldNotePosition.Start ? (DrawableNote)DrawableObject.Head : DrawableObject.Tail;
Anchor = note.Anchor;
Origin = note.Origin;
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index bf630cf892..02c2158383 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -26,18 +26,20 @@ using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Difficulty;
using osu.Game.Rulesets.Mania.Edit;
using osu.Game.Rulesets.Mania.Scoring;
+using osu.Game.Rulesets.Mania.Skinning;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Skinning;
using osu.Game.Scoring;
namespace osu.Game.Rulesets.Mania
{
- 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 ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor();
- public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score);
@@ -45,6 +47,8 @@ namespace osu.Game.Rulesets.Mania
public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this);
+ public override ISkin CreateLegacySkinProvider(ISkinSource source) => new ManiaLegacySkinTransformer(source);
+
public override IEnumerable ConvertLegacyMods(LegacyMods mods)
{
if (mods.HasFlag(LegacyMods.Nightcore))
@@ -151,6 +155,7 @@ namespace osu.Game.Rulesets.Mania
new ManiaModRandom(),
new ManiaModDualStages(),
new ManiaModMirror(),
+ new ManiaModDifficultyAdjust(),
};
case ModType.Automation:
@@ -174,11 +179,13 @@ namespace osu.Game.Rulesets.Mania
public override string ShortName => SHORT_NAME;
+ public override string PlayingVerb => "Smashing keys";
+
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetMania };
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/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index 87b9633c80..155adb958b 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -1,7 +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 System.Diagnostics;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
@@ -21,11 +20,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
public override bool DisplayResult => false;
- public DrawableNote Head => headContainer.Child;
- public DrawableNote Tail => tailContainer.Child;
+ public DrawableHoldNoteHead Head => headContainer.Child;
+ public DrawableHoldNoteTail Tail => tailContainer.Child;
- private readonly Container headContainer;
- private readonly Container tailContainer;
+ private readonly Container headContainer;
+ private readonly Container tailContainer;
private readonly Container tickContainer;
private readonly BodyPiece bodyPiece;
@@ -33,12 +32,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
///
/// Time at which the user started holding this hold note. Null if the user is not holding this hold note.
///
- private double? holdStartTime;
+ public double? HoldStartTime { get; private set; }
///
/// Whether the hold note has been released too early and shouldn't give full score for the release.
///
- private bool hasBroken;
+ public bool HasBroken { get; private set; }
public DrawableHoldNote(HoldNote hitObject)
: base(hitObject)
@@ -49,8 +48,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
bodyPiece = new BodyPiece { RelativeSizeAxes = Axes.X },
tickContainer = new Container { RelativeSizeAxes = Axes.Both },
- headContainer = new Container { RelativeSizeAxes = Axes.Both },
- tailContainer = new Container { RelativeSizeAxes = Axes.Both },
+ headContainer = new Container { RelativeSizeAxes = Axes.Both },
+ tailContainer = new Container { RelativeSizeAxes = Axes.Both },
});
AccentColour.BindValueChanged(colour =>
@@ -65,11 +64,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
switch (hitObject)
{
- case DrawableHeadNote head:
+ case DrawableHoldNoteHead head:
headContainer.Child = head;
break;
- case DrawableTailNote tail:
+ case DrawableHoldNoteTail tail:
tailContainer.Child = tail;
break;
@@ -92,7 +91,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
switch (hitObject)
{
case TailNote _:
- return new DrawableTailNote(this)
+ return new DrawableHoldNoteTail(this)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
@@ -100,7 +99,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
};
case Note _:
- return new DrawableHeadNote(this)
+ return new DrawableHoldNoteHead(this)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
@@ -110,7 +109,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
case HoldNoteTick tick:
return new DrawableHoldNoteTick(tick)
{
- HoldStartTime = () => holdStartTime,
+ HoldStartTime = () => HoldStartTime,
AccentColour = { BindTarget = AccentColour }
};
}
@@ -125,12 +124,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
bodyPiece.Anchor = bodyPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
}
- protected override void CheckForResult(bool userTriggered, double timeOffset)
- {
- if (Tail.AllJudged)
- ApplyResult(r => r.Type = HitResult.Perfect);
- }
-
protected override void Update()
{
base.Update();
@@ -146,146 +139,64 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
base.UpdateStateTransforms(state);
}
- protected void BeginHold()
+ protected override void CheckForResult(bool userTriggered, double timeOffset)
{
- holdStartTime = Time.Current;
- bodyPiece.Hitting = true;
- }
+ if (Tail.AllJudged)
+ ApplyResult(r => r.Type = HitResult.Perfect);
- protected void EndHold()
- {
- holdStartTime = null;
- bodyPiece.Hitting = false;
+ if (Tail.Result.Type == HitResult.Miss)
+ HasBroken = true;
}
public bool OnPressed(ManiaAction action)
{
- // Make sure the action happened within the body of the hold note
- if (Time.Current < HitObject.StartTime || Time.Current > HitObject.EndTime)
+ if (AllJudged)
return false;
if (action != Action.Value)
return false;
- // The user has pressed during the body of the hold note, after the head note and its hit windows have passed
- // and within the limited range of the above if-statement. This state will be managed by the head note if the
- // user has pressed during the hit windows of the head note.
- BeginHold();
+ beginHoldAt(Time.Current - Head.HitObject.StartTime);
+ Head.UpdateResult();
+
return true;
}
+ private void beginHoldAt(double timeOffset)
+ {
+ if (timeOffset < -Head.HitObject.HitWindows.WindowFor(HitResult.Miss))
+ return;
+
+ HoldStartTime = Time.Current;
+ bodyPiece.Hitting = true;
+ }
+
public bool OnReleased(ManiaAction action)
{
- // Make sure that the user started holding the key during the hold note
- if (!holdStartTime.HasValue)
+ if (AllJudged)
return false;
if (action != Action.Value)
return false;
- EndHold();
+ // Make sure a hold was started
+ if (HoldStartTime == null)
+ return false;
+
+ Tail.UpdateResult();
+ endHold();
// If the key has been released too early, the user should not receive full score for the release
if (!Tail.IsHit)
- hasBroken = true;
+ HasBroken = true;
return true;
}
- ///
- /// The head note of a hold.
- ///
- private class DrawableHeadNote : DrawableNote
+ private void endHold()
{
- private readonly DrawableHoldNote holdNote;
-
- public DrawableHeadNote(DrawableHoldNote holdNote)
- : base(holdNote.HitObject.Head)
- {
- this.holdNote = holdNote;
- }
-
- public override bool OnPressed(ManiaAction action)
- {
- if (!base.OnPressed(action))
- return false;
-
- // If the key has been released too early, the user should not receive full score for the release
- if (Result.Type == HitResult.Miss)
- holdNote.hasBroken = true;
-
- // The head note also handles early hits before the body, but we want accurate early hits to count as the body being held
- // The body doesn't handle these early early hits, so we have to explicitly set the holding state here
- holdNote.BeginHold();
-
- return true;
- }
- }
-
- ///
- /// The tail note of a hold.
- ///
- private class DrawableTailNote : DrawableNote
- {
- ///
- /// Lenience of release hit windows. This is to make cases where the hold note release
- /// is timed alongside presses of other hit objects less awkward.
- /// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
- ///
- private const double release_window_lenience = 1.5;
-
- private readonly DrawableHoldNote holdNote;
-
- public DrawableTailNote(DrawableHoldNote holdNote)
- : base(holdNote.HitObject.Tail)
- {
- this.holdNote = holdNote;
- }
-
- protected override void CheckForResult(bool userTriggered, double timeOffset)
- {
- Debug.Assert(HitObject.HitWindows != null);
-
- // Factor in the release lenience
- timeOffset /= release_window_lenience;
-
- if (!userTriggered)
- {
- if (!HitObject.HitWindows.CanBeHit(timeOffset))
- ApplyResult(r => r.Type = HitResult.Miss);
-
- return;
- }
-
- var result = HitObject.HitWindows.ResultFor(timeOffset);
- if (result == HitResult.None)
- return;
-
- ApplyResult(r =>
- {
- if (holdNote.hasBroken && (result == HitResult.Perfect || result == HitResult.Perfect))
- result = HitResult.Good;
-
- r.Type = result;
- });
- }
-
- public override bool OnPressed(ManiaAction action) => false; // Tail doesn't handle key down
-
- public override bool OnReleased(ManiaAction action)
- {
- // Make sure that the user started holding the key during the hold note
- if (!holdNote.holdStartTime.HasValue)
- return false;
-
- if (action != Action.Value)
- return false;
-
- UpdateResult(true);
-
- // Handled by the hold note, which will set holding = false
- return false;
- }
+ HoldStartTime = null;
+ bodyPiece.Hitting = false;
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs
new file mode 100644
index 0000000000..a5d03bf765
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs
@@ -0,0 +1,22 @@
+// 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.Mania.Objects.Drawables
+{
+ ///
+ /// The head of a .
+ ///
+ public class DrawableHoldNoteHead : DrawableNote
+ {
+ public DrawableHoldNoteHead(DrawableHoldNote holdNote)
+ : base(holdNote.HitObject.Head)
+ {
+ }
+
+ public void UpdateResult() => base.UpdateResult(true);
+
+ public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note
+
+ public override bool OnReleased(ManiaAction action) => false; // Handled by the hold note
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs
new file mode 100644
index 0000000000..a660144dd1
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs
@@ -0,0 +1,64 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Diagnostics;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Mania.Objects.Drawables
+{
+ ///
+ /// The tail of a .
+ ///
+ public class DrawableHoldNoteTail : DrawableNote
+ {
+ ///
+ /// Lenience of release hit windows. This is to make cases where the hold note release
+ /// is timed alongside presses of other hit objects less awkward.
+ /// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
+ ///
+ private const double release_window_lenience = 1.5;
+
+ private readonly DrawableHoldNote holdNote;
+
+ public DrawableHoldNoteTail(DrawableHoldNote holdNote)
+ : base(holdNote.HitObject.Tail)
+ {
+ this.holdNote = holdNote;
+ }
+
+ public void UpdateResult() => base.UpdateResult(true);
+
+ protected override void CheckForResult(bool userTriggered, double timeOffset)
+ {
+ Debug.Assert(HitObject.HitWindows != null);
+
+ // Factor in the release lenience
+ timeOffset /= release_window_lenience;
+
+ if (!userTriggered)
+ {
+ if (!HitObject.HitWindows.CanBeHit(timeOffset))
+ ApplyResult(r => r.Type = HitResult.Miss);
+
+ return;
+ }
+
+ var result = HitObject.HitWindows.ResultFor(timeOffset);
+ if (result == HitResult.None)
+ return;
+
+ ApplyResult(r =>
+ {
+ // If the head wasn't hit or the hold note was broken, cap the max score to Meh.
+ if (result > HitResult.Meh && (!holdNote.Head.IsHit || holdNote.HasBroken))
+ result = HitResult.Meh;
+
+ r.Type = result;
+ });
+ }
+
+ public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note
+
+ public override bool OnReleased(ManiaAction action) => false; // Handled by the hold note
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs
deleted file mode 100644
index 1d25a0c966..0000000000
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs
+++ /dev/null
@@ -1,68 +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 osu.Framework.Extensions.Color4Extensions;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Effects;
-using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics;
-using osuTK.Graphics;
-
-namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
-{
- public class GlowPiece : CompositeDrawable, IHasAccentColour
- {
- private const float glow_alpha = 0.7f;
- private const float glow_radius = 5;
-
- public GlowPiece()
- {
- RelativeSizeAxes = Axes.Both;
- Masking = true;
-
- InternalChild = new Box
- {
- RelativeSizeAxes = Axes.Both,
- Alpha = 0,
- AlwaysPresent = true
- };
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
- updateGlow();
- }
-
- private Color4 accentColour;
-
- public Color4 AccentColour
- {
- get => accentColour;
- set
- {
- if (accentColour == value)
- return;
-
- accentColour = value;
-
- updateGlow();
- }
- }
-
- private void updateGlow()
- {
- if (!IsLoaded)
- return;
-
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = AccentColour.Opacity(glow_alpha),
- Radius = glow_radius,
- Hollow = true
- };
- }
- }
-}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs
deleted file mode 100644
index 48c7ea7b7f..0000000000
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs
+++ /dev/null
@@ -1,85 +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 osuTK.Graphics;
-using osu.Framework.Extensions.Color4Extensions;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Colour;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics;
-
-namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
-{
- public class LaneGlowPiece : CompositeDrawable, IHasAccentColour
- {
- private const float total_height = 100;
- private const float glow_height = 50;
- private const float glow_alpha = 0.4f;
- private const float edge_alpha = 0.3f;
-
- public LaneGlowPiece()
- {
- BypassAutoSizeAxes = Axes.Both;
- RelativeSizeAxes = Axes.X;
- Height = total_height;
-
- InternalChildren = new[]
- {
- new Container
- {
- Name = "Left edge",
- RelativeSizeAxes = Axes.Y,
- Width = 1,
- Children = createGradient(edge_alpha)
- },
- new Container
- {
- Name = "Right edge",
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- RelativeSizeAxes = Axes.Y,
- Width = 1,
- Children = createGradient(edge_alpha)
- },
- new Container
- {
- Name = "Glow",
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.X,
- Height = glow_height,
- Children = createGradient(glow_alpha)
- }
- };
- }
-
- private Drawable[] createGradient(float alpha) => new Drawable[]
- {
- new Box
- {
- Name = "Top",
- RelativeSizeAxes = Axes.Both,
- Height = 0.5f,
- Blending = BlendingParameters.Additive,
- Colour = ColourInfo.GradientVertical(Color4.Transparent, Color4.White.Opacity(alpha))
- },
- new Box
- {
- Name = "Bottom",
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- RelativeSizeAxes = Axes.Both,
- Height = 0.5f,
- Blending = BlendingParameters.Additive,
- Colour = ColourInfo.GradientVertical(Color4.White.Opacity(alpha), Color4.Transparent)
- }
- };
-
- public Color4 AccentColour
- {
- get => Colour;
- set => Colour = value;
- }
- }
-}
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/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
index a678ef60e7..9b54b48de3 100644
--- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
@@ -1,87 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Scoring
{
internal class ManiaScoreProcessor : ScoreProcessor
{
- ///
- /// The hit HP multiplier at OD = 0.
- ///
- private const double hp_multiplier_min = 0.75;
-
- ///
- /// The hit HP multiplier at OD = 0.
- ///
- private const double hp_multiplier_mid = 0.85;
-
- ///
- /// The hit HP multiplier at OD = 0.
- ///
- private const double hp_multiplier_max = 1;
-
- ///
- /// The MISS HP multiplier at OD = 0.
- ///
- private const double hp_multiplier_miss_min = 0.5;
-
- ///
- /// The MISS HP multiplier at OD = 5.
- ///
- private const double hp_multiplier_miss_mid = 0.75;
-
- ///
- /// The MISS HP multiplier at OD = 10.
- ///
- private const double hp_multiplier_miss_max = 1;
-
- ///
- /// The MISS HP multiplier. This is multiplied to the miss hp increase.
- ///
- private double hpMissMultiplier = 1;
-
- ///
- /// The HIT HP multiplier. This is multiplied to hit hp increases.
- ///
- private double hpMultiplier = 1;
-
- public ManiaScoreProcessor(IBeatmap beatmap)
- : base(beatmap)
- {
- }
-
- protected override void ApplyBeatmap(IBeatmap beatmap)
- {
- base.ApplyBeatmap(beatmap);
-
- BeatmapDifficulty difficulty = beatmap.BeatmapInfo.BaseDifficulty;
- hpMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_min, hp_multiplier_mid, hp_multiplier_max);
- hpMissMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_miss_min, hp_multiplier_miss_mid, hp_multiplier_miss_max);
- }
-
- protected override void SimulateAutoplay(IBeatmap beatmap)
- {
- while (true)
- {
- base.SimulateAutoplay(beatmap);
-
- if (!HasFailed)
- break;
-
- hpMultiplier *= 1.01;
- hpMissMultiplier *= 0.98;
-
- Reset(false);
- }
- }
-
- protected override double HealthAdjustmentFactorFor(JudgementResult result)
- => result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier;
-
public override HitWindows CreateHitWindows() => new ManiaHitWindows();
}
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs
new file mode 100644
index 0000000000..f3739ce7c2
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs
@@ -0,0 +1,67 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Audio;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Mania.Skinning
+{
+ public class ManiaLegacySkinTransformer : ISkin
+ {
+ private readonly ISkin source;
+
+ public ManiaLegacySkinTransformer(ISkin source)
+ {
+ this.source = source;
+ }
+
+ public Drawable GetDrawableComponent(ISkinComponent component)
+ {
+ switch (component)
+ {
+ case GameplaySkinComponent resultComponent:
+ return getResult(resultComponent);
+ }
+
+ return null;
+ }
+
+ private Drawable getResult(GameplaySkinComponent resultComponent)
+ {
+ switch (resultComponent.Component)
+ {
+ case HitResult.Miss:
+ return this.GetAnimation("mania-hit0", true, true);
+
+ case HitResult.Meh:
+ return this.GetAnimation("mania-hit50", true, true);
+
+ case HitResult.Ok:
+ return this.GetAnimation("mania-hit100", true, true);
+
+ case HitResult.Good:
+ return this.GetAnimation("mania-hit200", true, true);
+
+ case HitResult.Great:
+ return this.GetAnimation("mania-hit300", true, true);
+
+ case HitResult.Perfect:
+ return this.GetAnimation("mania-hit300g", true, true);
+ }
+
+ return null;
+ }
+
+ public Texture GetTexture(string componentName) => source.GetTexture(componentName);
+
+ public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample);
+
+ public IBindable GetConfig(TLookup lookup) =>
+ source.GetConfig(lookup);
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
index 386bcbb724..ee2cec1bbd 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
@@ -22,26 +22,15 @@ namespace osu.Game.Rulesets.Mania.UI.Components
private readonly IBindable direction = new Bindable();
- private readonly Container hitTargetLine;
- private readonly Drawable hitTargetBar;
+ private readonly Drawable hitTarget;
public ColumnHitObjectArea(HitObjectContainer hitObjectContainer)
{
InternalChildren = new[]
{
- hitTargetBar = new Box
+ hitTarget = new DefaultHitTarget
{
RelativeSizeAxes = Axes.X,
- Height = NotePiece.NOTE_HEIGHT,
- Alpha = 0.6f,
- Colour = Color4.Black
- },
- hitTargetLine = new Container
- {
- RelativeSizeAxes = Axes.X,
- Height = hit_target_bar_height,
- Masking = true,
- Child = new Box { RelativeSizeAxes = Axes.Both }
},
hitObjectContainer
};
@@ -55,17 +44,10 @@ namespace osu.Game.Rulesets.Mania.UI.Components
{
Anchor anchor = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
- hitTargetBar.Anchor = hitTargetBar.Origin = anchor;
- hitTargetLine.Anchor = hitTargetLine.Origin = anchor;
+ hitTarget.Anchor = hitTarget.Origin = anchor;
}, true);
}
- protected override void LoadComplete()
- {
- base.LoadComplete();
- updateColours();
- }
-
private Color4 accentColour;
public Color4 AccentColour
@@ -78,21 +60,86 @@ namespace osu.Game.Rulesets.Mania.UI.Components
accentColour = value;
- updateColours();
+ if (hitTarget is IHasAccentColour colouredHitTarget)
+ colouredHitTarget.AccentColour = accentColour;
}
}
- private void updateColours()
+ private class DefaultHitTarget : CompositeDrawable, IHasAccentColour
{
- if (!IsLoaded)
- return;
+ private readonly IBindable direction = new Bindable();
- hitTargetLine.EdgeEffect = new EdgeEffectParameters
+ private readonly Container hitTargetLine;
+ private readonly Drawable hitTargetBar;
+
+ public DefaultHitTarget()
{
- Type = EdgeEffectType.Glow,
- Radius = 5,
- Colour = accentColour.Opacity(0.5f),
- };
+ InternalChildren = new[]
+ {
+ hitTargetBar = new Box
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = NotePiece.NOTE_HEIGHT,
+ Alpha = 0.6f,
+ Colour = Color4.Black
+ },
+ hitTargetLine = new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = hit_target_bar_height,
+ Masking = true,
+ Child = new Box { RelativeSizeAxes = Axes.Both }
+ },
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(IScrollingInfo scrollingInfo)
+ {
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(dir =>
+ {
+ Anchor anchor = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
+
+ hitTargetBar.Anchor = hitTargetBar.Origin = anchor;
+ hitTargetLine.Anchor = hitTargetLine.Origin = anchor;
+ }, true);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ updateColours();
+ }
+
+ private Color4 accentColour;
+
+ public Color4 AccentColour
+ {
+ get => accentColour;
+ set
+ {
+ if (accentColour == value)
+ return;
+
+ accentColour = value;
+
+ updateColours();
+ }
+ }
+
+ private void updateColours()
+ {
+ if (!IsLoaded)
+ return;
+
+ hitTargetLine.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Radius = 5,
+ Colour = accentColour.Opacity(0.5f),
+ };
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs
index 2fad3bd04f..d4c3000d3f 100644
--- a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs
+++ b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Tests
[BackgroundDependencyLoader]
private void load(AudioManager audio, SkinManager skinManager)
{
- var dllStore = new DllResourceStore("osu.Game.Rulesets.Osu.Tests.dll");
+ var dllStore = new DllResourceStore(typeof(SkinnableTestScene).Assembly);
metricsSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), audio, true);
defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info);
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
index eff4d919b0..c9b3d08a22 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
@@ -33,8 +33,8 @@ namespace osu.Game.Rulesets.Osu.Tests
typeof(CircularDistanceSnapGrid)
};
- [Cached(typeof(IEditorBeatmap))]
- private readonly EditorBeatmap editorBeatmap;
+ [Cached(typeof(EditorBeatmap))]
+ private readonly EditorBeatmap editorBeatmap;
[Cached]
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
@@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public TestSceneOsuDistanceSnapGrid()
{
- editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ editorBeatmap = new EditorBeatmap(new OsuBeatmap());
}
[SetUp]
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.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
index 9d4e016eae..1d8c4708c1 100644
--- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
+++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
@@ -4,7 +4,7 @@
-
+
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
index 2296030f81..147d74c929 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
@@ -7,7 +7,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using System.Collections.Generic;
using osu.Game.Rulesets.Objects.Types;
-using System;
+using System.Linq;
using osu.Game.Rulesets.Osu.UI;
using osu.Framework.Extensions.IEnumerableExtensions;
@@ -15,12 +15,12 @@ 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)
{
}
- protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasPosition) };
+ public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasPosition);
protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap)
{
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index a2c1a5f5f4..49624ea733 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -91,10 +91,10 @@ namespace osu.Game.Rulesets.Osu.Edit
if (sourceIndex == -1)
return null;
- OsuHitObject sourceObject = EditorBeatmap.HitObjects[sourceIndex];
+ HitObject sourceObject = EditorBeatmap.HitObjects[sourceIndex];
int targetIndex = sourceIndex + targetOffset;
- OsuHitObject targetObject = null;
+ HitObject targetObject = null;
// Keep advancing the target object while its start time falls before the end time of the source object
while (true)
@@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Osu.Edit
targetIndex++;
}
- return new OsuDistanceSnapGrid(sourceObject, targetObject);
+ return new OsuDistanceSnapGrid((OsuHitObject)sourceObject, (OsuHitObject)targetObject);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs
index 7a5b98864c..bf30fbc351 100644
--- a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs
+++ b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs
@@ -27,22 +27,5 @@ namespace osu.Game.Rulesets.Osu.Judgements
return 300;
}
}
-
- protected override double HealthIncreaseFor(HitResult result)
- {
- switch (result)
- {
- case HitResult.Miss:
- return -0.02;
-
- case HitResult.Meh:
- case HitResult.Good:
- case HitResult.Great:
- return 0.01;
-
- default:
- return 0;
- }
- }
}
}
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..7eee71be81
--- /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);
+
+ TransferSetting(CircleSize, difficulty.CircleSize);
+ TransferSetting(ApproachRate, 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..148869f5e8 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -32,13 +32,13 @@ 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 ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor();
- public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap);
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap, this);
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap);
@@ -130,6 +130,7 @@ namespace osu.Game.Rulesets.Osu
return new Mod[]
{
new OsuModTarget(),
+ new OsuModDifficultyAdjust(),
};
case ModType.Automation:
@@ -174,11 +175,13 @@ namespace osu.Game.Rulesets.Osu
public override string ShortName => SHORT_NAME;
+ public override string PlayingVerb => "Clicking circles";
+
public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this);
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/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
index 6779271cb3..1de7d488f3 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
@@ -1,7 +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 osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Judgements;
@@ -11,44 +10,6 @@ namespace osu.Game.Rulesets.Osu.Scoring
{
internal class OsuScoreProcessor : ScoreProcessor
{
- public OsuScoreProcessor(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);
public override HitWindows CreateHitWindows() => new OsuHitWindows();
diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
index d728d65bfd..da89b37fbf 100644
--- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
+++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
@@ -4,7 +4,7 @@
-
+
diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs
index aaf113f216..c31b07344d 100644
--- a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs
+++ b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs
@@ -26,10 +26,6 @@ namespace osu.Game.Rulesets.Taiko.Audio
var centre = s.GetSampleInfo();
var rim = s.GetSampleInfo(HitSampleInfo.HIT_CLAP);
- // todo: this is ugly
- centre.Namespace = "taiko";
- rim.Namespace = "taiko";
-
mappings[s.Time] = new DrumSample
{
Centre = addSound(centre),
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index 10cc861b7e..cc9d6e4470 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -39,14 +39,14 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
private readonly bool isForCurrentRuleset;
- protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(HitObject) };
-
- 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;
+
protected override Beatmap ConvertBeatmap(IBeatmap original)
{
// Rewrite the beatmap info to add the slider velocity multiplier
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/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
index 2da5a9c403..b9d31ff906 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
@@ -166,8 +166,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
// Normal and clap samples are handled by the drum
protected override IEnumerable GetSamples() => HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP);
- protected override string SampleNamespace => "taiko";
-
protected virtual TaikoPiece CreateMainPiece() => new CirclePiece();
///
diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
index 4b234b56d4..48eb33976e 100644
--- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
+++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
@@ -121,41 +121,13 @@ namespace osu.Game.Rulesets.Taiko.Replays
var nextHitObject = GetNextObject(i); // Get the next object that requires pressing the same button
bool canDelayKeyUp = nextHitObject == null || nextHitObject.StartTime > endTime + KEY_UP_DELAY;
-
double calculatedDelay = canDelayKeyUp ? KEY_UP_DELAY : (nextHitObject.StartTime - endTime) * 0.9;
-
Frames.Add(new TaikoReplayFrame(endTime + calculatedDelay));
- if (i < Beatmap.HitObjects.Count - 1)
- {
- double waitTime = Beatmap.HitObjects[i + 1].StartTime - 1000;
- if (waitTime > endTime)
- Frames.Add(new TaikoReplayFrame(waitTime));
- }
-
hitButton = !hitButton;
}
return Replay;
}
-
- protected override HitObject GetNextObject(int currentIndex)
- {
- Type desiredType = Beatmap.HitObjects[currentIndex].GetType();
-
- for (int i = currentIndex + 1; i < Beatmap.HitObjects.Count; i++)
- {
- var currentObj = Beatmap.HitObjects[i];
-
- if (currentObj.GetType() == desiredType ||
- // Un-press all keys before a DrumRoll or Swell
- currentObj is DrumRoll || currentObj is Swell)
- {
- return Beatmap.HitObjects[i];
- }
- }
-
- return null;
- }
}
}
diff --git a/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitclap.wav b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitclap.wav
new file mode 100755
index 0000000000..9ea2be5855
Binary files /dev/null and b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitclap.wav differ
diff --git a/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitfinish.wav b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitfinish.wav
new file mode 100755
index 0000000000..af270ae12a
Binary files /dev/null and b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitfinish.wav differ
diff --git a/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitnormal.wav b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitnormal.wav
new file mode 100755
index 0000000000..3d8024c6ae
Binary files /dev/null and b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitnormal.wav differ
diff --git a/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitwhistle.wav b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitwhistle.wav
new file mode 100755
index 0000000000..16d254cc87
Binary files /dev/null and b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitwhistle.wav differ
diff --git a/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitclap.wav b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitclap.wav
new file mode 100755
index 0000000000..b4cfa26265
Binary files /dev/null and b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitclap.wav differ
diff --git a/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitfinish.wav b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitfinish.wav
new file mode 100755
index 0000000000..97804a5a61
Binary files /dev/null and b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitfinish.wav differ
diff --git a/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitnormal.wav b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitnormal.wav
new file mode 100755
index 0000000000..67f02877a8
Binary files /dev/null and b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitnormal.wav differ
diff --git a/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitwhistle.wav b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitwhistle.wav
new file mode 100755
index 0000000000..10206cd228
Binary files /dev/null and b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitwhistle.wav differ
diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs
new file mode 100644
index 0000000000..edb089dbac
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.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 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
+{
+ ///
+ /// A for the taiko ruleset.
+ /// Taiko fails if the player has not half-filled their health by the end of the map.
+ ///
+ public class TaikoHealthProcessor : AccumulatingHealthProcessor
+ {
+ ///
+ /// A value used for calculating .
+ ///
+ private const double object_count_factor = 3;
+
+ ///
+ /// HP multiplier for a successful .
+ ///
+ private double hpMultiplier;
+
+ ///
+ /// HP multiplier for a .
+ ///
+ private double hpMissMultiplier;
+
+ public TaikoHealthProcessor()
+ : base(0.5)
+ {
+ }
+
+ public 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 GetHealthIncreaseFor(JudgementResult result)
+ => base.GetHealthIncreaseFor(result) * (result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier);
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
index ae593d2e3a..003d40af56 100644
--- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
+++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
@@ -1,60 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using 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/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
new file mode 100644
index 0000000000..381cd14cd4
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
@@ -0,0 +1,55 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Audio;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Taiko.Skinning
+{
+ public class TaikoLegacySkinTransformer : ISkin
+ {
+ private readonly ISkinSource source;
+
+ public TaikoLegacySkinTransformer(ISkinSource source)
+ {
+ this.source = source;
+ }
+
+ public Drawable GetDrawableComponent(ISkinComponent component) => source.GetDrawableComponent(component);
+
+ public Texture GetTexture(string componentName) => source.GetTexture(componentName);
+
+ public SampleChannel GetSample(ISampleInfo sampleInfo) => source.GetSample(new LegacyTaikoSampleInfo(sampleInfo));
+
+ public IBindable GetConfig(TLookup lookup) => source.GetConfig(lookup);
+
+ private class LegacyTaikoSampleInfo : ISampleInfo
+ {
+ private readonly ISampleInfo source;
+
+ public LegacyTaikoSampleInfo(ISampleInfo source)
+ {
+ this.source = source;
+ }
+
+ public IEnumerable LookupNames
+ {
+ get
+ {
+ foreach (var name in source.LookupNames)
+ yield return $"taiko-{name}";
+
+ foreach (var name in source.LookupNames)
+ yield return name;
+ }
+ }
+
+ public int Volume => source.Volume;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index ca7ab30867..fc79e59864 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -21,16 +21,22 @@ using osu.Game.Rulesets.Taiko.Difficulty;
using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Scoring;
using System;
+using osu.Game.Rulesets.Taiko.Skinning;
+using osu.Game.Skinning;
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 ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor();
- public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap);
+ public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new TaikoHealthProcessor();
+
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap, this);
+
+ public override ISkin CreateLegacySkinProvider(ISkinSource source) => new TaikoLegacySkinTransformer(source);
public const string SHORT_NAME = "taiko";
@@ -105,6 +111,12 @@ namespace osu.Game.Rulesets.Taiko
new TaikoModFlashlight(),
};
+ case ModType.Conversion:
+ return new Mod[]
+ {
+ new TaikoModDifficultyAdjust(),
+ };
+
case ModType.Automation:
return new Mod[]
{
@@ -127,13 +139,15 @@ namespace osu.Game.Rulesets.Taiko
public override string ShortName => SHORT_NAME;
+ public override string PlayingVerb => "Bashing drums";
+
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetTaiko };
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap);
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/EditorBeatmapTest.cs b/osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs
index 98e630abd2..12d729d09f 100644
--- a/osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestHitObjectAddEvent()
{
- var editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ var editorBeatmap = new EditorBeatmap(new OsuBeatmap());
HitObject addedObject = null;
editorBeatmap.HitObjectAdded += h => addedObject = h;
@@ -38,7 +38,7 @@ namespace osu.Game.Tests.Beatmaps
public void HitObjectRemoveEvent()
{
var hitCircle = new HitCircle();
- var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
+ var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
HitObject removedObject = null;
editorBeatmap.HitObjectRemoved += h => removedObject = h;
@@ -55,7 +55,7 @@ namespace osu.Game.Tests.Beatmaps
public void TestInitialHitObjectStartTimeChangeEvent()
{
var hitCircle = new HitCircle();
- var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
+ var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
HitObject changedObject = null;
editorBeatmap.StartTimeChanged += h => changedObject = h;
@@ -71,7 +71,7 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestAddedHitObjectStartTimeChangeEvent()
{
- var editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ var editorBeatmap = new EditorBeatmap(new OsuBeatmap());
HitObject changedObject = null;
editorBeatmap.StartTimeChanged += h => changedObject = h;
@@ -92,7 +92,7 @@ namespace osu.Game.Tests.Beatmaps
public void TestRemovedHitObjectStartTimeChangeEvent()
{
var hitCircle = new HitCircle();
- var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
+ var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
HitObject changedObject = null;
editorBeatmap.StartTimeChanged += h => changedObject = h;
@@ -110,7 +110,7 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestAddHitObjectInMiddle()
{
- var editorBeatmap = new EditorBeatmap(new OsuBeatmap
+ var editorBeatmap = new EditorBeatmap(new OsuBeatmap
{
HitObjects =
{
@@ -134,7 +134,7 @@ namespace osu.Game.Tests.Beatmaps
public void TestResortWhenStartTimeChanged()
{
var hitCircle = new HitCircle { StartTime = 1000 };
- var editorBeatmap = new EditorBeatmap(new OsuBeatmap
+ var editorBeatmap = new EditorBeatmap(new OsuBeatmap
{
HitObjects =
{
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/Editor/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs
index fe3cc375ea..2d336bd19c 100644
--- a/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs
+++ b/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs
@@ -2,11 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Testing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Edit;
-using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Tests.Visual;
@@ -17,6 +18,9 @@ namespace osu.Game.Tests.Editor
{
private TestHitObjectComposer composer;
+ [Cached(typeof(EditorBeatmap))]
+ private readonly EditorBeatmap editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+
[SetUp]
public void Setup() => Schedule(() =>
{
@@ -183,7 +187,7 @@ namespace osu.Game.Tests.Editor
private class TestHitObjectComposer : OsuHitObjectComposer
{
- public new EditorBeatmap EditorBeatmap => base.EditorBeatmap;
+ public new EditorBeatmap EditorBeatmap => base.EditorBeatmap;
public TestHitObjectComposer()
: base(new OsuRuleset())
diff --git a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs
new file mode 100644
index 0000000000..eec52669ff
--- /dev/null
+++ b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs
@@ -0,0 +1,159 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.MathUtils;
+using osu.Framework.Testing;
+using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Tests.Gameplay
+{
+ [HeadlessTest]
+ public class TestSceneDrainingHealthProcessor : OsuTestScene
+ {
+ private Bindable breakTime;
+ private HealthProcessor processor;
+ private ManualClock clock;
+
+ [Test]
+ public void TestInitialHealthStartsAtOne()
+ {
+ createProcessor(createBeatmap(1000, 2000));
+
+ assertHealthEqualTo(1);
+ }
+
+ [Test]
+ public void TestHealthNotDrainedBeforeGameplayStart()
+ {
+ createProcessor(createBeatmap(1000, 2000));
+
+ setTime(100);
+ assertHealthEqualTo(1);
+ setTime(900);
+ assertHealthEqualTo(1);
+ }
+
+ [Test]
+ public void TestHealthNotDrainedAfterGameplayEnd()
+ {
+ createProcessor(createBeatmap(1000, 2000));
+ setTime(2001); // After the hitobjects
+ setHealth(1); // Reset the current health for assertions to take place
+
+ setTime(2100);
+ assertHealthEqualTo(1);
+ setTime(3000);
+ assertHealthEqualTo(1);
+ }
+
+ [Test]
+ public void TestHealthNotDrainedDuringBreak()
+ {
+ createProcessor(createBeatmap(0, 2000));
+ setBreak(true);
+
+ setTime(700);
+ assertHealthEqualTo(1);
+ setTime(900);
+ assertHealthEqualTo(1);
+ }
+
+ [Test]
+ public void TestHealthDrainedDuringGameplay()
+ {
+ createProcessor(createBeatmap(0, 1000));
+
+ setTime(500);
+ assertHealthNotEqualTo(1);
+ }
+
+ [Test]
+ public void TestHealthGainedAfterRewind()
+ {
+ createProcessor(createBeatmap(0, 1000));
+ setTime(500);
+
+ setTime(0);
+ assertHealthEqualTo(1);
+ }
+
+ [Test]
+ public void TestHealthGainedOnHit()
+ {
+ Beatmap beatmap = createBeatmap(0, 1000);
+
+ createProcessor(beatmap);
+ setTime(10); // Decrease health slightly
+ assertHealthNotEqualTo(1);
+
+ AddStep("apply hit result", () => processor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new Judgement()) { Type = HitResult.Perfect }));
+ assertHealthEqualTo(1);
+ }
+
+ [Test]
+ public void TestHealthRemovedOnRevert()
+ {
+ var beatmap = createBeatmap(0, 1000);
+ JudgementResult result = null;
+
+ createProcessor(beatmap);
+ setTime(10); // Decrease health slightly
+ AddStep("apply hit result", () => processor.ApplyResult(result = new JudgementResult(beatmap.HitObjects[0], new Judgement()) { Type = HitResult.Perfect }));
+
+ AddStep("revert hit result", () => processor.RevertResult(result));
+ assertHealthNotEqualTo(1);
+ }
+
+ private Beatmap createBeatmap(double startTime, double endTime)
+ {
+ var beatmap = new Beatmap
+ {
+ BeatmapInfo = { BaseDifficulty = { DrainRate = 5 } },
+ };
+
+ for (double time = startTime; time <= endTime; time += 100)
+ beatmap.HitObjects.Add(new JudgeableHitObject { StartTime = time });
+
+ return beatmap;
+ }
+
+ private void createProcessor(Beatmap beatmap) => AddStep("create processor", () =>
+ {
+ breakTime = new Bindable();
+
+ Child = processor = new DrainingHealthProcessor(beatmap.HitObjects[0].StartTime).With(d =>
+ {
+ d.RelativeSizeAxes = Axes.Both;
+ d.Clock = new FramedClock(clock = new ManualClock());
+ });
+
+ processor.IsBreakTime.BindTo(breakTime);
+ processor.ApplyBeatmap(beatmap);
+ });
+
+ private void setTime(double time) => AddStep($"set time = {time}", () => clock.CurrentTime = time);
+
+ private void setHealth(double health) => AddStep($"set health = {health}", () => processor.Health.Value = health);
+
+ private void setBreak(bool enabled) => AddStep($"{(enabled ? "enable" : "disable")} break", () => breakTime.Value = enabled);
+
+ private void assertHealthEqualTo(double value)
+ => AddAssert($"health = {value}", () => Precision.AlmostEquals(value, processor.Health.Value, 0.0001f));
+
+ private void assertHealthNotEqualTo(double value)
+ => AddAssert($"health != {value}", () => !Precision.AlmostEquals(value, processor.Health.Value, 0.0001f));
+
+ private class JudgeableHitObject : HitObject
+ {
+ public override Judgement CreateJudgement() => new Judgement();
+ }
+ }
+}
diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
new file mode 100644
index 0000000000..84506739ab
--- /dev/null
+++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
@@ -0,0 +1,76 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
+using osu.Framework.IO.Stores;
+using osu.Framework.Testing;
+using osu.Game.Audio;
+using osu.Game.Skinning;
+using osu.Game.Tests.Resources;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Tests.Gameplay
+{
+ [HeadlessTest]
+ public class TestSceneStoryboardSamples : OsuTestScene
+ {
+ [Test]
+ public void TestRetrieveTopLevelSample()
+ {
+ ISkin skin = null;
+ SampleChannel channel = null;
+
+ AddStep("create skin", () => skin = new TestSkin("test-sample", Audio));
+ AddStep("retrieve sample", () => channel = skin.GetSample(new SampleInfo("test-sample")));
+
+ AddAssert("sample is non-null", () => channel != null);
+ }
+
+ [Test]
+ public void TestRetrieveSampleInSubFolder()
+ {
+ ISkin skin = null;
+ SampleChannel channel = null;
+
+ AddStep("create skin", () => skin = new TestSkin("folder/test-sample", Audio));
+ AddStep("retrieve sample", () => channel = skin.GetSample(new SampleInfo("folder/test-sample")));
+
+ AddAssert("sample is non-null", () => channel != null);
+ }
+
+ private class TestSkin : LegacySkin
+ {
+ public TestSkin(string resourceName, AudioManager audioManager)
+ : base(DefaultLegacySkin.Info, new TestResourceStore(resourceName), audioManager, "skin.ini")
+ {
+ }
+ }
+
+ private class TestResourceStore : IResourceStore
+ {
+ private readonly string resourceName;
+
+ public TestResourceStore(string resourceName)
+ {
+ this.resourceName = resourceName;
+ }
+
+ public byte[] Get(string name) => name == resourceName ? TestResources.GetStore().Get("Resources/test-sample.mp3") : null;
+
+ public Task GetAsync(string name) => name == resourceName ? TestResources.GetStore().GetAsync("Resources/test-sample.mp3") : null;
+
+ public Stream GetStream(string name) => name == resourceName ? TestResources.GetStore().GetStream("Resources/test-sample.mp3") : null;
+
+ public IEnumerable GetAvailableResources() => new[] { resourceName };
+
+ public void Dispose()
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus.osz b/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus.osz
new file mode 100644
index 0000000000..987dbea6db
Binary files /dev/null and b/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus.osz differ
diff --git a/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus_virtual.osz b/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus_virtual.osz
new file mode 100644
index 0000000000..8a92423d35
Binary files /dev/null and b/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus_virtual.osz differ
diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs
index a57405628a..8b892fbb2f 100644
--- a/osu.Game.Tests/Resources/TestResources.cs
+++ b/osu.Game.Tests/Resources/TestResources.cs
@@ -9,11 +9,11 @@ namespace osu.Game.Tests.Resources
{
public static class TestResources
{
- public static DllResourceStore GetStore() => new DllResourceStore("osu.Game.Tests.dll");
+ public static DllResourceStore GetStore() => new DllResourceStore(typeof(TestResources).Assembly);
public static Stream OpenResource(string name) => GetStore().GetStream($"Resources/{name}");
- public static Stream GetTestBeatmapStream(bool virtualTrack = false) => new DllResourceStore("osu.Game.Resources.dll").GetStream($"Beatmaps/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}.osz");
+ public static Stream GetTestBeatmapStream(bool virtualTrack = false) => OpenResource($"Archives/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}.osz");
public static string GetTestBeatmapForImport(bool virtualTrack = false)
{
diff --git a/osu.Game.Tests/Resources/test-sample.mp3 b/osu.Game.Tests/Resources/test-sample.mp3
new file mode 100644
index 0000000000..f7c344f39a
Binary files /dev/null and b/osu.Game.Tests/Resources/test-sample.mp3 differ
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs
index 9f16e1d781..3562689482 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs
@@ -4,6 +4,8 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Beatmaps;
+using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose;
namespace osu.Game.Tests.Visual.Editor
@@ -11,10 +13,21 @@ namespace osu.Game.Tests.Visual.Editor
[TestFixture]
public class TestSceneComposeScreen : EditorClockTestScene
{
+ [Cached(typeof(EditorBeatmap))]
+ private readonly EditorBeatmap editorBeatmap =
+ new EditorBeatmap(new OsuBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo
+ }
+ });
+
[BackgroundDependencyLoader]
private void load()
{
- Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
+ Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
+
Child = new ComposeScreen();
}
}
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs
index 39b4bf7218..847d168e51 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs
@@ -8,7 +8,6 @@ using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Beatmaps;
-using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
@@ -21,15 +20,15 @@ namespace osu.Game.Tests.Visual.Editor
private const double beat_length = 100;
private static readonly Vector2 grid_position = new Vector2(512, 384);
- [Cached(typeof(IEditorBeatmap))]
- private readonly EditorBeatmap editorBeatmap;
+ [Cached(typeof(EditorBeatmap))]
+ private readonly EditorBeatmap editorBeatmap;
[Cached(typeof(IDistanceSnapProvider))]
private readonly SnapProvider snapProvider = new SnapProvider();
public TestSceneDistanceSnapGrid()
{
- editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ editorBeatmap = new EditorBeatmap(new OsuBeatmap());
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
}
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs
index ed6bc5fe0c..29575cb42e 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Editor
{
Beatmap.Value = new WaveformTestBeatmap(audio);
- var editorBeatmap = new EditorBeatmap((Beatmap)Beatmap.Value.Beatmap);
+ var editorBeatmap = new EditorBeatmap((Beatmap)Beatmap.Value.Beatmap);
Children = new Drawable[]
{
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs
index b7c7028b52..c001c83877 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs
@@ -16,6 +16,7 @@ using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
@@ -59,9 +60,12 @@ namespace osu.Game.Tests.Visual.Editor
},
});
+ var editorBeatmap = new EditorBeatmap(Beatmap.Value.GetPlayableBeatmap(new OsuRuleset().RulesetInfo));
+
var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
Dependencies.CacheAs(clock);
Dependencies.CacheAs(clock);
+ Dependencies.CacheAs(editorBeatmap);
Child = new OsuHitObjectComposer(new OsuRuleset());
}
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs
index 121853d8d0..adfed9a299 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs
@@ -5,7 +5,8 @@ using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
-using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Beatmaps;
+using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Timing;
namespace osu.Game.Tests.Visual.Editor
@@ -25,10 +26,13 @@ namespace osu.Game.Tests.Visual.Editor
typeof(RowAttribute)
};
+ [Cached(typeof(EditorBeatmap))]
+ private readonly EditorBeatmap editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+
[BackgroundDependencyLoader]
private void load()
{
- Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
+ Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
Child = new TimingScreen();
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
index ae20bbc86d..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,12 +268,12 @@ 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)
{
}
- protected override IEnumerable ValidConversionTypes => new[] { typeof(HitObject) };
+ public override bool CanConvert() => true;
protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap)
{
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/Gameplay/TestSceneScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
index aa80819694..8629522dc2 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
@@ -3,12 +3,14 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Threading;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
@@ -28,12 +30,16 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached(typeof(IReadOnlyList))]
private IReadOnlyList mods { get; set; } = Array.Empty();
+ private const int spawn_interval = 5000;
+
private readonly ScrollingTestContainer[] scrollContainers = new ScrollingTestContainer[4];
private readonly TestPlayfield[] playfields = new TestPlayfield[4];
+ private ScheduledDelegate hitObjectSpawnDelegate;
- public TestSceneScrollingHitObjects()
+ [SetUp]
+ public void Setup() => Schedule(() =>
{
- Add(new GridContainer
+ Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
@@ -43,48 +49,66 @@ namespace osu.Game.Tests.Visual.Gameplay
scrollContainers[0] = new ScrollingTestContainer(ScrollingDirection.Up)
{
RelativeSizeAxes = Axes.Both,
- Child = playfields[0] = new TestPlayfield()
+ Child = playfields[0] = new TestPlayfield(),
+ TimeRange = spawn_interval
},
- scrollContainers[1] = new ScrollingTestContainer(ScrollingDirection.Up)
+ scrollContainers[1] = new ScrollingTestContainer(ScrollingDirection.Down)
{
RelativeSizeAxes = Axes.Both,
- Child = playfields[1] = new TestPlayfield()
+ Child = playfields[1] = new TestPlayfield(),
+ TimeRange = spawn_interval
},
},
new Drawable[]
{
- scrollContainers[2] = new ScrollingTestContainer(ScrollingDirection.Up)
+ scrollContainers[2] = new ScrollingTestContainer(ScrollingDirection.Left)
{
RelativeSizeAxes = Axes.Both,
- Child = playfields[2] = new TestPlayfield()
+ Child = playfields[2] = new TestPlayfield(),
+ TimeRange = spawn_interval
},
- scrollContainers[3] = new ScrollingTestContainer(ScrollingDirection.Up)
+ scrollContainers[3] = new ScrollingTestContainer(ScrollingDirection.Right)
{
RelativeSizeAxes = Axes.Both,
- Child = playfields[3] = new TestPlayfield()
+ Child = playfields[3] = new TestPlayfield(),
+ TimeRange = spawn_interval
}
}
}
- });
+ };
+ setUpHitObjects();
+ });
+
+ private void setUpHitObjects()
+ {
+ scrollContainers.ForEach(c => c.ControlPoints.Add(new MultiplierControlPoint(0)));
+
+ for (int i = 0; i <= spawn_interval; i += 1000)
+ addHitObject(Time.Current + i);
+
+ hitObjectSpawnDelegate?.Cancel();
+ hitObjectSpawnDelegate = Scheduler.AddDelayed(() => addHitObject(Time.Current + spawn_interval), 1000, true);
+ }
+
+ [Test]
+ public void TestScrollAlgorithms()
+ {
AddStep("Constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
AddStep("Overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
AddStep("Sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
- AddSliderStep("Time range", 100, 10000, 5000, v => scrollContainers.ForEach(c => c.TimeRange = v));
- AddStep("Add control point", () => addControlPoint(Time.Current + 5000));
+ AddSliderStep("Time range", 100, 10000, spawn_interval, v => scrollContainers.Where(c => c != null).ForEach(c => c.TimeRange = v));
+ AddStep("Add control point", () => addControlPoint(Time.Current + spawn_interval));
}
- protected override void LoadComplete()
+ [Test]
+ public void TestScrollLifetime()
{
- base.LoadComplete();
-
- scrollContainers.ForEach(c => c.ControlPoints.Add(new MultiplierControlPoint(0)));
-
- for (int i = 0; i <= 5000; i += 1000)
- addHitObject(Time.Current + i);
-
- Scheduler.AddDelayed(() => addHitObject(Time.Current + 5000), 1000, true);
+ AddStep("Set constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
+ // scroll container time range must be less than the rate of spawning hitobjects
+ // otherwise the hitobjects will spawn already partly visible on screen and look wrong
+ AddStep("Set time range", () => scrollContainers.ForEach(c => c.TimeRange = spawn_interval / 2.0));
}
private void addHitObject(double time)
@@ -207,7 +231,9 @@ namespace osu.Game.Tests.Visual.Gameplay
public TestDrawableHitObject(double time)
: base(new HitObject { StartTime = time })
{
- Origin = Anchor.Centre;
+ Origin = Anchor.Custom;
+ OriginPosition = new Vector2(75 / 4.0f);
+
AutoSizeAxes = Axes.Both;
AddInternal(new Box { Size = new Vector2(75) });
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
index 63b8acb234..63b46c991f 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Online
typeof(ProfileHeader),
typeof(RankGraph),
typeof(LineGraph),
- typeof(OverlayHeaderTabControl),
+ typeof(TabControlOverlayHeader.OverlayHeaderTabControl),
typeof(CentreHeaderContainer),
typeof(BottomHeaderContainer),
typeof(DetailHeaderContainer),
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.Tests/Visual/UserInterface/TestSceneBreadcrumbs.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControl.cs
similarity index 92%
rename from osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbs.cs
rename to osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControl.cs
index 554696765e..19eebc89b6 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbs.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControl.cs
@@ -10,11 +10,11 @@ using osu.Game.Graphics.UserInterface;
namespace osu.Game.Tests.Visual.UserInterface
{
[TestFixture]
- public class TestSceneBreadcrumbs : OsuTestScene
+ public class TestSceneBreadcrumbControl : OsuTestScene
{
private readonly BreadcrumbControl breadcrumbs;
- public TestSceneBreadcrumbs()
+ public TestSceneBreadcrumbControl()
{
Add(breadcrumbs = new BreadcrumbControl
{
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs
index d8a4514df1..35e5f9719c 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs
@@ -67,9 +67,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddRepeatStep(@"add many simple", sendManyNotifications, 3);
- AddWaitStep("wait some", 5);
-
- checkProgressingCount(0);
+ waitForCompletion();
AddStep(@"progress #3", sendUploadProgress);
@@ -77,9 +75,7 @@ namespace osu.Game.Tests.Visual.UserInterface
checkDisplayedCount(33);
- AddWaitStep("wait some", 10);
-
- checkProgressingCount(0);
+ waitForCompletion();
}
[Test]
@@ -109,9 +105,9 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep(@"background progress #1", sendBackgroundUploadProgress);
- AddWaitStep("wait some", 5);
+ checkProgressingCount(1);
- checkProgressingCount(0);
+ waitForCompletion();
checkDisplayedCount(2);
@@ -190,6 +186,8 @@ namespace osu.Game.Tests.Visual.UserInterface
private void checkProgressingCount(int expected) => AddAssert($"progressing count is {expected}", () => progressingNotifications.Count == expected);
+ private void waitForCompletion() => AddUntilStep("wait for notification progress completion", () => progressingNotifications.Count == 0);
+
private void sendBarrage()
{
switch (RNG.Next(0, 4))
diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj
index 6c799e5e90..3f8a548fd5 100644
--- a/osu.Game.Tests/osu.Game.Tests.csproj
+++ b/osu.Game.Tests/osu.Game.Tests.csproj
@@ -5,7 +5,7 @@
-
+
diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
index 7ecfd6ef70..75b88be1ab 100644
--- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
+++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
@@ -7,7 +7,7 @@
-
+
WinExe
diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs
index 4d7abfe272..bd91ad9704 100644
--- a/osu.Game.Tournament/TournamentGameBase.cs
+++ b/osu.Game.Tournament/TournamentGameBase.cs
@@ -52,7 +52,7 @@ namespace osu.Game.Tournament
[BackgroundDependencyLoader]
private void load(Storage storage, FrameworkConfigManager frameworkConfig)
{
- Resources.AddStore(new DllResourceStore(@"osu.Game.Tournament.dll"));
+ Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly));
AddFont(Resources, @"Resources/Fonts/Aquatico-Regular");
AddFont(Resources, @"Resources/Fonts/Aquatico-Light");
diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs
index 23a74d3fa6..f6b0107bd2 100644
--- a/osu.Game/Audio/HitSampleInfo.cs
+++ b/osu.Game/Audio/HitSampleInfo.cs
@@ -17,11 +17,6 @@ namespace osu.Game.Audio
public const string HIT_NORMAL = @"hitnormal";
public const string HIT_CLAP = @"hitclap";
- ///
- /// An optional ruleset namespace.
- ///
- public string Namespace;
-
///
/// The bank to load the sample from.
///
@@ -49,15 +44,6 @@ namespace osu.Game.Audio
{
get
{
- if (!string.IsNullOrEmpty(Namespace))
- {
- if (!string.IsNullOrEmpty(Suffix))
- yield return $"{Namespace}/{Bank}-{Name}{Suffix}";
-
- yield return $"{Namespace}/{Bank}-{Name}";
- }
-
- // check non-namespace as a fallback even when we have a namespace
if (!string.IsNullOrEmpty(Suffix))
yield return $"{Bank}-{Name}{Suffix}";
diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs
index 7922843626..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;
}
@@ -33,7 +34,7 @@ namespace osu.Game.Beatmaps
///
/// Whether can be converted by this .
///
- public bool CanConvert => !Beatmap.HitObjects.Any() || ValidConversionTypes.All(t => Beatmap.HitObjects.Any(t.IsInstanceOfType));
+ public abstract bool CanConvert();
///
/// Converts .
@@ -92,11 +93,6 @@ namespace osu.Game.Beatmaps
return result;
}
- ///
- /// The types of HitObjects that can be converted to be used for this Beatmap.
- ///
- protected abstract IEnumerable ValidConversionTypes { get; }
-
///
/// Creates the that will be returned by this .
///
diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
index 46efe38d37..bfcc38e4a9 100644
--- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
@@ -76,7 +76,7 @@ namespace osu.Game.Beatmaps
public IBeatmap Beatmap { get; set; }
- public bool CanConvert => true;
+ public bool CanConvert() => true;
public IBeatmap Convert()
{
diff --git a/osu.Game/Beatmaps/IBeatmapConverter.cs b/osu.Game/Beatmaps/IBeatmapConverter.cs
index f2213b85f1..173d5494ba 100644
--- a/osu.Game/Beatmaps/IBeatmapConverter.cs
+++ b/osu.Game/Beatmaps/IBeatmapConverter.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Beatmaps
///
/// Whether can be converted by this .
///
- bool CanConvert { get; }
+ bool CanConvert();
///
/// Converts .
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 1255665cf0..6aba5257f5 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -108,7 +108,7 @@ namespace osu.Game.Beatmaps
IBeatmapConverter converter = CreateBeatmapConverter(Beatmap, rulesetInstance);
// Check if the beatmap can be converted
- if (!converter.CanConvert)
+ if (Beatmap.HitObjects.Count > 0 && !converter.CanConvert())
throw new BeatmapInvalidForRulesetException($"{nameof(Beatmaps.Beatmap)} can not be converted for the ruleset (ruleset: {ruleset.InstantiationInfo}, converter: {converter}).");
// Apply conversion mods
diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs
index fd455d7cd5..45fe034a70 100644
--- a/osu.Game/Database/ArchiveModelManager.cs
+++ b/osu.Game/Database/ArchiveModelManager.cs
@@ -259,6 +259,9 @@ namespace osu.Game.Database
///
/// Create a SHA-2 hash from the provided archive based on file content of all files matching .
///
+ ///
+ /// In the case of no matching files, a hash will be generated from the passed archive's .
+ ///
private string computeHash(ArchiveReader reader)
{
// for now, concatenate all .osu files in the set to create a unique hash.
@@ -270,7 +273,7 @@ namespace osu.Game.Database
s.CopyTo(hashable);
}
- return hashable.Length > 0 ? hashable.ComputeSHA2Hash() : null;
+ return hashable.Length > 0 ? hashable.ComputeSHA2Hash() : reader.Name.ComputeSHA2Hash();
}
///
diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs
index 6d88808150..af492bacc9 100644
--- a/osu.Game/Graphics/Backgrounds/Triangles.cs
+++ b/osu.Game/Graphics/Backgrounds/Triangles.cs
@@ -29,8 +29,33 @@ namespace osu.Game.Graphics.Backgrounds
///
private const float edge_smoothness = 1;
- public Color4 ColourLight = Color4.White;
- public Color4 ColourDark = Color4.Black;
+ private Color4 colourLight = Color4.White;
+
+ public Color4 ColourLight
+ {
+ get => colourLight;
+ set
+ {
+ if (colourLight == value) return;
+
+ colourLight = value;
+ updateColours();
+ }
+ }
+
+ private Color4 colourDark = Color4.Black;
+
+ public Color4 ColourDark
+ {
+ get => colourDark;
+ set
+ {
+ if (colourDark == value) return;
+
+ colourDark = value;
+ updateColours();
+ }
+ }
///
/// Whether we want to expire triangles as they exit our draw area completely.
@@ -151,7 +176,8 @@ namespace osu.Game.Graphics.Backgrounds
TriangleParticle particle = CreateTriangle();
particle.Position = new Vector2(RNG.NextSingle(), randomY ? RNG.NextSingle() : 1);
- particle.Colour = CreateTriangleShade();
+ particle.ColourShade = RNG.NextSingle();
+ particle.Colour = CreateTriangleShade(particle.ColourShade);
return particle;
}
@@ -177,7 +203,17 @@ namespace osu.Game.Graphics.Backgrounds
/// Creates a shade of colour for the triangles.
///
/// The colour.
- protected virtual Color4 CreateTriangleShade() => Interpolation.ValueAt(RNG.NextSingle(), ColourDark, ColourLight, 0, 1);
+ protected virtual Color4 CreateTriangleShade(float shade) => Interpolation.ValueAt(shade, colourDark, colourLight, 0, 1);
+
+ private void updateColours()
+ {
+ for (int i = 0; i < parts.Count; i++)
+ {
+ TriangleParticle newParticle = parts[i];
+ newParticle.Colour = CreateTriangleShade(newParticle.ColourShade);
+ parts[i] = newParticle;
+ }
+ }
protected override DrawNode CreateDrawNode() => new TrianglesDrawNode(this);
@@ -264,6 +300,12 @@ namespace osu.Game.Graphics.Backgrounds
///
public Vector2 Position;
+ ///
+ /// The colour shade of the triangle.
+ /// This is needed for colour recalculation of visible triangles when or is changed.
+ ///
+ public float ColourShade;
+
///
/// The colour of the triangle.
///
diff --git a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs
index d1e55fee24..e2438cc4cd 100644
--- a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs
+++ b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs
@@ -15,14 +15,13 @@ namespace osu.Game.Graphics.UserInterface
public class BreadcrumbControl : OsuTabControl
{
private const float padding = 10;
- private const float item_chevron_size = 10;
protected override TabItem CreateTabItem(T value) => new BreadcrumbTabItem(value)
{
AccentColour = AccentColour,
};
- protected override float StripWidth() => base.StripWidth() - (padding + item_chevron_size);
+ protected override float StripWidth => base.StripWidth - TabContainer.FirstOrDefault()?.Padding.Right ?? 0;
public BreadcrumbControl()
{
@@ -41,8 +40,10 @@ namespace osu.Game.Graphics.UserInterface
};
}
- private class BreadcrumbTabItem : OsuTabItem, IStateful
+ protected class BreadcrumbTabItem : OsuTabItem, IStateful
{
+ protected virtual float ChevronSize => 10;
+
public event Action StateChanged;
public readonly SpriteIcon Chevron;
@@ -52,7 +53,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;
@@ -91,12 +91,12 @@ namespace osu.Game.Graphics.UserInterface
{
Text.Font = Text.Font.With(size: 18);
Text.Margin = new MarginPadding { Vertical = 8 };
- Padding = new MarginPadding { Right = padding + item_chevron_size };
+ Padding = new MarginPadding { Right = padding + ChevronSize };
Add(Chevron = new SpriteIcon
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreLeft,
- Size = new Vector2(item_chevron_size),
+ Size = new Vector2(ChevronSize),
Icon = FontAwesome.Solid.ChevronRight,
Margin = new MarginPadding { Left = padding },
Alpha = 0f,
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/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs
index 064cba6adf..ed8904db7e 100644
--- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs
+++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs
@@ -28,8 +28,7 @@ namespace osu.Game.Graphics.UserInterface
protected override TabItem CreateTabItem(T value) => new OsuTabItem(value);
- protected virtual float StripWidth() => TabContainer.Children.Sum(c => c.IsPresent ? c.DrawWidth + TabContainer.Spacing.X : 0) - TabContainer.Spacing.X;
- protected virtual float StripHeight() => 1;
+ protected virtual float StripWidth => TabContainer.Children.Sum(c => c.IsPresent ? c.DrawWidth + TabContainer.Spacing.X : 0) - TabContainer.Spacing.X;
///
/// Whether entries should be automatically populated if is an type.
@@ -46,7 +45,7 @@ namespace osu.Game.Graphics.UserInterface
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
- Height = StripHeight(),
+ Height = 1,
Colour = Color4.White.Opacity(0),
});
@@ -99,7 +98,7 @@ namespace osu.Game.Graphics.UserInterface
// dont bother calculating if the strip is invisible
if (strip.Colour.MaxAlpha > 0)
- strip.Width = Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, 1000), strip.Width, StripWidth(), 0, 500, Easing.OutQuint);
+ strip.Width = Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, 1000), strip.Width, StripWidth, 0, 500, Easing.OutQuint);
}
public class OsuTabItem : TabItem, IHasAccentColour
diff --git a/osu.Game/Graphics/UserInterface/ScreenTitle.cs b/osu.Game/Graphics/UserInterface/ScreenTitle.cs
index 10fc312d8b..ecd0508258 100644
--- a/osu.Game/Graphics/UserInterface/ScreenTitle.cs
+++ b/osu.Game/Graphics/UserInterface/ScreenTitle.cs
@@ -4,6 +4,7 @@
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Sprites;
using osuTK;
@@ -13,15 +14,15 @@ namespace osu.Game.Graphics.UserInterface
{
public abstract class ScreenTitle : CompositeDrawable, IHasAccentColour
{
- public const float ICON_WIDTH = ICON_SIZE + icon_spacing;
+ public const float ICON_WIDTH = ICON_SIZE + spacing;
public const float ICON_SIZE = 25;
+ private const float spacing = 6;
+ private const int text_offset = 2;
private SpriteIcon iconSprite;
private readonly OsuSpriteText titleText, pageText;
- private const float icon_spacing = 10;
-
protected IconUsage Icon
{
set
@@ -63,26 +64,35 @@ namespace osu.Game.Graphics.UserInterface
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
- Spacing = new Vector2(icon_spacing, 0),
+ Spacing = new Vector2(spacing, 0),
+ Direction = FillDirection.Horizontal,
Children = new[]
{
- CreateIcon(),
- new FillFlowContainer
+ CreateIcon().With(t =>
{
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(6, 0),
- Children = new[]
- {
- titleText = new OsuSpriteText
- {
- Font = OsuFont.GetFont(size: 30, weight: FontWeight.Light),
- },
- pageText = new OsuSpriteText
- {
- Font = OsuFont.GetFont(size: 30, weight: FontWeight.Light),
- }
- }
+ t.Anchor = Anchor.Centre;
+ t.Origin = Anchor.Centre;
+ }),
+ titleText = new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold),
+ Margin = new MarginPadding { Bottom = text_offset }
+ },
+ new Circle
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(4),
+ Colour = Color4.Gray,
+ },
+ pageText = new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Font = OsuFont.GetFont(size: 20),
+ Margin = new MarginPadding { Bottom = text_offset }
}
}
},
diff --git a/osu.Game/Graphics/UserInterface/ScreenTitleTextureIcon.cs b/osu.Game/Graphics/UserInterface/ScreenTitleTextureIcon.cs
index f590e7e357..c2a13970de 100644
--- a/osu.Game/Graphics/UserInterface/ScreenTitleTextureIcon.cs
+++ b/osu.Game/Graphics/UserInterface/ScreenTitleTextureIcon.cs
@@ -4,7 +4,6 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osuTK;
@@ -16,8 +15,6 @@ namespace osu.Game.Graphics.UserInterface
///
public class ScreenTitleTextureIcon : CompositeDrawable
{
- private const float circle_allowance = 0.8f;
-
private readonly string textureName;
public ScreenTitleTextureIcon(string textureName)
@@ -26,38 +23,17 @@ namespace osu.Game.Graphics.UserInterface
}
[BackgroundDependencyLoader]
- private void load(TextureStore textures, OsuColour colours)
+ private void load(TextureStore textures)
{
- Size = new Vector2(ScreenTitle.ICON_SIZE / circle_allowance);
+ Size = new Vector2(ScreenTitle.ICON_SIZE);
- InternalChildren = new Drawable[]
+ InternalChild = new Sprite
{
- new CircularContainer
- {
- Masking = true,
- BorderColour = colours.Violet,
- BorderThickness = 3,
- MaskingSmoothness = 1,
- RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
- {
- new Sprite
- {
- RelativeSizeAxes = Axes.Both,
- Texture = textures.Get(textureName),
- Size = new Vector2(circle_allowance),
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- },
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = colours.Violet,
- Alpha = 0,
- AlwaysPresent = true,
- },
- }
- },
+ RelativeSizeAxes = Axes.Both,
+ Texture = textures.Get(textureName),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ FillMode = FillMode.Fit
};
}
}
diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs
index ff3618b263..fe8756a4d2 100644
--- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs
+++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs
@@ -34,11 +34,21 @@ namespace osu.Game.Graphics.UserInterface
public override bool OnPressed(PlatformAction action)
{
- // Shift+delete is handled via PlatformAction on macOS. this is not so useful in the context of a SearchTextBox
- // as we do not allow arrow key navigation in the first place (ie. the caret should always be at the end of text)
- // Avoid handling it here to allow other components to potentially consume the shortcut.
- if (action.ActionType == PlatformActionType.CharNext && action.ActionMethod == PlatformActionMethod.Delete)
- return false;
+ switch (action.ActionType)
+ {
+ case PlatformActionType.LineEnd:
+ case PlatformActionType.LineStart:
+ return false;
+
+ // Shift+delete is handled via PlatformAction on macOS. this is not so useful in the context of a SearchTextBox
+ // as we do not allow arrow key navigation in the first place (ie. the caret should always be at the end of text)
+ // Avoid handling it here to allow other components to potentially consume the shortcut.
+ case PlatformActionType.CharNext:
+ if (action.ActionMethod == PlatformActionMethod.Delete)
+ return false;
+
+ break;
+ }
return base.OnPressed(action);
}
diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs
index bf758e21d9..e37567c72c 100644
--- a/osu.Game/Input/Bindings/GlobalActionContainer.cs
+++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs
@@ -32,6 +32,7 @@ namespace osu.Game.Input.Bindings
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings),
new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar),
new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings),
+ new KeyBinding(new[] { InputKey.Control, InputKey.D }, GlobalAction.ToggleDirect),
new KeyBinding(InputKey.Escape, GlobalAction.Back),
new KeyBinding(InputKey.ExtraMouseButton1, GlobalAction.Back),
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index c7c746bed3..84aba4af52 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -39,6 +39,7 @@ using osu.Game.Online.Chat;
using osu.Game.Skinning;
using osuTK.Graphics;
using osu.Game.Overlays.Volume;
+using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
using osu.Game.Screens.Select;
using osu.Game.Utils;
@@ -204,6 +205,7 @@ namespace osu.Game
Audio.AddAdjustment(AdjustableProperty.Volume, inactiveVolumeFade);
+ SelectedMods.BindValueChanged(modsChanged);
Beatmap.BindValueChanged(beatmapChanged, true);
}
@@ -403,9 +405,29 @@ namespace osu.Game
oldBeatmap.Track.Completed -= currentTrackCompleted;
}
+ updateModDefaults();
+
nextBeatmap?.LoadBeatmapAsync();
}
+ private void modsChanged(ValueChangedEvent> mods)
+ {
+ updateModDefaults();
+ }
+
+ private void updateModDefaults()
+ {
+ BeatmapDifficulty baseDifficulty = Beatmap.Value.BeatmapInfo.BaseDifficulty;
+
+ if (baseDifficulty != null && SelectedMods.Value.Any(m => m is IApplicableToDifficulty))
+ {
+ var adjustedDifficulty = baseDifficulty.Clone();
+
+ foreach (var mod in SelectedMods.Value.OfType())
+ mod.ReadFromDifficulty(adjustedDifficulty);
+ }
+ }
+
private void currentTrackCompleted() => Schedule(() =>
{
if (!Beatmap.Value.Track.Looping && !Beatmap.Disabled)
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 22b8d9d012..0819642d2d 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -29,6 +29,7 @@ using osu.Game.Database;
using osu.Game.Input;
using osu.Game.Input.Bindings;
using osu.Game.IO;
+using osu.Game.Resources;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
@@ -125,7 +126,7 @@ namespace osu.Game
[BackgroundDependencyLoader]
private void load()
{
- Resources.AddStore(new DllResourceStore(@"osu.Game.Resources.dll"));
+ Resources.AddStore(new DllResourceStore(OsuResources.ResourceAssembly));
dependencies.Cache(contextFactory = new DatabaseContextFactory(Storage));
diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs
index 6de14c51ee..e530ff5489 100644
--- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs
+++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs
@@ -196,7 +196,7 @@ namespace osu.Game.Overlays.AccountCreation
return;
}
- api.Login(emailTextBox.Text, passwordTextBox.Text);
+ api.Login(usernameTextBox.Text, passwordTextBox.Text);
});
});
}
diff --git a/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs b/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs
new file mode 100644
index 0000000000..8a82b1f0c0
--- /dev/null
+++ b/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs
@@ -0,0 +1,39 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.UserInterface;
+using osu.Game.Graphics.UserInterface;
+
+namespace osu.Game.Overlays
+{
+ public abstract class BreadcrumbControlOverlayHeader : OverlayHeader
+ {
+ protected OverlayHeaderBreadcrumbControl BreadcrumbControl;
+
+ protected override TabControl CreateTabControl() => BreadcrumbControl = new OverlayHeaderBreadcrumbControl();
+
+ public class OverlayHeaderBreadcrumbControl : BreadcrumbControl
+ {
+ public OverlayHeaderBreadcrumbControl()
+ {
+ RelativeSizeAxes = Axes.X;
+ }
+
+ protected override TabItem CreateTabItem(string value) => new ControlTabItem(value);
+
+ private class ControlTabItem : BreadcrumbTabItem
+ {
+ protected override float ChevronSize => 8;
+
+ public ControlTabItem(string value)
+ : base(value)
+ {
+ Text.Font = Text.Font.With(size: 14);
+ Chevron.Y = 3;
+ Bar.Height = 0;
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Changelog/ChangelogHeader.cs b/osu.Game/Overlays/Changelog/ChangelogHeader.cs
index b2e9be24b3..7e47a3e29f 100644
--- a/osu.Game/Overlays/Changelog/ChangelogHeader.cs
+++ b/osu.Game/Overlays/Changelog/ChangelogHeader.cs
@@ -15,7 +15,7 @@ using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Overlays.Changelog
{
- public class ChangelogHeader : OverlayHeader
+ public class ChangelogHeader : BreadcrumbControlOverlayHeader
{
public readonly Bindable Current = new Bindable();
@@ -23,12 +23,12 @@ namespace osu.Game.Overlays.Changelog
public UpdateStreamBadgeArea Streams;
- private const string listing_string = "Listing";
+ private const string listing_string = "listing";
public ChangelogHeader()
{
- TabControl.AddItem(listing_string);
- TabControl.Current.ValueChanged += e =>
+ BreadcrumbControl.AddItem(listing_string);
+ BreadcrumbControl.Current.ValueChanged += e =>
{
if (e.NewValue == listing_string)
ListingSelected?.Invoke();
@@ -46,7 +46,9 @@ namespace osu.Game.Overlays.Changelog
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
- TabControl.AccentColour = colours.Violet;
+ BreadcrumbControl.AccentColour = colours.Violet;
+ TitleBackgroundColour = colours.GreyVioletDarker;
+ ControlBackgroundColour = colours.GreyVioletDark;
}
private ChangelogHeaderTitle title;
@@ -54,12 +56,12 @@ namespace osu.Game.Overlays.Changelog
private void showBuild(ValueChangedEvent e)
{
if (e.OldValue != null)
- TabControl.RemoveItem(e.OldValue.ToString());
+ BreadcrumbControl.RemoveItem(e.OldValue.ToString());
if (e.NewValue != null)
{
- TabControl.AddItem(e.NewValue.ToString());
- TabControl.Current.Value = e.NewValue.ToString();
+ BreadcrumbControl.AddItem(e.NewValue.ToString());
+ BreadcrumbControl.Current.Value = e.NewValue.ToString();
Streams.Current.Value = Streams.Items.FirstOrDefault(s => s.Name == e.NewValue.UpdateStream.Name);
@@ -67,7 +69,7 @@ namespace osu.Game.Overlays.Changelog
}
else
{
- TabControl.Current.Value = listing_string;
+ BreadcrumbControl.Current.Value = listing_string;
Streams.Current.Value = null;
title.Version = null;
}
@@ -111,7 +113,7 @@ namespace osu.Game.Overlays.Changelog
public ChangelogHeaderTitle()
{
- Title = "Changelog";
+ Title = "changelog";
Version = null;
}
diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs
index fbc9dfcbd9..15b0079277 100644
--- a/osu.Game/Overlays/ChangelogOverlay.cs
+++ b/osu.Game/Overlays/ChangelogOverlay.cs
@@ -158,7 +158,8 @@ namespace osu.Game.Overlays
private Task initialFetchTask;
- private void performAfterFetch(Action action) => fetchListing()?.ContinueWith(_ => Schedule(action));
+ private void performAfterFetch(Action action) => fetchListing()?.ContinueWith(_ =>
+ Schedule(action), TaskContinuationOptions.OnlyOnRanToCompletion);
private Task fetchListing()
{
@@ -185,10 +186,10 @@ namespace osu.Game.Overlays
tcs.SetResult(true);
});
- req.Failure += _ =>
+ req.Failure += e =>
{
initialFetchTask = null;
- tcs.SetResult(false);
+ tcs.SetException(e);
};
await API.PerformAsync(req);
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/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs
index 7ae6efda6a..bdae9da226 100644
--- a/osu.Game/Overlays/Comments/DrawableComment.cs
+++ b/osu.Game/Overlays/Comments/DrawableComment.cs
@@ -16,6 +16,7 @@ using osu.Framework.Graphics.Shapes;
using System.Linq;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Chat;
+using osu.Framework.Allocation;
namespace osu.Game.Overlays.Comments
{
@@ -28,10 +29,16 @@ namespace osu.Game.Overlays.Comments
private readonly BindableBool childrenExpanded = new BindableBool(true);
- private readonly FillFlowContainer childCommentsVisibilityContainer;
+ private FillFlowContainer childCommentsVisibilityContainer;
private readonly Comment comment;
public DrawableComment(Comment comment)
+ {
+ this.comment = comment;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
{
LinkFlowContainer username;
FillFlowContainer childCommentsContainer;
@@ -41,8 +48,6 @@ namespace osu.Game.Overlays.Comments
GridContainer content;
VotePill votePill;
- this.comment = comment;
-
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChild = new FillFlowContainer
diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs
index c1c5113c5e..4ad8e95512 100644
--- a/osu.Game/Overlays/Direct/DirectPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectPanel.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 System.Diagnostics;
using System.Linq;
@@ -9,21 +10,25 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.Direct
{
- public abstract class DirectPanel : Container
+ public abstract class DirectPanel : OsuClickableContainer, IHasContextMenu
{
public readonly BeatmapSetInfo SetInfo;
@@ -32,8 +37,6 @@ namespace osu.Game.Overlays.Direct
private Container content;
- private BeatmapSetOverlay beatmapSetOverlay;
-
public PreviewTrack Preview => PlayButton.Preview;
public Bindable PreviewPlaying => PlayButton?.Playing;
@@ -44,6 +47,8 @@ namespace osu.Game.Overlays.Direct
protected override Container Content => content;
+ protected Action ViewBeatmap;
+
protected DirectPanel(BeatmapSetInfo setInfo)
{
Debug.Assert(setInfo.OnlineBeatmapSetID != null);
@@ -70,8 +75,6 @@ namespace osu.Game.Overlays.Direct
[BackgroundDependencyLoader(permitNulls: true)]
private void load(BeatmapManager beatmaps, OsuColour colours, BeatmapSetOverlay beatmapSetOverlay)
{
- this.beatmapSetOverlay = beatmapSetOverlay;
-
AddInternal(content = new Container
{
RelativeSizeAxes = Axes.Both,
@@ -88,6 +91,12 @@ namespace osu.Game.Overlays.Direct
},
}
});
+
+ Action = ViewBeatmap = () =>
+ {
+ Debug.Assert(SetInfo.OnlineBeatmapSetID != null);
+ beatmapSetOverlay?.FetchAndShowBeatmapSet(SetInfo.OnlineBeatmapSetID.Value);
+ };
}
protected override void Update()
@@ -120,13 +129,6 @@ namespace osu.Game.Overlays.Direct
base.OnHoverLost(e);
}
- protected override bool OnClick(ClickEvent e)
- {
- Debug.Assert(SetInfo.OnlineBeatmapSetID != null);
- beatmapSetOverlay?.FetchAndShowBeatmapSet(SetInfo.OnlineBeatmapSetID.Value);
- return true;
- }
-
protected override void LoadComplete()
{
base.LoadComplete();
@@ -203,5 +205,10 @@ namespace osu.Game.Overlays.Direct
Value = value;
}
}
+
+ public MenuItem[] ContextMenuItems => new MenuItem[]
+ {
+ new OsuMenuItem("View Beatmap", MenuItemType.Highlighted, ViewBeatmap),
+ };
}
}
diff --git a/osu.Game/Overlays/News/NewsHeader.cs b/osu.Game/Overlays/News/NewsHeader.cs
index 27620ab523..fc88c86df2 100644
--- a/osu.Game/Overlays/News/NewsHeader.cs
+++ b/osu.Game/Overlays/News/NewsHeader.cs
@@ -4,7 +4,6 @@
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Graphics;
@@ -13,9 +12,9 @@ using System;
namespace osu.Game.Overlays.News
{
- public class NewsHeader : OverlayHeader
+ public class NewsHeader : BreadcrumbControlOverlayHeader
{
- private const string front_page_string = "Front Page";
+ private const string front_page_string = "frontpage";
private NewsHeaderTitle title;
@@ -25,46 +24,46 @@ namespace osu.Game.Overlays.News
public NewsHeader()
{
- TabControl.AddItem(front_page_string);
+ BreadcrumbControl.AddItem(front_page_string);
- TabControl.Current.ValueChanged += e =>
+ BreadcrumbControl.Current.ValueChanged += e =>
{
if (e.NewValue == front_page_string)
ShowFrontPage?.Invoke();
};
- Current.ValueChanged += showArticle;
+ Current.ValueChanged += showPost;
}
[BackgroundDependencyLoader]
- private void load(OsuColour colour)
+ private void load(OsuColour colours)
{
- TabControl.AccentColour = colour.Violet;
+ BreadcrumbControl.AccentColour = colours.Violet;
+ TitleBackgroundColour = colours.GreyVioletDarker;
+ ControlBackgroundColour = colours.GreyVioletDark;
}
- private void showArticle(ValueChangedEvent e)
+ private void showPost(ValueChangedEvent e)
{
if (e.OldValue != null)
- TabControl.RemoveItem(e.OldValue);
+ BreadcrumbControl.RemoveItem(e.OldValue);
if (e.NewValue != null)
{
- TabControl.AddItem(e.NewValue);
- TabControl.Current.Value = e.NewValue;
+ BreadcrumbControl.AddItem(e.NewValue);
+ BreadcrumbControl.Current.Value = e.NewValue;
- title.IsReadingArticle = true;
+ title.IsReadingPost = true;
}
else
{
- TabControl.Current.Value = front_page_string;
- title.IsReadingArticle = false;
+ BreadcrumbControl.Current.Value = front_page_string;
+ title.IsReadingPost = false;
}
}
protected override Drawable CreateBackground() => new NewsHeaderBackground();
- protected override Drawable CreateContent() => new Container();
-
protected override ScreenTitle CreateTitle() => title = new NewsHeaderTitle();
private class NewsHeaderBackground : Sprite
@@ -84,17 +83,17 @@ namespace osu.Game.Overlays.News
private class NewsHeaderTitle : ScreenTitle
{
- private const string article_string = "Article";
+ private const string post_string = "post";
- public bool IsReadingArticle
+ public bool IsReadingPost
{
- set => Section = value ? article_string : front_page_string;
+ set => Section = value ? post_string : front_page_string;
}
public NewsHeaderTitle()
{
- Title = "News";
- IsReadingArticle = false;
+ Title = "news";
+ IsReadingPost = false;
}
protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/news");
diff --git a/osu.Game/Overlays/OverlayHeader.cs b/osu.Game/Overlays/OverlayHeader.cs
index 2e032db2ba..53da2da634 100644
--- a/osu.Game/Overlays/OverlayHeader.cs
+++ b/osu.Game/Overlays/OverlayHeader.cs
@@ -1,70 +1,104 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using JetBrains.Annotations;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface;
+using osuTK.Graphics;
namespace osu.Game.Overlays
{
public abstract class OverlayHeader : Container
{
- protected readonly OverlayHeaderTabControl TabControl;
+ private readonly Box titleBackground;
+ private readonly Box controlBackground;
+ private readonly Container background;
- private const float cover_height = 150;
- private const float cover_info_height = 75;
+ protected Color4 TitleBackgroundColour
+ {
+ set => titleBackground.Colour = value;
+ }
+
+ protected Color4 ControlBackgroundColour
+ {
+ set => controlBackground.Colour = value;
+ }
+
+ protected float BackgroundHeight
+ {
+ set => background.Height = value;
+ }
protected OverlayHeader()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
- Children = new Drawable[]
+ Add(new FillFlowContainer
{
- new Container
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Children = new[]
{
- RelativeSizeAxes = Axes.X,
- Height = cover_height,
- Masking = true,
- Child = CreateBackground()
- },
- new Container
- {
- Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN },
- Y = cover_height,
- Height = cover_info_height,
- RelativeSizeAxes = Axes.X,
- Anchor = Anchor.TopLeft,
- Origin = Anchor.BottomLeft,
- Depth = -float.MaxValue,
- Children = new Drawable[]
+ background = new Container
{
- CreateTitle().With(t => t.X = -ScreenTitle.ICON_WIDTH),
- TabControl = new OverlayHeaderTabControl
+ RelativeSizeAxes = Axes.X,
+ Height = 80,
+ Masking = true,
+ Child = CreateBackground()
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Children = new Drawable[]
{
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- RelativeSizeAxes = Axes.X,
- Height = cover_info_height - 30,
- Margin = new MarginPadding { Left = -UserProfileOverlay.CONTENT_X_MARGIN },
- Padding = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }
+ titleBackground = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Gray,
+ },
+ CreateTitle().With(title =>
+ {
+ title.Margin = new MarginPadding
+ {
+ Vertical = 10,
+ Left = UserProfileOverlay.CONTENT_X_MARGIN
+ };
+ })
}
- }
- },
- new Container
- {
- Margin = new MarginPadding { Top = cover_height },
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Child = CreateContent()
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Depth = -float.MaxValue,
+ Children = new Drawable[]
+ {
+ controlBackground = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Gray,
+ },
+ CreateTabControl().With(control => control.Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN })
+ }
+ },
+ CreateContent()
}
- };
+ });
}
protected abstract Drawable CreateBackground();
- protected abstract Drawable CreateContent();
+ [NotNull]
+ protected virtual Drawable CreateContent() => new Container();
protected abstract ScreenTitle CreateTitle();
+
+ protected abstract TabControl CreateTabControl();
}
}
diff --git a/osu.Game/Overlays/OverlayHeaderTabControl.cs b/osu.Game/Overlays/OverlayHeaderTabControl.cs
deleted file mode 100644
index 7d0cdad6d8..0000000000
--- a/osu.Game/Overlays/OverlayHeaderTabControl.cs
+++ /dev/null
@@ -1,24 +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 osu.Framework.Graphics.UserInterface;
-
-namespace osu.Game.Overlays
-{
- public class OverlayHeaderTabControl : OverlayTabControl
- {
- protected override TabItem CreateTabItem(string value) => new OverlayHeaderTabItem(value)
- {
- AccentColour = AccentColour,
- };
-
- private class OverlayHeaderTabItem : OverlayTabItem
- {
- public OverlayHeaderTabItem(string value)
- : base(value)
- {
- Text.Text = value;
- }
- }
- }
-}
diff --git a/osu.Game/Overlays/OverlayTabControl.cs b/osu.Game/Overlays/OverlayTabControl.cs
index 4c396eabc1..812f8963c9 100644
--- a/osu.Game/Overlays/OverlayTabControl.cs
+++ b/osu.Game/Overlays/OverlayTabControl.cs
@@ -43,6 +43,11 @@ namespace osu.Game.Overlays
set => TabContainer.Padding = value;
}
+ protected float BarHeight
+ {
+ set => bar.Height = value;
+ }
+
protected OverlayTabControl()
{
TabContainer.Masking = false;
@@ -63,8 +68,7 @@ namespace osu.Game.Overlays
protected class OverlayTabItem : TabItem
{
- private readonly ExpandingBar bar;
-
+ protected readonly ExpandingBar Bar;
protected readonly OsuSpriteText Text;
private Color4 accentColour;
@@ -78,7 +82,7 @@ namespace osu.Game.Overlays
return;
accentColour = value;
- bar.Colour = value;
+ Bar.Colour = value;
updateState();
}
@@ -99,7 +103,7 @@ namespace osu.Game.Overlays
Anchor = Anchor.BottomLeft,
Font = OsuFont.GetFont(),
},
- bar = new ExpandingBar
+ Bar = new ExpandingBar
{
Anchor = Anchor.BottomCentre,
ExpandedSize = 7.5f,
@@ -149,13 +153,13 @@ namespace osu.Game.Overlays
protected virtual void HoverAction()
{
- bar.Expand();
+ Bar.Expand();
Text.FadeColour(Color4.White, 120, Easing.InQuad);
}
protected virtual void UnhoverAction()
{
- bar.Collapse();
+ Bar.Collapse();
Text.FadeColour(AccentColour, 120, Easing.InQuad);
}
}
diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs
index 76613c156d..59e64dfc26 100644
--- a/osu.Game/Overlays/Profile/ProfileHeader.cs
+++ b/osu.Game/Overlays/Profile/ProfileHeader.cs
@@ -15,7 +15,7 @@ using osu.Game.Users;
namespace osu.Game.Overlays.Profile
{
- public class ProfileHeader : OverlayHeader
+ public class ProfileHeader : TabControlOverlayHeader
{
private UserCoverBackground coverContainer;
@@ -26,10 +26,12 @@ namespace osu.Game.Overlays.Profile
public ProfileHeader()
{
+ BackgroundHeight = 150;
+
User.ValueChanged += e => updateDisplay(e.NewValue);
- TabControl.AddItem("Info");
- TabControl.AddItem("Modding");
+ TabControl.AddItem("info");
+ TabControl.AddItem("modding");
centreHeaderContainer.DetailsVisible.BindValueChanged(visible => detailHeaderContainer.Expanded = visible.NewValue, true);
}
@@ -38,6 +40,8 @@ namespace osu.Game.Overlays.Profile
private void load(OsuColour colours)
{
TabControl.AccentColour = colours.Seafoam;
+ TitleBackgroundColour = colours.GreySeafoamDarker;
+ ControlBackgroundColour = colours.GreySeafoam;
}
protected override Drawable CreateBackground() =>
@@ -101,8 +105,8 @@ namespace osu.Game.Overlays.Profile
{
public ProfileHeaderTitle()
{
- Title = "Player";
- Section = "Info";
+ Title = "player";
+ Section = "info";
}
[BackgroundDependencyLoader]
@@ -110,6 +114,8 @@ namespace osu.Game.Overlays.Profile
{
AccentColour = colours.Seafoam;
}
+
+ protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/profile");
}
}
}
diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs
index 37478d902b..5975e94ffc 100644
--- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs
+++ b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.Cursor;
namespace osu.Game.Overlays.SearchableList
{
@@ -61,21 +62,20 @@ namespace osu.Game.Overlays.SearchableList
scrollContainer = new Container
{
RelativeSizeAxes = Axes.Both,
- Children = new[]
+ Child = new OsuContextMenuContainer
{
- new OsuScrollContainer
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Child = new OsuScrollContainer
{
RelativeSizeAxes = Axes.Both,
ScrollbarVisible = false,
- Children = new[]
+ Child = ScrollFlow = new FillFlowContainer
{
- ScrollFlow = new FillFlowContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Padding = new MarginPadding { Horizontal = WIDTH_PADDING, Bottom = 50 },
- Direction = FillDirection.Vertical,
- },
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Padding = new MarginPadding { Horizontal = WIDTH_PADDING, Bottom = 50 },
+ Direction = FillDirection.Vertical,
},
},
},
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/Overlays/TabControlOverlayHeader.cs b/osu.Game/Overlays/TabControlOverlayHeader.cs
new file mode 100644
index 0000000000..f3521b66c8
--- /dev/null
+++ b/osu.Game/Overlays/TabControlOverlayHeader.cs
@@ -0,0 +1,55 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.UserInterface;
+using osu.Game.Graphics;
+using osuTK;
+
+namespace osu.Game.Overlays
+{
+ public abstract class TabControlOverlayHeader : OverlayHeader
+ {
+ protected OverlayHeaderTabControl TabControl;
+
+ protected override TabControl CreateTabControl() => TabControl = new OverlayHeaderTabControl();
+
+ public class OverlayHeaderTabControl : OverlayTabControl
+ {
+ public OverlayHeaderTabControl()
+ {
+ BarHeight = 1;
+ RelativeSizeAxes = Axes.None;
+ AutoSizeAxes = Axes.X;
+ Anchor = Anchor.BottomLeft;
+ Origin = Anchor.BottomLeft;
+ Height = 35;
+ }
+
+ protected override TabItem CreateTabItem(string value) => new OverlayHeaderTabItem(value)
+ {
+ AccentColour = AccentColour,
+ };
+
+ protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer
+ {
+ RelativeSizeAxes = Axes.Y,
+ AutoSizeAxes = Axes.X,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(5, 0),
+ };
+
+ private class OverlayHeaderTabItem : OverlayTabItem
+ {
+ public OverlayHeaderTabItem(string value)
+ : base(value)
+ {
+ Text.Text = value;
+ Text.Font = OsuFont.GetFont(size: 14);
+ Bar.ExpandedSize = 5;
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs
index 4710465536..89e7866707 100644
--- a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs
+++ b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Edit
private readonly DrawableRuleset drawableRuleset;
[Resolved]
- private IEditorBeatmap beatmap { get; set; }
+ private EditorBeatmap beatmap { get; set; }
public DrawableEditRulesetWrapper(DrawableRuleset drawableRuleset)
{
diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
index 22d94abcb9..bfaa7e8872 100644
--- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs
+++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
@@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
-using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
@@ -35,20 +34,20 @@ namespace osu.Game.Rulesets.Edit
{
protected IRulesetConfigManager Config { get; private set; }
- protected new EditorBeatmap EditorBeatmap { get; private set; }
-
protected readonly Ruleset Ruleset;
[Resolved]
protected IFrameBasedClock EditorClock { get; private set; }
+ [Resolved]
+ protected EditorBeatmap EditorBeatmap { get; private set; }
+
[Resolved]
private IAdjustableClock adjustableClock { get; set; }
[Resolved]
private BindableBeatDivisor beatDivisor { get; set; }
- private Beatmap playableBeatmap;
private IBeatmapProcessor beatmapProcessor;
private DrawableEditRulesetWrapper drawableRulesetWrapper;
@@ -68,9 +67,17 @@ namespace osu.Game.Rulesets.Edit
[BackgroundDependencyLoader]
private void load(IFrameBasedClock framedClock)
{
+ beatmapProcessor = Ruleset.CreateBeatmapProcessor(EditorBeatmap.PlayableBeatmap);
+
+ EditorBeatmap.HitObjectAdded += addHitObject;
+ EditorBeatmap.HitObjectRemoved += removeHitObject;
+ EditorBeatmap.StartTimeChanged += UpdateHitObject;
+
+ Config = Dependencies.Get().GetConfigFor(Ruleset);
+
try
{
- drawableRulesetWrapper = new DrawableEditRulesetWrapper(CreateDrawableRuleset(Ruleset, playableBeatmap))
+ drawableRulesetWrapper = new DrawableEditRulesetWrapper(CreateDrawableRuleset(Ruleset, EditorBeatmap.PlayableBeatmap))
{
Clock = framedClock,
ProcessCustomClock = false
@@ -140,28 +147,6 @@ namespace osu.Game.Rulesets.Edit
blueprintContainer.SelectionChanged += selectionChanged;
}
- protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
- {
- var parentWorkingBeatmap = parent.Get>().Value;
-
- playableBeatmap = (Beatmap)parentWorkingBeatmap.GetPlayableBeatmap(Ruleset.RulesetInfo);
-
- beatmapProcessor = Ruleset.CreateBeatmapProcessor(playableBeatmap);
-
- base.EditorBeatmap = EditorBeatmap = new EditorBeatmap(playableBeatmap);
- EditorBeatmap.HitObjectAdded += addHitObject;
- EditorBeatmap.HitObjectRemoved += removeHitObject;
- EditorBeatmap.StartTimeChanged += UpdateHitObject;
-
- var dependencies = new DependencyContainer(parent);
- dependencies.CacheAs(EditorBeatmap);
- dependencies.CacheAs>(EditorBeatmap);
-
- Config = dependencies.Get().GetConfigFor(Ruleset);
-
- return base.CreateChildDependencies(dependencies);
- }
-
protected override void LoadComplete()
{
base.LoadComplete();
@@ -234,7 +219,7 @@ namespace osu.Game.Rulesets.Edit
scheduledUpdate = Schedule(() =>
{
beatmapProcessor?.PreProcess();
- hitObject?.ApplyDefaults(playableBeatmap.ControlPointInfo, playableBeatmap.BeatmapInfo.BaseDifficulty);
+ hitObject?.ApplyDefaults(EditorBeatmap.ControlPointInfo, EditorBeatmap.BeatmapInfo.BaseDifficulty);
beatmapProcessor?.PostProcess();
});
}
@@ -333,11 +318,6 @@ namespace osu.Game.Rulesets.Edit
///
public abstract IEnumerable HitObjects { get; }
- ///
- /// An editor-specific beatmap, exposing mutation events.
- ///
- public IEditorBeatmap EditorBeatmap { get; protected set; }
-
///
/// Whether the user's cursor is currently in an area of the that is valid for placement.
///
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/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs
index f07f76a2b8..599135ba54 100644
--- a/osu.Game/Rulesets/Judgements/Judgement.cs
+++ b/osu.Game/Rulesets/Judgements/Judgement.cs
@@ -11,6 +11,12 @@ namespace osu.Game.Rulesets.Judgements
///
public class Judgement
{
+ ///
+ /// The default health increase for a maximum judgement, as a proportion of total health.
+ /// By default, each maximum judgement restores 5% of total health.
+ ///
+ protected const double DEFAULT_MAX_HEALTH_INCREASE = 0.05;
+
///
/// The maximum that can be achieved.
///
@@ -55,7 +61,32 @@ namespace osu.Game.Rulesets.Judgements
///
/// The to find the numeric health increase for.
/// The numeric health increase of .
- protected virtual double HealthIncreaseFor(HitResult result) => 0;
+ protected virtual double HealthIncreaseFor(HitResult result)
+ {
+ switch (result)
+ {
+ case HitResult.Miss:
+ return -DEFAULT_MAX_HEALTH_INCREASE;
+
+ case HitResult.Meh:
+ return -DEFAULT_MAX_HEALTH_INCREASE * 0.05;
+
+ case HitResult.Ok:
+ return -DEFAULT_MAX_HEALTH_INCREASE * 0.01;
+
+ case HitResult.Good:
+ return DEFAULT_MAX_HEALTH_INCREASE * 0.3;
+
+ case HitResult.Great:
+ return DEFAULT_MAX_HEALTH_INCREASE;
+
+ case HitResult.Perfect:
+ return DEFAULT_MAX_HEALTH_INCREASE * 1.05;
+
+ default:
+ return 0;
+ }
+ }
///
/// Retrieves the numeric health increase of a .
diff --git a/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs b/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs
index 4d4cd75434..34198da722 100644
--- a/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs
+++ b/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs
@@ -10,6 +10,17 @@ namespace osu.Game.Rulesets.Mods
///
public interface IApplicableToDifficulty : IApplicableMod
{
+ ///
+ /// Called when a beatmap is changed. Can be used to read default values.
+ /// Any changes made will not be preserved.
+ ///
+ /// The difficulty to read from.
+ void ReadFromDifficulty(BeatmapDifficulty difficulty);
+
+ ///
+ /// Called post beatmap conversion. Can be used to apply changes to difficulty attributes.
+ ///
+ /// The difficulty to mutate.
void ApplyToDifficulty(BeatmapDifficulty difficulty);
}
}
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..c5b8a1bc73
--- /dev/null
+++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs
@@ -0,0 +1,105 @@
+// 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 System.Collections.Generic;
+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 ReadFromDifficulty(BeatmapDifficulty difficulty)
+ {
+ if (this.difficulty == null || this.difficulty.ID != difficulty.ID)
+ {
+ TransferSettings(difficulty);
+ this.difficulty = difficulty;
+ }
+ }
+
+ public void ApplyToDifficulty(BeatmapDifficulty difficulty) => ApplySettings(difficulty);
+
+ ///
+ /// Transfer initial settings from the beatmap to settings.
+ ///
+ /// The beatmap's initial values.
+ protected virtual void TransferSettings(BeatmapDifficulty difficulty)
+ {
+ TransferSetting(DrainRate, difficulty.DrainRate);
+ TransferSetting(OverallDifficulty, difficulty.OverallDifficulty);
+ }
+
+ private readonly Dictionary userChangedSettings = new Dictionary();
+
+ ///
+ /// Transfer a setting from to a configuration bindable.
+ /// Only performs the transfer if the user it not currently overriding..
+ ///
+ protected void TransferSetting(BindableNumber bindable, T beatmapDefault)
+ where T : struct, IComparable, IConvertible, IEquatable
+ {
+ bindable.UnbindEvents();
+
+ userChangedSettings.TryAdd(bindable, false);
+
+ bindable.Default = beatmapDefault;
+
+ // users generally choose a difficulty setting and want it to stick across multiple beatmap changes.
+ // we only want to value transfer if the user hasn't changed the value previously.
+ if (!userChangedSettings[bindable])
+ bindable.Value = beatmapDefault;
+
+ bindable.ValueChanged += _ => userChangedSettings[bindable] = !bindable.IsDefault;
+ }
+
+ ///
+ /// 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..ec0f50c0be 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,12 +19,21 @@ 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;
+ public void ReadFromDifficulty(BeatmapDifficulty difficulty) { }
+
public void ApplyToDifficulty(BeatmapDifficulty difficulty)
{
const float ratio = 0.5f;
@@ -32,6 +41,8 @@ namespace osu.Game.Rulesets.Mods
difficulty.ApproachRate *= ratio;
difficulty.DrainRate *= ratio;
difficulty.OverallDifficulty *= ratio;
+
+ retries = Retries.Value;
}
public bool AllowFail
@@ -49,11 +60,9 @@ namespace osu.Game.Rulesets.Mods
public bool RestartOnFail => false;
- public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
+ public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
{
- health = scoreProcessor.Health.GetBoundCopy();
+ health = healthProcessor.Health.GetBoundCopy();
}
-
- public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
}
}
diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs
index 2044cbeae2..a613d41cf4 100644
--- a/osu.Game/Rulesets/Mods/ModHardRock.cs
+++ b/osu.Game/Rulesets/Mods/ModHardRock.cs
@@ -15,7 +15,9 @@ 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 ReadFromDifficulty(BeatmapDifficulty difficulty) { }
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 a959fee9be..4ac30fe7fb 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -31,9 +31,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
public readonly Bindable AccentColour = new Bindable(Color4.Gray);
- // Todo: Rulesets should be overriding the resources instead, but we need to figure out where/when to apply overrides first
- protected virtual string SampleNamespace => null;
-
protected SkinnableSound Samples { get; private set; }
protected virtual IEnumerable GetSamples() => HitObject.Samples;
@@ -154,11 +151,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
}
- samples = samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).ToArray();
- foreach (var s in samples)
- s.Namespace = SampleNamespace;
-
- AddInternal(Samples = new SkinnableSound(samples));
+ AddInternal(Samples = new SkinnableSound(samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s))));
}
private void onDefaultsApplied() => apply(HitObject);
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index 7ad93379f0..c38a5c6af7 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -21,12 +21,13 @@ using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Skinning;
+using osu.Game.Users;
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 +52,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,
+ };
}
///
@@ -64,10 +72,16 @@ namespace osu.Game.Rulesets
public abstract DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null);
///
- /// Creates a for a beatmap converted to this ruleset.
+ /// Creates a for this .
///
/// The score processor.
- public virtual ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new ScoreProcessor(beatmap);
+ public virtual ScoreProcessor CreateScoreProcessor() => new ScoreProcessor();
+
+ ///
+ /// Creates a for this .
+ ///
+ /// The health processor.
+ public virtual HealthProcessor CreateHealthProcessor(double drainStartTime) => new DrainingHealthProcessor(drainStartTime);
///
/// Creates a to convert a to one that is applicable for this .
@@ -91,7 +105,7 @@ namespace osu.Game.Rulesets
public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.QuestionCircle };
- public virtual IResourceStore CreateResourceStore() => new NamespacedResourceStore(new DllResourceStore(GetType().Assembly.Location), @"Resources");
+ public virtual IResourceStore CreateResourceStore() => new NamespacedResourceStore(new DllResourceStore(GetType().Assembly), @"Resources");
public abstract string Description { get; }
@@ -103,16 +117,16 @@ 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.
///
public abstract string ShortName { get; }
+ ///
+ /// The playing verb to be shown in the .
+ ///
+ public virtual string PlayingVerb => "Playing solo";
+
///
/// A list of available variant ids.
///
@@ -138,18 +152,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/AccumulatingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/AccumulatingHealthProcessor.cs
new file mode 100644
index 0000000000..5dfb5167f4
--- /dev/null
+++ b/osu.Game/Rulesets/Scoring/AccumulatingHealthProcessor.cs
@@ -0,0 +1,32 @@
+// 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.Scoring
+{
+ ///
+ /// A that accumulates health and causes a fail if the final health
+ /// is less than a value required to pass the beatmap.
+ ///
+ public class AccumulatingHealthProcessor : HealthProcessor
+ {
+ protected override bool DefaultFailCondition => JudgedHits == MaxHits && Health.Value < requiredHealth;
+
+ private readonly double requiredHealth;
+
+ ///
+ /// Creates a new .
+ ///
+ /// The minimum amount of health required to beatmap.
+ public AccumulatingHealthProcessor(double requiredHealth)
+ {
+ this.requiredHealth = requiredHealth;
+ }
+
+ protected override void Reset(bool storeResults)
+ {
+ base.Reset(storeResults);
+
+ Health.Value = 0;
+ }
+ }
+}
diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs
new file mode 100644
index 0000000000..fffcbb3c9f
--- /dev/null
+++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs
@@ -0,0 +1,157 @@
+// 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.Game.Beatmaps;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
+
+namespace osu.Game.Rulesets.Scoring
+{
+ ///
+ /// A which continuously drains health.
+ /// At HP=0, the minimum health reached for a perfect play is 95%.
+ /// At HP=5, the minimum health reached for a perfect play is 70%.
+ /// At HP=10, the minimum health reached for a perfect play is 30%.
+ ///
+ public class DrainingHealthProcessor : HealthProcessor
+ {
+ ///
+ /// A reasonable allowable error for the minimum health offset from . A 1% error is unnoticeable.
+ ///
+ private const double minimum_health_error = 0.01;
+
+ ///
+ /// The minimum health target at an HP drain rate of 0.
+ ///
+ private const double min_health_target = 0.95;
+
+ ///
+ /// The minimum health target at an HP drain rate of 5.
+ ///
+ private const double mid_health_target = 0.70;
+
+ ///
+ /// The minimum health target at an HP drain rate of 10.
+ ///
+ private const double max_health_target = 0.30;
+
+ private IBeatmap beatmap;
+
+ private double gameplayEndTime;
+
+ private readonly double drainStartTime;
+
+ private readonly List<(double time, double health)> healthIncreases = new List<(double, double)>();
+ private double targetMinimumHealth;
+ private double drainRate = 1;
+
+ ///
+ /// Creates a new .
+ ///
+ /// The time after which draining should begin.
+ public DrainingHealthProcessor(double drainStartTime)
+ {
+ this.drainStartTime = drainStartTime;
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (!IsBreakTime.Value)
+ {
+ // When jumping in and out of gameplay time within a single frame, health should only be drained for the period within the gameplay time
+ double lastGameplayTime = Math.Clamp(Time.Current - Time.Elapsed, drainStartTime, gameplayEndTime);
+ double currentGameplayTime = Math.Clamp(Time.Current, drainStartTime, gameplayEndTime);
+
+ Health.Value -= drainRate * (currentGameplayTime - lastGameplayTime);
+ }
+ }
+
+ public override void ApplyBeatmap(IBeatmap beatmap)
+ {
+ this.beatmap = beatmap;
+
+ if (beatmap.HitObjects.Count > 0)
+ gameplayEndTime = beatmap.HitObjects[^1].GetEndTime();
+
+ targetMinimumHealth = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, min_health_target, mid_health_target, max_health_target);
+
+ base.ApplyBeatmap(beatmap);
+ }
+
+ protected override void ApplyResultInternal(JudgementResult result)
+ {
+ base.ApplyResultInternal(result);
+ healthIncreases.Add((result.HitObject.GetEndTime() + result.TimeOffset, GetHealthIncreaseFor(result)));
+ }
+
+ protected override void Reset(bool storeResults)
+ {
+ base.Reset(storeResults);
+
+ drainRate = 1;
+
+ if (storeResults)
+ drainRate = computeDrainRate();
+
+ healthIncreases.Clear();
+ }
+
+ private double computeDrainRate()
+ {
+ if (healthIncreases.Count == 0)
+ return 0;
+
+ int adjustment = 1;
+ double result = 1;
+
+ // Although we expect the following loop to converge within 30 iterations (health within 1/2^31 accuracy of the target),
+ // we'll still keep a safety measure to avoid infinite loops by detecting overflows.
+ while (adjustment > 0)
+ {
+ double currentHealth = 1;
+ double lowestHealth = 1;
+ int currentBreak = -1;
+
+ for (int i = 0; i < healthIncreases.Count; i++)
+ {
+ double currentTime = healthIncreases[i].time;
+ double lastTime = i > 0 ? healthIncreases[i - 1].time : drainStartTime;
+
+ // Subtract any break time from the duration since the last object
+ if (beatmap.Breaks.Count > 0)
+ {
+ // Advance the last break occuring before the current time
+ while (currentBreak + 1 < beatmap.Breaks.Count && beatmap.Breaks[currentBreak + 1].EndTime < currentTime)
+ currentBreak++;
+
+ if (currentBreak >= 0)
+ lastTime = Math.Max(lastTime, beatmap.Breaks[currentBreak].EndTime);
+ }
+
+ // Apply health adjustments
+ currentHealth -= (healthIncreases[i].time - lastTime) * result;
+ lowestHealth = Math.Min(lowestHealth, currentHealth);
+ currentHealth = Math.Min(1, currentHealth + healthIncreases[i].health);
+
+ // Common scenario for when the drain rate is definitely too harsh
+ if (lowestHealth < 0)
+ break;
+ }
+
+ // Stop if the resulting health is within a reasonable offset from the target
+ if (Math.Abs(lowestHealth - targetMinimumHealth) <= minimum_health_error)
+ break;
+
+ // This effectively works like a binary search - each iteration the search space moves closer to the target, but may exceed it.
+ adjustment *= 2;
+ result += 1.0 / adjustment * Math.Sign(lowestHealth - targetMinimumHealth);
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/osu.Game/Rulesets/Scoring/HealthProcessor.cs b/osu.Game/Rulesets/Scoring/HealthProcessor.cs
new file mode 100644
index 0000000000..0c6b3f67b4
--- /dev/null
+++ b/osu.Game/Rulesets/Scoring/HealthProcessor.cs
@@ -0,0 +1,83 @@
+// 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.Rulesets.Judgements;
+
+namespace osu.Game.Rulesets.Scoring
+{
+ public abstract 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 gameplay is currently in a break.
+ ///
+ public readonly IBindable IsBreakTime = new Bindable();
+
+ ///
+ /// Whether this ScoreProcessor has already triggered the failed state.
+ ///
+ public bool HasFailed { get; private set; }
+
+ protected override void ApplyResultInternal(JudgementResult result)
+ {
+ result.HealthAtJudgement = Health.Value;
+ result.FailedAtJudgement = HasFailed;
+
+ if (HasFailed)
+ return;
+
+ Health.Value += GetHealthIncreaseFor(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
+ }
+
+ ///
+ /// Retrieves the health increase for a .
+ ///
+ /// The .
+ /// The health increase.
+ protected virtual double GetHealthIncreaseFor(JudgementResult result) => result.Judgement.HealthIncreaseFor(result);
+
+ ///
+ /// 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..3016007f98
--- /dev/null
+++ b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs
@@ -0,0 +1,140 @@
+// 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.Framework.Graphics;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
+
+namespace osu.Game.Rulesets.Scoring
+{
+ public abstract class JudgementProcessor : Component
+ {
+ ///
+ /// 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;
+
+ ///
+ /// Applies a to this .
+ ///
+ /// The to read properties from.
+ public virtual void ApplyBeatmap(IBeatmap beatmap)
+ {
+ Reset(false);
+ SimulateAutoplay(beatmap);
+ Reset(true);
+ }
+
+ ///
+ /// 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..8ccc2af93b 100644
--- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
+++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
@@ -7,44 +7,18 @@ 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 +29,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 +54,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;
@@ -114,7 +63,7 @@ namespace osu.Game.Rulesets.Scoring
private double scoreMultiplier = 1;
- public ScoreProcessor(IBeatmap beatmap)
+ public ScoreProcessor()
{
Debug.Assert(base_portion + combo_portion == 1.0);
@@ -126,18 +75,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;
- Mode.Disabled = true;
- }
-
Mode.ValueChanged += _ => updateScore();
Mods.ValueChanged += mods =>
{
@@ -150,91 +87,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 +128,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 +152,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 +178,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 +202,33 @@ 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;
+
+ if (maxBaseScore == 0 || maxHighestCombo == 0)
+ {
+ Mode.Value = ScoringMode.Classic;
+ Mode.Disabled = true;
+ }
}
- 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 +249,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..e624fb80fa 100644
--- a/osu.Game/Rulesets/UI/DrawableRuleset.cs
+++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs
@@ -72,10 +72,9 @@ namespace osu.Game.Rulesets.UI
///
public override Playfield Playfield => playfield.Value;
- ///
- /// Place to put drawables above hit objects but below UI.
- ///
- public Container Overlays { get; private set; }
+ private Container overlays;
+
+ public override Container Overlays => overlays;
public override GameplayClock FrameStableClock => frameStabilityContainer.GameplayClock;
@@ -159,7 +158,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();
@@ -185,12 +184,15 @@ namespace osu.Game.Rulesets.UI
frameStabilityContainer = new FrameStabilityContainer(GameplayStartTime)
{
FrameStablePlayback = FrameStablePlayback,
- Child = KeyBindingInputManager
- .WithChild(CreatePlayfieldAdjustmentContainer()
- .WithChild(Playfield)
- )
+ Children = new Drawable[]
+ {
+ KeyBindingInputManager
+ .WithChild(CreatePlayfieldAdjustmentContainer()
+ .WithChild(Playfield)
+ ),
+ overlays = new Container { RelativeSizeAxes = Axes.Both }
+ }
},
- Overlays = new Container { RelativeSizeAxes = Axes.Both }
};
if ((ResumeOverlay = CreateResumeOverlay()) != null)
@@ -385,6 +387,11 @@ namespace osu.Game.Rulesets.UI
///
public abstract Playfield Playfield { get; }
+ ///
+ /// Place to put drawables above hit objects but below UI.
+ ///
+ public abstract Container Overlays { get; }
+
///
/// The frame-stable clock which is being used for playfield display.
///
diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
index 857929ff9e..04b4374fc4 100644
--- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
@@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
private void computeLifetimeStartRecursive(DrawableHitObject hitObject)
{
- hitObject.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, timeRange.Value);
+ hitObject.LifetimeStart = computeOriginAdjustedLifetimeStart(hitObject);
foreach (var obj in hitObject.NestedHitObjects)
computeLifetimeStartRecursive(obj);
@@ -108,6 +108,35 @@ namespace osu.Game.Rulesets.UI.Scrolling
private readonly Dictionary hitObjectInitialStateCache = new Dictionary();
+ private double computeOriginAdjustedLifetimeStart(DrawableHitObject hitObject)
+ {
+ float originAdjustment = 0.0f;
+
+ // calculate the dimension of the part of the hitobject that should already be visible
+ // when the hitobject origin first appears inside the scrolling container
+ switch (direction.Value)
+ {
+ case ScrollingDirection.Up:
+ originAdjustment = hitObject.OriginPosition.Y;
+ break;
+
+ case ScrollingDirection.Down:
+ originAdjustment = hitObject.DrawHeight - hitObject.OriginPosition.Y;
+ break;
+
+ case ScrollingDirection.Left:
+ originAdjustment = hitObject.OriginPosition.X;
+ break;
+
+ case ScrollingDirection.Right:
+ originAdjustment = hitObject.DrawWidth - hitObject.OriginPosition.X;
+ break;
+ }
+
+ var adjustedStartTime = scrollingInfo.Algorithm.TimeAt(-originAdjustment, hitObject.HitObject.StartTime, timeRange.Value, scrollLength);
+ return scrollingInfo.Algorithm.GetDisplayStartTime(adjustedStartTime, timeRange.Value);
+ }
+
// Cant use AddOnce() since the delegate is re-constructed every invocation
private void computeInitialStateRecursive(DrawableHitObject hitObject) => hitObject.Schedule(() =>
{
diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
index 195bc663f1..cafaddc39e 100644
--- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private HitObjectComposer composer { get; set; }
[Resolved]
- private IEditorBeatmap beatmap { get; set; }
+ private EditorBeatmap beatmap { get; set; }
public BlueprintContainer()
{
diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs
index 00326d04f7..53c5cf97fa 100644
--- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected IDistanceSnapProvider SnapProvider { get; private set; }
[Resolved]
- private IEditorBeatmap beatmap { get; set; }
+ private EditorBeatmap beatmap { get; set; }
[Resolved]
private BindableBeatDivisor beatDivisor { get; set; }
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs
index db4aca75e5..b20f2fa11d 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectDisplay.cs
@@ -16,9 +16,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{
internal class TimelineHitObjectDisplay : TimelinePart
{
- private IEditorBeatmap beatmap { get; }
+ private EditorBeatmap beatmap { get; }
- public TimelineHitObjectDisplay(IEditorBeatmap beatmap)
+ public TimelineHitObjectDisplay(EditorBeatmap beatmap)
{
RelativeSizeAxes = Axes.Both;
diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
index 5d9757778d..1a6aae294a 100644
--- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
+++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
@@ -32,6 +32,6 @@ namespace osu.Game.Screens.Edit.Compose
return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(composer));
}
- protected override Drawable CreateTimelineContent() => composer == null ? base.CreateTimelineContent() : new TimelineHitObjectDisplay(composer.EditorBeatmap);
+ protected override Drawable CreateTimelineContent() => composer == null ? base.CreateTimelineContent() : new TimelineHitObjectDisplay(EditorBeatmap);
}
}
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index 1b4964c068..14d69cddd1 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -23,6 +23,7 @@ using osuTK.Input;
using System.Collections.Generic;
using osu.Framework;
using osu.Framework.Input.Bindings;
+using osu.Game.Beatmaps;
using osu.Game.Graphics.Cursor;
using osu.Game.Input.Bindings;
using osu.Game.Screens.Edit.Compose;
@@ -49,9 +50,11 @@ namespace osu.Game.Screens.Edit
private EditorScreen currentScreen;
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
-
private EditorClock clock;
+ private IBeatmap playableBeatmap;
+ private EditorBeatmap editorBeatmap;
+
private DependencyContainer dependencies;
private GameHost host;
@@ -73,9 +76,13 @@ namespace osu.Game.Screens.Edit
clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false };
clock.ChangeSource(sourceClock);
+ playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset);
+ editorBeatmap = new EditorBeatmap(playableBeatmap);
+
dependencies.CacheAs(clock);
dependencies.CacheAs(clock);
dependencies.Cache(beatDivisor);
+ dependencies.CacheAs(editorBeatmap);
EditorMenuBar menuBar;
diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs
index c3a322ea36..6ed74dfdb0 100644
--- a/osu.Game/Screens/Edit/EditorBeatmap.cs
+++ b/osu.Game/Screens/Edit/EditorBeatmap.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections;
using System.Collections.Generic;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
@@ -11,30 +12,30 @@ using osu.Game.Rulesets.Objects;
namespace osu.Game.Screens.Edit
{
- public class EditorBeatmap : IEditorBeatmap
- where T : HitObject
+ public class EditorBeatmap : IBeatmap
{
///
- /// Invoked when a is added to this .
+ /// Invoked when a is added to this .
///
public event Action HitObjectAdded;
///
- /// Invoked when a is removed from this .
+ /// Invoked when a is removed from this .
///
public event Action HitObjectRemoved;
///
- /// Invoked when the start time of a in this was changed.
+ /// Invoked when the start time of a in this was changed.
///
public event Action StartTimeChanged;
- private readonly Dictionary> startTimeBindables = new Dictionary>();
- private readonly Beatmap beatmap;
+ public readonly IBeatmap PlayableBeatmap;
- public EditorBeatmap(Beatmap beatmap)
+ private readonly Dictionary> startTimeBindables = new Dictionary>();
+
+ public EditorBeatmap(IBeatmap playableBeatmap)
{
- this.beatmap = beatmap;
+ PlayableBeatmap = playableBeatmap;
foreach (var obj in HitObjects)
trackStartTime(obj);
@@ -42,82 +43,83 @@ namespace osu.Game.Screens.Edit
public BeatmapInfo BeatmapInfo
{
- get => beatmap.BeatmapInfo;
- set => beatmap.BeatmapInfo = value;
+ get => PlayableBeatmap.BeatmapInfo;
+ set => PlayableBeatmap.BeatmapInfo = value;
}
- public BeatmapMetadata Metadata => beatmap.Metadata;
+ public BeatmapMetadata Metadata => PlayableBeatmap.Metadata;
- public ControlPointInfo ControlPointInfo => beatmap.ControlPointInfo;
+ public ControlPointInfo ControlPointInfo => PlayableBeatmap.ControlPointInfo;
- public List Breaks => beatmap.Breaks;
+ public List Breaks => PlayableBeatmap.Breaks;
- public double TotalBreakTime => beatmap.TotalBreakTime;
+ public double TotalBreakTime => PlayableBeatmap.TotalBreakTime;
- public IReadOnlyList HitObjects => beatmap.HitObjects;
+ public IReadOnlyList HitObjects => PlayableBeatmap.HitObjects;
- IReadOnlyList IBeatmap.HitObjects => beatmap.HitObjects;
+ public IEnumerable GetStatistics() => PlayableBeatmap.GetStatistics();
- public IEnumerable GetStatistics() => beatmap.GetStatistics();
+ public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone();
- public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone();
+ private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects;
///
- /// Adds a to this .
+ /// Adds a to this .
///
/// The to add.
- public void Add(T hitObject)
+ public void Add(HitObject hitObject)
{
trackStartTime(hitObject);
// Preserve existing sorting order in the beatmap
- var insertionIndex = beatmap.HitObjects.FindLastIndex(h => h.StartTime <= hitObject.StartTime);
- beatmap.HitObjects.Insert(insertionIndex + 1, hitObject);
+ var insertionIndex = findInsertionIndex(PlayableBeatmap.HitObjects, hitObject.StartTime);
+ mutableHitObjects.Insert(insertionIndex + 1, hitObject);
HitObjectAdded?.Invoke(hitObject);
}
///
- /// Removes a from this .
+ /// Removes a from this .
///
/// The to add.
- public void Remove(T hitObject)
+ public void Remove(HitObject hitObject)
{
- if (beatmap.HitObjects.Remove(hitObject))
- {
- var bindable = startTimeBindables[hitObject];
- bindable.UnbindAll();
+ if (!mutableHitObjects.Contains(hitObject))
+ return;
- startTimeBindables.Remove(hitObject);
- HitObjectRemoved?.Invoke(hitObject);
- }
+ mutableHitObjects.Remove(hitObject);
+
+ var bindable = startTimeBindables[hitObject];
+ bindable.UnbindAll();
+
+ startTimeBindables.Remove(hitObject);
+ HitObjectRemoved?.Invoke(hitObject);
}
- private void trackStartTime(T hitObject)
+ private void trackStartTime(HitObject hitObject)
{
startTimeBindables[hitObject] = hitObject.StartTimeBindable.GetBoundCopy();
startTimeBindables[hitObject].ValueChanged += _ =>
{
// For now we'll remove and re-add the hitobject. This is not optimal and can be improved if required.
- beatmap.HitObjects.Remove(hitObject);
+ mutableHitObjects.Remove(hitObject);
- var insertionIndex = beatmap.HitObjects.FindLastIndex(h => h.StartTime <= hitObject.StartTime);
- beatmap.HitObjects.Insert(insertionIndex + 1, hitObject);
+ var insertionIndex = findInsertionIndex(PlayableBeatmap.HitObjects, hitObject.StartTime);
+ mutableHitObjects.Insert(insertionIndex + 1, hitObject);
StartTimeChanged?.Invoke(hitObject);
};
}
- ///
- /// Adds a to this .
- ///
- /// The to add.
- public void Add(HitObject hitObject) => Add((T)hitObject);
+ private int findInsertionIndex(IReadOnlyList list, double startTime)
+ {
+ for (int i = 0; i < list.Count; i++)
+ {
+ if (list[i].StartTime > startTime)
+ return i - 1;
+ }
- ///
- /// Removes a from this .
- ///
- /// The to add.
- public void Remove(HitObject hitObject) => Remove((T)hitObject);
+ return list.Count - 1;
+ }
}
}
diff --git a/osu.Game/Screens/Edit/EditorScreen.cs b/osu.Game/Screens/Edit/EditorScreen.cs
index 1b57c703ae..d42447ac4b 100644
--- a/osu.Game/Screens/Edit/EditorScreen.cs
+++ b/osu.Game/Screens/Edit/EditorScreen.cs
@@ -17,6 +17,9 @@ namespace osu.Game.Screens.Edit
[Resolved]
protected IBindable Beatmap { get; private set; }
+ [Resolved]
+ protected EditorBeatmap EditorBeatmap { get; private set; }
+
protected override Container Content => content;
private readonly Container content;
diff --git a/osu.Game/Screens/Edit/IEditorBeatmap.cs b/osu.Game/Screens/Edit/IEditorBeatmap.cs
deleted file mode 100644
index 3e3418ef79..0000000000
--- a/osu.Game/Screens/Edit/IEditorBeatmap.cs
+++ /dev/null
@@ -1,41 +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 osu.Game.Beatmaps;
-using osu.Game.Rulesets.Edit;
-using osu.Game.Rulesets.Objects;
-
-namespace osu.Game.Screens.Edit
-{
- ///
- /// Interface for the contained by the see .
- /// Children of may resolve the beatmap via or .
- ///
- public interface IEditorBeatmap : IBeatmap
- {
- ///
- /// Invoked when a is added to this .
- ///
- event Action HitObjectAdded;
-
- ///
- /// Invoked when a is removed from this .
- ///
- event Action HitObjectRemoved;
-
- ///
- /// Invoked when the start time of a in this was changed.
- ///
- event Action StartTimeChanged;
- }
-
- ///
- /// Interface for the contained by the see .
- /// Children of may resolve the beatmap via or .
- ///
- public interface IEditorBeatmap : IEditorBeatmap, IBeatmap
- where T : HitObject
- {
- }
-}
diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs
index e2f362780d..236bdc8442 100644
--- a/osu.Game/Screens/Play/HUDOverlay.cs
+++ b/osu.Game/Screens/Play/HUDOverlay.cs
@@ -41,6 +41,7 @@ namespace osu.Game.Screens.Play
public Bindable ShowHealthbar = new Bindable(true);
private readonly ScoreProcessor scoreProcessor;
+ private readonly HealthProcessor healthProcessor;
private readonly DrawableRuleset drawableRuleset;
private readonly IReadOnlyList mods;
@@ -63,9 +64,10 @@ namespace osu.Game.Screens.Play
private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter };
- public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods)
+ public HUDOverlay(ScoreProcessor scoreProcessor, HealthProcessor healthProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods)
{
this.scoreProcessor = scoreProcessor;
+ this.healthProcessor = healthProcessor;
this.drawableRuleset = drawableRuleset;
this.mods = mods;
@@ -119,7 +121,10 @@ namespace osu.Game.Screens.Play
private void load(OsuConfigManager config, NotificationOverlay notificationOverlay)
{
if (scoreProcessor != null)
- BindProcessor(scoreProcessor);
+ BindScoreProcessor(scoreProcessor);
+
+ if (healthProcessor != null)
+ BindHealthProcessor(healthProcessor);
if (drawableRuleset != null)
{
@@ -288,15 +293,19 @@ namespace osu.Game.Screens.Play
protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay();
- protected virtual void BindProcessor(ScoreProcessor processor)
+ protected virtual void BindScoreProcessor(ScoreProcessor processor)
{
ScoreCounter?.Current.BindTo(processor.TotalScore);
AccuracyCounter?.Current.BindTo(processor.Accuracy);
ComboCounter?.Current.BindTo(processor.Combo);
- HealthDisplay?.Current.BindTo(processor.Health);
if (HealthDisplay is StandardHealthDisplay shd)
processor.NewJudgement += shd.Flash;
}
+
+ protected virtual void BindHealthProcessor(HealthProcessor processor)
+ {
+ HealthDisplay?.Current.BindTo(processor.Health);
+ }
}
}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 8970f9ac88..7228e22382 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; }
@@ -128,9 +131,13 @@ namespace osu.Game.Screens.Play
DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value);
- ScoreProcessor = ruleset.CreateScoreProcessor(playableBeatmap);
+ ScoreProcessor = ruleset.CreateScoreProcessor();
+ ScoreProcessor.ApplyBeatmap(playableBeatmap);
ScoreProcessor.Mods.BindTo(Mods);
+ HealthProcessor = ruleset.CreateHealthProcessor(playableBeatmap.HitObjects[0].StartTime);
+ HealthProcessor.ApplyBeatmap(playableBeatmap);
+
if (!ScoreProcessor.Mode.Disabled)
config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode);
@@ -145,15 +152,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();
}
@@ -188,16 +208,10 @@ namespace osu.Game.Screens.Play
{
target.AddRange(new[]
{
- BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, DrawableRuleset.GameplayStartTime, ScoreProcessor)
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Breaks = working.Beatmap.Breaks
- },
// 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 =
{
@@ -248,6 +262,18 @@ namespace osu.Game.Screens.Play
},
failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, }
});
+
+ DrawableRuleset.Overlays.Add(BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, DrawableRuleset.GameplayStartTime, ScoreProcessor)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Breaks = working.Beatmap.Breaks
+ });
+
+ DrawableRuleset.Overlays.Add(ScoreProcessor);
+ DrawableRuleset.Overlays.Add(HealthProcessor);
+
+ HealthProcessor.IsBreakTime.BindTo(BreakOverlay.IsBreakTime);
}
private void updatePauseOnFocusLostState() =>
@@ -342,7 +368,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 +376,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 +557,7 @@ namespace osu.Game.Screens.Play
if (completionProgressDelegate != null && !completionProgressDelegate.Cancelled && !completionProgressDelegate.Completed)
{
// proceed to result screen if beatmap already finished playing
- completionProgressDelegate.RunTask();
+ scheduleGotoRanking();
return true;
}
@@ -577,6 +592,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/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs
index 57021dfc68..64fcc48004 100644
--- a/osu.Game/Screens/Play/PlayerLoader.cs
+++ b/osu.Game/Screens/Play/PlayerLoader.cs
@@ -118,8 +118,6 @@ namespace osu.Game.Screens.Play
},
idleTracker = new IdleTracker(750)
});
-
- loadNewPlayer();
}
protected override void LoadComplete()
@@ -127,6 +125,21 @@ namespace osu.Game.Screens.Play
base.LoadComplete();
inputManager = GetContainingInputManager();
+ }
+
+ public override void OnEntering(IScreen last)
+ {
+ base.OnEntering(last);
+
+ loadNewPlayer();
+
+ content.ScaleTo(0.7f);
+ Background?.FadeColour(Color4.White, 800, Easing.OutQuint);
+
+ contentIn();
+
+ info.Delay(750).FadeIn(500);
+ this.Delay(1800).Schedule(pushWhenLoaded);
if (!muteWarningShownOnce.Value)
{
@@ -179,19 +192,6 @@ namespace osu.Game.Screens.Play
content.FadeOut(250);
}
- public override void OnEntering(IScreen last)
- {
- base.OnEntering(last);
-
- content.ScaleTo(0.7f);
- Background?.FadeColour(Color4.White, 800, Easing.OutQuint);
-
- contentIn();
-
- info.Delay(750).FadeIn(500);
- this.Delay(1800).Schedule(pushWhenLoaded);
- }
-
protected override void LogoArriving(OsuLogo logo, bool resuming)
{
base.LogoArriving(logo, resuming);
diff --git a/osu.Game/Screens/Play/ReplayPlayerLoader.cs b/osu.Game/Screens/Play/ReplayPlayerLoader.cs
index c8ca604902..4572570437 100644
--- a/osu.Game/Screens/Play/ReplayPlayerLoader.cs
+++ b/osu.Game/Screens/Play/ReplayPlayerLoader.cs
@@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using osu.Framework.Allocation;
+using osu.Framework.Screens;
using osu.Game.Scoring;
namespace osu.Game.Screens.Play
@@ -20,15 +20,13 @@ namespace osu.Game.Screens.Play
scoreInfo = score.ScoreInfo;
}
- protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
+ public override void OnEntering(IScreen last)
{
- var dependencies = base.CreateChildDependencies(parent);
-
// these will be reverted thanks to PlayerLoader's lease.
Mods.Value = scoreInfo.Mods;
Ruleset.Value = scoreInfo.Ruleset;
- return dependencies;
+ base.OnEntering(last);
}
}
}
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/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index 48c520986a..671d37fda4 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -175,7 +175,7 @@ namespace osu.Game.Skinning
{
foreach (var lookup in sampleInfo.LookupNames)
{
- var sample = Samples?.Get(getFallbackName(lookup));
+ var sample = Samples?.Get(lookup);
if (sample != null)
return sample;
diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs
index 18dbd212cc..8926c76018 100644
--- a/osu.Game/Tests/Visual/OsuTestScene.cs
+++ b/osu.Game/Tests/Visual/OsuTestScene.cs
@@ -105,7 +105,7 @@ namespace osu.Game.Tests.Visual
}
[Resolved]
- private AudioManager audio { get; set; }
+ protected AudioManager Audio { get; private set; }
protected virtual IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset);
@@ -113,7 +113,7 @@ namespace osu.Game.Tests.Visual
CreateWorkingBeatmap(CreateBeatmap(ruleset), null);
protected virtual WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) =>
- new ClockBackedTestWorkingBeatmap(beatmap, storyboard, Clock, audio);
+ new ClockBackedTestWorkingBeatmap(beatmap, storyboard, Clock, Audio);
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
diff --git a/osu.Game/Users/UserActivity.cs b/osu.Game/Users/UserActivity.cs
index 8030fc55a2..3c9f201805 100644
--- a/osu.Game/Users/UserActivity.cs
+++ b/osu.Game/Users/UserActivity.cs
@@ -3,6 +3,7 @@
using osu.Game.Beatmaps;
using osu.Game.Graphics;
+using osu.Game.Rulesets;
using osuTK.Graphics;
namespace osu.Game.Users
@@ -44,15 +45,15 @@ namespace osu.Game.Users
{
public BeatmapInfo Beatmap { get; }
- public Rulesets.RulesetInfo Ruleset { get; }
+ public RulesetInfo Ruleset { get; }
- public SoloGame(BeatmapInfo info, Rulesets.RulesetInfo ruleset)
+ public SoloGame(BeatmapInfo info, RulesetInfo ruleset)
{
Beatmap = info;
Ruleset = ruleset;
}
- public override string Status => @"Playing alone";
+ public override string Status => Ruleset.CreateInstance().PlayingVerb;
}
public class Spectating : UserActivity
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 4c5f3e0f38..565608b40f 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -22,8 +22,8 @@
-
-
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index ccfbee9a62..60355b8592 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -73,8 +73,8 @@
-
-
+
+
@@ -82,7 +82,7 @@
-
+
diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist
index 5ceccdf99f..249474b1d7 100644
--- a/osu.iOS/Info.plist
+++ b/osu.iOS/Info.plist
@@ -14,8 +14,6 @@
0.1.0
LSRequiresIPhoneOS
- LSSupportsOpeningDocumentsInPlace
-
MinimumOSVersion
10.0
UIDeviceFamily
@@ -23,6 +21,8 @@
1
2
+ UIFileSharingEnabled
+
UILaunchStoryboardName
LaunchScreen
UIRequiredDeviceCapabilities
@@ -51,7 +51,7 @@
UTTypeConformsTo
-
+ public.data
UTTypeIdentifier
sh.ppy.osu.items
@@ -105,6 +105,8 @@
Owner
CFBundleTypeName
Supported osu! files
+ CFBundleTypeRole
+ Viewer
LSItemContentTypes
sh.ppy.osu.items