diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 082e0d247c..56b3ebe87b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -4,6 +4,9 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
+permissions:
+ contents: read # to fetch code (actions/checkout)
+
jobs:
inspect-code:
name: Code Quality
@@ -85,7 +88,7 @@ jobs:
run: dotnet build -c Debug -warnaserror osu.Desktop.slnf
- name: Test
- run: dotnet test $pwd/**/*.Tests/bin/Debug/*/*.Tests.dll --logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx"
+ run: dotnet test $pwd/**/*.Tests/bin/Debug/*/*.Tests.dll --logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx" -- NUnit.ConsoleOut=0
shell: pwsh
# Attempt to upload results even if test fails.
diff --git a/.github/workflows/report-nunit.yml b/.github/workflows/report-nunit.yml
index 358cbda17a..bfc9620174 100644
--- a/.github/workflows/report-nunit.yml
+++ b/.github/workflows/report-nunit.yml
@@ -8,8 +8,12 @@ on:
workflows: ["Continuous Integration"]
types:
- completed
+permissions: {}
jobs:
annotate:
+ permissions:
+ checks: write # to create checks (dorny/test-reporter)
+
name: Annotate CI run with test results
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion != 'cancelled' }}
diff --git a/.github/workflows/sentry-release.yml b/.github/workflows/sentry-release.yml
index 442b97c473..cce3f23e5f 100644
--- a/.github/workflows/sentry-release.yml
+++ b/.github/workflows/sentry-release.yml
@@ -5,6 +5,9 @@ on:
tags:
- '*'
+permissions:
+ contents: read # to fetch code (actions/checkout)
+
jobs:
sentry_release:
runs-on: ubuntu-latest
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj
index b8604169aa..936808f38b 100644
--- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj
@@ -9,9 +9,9 @@
false
-
+
-
+
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
index 4117452579..35e7742172 100644
--- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
@@ -9,9 +9,9 @@
false
-
+
-
+
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj
index 0b119c8680..c1044965b5 100644
--- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj
@@ -9,9 +9,9 @@
false
-
+
-
+
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
index 4117452579..35e7742172 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
@@ -9,9 +9,9 @@
false
-
+
-
+
diff --git a/osu.Android.props b/osu.Android.props
index dd263d6aaa..3f4c8e2d24 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,11 +51,11 @@
-
-
+
+
-
+
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index d9ad95f96a..3ee1b3da30 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -137,12 +137,13 @@ namespace osu.Desktop
{
base.SetHost(host);
- var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
-
var desktopWindow = (SDL2DesktopWindow)host.Window;
+ var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
+ if (iconStream != null)
+ desktopWindow.SetIconFromStream(iconStream);
+
desktopWindow.CursorState |= CursorState.Hidden;
- desktopWindow.SetIconFromStream(iconStream);
desktopWindow.Title = Name;
desktopWindow.DragDrop += f => fileDrop(new[] { f });
}
diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
index 36ffd3b5b6..d62d422f33 100644
--- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
+++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
@@ -7,9 +7,9 @@
-
+
-
+
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs
index cbf6e8f202..cf6a8169c4 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs
@@ -1,10 +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 System.Collections.Generic;
+using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Testing;
+using osu.Game.Rulesets.Catch.Beatmaps;
+using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Mods;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
@@ -12,11 +17,11 @@ namespace osu.Game.Rulesets.Catch.Tests
[TestFixture]
public class TestSceneCatchTouchInput : OsuTestScene
{
- private CatchTouchInputMapper catchTouchInputMapper = null!;
-
- [SetUpSteps]
- public void SetUpSteps()
+ [Test]
+ public void TestBasic()
{
+ CatchTouchInputMapper catchTouchInputMapper = null!;
+
AddStep("create input overlay", () =>
{
Child = new CatchInputManager(new CatchRuleset().RulesetInfo)
@@ -32,12 +37,30 @@ namespace osu.Game.Rulesets.Catch.Tests
}
};
});
+
+ AddStep("show overlay", () => catchTouchInputMapper.Show());
}
[Test]
- public void TestBasic()
+ public void TestWithoutRelax()
{
- AddStep("show overlay", () => catchTouchInputMapper.Show());
+ AddStep("create drawable ruleset without relax mod", () =>
+ {
+ Child = new DrawableCatchRuleset(new CatchRuleset(), new CatchBeatmap(), new List());
+ });
+ AddUntilStep("wait for load", () => Child.IsLoaded);
+ AddAssert("check touch input is shown", () => this.ChildrenOfType().Any());
+ }
+
+ [Test]
+ public void TestWithRelax()
+ {
+ AddStep("create drawable ruleset with relax mod", () =>
+ {
+ Child = new DrawableCatchRuleset(new CatchRuleset(), new CatchBeatmap(), new List { new CatchModRelax() });
+ });
+ AddUntilStep("wait for load", () => Child.IsLoaded);
+ AddAssert("check touch input is not shown", () => !this.ChildrenOfType().Any());
}
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs
index 7f513728af..f3161f32be 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs
@@ -1,10 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Utils;
using osu.Game.Rulesets.Catch.Objects;
@@ -12,6 +12,8 @@ using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Screens.Play;
+using osu.Game.Tests.Visual;
using osuTK;
using osuTK.Graphics;
@@ -19,15 +21,28 @@ namespace osu.Game.Rulesets.Catch.Tests
{
public class TestSceneComboCounter : CatchSkinnableTestScene
{
- private ScoreProcessor scoreProcessor;
+ private ScoreProcessor scoreProcessor = null!;
private Color4 judgedObjectColour = Color4.White;
+ private readonly Bindable showHud = new Bindable(true);
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Dependencies.CacheAs(new TestPlayer
+ {
+ ShowingOverlayComponents = { BindTarget = showHud },
+ });
+ }
+
[SetUp]
public void SetUp() => Schedule(() =>
{
scoreProcessor = new ScoreProcessor(new CatchRuleset());
+ showHud.Value = true;
+
SetContents(_ => new CatchComboDisplay
{
Anchor = Anchor.Centre,
@@ -51,9 +66,15 @@ namespace osu.Game.Rulesets.Catch.Tests
1f
);
});
+
+ AddStep("set hud to never show", () => showHud.Value = false);
+ AddRepeatStep("perform hit", () => performJudgement(HitResult.Great), 5);
+
+ AddStep("set hud to show", () => showHud.Value = true);
+ AddRepeatStep("perform hit", () => performJudgement(HitResult.Great), 5);
}
- private void performJudgement(HitResult type, Judgement judgement = null)
+ private void performJudgement(HitResult type, Judgement? judgement = null)
{
var judgedObject = new DrawableFruit(new Fruit()) { AccentColour = { Value = judgedObjectColour } };
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 d45f8a9692..c9db824615 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
@@ -1,9 +1,9 @@
-
+
-
+
WinExe
diff --git a/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs b/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs
index 58f493b4b8..a0a11424d0 100644
--- a/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs
+++ b/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs
@@ -36,5 +36,7 @@ namespace osu.Game.Rulesets.Catch.Edit
return base.CreateHitObjectBlueprintFor(hitObject);
}
+
+ protected sealed override DragBox CreateDragBox() => new ScrollingDragBox(Composer.Playfield);
}
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs
index 9038153e20..19b4a39f97 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs
@@ -6,8 +6,6 @@ using osu.Framework.Bindables;
using osu.Framework.Localisation;
using osu.Game.Rulesets.Mods;
using osu.Framework.Utils;
-using osu.Game.Configuration;
-using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.UI;
@@ -17,15 +15,8 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public override LocalisableString Description => "Where's the catcher?";
- [SettingSource(
- "Hidden at combo",
- "The combo count at which the catcher becomes completely hidden",
- SettingControlType = typeof(SettingsSlider)
- )]
- public override BindableInt HiddenComboCount { get; } = new BindableInt
+ public override BindableInt HiddenComboCount { get; } = new BindableInt(10)
{
- Default = 10,
- Value = 10,
MinValue = 0,
MaxValue = 50,
};
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs
index b2dd29841b..b4d29988d9 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Catch.UI;
diff --git a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs
index e9c289e46a..a5b7d8d0af 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs
@@ -1,12 +1,13 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
-using JetBrains.Annotations;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Screens.Play;
using osu.Game.Skinning;
using osuTK.Graphics;
@@ -19,14 +20,29 @@ namespace osu.Game.Rulesets.Catch.UI
{
private int currentCombo;
- [CanBeNull]
- public ICatchComboCounter ComboCounter => Drawable as ICatchComboCounter;
+ public ICatchComboCounter? ComboCounter => Drawable as ICatchComboCounter;
+
+ private readonly IBindable showCombo = new BindableBool(true);
public CatchComboDisplay()
: base(new CatchSkinComponent(CatchSkinComponents.CatchComboCounter), _ => Empty())
{
}
+ [Resolved(canBeNull: true)]
+ private Player? player { get; set; }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ if (player != null)
+ {
+ showCombo.BindTo(player.ShowingOverlayComponents);
+ showCombo.BindValueChanged(s => this.FadeTo(s.NewValue ? 1 : 0, HUDOverlay.FADE_DURATION, HUDOverlay.FADE_EASING), true);
+ }
+ }
+
protected override void SkinChanged(ISkinSource skin)
{
base.SkinChanged(skin);
diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
index ef2936ac94..27f7886d79 100644
--- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
@@ -4,6 +4,7 @@
#nullable disable
using System.Collections.Generic;
+using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Input;
using osu.Game.Beatmaps;
@@ -36,7 +37,9 @@ namespace osu.Game.Rulesets.Catch.UI
[BackgroundDependencyLoader]
private void load()
{
- KeyBindingInputManager.Add(new CatchTouchInputMapper());
+ // With relax mod, input maps directly to x position and left/right buttons are not used.
+ if (!Mods.Any(m => m is ModRelax))
+ KeyBindingInputManager.Add(new CatchTouchInputMapper());
}
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs
index 6e6e83f9cf..0e4f612999 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaPlacementBlueprintTestScene.cs
@@ -10,6 +10,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
@@ -30,15 +31,18 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
[Cached(typeof(IScrollingInfo))]
private IScrollingInfo scrollingInfo;
+ [Cached]
+ private readonly StageDefinition stage = new StageDefinition(5);
+
protected ManiaPlacementBlueprintTestScene()
{
scrollingInfo = ((ScrollingTestContainer)HitObjectContainer).ScrollingInfo;
- Add(column = new Column(0)
+ Add(column = new Column(0, false)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- AccentColour = Color4.OrangeRed,
+ AccentColour = { Value = Color4.OrangeRed },
Clock = new FramedClock(new StopwatchClock()), // No scroll
});
}
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs
index 679a15e8cb..4cadcf138b 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs
@@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
protected ManiaSelectionBlueprintTestScene(int columns)
{
- var stageDefinitions = new List { new StageDefinition { Columns = columns } };
+ var stageDefinitions = new List { new StageDefinition(columns) };
base.Content.Child = scrollingTestContainer = new ScrollingTestContainer(ScrollingDirection.Up)
{
RelativeSizeAxes = Axes.Both,
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs
index ec96205067..ef140995ec 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
private ScrollingTestContainer.TestScrollingInfo scrollingInfo = new ScrollingTestContainer.TestScrollingInfo();
[Cached(typeof(EditorBeatmap))]
- private EditorBeatmap editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition())
+ private EditorBeatmap editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition(2))
{
BeatmapInfo =
{
@@ -56,8 +56,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
{
Playfield = new ManiaPlayfield(new List
{
- new StageDefinition { Columns = 4 },
- new StageDefinition { Columns = 3 }
+ new StageDefinition(4),
+ new StageDefinition(3)
})
{
Clock = new FramedClock(new StopwatchClock())
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs
index e96a186ae4..e082b90d3b 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs
@@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
{
AddStep("setup compose screen", () =>
{
- var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 4 })
+ var beatmap = new ManiaBeatmap(new StageDefinition(4))
{
BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo },
};
diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs
index fcc9e2e6c3..a3985be936 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs
@@ -205,7 +205,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
{
InternalChildren = new Drawable[]
{
- EditorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 })
+ EditorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition(4))
{
BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }
}),
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaColumnTypeTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaColumnTypeTest.cs
deleted file mode 100644
index e53deb5269..0000000000
--- a/osu.Game.Rulesets.Mania.Tests/ManiaColumnTypeTest.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-#nullable disable
-
-using System.Collections.Generic;
-using osu.Game.Rulesets.Mania.Beatmaps;
-using NUnit.Framework;
-
-namespace osu.Game.Rulesets.Mania.Tests
-{
- [TestFixture]
- public class ManiaColumnTypeTest
- {
- [TestCase(new[]
- {
- ColumnType.Special
- }, 1)]
- [TestCase(new[]
- {
- ColumnType.Odd,
- ColumnType.Even,
- ColumnType.Even,
- ColumnType.Odd
- }, 4)]
- [TestCase(new[]
- {
- ColumnType.Odd,
- ColumnType.Even,
- ColumnType.Odd,
- ColumnType.Special,
- ColumnType.Odd,
- ColumnType.Even,
- ColumnType.Odd
- }, 7)]
- public void Test(IEnumerable expected, int columns)
- {
- var definition = new StageDefinition
- {
- Columns = columns
- };
- var results = getResults(definition);
- Assert.AreEqual(expected, results);
- }
-
- private IEnumerable getResults(StageDefinition definition)
- {
- for (int i = 0; i < definition.Columns; i++)
- yield return definition.GetTypeOfColumn(i);
- }
- }
-}
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs
index eb380c07a6..e456659ac4 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs
@@ -3,9 +3,11 @@
#nullable disable
+using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
+using osu.Game.Input.Bindings;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
@@ -37,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{
}
- protected override void ReloadMappings()
+ protected override void ReloadMappings(IQueryable realmKeyBindings)
{
KeyBindings = DefaultKeyBindings;
}
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs
index b64006316e..7d1a934456 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Tests
[TestCase(ManiaAction.Key8)]
public void TestEncodeDecodeSingleStage(params ManiaAction[] actions)
{
- var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 9 });
+ var beatmap = new ManiaBeatmap(new StageDefinition(9));
var frame = new ManiaReplayFrame(0, actions);
var legacyFrame = frame.ToLegacy(beatmap);
@@ -38,8 +38,8 @@ namespace osu.Game.Rulesets.Mania.Tests
[TestCase(ManiaAction.Key8)]
public void TestEncodeDecodeDualStage(params ManiaAction[] actions)
{
- var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 5 });
- beatmap.Stages.Add(new StageDefinition { Columns = 5 });
+ var beatmap = new ManiaBeatmap(new StageDefinition(5));
+ beatmap.Stages.Add(new StageDefinition(5));
var frame = new ManiaReplayFrame(0, actions);
var legacyFrame = frame.ToLegacy(beatmap);
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaSpecialColumnTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaSpecialColumnTest.cs
new file mode 100644
index 0000000000..3bd654e75e
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaSpecialColumnTest.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.
+
+#nullable disable
+
+using System.Collections.Generic;
+using osu.Game.Rulesets.Mania.Beatmaps;
+using NUnit.Framework;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ [TestFixture]
+ public class ManiaSpecialColumnTest
+ {
+ [TestCase(new[]
+ {
+ true
+ }, 1)]
+ [TestCase(new[]
+ {
+ false,
+ false,
+ false,
+ false
+ }, 4)]
+ [TestCase(new[]
+ {
+ false,
+ false,
+ false,
+ true,
+ false,
+ false,
+ false
+ }, 7)]
+ public void Test(IEnumerable special, int columns)
+ {
+ var definition = new StageDefinition(columns);
+ var results = getResults(definition);
+ Assert.AreEqual(special, results);
+ }
+
+ private IEnumerable getResults(StageDefinition definition)
+ {
+ for (int i = 0; i < definition.Columns; i++)
+ yield return definition.IsSpecialColumn(i);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs
index 7970d5b594..d27a79c41d 100644
--- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs
@@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
private static ManiaBeatmap createRawBeatmap()
{
- var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 });
+ var beatmap = new ManiaBeatmap(new StageDefinition(1));
beatmap.ControlPointInfo.Add(0.0, new TimingControlPoint { BeatLength = 1000 }); // Set BPM to 60
// Add test hit objects
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs
index 1a3513d46c..d3e90170b2 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs
@@ -8,7 +8,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.UI;
-using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
@@ -24,15 +23,16 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
[Cached]
private readonly Column column;
+ [Cached]
+ private readonly StageDefinition stageDefinition = new StageDefinition(5);
+
public ColumnTestContainer(int column, ManiaAction action, bool showColumn = false)
{
InternalChildren = new[]
{
- this.column = new Column(column)
+ this.column = new Column(column, false)
{
Action = { Value = action },
- AccentColour = Color4.Orange,
- ColumnType = column % 2 == 0 ? ColumnType.Even : ColumnType.Odd,
Alpha = showColumn ? 1 : 0
},
content = new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4)
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs
index fd82041ad8..75175c43d8 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs
@@ -61,7 +61,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
c.Add(CreateHitObject().With(h =>
{
h.HitObject.StartTime = Time.Current + 5000;
- h.AccentColour.Value = Color4.Orange;
}));
})
},
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs
index 9f235689b4..2c5535a65f 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs
@@ -9,6 +9,7 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Rulesets.UI.Scrolling.Algorithms;
using osu.Game.Tests.Visual;
@@ -24,6 +25,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
[Cached(Type = typeof(IScrollingInfo))]
private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo();
+ [Cached]
+ private readonly StageDefinition stage = new StageDefinition(4);
+
protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset();
protected ManiaSkinnableTestScene()
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.cs
index ff557638a9..1bfe55b074 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
var stageDefinitions = new List
{
- new StageDefinition { Columns = 4 },
+ new StageDefinition(4),
};
SetContents(_ => new ManiaPlayfield(stageDefinitions).With(s =>
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs
deleted file mode 100644
index bbbd7edb7b..0000000000
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneKeyArea.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-#nullable disable
-
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Rulesets.Mania.UI.Components;
-using osu.Game.Skinning;
-using osuTK;
-
-namespace osu.Game.Rulesets.Mania.Tests.Skinning
-{
- public class TestSceneKeyArea : ManiaSkinnableTestScene
- {
- [BackgroundDependencyLoader]
- private void load()
- {
- SetContents(_ => new FillFlowContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Size = new Vector2(0.8f),
- Direction = FillDirection.Horizontal,
- Children = new Drawable[]
- {
- new ColumnTestContainer(0, ManiaAction.Key1)
- {
- RelativeSizeAxes = Axes.Both,
- Width = 0.5f,
- Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
- {
- RelativeSizeAxes = Axes.Both
- },
- },
- new ColumnTestContainer(1, ManiaAction.Key2)
- {
- RelativeSizeAxes = Axes.Both,
- Width = 0.5f,
- Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
- {
- RelativeSizeAxes = Axes.Both
- },
- },
- }
- });
- }
- }
-}
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs
index 62dadbc3dd..9817719c94 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
stageDefinitions = new List
{
- new StageDefinition { Columns = 2 }
+ new StageDefinition(2)
};
SetContents(_ => new ManiaPlayfield(stageDefinitions));
@@ -36,8 +36,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
stageDefinitions = new List
{
- new StageDefinition { Columns = 2 },
- new StageDefinition { Columns = 2 }
+ new StageDefinition(2),
+ new StageDefinition(2)
};
SetContents(_ => new ManiaPlayfield(stageDefinitions));
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs
index f3f1b9416f..07aa0b845f 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
return new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4)
{
- Child = new Stage(0, new StageDefinition { Columns = 4 }, ref normalAction, ref specialAction)
+ Child = new Stage(0, new StageDefinition(4), ref normalAction, ref specialAction)
};
});
}
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs
index 687b3a747d..0744d7e2e7 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageBackground.cs
@@ -5,7 +5,6 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Skinning;
@@ -16,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
[BackgroundDependencyLoader]
private void load()
{
- SetContents(_ => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground, stageDefinition: new StageDefinition { Columns = 4 }),
+ SetContents(_ => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground),
_ => new DefaultStageBackground())
{
Anchor = Anchor.Centre,
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageForeground.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageForeground.cs
index 6cbc172755..979c90c802 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageForeground.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStageForeground.cs
@@ -5,7 +5,6 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania.Tests.Skinning
@@ -15,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
[BackgroundDependencyLoader]
private void load()
{
- SetContents(_ => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground, stageDefinition: new StageDefinition { Columns = 4 }), _ => null)
+ SetContents(_ => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground), _ => null)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs
index 21ec85bbe6..3abeb8a5f6 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | - |
// | |
- var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 });
+ var beatmap = new ManiaBeatmap(new StageDefinition(1));
beatmap.HitObjects.Add(new Note { StartTime = 1000 });
var generated = new ManiaAutoGenerator(beatmap).Generate();
@@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | * |
// | |
- var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 });
+ var beatmap = new ManiaBeatmap(new StageDefinition(1));
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
var generated = new ManiaAutoGenerator(beatmap).Generate();
@@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | - | - |
// | | |
- var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
+ var beatmap = new ManiaBeatmap(new StageDefinition(2));
beatmap.HitObjects.Add(new Note { StartTime = 1000 });
beatmap.HitObjects.Add(new Note { StartTime = 1000, Column = 1 });
@@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | * | * |
// | | |
- var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
+ var beatmap = new ManiaBeatmap(new StageDefinition(2));
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000, Column = 1 });
@@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | - | |
// | | |
- var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
+ var beatmap = new ManiaBeatmap(new StageDefinition(2));
beatmap.HitObjects.Add(new Note { StartTime = 1000 });
beatmap.HitObjects.Add(new Note { StartTime = 2000, Column = 1 });
@@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | * | |
// | | |
- var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
+ var beatmap = new ManiaBeatmap(new StageDefinition(2));
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 2000, Duration = 2000, Column = 1 });
@@ -169,7 +169,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | * | |
// | | |
- var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
+ var beatmap = new ManiaBeatmap(new StageDefinition(2));
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 });
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs
index 2922d18713..83491b6fe9 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs
@@ -11,6 +11,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
@@ -28,6 +29,9 @@ namespace osu.Game.Rulesets.Mania.Tests
[Cached(typeof(IReadOnlyList))]
private IReadOnlyList mods { get; set; } = Array.Empty();
+ [Cached]
+ private readonly StageDefinition stage = new StageDefinition(1);
+
private readonly List columns = new List();
public TestSceneColumn()
@@ -84,12 +88,12 @@ namespace osu.Game.Rulesets.Mania.Tests
private Drawable createColumn(ScrollingDirection direction, ManiaAction action, int index)
{
- var column = new Column(index)
+ var column = new Column(index, false)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Height = 0.85f,
- AccentColour = Color4.OrangeRed,
+ AccentColour = { Value = Color4.OrangeRed },
Action = { Value = action },
};
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs
index 223f8dae44..d273f5cb35 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableManiaHitObject.cs
@@ -4,11 +4,13 @@
#nullable disable
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
@@ -24,6 +26,9 @@ namespace osu.Game.Rulesets.Mania.Tests
private Column column;
+ [Cached]
+ private readonly StageDefinition stage = new StageDefinition(1);
+
[SetUp]
public void SetUp() => Schedule(() =>
{
@@ -35,11 +40,11 @@ namespace osu.Game.Rulesets.Mania.Tests
RelativeSizeAxes = Axes.Y,
TimeRange = 2000,
Clock = new FramedClock(clock),
- Child = column = new Column(0)
+ Child = column = new Column(0, false)
{
Action = { Value = ManiaAction.Key1 },
Height = 0.85f,
- AccentColour = Color4.Gray
+ AccentColour = { Value = Color4.Gray },
},
};
});
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs
index a563dc3106..1f139b5b78 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs
@@ -141,7 +141,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{
AddStep("load player", () =>
{
- Beatmap.Value = CreateWorkingBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 })
+ Beatmap.Value = CreateWorkingBeatmap(new ManiaBeatmap(new StageDefinition(4))
{
HitObjects = hitObjects,
BeatmapInfo =
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs
index cf8947c1ed..6387dac957 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs
@@ -135,7 +135,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{
var specialAction = ManiaAction.Special1;
- var stage = new Stage(0, new StageDefinition { Columns = 2 }, ref action, ref specialAction);
+ var stage = new Stage(0, new StageDefinition(2), ref action, ref specialAction);
stages.Add(stage);
return new ScrollingTestContainer(direction)
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs
index e84d02775a..9f2e3d2502 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs
@@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{
const double beat_length = 500;
- var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 })
+ var beatmap = new ManiaBeatmap(new StageDefinition(1))
{
HitObjects =
{
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 2951076591..0d7b03d830 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
@@ -1,9 +1,9 @@
-
+
-
+
WinExe
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ColumnType.cs b/osu.Game.Rulesets.Mania/Beatmaps/ColumnType.cs
deleted file mode 100644
index 0114987e3c..0000000000
--- a/osu.Game.Rulesets.Mania/Beatmaps/ColumnType.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-#nullable disable
-
-namespace osu.Game.Rulesets.Mania.Beatmaps
-{
- public enum ColumnType
- {
- Even,
- Odd,
- Special
- }
-}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs
index 4879ce6748..b5655a4579 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs
@@ -3,6 +3,7 @@
#nullable disable
+using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
@@ -60,5 +61,18 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
},
};
}
+
+ public StageDefinition GetStageForColumnIndex(int column)
+ {
+ foreach (var stage in Stages)
+ {
+ if (column < stage.Columns)
+ return stage;
+
+ column -= stage.Columns;
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(column), "Provided index exceeds all available stages");
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 90cd7f57b5..632b7cdcc7 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -93,10 +93,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
protected override Beatmap CreateBeatmap()
{
- beatmap = new ManiaBeatmap(new StageDefinition { Columns = TargetColumns }, originalTargetColumns);
+ beatmap = new ManiaBeatmap(new StageDefinition(TargetColumns), originalTargetColumns);
if (Dual)
- beatmap.Stages.Add(new StageDefinition { Columns = TargetColumns });
+ beatmap.Stages.Add(new StageDefinition(TargetColumns));
return beatmap;
}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs b/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs
index 54e2d4686f..898b558eb3 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs
@@ -11,32 +11,26 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
///
/// Defines properties for each stage in a .
///
- public struct StageDefinition
+ public class StageDefinition
{
///
/// The number of s which this stage contains.
///
- public int Columns;
+ public readonly int Columns;
+
+ public StageDefinition(int columns)
+ {
+ if (columns < 1)
+ throw new ArgumentException("Column count must be above zero.", nameof(columns));
+
+ Columns = columns;
+ }
///
/// Whether the column index is a special column for this stage.
///
/// The 0-based column index.
/// Whether the column is a special column.
- public readonly bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2;
-
- ///
- /// Get the type of column given a column index.
- ///
- /// The 0-based column index.
- /// The type of the column.
- public readonly ColumnType GetTypeOfColumn(int column)
- {
- if (IsSpecialColumn(column))
- return ColumnType.Special;
-
- int distanceToEdge = Math.Min(column, (Columns - 1) - column);
- return distanceToEdge % 2 == 0 ? ColumnType.Odd : ColumnType.Even;
- }
+ public bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2;
}
}
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
index a925e7c0ac..440dec82af 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
- scoreAccuracy = customAccuracy;
+ scoreAccuracy = calculateCustomAccuracy();
// Arbitrary initial value for scaling pp in order to standardize distributions across game modes.
// The specific number has no intrinsic meaning and can be adjusted as needed.
@@ -73,6 +73,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty
///
/// Accuracy used to weight judgements independently from the score's actual accuracy.
///
- private double customAccuracy => (countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50) / (totalHits * 320);
+ private double calculateCustomAccuracy()
+ {
+ if (totalHits == 0)
+ return 0;
+
+ return (countPerfect * 320 + countGreat * 300 + countGood * 200 + countOk * 100 + countMeh * 50) / (totalHits * 320);
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs
index ad75afff8e..f438d6497c 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs
@@ -33,5 +33,7 @@ namespace osu.Game.Rulesets.Mania.Edit
}
protected override SelectionHandler CreateSelectionHandler() => new ManiaSelectionHandler();
+
+ protected sealed override DragBox CreateDragBox() => new ScrollingDragBox(Composer.Playfield);
}
}
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index 061dedb07a..6162184c9a 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -26,6 +26,8 @@ using osu.Game.Rulesets.Mania.Edit.Setup;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Mania.Scoring;
+using osu.Game.Rulesets.Mania.Skinning.Argon;
+using osu.Game.Rulesets.Mania.Skinning.Default;
using osu.Game.Rulesets.Mania.Skinning.Legacy;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
@@ -66,6 +68,15 @@ namespace osu.Game.Rulesets.Mania
{
switch (skin)
{
+ case TrianglesSkin:
+ return new ManiaTrianglesSkinTransformer(skin, beatmap);
+
+ case ArgonSkin:
+ return new ManiaArgonSkinTransformer(skin, beatmap);
+
+ case DefaultLegacySkin:
+ return new ManiaClassicSkinTransformer(skin, beatmap);
+
case LegacySkin:
return new ManiaLegacySkinTransformer(skin, beatmap);
}
diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs
index 21b362df00..f05edb4677 100644
--- a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs
+++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs
@@ -3,29 +3,19 @@
#nullable disable
-using osu.Game.Rulesets.Mania.Beatmaps;
-using osu.Game.Rulesets.Mania.UI;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania
{
public class ManiaSkinComponent : GameplaySkinComponent
{
- ///
- /// The intended for this component.
- /// May be null if the component is not a direct member of a .
- ///
- public readonly StageDefinition? StageDefinition;
-
///
/// Creates a new .
///
/// The component.
- /// The intended for this component. May be null if the component is not a direct member of a .
- public ManiaSkinComponent(ManiaSkinComponents component, StageDefinition? stageDefinition = null)
+ public ManiaSkinComponent(ManiaSkinComponents component)
: base(component)
{
- StageDefinition = stageDefinition;
}
protected override string RulesetPrefix => ManiaRuleset.SHORT_NAME;
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
index 6020348938..a607ed572d 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
@@ -54,10 +54,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
}
}
- protected override void UpdateInitialTransforms()
- {
- }
-
protected override void UpdateStartTimeStateTransforms() => this.FadeOut(150);
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index 19792086a7..48647f9f5f 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -4,12 +4,14 @@
#nullable disable
using System;
+using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
+using osu.Game.Audio;
using osu.Game.Rulesets.Mania.Skinning.Default;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
@@ -38,6 +40,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
private Container tailContainer;
private Container tickContainer;
+ private PausableSkinnableSound slidingSample;
+
///
/// Contains the size of the hold note covering the whole head/tail bounds. The size of this container changes as the hold note is being pressed.
///
@@ -108,6 +112,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
},
tickContainer = new Container { RelativeSizeAxes = Axes.Both },
tailContainer = new Container { RelativeSizeAxes = Axes.Both },
+ slidingSample = new PausableSkinnableSound { Looping = true }
});
maskedContents.AddRange(new[]
@@ -118,6 +123,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
});
}
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ isHitting.BindValueChanged(updateSlidingSample, true);
+ }
+
protected override void OnApply()
{
base.OnApply();
@@ -322,5 +334,38 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
HoldStartTime = null;
isHitting.Value = false;
}
+
+ protected override void LoadSamples()
+ {
+ // Note: base.LoadSamples() isn't called since the slider plays the tail's hitsounds for the time being.
+
+ if (HitObject.SampleControlPoint == null)
+ {
+ throw new InvalidOperationException($"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}."
+ + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
+ }
+
+ slidingSample.Samples = HitObject.CreateSlidingSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray();
+ }
+
+ public override void StopAllSamples()
+ {
+ base.StopAllSamples();
+ slidingSample?.Stop();
+ }
+
+ private void updateSlidingSample(ValueChangedEvent tracking)
+ {
+ if (tracking.NewValue)
+ slidingSample?.Play();
+ else
+ slidingSample?.Stop();
+ }
+
+ protected override void OnFree()
+ {
+ slidingSample.Samples = null;
+ base.OnFree();
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs
index d374e935ec..ac646ea427 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs
@@ -30,20 +30,15 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public bool UpdateResult() => base.UpdateResult(true);
- protected override void UpdateInitialTransforms()
- {
- base.UpdateInitialTransforms();
-
- // This hitobject should never expire, so this is just a safe maximum.
- LifetimeEnd = LifetimeStart + 30000;
- }
-
protected override void UpdateHitStateTransforms(ArmedState state)
{
// suppress the base call explicitly.
// the hold note head should never change its visual state on its own due to the "freezing" mechanic
// (when hit, it remains visible in place at the judgement line; when dropped, it will scroll past the line).
// it will be hidden along with its parenting hold note when required.
+
+ // Set `LifetimeEnd` explicitly to a non-`double.MaxValue` because otherwise this DHO is automatically expired.
+ LifetimeEnd = double.PositiveInfinity;
}
public override bool OnPressed(KeyBindingPressEvent e) => 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
index a7bdcd047e..3084f71be2 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public void UpdateResult() => base.UpdateResult(true);
- protected override double MaximumJudgementOffset => base.MaximumJudgementOffset * release_window_lenience;
+ public override double MaximumJudgementOffset => base.MaximumJudgementOffset * release_window_lenience;
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
index bcc10ab7bc..73dc937a00 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
@@ -23,10 +23,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
protected readonly IBindable Direction = new Bindable();
- // Leaving the default (10s) makes hitobjects not appear, as this offset is used for the initial state transforms.
- // Calculated as DrawableManiaRuleset.MAX_TIME_RANGE + some additional allowance for velocity < 1.
- protected override double InitialLifetimeOffset => 30000;
-
[Resolved(canBeNull: true)]
private ManiaPlayfield playfield { get; set; }
@@ -69,22 +65,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
Direction.BindValueChanged(OnDirectionChanged, true);
}
- protected override void OnApply()
- {
- base.OnApply();
-
- if (ParentHitObject != null)
- AccentColour.BindTo(ParentHitObject.AccentColour);
- }
-
- protected override void OnFree()
- {
- base.OnFree();
-
- if (ParentHitObject != null)
- AccentColour.UnbindFrom(ParentHitObject.AccentColour);
- }
-
protected virtual void OnDirectionChanged(ValueChangedEvent e)
{
Anchor = Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonColumnBackground.cs
similarity index 50%
rename from osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
rename to osu.Game.Rulesets.Mania/Skinning/Argon/ArgonColumnBackground.cs
index 5bd2d3ab48..598a765d3c 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonColumnBackground.cs
@@ -1,8 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
@@ -12,26 +10,38 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
-using osu.Game.Graphics;
+using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK.Graphics;
-namespace osu.Game.Rulesets.Mania.UI.Components
+namespace osu.Game.Rulesets.Mania.Skinning.Argon
{
- public class ColumnBackground : CompositeDrawable, IKeyBindingHandler, IHasAccentColour
+ public class ArgonColumnBackground : CompositeDrawable, IKeyBindingHandler
{
- private readonly IBindable action = new Bindable();
-
- private Box background;
- private Box backgroundOverlay;
-
private readonly IBindable direction = new Bindable();
- [BackgroundDependencyLoader]
- private void load(IBindable action, IScrollingInfo scrollingInfo)
- {
- this.action.BindTo(action);
+ private Color4 brightColour;
+ private Color4 dimColour;
+ private Box background = null!;
+ private Box backgroundOverlay = null!;
+
+ [Resolved]
+ private Column column { get; set; } = null!;
+
+ private Bindable accentColour = null!;
+
+ public ArgonColumnBackground()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ Masking = true;
+ CornerRadius = ArgonNotePiece.CORNER_RADIUS;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(IScrollingInfo scrollingInfo)
+ {
InternalChildren = new[]
{
background = new Box
@@ -49,61 +59,42 @@ namespace osu.Game.Rulesets.Mania.UI.Components
}
};
- direction.BindTo(scrollingInfo.Direction);
- direction.BindValueChanged(dir =>
+ accentColour = column.AccentColour.GetBoundCopy();
+ accentColour.BindValueChanged(colour =>
{
- backgroundOverlay.Anchor = backgroundOverlay.Origin = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
- updateColours();
+ background.Colour = colour.NewValue.Darken(3).Opacity(0.8f);
+ brightColour = colour.NewValue.Opacity(0.6f);
+ dimColour = colour.NewValue.Opacity(0);
}, true);
+
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(onDirectionChanged, true);
}
- protected override void LoadComplete()
+ private void onDirectionChanged(ValueChangedEvent direction)
{
- base.LoadComplete();
- updateColours();
- }
-
- private Color4 accentColour;
-
- public Color4 AccentColour
- {
- get => accentColour;
- set
+ if (direction.NewValue == ScrollingDirection.Up)
{
- if (accentColour == value)
- return;
-
- accentColour = value;
-
- updateColours();
+ backgroundOverlay.Anchor = backgroundOverlay.Origin = Anchor.TopLeft;
+ backgroundOverlay.Colour = ColourInfo.GradientVertical(brightColour, dimColour);
+ }
+ else
+ {
+ backgroundOverlay.Anchor = backgroundOverlay.Origin = Anchor.BottomLeft;
+ backgroundOverlay.Colour = ColourInfo.GradientVertical(dimColour, brightColour);
}
- }
-
- private void updateColours()
- {
- if (!IsLoaded)
- return;
-
- background.Colour = AccentColour.Darken(5);
-
- var brightPoint = AccentColour.Opacity(0.6f);
- var dimPoint = AccentColour.Opacity(0);
-
- backgroundOverlay.Colour = ColourInfo.GradientVertical(
- direction.Value == ScrollingDirection.Up ? brightPoint : dimPoint,
- direction.Value == ScrollingDirection.Up ? dimPoint : brightPoint);
}
public bool OnPressed(KeyBindingPressEvent e)
{
- if (e.Action == action.Value)
+ if (e.Action == column.Action.Value)
backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint);
return false;
}
public void OnReleased(KeyBindingReleaseEvent e)
{
- if (e.Action == action.Value)
+ if (e.Action == column.Action.Value)
backgroundOverlay.FadeTo(0, 250, Easing.OutQuint);
}
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitExplosion.cs
new file mode 100644
index 0000000000..af179d5580
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitExplosion.cs
@@ -0,0 +1,97 @@
+// 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.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.UI.Scrolling;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Skinning.Argon
+{
+ public class ArgonHitExplosion : CompositeDrawable, IHitExplosion
+ {
+ public override bool RemoveWhenNotAlive => true;
+
+ [Resolved]
+ private Column column { get; set; } = null!;
+
+ private readonly IBindable direction = new Bindable();
+
+ private Container largeFaint = null!;
+
+ private Bindable accentColour = null!;
+
+ public ArgonHitExplosion()
+ {
+ Origin = Anchor.Centre;
+
+ RelativeSizeAxes = Axes.X;
+ Height = ArgonNotePiece.NOTE_HEIGHT;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(IScrollingInfo scrollingInfo)
+ {
+ InternalChildren = new Drawable[]
+ {
+ largeFaint = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ CornerRadius = ArgonNotePiece.CORNER_RADIUS,
+ Blending = BlendingParameters.Additive,
+ Child = new Box
+ {
+ Colour = Color4.White,
+ RelativeSizeAxes = Axes.Both,
+ },
+ },
+ };
+
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(onDirectionChanged, true);
+
+ accentColour = column.AccentColour.GetBoundCopy();
+ accentColour.BindValueChanged(colour =>
+ {
+ largeFaint.Colour = Interpolation.ValueAt(0.8f, colour.NewValue, Color4.White, 0, 1);
+
+ largeFaint.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = colour.NewValue,
+ Roundness = 40,
+ Radius = 60,
+ };
+ }, true);
+ }
+
+ private void onDirectionChanged(ValueChangedEvent direction)
+ {
+ if (direction.NewValue == ScrollingDirection.Up)
+ {
+ Anchor = Anchor.TopCentre;
+ Y = ArgonNotePiece.NOTE_HEIGHT / 2;
+ }
+ else
+ {
+ Anchor = Anchor.BottomCentre;
+ Y = -ArgonNotePiece.NOTE_HEIGHT / 2;
+ }
+ }
+
+ public void Animate(JudgementResult result)
+ {
+ this.FadeOutFromOne(PoolableHitExplosion.DURATION, Easing.Out);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitTarget.cs
new file mode 100644
index 0000000000..9e449623d5
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitTarget.cs
@@ -0,0 +1,47 @@
+// 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.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.UI.Scrolling;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Skinning.Argon
+{
+ public class ArgonHitTarget : CompositeDrawable
+ {
+ private readonly IBindable direction = new Bindable();
+
+ [BackgroundDependencyLoader]
+ private void load(IScrollingInfo scrollingInfo)
+ {
+ RelativeSizeAxes = Axes.X;
+ Height = ArgonNotePiece.NOTE_HEIGHT;
+
+ Masking = true;
+ CornerRadius = ArgonNotePiece.CORNER_RADIUS;
+
+ InternalChildren = new[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0.3f,
+ Blending = BlendingParameters.Additive,
+ Colour = Color4.White
+ },
+ };
+
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(onDirectionChanged, true);
+ }
+
+ private void onDirectionChanged(ValueChangedEvent direction)
+ {
+ Anchor = Origin = direction.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldBodyPiece.cs
new file mode 100644
index 0000000000..757190c4ae
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldBodyPiece.cs
@@ -0,0 +1,97 @@
+// 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.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Mania.Skinning.Default;
+using osu.Game.Rulesets.Objects.Drawables;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Skinning.Argon
+{
+ ///
+ /// Represents length-wise portion of a hold note.
+ ///
+ public class ArgonHoldBodyPiece : CompositeDrawable, IHoldNoteBody
+ {
+ protected readonly Bindable AccentColour = new Bindable();
+ protected readonly IBindable IsHitting = new Bindable();
+
+ private Drawable background = null!;
+ private Box foreground = null!;
+
+ public ArgonHoldBodyPiece()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ // Without this, the width of the body will be slightly larger than the head/tail.
+ Masking = true;
+ CornerRadius = ArgonNotePiece.CORNER_RADIUS;
+ Blending = BlendingParameters.Additive;
+ }
+
+ [BackgroundDependencyLoader(true)]
+ private void load(DrawableHitObject? drawableObject)
+ {
+ InternalChildren = new[]
+ {
+ background = new Box { RelativeSizeAxes = Axes.Both },
+ foreground = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Blending = BlendingParameters.Additive,
+ Alpha = 0,
+ },
+ };
+
+ if (drawableObject != null)
+ {
+ var holdNote = (DrawableHoldNote)drawableObject;
+
+ AccentColour.BindTo(holdNote.AccentColour);
+ IsHitting.BindTo(holdNote.IsHitting);
+ }
+
+ AccentColour.BindValueChanged(colour =>
+ {
+ background.Colour = colour.NewValue.Darken(1.2f);
+ foreground.Colour = colour.NewValue.Opacity(0.2f);
+ }, true);
+
+ IsHitting.BindValueChanged(hitting =>
+ {
+ const float animation_length = 50;
+
+ foreground.ClearTransforms();
+
+ if (hitting.NewValue)
+ {
+ // wait for the next sync point
+ double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
+
+ using (foreground.BeginDelayedSequence(synchronisedOffset))
+ {
+ foreground.FadeTo(1, animation_length).Then()
+ .FadeTo(0.5f, animation_length)
+ .Loop();
+ }
+ }
+ else
+ {
+ foreground.FadeOut(animation_length);
+ }
+ });
+ }
+
+ public void Recycle()
+ {
+ foreground.ClearTransforms();
+ foreground.Alpha = 0;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs
new file mode 100644
index 0000000000..e1068c6cd8
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs
@@ -0,0 +1,91 @@
+// 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.Allocation;
+using osu.Framework.Bindables;
+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.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.UI.Scrolling;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Skinning.Argon
+{
+ internal class ArgonHoldNoteTailPiece : CompositeDrawable
+ {
+ private readonly IBindable direction = new Bindable();
+ private readonly IBindable accentColour = new Bindable();
+
+ private readonly Box colouredBox;
+ private readonly Box shadow;
+
+ public ArgonHoldNoteTailPiece()
+ {
+ RelativeSizeAxes = Axes.X;
+ Height = ArgonNotePiece.NOTE_HEIGHT;
+
+ CornerRadius = ArgonNotePiece.CORNER_RADIUS;
+ Masking = true;
+
+ InternalChildren = new Drawable[]
+ {
+ shadow = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Height = 0.82f,
+ Masking = true,
+ CornerRadius = ArgonNotePiece.CORNER_RADIUS,
+ Children = new Drawable[]
+ {
+ colouredBox = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ }
+ }
+ },
+ new Circle
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = ArgonNotePiece.CORNER_RADIUS * 2,
+ },
+ };
+ }
+
+ [BackgroundDependencyLoader(true)]
+ private void load(IScrollingInfo scrollingInfo, DrawableHitObject? drawableObject)
+ {
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(onDirectionChanged, true);
+
+ if (drawableObject != null)
+ {
+ accentColour.BindTo(drawableObject.AccentColour);
+ accentColour.BindValueChanged(onAccentChanged, true);
+ }
+ }
+
+ private void onDirectionChanged(ValueChangedEvent direction)
+ {
+ colouredBox.Anchor = colouredBox.Origin = direction.NewValue == ScrollingDirection.Up
+ ? Anchor.TopCentre
+ : Anchor.BottomCentre;
+ }
+
+ private void onAccentChanged(ValueChangedEvent accent)
+ {
+ colouredBox.Colour = ColourInfo.GradientVertical(
+ accent.NewValue,
+ accent.NewValue.Darken(0.1f)
+ );
+
+ shadow.Colour = accent.NewValue.Darken(0.5f);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs
new file mode 100644
index 0000000000..e7dfec256d
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs
@@ -0,0 +1,193 @@
+// 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.Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Utils;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Skinning.Argon
+{
+ public class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement
+ {
+ protected readonly HitResult Result;
+
+ protected SpriteText JudgementText { get; private set; } = null!;
+
+ private RingExplosion? ringExplosion;
+
+ [Resolved]
+ private OsuColour colours { get; set; } = null!;
+
+ public ArgonJudgementPiece(HitResult result)
+ {
+ Result = result;
+ Origin = Anchor.Centre;
+ Y = 160;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ AutoSizeAxes = Axes.Both;
+
+ InternalChildren = new Drawable[]
+ {
+ JudgementText = new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Text = Result.GetDescription().ToUpperInvariant(),
+ Colour = colours.ForHitResult(Result),
+ Blending = BlendingParameters.Additive,
+ Spacing = new Vector2(10, 0),
+ Font = OsuFont.Default.With(size: 28, weight: FontWeight.Regular),
+ },
+ };
+
+ if (Result.IsHit())
+ {
+ AddInternal(ringExplosion = new RingExplosion(Result)
+ {
+ Colour = colours.ForHitResult(Result),
+ });
+ }
+ }
+
+ ///
+ /// Plays the default animation for this judgement piece.
+ ///
+ ///
+ /// The base implementation only handles fade (for all result types) and misses.
+ /// Individual rulesets are recommended to implement their appropriate hit animations.
+ ///
+ public virtual void PlayAnimation()
+ {
+ switch (Result)
+ {
+ default:
+ JudgementText
+ .ScaleTo(Vector2.One)
+ .ScaleTo(new Vector2(1.4f), 1800, Easing.OutQuint);
+ break;
+
+ case HitResult.Miss:
+ this.ScaleTo(1.6f);
+ this.ScaleTo(1, 100, Easing.In);
+
+ this.MoveTo(Vector2.Zero);
+ this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
+
+ this.RotateTo(0);
+ this.RotateTo(40, 800, Easing.InQuint);
+ break;
+ }
+
+ this.FadeOutFromOne(800);
+
+ ringExplosion?.PlayAnimation();
+ }
+
+ public Drawable? GetAboveHitObjectsProxiedContent() => null;
+
+ private class RingExplosion : CompositeDrawable
+ {
+ private readonly float travel = 52;
+
+ public RingExplosion(HitResult result)
+ {
+ const float thickness = 4;
+
+ const float small_size = 9;
+ const float large_size = 14;
+
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+
+ Blending = BlendingParameters.Additive;
+
+ int countSmall = 0;
+ int countLarge = 0;
+
+ switch (result)
+ {
+ case HitResult.Meh:
+ countSmall = 3;
+ travel *= 0.3f;
+ break;
+
+ case HitResult.Ok:
+ case HitResult.Good:
+ countSmall = 4;
+ travel *= 0.6f;
+ break;
+
+ case HitResult.Great:
+ case HitResult.Perfect:
+ countSmall = 4;
+ countLarge = 4;
+ break;
+ }
+
+ for (int i = 0; i < countSmall; i++)
+ AddInternal(new RingPiece(thickness) { Size = new Vector2(small_size) });
+
+ for (int i = 0; i < countLarge; i++)
+ AddInternal(new RingPiece(thickness) { Size = new Vector2(large_size) });
+ }
+
+ public void PlayAnimation()
+ {
+ foreach (var c in InternalChildren)
+ {
+ const float start_position_ratio = 0.3f;
+
+ float direction = RNG.NextSingle(0, 360);
+ float distance = RNG.NextSingle(travel / 2, travel);
+
+ c.MoveTo(new Vector2(
+ MathF.Cos(direction) * distance * start_position_ratio,
+ MathF.Sin(direction) * distance * start_position_ratio
+ ));
+
+ c.MoveTo(new Vector2(
+ MathF.Cos(direction) * distance,
+ MathF.Sin(direction) * distance
+ ), 600, Easing.OutQuint);
+ }
+
+ this.FadeOutFromOne(1000, Easing.OutQuint);
+ }
+
+ public class RingPiece : CircularContainer
+ {
+ public RingPiece(float thickness = 9)
+ {
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+
+ Masking = true;
+ BorderThickness = thickness;
+ BorderColour = Color4.White;
+
+ Child = new Box
+ {
+ AlwaysPresent = true,
+ Alpha = 0,
+ RelativeSizeAxes = Axes.Both
+ };
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs
new file mode 100644
index 0000000000..7670c9bdf2
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs
@@ -0,0 +1,272 @@
+// 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.Allocation;
+using osu.Framework.Bindables;
+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.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
+using osu.Framework.Utils;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.UI.Scrolling;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Skinning.Argon
+{
+ public class ArgonKeyArea : CompositeDrawable, IKeyBindingHandler
+ {
+ private readonly IBindable direction = new Bindable();
+
+ private Container directionContainer = null!;
+ private Drawable background = null!;
+
+ private Circle hitTargetLine = null!;
+
+ private Container bottomIcon = null!;
+ private CircularContainer topIcon = null!;
+
+ private Bindable accentColour = null!;
+
+ [Resolved]
+ private Column column { get; set; } = null!;
+
+ public ArgonKeyArea()
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(IScrollingInfo scrollingInfo)
+ {
+ const float icon_circle_size = 8;
+ const float icon_spacing = 7;
+ const float icon_vertical_offset = -30;
+
+ InternalChild = directionContainer = new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ // Ensure the area is tall enough to put the target line in the correct location.
+ // This is to also allow the main background component to overlap the target line
+ // and avoid an inner corner radius being shown below the target line.
+ Height = Stage.HIT_TARGET_POSITION + ArgonNotePiece.CORNER_RADIUS * 2,
+ Children = new[]
+ {
+ new Container
+ {
+ Masking = true,
+ RelativeSizeAxes = Axes.Both,
+ CornerRadius = ArgonNotePiece.CORNER_RADIUS,
+ Child = background = new Box
+ {
+ Name = "Key gradient",
+ Alpha = 0,
+ RelativeSizeAxes = Axes.Both,
+ },
+ },
+ hitTargetLine = new Circle
+ {
+ RelativeSizeAxes = Axes.X,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Colour = OsuColour.Gray(196 / 255f),
+ Height = ArgonNotePiece.CORNER_RADIUS * 2,
+ Masking = true,
+ EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow },
+ },
+ new Container
+ {
+ Name = "Icons",
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Children = new Drawable[]
+ {
+ bottomIcon = new Container
+ {
+ AutoSizeAxes = Axes.Both,
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.Centre,
+ Blending = BlendingParameters.Additive,
+ Y = icon_vertical_offset,
+ Children = new[]
+ {
+ new Circle
+ {
+ Size = new Vector2(icon_circle_size),
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.Centre,
+ EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow },
+ },
+ new Circle
+ {
+ X = -icon_spacing,
+ Y = icon_spacing * 1.2f,
+ Size = new Vector2(icon_circle_size),
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.Centre,
+ EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow },
+ },
+ new Circle
+ {
+ X = icon_spacing,
+ Y = icon_spacing * 1.2f,
+ Size = new Vector2(icon_circle_size),
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.Centre,
+ EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow },
+ },
+ }
+ },
+ topIcon = new CircularContainer
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.Centre,
+ Y = -icon_vertical_offset,
+ Size = new Vector2(22, 14),
+ Masking = true,
+ BorderThickness = 4,
+ BorderColour = Color4.White,
+ EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow },
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ AlwaysPresent = true,
+ },
+ },
+ }
+ }
+ },
+ }
+ };
+
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(onDirectionChanged, true);
+
+ accentColour = column.AccentColour.GetBoundCopy();
+ accentColour.BindValueChanged(colour =>
+ {
+ background.Colour = colour.NewValue.Darken(0.2f);
+ bottomIcon.Colour = colour.NewValue;
+ },
+ true);
+
+ // Yes, proxy everything.
+ column.TopLevelContainer.Add(CreateProxy());
+ }
+
+ private void onDirectionChanged(ValueChangedEvent direction)
+ {
+ switch (direction.NewValue)
+ {
+ case ScrollingDirection.Up:
+ directionContainer.Scale = new Vector2(1, -1);
+ directionContainer.Anchor = Anchor.TopLeft;
+ directionContainer.Origin = Anchor.BottomLeft;
+ break;
+
+ case ScrollingDirection.Down:
+ directionContainer.Scale = new Vector2(1, 1);
+ directionContainer.Anchor = Anchor.BottomLeft;
+ directionContainer.Origin = Anchor.BottomLeft;
+ break;
+ }
+ }
+
+ public bool OnPressed(KeyBindingPressEvent e)
+ {
+ if (e.Action != column.Action.Value) return false;
+
+ const double lighting_fade_in_duration = 70;
+ Color4 lightingColour = getLightingColour();
+
+ background
+ .FlashColour(accentColour.Value.Lighten(0.8f), 200, Easing.OutQuint)
+ .FadeTo(1, lighting_fade_in_duration, Easing.OutQuint)
+ .Then()
+ .FadeTo(0.8f, 500);
+
+ hitTargetLine.FadeColour(Color4.White, lighting_fade_in_duration, Easing.OutQuint);
+ hitTargetLine.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = lightingColour.Opacity(0.4f),
+ Radius = 20,
+ }, lighting_fade_in_duration, Easing.OutQuint);
+
+ topIcon.ScaleTo(0.9f, lighting_fade_in_duration, Easing.OutQuint);
+ topIcon.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = lightingColour.Opacity(0.1f),
+ Radius = 20,
+ }, lighting_fade_in_duration, Easing.OutQuint);
+
+ bottomIcon.FadeColour(Color4.White, lighting_fade_in_duration, Easing.OutQuint);
+
+ foreach (var circle in bottomIcon)
+ {
+ circle.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = lightingColour.Opacity(0.2f),
+ Radius = 60,
+ }, lighting_fade_in_duration, Easing.OutQuint);
+ }
+
+ return false;
+ }
+
+ public void OnReleased(KeyBindingReleaseEvent e)
+ {
+ if (e.Action != column.Action.Value) return;
+
+ const double lighting_fade_out_duration = 800;
+
+ Color4 lightingColour = getLightingColour().Opacity(0);
+
+ // background fades out faster than lighting elements to give better definition to the player.
+ background.FadeTo(0.3f, 50, Easing.OutQuint)
+ .Then()
+ .FadeOut(lighting_fade_out_duration, Easing.OutQuint);
+
+ topIcon.ScaleTo(1f, 200, Easing.OutQuint);
+ topIcon.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = lightingColour,
+ Radius = 20,
+ }, lighting_fade_out_duration, Easing.OutQuint);
+
+ hitTargetLine.FadeColour(OsuColour.Gray(196 / 255f), lighting_fade_out_duration, Easing.OutQuint);
+ hitTargetLine.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = lightingColour,
+ Radius = 25,
+ }, lighting_fade_out_duration, Easing.OutQuint);
+
+ bottomIcon.FadeColour(accentColour.Value, lighting_fade_out_duration, Easing.OutQuint);
+
+ foreach (var circle in bottomIcon)
+ {
+ circle.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = lightingColour,
+ Radius = 30,
+ }, lighting_fade_out_duration, Easing.OutQuint);
+ }
+ }
+
+ private Color4 getLightingColour() => Interpolation.ValueAt(0.2f, accentColour.Value, Color4.White, 0, 1);
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonNotePiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonNotePiece.cs
new file mode 100644
index 0000000000..454a6b012b
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonNotePiece.cs
@@ -0,0 +1,110 @@
+// 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.Allocation;
+using osu.Framework.Bindables;
+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.Framework.Graphics.Sprites;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.UI.Scrolling;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Skinning.Argon
+{
+ internal class ArgonNotePiece : CompositeDrawable
+ {
+ public const float NOTE_HEIGHT = 42;
+
+ public const float CORNER_RADIUS = 3.4f;
+
+ private readonly IBindable direction = new Bindable();
+ private readonly IBindable accentColour = new Bindable();
+
+ private readonly Box colouredBox;
+ private readonly Box shadow;
+
+ public ArgonNotePiece()
+ {
+ RelativeSizeAxes = Axes.X;
+ Height = NOTE_HEIGHT;
+
+ CornerRadius = CORNER_RADIUS;
+ Masking = true;
+
+ InternalChildren = new Drawable[]
+ {
+ shadow = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ new Container
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ RelativeSizeAxes = Axes.Both,
+ Height = 0.82f,
+ Masking = true,
+ CornerRadius = CORNER_RADIUS,
+ Children = new Drawable[]
+ {
+ colouredBox = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ }
+ }
+ },
+ new Circle
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ RelativeSizeAxes = Axes.X,
+ Height = CORNER_RADIUS * 2,
+ },
+ new SpriteIcon
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Y = 4,
+ Icon = FontAwesome.Solid.AngleDown,
+ Size = new Vector2(20),
+ Scale = new Vector2(1, 0.7f)
+ }
+ };
+ }
+
+ [BackgroundDependencyLoader(true)]
+ private void load(IScrollingInfo scrollingInfo, DrawableHitObject? drawableObject)
+ {
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(onDirectionChanged, true);
+
+ if (drawableObject != null)
+ {
+ accentColour.BindTo(drawableObject.AccentColour);
+ accentColour.BindValueChanged(onAccentChanged, true);
+ }
+ }
+
+ private void onDirectionChanged(ValueChangedEvent direction)
+ {
+ colouredBox.Anchor = colouredBox.Origin = direction.NewValue == ScrollingDirection.Up
+ ? Anchor.TopCentre
+ : Anchor.BottomCentre;
+ }
+
+ private void onAccentChanged(ValueChangedEvent accent)
+ {
+ colouredBox.Colour = ColourInfo.GradientVertical(
+ accent.NewValue.Lighten(0.1f),
+ accent.NewValue
+ );
+
+ shadow.Colour = accent.NewValue.Darken(0.5f);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonStageBackground.cs
new file mode 100644
index 0000000000..1881695b14
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonStageBackground.cs
@@ -0,0 +1,16 @@
+// 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;
+
+namespace osu.Game.Rulesets.Mania.Skinning.Argon
+{
+ public class ArgonStageBackground : CompositeDrawable
+ {
+ public ArgonStageBackground()
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs
new file mode 100644
index 0000000000..ae313e0b91
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs
@@ -0,0 +1,141 @@
+// 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.Graphics;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Skinning;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Skinning.Argon
+{
+ public class ManiaArgonSkinTransformer : SkinTransformer
+ {
+ private readonly ManiaBeatmap beatmap;
+
+ public ManiaArgonSkinTransformer(ISkin skin, IBeatmap beatmap)
+ : base(skin)
+ {
+ this.beatmap = (ManiaBeatmap)beatmap;
+ }
+
+ public override Drawable? GetDrawableComponent(ISkinComponent component)
+ {
+ switch (component)
+ {
+ case GameplaySkinComponent resultComponent:
+ return new ArgonJudgementPiece(resultComponent.Component);
+
+ case ManiaSkinComponent maniaComponent:
+ // TODO: Once everything is finalised, consider throwing UnsupportedSkinComponentException on missing entries.
+ switch (maniaComponent.Component)
+ {
+ case ManiaSkinComponents.StageBackground:
+ return new ArgonStageBackground();
+
+ case ManiaSkinComponents.ColumnBackground:
+ return new ArgonColumnBackground();
+
+ case ManiaSkinComponents.HoldNoteBody:
+ return new ArgonHoldBodyPiece();
+
+ case ManiaSkinComponents.HoldNoteTail:
+ return new ArgonHoldNoteTailPiece();
+
+ case ManiaSkinComponents.HoldNoteHead:
+ case ManiaSkinComponents.Note:
+ return new ArgonNotePiece();
+
+ case ManiaSkinComponents.HitTarget:
+ return new ArgonHitTarget();
+
+ case ManiaSkinComponents.KeyArea:
+ return new ArgonKeyArea();
+
+ case ManiaSkinComponents.HitExplosion:
+ return new ArgonHitExplosion();
+ }
+
+ break;
+ }
+
+ return base.GetDrawableComponent(component);
+ }
+
+ public override IBindable? GetConfig(TLookup lookup)
+ {
+ if (lookup is ManiaSkinConfigurationLookup maniaLookup)
+ {
+ int column = maniaLookup.ColumnIndex ?? 0;
+ var stage = beatmap.GetStageForColumnIndex(column);
+
+ switch (maniaLookup.Lookup)
+ {
+ case LegacyManiaSkinConfigurationLookups.ColumnSpacing:
+ return SkinUtils.As(new Bindable(2));
+
+ case LegacyManiaSkinConfigurationLookups.StagePaddingBottom:
+ case LegacyManiaSkinConfigurationLookups.StagePaddingTop:
+ return SkinUtils.As(new Bindable(30));
+
+ case LegacyManiaSkinConfigurationLookups.ColumnWidth:
+ return SkinUtils.As(new Bindable(
+ stage.IsSpecialColumn(column) ? 120 : 60
+ ));
+
+ case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour:
+
+ Color4 colour;
+
+ const int total_colours = 7;
+
+ if (stage.IsSpecialColumn(column))
+ colour = new Color4(159, 101, 255, 255);
+ else
+ {
+ switch (column % total_colours)
+ {
+ case 0:
+ colour = new Color4(240, 216, 0, 255);
+ break;
+
+ case 1:
+ colour = new Color4(240, 101, 0, 255);
+ break;
+
+ case 2:
+ colour = new Color4(240, 0, 130, 255);
+ break;
+
+ case 3:
+ colour = new Color4(192, 0, 240, 255);
+ break;
+
+ case 4:
+ colour = new Color4(0, 96, 240, 255);
+ break;
+
+ case 5:
+ colour = new Color4(0, 226, 240, 255);
+ break;
+
+ case 6:
+ colour = new Color4(0, 240, 96, 255);
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ return SkinUtils.As(new Bindable(colour));
+ }
+ }
+
+ return base.GetConfig(lookup);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Default/ManiaTrianglesSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Default/ManiaTrianglesSkinTransformer.cs
new file mode 100644
index 0000000000..eb51179cea
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/Default/ManiaTrianglesSkinTransformer.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;
+using osu.Framework.Bindables;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Skinning;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Skinning.Default
+{
+ public class ManiaTrianglesSkinTransformer : SkinTransformer
+ {
+ private readonly ManiaBeatmap beatmap;
+
+ public ManiaTrianglesSkinTransformer(ISkin skin, IBeatmap beatmap)
+ : base(skin)
+ {
+ this.beatmap = (ManiaBeatmap)beatmap;
+ }
+
+ private readonly Color4 colourEven = new Color4(6, 84, 0, 255);
+ private readonly Color4 colourOdd = new Color4(94, 0, 57, 255);
+ private readonly Color4 colourSpecial = new Color4(0, 48, 63, 255);
+
+ public override IBindable? GetConfig(TLookup lookup)
+ {
+ if (lookup is ManiaSkinConfigurationLookup maniaLookup)
+ {
+ switch (maniaLookup.Lookup)
+ {
+ case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour:
+ int column = maniaLookup.ColumnIndex ?? 0;
+
+ var stage = beatmap.GetStageForColumnIndex(column);
+
+ if (stage.IsSpecialColumn(column))
+ return SkinUtils.As(new Bindable(colourSpecial));
+
+ int distanceToEdge = Math.Min(column, (stage.Columns - 1) - column);
+ return SkinUtils.As(new Bindable(distanceToEdge % 2 == 0 ? colourOdd : colourEven));
+ }
+ }
+
+ return base.GetConfig(lookup);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaColumnElement.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaColumnElement.cs
index ab953ccfb9..e227c80845 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaColumnElement.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaColumnElement.cs
@@ -3,6 +3,7 @@
#nullable disable
+using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
@@ -20,6 +21,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
[Resolved]
protected Column Column { get; private set; }
+ [Resolved]
+ private StageDefinition stage { get; set; }
+
///
/// The column type identifier to use for texture lookups, in the case of no user-provided configuration.
///
@@ -28,19 +32,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
[BackgroundDependencyLoader]
private void load()
{
- switch (Column.ColumnType)
+ if (Column.IsSpecial)
+ FallbackColumnIndex = "S";
+ else
{
- case ColumnType.Special:
- FallbackColumnIndex = "S";
- break;
-
- case ColumnType.Odd:
- FallbackColumnIndex = "1";
- break;
-
- case ColumnType.Even:
- FallbackColumnIndex = "2";
- break;
+ int distanceToEdge = Math.Min(Column.Index, (stage.Columns - 1) - Column.Index);
+ FallbackColumnIndex = distanceToEdge % 2 == 0 ? "1" : "2";
}
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs
index 740ccbfe27..d039551cd7 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyStageBackground.cs
@@ -18,20 +18,17 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public class LegacyStageBackground : CompositeDrawable
{
- private readonly StageDefinition stageDefinition;
-
private Drawable leftSprite;
private Drawable rightSprite;
private ColumnFlow columnBackgrounds;
- public LegacyStageBackground(StageDefinition stageDefinition)
+ public LegacyStageBackground()
{
- this.stageDefinition = stageDefinition;
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
- private void load(ISkinSource skin)
+ private void load(ISkinSource skin, StageDefinition stageDefinition)
{
string leftImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.LeftStageImage)?.Value
?? "mania-stage-left";
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaClassicSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaClassicSkinTransformer.cs
new file mode 100644
index 0000000000..e57927897c
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaClassicSkinTransformer.cs
@@ -0,0 +1,38 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Bindables;
+using osu.Game.Beatmaps;
+using osu.Game.Skinning;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Skinning.Legacy
+{
+ public class ManiaClassicSkinTransformer : ManiaLegacySkinTransformer
+ {
+ public ManiaClassicSkinTransformer(ISkin skin, IBeatmap beatmap)
+ : base(skin, beatmap)
+ {
+ }
+
+ public override IBindable GetConfig(TLookup lookup)
+ {
+ if (lookup is ManiaSkinConfigurationLookup maniaLookup)
+ {
+ var baseLookup = base.GetConfig(lookup);
+
+ if (baseLookup != null)
+ return baseLookup;
+
+ // default provisioning.
+ switch (maniaLookup.Lookup)
+ {
+ case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour:
+ return SkinUtils.As(new Bindable(Color4.Black));
+ }
+ }
+
+ return base.GetConfig(lookup);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
index dd5baa8150..1d39721a2b 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
@@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -20,8 +19,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public class ManiaLegacySkinTransformer : LegacySkinTransformer
{
- private readonly ManiaBeatmap beatmap;
-
///
/// Mapping of to their corresponding
/// value.
@@ -60,6 +57,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
///
private readonly Lazy hasKeyTexture;
+ private readonly ManiaBeatmap beatmap;
+
public ManiaLegacySkinTransformer(ISkin skin, IBeatmap beatmap)
: base(skin)
{
@@ -113,8 +112,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
return new LegacyHitExplosion();
case ManiaSkinComponents.StageBackground:
- Debug.Assert(maniaComponent.StageDefinition != null);
- return new LegacyStageBackground(maniaComponent.StageDefinition.Value);
+ return new LegacyStageBackground();
case ManiaSkinComponents.StageForeground:
return new LegacyStageForeground();
@@ -151,7 +149,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
public override IBindable GetConfig(TLookup lookup)
{
if (lookup is ManiaSkinConfigurationLookup maniaLookup)
- return base.GetConfig(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.TargetColumn));
+ {
+ return base.GetConfig(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.ColumnIndex));
+ }
return base.GetConfig(lookup);
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigExtensions.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigExtensions.cs
index 4d0c321116..e22bf63049 100644
--- a/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigExtensions.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigExtensions.cs
@@ -15,9 +15,9 @@ namespace osu.Game.Rulesets.Mania.Skinning
///
/// The skin from which configuration is retrieved.
/// The value to retrieve.
- /// If not null, denotes the index of the column to which the entry applies.
- public static IBindable GetManiaSkinConfig(this ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null)
+ /// If not null, denotes the index of the column to which the entry applies.
+ public static IBindable GetManiaSkinConfig(this ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? columnIndex = null)
=> skin.GetConfig(
- new ManiaSkinConfigurationLookup(lookup, index));
+ new ManiaSkinConfigurationLookup(lookup, columnIndex));
}
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs
index e9005a3da0..59188f02f9 100644
--- a/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs
@@ -16,20 +16,21 @@ namespace osu.Game.Rulesets.Mania.Skinning
public readonly LegacyManiaSkinConfigurationLookups Lookup;
///
- /// The intended index for the configuration.
+ /// The column which is being looked up.
/// May be null if the configuration does not apply to a .
+ /// Note that this is the absolute index across all stages.
///
- public readonly int? TargetColumn;
+ public readonly int? ColumnIndex;
///
/// Creates a new .
///
/// The lookup value.
- /// The intended index for the configuration. May be null if the configuration does not apply to a .
- public ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups lookup, int? targetColumn = null)
+ /// The intended index for the configuration. May be null if the configuration does not apply to a .
+ public ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups lookup, int? columnIndex = null)
{
Lookup = lookup;
- TargetColumn = targetColumn;
+ ColumnIndex = columnIndex;
}
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index deb1b155b5..3d46bdaa7b 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.cs
@@ -3,30 +3,30 @@
#nullable disable
-using osuTK.Graphics;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Graphics;
-using osu.Game.Rulesets.Objects.Drawables;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
+using osu.Framework.Platform;
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Mania.Skinning;
using osu.Game.Rulesets.Mania.UI.Components;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
using osuTK;
-using osu.Game.Rulesets.Mania.Beatmaps;
-using osu.Game.Rulesets.Mania.Objects;
-using osu.Game.Rulesets.Mania.Objects.Drawables;
-using osu.Game.Rulesets.UI;
+using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.UI
{
[Cached]
- public class Column : ScrollingPlayfield, IKeyBindingHandler, IHasAccentColour
+ public class Column : ScrollingPlayfield, IKeyBindingHandler
{
public const float COLUMN_WIDTH = 80;
public const float SPECIAL_COLUMN_WIDTH = 70;
@@ -39,23 +39,46 @@ namespace osu.Game.Rulesets.Mania.UI
public readonly Bindable Action = new Bindable();
public readonly ColumnHitObjectArea HitObjectArea;
- internal readonly Container TopLevelContainer;
- private readonly DrawablePool hitExplosionPool;
+ internal readonly Container TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both };
+ private DrawablePool hitExplosionPool;
private readonly OrderedHitPolicy hitPolicy;
public Container UnderlayElements => HitObjectArea.UnderlayElements;
- private readonly GameplaySampleTriggerSource sampleTriggerSource;
+ private GameplaySampleTriggerSource sampleTriggerSource;
- public Column(int index)
+ ///
+ /// Whether this is a special (ie. scratch) column.
+ ///
+ public readonly bool IsSpecial;
+
+ public readonly Bindable AccentColour = new Bindable(Color4.Black);
+
+ public Column(int index, bool isSpecial)
{
Index = index;
+ IsSpecial = isSpecial;
RelativeSizeAxes = Axes.Y;
Width = COLUMN_WIDTH;
+ hitPolicy = new OrderedHitPolicy(HitObjectContainer);
+ HitObjectArea = new ColumnHitObjectArea(HitObjectContainer) { RelativeSizeAxes = Axes.Both };
+ }
+
+ [Resolved]
+ private ISkinSource skin { get; set; }
+
+ [BackgroundDependencyLoader]
+ private void load(GameHost host)
+ {
+ SkinnableDrawable keyArea;
+
+ skin.SourceChanged += onSourceChanged;
+ onSourceChanged();
+
Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground())
{
- RelativeSizeAxes = Axes.Both
+ RelativeSizeAxes = Axes.Both,
};
InternalChildren = new[]
@@ -64,17 +87,18 @@ namespace osu.Game.Rulesets.Mania.UI
sampleTriggerSource = new GameplaySampleTriggerSource(HitObjectContainer),
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
background.CreateProxy(),
- HitObjectArea = new ColumnHitObjectArea(HitObjectContainer) { RelativeSizeAxes = Axes.Both },
- new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
+ HitObjectArea,
+ keyArea = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
{
- RelativeSizeAxes = Axes.Both
+ RelativeSizeAxes = Axes.Both,
},
background,
- TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both },
+ TopLevelContainer,
new ColumnTouchInputArea(this)
};
- hitPolicy = new OrderedHitPolicy(HitObjectContainer);
+ applyGameWideClock(background);
+ applyGameWideClock(keyArea);
TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy());
@@ -83,20 +107,38 @@ namespace osu.Game.Rulesets.Mania.UI
RegisterPool(10, 50);
RegisterPool(10, 50);
RegisterPool(50, 250);
+
+ // Some elements don't handle rewind correctly and fixing them is non-trivial.
+ // In the future we need a better solution to this, but as a temporary work-around, give these components the game-wide
+ // clock so they don't need to worry about rewind.
+ // This only works because they handle OnPressed/OnReleased which results in a correct state while rewinding.
+ //
+ // This is kinda dodgy (and will cause weirdness when pausing gameplay) but is better than completely broken rewind.
+ void applyGameWideClock(Drawable drawable)
+ {
+ drawable.Clock = host.UpdateThread.Clock;
+ drawable.ProcessCustomClock = false;
+ }
+ }
+
+ private void onSourceChanged()
+ {
+ AccentColour.Value = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour, Index)?.Value ?? Color4.Black;
}
protected override void LoadComplete()
{
base.LoadComplete();
-
NewResult += OnNewResult;
}
- public ColumnType ColumnType { get; set; }
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
- public bool IsSpecial => ColumnType == ColumnType.Special;
-
- public Color4 AccentColour { get; set; }
+ if (skin != null)
+ skin.SourceChanged -= onSourceChanged;
+ }
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
@@ -111,7 +153,7 @@ namespace osu.Game.Rulesets.Mania.UI
DrawableManiaHitObject maniaObject = (DrawableManiaHitObject)drawableHitObject;
- maniaObject.AccentColour.Value = AccentColour;
+ maniaObject.AccentColour.BindTo(AccentColour);
maniaObject.CheckHittable = hitPolicy.IsHittable;
}
diff --git a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs
index 871ec9f1a3..9b3f6d7033 100644
--- a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs
+++ b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs
@@ -36,6 +36,8 @@ namespace osu.Game.Rulesets.Mania.UI
AutoSizeAxes = Axes.X;
+ Masking = true;
+
InternalChild = columns = new FillFlowContainer
{
RelativeSizeAxes = Axes.Y,
diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs
index 39d17db6be..3680e7ea0a 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs
@@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Mania.UI.Components
[Resolved]
private Column column { get; set; }
+ private Bindable accentColour;
+
public DefaultColumnBackground()
{
RelativeSizeAxes = Axes.Both;
@@ -55,9 +57,13 @@ namespace osu.Game.Rulesets.Mania.UI.Components
}
};
- background.Colour = column.AccentColour.Darken(5);
- brightColour = column.AccentColour.Opacity(0.6f);
- dimColour = column.AccentColour.Opacity(0);
+ accentColour = column.AccentColour.GetBoundCopy();
+ accentColour.BindValueChanged(colour =>
+ {
+ background.Colour = colour.NewValue.Darken(5);
+ brightColour = colour.NewValue.Opacity(0.6f);
+ dimColour = colour.NewValue.Opacity(0);
+ }, true);
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs
index 53fa86125f..97aa897782 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs
@@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Mania.UI.Components
private Container hitTargetLine;
private Drawable hitTargetBar;
+ private Bindable accentColour;
+
[Resolved]
private Column column { get; set; }
@@ -54,12 +56,16 @@ namespace osu.Game.Rulesets.Mania.UI.Components
},
};
- hitTargetLine.EdgeEffect = new EdgeEffectParameters
+ accentColour = column.AccentColour.GetBoundCopy();
+ accentColour.BindValueChanged(colour =>
{
- Type = EdgeEffectType.Glow,
- Radius = 5,
- Colour = column.AccentColour.Opacity(0.5f),
- };
+ hitTargetLine.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Radius = 5,
+ Colour = colour.NewValue.Opacity(0.5f),
+ };
+ }, true);
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
diff --git a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs
index 5a0fab2ff4..600c9feb73 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs
@@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Mania.UI.Components
private Container keyIcon;
private Drawable gradient;
+ private Bindable accentColour;
+
[Resolved]
private Column column { get; set; }
@@ -75,15 +77,19 @@ namespace osu.Game.Rulesets.Mania.UI.Components
}
};
- keyIcon.EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Radius = 5,
- Colour = column.AccentColour.Opacity(0.5f),
- };
-
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
+
+ accentColour = column.AccentColour.GetBoundCopy();
+ accentColour.BindValueChanged(colour =>
+ {
+ keyIcon.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Radius = 5,
+ Colour = colour.NewValue.Opacity(0.5f),
+ };
+ }, true);
}
private void onDirectionChanged(ValueChangedEvent direction)
diff --git a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs
index e83cd10d2d..59716ee3e2 100644
--- a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs
+++ b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs
@@ -32,6 +32,10 @@ namespace osu.Game.Rulesets.Mania.UI
private CircularContainer largeFaint;
private CircularContainer mainGlow1;
+ private CircularContainer mainGlow2;
+ private CircularContainer mainGlow3;
+
+ private Bindable accentColour;
public DefaultHitExplosion()
{
@@ -48,8 +52,6 @@ namespace osu.Game.Rulesets.Mania.UI
const float roundness = 80;
const float initial_height = 10;
- var colour = Interpolation.ValueAt(0.4f, column.AccentColour, Color4.White, 0, 1);
-
InternalChildren = new Drawable[]
{
largeFaint = new CircularContainer
@@ -61,13 +63,6 @@ namespace osu.Game.Rulesets.Mania.UI
// we want our size to be very small so the glow dominates it.
Size = new Vector2(default_large_faint_size),
Blending = BlendingParameters.Additive,
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = Interpolation.ValueAt(0.1f, column.AccentColour, Color4.White, 0, 1).Opacity(0.3f),
- Roundness = 160,
- Radius = 200,
- },
},
mainGlow1 = new CircularContainer
{
@@ -76,15 +71,8 @@ namespace osu.Game.Rulesets.Mania.UI
RelativeSizeAxes = Axes.Both,
Masking = true,
Blending = BlendingParameters.Additive,
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = Interpolation.ValueAt(0.6f, column.AccentColour, Color4.White, 0, 1),
- Roundness = 20,
- Radius = 50,
- },
},
- new CircularContainer
+ mainGlow2 = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -93,15 +81,8 @@ namespace osu.Game.Rulesets.Mania.UI
Size = new Vector2(0.01f, initial_height),
Blending = BlendingParameters.Additive,
Rotation = RNG.NextSingle(-angle_variance, angle_variance),
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = colour,
- Roundness = roundness,
- Radius = 40,
- },
},
- new CircularContainer
+ mainGlow3 = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -110,18 +91,44 @@ namespace osu.Game.Rulesets.Mania.UI
Size = new Vector2(0.01f, initial_height),
Blending = BlendingParameters.Additive,
Rotation = RNG.NextSingle(-angle_variance, angle_variance),
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = colour,
- Roundness = roundness,
- Radius = 40,
- },
}
};
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
+
+ accentColour = column.AccentColour.GetBoundCopy();
+ accentColour.BindValueChanged(colour =>
+ {
+ largeFaint.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.1f, colour.NewValue, Color4.White, 0, 1).Opacity(0.3f),
+ Roundness = 160,
+ Radius = 200,
+ };
+ mainGlow1.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.6f, colour.NewValue, Color4.White, 0, 1),
+ Roundness = 20,
+ Radius = 50,
+ };
+ mainGlow2.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.4f, colour.NewValue, Color4.White, 0, 1),
+ Roundness = roundness,
+ Radius = 40,
+ };
+ mainGlow3.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.4f, colour.NewValue, Color4.White, 0, 1),
+ Roundness = roundness,
+ Radius = 40,
+ };
+ }, true);
}
private void onDirectionChanged(ValueChangedEvent direction)
diff --git a/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs
index 28509d1f4e..a7b94f9f22 100644
--- a/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs
+++ b/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs
@@ -42,6 +42,8 @@ namespace osu.Game.Rulesets.Mania.UI
{
base.PrepareForUse();
+ LifetimeStart = Time.Current;
+
(skinnableExplosion?.Drawable as IHitExplosion)?.Animate(Result);
this.Delay(DURATION).Then().Expire();
diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs
index c578bbb703..1273cb3d32 100644
--- a/osu.Game.Rulesets.Mania/UI/Stage.cs
+++ b/osu.Game.Rulesets.Mania/UI/Stage.cs
@@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling;
@@ -12,6 +13,7 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Mania.Skinning;
using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
@@ -19,7 +21,6 @@ using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
using osuTK;
-using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.UI
{
@@ -28,6 +29,9 @@ namespace osu.Game.Rulesets.Mania.UI
///
public class Stage : ScrollingPlayfield
{
+ [Cached]
+ public readonly StageDefinition Definition;
+
public const float COLUMN_SPACING = 1;
public const float HIT_TARGET_POSITION = 110;
@@ -40,13 +44,6 @@ namespace osu.Game.Rulesets.Mania.UI
private readonly Drawable barLineContainer;
- private readonly Dictionary columnColours = new Dictionary
- {
- { ColumnType.Even, new Color4(6, 84, 0, 255) },
- { ColumnType.Odd, new Color4(94, 0, 57, 255) },
- { ColumnType.Special, new Color4(0, 48, 63, 255) }
- };
-
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Columns.Any(c => c.ReceivePositionalInputAt(screenSpacePos));
private readonly int firstColumnIndex;
@@ -54,6 +51,7 @@ namespace osu.Game.Rulesets.Mania.UI
public Stage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction)
{
this.firstColumnIndex = firstColumnIndex;
+ Definition = definition;
Name = "Stage";
@@ -75,7 +73,7 @@ namespace osu.Game.Rulesets.Mania.UI
AutoSizeAxes = Axes.X,
Children = new Drawable[]
{
- new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground, stageDefinition: definition), _ => new DefaultStageBackground())
+ new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground), _ => new DefaultStageBackground())
{
RelativeSizeAxes = Axes.Both
},
@@ -100,7 +98,7 @@ namespace osu.Game.Rulesets.Mania.UI
RelativeSizeAxes = Axes.Y,
}
},
- new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground, stageDefinition: definition), _ => null)
+ new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground), _ => null)
{
RelativeSizeAxes = Axes.Both
},
@@ -118,15 +116,13 @@ namespace osu.Game.Rulesets.Mania.UI
for (int i = 0; i < definition.Columns; i++)
{
- var columnType = definition.GetTypeOfColumn(i);
+ bool isSpecial = definition.IsSpecialColumn(i);
- var column = new Column(firstColumnIndex + i)
+ var column = new Column(firstColumnIndex + i, isSpecial)
{
RelativeSizeAxes = Axes.Both,
Width = 1,
- ColumnType = columnType,
- AccentColour = columnColours[columnType],
- Action = { Value = columnType == ColumnType.Special ? specialColumnStartAction++ : normalColumnStartAction++ }
+ Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ }
};
topLevelContainer.Add(column.TopLevelContainer.CreateProxy());
@@ -135,6 +131,37 @@ namespace osu.Game.Rulesets.Mania.UI
}
}
+ private ISkinSource currentSkin;
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ currentSkin = skin;
+
+ skin.SourceChanged += onSkinChanged;
+ onSkinChanged();
+ }
+
+ private void onSkinChanged()
+ {
+ float paddingTop = currentSkin.GetConfig(new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.StagePaddingTop))?.Value ?? 0;
+ float paddingBottom = currentSkin.GetConfig(new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.StagePaddingBottom))?.Value ?? 0;
+
+ Padding = new MarginPadding
+ {
+ Top = paddingTop,
+ Bottom = paddingBottom,
+ };
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (currentSkin != null)
+ currentSkin.SourceChanged -= onSkinChanged;
+ }
+
protected override void LoadComplete()
{
base.LoadComplete();
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs
index 51871dd9e5..0601dc6068 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs
@@ -148,6 +148,37 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
});
}
+ [Test]
+ public void TestFloatEdgeCaseConversion()
+ {
+ Slider slider = null;
+
+ AddStep("select first slider", () =>
+ {
+ slider = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider);
+ EditorClock.Seek(slider.StartTime);
+ EditorBeatmap.SelectedHitObjects.Add(slider);
+ });
+
+ AddStep("change to these specific circumstances", () =>
+ {
+ EditorBeatmap.Difficulty.SliderMultiplier = 1;
+ var timingPoint = EditorBeatmap.ControlPointInfo.TimingPointAt(slider.StartTime);
+ timingPoint.BeatLength = 352.941176470588;
+ slider.Path.ControlPoints[^1].Position = new Vector2(-110, 16);
+ slider.Path.ExpectedDistance.Value = 100;
+ });
+
+ convertToStream();
+
+ AddAssert("stream created", () => streamCreatedFor(slider,
+ (time: 0, pathPosition: 0),
+ (time: 0.25, pathPosition: 0.25),
+ (time: 0.5, pathPosition: 0.5),
+ (time: 0.75, pathPosition: 0.75),
+ (time: 1, pathPosition: 1)));
+ }
+
private bool streamCreatedFor(Slider slider, params (double time, double pathPosition)[] expectedCircles)
{
if (EditorBeatmap.HitObjects.Contains(slider))
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/cursor-smoke@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/cursor-smoke@2x.png
new file mode 100644
index 0000000000..b1380a47a4
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/cursor-smoke@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/cursor-smoke.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/cursor-smoke.png
new file mode 100644
index 0000000000..5f7beae4e9
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/cursor-smoke.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSmoke.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSmoke.cs
new file mode 100644
index 0000000000..1cb64b71fc
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSmoke.cs
@@ -0,0 +1,136 @@
+// 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 NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Input.Events;
+using osu.Framework.Input.States;
+using osu.Framework.Logging;
+using osu.Framework.Testing.Input;
+using osu.Game.Rulesets.Osu.UI;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestSceneSmoke : OsuSkinnableTestScene
+ {
+ [Test]
+ public void TestSmoking()
+ {
+ addStep("Create short smoke", 2_000);
+ addStep("Create medium smoke", 5_000);
+ addStep("Create long smoke", 10_000);
+ }
+
+ private void addStep(string stepName, double duration)
+ {
+ var smokeContainers = new List();
+
+ AddStep(stepName, () =>
+ {
+ smokeContainers.Clear();
+ SetContents(_ =>
+ {
+ smokeContainers.Add(new TestSmokeContainer
+ {
+ Duration = duration,
+ RelativeSizeAxes = Axes.Both
+ });
+
+ return new SmokingInputManager
+ {
+ Duration = duration,
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(0.95f),
+ Child = smokeContainers[^1],
+ };
+ });
+ });
+
+ AddUntilStep("Until skinnable expires", () =>
+ {
+ if (smokeContainers.Count == 0)
+ return false;
+
+ Logger.Log("How many: " + smokeContainers.Count);
+
+ foreach (var smokeContainer in smokeContainers)
+ {
+ if (smokeContainer.Children.Count != 0)
+ return false;
+ }
+
+ return true;
+ });
+ }
+
+ private class SmokingInputManager : ManualInputManager
+ {
+ public double Duration { get; init; }
+
+ private double? startTime;
+
+ public SmokingInputManager()
+ {
+ UseParentInput = false;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ MoveMouseTo(ToScreenSpace(DrawSize / 2));
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ const float spin_angle = 4 * MathF.PI;
+
+ startTime ??= Time.Current;
+
+ float fraction = (float)((Time.Current - startTime) / Duration);
+
+ float angle = fraction * spin_angle;
+ float radius = fraction * Math.Min(DrawSize.X, DrawSize.Y) / 2;
+
+ Vector2 pos = radius * new Vector2(MathF.Cos(angle), MathF.Sin(angle)) + DrawSize / 2;
+ MoveMouseTo(ToScreenSpace(pos));
+ }
+ }
+
+ private class TestSmokeContainer : SmokeContainer
+ {
+ public double Duration { get; init; }
+
+ private bool isPressing;
+ private bool isFinished;
+
+ private double? startTime;
+
+ protected override void Update()
+ {
+ base.Update();
+
+ startTime ??= Time.Current + 0.1;
+
+ if (!isPressing && !isFinished && Time.Current > startTime)
+ {
+ OnPressed(new KeyBindingPressEvent(new InputState(), OsuAction.Smoke));
+ isPressing = true;
+ isFinished = false;
+ }
+
+ if (isPressing && Time.Current > startTime + Duration)
+ {
+ OnReleased(new KeyBindingReleaseEvent(new InputState(), OsuAction.Smoke));
+ isPressing = false;
+ isFinished = true;
+ }
+ }
+ }
+ }
+}
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 c2973644cf..1eb1c85d93 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
@@ -1,10 +1,10 @@
-
+
-
+
WinExe
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs
index 2ba856d014..dabbfcd2fb 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
if (!(currentObj.BaseObject is Spinner))
{
- double jumpDistance = (osuHitObject.StackedPosition - currentHitObject.EndPosition).Length;
+ double jumpDistance = (osuHitObject.StackedPosition - currentHitObject.StackedEndPosition).Length;
cumulativeStrainTime += lastObj.StrainTime;
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
index 7c289b5b05..265a1d21b1 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
@@ -342,7 +342,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
double positionWithRepeats = (time - HitObject.StartTime) / HitObject.Duration * HitObject.SpanCount();
double pathPosition = positionWithRepeats - (int)positionWithRepeats;
// every second span is in the reverse direction - need to reverse the path position.
- if (Precision.AlmostBigger(positionWithRepeats % 2, 1))
+ if (positionWithRepeats % 2 >= 1)
pathPosition = 1 - pathPosition;
Vector2 position = HitObject.Position + HitObject.Path.PositionAt(pathPosition);
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs
index e624660410..f6622c268d 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs
@@ -4,7 +4,6 @@
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
-using osu.Game.Configuration;
namespace osu.Game.Rulesets.Osu.Mods
{
@@ -18,13 +17,10 @@ namespace osu.Game.Rulesets.Osu.Mods
public override LocalisableString Description => "Hit them at the right size!";
- [SettingSource("Starting Size", "The initial size multiplier applied to all objects.")]
- public override BindableNumber StartScale { get; } = new BindableFloat
+ public override BindableNumber StartScale { get; } = new BindableFloat(2)
{
MinValue = 1f,
MaxValue = 25f,
- Default = 2f,
- Value = 2f,
Precision = 0.1f,
};
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
index b77c887cd3..3d066d3ada 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
@@ -4,7 +4,6 @@
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
-using osu.Game.Configuration;
namespace osu.Game.Rulesets.Osu.Mods
{
@@ -18,13 +17,10 @@ namespace osu.Game.Rulesets.Osu.Mods
public override LocalisableString Description => "Hit them at the right size!";
- [SettingSource("Starting Size", "The initial size multiplier applied to all objects.")]
- public override BindableNumber StartScale { get; } = new BindableFloat
+ public override BindableNumber StartScale { get; } = new BindableFloat(0.5f)
{
MinValue = 0f,
MaxValue = 0.99f,
- Default = 0.5f,
- Value = 0.5f,
Precision = 0.01f,
};
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
index 817f7b599c..2f84c30581 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
@@ -7,8 +7,6 @@ using osu.Framework.Bindables;
using osu.Framework.Localisation;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
-using osu.Game.Configuration;
-using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.UI;
@@ -22,15 +20,8 @@ namespace osu.Game.Rulesets.Osu.Mods
private PeriodTracker spinnerPeriods = null!;
- [SettingSource(
- "Hidden at combo",
- "The combo count at which the cursor becomes completely hidden",
- SettingControlType = typeof(SettingsSlider)
- )]
- public override BindableInt HiddenComboCount { get; } = new BindableInt
+ public override BindableInt HiddenComboCount { get; } = new BindableInt(10)
{
- Default = 10,
- Value = 10,
MinValue = 0,
MaxValue = 50,
};
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs
index 59984f9a7b..6f1206382a 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs
@@ -4,6 +4,7 @@
using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
@@ -20,6 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override double ScoreMultiplier => 1;
+ [SettingSource("Starting Size", "The initial size multiplier applied to all objects.")]
public abstract BindableNumber StartScale { get; }
protected virtual float EndScale => 1;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs
index 78c7aa53f4..618fcfe05d 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs
@@ -29,10 +29,8 @@ namespace osu.Game.Rulesets.Osu.Mods
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModTarget)).ToArray();
[SettingSource("Angle sharpness", "How sharp angles should be", SettingControlType = typeof(SettingsSlider))]
- public BindableFloat AngleSharpness { get; } = new BindableFloat
+ public BindableFloat AngleSharpness { get; } = new BindableFloat(7)
{
- Default = 7,
- Value = 7,
MinValue = 1,
MaxValue = 10,
Precision = 0.1f
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
index fac1cbfd47..753de6231a 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
@@ -20,7 +20,9 @@ namespace osu.Game.Rulesets.Osu.Mods
public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset, IApplicableToPlayer
{
public override LocalisableString Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
- public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray();
+
+ public override Type[] IncompatibleMods =>
+ base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray();
///
/// How early before a hitobject's start time to trigger a hit.
@@ -51,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Mods
return;
}
- osuInputManager.AllowUserPresses = false;
+ osuInputManager.AllowGameplayInputs = false;
}
public void Update(Playfield playfield)
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs
index 861ad80b7f..406968ba08 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs
@@ -53,11 +53,7 @@ namespace osu.Game.Rulesets.Osu.Mods
}).ToArray();
[SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))]
- public Bindable Seed { get; } = new Bindable
- {
- Default = null,
- Value = null
- };
+ public Bindable Seed { get; } = new Bindable();
[SettingSource("Metronome ticks", "Whether a metronome beat should play in the background")]
public Bindable Metronome { get; } = new BindableBool(true);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
index c5992b359d..23db29b9a6 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -204,12 +204,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
// todo: temporary / arbitrary, used for lifetime optimisation.
this.Delay(800).FadeOut();
- // in the case of an early state change, the fade should be expedited to the current point in time.
- if (HitStateUpdateTime < HitObject.StartTime)
- ApproachCircle.FadeOut(50);
-
switch (state)
{
+ default:
+ ApproachCircle.FadeOut();
+ break;
+
case ArmedState.Idle:
HitArea.HitAction = null;
break;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
index 6f4ca30bd0..d9d0d28477 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
@@ -11,7 +11,9 @@ using osu.Framework.Graphics.Primitives;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Judgements;
+using osu.Game.Rulesets.Osu.Scoring;
using osuTK;
+using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@@ -64,6 +66,23 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
ScaleBindable.UnbindFrom(HitObject.ScaleBindable);
}
+ protected override void UpdateInitialTransforms()
+ {
+ base.UpdateInitialTransforms();
+
+ // Dim should only be applied at a top level, as it will be implicitly applied to nested objects.
+ if (ParentHitObject == null)
+ {
+ // Of note, no one noticed this was missing for years, but it definitely feels like it should still exist.
+ // For now this is applied across all skins, and matches stable.
+ // For simplicity, dim colour is applied to the DrawableHitObject itself.
+ // We may need to make a nested container setup if this even causes a usage conflict (ie. with a mod).
+ this.FadeColour(new Color4(195, 195, 195, 255));
+ using (BeginDelayedSequence(InitialLifetimeOffset - OsuHitWindows.MISS_WINDOW))
+ this.FadeColour(Color4.White, 100);
+ }
+ }
+
protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
private OsuInputManager osuActionInputManager;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs
index 6bfb4e8aae..a2fe623897 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs
@@ -186,17 +186,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private Vector2? lastPosition;
+ private bool rewinding;
+
public void UpdateProgress(double completionProgress)
{
Position = drawableSlider.HitObject.CurvePositionAt(completionProgress);
var diff = lastPosition.HasValue ? lastPosition.Value - Position : Position - drawableSlider.HitObject.CurvePositionAt(completionProgress + 0.01f);
+ if (Clock.ElapsedFrameTime != 0)
+ rewinding = Clock.ElapsedFrameTime < 0;
+
// Ensure the value is substantially high enough to allow for Atan2 to get a valid angle.
if (diff.LengthFast < 0.01f)
return;
- ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI);
+ ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI) + (rewinding ? 180 : 0);
lastPosition = Position;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs
index 6a15463a32..4975ca1248 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Origin = Anchor.Centre;
}
- protected override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration;
+ public override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration;
///
/// Apply a judgement result.
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index e3c1b1e168..6c2be8a49a 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -34,21 +34,6 @@ namespace osu.Game.Rulesets.Osu.Objects
public override IList AuxiliarySamples => CreateSlidingSamples().Concat(TailSamples).ToArray();
- public IList CreateSlidingSamples()
- {
- var slidingSamples = new List();
-
- var normalSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL);
- if (normalSample != null)
- slidingSamples.Add(normalSample.With("sliderslide"));
-
- var whistleSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_WHISTLE);
- if (whistleSample != null)
- slidingSamples.Add(whistleSample.With("sliderwhistle"));
-
- return slidingSamples;
- }
-
private readonly Cached endPositionCache = new Cached();
public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1);
diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
index 569e9b7c1c..676ff62455 100644
--- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Objects
// This is so on repeats ticks don't appear too late to be visually processed by the player.
offset = 200;
else
- offset = TimeFadeIn * 0.66f;
+ offset = TimePreempt * 0.66f;
TimePreempt = (StartTime - SpanStartTime) / 2 + offset;
}
diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs
index 12256e93d0..1e59e19246 100644
--- a/osu.Game.Rulesets.Osu/OsuInputManager.cs
+++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs
@@ -5,10 +5,12 @@
using System.Collections.Generic;
using System.ComponentModel;
+using System.Linq;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Input.StateChanges.Events;
+using osu.Game.Input.Bindings;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu
@@ -17,9 +19,16 @@ namespace osu.Game.Rulesets.Osu
{
public IEnumerable PressedActions => KeyBindingContainer.PressedActions;
- public bool AllowUserPresses
+ ///
+ /// Whether gameplay input buttons should be allowed.
+ /// Defaults to true, generally used for mods like Relax which turn off main inputs.
+ ///
+ ///
+ /// Of note, auxiliary inputs like the "smoke" key are left usable.
+ ///
+ public bool AllowGameplayInputs
{
- set => ((OsuKeyBindingContainer)KeyBindingContainer).AllowUserPresses = value;
+ set => ((OsuKeyBindingContainer)KeyBindingContainer).AllowGameplayInputs = value;
}
///
@@ -58,18 +67,36 @@ namespace osu.Game.Rulesets.Osu
private class OsuKeyBindingContainer : RulesetKeyBindingContainer
{
- public bool AllowUserPresses = true;
+ private bool allowGameplayInputs = true;
+
+ ///
+ /// Whether gameplay input buttons should be allowed.
+ /// Defaults to true, generally used for mods like Relax which turn off main inputs.
+ ///
+ ///
+ /// Of note, auxiliary inputs like the "smoke" key are left usable.
+ ///
+ public bool AllowGameplayInputs
+ {
+ get => allowGameplayInputs;
+ set
+ {
+ allowGameplayInputs = value;
+ ReloadMappings();
+ }
+ }
public OsuKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
: base(ruleset, variant, unique)
{
}
- protected override bool Handle(UIEvent e)
+ protected override void ReloadMappings(IQueryable realmKeyBindings)
{
- if (!AllowUserPresses) return false;
+ base.ReloadMappings(realmKeyBindings);
- return base.Handle(e);
+ if (!AllowGameplayInputs)
+ KeyBindings = KeyBindings.Where(b => b.GetAction() == OsuAction.Smoke).ToList();
}
}
}
@@ -80,6 +107,9 @@ namespace osu.Game.Rulesets.Osu
LeftButton,
[Description("Right button")]
- RightButton
+ RightButton,
+
+ [Description("Smoke")]
+ Smoke,
}
}
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 3f5e728651..e823053be9 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -59,6 +59,7 @@ namespace osu.Game.Rulesets.Osu
{
new KeyBinding(InputKey.Z, OsuAction.LeftButton),
new KeyBinding(InputKey.X, OsuAction.RightButton),
+ new KeyBinding(InputKey.C, OsuAction.Smoke),
new KeyBinding(InputKey.MouseLeft, OsuAction.LeftButton),
new KeyBinding(InputKey.MouseRight, OsuAction.RightButton),
};
diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
index fcf079b6aa..4248cce55a 100644
--- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
+++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
@@ -21,6 +21,7 @@ namespace osu.Game.Rulesets.Osu
SliderBall,
SliderBody,
SpinnerBody,
+ CursorSmoke,
ApproachCircle,
}
}
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs
index 85060261fe..8082c5aef4 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs
@@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Osu.Replays
Position = currentFrame.Position;
if (currentFrame.MouseLeft) Actions.Add(OsuAction.LeftButton);
if (currentFrame.MouseRight) Actions.Add(OsuAction.RightButton);
+ if (currentFrame.Smoke) Actions.Add(OsuAction.Smoke);
}
public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
@@ -41,6 +42,8 @@ namespace osu.Game.Rulesets.Osu.Replays
state |= ReplayButtonState.Left1;
if (Actions.Contains(OsuAction.RightButton))
state |= ReplayButtonState.Right1;
+ if (Actions.Contains(OsuAction.Smoke))
+ state |= ReplayButtonState.Smoke;
return new LegacyReplayFrame(Time, Position.X, Position.Y, state);
}
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs
index 05fbac625e..6f55e1790f 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs
@@ -1,20 +1,23 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Scoring
{
public class OsuHitWindows : HitWindows
{
+ ///
+ /// osu! ruleset has a fixed miss window regardless of difficulty settings.
+ ///
+ public const double MISS_WINDOW = 400;
+
private static readonly DifficultyRange[] osu_ranges =
{
new DifficultyRange(HitResult.Great, 80, 50, 20),
new DifficultyRange(HitResult.Ok, 140, 100, 60),
new DifficultyRange(HitResult.Meh, 200, 150, 100),
- new DifficultyRange(HitResult.Miss, 400, 400, 400),
+ new DifficultyRange(HitResult.Miss, MISS_WINDOW, MISS_WINDOW, MISS_WINDOW),
};
public override bool IsHitResultAllowed(HitResult result)
diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs
index 7bc6723afb..bf507db50c 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs
@@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
return new ArgonJudgementPiece(resultComponent.Component);
case OsuSkinComponent osuComponent:
+ // TODO: Once everything is finalised, consider throwing UnsupportedSkinComponentException on missing entries.
switch (osuComponent.Component)
{
case OsuSkinComponents.HitCircle:
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmokeSegment.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmokeSegment.cs
new file mode 100644
index 0000000000..27a2dc3960
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmokeSegment.cs
@@ -0,0 +1,19 @@
+// 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.Allocation;
+using osu.Framework.Graphics.Textures;
+
+namespace osu.Game.Rulesets.Osu.Skinning.Default
+{
+ public class DefaultSmokeSegment : SmokeSegment
+ {
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ // ISkinSource doesn't currently fallback to global textures.
+ // We might want to change this in the future if the intention is to allow the user to skin this as per legacy skins.
+ Texture = textures.Get("Gameplay/osu/cursor-smoke");
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmokeSegment.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmokeSegment.cs
new file mode 100644
index 0000000000..c9c7e86e86
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmokeSegment.cs
@@ -0,0 +1,19 @@
+// 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.Allocation;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Osu.Skinning.Legacy
+{
+ public class LegacySmokeSegment : SmokeSegment
+ {
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ base.LoadComplete();
+
+ Texture = skin.GetTexture("cursor-smoke");
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
index 885a2c12fb..b778bc21d1 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs
@@ -106,6 +106,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
return null;
+ case OsuSkinComponents.CursorSmoke:
+ if (GetTexture("cursor-smoke") != null)
+ return new LegacySmokeSegment();
+
+ return null;
+
case OsuSkinComponents.HitCircleText:
if (!this.HasFont(LegacyFont.HitCircle))
return null;
diff --git a/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs b/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs
new file mode 100644
index 0000000000..6c998e244c
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs
@@ -0,0 +1,366 @@
+// 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 osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Primitives;
+using osu.Framework.Graphics.Rendering;
+using osu.Framework.Graphics.Rendering.Vertices;
+using osu.Framework.Graphics.Shaders;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Utils;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.Skinning
+{
+ public abstract class SmokeSegment : Drawable, ITexturedShaderDrawable
+ {
+ private const int max_point_count = 18_000;
+
+ // fade anim values
+ private const double initial_fade_out_duration = 4000;
+
+ private const double re_fade_in_speed = 3;
+ private const double re_fade_in_duration = 50;
+
+ private const double final_fade_out_speed = 2;
+ private const double final_fade_out_duration = 8000;
+
+ private const float initial_alpha = 0.6f;
+ private const float re_fade_in_alpha = 1f;
+
+ private readonly int rotationSeed = RNG.Next();
+
+ // scale anim values
+ private const double scale_duration = 1200;
+
+ private const float initial_scale = 0.65f;
+ private const float final_scale = 1f;
+
+ // rotation anim values
+ private const double rotation_duration = 500;
+
+ private const float max_rotation = 0.25f;
+
+ public IShader? TextureShader { get; private set; }
+ public IShader? RoundedTextureShader { get; private set; }
+
+ protected Texture? Texture { get; set; }
+
+ private float radius => Texture?.DisplayWidth * 0.165f ?? 3;
+
+ protected readonly List SmokePoints = new List();
+
+ private float pointInterval => radius * 7f / 8;
+
+ private double smokeStartTime { get; set; } = double.MinValue;
+
+ private double smokeEndTime { get; set; } = double.MaxValue;
+
+ private float totalDistance;
+ private Vector2? lastPosition;
+
+ [BackgroundDependencyLoader]
+ private void load(ShaderManager shaders)
+ {
+ RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED);
+ TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ RelativeSizeAxes = Axes.Both;
+
+ LifetimeStart = smokeStartTime = Time.Current;
+
+ totalDistance = pointInterval;
+ }
+
+ private Vector2 nextPointDirection()
+ {
+ float angle = RNG.NextSingle(0, 2 * MathF.PI);
+ return new Vector2(MathF.Sin(angle), -MathF.Cos(angle));
+ }
+
+ public void AddPosition(Vector2 position, double time)
+ {
+ lastPosition ??= position;
+
+ float delta = (position - (Vector2)lastPosition).LengthFast;
+ totalDistance += delta;
+ int count = (int)(totalDistance / pointInterval);
+
+ if (count > 0)
+ {
+ Vector2 increment = position - (Vector2)lastPosition;
+ increment.NormalizeFast();
+
+ Vector2 pointPos = (pointInterval - (totalDistance - delta)) * increment + (Vector2)lastPosition;
+ increment *= pointInterval;
+
+ if (SmokePoints.Count > 0 && SmokePoints[^1].Time > time)
+ {
+ int index = ~SmokePoints.BinarySearch(new SmokePoint { Time = time }, new SmokePoint.UpperBoundComparer());
+ SmokePoints.RemoveRange(index, SmokePoints.Count - index);
+ }
+
+ totalDistance %= pointInterval;
+
+ for (int i = 0; i < count; i++)
+ {
+ SmokePoints.Add(new SmokePoint
+ {
+ Position = pointPos,
+ Time = time,
+ Direction = nextPointDirection(),
+ });
+
+ pointPos += increment;
+ }
+
+ Invalidate(Invalidation.DrawNode);
+ }
+
+ lastPosition = position;
+
+ if (SmokePoints.Count >= max_point_count)
+ FinishDrawing(time);
+ }
+
+ public void FinishDrawing(double time)
+ {
+ smokeEndTime = time;
+
+ double initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, smokeEndTime - smokeStartTime);
+ LifetimeEnd = smokeEndTime + final_fade_out_duration + initialFadeOutDurationTrunc / re_fade_in_speed + initialFadeOutDurationTrunc / final_fade_out_speed;
+ }
+
+ protected override DrawNode CreateDrawNode() => new SmokeDrawNode(this);
+
+ protected override void Update()
+ {
+ base.Update();
+
+ Invalidate(Invalidation.DrawNode);
+ }
+
+ protected struct SmokePoint
+ {
+ public Vector2 Position;
+ public double Time;
+ public Vector2 Direction;
+
+ public struct UpperBoundComparer : IComparer
+ {
+ public int Compare(SmokePoint x, SmokePoint target)
+ {
+ // By returning -1 when the target value is equal to x, guarantees that the
+ // element at BinarySearch's returned index will always be the first element
+ // larger. Since 0 is never returned, the target is never "found", so the return
+ // value will be the index's complement.
+
+ return x.Time > target.Time ? 1 : -1;
+ }
+ }
+ }
+
+ protected class SmokeDrawNode : TexturedShaderDrawNode
+ {
+ protected new SmokeSegment Source => (SmokeSegment)base.Source;
+
+ protected double SmokeStartTime { get; private set; }
+ protected double SmokeEndTime { get; private set; }
+ protected double CurrentTime { get; private set; }
+
+ private readonly List points = new List();
+ private IVertexBatch? quadBatch;
+ private float radius;
+ private Vector2 drawSize;
+ private Texture? texture;
+
+ // anim calculation vars (color, scale, direction)
+ private double initialFadeOutDurationTrunc;
+ private double firstVisiblePointTime;
+
+ private double initialFadeOutTime;
+ private double reFadeInTime;
+ private double finalFadeOutTime;
+
+ private Random rotationRNG = new Random();
+
+ public SmokeDrawNode(ITexturedShaderDrawable source)
+ : base(source)
+ {
+ }
+
+ public override void ApplyState()
+ {
+ base.ApplyState();
+
+ points.Clear();
+ points.AddRange(Source.SmokePoints);
+
+ radius = Source.radius;
+ drawSize = Source.DrawSize;
+ texture = Source.Texture;
+
+ SmokeStartTime = Source.smokeStartTime;
+ SmokeEndTime = Source.smokeEndTime;
+ CurrentTime = Source.Clock.CurrentTime;
+
+ rotationRNG = new Random(Source.rotationSeed);
+
+ initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime);
+ firstVisiblePointTime = SmokeEndTime - initialFadeOutDurationTrunc;
+
+ initialFadeOutTime = CurrentTime;
+ reFadeInTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTime * (1 - 1 / re_fade_in_speed);
+ finalFadeOutTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTime * (1 - 1 / final_fade_out_speed);
+ }
+
+ public sealed override void Draw(IRenderer renderer)
+ {
+ base.Draw(renderer);
+
+ if (points.Count == 0)
+ return;
+
+ quadBatch ??= renderer.CreateQuadBatch(max_point_count / 10, 10);
+ texture ??= renderer.WhitePixel;
+ RectangleF textureRect = texture.GetTextureRect();
+
+ var shader = GetAppropriateShader(renderer);
+
+ renderer.SetBlend(BlendingParameters.Additive);
+ renderer.PushLocalMatrix(DrawInfo.Matrix);
+
+ shader.Bind();
+ texture.Bind();
+
+ foreach (var point in points)
+ drawPointQuad(point, textureRect);
+
+ shader.Unbind();
+ renderer.PopLocalMatrix();
+ }
+
+ protected Color4 ColourAtPosition(Vector2 localPos) => DrawColourInfo.Colour.HasSingleColour
+ ? ((SRGBColour)DrawColourInfo.Colour).Linear
+ : DrawColourInfo.Colour.Interpolate(Vector2.Divide(localPos, drawSize)).Linear;
+
+ protected virtual Color4 PointColour(SmokePoint point)
+ {
+ var color = Color4.White;
+
+ double timeDoingInitialFadeOut = Math.Min(initialFadeOutTime, SmokeEndTime) - point.Time;
+
+ if (timeDoingInitialFadeOut > 0)
+ {
+ float fraction = Math.Clamp((float)(timeDoingInitialFadeOut / initial_fade_out_duration), 0, 1);
+ color.A = (1 - fraction) * initial_alpha;
+ }
+
+ if (color.A > 0)
+ {
+ double timeDoingReFadeIn = reFadeInTime - point.Time / re_fade_in_speed;
+ double timeDoingFinalFadeOut = finalFadeOutTime - point.Time / final_fade_out_speed;
+
+ if (timeDoingFinalFadeOut > 0)
+ {
+ float fraction = Math.Clamp((float)(timeDoingFinalFadeOut / final_fade_out_duration), 0, 1);
+ fraction = MathF.Pow(fraction, 5);
+ color.A = (1 - fraction) * re_fade_in_alpha;
+ }
+ else if (timeDoingReFadeIn > 0)
+ {
+ float fraction = Math.Clamp((float)(timeDoingReFadeIn / re_fade_in_duration), 0, 1);
+ fraction = 1 - MathF.Pow(1 - fraction, 5);
+ color.A = fraction * (re_fade_in_alpha - color.A) + color.A;
+ }
+ }
+
+ return color;
+ }
+
+ protected virtual float PointScale(SmokePoint point)
+ {
+ double timeDoingScale = CurrentTime - point.Time;
+ float fraction = Math.Clamp((float)(timeDoingScale / scale_duration), 0, 1);
+ fraction = 1 - MathF.Pow(1 - fraction, 5);
+ return fraction * (final_scale - initial_scale) + initial_scale;
+ }
+
+ protected virtual Vector2 PointDirection(SmokePoint point)
+ {
+ float initialAngle = MathF.Atan2(point.Direction.Y, point.Direction.X);
+ float finalAngle = initialAngle + nextRotation();
+
+ double timeDoingRotation = CurrentTime - point.Time;
+ float fraction = Math.Clamp((float)(timeDoingRotation / rotation_duration), 0, 1);
+ fraction = 1 - MathF.Pow(1 - fraction, 5);
+ float angle = fraction * (finalAngle - initialAngle) + initialAngle;
+
+ return new Vector2(MathF.Sin(angle), -MathF.Cos(angle));
+ }
+
+ private float nextRotation() => max_rotation * ((float)rotationRNG.NextDouble() * 2 - 1);
+
+ private void drawPointQuad(SmokePoint point, RectangleF textureRect)
+ {
+ Debug.Assert(quadBatch != null);
+
+ var colour = PointColour(point);
+ float scale = PointScale(point);
+ var dir = PointDirection(point);
+ var ortho = dir.PerpendicularLeft;
+
+ if (colour.A == 0 || scale == 0)
+ return;
+
+ var localTopLeft = point.Position + (radius * scale * (-ortho - dir));
+ var localTopRight = point.Position + (radius * scale * (-ortho + dir));
+ var localBotLeft = point.Position + (radius * scale * (ortho - dir));
+ var localBotRight = point.Position + (radius * scale * (ortho + dir));
+
+ quadBatch.Add(new TexturedVertex2D
+ {
+ Position = localTopLeft,
+ TexturePosition = textureRect.TopLeft,
+ Colour = Color4Extensions.Multiply(ColourAtPosition(localTopLeft), colour),
+ });
+ quadBatch.Add(new TexturedVertex2D
+ {
+ Position = localTopRight,
+ TexturePosition = textureRect.TopRight,
+ Colour = Color4Extensions.Multiply(ColourAtPosition(localTopRight), colour),
+ });
+ quadBatch.Add(new TexturedVertex2D
+ {
+ Position = localBotRight,
+ TexturePosition = textureRect.BottomRight,
+ Colour = Color4Extensions.Multiply(ColourAtPosition(localBotRight), colour),
+ });
+ quadBatch.Add(new TexturedVertex2D
+ {
+ Position = localBotLeft,
+ TexturePosition = textureRect.BottomLeft,
+ Colour = Color4Extensions.Multiply(ColourAtPosition(localBotLeft), colour),
+ });
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ quadBatch?.Dispose();
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index fc2ba8ea2f..2e67e91460 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -54,6 +54,7 @@ namespace osu.Game.Rulesets.Osu.UI
InternalChildren = new Drawable[]
{
playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both },
+ new SmokeContainer { RelativeSizeAxes = Axes.Both },
spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both },
FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both },
judgementLayer = new JudgementContainer { RelativeSizeAxes = Axes.Both },
diff --git a/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs
new file mode 100644
index 0000000000..beba834e88
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/UI/SmokeContainer.cs
@@ -0,0 +1,77 @@
+// 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;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Input;
+using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
+using osu.Game.Rulesets.Osu.Skinning;
+using osu.Game.Rulesets.Osu.Skinning.Default;
+using osu.Game.Skinning;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.UI
+{
+ ///
+ /// Manages smoke trails generated from user input.
+ ///
+ public class SmokeContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler
+ {
+ private SmokeSkinnableDrawable? currentSegmentSkinnable;
+
+ private Vector2 lastMousePosition;
+
+ public override bool ReceivePositionalInputAt(Vector2 _) => true;
+
+ public bool OnPressed(KeyBindingPressEvent e)
+ {
+ if (e.Action == OsuAction.Smoke)
+ {
+ AddInternal(currentSegmentSkinnable = new SmokeSkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorSmoke), _ => new DefaultSmokeSegment()));
+
+ // Add initial position immediately.
+ addPosition();
+ return true;
+ }
+
+ return false;
+ }
+
+ public void OnReleased(KeyBindingReleaseEvent e)
+ {
+ if (e.Action == OsuAction.Smoke)
+ {
+ if (currentSegmentSkinnable?.Drawable is SmokeSegment segment)
+ {
+ segment.FinishDrawing(Time.Current);
+ currentSegmentSkinnable = null;
+ }
+ }
+ }
+
+ protected override bool OnMouseMove(MouseMoveEvent e)
+ {
+ lastMousePosition = e.MousePosition;
+ addPosition();
+
+ return base.OnMouseMove(e);
+ }
+
+ private void addPosition() => (currentSegmentSkinnable?.Drawable as SmokeSegment)?.AddPosition(lastMousePosition, Time.Current);
+
+ private class SmokeSkinnableDrawable : SkinnableDrawable
+ {
+ public override bool RemoveWhenNotAlive => true;
+
+ public override double LifetimeStart => Drawable.LifetimeStart;
+ public override double LifetimeEnd => Drawable.LifetimeEnd;
+
+ public SmokeSkinnableDrawable(ISkinComponent component, Func? defaultImplementation = null, ConfineMode confineMode = ConfineMode.NoScaling)
+ : base(component, defaultImplementation, confineMode)
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs
index a405f0e8ba..d2d5cdb6ac 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using NUnit.Framework;
+using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
@@ -129,5 +130,32 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements
AssertResult(0, HitResult.Miss);
AssertResult(0, HitResult.IgnoreMiss);
}
+
+ [Test]
+ public void TestHighVelocityHit()
+ {
+ const double hit_time = 1000;
+
+ var beatmap = CreateBeatmap(new Hit
+ {
+ Type = HitType.Centre,
+ StartTime = hit_time,
+ });
+
+ beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 6 });
+ beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 10 });
+
+ var hitWindows = new HitWindows();
+ hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);
+
+ PerformTest(new List
+ {
+ new TaikoReplayFrame(0),
+ new TaikoReplayFrame(hit_time - hitWindows.WindowFor(HitResult.Great), TaikoAction.LeftCentre),
+ }, beatmap);
+
+ AssertJudgementCount(1);
+ AssertResult(0, HitResult.Ok);
+ }
}
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineGeneration.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineGeneration.cs
new file mode 100644
index 0000000000..095fddc33f
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineGeneration.cs
@@ -0,0 +1,87 @@
+// 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 NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Beatmaps.Timing;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Taiko.Tests
+{
+ public class TestSceneBarLineGeneration : OsuTestScene
+ {
+ [Test]
+ public void TestCloseBarLineGeneration()
+ {
+ const double start_time = 1000;
+
+ var beatmap = new Beatmap
+ {
+ HitObjects =
+ {
+ new Hit
+ {
+ Type = HitType.Centre,
+ StartTime = start_time
+ }
+ },
+ BeatmapInfo =
+ {
+ Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
+ Ruleset = new TaikoRuleset().RulesetInfo
+ },
+ };
+
+ beatmap.ControlPointInfo.Add(start_time, new TimingControlPoint());
+ beatmap.ControlPointInfo.Add(start_time + 1, new TimingControlPoint());
+
+ var barlines = new BarLineGenerator(beatmap).BarLines;
+
+ AddAssert("first barline generated", () => barlines.Any(b => b.StartTime == start_time));
+ AddAssert("second barline generated", () => barlines.Any(b => b.StartTime == start_time + 1));
+ }
+
+ [Test]
+ public void TestOmitBarLineEffectPoint()
+ {
+ const double start_time = 1000;
+ const double beat_length = 500;
+
+ const int time_signature_numerator = 4;
+
+ var beatmap = new Beatmap
+ {
+ HitObjects =
+ {
+ new Hit
+ {
+ Type = HitType.Centre,
+ StartTime = start_time
+ }
+ },
+ BeatmapInfo =
+ {
+ Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
+ Ruleset = new TaikoRuleset().RulesetInfo
+ },
+ };
+
+ beatmap.ControlPointInfo.Add(start_time, new TimingControlPoint
+ {
+ BeatLength = beat_length,
+ TimeSignature = new TimeSignature(time_signature_numerator)
+ });
+
+ beatmap.ControlPointInfo.Add(start_time, new EffectControlPoint { OmitFirstBarLine = true });
+
+ var barlines = new BarLineGenerator(beatmap).BarLines;
+
+ AddAssert("first barline ommited", () => barlines.All(b => b.StartTime != start_time));
+ AddAssert("second barline generated", () => barlines.Any(b => b.StartTime == start_time + (beat_length * time_signature_numerator)));
+ }
+ }
+}
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 e8eaff4368..38e61f5624 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
@@ -1,9 +1,9 @@
-
+
-
+
WinExe
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 95a1e8bc66..dc7bad2f75 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private int countOk;
private int countMeh;
private int countMiss;
+ private double accuracy;
private double effectiveMissCount;
@@ -36,6 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
+ accuracy = customAccuracy;
// The effectiveMissCount is calculated by gaining a ratio for totalSuccessfulHits and increasing the miss penalty for shorter object counts lower than 1000.
if (totalSuccessfulHits > 0)
@@ -87,7 +89,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (score.Mods.Any(m => m is ModFlashlight))
difficultyValue *= 1.050 * lengthBonus;
- return difficultyValue * Math.Pow(score.Accuracy, 2.0);
+ return difficultyValue * Math.Pow(accuracy, 2.0);
}
private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes)
@@ -95,7 +97,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (attributes.GreatHitWindow <= 0)
return 0;
- double accuracyValue = Math.Pow(60.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(score.Accuracy, 8.0) * Math.Pow(attributes.StarRating, 0.4) * 27.0;
+ double accuracyValue = Math.Pow(60.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(accuracy, 8.0) * Math.Pow(attributes.StarRating, 0.4) * 27.0;
double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
accuracyValue *= lengthBonus;
@@ -110,5 +112,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private int totalHits => countGreat + countOk + countMeh + countMiss;
private int totalSuccessfulHits => countGreat + countOk + countMeh;
+
+ private double customAccuracy => totalHits > 0 ? (countGreat * 300 + countOk * 150) / (totalHits * 300.0) : 0;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
index 705a0a8047..451c5a793b 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Filled = HitObject.FirstTick
});
- protected override double MaximumJudgementOffset => HitObject.HitWindow;
+ public override double MaximumJudgementOffset => HitObject.HitWindow;
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
diff --git a/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs b/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs
index f14288e7ba..4ef466efbf 100644
--- a/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs
+++ b/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs
@@ -9,8 +9,10 @@ using osu.Framework.Extensions;
using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
+using osu.Game.Collections;
using osu.Game.Database;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Osu;
using osu.Game.Tests.Resources;
using osu.Game.Tests.Visual;
@@ -96,5 +98,41 @@ namespace osu.Game.Tests.Beatmaps
var second = beatmaps.GetWorkingBeatmap(beatmap, true);
Assert.That(first, Is.Not.SameAs(second));
});
+
+ [Test]
+ public void TestSavePreservesCollections() => AddStep("run test", () =>
+ {
+ var beatmap = Realm.Run(r => r.Find(importedSet.Beatmaps.First().ID).Detach());
+
+ var working = beatmaps.GetWorkingBeatmap(beatmap);
+
+ Assert.That(working.BeatmapInfo.BeatmapSet?.Files, Has.Count.GreaterThan(0));
+
+ string initialHash = working.BeatmapInfo.MD5Hash;
+
+ var preserveCollection = new BeatmapCollection("test contained");
+ preserveCollection.BeatmapMD5Hashes.Add(initialHash);
+
+ var noNewCollection = new BeatmapCollection("test not contained");
+
+ Realm.Write(r =>
+ {
+ r.Add(preserveCollection);
+ r.Add(noNewCollection);
+ });
+
+ Assert.That(preserveCollection.BeatmapMD5Hashes, Does.Contain(initialHash));
+ Assert.That(noNewCollection.BeatmapMD5Hashes, Does.Not.Contain(initialHash));
+
+ beatmaps.Save(working.BeatmapInfo, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo));
+
+ string finalHash = working.BeatmapInfo.MD5Hash;
+
+ Assert.That(finalHash, Is.Not.SameAs(initialHash));
+
+ Assert.That(preserveCollection.BeatmapMD5Hashes, Does.Not.Contain(initialHash));
+ Assert.That(preserveCollection.BeatmapMD5Hashes, Does.Contain(finalHash));
+ Assert.That(noNewCollection.BeatmapMD5Hashes, Does.Not.Contain(finalHash));
+ });
}
}
diff --git a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs
index 17709fb10f..da250c1e05 100644
--- a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs
+++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs
@@ -204,31 +204,23 @@ namespace osu.Game.Tests.Online
public override double ScoreMultiplier => 1;
[SettingSource("Initial rate", "The starting speed of the track")]
- public override BindableNumber InitialRate { get; } = new BindableDouble
+ public override BindableNumber InitialRate { get; } = new BindableDouble(1.5)
{
MinValue = 1,
MaxValue = 2,
- Default = 1.5,
- Value = 1.5,
Precision = 0.01,
};
[SettingSource("Final rate", "The speed increase to ramp towards")]
- public override BindableNumber FinalRate { get; } = new BindableDouble
+ public override BindableNumber FinalRate { get; } = new BindableDouble(0.5)
{
MinValue = 0,
MaxValue = 1,
- Default = 0.5,
- Value = 0.5,
Precision = 0.01,
};
[SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
- public override BindableBool AdjustPitch { get; } = new BindableBool
- {
- Default = true,
- Value = true
- };
+ public override BindableBool AdjustPitch { get; } = new BindableBool(true);
}
private class TestModDifficultyAdjust : ModDifficultyAdjust
diff --git a/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs b/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs
index b17414e026..1d8cbffcdb 100644
--- a/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs
+++ b/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs
@@ -124,31 +124,23 @@ namespace osu.Game.Tests.Online
public override double ScoreMultiplier => 1;
[SettingSource("Initial rate", "The starting speed of the track")]
- public override BindableNumber InitialRate { get; } = new BindableDouble
+ public override BindableNumber InitialRate { get; } = new BindableDouble(1.5)
{
MinValue = 1,
MaxValue = 2,
- Default = 1.5,
- Value = 1.5,
Precision = 0.01,
};
[SettingSource("Final rate", "The speed increase to ramp towards")]
- public override BindableNumber FinalRate { get; } = new BindableDouble
+ public override BindableNumber FinalRate { get; } = new BindableDouble(0.5)
{
MinValue = 0,
MaxValue = 1,
- Default = 0.5,
- Value = 0.5,
Precision = 0.01,
};
[SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
- public override BindableBool AdjustPitch { get; } = new BindableBool
- {
- Default = true,
- Value = true
- };
+ public override BindableBool AdjustPitch { get; } = new BindableBool(true);
}
private class TestModEnum : Mod
diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
index 3f20f843a7..e7590df3e0 100644
--- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
+++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
@@ -8,7 +8,6 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
-using System.Threading.Tasks;
using JetBrains.Annotations;
using NUnit.Framework;
using osu.Framework.Allocation;
@@ -78,7 +77,7 @@ namespace osu.Game.Tests.Online
}
};
- beatmaps.AllowImport = new TaskCompletionSource();
+ beatmaps.AllowImport.Reset();
testBeatmapFile = TestResources.GetQuickTestBeatmapForImport();
@@ -132,7 +131,7 @@ namespace osu.Game.Tests.Online
AddStep("finish download", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet))!.TriggerSuccess(testBeatmapFile));
addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing);
- AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
+ AddStep("allow importing", () => beatmaps.AllowImport.Set());
AddUntilStep("wait for import", () => beatmaps.CurrentImport != null);
AddUntilStep("ensure beatmap available", () => beatmaps.IsAvailableLocally(testBeatmapSet));
addAvailabilityCheckStep("state is locally available", BeatmapAvailability.LocallyAvailable);
@@ -141,7 +140,7 @@ namespace osu.Game.Tests.Online
[Test]
public void TestTrackerRespectsSoftDeleting()
{
- AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
+ AddStep("allow importing", () => beatmaps.AllowImport.Set());
AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely());
addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
@@ -155,7 +154,7 @@ namespace osu.Game.Tests.Online
[Test]
public void TestTrackerRespectsChecksum()
{
- AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
+ AddStep("allow importing", () => beatmaps.AllowImport.Set());
AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely());
addAvailabilityCheckStep("initially locally available", BeatmapAvailability.LocallyAvailable);
@@ -202,7 +201,7 @@ namespace osu.Game.Tests.Online
private class TestBeatmapManager : BeatmapManager
{
- public TaskCompletionSource AllowImport = new TaskCompletionSource();
+ public readonly ManualResetEventSlim AllowImport = new ManualResetEventSlim();
public Live CurrentImport { get; private set; }
@@ -229,7 +228,9 @@ namespace osu.Game.Tests.Online
public override Live ImportModel(BeatmapSetInfo item, ArchiveReader archive = null, bool batchImport = false, CancellationToken cancellationToken = default)
{
- testBeatmapManager.AllowImport.Task.WaitSafely();
+ if (!testBeatmapManager.AllowImport.Wait(TimeSpan.FromSeconds(10), cancellationToken))
+ throw new TimeoutException("Timeout waiting for import to be allowed.");
+
return (testBeatmapManager.CurrentImport = base.ImportModel(item, archive, batchImport, cancellationToken));
}
}
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs
index 7e0981ce69..54ad4e25e4 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs
@@ -29,16 +29,18 @@ namespace osu.Game.Tests.Visual.Editing
private TimelineBlueprintContainer blueprintContainer
=> Editor.ChildrenOfType().First();
+ private Vector2 getPosition(HitObject hitObject) =>
+ blueprintContainer.SelectionBlueprints.First(s => s.Item == hitObject).ScreenSpaceDrawQuad.Centre;
+
+ private Vector2 getMiddlePosition(HitObject hitObject1, HitObject hitObject2) =>
+ (getPosition(hitObject1) + getPosition(hitObject2)) / 2;
+
private void moveMouseToObject(Func targetFunc)
{
AddStep("move mouse to object", () =>
{
- var pos = blueprintContainer.SelectionBlueprints
- .First(s => s.Item == targetFunc())
- .ChildrenOfType()
- .First().ScreenSpaceDrawQuad.Centre;
-
- InputManager.MoveMouseTo(pos);
+ var hitObject = targetFunc();
+ InputManager.MoveMouseTo(getPosition(hitObject));
});
}
@@ -262,6 +264,56 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
}
+ [Test]
+ public void TestBasicDragSelection()
+ {
+ var addedObjects = new[]
+ {
+ new HitCircle { StartTime = 0 },
+ new HitCircle { StartTime = 500, Position = new Vector2(100) },
+ new HitCircle { StartTime = 1000, Position = new Vector2(200) },
+ new HitCircle { StartTime = 1500, Position = new Vector2(300) },
+ };
+ AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
+
+ AddStep("move mouse", () => InputManager.MoveMouseTo(getMiddlePosition(addedObjects[0], addedObjects[1])));
+ AddStep("mouse down", () => InputManager.PressButton(MouseButton.Left));
+
+ AddStep("drag to select", () => InputManager.MoveMouseTo(getMiddlePosition(addedObjects[2], addedObjects[3])));
+ assertSelectionIs(new[] { addedObjects[1], addedObjects[2] });
+
+ AddStep("drag to deselect", () => InputManager.MoveMouseTo(getMiddlePosition(addedObjects[1], addedObjects[2])));
+ assertSelectionIs(new[] { addedObjects[1] });
+
+ AddStep("mouse up", () => InputManager.ReleaseButton(MouseButton.Left));
+ assertSelectionIs(new[] { addedObjects[1] });
+ }
+
+ [Test]
+ public void TestFastDragSelection()
+ {
+ var addedObjects = new[]
+ {
+ new HitCircle { StartTime = 0 },
+ new HitCircle { StartTime = 500 },
+ new HitCircle { StartTime = 20000, Position = new Vector2(100) },
+ new HitCircle { StartTime = 31000, Position = new Vector2(200) },
+ new HitCircle { StartTime = 60000, Position = new Vector2(300) },
+ };
+
+ AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
+
+ AddStep("move mouse", () => InputManager.MoveMouseTo(getMiddlePosition(addedObjects[0], addedObjects[1])));
+ AddStep("mouse down", () => InputManager.PressButton(MouseButton.Left));
+ AddStep("start drag", () => InputManager.MoveMouseTo(getPosition(addedObjects[1])));
+
+ AddStep("jump editor clock", () => EditorClock.Seek(30000));
+ AddStep("jump editor clock", () => EditorClock.Seek(60000));
+ AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left));
+ assertSelectionIs(addedObjects.Skip(1));
+ AddAssert("all blueprints are present", () => blueprintContainer.SelectionBlueprints.Count == EditorBeatmap.SelectedHitObjects.Count);
+ }
+
private void assertSelectionIs(IEnumerable hitObjects)
=> AddAssert("correct hitobjects selected", () => EditorBeatmap.SelectedHitObjects.OrderBy(h => h.StartTime).SequenceEqual(hitObjects));
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs
index 72656c29b1..171ae829a9 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs
@@ -58,6 +58,16 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for some scores not masked away",
() => leaderboard.ChildrenOfType().Any(s => leaderboard.ScreenSpaceDrawQuad.Contains(s.ScreenSpaceDrawQuad.Centre)));
+
+ AddUntilStep("wait for tracked score fully visible", () => leaderboard.ScreenSpaceDrawQuad.Intersects(leaderboard.TrackedScore!.ScreenSpaceDrawQuad));
+
+ AddStep("change score to middle", () => playerScore.Value = 1000000);
+ AddWaitStep("wait for movement", 5);
+ AddUntilStep("wait for tracked score fully visible", () => leaderboard.ScreenSpaceDrawQuad.Intersects(leaderboard.TrackedScore!.ScreenSpaceDrawQuad));
+
+ AddStep("change score to first", () => playerScore.Value = 5000000);
+ AddWaitStep("wait for movement", 5);
+ AddUntilStep("wait for tracked score fully visible", () => leaderboard.ScreenSpaceDrawQuad.Intersects(leaderboard.TrackedScore!.ScreenSpaceDrawQuad));
}
[Test]
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
index 0a32513834..bd55ed8bd6 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
@@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
addSeekStep(3000);
AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
- AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses >= 7));
+ AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Select(kc => kc.CountPresses).Sum() == 15);
AddStep("clear results", () => Player.Results.Clear());
addSeekStep(0);
AddAssert("none judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged));
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
index da6604a653..a984f508ea 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
@@ -16,6 +16,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
+using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.HUD.HitErrorMeters;
using osu.Game.Skinning;
using osu.Game.Tests.Gameplay;
@@ -148,6 +149,42 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent);
}
+ [Test]
+ public void TestInputDoesntWorkWhenHUDHidden()
+ {
+ SongProgressBar getSongProgress() => hudOverlay.ChildrenOfType().Single();
+
+ bool seeked = false;
+
+ createNew();
+
+ AddStep("bind seek", () =>
+ {
+ seeked = false;
+
+ var progress = getSongProgress();
+
+ progress.ShowHandle = true;
+ progress.OnSeek += _ => seeked = true;
+ });
+
+ AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
+ AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
+
+ AddStep("attempt seek", () =>
+ {
+ InputManager.MoveMouseTo(getSongProgress());
+ InputManager.Click(MouseButton.Left);
+ });
+
+ AddAssert("seek not performed", () => !seeked);
+
+ AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
+
+ AddStep("attempt seek", () => InputManager.Click(MouseButton.Left));
+ AddAssert("seek performed", () => seeked);
+ }
+
[Test]
public void TestHiddenHUDDoesntBlockComponentUpdates()
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
index 8a4818d2f8..156a1ee34a 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
@@ -11,8 +11,10 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Threading;
+using osu.Framework.Timing;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
@@ -167,14 +169,39 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
}
- private void addHitObject(double time)
+ [Test]
+ public void TestVeryFlowScroll()
+ {
+ const double long_time_range = 100000;
+ var manualClock = new ManualClock();
+
+ AddStep("set manual clock", () =>
+ {
+ manualClock.CurrentTime = 0;
+ scrollContainers.ForEach(c => c.Clock = new FramedClock(manualClock));
+
+ setScrollAlgorithm(ScrollVisualisationMethod.Constant);
+ scrollContainers.ForEach(c => c.TimeRange = long_time_range);
+ });
+
+ AddStep("add hit objects", () =>
+ {
+ addHitObject(long_time_range);
+ addHitObject(long_time_range + 100, 250);
+ });
+
+ AddAssert("hit objects are alive", () => playfields.All(p => p.HitObjectContainer.AliveObjects.Count() == 2));
+ }
+
+ private void addHitObject(double time, float size = 75)
{
playfields.ForEach(p =>
{
- var hitObject = new TestDrawableHitObject(time);
- setAnchor(hitObject, p);
+ var hitObject = new TestHitObject(size) { StartTime = time };
+ var drawable = new TestDrawableHitObject(hitObject);
- p.Add(hitObject);
+ setAnchor(drawable, p);
+ p.Add(drawable);
});
}
@@ -248,6 +275,8 @@ namespace osu.Game.Tests.Visual.Gameplay
}
};
}
+
+ protected override ScrollingHitObjectContainer CreateScrollingHitObjectContainer() => new TestScrollingHitObjectContainer();
}
private class TestDrawableControlPoint : DrawableHitObject
@@ -281,22 +310,41 @@ namespace osu.Game.Tests.Visual.Gameplay
}
}
- private class TestDrawableHitObject : DrawableHitObject
+ private class TestHitObject : HitObject
{
- public TestDrawableHitObject(double time)
- : base(new HitObject { StartTime = time, HitWindows = HitWindows.Empty })
- {
- Origin = Anchor.Custom;
- OriginPosition = new Vector2(75 / 4.0f);
+ public readonly float Size;
- AutoSizeAxes = Axes.Both;
+ public TestHitObject(float size)
+ {
+ Size = size;
+ }
+ }
+
+ private class TestDrawableHitObject : DrawableHitObject
+ {
+ public TestDrawableHitObject(TestHitObject hitObject)
+ : base(hitObject)
+ {
+ Origin = Anchor.Centre;
+ Size = new Vector2(hitObject.Size);
AddInternal(new Box
{
- Size = new Vector2(75),
+ RelativeSizeAxes = Axes.Both,
Colour = new Color4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1)
});
}
}
+
+ private class TestScrollingHitObjectContainer : ScrollingHitObjectContainer
+ {
+ protected override RectangleF GetConservativeBoundingBox(HitObjectLifetimeEntry entry)
+ {
+ if (entry.HitObject is TestHitObject testObject)
+ return new RectangleF().Inflate(testObject.Size / 2);
+
+ return base.GetConservativeBoundingBox(entry);
+ }
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs
index ac73e88468..60ed0012ae 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSoloGameplayLeaderboard.cs
@@ -9,6 +9,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Framework.Utils;
+using osu.Game.Configuration;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
@@ -24,6 +25,16 @@ namespace osu.Game.Tests.Visual.Gameplay
private readonly BindableList scores = new BindableList