diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 33ec3d6602..56b3ebe87b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -88,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/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt
index 022da0a2ea..03fd21829d 100644
--- a/CodeAnalysis/BannedSymbols.txt
+++ b/CodeAnalysis/BannedSymbols.txt
@@ -15,6 +15,8 @@ M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Gen
M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks.
P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks.
M:System.Threading.ManualResetEventSlim.Wait();Specify a timeout to avoid waiting forever.
+M:System.Char.ToLower(System.Char);char.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture.
+M:System.Char.ToUpper(System.Char);char.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture.
M:System.String.ToLower();string.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString.
M:System.String.ToUpper();string.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString.
M:Humanizer.InflectorExtensions.Pascalize(System.String);Humanizer's .Pascalize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToPascalCase() instead.
diff --git a/Gemfile.lock b/Gemfile.lock
index cae682ec2b..07ca3542f9 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -3,25 +3,25 @@ GEM
specs:
CFPropertyList (3.0.5)
rexml
- addressable (2.8.0)
- public_suffix (>= 2.0.2, < 5.0)
+ addressable (2.8.1)
+ public_suffix (>= 2.0.2, < 6.0)
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.2.0)
- aws-partitions (1.601.0)
- aws-sdk-core (3.131.2)
+ aws-partitions (1.653.0)
+ aws-sdk-core (3.166.0)
aws-eventstream (~> 1, >= 1.0.2)
- aws-partitions (~> 1, >= 1.525.0)
- aws-sigv4 (~> 1.1)
+ aws-partitions (~> 1, >= 1.651.0)
+ aws-sigv4 (~> 1.5)
jmespath (~> 1, >= 1.6.1)
- aws-sdk-kms (1.57.0)
- aws-sdk-core (~> 3, >= 3.127.0)
+ aws-sdk-kms (1.59.0)
+ aws-sdk-core (~> 3, >= 3.165.0)
aws-sigv4 (~> 1.1)
- aws-sdk-s3 (1.114.0)
- aws-sdk-core (~> 3, >= 3.127.0)
+ aws-sdk-s3 (1.117.1)
+ aws-sdk-core (~> 3, >= 3.165.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
- aws-sigv4 (1.5.0)
+ aws-sigv4 (1.5.2)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.1.0)
@@ -34,10 +34,10 @@ GEM
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
- dotenv (2.7.6)
+ dotenv (2.8.1)
emoji_regex (3.2.3)
- excon (0.92.3)
- faraday (1.10.0)
+ excon (0.93.1)
+ faraday (1.10.2)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
@@ -66,7 +66,7 @@ GEM
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.6)
- fastlane (2.206.2)
+ fastlane (2.210.1)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
@@ -110,9 +110,9 @@ GEM
souyuz (= 0.11.1)
fastlane-plugin-xamarin (0.6.3)
gh_inspector (1.1.3)
- google-apis-androidpublisher_v3 (0.23.0)
- google-apis-core (>= 0.6, < 2.a)
- google-apis-core (0.6.0)
+ google-apis-androidpublisher_v3 (0.29.0)
+ google-apis-core (>= 0.9.0, < 2.a)
+ google-apis-core (0.9.1)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
@@ -121,27 +121,27 @@ GEM
retriable (>= 2.0, < 4.a)
rexml
webrick
- google-apis-iamcredentials_v1 (0.12.0)
- google-apis-core (>= 0.6, < 2.a)
- google-apis-playcustomapp_v1 (0.9.0)
- google-apis-core (>= 0.6, < 2.a)
- google-apis-storage_v1 (0.16.0)
- google-apis-core (>= 0.6, < 2.a)
+ google-apis-iamcredentials_v1 (0.15.0)
+ google-apis-core (>= 0.9.0, < 2.a)
+ google-apis-playcustomapp_v1 (0.12.0)
+ google-apis-core (>= 0.9.1, < 2.a)
+ google-apis-storage_v1 (0.19.0)
+ google-apis-core (>= 0.9.0, < 2.a)
google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
- google-cloud-errors (1.2.0)
- google-cloud-storage (1.36.2)
+ google-cloud-errors (1.3.0)
+ google-cloud-storage (1.43.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
- google-apis-storage_v1 (~> 0.1)
+ google-apis-storage_v1 (~> 0.19.0)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
- googleauth (1.2.0)
+ googleauth (1.3.0)
faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
@@ -154,22 +154,22 @@ GEM
httpclient (2.8.3)
jmespath (1.6.1)
json (2.6.2)
- jwt (2.4.1)
+ jwt (2.5.0)
memoist (0.16.2)
mini_magick (4.11.0)
mini_mime (1.1.2)
- mini_portile2 (2.7.1)
+ mini_portile2 (2.8.0)
multi_json (1.15.0)
multipart-post (2.0.0)
nanaimo (0.3.0)
naturally (2.2.1)
- nokogiri (1.13.1)
- mini_portile2 (~> 2.7.0)
+ nokogiri (1.13.9)
+ mini_portile2 (~> 2.8.0)
racc (~> 1.4)
optparse (0.1.1)
os (1.1.4)
plist (3.6.0)
- public_suffix (4.0.7)
+ public_suffix (5.0.0)
racc (1.6.0)
rake (13.0.6)
representable (3.2.0)
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/fastlane/Fastfile b/fastlane/Fastfile
index cc5abf5b03..716115e5c6 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -138,10 +138,10 @@ platform :ios do
end
lane :testflight_prune_dry do
- clean_testflight_testers(days_of_inactivity:45, dry_run: true)
+ clean_testflight_testers(days_of_inactivity:30, dry_run: true)
end
lane :testflight_prune do
- clean_testflight_testers(days_of_inactivity: 45)
+ clean_testflight_testers(days_of_inactivity: 30)
end
end
diff --git a/osu.Android.props b/osu.Android.props
index c33937ad95..b691751f13 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..09f7292845 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -18,17 +18,9 @@ using osu.Framework;
using osu.Framework.Logging;
using osu.Game.Updater;
using osu.Desktop.Windows;
-using osu.Framework.Input.Handlers;
-using osu.Framework.Input.Handlers.Joystick;
-using osu.Framework.Input.Handlers.Mouse;
-using osu.Framework.Input.Handlers.Tablet;
-using osu.Framework.Input.Handlers.Touch;
using osu.Framework.Threading;
using osu.Game.IO;
using osu.Game.IPC;
-using osu.Game.Overlays.Settings;
-using osu.Game.Overlays.Settings.Sections;
-using osu.Game.Overlays.Settings.Sections.Input;
using osu.Game.Utils;
using SDL2;
@@ -137,37 +129,17 @@ 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 });
}
- public override SettingsSubsection CreateSettingsSubsectionFor(InputHandler handler)
- {
- switch (handler)
- {
- case ITabletHandler th:
- return new TabletSettings(th);
-
- case MouseHandler mh:
- return new MouseSettings(mh);
-
- case JoystickHandler jh:
- return new JoystickSettings(jh);
-
- case TouchHandler th:
- return new InputSection.HandlerSection(th);
-
- default:
- return base.CreateSettingsSubsectionFor(handler);
- }
- }
-
protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo();
private readonly List importableFiles = new List();
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/Mods/TestSceneCatchModFlashlight.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModFlashlight.cs
new file mode 100644
index 0000000000..538fc7fac6
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModFlashlight.cs
@@ -0,0 +1,23 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Rulesets.Catch.Mods;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Catch.Tests.Mods
+{
+ public class TestSceneCatchModFlashlight : ModTestScene
+ {
+ protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
+
+ [TestCase(1f)]
+ [TestCase(0.5f)]
+ [TestCase(1.25f)]
+ [TestCase(1.5f)]
+ public void TestSizeMultiplier(float sizeMultiplier) => CreateModTest(new ModTestData { Mod = new CatchModFlashlight { SizeMultiplier = { Value = sizeMultiplier } }, PassCondition = () => true });
+
+ [Test]
+ public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new CatchModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
+ }
+}
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/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index 5c9c95827a..e0f7820262 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -17,6 +17,7 @@ using osu.Game.Rulesets.Catch.Edit;
using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Catch.Scoring;
+using osu.Game.Rulesets.Catch.Skinning.Argon;
using osu.Game.Rulesets.Catch.Skinning.Legacy;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Difficulty;
@@ -188,6 +189,9 @@ namespace osu.Game.Rulesets.Catch
{
case LegacySkin:
return new CatchLegacySkinTransformer(skin);
+
+ case ArgonSkin:
+ return new CatchArgonSkinTransformer(skin);
}
return null;
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/Edit/CatchEditorPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfieldAdjustmentContainer.cs
new file mode 100644
index 0000000000..0a0f91c781
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfieldAdjustmentContainer.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.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.UI;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Edit
+{
+ public class CatchEditorPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer
+ {
+ protected override Container Content => content;
+ private readonly Container content;
+
+ public CatchEditorPlayfieldAdjustmentContainer()
+ {
+ Anchor = Anchor.TopCentre;
+ Origin = Anchor.TopCentre;
+ Size = new Vector2(0.8f, 0.9f);
+
+ InternalChild = new ScalingContainer
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Child = content = new Container { RelativeSizeAxes = Axes.Both },
+ };
+ }
+
+ private class ScalingContainer : Container
+ {
+ public ScalingContainer()
+ {
+ RelativeSizeAxes = Axes.Y;
+ Width = CatchPlayfield.WIDTH;
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ Scale = new Vector2(Math.Min(Parent.ChildSize.X / CatchPlayfield.WIDTH, Parent.ChildSize.Y / CatchPlayfield.HEIGHT));
+ Height = 1 / Scale.Y;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
index f31dc3ef9c..220bc49203 100644
--- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
@@ -3,6 +3,7 @@
#nullable disable
+using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
@@ -10,10 +11,11 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
+using osu.Framework.Input.Events;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Edit;
@@ -21,7 +23,6 @@ using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI;
-using osu.Game.Screens.Edit.Components.TernaryButtons;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
@@ -33,10 +34,14 @@ namespace osu.Game.Rulesets.Catch.Edit
private CatchDistanceSnapGrid distanceSnapGrid;
- private readonly Bindable distanceSnapToggle = new Bindable();
-
private InputManager inputManager;
+ private readonly BindableDouble timeRangeMultiplier = new BindableDouble(1)
+ {
+ MinValue = 1,
+ MaxValue = 10,
+ };
+
public CatchHitObjectComposer(CatchRuleset ruleset)
: base(ruleset)
{
@@ -51,7 +56,10 @@ namespace osu.Game.Rulesets.Catch.Edit
LayerBelowRuleset.Add(new PlayfieldBorder
{
- RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre,
+ RelativeSizeAxes = Axes.X,
+ Height = CatchPlayfield.HEIGHT,
PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners }
});
@@ -70,6 +78,19 @@ namespace osu.Game.Rulesets.Catch.Edit
inputManager = GetContainingInputManager();
}
+ protected override double ReadCurrentDistanceSnap(HitObject before, HitObject after)
+ {
+ // osu!catch's distance snap implementation is limited, in that a custom spacing cannot be specified.
+ // Therefore this functionality is not currently used.
+ //
+ // The implementation below is probably correct but should be checked if/when exposed via controls.
+
+ float expectedDistance = DurationToDistance(before, after.StartTime - before.GetEndTime());
+ float actualDistance = Math.Abs(((CatchHitObject)before).EffectiveX - ((CatchHitObject)after).EffectiveX);
+
+ return actualDistance / expectedDistance;
+ }
+
protected override void Update()
{
base.Update();
@@ -77,8 +98,30 @@ namespace osu.Game.Rulesets.Catch.Edit
updateDistanceSnapGrid();
}
+ public override bool OnPressed(KeyBindingPressEvent e)
+ {
+ switch (e.Action)
+ {
+ // Note that right now these are hard to use as the default key bindings conflict with existing editor key bindings.
+ // In the future we will want to expose this via UI and potentially change the key bindings to be editor-specific.
+ // May be worth considering standardising "zoom" behaviour with what the timeline uses (ie. alt-wheel) but that may cause new conflicts.
+ case GlobalAction.IncreaseScrollSpeed:
+ this.TransformBindableTo(timeRangeMultiplier, timeRangeMultiplier.Value - 1, 200, Easing.OutQuint);
+ break;
+
+ case GlobalAction.DecreaseScrollSpeed:
+ this.TransformBindableTo(timeRangeMultiplier, timeRangeMultiplier.Value + 1, 200, Easing.OutQuint);
+ break;
+ }
+
+ return base.OnPressed(e);
+ }
+
protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) =>
- new DrawableCatchEditorRuleset(ruleset, beatmap, mods);
+ new DrawableCatchEditorRuleset(ruleset, beatmap, mods)
+ {
+ TimeRangeMultiplier = { BindTarget = timeRangeMultiplier, }
+ };
protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[]
{
@@ -87,11 +130,6 @@ namespace osu.Game.Rulesets.Catch.Edit
new BananaShowerCompositionTool()
};
- protected override IEnumerable CreateTernaryButtons() => base.CreateTernaryButtons().Concat(new[]
- {
- new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler })
- });
-
public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
{
var result = base.FindSnappedPositionAndTime(screenSpacePosition, snapType);
@@ -163,7 +201,7 @@ namespace osu.Game.Rulesets.Catch.Edit
private void updateDistanceSnapGrid()
{
- if (distanceSnapToggle.Value != TernaryState.True)
+ if (DistanceSnapToggle.Value != TernaryState.True)
{
distanceSnapGrid.Hide();
return;
diff --git a/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs b/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs
index c81afafae5..67238f66d4 100644
--- a/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs
+++ b/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs
@@ -4,6 +4,7 @@
#nullable disable
using System.Collections.Generic;
+using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods;
@@ -13,11 +14,24 @@ namespace osu.Game.Rulesets.Catch.Edit
{
public class DrawableCatchEditorRuleset : DrawableCatchRuleset
{
+ public readonly BindableDouble TimeRangeMultiplier = new BindableDouble(1);
+
public DrawableCatchEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null)
: base(ruleset, beatmap, mods)
{
}
+ protected override void Update()
+ {
+ base.Update();
+
+ double gamePlayTimeRange = GetTimeRange(Beatmap.Difficulty.ApproachRate);
+ float playfieldStretch = Playfield.DrawHeight / CatchPlayfield.HEIGHT;
+ TimeRange.Value = gamePlayTimeRange * TimeRangeMultiplier.Value * playfieldStretch;
+ }
+
protected override Playfield CreatePlayfield() => new CatchEditorPlayfield(Beatmap.Difficulty);
+
+ public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchEditorPlayfieldAdjustmentContainer();
}
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs
index 1adc969f8f..a9e9e8fbd5 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Mods
public override BindableBool ComboBasedSize { get; } = new BindableBool(true);
- public override float DefaultFlashlightSize => 350;
+ public override float DefaultFlashlightSize => 325;
protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield);
@@ -44,7 +44,19 @@ namespace osu.Game.Rulesets.Catch.Mods
: base(modFlashlight)
{
this.playfield = playfield;
- FlashlightSize = new Vector2(0, GetSizeFor(0));
+
+ FlashlightSize = new Vector2(0, GetSize());
+ FlashlightSmoothness = 1.4f;
+ }
+
+ protected override float GetComboScaleFor(int combo)
+ {
+ if (combo >= 200)
+ return 0.770f;
+ if (combo >= 100)
+ return 0.885f;
+
+ return 1.0f;
}
protected override void Update()
@@ -54,9 +66,9 @@ namespace osu.Game.Rulesets.Catch.Mods
FlashlightPosition = playfield.CatcherArea.ToSpaceOfOtherDrawable(playfield.Catcher.DrawPosition, this);
}
- protected override void OnComboChange(ValueChangedEvent e)
+ protected override void UpdateFlashlightSize(float size)
{
- this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
+ this.TransformTo(nameof(FlashlightSize), new Vector2(0, size), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "CircularFlashlight";
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
index 69ae8328e9..3f7560844c 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
@@ -19,17 +19,20 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public override LocalisableString Description => @"Use the mouse to control the catcher.";
- private DrawableRuleset drawableRuleset = null!;
+ private DrawableCatchRuleset drawableRuleset = null!;
public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
- this.drawableRuleset = drawableRuleset;
+ this.drawableRuleset = (DrawableCatchRuleset)drawableRuleset;
}
public void ApplyToPlayer(Player player)
{
if (!drawableRuleset.HasReplayLoaded.Value)
- drawableRuleset.Cursor.Add(new MouseInputHelper((CatchPlayfield)drawableRuleset.Playfield));
+ {
+ var catchPlayfield = (CatchPlayfield)drawableRuleset.Playfield;
+ catchPlayfield.CatcherArea.Add(new MouseInputHelper(catchPlayfield.CatcherArea));
+ }
}
private class MouseInputHelper : Drawable, IKeyBindingHandler, IRequireHighFrequencyMousePosition
@@ -38,9 +41,10 @@ namespace osu.Game.Rulesets.Catch.Mods
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
- public MouseInputHelper(CatchPlayfield playfield)
+ public MouseInputHelper(CatcherArea catcherArea)
{
- catcherArea = playfield.CatcherArea;
+ this.catcherArea = catcherArea;
+
RelativeSizeAxes = Axes.Both;
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs
index fd0ffbd032..ddfbb34435 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs
@@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
public float DisplayRotation => Rotation;
+ public double DisplayStartTime => HitObject.StartTime;
+
///
/// Whether this hit object should stay on the catcher plate when the object is caught by the catcher.
///
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs
index 5de372852b..dd09b6c06d 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs
@@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
public new PalpableCatchHitObject HitObject => (PalpableCatchHitObject)base.HitObject;
+ public double DisplayStartTime => LifetimeStart;
+
Bindable IHasCatchObjectState.AccentColour => AccentColour;
public Bindable HyperDash { get; } = new Bindable();
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs
index 93c80b09db..f30ef0831a 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs
@@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
PalpableCatchHitObject HitObject { get; }
+ double DisplayStartTime { get; }
+
Bindable AccentColour { get; }
Bindable HyperDash { get; }
diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonBananaPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonBananaPiece.cs
new file mode 100644
index 0000000000..9a657c9216
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonBananaPiece.cs
@@ -0,0 +1,122 @@
+// 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.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Catch.Objects;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Argon
+{
+ internal class ArgonBananaPiece : ArgonFruitPiece
+ {
+ private Container stabilisedPieceContainer = null!;
+
+ private Drawable fadeContent = null!;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ AddInternal(fadeContent = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ stabilisedPieceContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Children = new Drawable[]
+ {
+ new Circle
+ {
+ Colour = Color4.White.Opacity(0.4f),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Blending = BlendingParameters.Additive,
+ Size = new Vector2(8),
+ Scale = new Vector2(25, 1),
+ },
+ new Box
+ {
+ Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0), Color4.White.Opacity(0.8f)),
+ RelativeSizeAxes = Axes.X,
+ Blending = BlendingParameters.Additive,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.CentreRight,
+ Width = 1.6f,
+ Height = 2,
+ },
+ new Circle
+ {
+ Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0.8f), Color4.White.Opacity(0)),
+ RelativeSizeAxes = Axes.X,
+ Blending = BlendingParameters.Additive,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.CentreLeft,
+ Width = 1.6f,
+ Height = 2,
+ },
+ }
+ },
+ new Circle
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(1.2f),
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Hollow = false,
+ Colour = Color4.White.Opacity(0.1f),
+ Radius = 50,
+ },
+ Child =
+ {
+ Alpha = 0,
+ AlwaysPresent = true,
+ },
+ BorderColour = Color4.White.Opacity(0.1f),
+ BorderThickness = 3,
+ },
+ }
+ });
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ const float parent_scale_application = 0.4f;
+
+ // relative to time on screen
+ const float lens_flare_start = 0.3f;
+ const float lens_flare_end = 0.8f;
+
+ // Undo some of the parent scale being applied to make the lens flare feel a bit better..
+ float scale = parent_scale_application + (1 - parent_scale_application) * (1 / (ObjectState.DisplaySize.X / (CatchHitObject.OBJECT_RADIUS * 2)));
+
+ stabilisedPieceContainer.Rotation = -ObjectState.DisplayRotation;
+ stabilisedPieceContainer.Scale = new Vector2(scale, 1);
+
+ double duration = ObjectState.HitObject.StartTime - ObjectState.DisplayStartTime;
+
+ fadeContent.Alpha = MathHelper.Clamp(
+ Interpolation.ValueAt(
+ Time.Current, 1f, 0f,
+ ObjectState.DisplayStartTime + duration * lens_flare_start,
+ ObjectState.DisplayStartTime + duration * lens_flare_end,
+ Easing.OutQuint
+ ), 0, 1);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonCatcher.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonCatcher.cs
new file mode 100644
index 0000000000..4db0df4a34
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonCatcher.cs
@@ -0,0 +1,85 @@
+// 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;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.Catch.UI;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Argon
+{
+ public class ArgonCatcher : CompositeDrawable
+ {
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ InternalChildren = new Drawable[]
+ {
+ new Container
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ RelativeSizeAxes = Axes.X,
+ Height = 10,
+ Children = new Drawable[]
+ {
+ new Circle
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Colour = Color4.White,
+ Width = Catcher.ALLOWED_CATCH_RANGE,
+ },
+ new Box
+ {
+ Name = "long line left",
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreRight,
+ Colour = Color4.White,
+ Alpha = 0.25f,
+ RelativeSizeAxes = Axes.X,
+ Width = 20,
+ Height = 1.8f,
+ },
+ new Circle
+ {
+ Name = "bumper left",
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Colour = Color4.White,
+ RelativeSizeAxes = Axes.X,
+ Width = (1 - Catcher.ALLOWED_CATCH_RANGE) / 2,
+ Height = 4,
+ },
+ new Box
+ {
+ Name = "long line right",
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreLeft,
+ Colour = Color4.White,
+ Alpha = 0.25f,
+ RelativeSizeAxes = Axes.X,
+ Width = 20,
+ Height = 1.8f,
+ },
+ new Circle
+ {
+ Name = "bumper right",
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ Colour = Color4.White,
+ RelativeSizeAxes = Axes.X,
+ Width = (1 - Catcher.ALLOWED_CATCH_RANGE) / 2,
+ Height = 4,
+ },
+ }
+ },
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonDropletPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonDropletPiece.cs
new file mode 100644
index 0000000000..267f8a06a3
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonDropletPiece.cs
@@ -0,0 +1,121 @@
+// 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;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Catch.Skinning.Default;
+using osu.Game.Rulesets.Catch.UI;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Argon
+{
+ internal class ArgonDropletPiece : CatchHitObjectPiece
+ {
+ protected override Drawable HyperBorderPiece => hyperBorderPiece;
+
+ private Drawable hyperBorderPiece = null!;
+
+ private Container layers = null!;
+
+ private float rotationRandomness;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ const float droplet_scale_down = 0.7f;
+
+ int largeBlobSeed = RNG.Next();
+
+ InternalChildren = new[]
+ {
+ new Circle
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(20),
+ },
+ layers = new Container
+ {
+ Scale = new Vector2(droplet_scale_down),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new CircularBlob
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Blending = BlendingParameters.Additive,
+ InnerRadius = 0.5f,
+ Alpha = 0.15f,
+ Seed = largeBlobSeed
+ },
+ new CircularBlob
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Blending = BlendingParameters.Additive,
+ InnerRadius = 0.4f,
+ Alpha = 0.5f,
+ Scale = new Vector2(0.7f),
+ Seed = RNG.Next()
+ },
+ }
+ },
+ hyperBorderPiece = new CircularBlob
+ {
+ Scale = new Vector2(droplet_scale_down),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
+ RelativeSizeAxes = Axes.Both,
+ Blending = BlendingParameters.Additive,
+ InnerRadius = 0.5f,
+ Alpha = 0.15f,
+ Seed = largeBlobSeed
+ },
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ AccentColour.BindValueChanged(colour =>
+ {
+ foreach (var sprite in layers)
+ sprite.Colour = colour.NewValue;
+ }, true);
+
+ rotationRandomness = RNG.NextSingle(0.2f, 1);
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ // Note that droplets are rotated at a higher level, so this is mostly just to create more
+ // random arrangements of the multiple layers than actually rotate.
+ //
+ // Because underlying rotation is always clockwise, we apply anti-clockwise resistance to avoid
+ // making things spin too fast.
+ for (int i = 0; i < layers.Count; i++)
+ {
+ layers[i].Rotation -=
+ (float)Clock.ElapsedFrameTime
+ * 0.4f * rotationRandomness
+ // Each layer should alternate rotation speed.
+ * (i % 2 == 1 ? 0.5f : 1);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonFruitPiece.cs
new file mode 100644
index 0000000000..28538d48b3
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonFruitPiece.cs
@@ -0,0 +1,121 @@
+// 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;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Catch.Skinning.Default;
+using osu.Game.Rulesets.Catch.UI;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Argon
+{
+ internal class ArgonFruitPiece : CatchHitObjectPiece
+ {
+ protected override Drawable HyperBorderPiece => hyperBorderPiece;
+
+ private Drawable hyperBorderPiece = null!;
+
+ private Container layers = null!;
+
+ private float rotationRandomness;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ int largeBlobSeed = RNG.Next();
+
+ InternalChildren = new[]
+ {
+ new Circle
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(20),
+ },
+ layers = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new CircularBlob
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Blending = BlendingParameters.Additive,
+ Alpha = 0.15f,
+ InnerRadius = 0.5f,
+ Size = new Vector2(1.1f),
+ Seed = largeBlobSeed,
+ },
+ new CircularBlob
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Blending = BlendingParameters.Additive,
+ InnerRadius = 0.2f,
+ Alpha = 0.5f,
+ Seed = RNG.Next(),
+ },
+ new CircularBlob
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Blending = BlendingParameters.Additive,
+ InnerRadius = 0.05f,
+ Seed = RNG.Next(),
+ },
+ }
+ },
+ hyperBorderPiece = new CircularBlob
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
+ RelativeSizeAxes = Axes.Both,
+ Blending = BlendingParameters.Additive,
+ InnerRadius = 0.08f,
+ Size = new Vector2(1.15f),
+ Seed = largeBlobSeed
+ },
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ AccentColour.BindValueChanged(colour =>
+ {
+ foreach (var sprite in layers)
+ sprite.Colour = colour.NewValue;
+ }, true);
+
+ rotationRandomness = RNG.NextSingle(0.2f, 1) * (RNG.NextBool() ? -1 : 1);
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ for (int i = 0; i < layers.Count; i++)
+ {
+ layers[i].Rotation +=
+ // Layers are ordered from largest to smallest. Smaller layers should rotate more.
+ (i * 2)
+ * (float)Clock.ElapsedFrameTime
+ * 0.02f * rotationRandomness
+ // Each layer should alternate rotation direction.
+ * (i % 2 == 1 ? 1 : -1);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonHitExplosion.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonHitExplosion.cs
new file mode 100644
index 0000000000..90dca49dfd
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonHitExplosion.cs
@@ -0,0 +1,112 @@
+// 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.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.Catch.Objects;
+using osu.Game.Rulesets.Catch.UI;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Argon
+{
+ public class ArgonHitExplosion : CompositeDrawable, IHitExplosion
+ {
+ public override bool RemoveWhenNotAlive => true;
+
+ private Container tallExplosion = null!;
+ private Container largeFaint = null!;
+
+ private readonly Bindable accentColour = new Bindable();
+
+ public ArgonHitExplosion()
+ {
+ Size = new Vector2(20);
+ Anchor = Anchor.BottomCentre;
+ Origin = Anchor.BottomCentre;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ InternalChildren = new Drawable[]
+ {
+ tallExplosion = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Width = 0.1f,
+ Child = new Box
+ {
+ AlwaysPresent = true,
+ Alpha = 0,
+ RelativeSizeAxes = Axes.Both,
+ },
+ },
+ largeFaint = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Child = new Box
+ {
+ AlwaysPresent = true,
+ Alpha = 0,
+ RelativeSizeAxes = Axes.Both,
+ },
+ },
+ };
+
+ accentColour.BindValueChanged(colour =>
+ {
+ tallExplosion.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = colour.NewValue,
+ Hollow = false,
+ Roundness = 15,
+ Radius = 15,
+ };
+
+ largeFaint.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.2f, colour.NewValue, Color4.White, 0, 1),
+ Hollow = false,
+ Radius = 50,
+ };
+ }, true);
+ }
+
+ public void Animate(HitExplosionEntry entry)
+ {
+ X = entry.Position;
+ Scale = new Vector2(entry.HitObject.Scale);
+ accentColour.Value = entry.ObjectColour;
+
+ using (BeginAbsoluteSequence(entry.LifetimeStart))
+ {
+ this.FadeOutFromOne(400);
+
+ if (!(entry.HitObject is Droplet))
+ {
+ float scale = Math.Clamp(entry.JudgementResult.ComboAtJudgement / 200f, 0.35f, 1.125f);
+
+ tallExplosion
+ .ScaleTo(new Vector2(1.1f, 20 * scale), 200, Easing.OutQuint)
+ .Then()
+ .ScaleTo(new Vector2(1.1f, 1), 600, Easing.In);
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs
new file mode 100644
index 0000000000..59e8b5a0b3
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/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.Catch.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.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs
new file mode 100644
index 0000000000..8dae0a2b78
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs
@@ -0,0 +1,46 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Argon
+{
+ public class CatchArgonSkinTransformer : SkinTransformer
+ {
+ public CatchArgonSkinTransformer(ISkin skin)
+ : base(skin)
+ {
+ }
+
+ public override Drawable? GetDrawableComponent(ISkinComponent component)
+ {
+ switch (component)
+ {
+ case CatchSkinComponent catchComponent:
+ // TODO: Once everything is finalised, consider throwing UnsupportedSkinComponentException on missing entries.
+ switch (catchComponent.Component)
+ {
+ case CatchSkinComponents.HitExplosion:
+ return new ArgonHitExplosion();
+
+ case CatchSkinComponents.Catcher:
+ return new ArgonCatcher();
+
+ case CatchSkinComponents.Fruit:
+ return new ArgonFruitPiece();
+
+ case CatchSkinComponents.Banana:
+ return new ArgonBananaPiece();
+
+ case CatchSkinComponents.Droplet:
+ return new ArgonDropletPiece();
+ }
+
+ break;
+ }
+
+ return base.GetDrawableComponent(component);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/BananaPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Default/BananaPiece.cs
index 27252594af..359756f159 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Default/BananaPiece.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/BananaPiece.cs
@@ -1,21 +1,19 @@
// 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;
namespace osu.Game.Rulesets.Catch.Skinning.Default
{
public class BananaPiece : CatchHitObjectPiece
{
- protected override BorderPiece BorderPiece { get; }
+ protected override Drawable BorderPiece { get; }
public BananaPiece()
{
RelativeSizeAxes = Axes.Both;
- InternalChildren = new Drawable[]
+ InternalChildren = new[]
{
new BananaPulpFormation
{
diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/CatchHitObjectPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Default/CatchHitObjectPiece.cs
index 6cc5220699..3b8df6ee6f 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Default/CatchHitObjectPiece.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/CatchHitObjectPiece.cs
@@ -7,6 +7,7 @@ using System;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osuTK.Graphics;
@@ -26,13 +27,13 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
/// A part of this piece that will be faded out while falling in the playfield.
///
[CanBeNull]
- protected virtual BorderPiece BorderPiece => null;
+ protected virtual Drawable BorderPiece => null;
///
/// A part of this piece that will be only visible when is true.
///
[CanBeNull]
- protected virtual HyperBorderPiece HyperBorderPiece => null;
+ protected virtual Drawable HyperBorderPiece => null;
protected override void LoadComplete()
{
diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/DropletPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Default/DropletPiece.cs
index 6b7f25eed1..b8ae062382 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Default/DropletPiece.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/DropletPiece.cs
@@ -11,13 +11,13 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
{
public class DropletPiece : CatchHitObjectPiece
{
- protected override HyperBorderPiece HyperBorderPiece { get; }
+ protected override Drawable HyperBorderPiece { get; }
public DropletPiece()
{
Size = new Vector2(CatchHitObject.OBJECT_RADIUS / 2);
- InternalChildren = new Drawable[]
+ InternalChildren = new[]
{
new Pulp
{
diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/FruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Default/FruitPiece.cs
index 8fb5c8f84a..adee960c3c 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Default/FruitPiece.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/FruitPiece.cs
@@ -18,14 +18,14 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
public readonly Bindable VisualRepresentation = new Bindable();
- protected override BorderPiece BorderPiece { get; }
- protected override HyperBorderPiece HyperBorderPiece { get; }
+ protected override Drawable BorderPiece { get; }
+ protected override Drawable HyperBorderPiece { get; }
public FruitPiece()
{
RelativeSizeAxes = Axes.Both;
- InternalChildren = new Drawable[]
+ InternalChildren = new[]
{
new FruitPulpFormation
{
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
index c06d9f520f..a73b34c9b6 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
@@ -13,6 +13,10 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
{
public class CatchLegacySkinTransformer : LegacySkinTransformer
{
+ public override bool IsProvidingLegacyResources => base.IsProvidingLegacyResources || hasPear;
+
+ private bool hasPear => GetTexture("fruit-pear") != null;
+
///
/// For simplicity, let's use legacy combo font texture existence as a way to identify legacy skins from default.
///
@@ -49,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
switch (catchSkinComponent.Component)
{
case CatchSkinComponents.Fruit:
- if (GetTexture("fruit-pear") != null)
+ if (hasPear)
return new LegacyFruitPiece();
return null;
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/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
index dad22fbe69..ce000b0fad 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
@@ -23,6 +23,12 @@ namespace osu.Game.Rulesets.Catch.UI
///
public const float WIDTH = 512;
+ ///
+ /// The height of the playfield.
+ /// This doesn't include the catcher area.
+ ///
+ public const float HEIGHT = 384;
+
///
/// The center position of the playfield.
///
diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
index ef2936ac94..e02b915508 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;
@@ -30,15 +31,19 @@ namespace osu.Game.Rulesets.Catch.UI
: base(ruleset, beatmap, mods)
{
Direction.Value = ScrollingDirection.Down;
- TimeRange.Value = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450);
+ TimeRange.Value = GetTimeRange(beatmap.Difficulty.ApproachRate);
}
[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 double GetTimeRange(float approachRate) => IBeatmapDifficultyInfo.DifficultyRange(approachRate, 1800, 1200, 450);
+
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
protected override ReplayRecorder CreateReplayRecorder(Score score) => new CatchReplayRecorder(score, (CatchPlayfield)Playfield);
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/TestSceneManiaModFlashlight.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFlashlight.cs
new file mode 100644
index 0000000000..0e222fea89
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModFlashlight.cs
@@ -0,0 +1,23 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Rulesets.Mania.Mods;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Mania.Tests.Mods
+{
+ public class TestSceneManiaModFlashlight : ModTestScene
+ {
+ protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
+
+ [TestCase(1f)]
+ [TestCase(0.5f)]
+ [TestCase(1.5f)]
+ [TestCase(3f)]
+ public void TestSizeMultiplier(float sizeMultiplier) => CreateModTest(new ModTestData { Mod = new ManiaModFlashlight { SizeMultiplier = { Value = sizeMultiplier } }, PassCondition = () => true });
+
+ [Test]
+ public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new ManiaModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
+ }
+}
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/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
index 8f776ff507..0296303867 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
@@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania.Tests
assertHeadJudgement(HitResult.Miss);
assertTickJudgement(HitResult.LargeTickMiss);
assertTailJudgement(HitResult.Miss);
- assertNoteJudgement(HitResult.IgnoreHit);
+ assertNoteJudgement(HitResult.IgnoreMiss);
}
///
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs
index a563dc3106..464dbecee5 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs
@@ -76,8 +76,8 @@ namespace osu.Game.Rulesets.Mania.Tests
performTest(objects, new List());
- addJudgementAssert(objects[0], HitResult.IgnoreHit);
- addJudgementAssert(objects[1], HitResult.IgnoreHit);
+ addJudgementAssert(objects[0], HitResult.IgnoreMiss);
+ addJudgementAssert(objects[1], HitResult.IgnoreMiss);
}
[Test]
@@ -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/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs
index 6eaede2112..947915cdf9 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public ManiaFlashlight(ManiaModFlashlight modFlashlight)
: base(modFlashlight)
{
- FlashlightSize = new Vector2(DrawWidth, GetSizeFor(0));
+ FlashlightSize = new Vector2(DrawWidth, GetSize());
AddLayout(flashlightProperties);
}
@@ -54,9 +54,9 @@ namespace osu.Game.Rulesets.Mania.Mods
}
}
- protected override void OnComboChange(ValueChangedEvent e)
+ protected override void UpdateFlashlightSize(float size)
{
- this.TransformTo(nameof(FlashlightSize), new Vector2(DrawWidth, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
+ this.TransformTo(nameof(FlashlightSize), new Vector2(DrawWidth, size), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "RectangularFlashlight";
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..14dbc432ff 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.
///
@@ -65,6 +69,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
///
private double? releaseTime;
+ public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
+
public DrawableHoldNote()
: this(null)
{
@@ -108,6 +114,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 +125,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
});
}
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ isHitting.BindValueChanged(updateSlidingSample, true);
+ }
+
protected override void OnApply()
{
base.OnApply();
@@ -248,7 +262,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
tick.MissForcefully();
}
- ApplyResult(r => r.Type = r.Judgement.MaxResult);
+ ApplyResult(r => r.Type = Tail.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
endHold();
}
@@ -322,5 +336,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/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..a07dbea368 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,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public class ManiaLegacySkinTransformer : LegacySkinTransformer
{
- private readonly ManiaBeatmap beatmap;
+ public override bool IsProvidingLegacyResources => base.IsProvidingLegacyResources || hasKeyTexture.Value;
///
/// Mapping of to their corresponding
@@ -60,6 +59,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 +114,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 +151,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/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs
index c102678e00..1b67fc2ca9 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs
@@ -4,14 +4,18 @@
#nullable disable
using System;
+using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
+using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Overlays;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Edit;
@@ -33,6 +37,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
[Cached(typeof(IBeatSnapProvider))]
private readonly EditorBeatmap editorBeatmap;
+ [Cached]
+ private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
+
[Cached]
private readonly EditorClock editorClock;
@@ -48,6 +55,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
};
private OsuDistanceSnapGrid grid;
+ private SnappingCursorContainer cursor;
public TestSceneOsuDistanceSnapGrid()
{
@@ -84,8 +92,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray
},
+ cursor = new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position },
grid = new OsuDistanceSnapGrid(new HitCircle { Position = grid_position }),
- new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position }
};
});
@@ -150,6 +158,37 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertSnappedDistance(expectedDistance);
}
+ [Test]
+ public void TestReferenceObjectNotOnSnapGrid()
+ {
+ AddStep("create grid", () =>
+ {
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.SlateGray
+ },
+ cursor = new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position },
+ grid = new OsuDistanceSnapGrid(new HitCircle
+ {
+ Position = grid_position,
+ // This is important. It sets the reference object to a point in time that isn't on the current snap divisor's grid.
+ // We are testing that the grid's display is offset correctly.
+ StartTime = 40,
+ }),
+ };
+ });
+
+ AddStep("move mouse to point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2(beat_length, 0) * 2)));
+
+ AddAssert("Ensure cursor is on a grid line", () =>
+ {
+ return grid.ChildrenOfType().Any(p => Precision.AlmostEquals(p.ScreenSpaceDrawQuad.TopRight.X, grid.ToScreenSpace(cursor.LastSnappedPosition).X));
+ });
+ }
+
[Test]
public void TestLimitedDistance()
{
@@ -162,8 +201,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray
},
+ cursor = new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position },
grid = new OsuDistanceSnapGrid(new HitCircle { Position = grid_position }, new HitCircle { StartTime = 200 }),
- new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position }
};
});
@@ -182,6 +221,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{
public Func GetSnapPosition;
+ public Vector2 LastSnappedPosition { get; private set; }
+
private readonly Drawable cursor;
private InputManager inputManager;
@@ -210,7 +251,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
protected override void Update()
{
base.Update();
- cursor.Position = GetSnapPosition.Invoke(inputManager.CurrentState.Mouse.Position);
+ cursor.Position = LastSnappedPosition = GetSnapPosition.Invoke(inputManager.CurrentState.Mouse.Position);
}
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs
index 1e73885540..f9cea5761b 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs
@@ -20,20 +20,49 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
[Test]
- public void TestGridExclusivity()
+ public void TestGridToggles()
{
AddStep("enable distance snap grid", () => InputManager.Key(Key.T));
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
+
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType().Any());
rectangularGridActive(false);
AddStep("enable rectangular grid", () => InputManager.Key(Key.Y));
- AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType().Any());
+
+ AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
+ AddUntilStep("distance snap grid still visible", () => this.ChildrenOfType().Any());
rectangularGridActive(true);
- AddStep("enable distance snap grid", () => InputManager.Key(Key.T));
+ AddStep("disable distance snap grid", () => InputManager.Key(Key.T));
+ AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType().Any());
AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
+ rectangularGridActive(true);
+
+ AddStep("disable rectangular grid", () => InputManager.Key(Key.Y));
+ AddUntilStep("distance snap grid still hidden", () => !this.ChildrenOfType().Any());
+ rectangularGridActive(false);
+ }
+
+ [Test]
+ public void TestDistanceSnapMomentaryToggle()
+ {
+ AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
+
+ AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType().Any());
+ AddStep("hold alt", () => InputManager.PressKey(Key.AltLeft));
AddUntilStep("distance snap grid visible", () => this.ChildrenOfType().Any());
+ AddStep("release alt", () => InputManager.ReleaseKey(Key.AltLeft));
+ AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType().Any());
+ }
+
+ [Test]
+ public void TestGridSnapMomentaryToggle()
+ {
+ rectangularGridActive(false);
+ AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft));
+ rectangularGridActive(true);
+ AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
rectangularGridActive(false);
}
@@ -50,8 +79,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddAssert("placement blueprint at (0, 0)", () => Precision.AlmostEquals(Editor.ChildrenOfType().Single().HitObject.Position, new Vector2(0, 0)));
else
AddAssert("placement blueprint at (1, 1)", () => Precision.AlmostEquals(Editor.ChildrenOfType().Single().HitObject.Position, new Vector2(1, 1)));
-
- AddStep("choose selection tool", () => InputManager.Key(Key.Number1));
}
[Test]
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/Mods/TestSceneOsuModFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs
new file mode 100644
index 0000000000..704a548c61
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs
@@ -0,0 +1,25 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Rulesets.Osu.Mods;
+
+namespace osu.Game.Rulesets.Osu.Tests.Mods
+{
+ public class TestSceneOsuModFlashlight : OsuModTestScene
+ {
+ [TestCase(600)]
+ [TestCase(120)]
+ [TestCase(1200)]
+ public void TestFollowDelay(double followDelay) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { FollowDelay = { Value = followDelay } }, PassCondition = () => true });
+
+ [TestCase(1f)]
+ [TestCase(0.5f)]
+ [TestCase(1.5f)]
+ [TestCase(2f)]
+ public void TestSizeMultiplier(float sizeMultiplier) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { SizeMultiplier = { Value = sizeMultiplier } }, PassCondition = () => true });
+
+ [Test]
+ public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFreezeFrame.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFreezeFrame.cs
new file mode 100644
index 0000000000..7d7b2d9071
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFreezeFrame.cs
@@ -0,0 +1,22 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Rulesets.Osu.Mods;
+
+namespace osu.Game.Rulesets.Osu.Tests.Mods
+{
+ public class TestSceneOsuModFreezeFrame : OsuModTestScene
+ {
+ [Test]
+ public void TestFreezeFrame()
+ {
+ CreateModTest(new ModTestData
+ {
+ Mod = new OsuModFreezeFrame(),
+ PassCondition = () => true,
+ Autoplay = false,
+ });
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs
index 44404ca245..da6fac3269 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Utils;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
@@ -12,6 +13,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Osu.UI;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests.Mods
@@ -145,6 +147,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
private bool isBreak() => Player.IsBreakTime.Value;
- private bool cursorAlphaAlmostEquals(float alpha) => Precision.AlmostEquals(Player.DrawableRuleset.Cursor.Alpha, alpha, 0.1f);
+ private OsuPlayfield playfield => (OsuPlayfield)Player.DrawableRuleset.Playfield;
+
+ private bool cursorAlphaAlmostEquals(float alpha) =>
+ Precision.AlmostEquals(playfield.Cursor.AsNonNull().Alpha, alpha, 0.1f) &&
+ Precision.AlmostEquals(playfield.Smoke.Alpha, alpha, 0.1f);
}
}
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/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs
index 1665c40b40..ed1891b7d9 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs
@@ -377,7 +377,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private void addJudgementAssert(OsuHitObject hitObject, HitResult result)
{
AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}",
- () => judgementResults.Single(r => r.HitObject == hitObject).Type == result);
+ () => judgementResults.Single(r => r.HitObject == hitObject).Type, () => Is.EqualTo(result));
}
private void addJudgementAssert(string name, Func hitObject, HitResult result)
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs
deleted file mode 100644
index e0d1646cb0..0000000000
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs
+++ /dev/null
@@ -1,21 +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.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Osu.Mods;
-using osu.Game.Tests.Visual;
-
-namespace osu.Game.Rulesets.Osu.Tests
-{
- public class TestSceneOsuFlashlight : TestSceneOsuPlayer
- {
- protected override TestPlayer CreatePlayer(Ruleset ruleset)
- {
- SelectedMods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), };
-
- return base.CreatePlayer(ruleset);
- }
- }
-}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs
index 0169627867..728aa27da2 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs
@@ -14,6 +14,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Skinning.Legacy;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
using osuTK;
@@ -68,10 +69,8 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("create slider", () =>
{
- var tintingSkin = skinManager.GetSkin(DefaultLegacySkin.CreateInfo());
- tintingSkin.Configuration.ConfigDictionary["AllowSliderBallTint"] = "1";
-
- var provider = Ruleset.Value.CreateInstance().CreateSkinTransformer(tintingSkin, Beatmap.Value.Beatmap);
+ var skin = skinManager.GetSkin(DefaultLegacySkin.CreateInfo());
+ var provider = Ruleset.Value.CreateInstance().CreateSkinTransformer(skin, Beatmap.Value.Beatmap);
Child = new SkinProvidingContainer(provider)
{
@@ -92,10 +91,10 @@ namespace osu.Game.Rulesets.Osu.Tests
});
AddStep("set accent white", () => dho.AccentColour.Value = Color4.White);
- AddAssert("ball is white", () => dho.ChildrenOfType().Single().AccentColour == Color4.White);
+ AddAssert("ball is white", () => dho.ChildrenOfType().Single().BallColour == Color4.White);
AddStep("set accent red", () => dho.AccentColour.Value = Color4.Red);
- AddAssert("ball is red", () => dho.ChildrenOfType().Single().AccentColour == Color4.Red);
+ AddAssert("ball is red", () => dho.ChildrenOfType().Single().BallColour == Color4.Red);
}
private Slider prepareObject(Slider slider)
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/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs
index bb967a0a76..da2a6ced67 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs
@@ -40,16 +40,23 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
body.BorderColour = colours.Yellow;
}
+ private int? lastVersion;
+
public override void UpdateFrom(Slider hitObject)
{
base.UpdateFrom(hitObject);
body.PathRadius = hitObject.Scale * OsuHitObject.OBJECT_RADIUS;
- var vertices = new List();
- hitObject.Path.GetPathToProgress(vertices, 0, 1);
+ if (lastVersion != hitObject.Path.Version.Value)
+ {
+ lastVersion = hitObject.Path.Version.Value;
- body.SetVertices(vertices);
+ var vertices = new List();
+ hitObject.Path.GetPathToProgress(vertices, 0, 1);
+
+ body.SetVertices(vertices);
+ }
Size = body.Size;
OriginPosition = body.PathOffset;
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
index 7c289b5b05..36ee7c2460 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
@@ -59,6 +59,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private readonly BindableList controlPoints = new BindableList();
private readonly IBindable pathVersion = new Bindable();
+ private readonly BindableList selectedObjects = new BindableList();
public SliderSelectionBlueprint(Slider slider)
: base(slider)
@@ -86,6 +87,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
pathVersion.BindValueChanged(_ => editorBeatmap?.Update(HitObject));
BodyPiece.UpdateFrom(HitObject);
+
+ if (editorBeatmap != null)
+ selectedObjects.BindTo(editorBeatmap.SelectedHitObjects);
+ selectedObjects.BindCollectionChanged((_, _) => updateVisualDefinition(), true);
}
public override bool HandleQuickDeletion()
@@ -100,6 +105,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
return true;
}
+ private bool hasSingleObjectSelected => selectedObjects.Count == 1;
+
protected override void Update()
{
base.Update();
@@ -108,14 +115,25 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
BodyPiece.UpdateFrom(HitObject);
}
+ protected override bool OnHover(HoverEvent e)
+ {
+ updateVisualDefinition();
+
+ // In the case more than a single object is selected, block hover from arriving at sliders behind this one.
+ // Without doing this, the path visualisers of potentially hundreds of sliders will render, which is not only
+ // visually noisy but also functionally useless.
+ return !hasSingleObjectSelected;
+ }
+
+ protected override void OnHoverLost(HoverLostEvent e)
+ {
+ updateVisualDefinition();
+ base.OnHoverLost(e);
+ }
+
protected override void OnSelected()
{
- AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(HitObject, true)
- {
- RemoveControlPointsRequested = removeControlPoints,
- SplitControlPointsRequested = splitControlPoints
- });
-
+ updateVisualDefinition();
base.OnSelected();
}
@@ -123,13 +141,31 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
base.OnDeselected();
- // throw away frame buffers on deselection.
- ControlPointVisualiser?.Expire();
- ControlPointVisualiser = null;
-
+ updateVisualDefinition();
BodyPiece.RecyclePath();
}
+ private void updateVisualDefinition()
+ {
+ // To reduce overhead of drawing these blueprints, only add extra detail when hovered or when only this slider is selected.
+ if (IsSelected && (hasSingleObjectSelected || IsHovered))
+ {
+ if (ControlPointVisualiser == null)
+ {
+ AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(HitObject, true)
+ {
+ RemoveControlPointsRequested = removeControlPoints,
+ SplitControlPointsRequested = splitControlPoints
+ });
+ }
+ }
+ else
+ {
+ ControlPointVisualiser?.Expire();
+ ControlPointVisualiser = null;
+ }
+ }
+
private Vector2 rightClickPosition;
protected override bool OnMouseDown(MouseDownEvent e)
@@ -342,7 +378,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/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs
index 28690ee0b7..b5a13a22ce 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs
@@ -5,19 +5,17 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Osu.Skinning.Default;
using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components
{
public class SpinnerPiece : BlueprintPiece
{
- private readonly CircularContainer circle;
- private readonly RingPiece ring;
+ private readonly Circle circle;
+ private readonly Circle ring;
public SpinnerPiece()
{
@@ -25,18 +23,21 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components
RelativeSizeAxes = Axes.Both;
FillMode = FillMode.Fit;
- Size = new Vector2(1.3f);
+ Size = new Vector2(1);
InternalChildren = new Drawable[]
{
- circle = new CircularContainer
+ circle = new Circle
{
RelativeSizeAxes = Axes.Both,
- Masking = true,
Alpha = 0.5f,
- Child = new Box { RelativeSizeAxes = Axes.Both }
},
- ring = new RingPiece()
+ ring = new Circle
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(OsuHitObject.OBJECT_RADIUS),
+ },
};
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index 60896b17bf..1460fae4d7 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -13,8 +13,11 @@ using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Utils;
+using osu.Framework.Input.Events;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mods;
@@ -44,12 +47,10 @@ namespace osu.Game.Rulesets.Osu.Edit
new SpinnerCompositionTool()
};
- private readonly Bindable distanceSnapToggle = new Bindable();
private readonly Bindable rectangularGridSnapToggle = new Bindable();
protected override IEnumerable CreateTernaryButtons() => base.CreateTernaryButtons().Concat(new[]
{
- new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler }),
new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Th })
});
@@ -60,6 +61,9 @@ namespace osu.Game.Rulesets.Osu.Edit
[BackgroundDependencyLoader]
private void load()
{
+ // Give a bit of breathing room around the playfield content.
+ PlayfieldContentContainer.Padding = new MarginPadding(10);
+
LayerBelowRuleset.AddRange(new Drawable[]
{
distanceSnapGridContainer = new Container
@@ -77,19 +81,7 @@ namespace osu.Game.Rulesets.Osu.Edit
placementObject = EditorBeatmap.PlacementObject.GetBoundCopy();
placementObject.ValueChanged += _ => updateDistanceSnapGrid();
- distanceSnapToggle.ValueChanged += _ =>
- {
- updateDistanceSnapGrid();
-
- if (distanceSnapToggle.Value == TernaryState.True)
- rectangularGridSnapToggle.Value = TernaryState.False;
- };
-
- rectangularGridSnapToggle.ValueChanged += _ =>
- {
- if (rectangularGridSnapToggle.Value == TernaryState.True)
- distanceSnapToggle.Value = TernaryState.False;
- };
+ DistanceSnapToggle.ValueChanged += _ => updateDistanceSnapGrid();
// we may be entering the screen with a selection already active
updateDistanceSnapGrid();
@@ -109,6 +101,14 @@ namespace osu.Game.Rulesets.Osu.Edit
private RectangularPositionSnapGrid rectangularPositionSnapGrid;
+ protected override double ReadCurrentDistanceSnap(HitObject before, HitObject after)
+ {
+ float expectedDistance = DurationToDistance(before, after.StartTime - before.GetEndTime());
+ float actualDistance = Vector2.Distance(((OsuHitObject)before).EndPosition, ((OsuHitObject)after).Position);
+
+ return actualDistance / expectedDistance;
+ }
+
protected override void Update()
{
base.Update();
@@ -129,24 +129,46 @@ namespace osu.Game.Rulesets.Osu.Edit
public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
{
if (snapType.HasFlagFast(SnapType.NearbyObjects) && snapToVisibleBlueprints(screenSpacePosition, out var snapResult))
+ {
+ // In the case of snapping to nearby objects, a time value is not provided.
+ // This matches the stable editor (which also uses current time), but with the introduction of time-snapping distance snap
+ // this could result in unexpected behaviour when distance snapping is turned on and a user attempts to place an object that is
+ // BOTH on a valid distance snap ring, and also at the same position as a previous object.
+ //
+ // We want to ensure that in this particular case, the time-snapping component of distance snap is still applied.
+ // The easiest way to ensure this is to attempt application of distance snap after a nearby object is found, and copy over
+ // the time value if the proposed positions are roughly the same.
+ if (snapType.HasFlagFast(SnapType.Grids) && DistanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
+ {
+ (Vector2 distanceSnappedPosition, double distanceSnappedTime) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(snapResult.ScreenSpacePosition));
+ if (Precision.AlmostEquals(distanceSnapGrid.ToScreenSpace(distanceSnappedPosition), snapResult.ScreenSpacePosition, 1))
+ snapResult.Time = distanceSnappedTime;
+ }
+
return snapResult;
+ }
+
+ SnapResult result = base.FindSnappedPositionAndTime(screenSpacePosition, snapType);
if (snapType.HasFlagFast(SnapType.Grids))
{
- if (distanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
+ if (DistanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null)
{
(Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition));
- return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time, PlayfieldAtScreenSpacePosition(screenSpacePosition));
+
+ result.ScreenSpacePosition = distanceSnapGrid.ToScreenSpace(pos);
+ result.Time = time;
}
if (rectangularGridSnapToggle.Value == TernaryState.True)
{
- Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(screenSpacePosition));
- return new SnapResult(rectangularPositionSnapGrid.ToScreenSpace(pos), null, PlayfieldAtScreenSpacePosition(screenSpacePosition));
+ Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(result.ScreenSpacePosition));
+
+ result.ScreenSpacePosition = rectangularPositionSnapGrid.ToScreenSpace(pos);
}
}
- return base.FindSnappedPositionAndTime(screenSpacePosition, snapType);
+ return result;
}
private bool snapToVisibleBlueprints(Vector2 screenSpacePosition, out SnapResult snapResult)
@@ -199,7 +221,7 @@ namespace osu.Game.Rulesets.Osu.Edit
distanceSnapGridCache.Invalidate();
distanceSnapGrid = null;
- if (distanceSnapToggle.Value != TernaryState.True)
+ if (DistanceSnapToggle.Value != TernaryState.True)
return;
switch (BlueprintContainer.CurrentTool)
@@ -226,6 +248,42 @@ namespace osu.Game.Rulesets.Osu.Edit
}
}
+ protected override bool OnKeyDown(KeyDownEvent e)
+ {
+ if (e.Repeat)
+ return false;
+
+ handleToggleViaKey(e);
+ return base.OnKeyDown(e);
+ }
+
+ protected override void OnKeyUp(KeyUpEvent e)
+ {
+ handleToggleViaKey(e);
+ base.OnKeyUp(e);
+ }
+
+ protected override bool AdjustDistanceSpacing(GlobalAction action, float amount)
+ {
+ // To allow better visualisation, ensure that the spacing grid is visible before adjusting.
+ DistanceSnapToggle.Value = TernaryState.True;
+
+ return base.AdjustDistanceSpacing(action, amount);
+ }
+
+ private bool gridSnapMomentary;
+
+ private void handleToggleViaKey(KeyboardEvent key)
+ {
+ bool shiftPressed = key.ShiftPressed;
+
+ if (shiftPressed != gridSnapMomentary)
+ {
+ gridSnapMomentary = shiftPressed;
+ rectangularGridSnapToggle.Value = rectangularGridSnapToggle.Value == TernaryState.False ? TernaryState.True : TernaryState.False;
+ }
+ }
+
private DistanceSnapGrid createDistanceSnapGrid(IEnumerable selectedHitObjects)
{
if (BlueprintContainer.CurrentTool is SpinnerCompositionTool)
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs
index ec93f19e17..f213d9f193 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override double ScoreMultiplier => 1;
public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle;
- public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles) };
+ public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModFreezeFrame) };
[SettingSource("Initial size", "Change the initial size of the approach circle, relative to hit circles.", 0)]
public BindableFloat Scale { get; } = new BindableFloat(4)
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
index 79f5eed139..1a86901d9c 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override BindableBool ComboBasedSize { get; } = new BindableBool(true);
- public override float DefaultFlashlightSize => 180;
+ public override float DefaultFlashlightSize => 200;
private OsuFlashlight flashlight = null!;
@@ -62,7 +62,8 @@ namespace osu.Game.Rulesets.Osu.Mods
{
followDelay = modFlashlight.FollowDelay.Value;
- FlashlightSize = new Vector2(0, GetSizeFor(0));
+ FlashlightSize = new Vector2(0, GetSize());
+ FlashlightSmoothness = 1.4f;
}
public void OnSliderTrackingChange(ValueChangedEvent e)
@@ -82,9 +83,9 @@ namespace osu.Game.Rulesets.Osu.Mods
return base.OnMouseMove(e);
}
- protected override void OnComboChange(ValueChangedEvent e)
+ protected override void UpdateFlashlightSize(float size)
{
- this.TransformTo(nameof(FlashlightSize), new Vector2(0, GetSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION);
+ this.TransformTo(nameof(FlashlightSize), new Vector2(0, size), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "CircularFlashlight";
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs
new file mode 100644
index 0000000000..bea5d4f5d9
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs
@@ -0,0 +1,89 @@
+// 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.Linq;
+using osu.Framework.Graphics;
+using osu.Framework.Localisation;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+
+namespace osu.Game.Rulesets.Osu.Mods
+{
+ public class OsuModFreezeFrame : Mod, IApplicableToDrawableHitObject, IApplicableToBeatmap
+ {
+ public override string Name => "Freeze Frame";
+
+ public override string Acronym => "FR";
+
+ public override double ScoreMultiplier => 1;
+
+ public override LocalisableString Description => "Burn the notes into your memory.";
+
+ //Alters the transforms of the approach circles, breaking the effects of these mods.
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModApproachDifferent) };
+
+ public override ModType Type => ModType.Fun;
+
+ //mod breaks normal approach circle preempt
+ private double originalPreempt;
+
+ public void ApplyToBeatmap(IBeatmap beatmap)
+ {
+ var firstHitObject = beatmap.HitObjects.OfType().FirstOrDefault();
+ if (firstHitObject == null)
+ return;
+
+ double lastNewComboTime = 0;
+
+ originalPreempt = firstHitObject.TimePreempt;
+
+ foreach (var obj in beatmap.HitObjects.OfType())
+ {
+ if (obj.NewCombo) { lastNewComboTime = obj.StartTime; }
+
+ applyFadeInAdjustment(obj);
+ }
+
+ void applyFadeInAdjustment(OsuHitObject osuObject)
+ {
+ osuObject.TimePreempt += osuObject.StartTime - lastNewComboTime;
+
+ foreach (var nested in osuObject.NestedHitObjects.OfType())
+ {
+ switch (nested)
+ {
+ //SliderRepeat wont layer correctly if preempt is changed.
+ case SliderRepeat:
+ break;
+
+ default:
+ applyFadeInAdjustment(nested);
+ break;
+ }
+ }
+ }
+ }
+
+ public void ApplyToDrawableHitObject(DrawableHitObject drawableObject)
+ {
+ drawableObject.ApplyCustomUpdateState += (drawableHitObject, _) =>
+ {
+ if (drawableHitObject is not DrawableHitCircle drawableHitCircle) return;
+
+ var hitCircle = drawableHitCircle.HitObject;
+ var approachCircle = drawableHitCircle.ApproachCircle;
+
+ // Reapply scale, ensuring the AR isn't changed due to the new preempt.
+ approachCircle.ClearTransforms(targetMember: nameof(approachCircle.Scale));
+ approachCircle.ScaleTo(4 * (float)(hitCircle.TimePreempt / originalPreempt));
+
+ using (drawableHitCircle.ApproachCircle.BeginAbsoluteSequence(hitCircle.StartTime - hitCircle.TimePreempt))
+ approachCircle.ScaleTo(1, hitCircle.TimePreempt).Then().Expire();
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs
index fbde9e0491..38d90eb121 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs
@@ -3,6 +3,7 @@
using System;
using osu.Framework.Bindables;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Framework.Timing;
@@ -46,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public void Update(Playfield playfield)
{
- var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition;
+ var cursorPos = playfield.Cursor.AsNonNull().ActiveCursor.DrawPosition;
foreach (var drawable in playfield.HitObjectContainer.AliveObjects)
{
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
index 2f84c30581..d1bbae8e1a 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Diagnostics;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Localisation;
@@ -9,6 +10,7 @@ using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI;
using osu.Game.Utils;
@@ -33,9 +35,15 @@ namespace osu.Game.Rulesets.Osu.Mods
public void Update(Playfield playfield)
{
- bool shouldAlwaysShowCursor = IsBreakTime.Value || spinnerPeriods.IsInAny(playfield.Clock.CurrentTime);
+ var osuPlayfield = (OsuPlayfield)playfield;
+ Debug.Assert(osuPlayfield.Cursor != null);
+
+ bool shouldAlwaysShowCursor = IsBreakTime.Value || spinnerPeriods.IsInAny(osuPlayfield.Clock.CurrentTime);
float targetAlpha = shouldAlwaysShowCursor ? 1 : ComboBasedAlpha;
- playfield.Cursor.Alpha = (float)Interpolation.Lerp(playfield.Cursor.Alpha, targetAlpha, Math.Clamp(playfield.Time.Elapsed / TRANSITION_DURATION, 0, 1));
+ float currentAlpha = (float)Interpolation.Lerp(osuPlayfield.Cursor.Alpha, targetAlpha, Math.Clamp(osuPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1));
+
+ osuPlayfield.Cursor.Alpha = currentAlpha;
+ osuPlayfield.Smoke.Alpha = currentAlpha;
}
}
}
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/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs
index 911363a27e..31a6b69d6b 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs
@@ -3,6 +3,7 @@
using System;
using osu.Framework.Bindables;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Localisation;
using osu.Framework.Timing;
using osu.Framework.Utils;
@@ -45,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public void Update(Playfield playfield)
{
- var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition;
+ var cursorPos = playfield.Cursor.AsNonNull().ActiveCursor.DrawPosition;
foreach (var drawable in playfield.HitObjectContainer.AliveObjects)
{
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
index c5992b359d..841a52da7b 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -102,8 +102,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Size = HitArea.DrawSize;
- PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
- StackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
+ PositionBindable.BindValueChanged(_ => UpdatePosition());
+ StackHeightBindable.BindValueChanged(_ => UpdatePosition());
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
}
@@ -134,6 +134,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
}
+ protected virtual void UpdatePosition()
+ {
+ Position = HitObject.StackedPosition;
+ }
+
public override void Shake() => shakeContainer.Shake();
protected override void CheckForResult(bool userTriggered, double timeOffset)
@@ -204,12 +209,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/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index d58a435728..785d15c15b 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -14,12 +14,10 @@ using osu.Game.Audio;
using osu.Game.Graphics.Containers;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
using osuTK;
-using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@@ -106,7 +104,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
foreach (var drawableHitObject in NestedHitObjects)
drawableHitObject.AccentColour.Value = colour.NewValue;
- updateBallTint();
}, true);
Tracking.BindValueChanged(updateSlidingSample);
@@ -257,22 +254,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
SliderBody?.RecyclePath();
}
- protected override void ApplySkin(ISkinSource skin, bool allowFallback)
- {
- base.ApplySkin(skin, allowFallback);
-
- updateBallTint();
- }
-
- private void updateBallTint()
- {
- if (CurrentSkin == null)
- return;
-
- bool allowBallTint = CurrentSkin.GetConfig(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false;
- Ball.AccentColour = allowBallTint ? AccentColour.Value : Color4.White;
- }
-
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (userTriggered || Time.Current < HitObject.EndTime)
@@ -331,7 +312,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
base.UpdateHitStateTransforms(state);
- const float fade_out_time = 450;
+ const float fade_out_time = 240;
switch (state)
{
@@ -341,7 +322,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
break;
}
- this.FadeOut(fade_out_time, Easing.OutQuint).Expire();
+ this.FadeOut(fade_out_time).Expire();
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => SliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs
index 6bfb4e8aae..9966ad3a90 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs
@@ -11,28 +11,20 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Input.Events;
-using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Skinning;
using osuTK;
-using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
- public class DrawableSliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition, IHasAccentColour
+ public class DrawableSliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition
{
public const float FOLLOW_AREA = 2.4f;
public Func GetInitialHitAction;
- public Color4 AccentColour
- {
- get => ball.Colour;
- set => ball.Colour = value;
- }
-
private Drawable followCircleReceptor;
private DrawableSlider drawableSlider;
private Drawable ball;
@@ -87,7 +79,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public override void ApplyTransformsAt(double time, bool propagateChildren = false)
{
// For the same reasons as above w.r.t rewinding, we shouldn't propagate to children here either.
- // ReSharper disable once RedundantArgumentDefaultValue - removing the "redundant" default value triggers BaseMethodCallWithDefaultParameter
+
+ // ReSharper disable once RedundantArgumentDefaultValue
base.ApplyTransformsAt(time, false);
}
@@ -186,17 +179,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/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
index 80b9544e5b..d1d749d7e2 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
@@ -6,7 +6,6 @@
using System;
using System.Diagnostics;
using JetBrains.Annotations;
-using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
@@ -43,13 +42,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
}
- [BackgroundDependencyLoader]
- private void load()
- {
- PositionBindable.BindValueChanged(_ => updatePosition());
- pathVersion.BindValueChanged(_ => updatePosition());
- }
-
protected override void OnFree()
{
base.OnFree();
@@ -57,6 +49,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
pathVersion.UnbindFrom(DrawableSlider.PathVersion);
}
+ protected override void UpdatePosition()
+ {
+ // Slider head is always drawn at (0,0).
+ }
+
protected override void OnApply()
{
base.OnApply();
@@ -100,11 +97,5 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
base.Shake();
DrawableSlider.Shake();
}
-
- private void updatePosition()
- {
- if (Slider != null)
- Position = HitObject.Position - Slider.Position;
- }
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index 53f4d21975..6ae9d5bc34 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
///
public readonly IBindable SpinsPerMinute = new BindableDouble();
- private const double fade_out_duration = 160;
+ private const double fade_out_duration = 240;
public DrawableSpinner()
: this(null)
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/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