diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 33ec3d660205..56b3ebe87b18 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 022da0a2ea1d..03fd21829d67 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/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 b8604169aa11..936808f38b8d 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 41174525794b..35e774217281 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 0b119c868029..c1044965b5a9 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 41174525794b..35e774217281 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
@@ -9,9 +9,9 @@
false
-
+
-
+
diff --git a/osu.Android.props b/osu.Android.props
index dd263d6aaaaf..f251e8ee719a 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 d9ad95f96adb..3ee1b3da3094 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -137,12 +137,13 @@ public override void SetHost(GameHost host)
{
base.SetHost(host);
- var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
-
var desktopWindow = (SDL2DesktopWindow)host.Window;
+ var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
+ if (iconStream != null)
+ desktopWindow.SetIconFromStream(iconStream);
+
desktopWindow.CursorState |= CursorState.Hidden;
- desktopWindow.SetIconFromStream(iconStream);
desktopWindow.Title = Name;
desktopWindow.DragDrop += f => fileDrop(new[] { f });
}
diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
index 36ffd3b5b662..d62d422f3356 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 000000000000..538fc7fac6d5
--- /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 cbf6e8f20276..cf6a8169c47c 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 @@ public void SetUpSteps()
}
};
});
+
+ 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 7f513728affa..f3161f32be98 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.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 @@ public void TestCatchComboCounter()
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 d45f8a969244..c9db8246158b 100644
--- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
+++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
@@ -1,9 +1,9 @@
-
+
-
+
WinExe
diff --git a/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs b/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs
index 58f493b4b899..a0a11424d0f5 100644
--- a/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs
+++ b/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs
@@ -36,5 +36,7 @@ public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObjec
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 000000000000..0a0f91c781f2
--- /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 f31dc3ef9c9e..220bc49203e6 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.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.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 @@ public class CatchHitObjectComposer : DistancedHitObjectComposer
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 @@ private void load()
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 @@ protected override void LoadComplete()
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 @@ protected override void Update()
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 @@ protected override DrawableRuleset CreateDrawableRuleset(Ruleset
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 @@ private PalpableCatchHitObject getDistanceSnapGridSourceHitObject()
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 c81afafae577..67238f66d463 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 1adc969f8fdb..a9e9e8fbd518 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs
@@ -24,7 +24,7 @@ public class CatchModFlashlight : ModFlashlight
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 @@ public CatchFlashlight(CatchModFlashlight modFlashlight, CatchPlayfield playfiel
: 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 @@ protected override void Update()
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 69ae8328e903..3f7560844ce0 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
@@ -19,17 +19,20 @@ public class CatchModRelax : ModRelax, IApplicableToDrawableRuleset @"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 @@ private class MouseInputHelper : Drawable, IKeyBindingHandler, IReq
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/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
index c06d9f520f5f..a73b34c9b636 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 @@ public override Drawable GetDrawableComponent(ISkinComponent component)
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 b2dd29841b4c..b4d29988d969 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 e9c289e46acc..a5b7d8d0af4b 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 @@ public class CatchComboDisplay : SkinnableDrawable
{
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 dad22fbe699c..ce000b0fad0a 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
@@ -23,6 +23,12 @@ public class CatchPlayfield : ScrollingPlayfield
///
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 ef2936ac947f..e02b91550848 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 @@ public DrawableCatchRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList 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 6e6e83f9cff8..0e4f612999b1 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.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 @@ public abstract class ManiaPlacementBlueprintTestScene : PlacementBlueprintTestS
[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 679a15e8cbb4..4cadcf138bc8 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/ManiaSelectionBlueprintTestScene.cs
@@ -33,7 +33,7 @@ protected ScrollingDirection Direction
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 ec9620506781..ef140995ecd4 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs
@@ -30,7 +30,7 @@ public class TestSceneManiaBeatSnapGrid : EditorClockTestScene
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 @@ public TestSceneManiaBeatSnapGrid()
{
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 e96a186ae4ea..e082b90d3b62 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs
@@ -35,7 +35,7 @@ public void SetUpSteps()
{
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 fcc9e2e6c344..a3985be936ca 100644
--- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs
@@ -205,7 +205,7 @@ public TestComposer()
{
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 e53deb52690c..000000000000
--- 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 eb380c07a64d..e456659ac4df 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 @@ public LocalKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBi
{
}
- 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 b64006316e9d..7d1a934456bc 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs
@@ -18,7 +18,7 @@ public class ManiaLegacyReplayTest
[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 @@ public void TestEncodeDecodeSingleStage(params ManiaAction[] actions)
[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 000000000000..3bd654e75e48
--- /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 000000000000..0e222fea8999
--- /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 7970d5b594b8..d27a79c41d92 100644
--- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs
@@ -85,7 +85,7 @@ private static ManiaBeatmap createModdedBeatmap()
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 1a3513d46c93..d3e90170b297 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.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 @@ public class ColumnTestContainer : Container
[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 fd82041ad834..75175c43d865 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs
@@ -61,7 +61,6 @@ public void SetUp() => Schedule(() =>
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 9f235689b4a0..2c5535a65fec 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.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 @@ public abstract class ManiaSkinnableTestScene : SkinnableTestScene
[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 ff557638a9b8..1bfe55b07439 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneBarLine.cs
@@ -23,7 +23,7 @@ private void recreate(Func>? createBarLines = null)
{
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 bbbd7edb7bbe..000000000000
--- 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 62dadbc3dd17..9817719c94e5 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs
@@ -22,7 +22,7 @@ public void TestSingleStage()
{
stageDefinitions = new List
{
- new StageDefinition { Columns = 2 }
+ new StageDefinition(2)
};
SetContents(_ => new ManiaPlayfield(stageDefinitions));
@@ -36,8 +36,8 @@ public void TestDualStages()
{
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 f3f1b9416f5c..07aa0b845f6f 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs
@@ -21,7 +21,7 @@ private void load()
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 687b3a747da5..0744d7e2e7d7 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 @@ public class TestSceneStageBackground : ManiaSkinnableTestScene
[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 6cbc1727557a..979c90c80297 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 @@ public class TestSceneStageForeground : ManiaSkinnableTestScene
[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 21ec85bbe623..3abeb8a5f681 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs
@@ -30,7 +30,7 @@ public void TestSingleNote()
// | - |
// | |
- 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 @@ public void TestSingleHoldNote()
// | * |
// | |
- 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 @@ public void TestSingleNoteChord()
// | - | - |
// | | |
- 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 @@ public void TestHoldNoteChord()
// | * | * |
// | | |
- 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 @@ public void TestSingleNoteStair()
// | - | |
// | | |
- 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 @@ public void TestHoldNoteStair()
// | * | |
// | | |
- 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 @@ public void TestHoldNoteWithReleasePress()
// | * | |
// | | |
- 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 2922d1871341..83491b6fe950 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs
@@ -11,6 +11,7 @@
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 @@ public class TestSceneColumn : ManiaInputTestScene
[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 @@ private void createHoldNote()
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 223f8dae444e..d273f5cb35d8 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 @@ public class TestSceneDrawableManiaHitObject : OsuTestScene
private Column column;
+ [Cached]
+ private readonly StageDefinition stage = new StageDefinition(1);
+
[SetUp]
public void SetUp() => Schedule(() =>
{
@@ -35,11 +40,11 @@ public void SetUp() => Schedule(() =>
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 8f776ff5079f..0296303867d0 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
@@ -49,7 +49,7 @@ public void TestNoInput()
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 a563dc3106e6..464dbecee5e7 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs
@@ -76,8 +76,8 @@ public void TestHoldNoteMissAfterNextObjectStartTime()
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 @@ private void performTest(List hitObjects, List fram
{
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 cf8947c1edf2..6387dac957a9 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs
@@ -135,7 +135,7 @@ private ScrollingTestContainer createStage(ScrollingDirection direction, ManiaAc
{
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 e84d02775aa2..9f2e3d2502a9 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs
@@ -85,7 +85,7 @@ private ManiaBeatmap createTestBeatmap()
{
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 29510765910e..0d7b03d830d1 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 0114987e3c5e..000000000000
--- 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 4879ce6748b6..b5655a457964 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 @@ public override IEnumerable GetStatistics()
},
};
}
+
+ 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 90cd7f57b576..632b7cdcc715 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -93,10 +93,10 @@ protected override Beatmap ConvertBeatmap(IBeatmap original, Can
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 54e2d4686fee..898b558eb33c 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 a925e7c0acf6..440dec82af34 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
@@ -38,7 +38,7 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s
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 @@ private double computeDifficultyValue(ManiaDifficultyAttributes attributes)
///
/// 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 ad75afff8e31..f438d6497cb6 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs
@@ -33,5 +33,7 @@ public override HitObjectSelectionBlueprint CreateHitObjectBlueprintFor(HitObjec
}
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 061dedb07ad4..6162184c9a8f 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -26,6 +26,8 @@
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 @@ public class ManiaRuleset : Ruleset, ILegacyRuleset
{
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 21b362df00fa..f05edb467755 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 6eaede211225..947915cdf9b4 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs
@@ -36,7 +36,7 @@ private class ManiaFlashlight : Flashlight
public ManiaFlashlight(ManiaModFlashlight modFlashlight)
: base(modFlashlight)
{
- FlashlightSize = new Vector2(DrawWidth, GetSizeFor(0));
+ FlashlightSize = new Vector2(DrawWidth, GetSize());
AddLayout(flashlightProperties);
}
@@ -54,9 +54,9 @@ protected override void Update()
}
}
- 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 602034893801..a607ed572ddb 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
@@ -54,10 +54,6 @@ public DrawableBarLine(BarLine barLine)
}
}
- 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 19792086a799..14dbc432ff3d 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 @@ public class DrawableHoldNote : DrawableManiaHitObject, IKeyBindingHan
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 @@ public class DrawableHoldNote : DrawableManiaHitObject, IKeyBindingHan
///
private double? releaseTime;
+ public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
+
public DrawableHoldNote()
: this(null)
{
@@ -108,6 +114,7 @@ private void load()
},
tickContainer = new Container { RelativeSizeAxes = Axes.Both },
tailContainer = new Container { RelativeSizeAxes = Axes.Both },
+ slidingSample = new PausableSkinnableSound { Looping = true }
});
maskedContents.AddRange(new[]
@@ -118,6 +125,13 @@ private void load()
});
}
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ isHitting.BindValueChanged(updateSlidingSample, true);
+ }
+
protected override void OnApply()
{
base.OnApply();
@@ -248,7 +262,7 @@ protected override void CheckForResult(bool userTriggered, double timeOffset)
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 @@ private void endHold()
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 d374e935ec8d..ac646ea427d3 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs
@@ -30,20 +30,15 @@ public DrawableHoldNoteHead(HeadNote headNote)
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 bcc10ab7bcbc..73dc937a00a9 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
@@ -23,10 +23,6 @@ public abstract class DrawableManiaHitObject : DrawableHitObject
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 @@ protected override void LoadComplete()
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 5bd2d3ab48a0..598a765d3cf4 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.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 readonly IBindable direction = new Bindable();
- private Box background;
- private Box backgroundOverlay;
+ private Color4 brightColour;
+ private Color4 dimColour;
- private readonly IBindable direction = new Bindable();
+ private Box background = null!;
+ private Box backgroundOverlay = null!;
- [BackgroundDependencyLoader]
- private void load(IBindable action, IScrollingInfo scrollingInfo)
+ [Resolved]
+ private Column column { get; set; } = null!;
+
+ private Bindable accentColour = null!;
+
+ public ArgonColumnBackground()
{
- this.action.BindTo(action);
+ RelativeSizeAxes = Axes.Both;
+ Masking = true;
+ CornerRadius = ArgonNotePiece.CORNER_RADIUS;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(IScrollingInfo scrollingInfo)
+ {
InternalChildren = new[]
{
background = new Box
@@ -49,61 +59,42 @@ private void load(IBindable action, IScrollingInfo scrollingInfo)
}
};
- 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);
- }
- protected override void LoadComplete()
- {
- base.LoadComplete();
- updateColours();
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(onDirectionChanged, true);
}
- private Color4 accentColour;
-
- public Color4 AccentColour
+ private void onDirectionChanged(ValueChangedEvent direction)
{
- 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 000000000000..af179d5580fa
--- /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 000000000000..9e449623d5a8
--- /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 000000000000..757190c4ae51
--- /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 000000000000..e1068c6cd8ec
--- /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 000000000000..e7dfec256d4b
--- /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 000000000000..7670c9bdf237
--- /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 000000000000..454a6b012b6f
--- /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 000000000000..1881695b141e
--- /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 000000000000..ae313e0b91a8
--- /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 000000000000..eb51179cea22
--- /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 ab953ccfb9cc..e227c808450f 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 @@ public class LegacyManiaColumnElement : CompositeDrawable
[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 @@ public class LegacyManiaColumnElement : CompositeDrawable
[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 740ccbfe27f6..d039551cd721 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 000000000000..e57927897cad
--- /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 dd5baa81506e..a07dbea3688e 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 @@ private static readonly IReadOnlyDictionary default_hit_resul
///
private readonly Lazy hasKeyTexture;
+ private readonly ManiaBeatmap beatmap;
+
public ManiaLegacySkinTransformer(ISkin skin, IBeatmap beatmap)
: base(skin)
{
@@ -113,8 +114,7 @@ public override Drawable GetDrawableComponent(ISkinComponent component)
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 @@ public override ISample GetSample(ISampleInfo sampleInfo)
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 4d0c32111663..e22bf630496d 100644
--- a/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigExtensions.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigExtensions.cs
@@ -15,9 +15,9 @@ public static class ManiaSkinConfigExtensions
///
/// 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 e9005a3da047..59188f02f95e 100644
--- a/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigurationLookup.cs
@@ -16,20 +16,21 @@ public class ManiaSkinConfigurationLookup
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 deb1b155b56c..3d46bdaa7b30 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 @@ public class Column : ScrollingPlayfield, IKeyBindingHandler, IHasA
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;
+
+ ///
+ /// Whether this is a special (ie. scratch) column.
+ ///
+ public readonly bool IsSpecial;
+
+ public readonly Bindable AccentColour = new Bindable(Color4.Black);
- public Column(int index)
+ 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 @@ public Column(int index)
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 @@ public Column(int index)
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; }
-
- public bool IsSpecial => ColumnType == ColumnType.Special;
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
- public Color4 AccentColour { get; set; }
+ if (skin != null)
+ skin.SourceChanged -= onSourceChanged;
+ }
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
@@ -111,7 +153,7 @@ protected override void OnNewDrawableHitObject(DrawableHitObject drawableHitObje
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 871ec9f1a396..9b3f6d70330a 100644
--- a/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs
+++ b/osu.Game.Rulesets.Mania/UI/ColumnFlow.cs
@@ -36,6 +36,8 @@ public ColumnFlow(StageDefinition stageDefinition)
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 39d17db6bef2..3680e7ea0a06 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultColumnBackground.cs
@@ -30,6 +30,8 @@ public class DefaultColumnBackground : CompositeDrawable, IKeyBindingHandler accentColour;
+
public DefaultColumnBackground()
{
RelativeSizeAxes = Axes.Both;
@@ -55,9 +57,13 @@ private void load(IScrollingInfo scrollingInfo)
}
};
- 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 53fa86125fe6..97aa89778291 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultHitTarget.cs
@@ -25,6 +25,8 @@ public class DefaultHitTarget : CompositeDrawable
private Container hitTargetLine;
private Drawable hitTargetBar;
+ private Bindable accentColour;
+
[Resolved]
private Column column { get; set; }
@@ -54,12 +56,16 @@ private void load(IScrollingInfo scrollingInfo)
},
};
- 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 5a0fab2ff4f8..600c9feb73bd 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/DefaultKeyArea.cs
@@ -30,6 +30,8 @@ public class DefaultKeyArea : CompositeDrawable, IKeyBindingHandler
private Container keyIcon;
private Drawable gradient;
+ private Bindable accentColour;
+
[Resolved]
private Column column { get; set; }
@@ -75,15 +77,19 @@ private void load(IScrollingInfo scrollingInfo)
}
};
- 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 e83cd10d2dd0..59716ee3e26d 100644
--- a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs
+++ b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs
@@ -32,6 +32,10 @@ public class DefaultHitExplosion : CompositeDrawable, IHitExplosion
private CircularContainer largeFaint;
private CircularContainer mainGlow1;
+ private CircularContainer mainGlow2;
+ private CircularContainer mainGlow3;
+
+ private Bindable accentColour;
public DefaultHitExplosion()
{
@@ -48,8 +52,6 @@ private void load(IScrollingInfo scrollingInfo)
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 @@ private void load(IScrollingInfo scrollingInfo)
// 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 @@ private void load(IScrollingInfo scrollingInfo)
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 @@ private void load(IScrollingInfo scrollingInfo)
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 @@ private void load(IScrollingInfo scrollingInfo)
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 28509d1f4e97..a7b94f9f22f7 100644
--- a/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs
+++ b/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs
@@ -42,6 +42,8 @@ protected override void PrepareForUse()
{
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 c578bbb703a7..1273cb3d3228 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.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.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 @@ public class Stage : ScrollingPlayfield
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 @@ public class Stage : ScrollingPlayfield
public Stage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction)
{
this.firstColumnIndex = firstColumnIndex;
+ Definition = definition;
Name = "Stage";
@@ -75,7 +73,7 @@ public Stage(int firstColumnIndex, StageDefinition definition, ref ManiaAction n
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 @@ public Stage(int firstColumnIndex, StageDefinition definition, ref ManiaAction n
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 @@ public Stage(int firstColumnIndex, StageDefinition definition, ref ManiaAction n
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 @@ public Stage(int firstColumnIndex, StageDefinition definition, ref ManiaAction n
}
}
+ 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 c102678e002e..1b67fc2ca90a 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 @@ public class TestSceneOsuDistanceSnapGrid : OsuManualInputManagerTestScene
[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 @@ public class TestSceneOsuDistanceSnapGrid : OsuManualInputManagerTestScene
};
private OsuDistanceSnapGrid grid;
+ private SnappingCursorContainer cursor;
public TestSceneOsuDistanceSnapGrid()
{
@@ -84,8 +92,8 @@ public void Setup() => Schedule(() =>
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 @@ public void TestDistanceSpacingAdjust(float multiplier, float expectedDistance)
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 @@ public void TestLimitedDistance()
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 @@ private class SnappingCursorContainer : CompositeDrawable
{
public Func GetSnapPosition;
+ public Vector2 LastSnappedPosition { get; private set; }
+
private readonly Drawable cursor;
private InputManager inputManager;
@@ -210,7 +251,7 @@ protected override void LoadComplete()
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 1e7388554000..f9cea5761b5e 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs
@@ -20,20 +20,49 @@ public class TestSceneOsuEditorGrids : EditorTestScene
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));
+
+ AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1)));
+ AddUntilStep("distance snap grid still visible", () => this.ChildrenOfType().Any());
+ rectangularGridActive(true);
+
+ 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("enable distance snap grid", () => InputManager.Key(Key.T));
+ 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 @@ private void rectangularGridActive(bool active)
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 51871dd9e5db..0601dc606859 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs
@@ -148,6 +148,37 @@ private void convertToStream()
});
}
+ [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 000000000000..704a548c61f0
--- /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 000000000000..7d7b2d907148
--- /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 44404ca245ba..da6fac3269e8 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.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 @@ public void TestVisibleAfterComboBreak()
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 000000000000..b1380a47a47f
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 000000000000..5f7beae4e9dd
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 1665c40b4041..ed1891b7d91b 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs
@@ -377,7 +377,7 @@ public void TestHitSliderHeadBeforeHitCircle()
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 e0d1646cb07f..000000000000
--- 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 016962786722..728aa27da20a 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.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 @@ public void TestBallTintChangedOnAccentChange()
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 @@ public void TestBallTintChangedOnAccentChange()
});
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 000000000000..1cb64b71fce5
--- /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 c2973644cfdc..1eb1c85d93b6 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 bb967a0a760b..da2a6ced67fd 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 @@ private void load(OsuColour colours)
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;
+
+ var vertices = new List();
+ hitObject.Path.GetPathToProgress(vertices, 0, 1);
- body.SetVertices(vertices);
+ 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 7c289b5b05e2..265a1d21b15c 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
@@ -342,7 +342,7 @@ private void convertToStream()
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 28690ee0b783..b5a13a22ceb2 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 @@ public SpinnerPiece()
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 60896b17bf3b..1460fae4d753 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -13,8 +13,11 @@
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 @@ protected override DrawableRuleset CreateDrawableRuleset(Ruleset r
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 @@ protected override IEnumerable CreateTernaryButtons() => base.Cre
[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 @@ private void load()
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 @@ public override string ConvertSelectionToString()
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 @@ protected override void Update()
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 @@ private void updateDistanceSnapGrid()
distanceSnapGridCache.Invalidate();
distanceSnapGrid = null;
- if (distanceSnapToggle.Value != TernaryState.True)
+ if (DistanceSnapToggle.Value != TernaryState.True)
return;
switch (BlueprintContainer.CurrentTool)
@@ -226,6 +248,42 @@ private void updateDistanceSnapGrid()
}
}
+ 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 ec93f19e1789..f213d9f193e4 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs
@@ -21,7 +21,7 @@ public class OsuModApproachDifferent : Mod, IApplicableToDrawableHitObject, IReq
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 79f5eed139cd..1a86901d9c95 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
@@ -41,7 +41,7 @@ public class OsuModFlashlight : ModFlashlight, IApplicableToDrawab
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 @@ public OsuFlashlight(OsuModFlashlight modFlashlight)
{
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 @@ protected override bool OnMouseMove(MouseMoveEvent e)
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 000000000000..bea5d4f5d9ea
--- /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 fbde9e0491bc..38d90eb12102 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 @@ public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset
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 2f84c3058175..d1bbae8e1aca 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.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 @@ public void ApplyToBeatmap(IBeatmap beatmap)
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 fac1cbfd47a0..753de6231a74 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 @@ public void ApplyToPlayer(Player player)
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 911363a27ea0..31a6b69d6baa 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 @@ public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset
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 c5992b359dd3..841a52da7b17 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -102,8 +102,8 @@ private void load()
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 @@ public override double LifetimeEnd
}
}
+ 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 @@ protected override void UpdateHitStateTransforms(ArmedState state)
// 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 6f4ca30bd073..d9d0d284777e 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.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 @@ protected override void OnFree()
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 d58a43572816..785d15c15b57 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.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 @@ private void load()
{
foreach (var drawableHitObject in NestedHitObjects)
drawableHitObject.AccentColour.Value = colour.NewValue;
- updateBallTint();
}, true);
Tracking.BindValueChanged(updateSlidingSample);
@@ -257,22 +254,6 @@ public override void OnKilled()
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 @@ protected override void UpdateHitStateTransforms(ArmedState state)
{
base.UpdateHitStateTransforms(state);
- const float fade_out_time = 450;
+ const float fade_out_time = 240;
switch (state)
{
@@ -341,7 +322,7 @@ protected override void UpdateHitStateTransforms(ArmedState state)
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 6bfb4e8aaeb6..de6ca7dd3872 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.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;
@@ -186,17 +178,22 @@ private bool isValidTrackingAction(OsuAction action)
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 80b9544e5bea..d1d749d7e238 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 @@ public DrawableSliderHead(SliderHeadCircle h)
{
}
- [BackgroundDependencyLoader]
- private void load()
- {
- PositionBindable.BindValueChanged(_ => updatePosition());
- pathVersion.BindValueChanged(_ => updatePosition());
- }
-
protected override void OnFree()
{
base.OnFree();
@@ -57,6 +49,11 @@ protected override void OnFree()
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 @@ public override void Shake()
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 53f4d2197503..6ae9d5bc3402 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -57,7 +57,7 @@ public class DrawableSpinner : DrawableOsuHitObject
///
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 e3c1b1e16808..6c2be8a49ad7 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -34,21 +34,6 @@ public double Duration
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 12256e93d0d7..1e59e192461c 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 @@ public class OsuInputManager : RulesetInputManager
{
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 @@ protected override bool HandleMouseTouchStateChange(TouchStateChangeEvent e)
private class OsuKeyBindingContainer : RulesetKeyBindingContainer
{
- public bool AllowUserPresses = true;
+ private bool allowGameplayInputs = true;
+
+ ///
+ /// Whether gameplay input buttons should be allowed.
+ /// Defaults to true, generally used for mods like Relax which turn off main inputs.
+ ///
+ ///
+ /// Of note, auxiliary inputs like the "smoke" key are left usable.
+ ///
+ public bool AllowGameplayInputs
+ {
+ get => allowGameplayInputs;
+ set
+ {
+ allowGameplayInputs = value;
+ ReloadMappings();
+ }
+ }
public OsuKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
: base(ruleset, variant, unique)
{
}
- protected override bool Handle(UIEvent e)
+ protected override void ReloadMappings(IQueryable realmKeyBindings)
{
- if (!AllowUserPresses) return false;
+ base.ReloadMappings(realmKeyBindings);
- return base.Handle(e);
+ if (!AllowGameplayInputs)
+ KeyBindings = KeyBindings.Where(b => b.GetAction() == OsuAction.Smoke).ToList();
}
}
}
@@ -80,6 +107,9 @@ public enum OsuAction
LeftButton,
[Description("Right button")]
- RightButton
+ RightButton,
+
+ [Description("Smoke")]
+ Smoke,
}
}
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 3f5e72865185..69df12ff6d7b 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -59,6 +59,7 @@ public override IEnumerable GetDefaultKeyBindings(int variant = 0) =
{
new KeyBinding(InputKey.Z, OsuAction.LeftButton),
new KeyBinding(InputKey.X, OsuAction.RightButton),
+ new KeyBinding(InputKey.C, OsuAction.Smoke),
new KeyBinding(InputKey.MouseLeft, OsuAction.LeftButton),
new KeyBinding(InputKey.MouseRight, OsuAction.RightButton),
};
@@ -200,7 +201,8 @@ public override IEnumerable GetModsFor(ModType type)
new OsuModMuted(),
new OsuModNoScope(),
new MultiMod(new OsuModMagnetised(), new OsuModRepel()),
- new ModAdaptiveSpeed()
+ new ModAdaptiveSpeed(),
+ new OsuModFreezeFrame()
};
case ModType.System:
diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
index fcf079b6aa4f..4248cce55a40 100644
--- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
+++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
@@ -21,6 +21,7 @@ public enum OsuSkinComponents
SliderBall,
SliderBody,
SpinnerBody,
+ CursorSmoke,
ApproachCircle,
}
}
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs
index 85060261feca..8082c5aef455 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs
@@ -31,6 +31,7 @@ public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayF
Position = currentFrame.Position;
if (currentFrame.MouseLeft) Actions.Add(OsuAction.LeftButton);
if (currentFrame.MouseRight) Actions.Add(OsuAction.RightButton);
+ if (currentFrame.Smoke) Actions.Add(OsuAction.Smoke);
}
public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
@@ -41,6 +42,8 @@ public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
state |= ReplayButtonState.Left1;
if (Actions.Contains(OsuAction.RightButton))
state |= ReplayButtonState.Right1;
+ if (Actions.Contains(OsuAction.Smoke))
+ state |= ReplayButtonState.Smoke;
return new LegacyReplayFrame(Time, Position.X, Position.Y, state);
}
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs
index 05fbac625eb5..6f55e1790fe6 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs
@@ -1,20 +1,23 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Scoring
{
public class OsuHitWindows : HitWindows
{
+ ///
+ /// osu! ruleset has a fixed miss window regardless of difficulty settings.
+ ///
+ public const double MISS_WINDOW = 400;
+
private static readonly DifficultyRange[] osu_ranges =
{
new DifficultyRange(HitResult.Great, 80, 50, 20),
new DifficultyRange(HitResult.Ok, 140, 100, 60),
new DifficultyRange(HitResult.Meh, 200, 150, 100),
- new DifficultyRange(HitResult.Miss, 400, 400, 400),
+ new DifficultyRange(HitResult.Miss, MISS_WINDOW, MISS_WINDOW, MISS_WINDOW),
};
public override bool IsHitResultAllowed(HitResult result)
diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs
index b08b7b4e8550..bb68c7298fb0 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs
@@ -75,6 +75,7 @@ public virtual void PlayAnimation()
{
default:
JudgementText
+ .FadeInFromZero(300, Easing.OutQuint)
.ScaleTo(Vector2.One)
.ScaleTo(new Vector2(1.2f), 1800, Easing.OutQuint);
break;
@@ -96,7 +97,7 @@ public virtual void PlayAnimation()
ringExplosion?.PlayAnimation();
}
- public Drawable? GetAboveHitObjectsProxiedContent() => null;
+ public Drawable? GetAboveHitObjectsProxiedContent() => JudgementText.CreateProxy();
private class RingExplosion : CompositeDrawable
{
diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs
index ffdcba3cdb6f..36dc8c801d97 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs
@@ -108,18 +108,23 @@ protected override void LoadComplete()
{
base.LoadComplete();
+ indexInCurrentCombo.BindValueChanged(index => number.Text = (index.NewValue + 1).ToString(), true);
+
accentColour.BindValueChanged(colour =>
{
- outerFill.Colour = innerFill.Colour = colour.NewValue.Darken(4);
+ // A colour transform is applied.
+ // Without removing transforms first, when it is rewound it may apply an old colour.
+ outerGradient.ClearTransforms(targetMember: nameof(Colour));
outerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.1f));
+
+ outerFill.Colour = innerFill.Colour = colour.NewValue.Darken(4);
innerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue.Darken(0.5f), colour.NewValue.Darken(0.6f));
flash.Colour = colour.NewValue;
- }, true);
- indexInCurrentCombo.BindValueChanged(index => number.Text = (index.NewValue + 1).ToString(), true);
+ updateStateTransforms(drawableObject, drawableObject.State.Value);
+ }, true);
drawableObject.ApplyCustomUpdateState += updateStateTransforms;
- updateStateTransforms(drawableObject, drawableObject.State.Value);
}
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
@@ -173,11 +178,7 @@ private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedSta
.FadeOut(flash_in_duration);
}
- // The flash layer starts white to give the wanted brightness, but is almost immediately
- // recoloured to the accent colour. This would more correctly be done with two layers (one for the initial flash)
- // but works well enough with the colour fade.
flash.FadeTo(1, flash_in_duration, Easing.OutQuint);
- flash.FlashColour(accentColour.Value, fade_out_time, Easing.OutQuint);
this.FadeOut(fade_out_time, Easing.OutQuad);
break;
diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs
index 7bc6723afb92..bf507db50c3f 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs
@@ -22,6 +22,7 @@ public OsuArgonSkinTransformer(ISkin skin)
return new ArgonJudgementPiece(resultComponent.Component);
case OsuSkinComponent osuComponent:
+ // TODO: Once everything is finalised, consider throwing UnsupportedSkinComponentException on missing entries.
switch (osuComponent.Component)
{
case OsuSkinComponents.HitCircle:
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmokeSegment.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmokeSegment.cs
new file mode 100644
index 000000000000..27a2dc396015
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSmokeSegment.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics.Textures;
+
+namespace osu.Game.Rulesets.Osu.Skinning.Default
+{
+ public class DefaultSmokeSegment : SmokeSegment
+ {
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ // ISkinSource doesn't currently fallback to global textures.
+ // We might want to change this in the future if the intention is to allow the user to skin this as per legacy skins.
+ Texture = textures.Get("Gameplay/osu/cursor-smoke");
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs
index 1b2ab8204481..a6e62b83e4be 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs
@@ -68,7 +68,7 @@ private void load()
InternalChildren = new[]
{
- CircleSprite = new KiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(circleName) })
+ CircleSprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(circleName) })
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -77,7 +77,7 @@ private void load()
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Child = OverlaySprite = new KiaiFlashingDrawable(() => skin.GetAnimation(@$"{circleName}overlay", true, true, frameLength: 1000 / 2d))
+ Child = OverlaySprite = new LegacyKiaiFlashingDrawable(() => skin.GetAnimation(@$"{circleName}overlay", true, true, frameLength: 1000 / 2d))
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -134,10 +134,10 @@ private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedSta
switch (state)
{
case ArmedState.Hit:
- CircleSprite.FadeOut(legacy_fade_duration, Easing.Out);
+ CircleSprite.FadeOut(legacy_fade_duration);
CircleSprite.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
- OverlaySprite.FadeOut(legacy_fade_duration, Easing.Out);
+ OverlaySprite.FadeOut(legacy_fade_duration);
OverlaySprite.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
if (hasNumber)
@@ -146,11 +146,11 @@ private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedSta
if (legacyVersion >= 2.0m)
// legacy skins of version 2.0 and newer only apply very short fade out to the number piece.
- hitCircleText.FadeOut(legacy_fade_duration / 4, Easing.Out);
+ hitCircleText.FadeOut(legacy_fade_duration / 4);
else
{
// old skins scale and fade it normally along other pieces.
- hitCircleText.FadeOut(legacy_fade_duration, Easing.Out);
+ hitCircleText.FadeOut(legacy_fade_duration);
hitCircleText.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs
index 22944becf3a4..71c3e4c9f0d9 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs
@@ -107,8 +107,8 @@ protected override void UpdateStateTransforms(DrawableHitObject drawableHitObjec
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
this.FadeOut();
- using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2))
- this.FadeInFromZero(spinner.TimeFadeIn / 2);
+ using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn))
+ this.FadeInFromZero(spinner.TimeFadeIn);
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
{
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs
index 414879f42d94..60d71ae84366 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs
@@ -2,6 +2,7 @@
// 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.Sprites;
@@ -21,6 +22,8 @@ public class LegacySliderBall : CompositeDrawable
[Resolved(canBeNull: true)]
private DrawableHitObject? parentObject { get; set; }
+ public Color4 BallColour => animationContent.Colour;
+
private Sprite layerNd = null!;
private Sprite layerSpec = null!;
@@ -61,6 +64,8 @@ private void load()
};
}
+ private readonly IBindable accentColour = new Bindable();
+
protected override void LoadComplete()
{
base.LoadComplete();
@@ -69,6 +74,12 @@ protected override void LoadComplete()
{
parentObject.ApplyCustomUpdateState += updateStateTransforms;
updateStateTransforms(parentObject, parentObject.State.Value);
+
+ if (skin.GetConfig(SkinConfiguration.LegacySetting.AllowSliderBallTint)?.Value == true)
+ {
+ accentColour.BindTo(parentObject.AccentColour);
+ accentColour.BindValueChanged(a => animationContent.Colour = a.NewValue, true);
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmokeSegment.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmokeSegment.cs
new file mode 100644
index 000000000000..c9c7e86e8657
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmokeSegment.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd