diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
index 8f776ff507..0296303867 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
@@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania.Tests
assertHeadJudgement(HitResult.Miss);
assertTickJudgement(HitResult.LargeTickMiss);
assertTailJudgement(HitResult.Miss);
- assertNoteJudgement(HitResult.IgnoreHit);
+ assertNoteJudgement(HitResult.IgnoreMiss);
}
///
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs
index 1f139b5b78..464dbecee5 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs
@@ -76,8 +76,8 @@ namespace osu.Game.Rulesets.Mania.Tests
performTest(objects, new List());
- addJudgementAssert(objects[0], HitResult.IgnoreHit);
- addJudgementAssert(objects[1], HitResult.IgnoreHit);
+ addJudgementAssert(objects[0], HitResult.IgnoreMiss);
+ addJudgementAssert(objects[1], HitResult.IgnoreMiss);
}
[Test]
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index 48647f9f5f..14dbc432ff 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -69,6 +69,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
///
private double? releaseTime;
+ public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
+
public DrawableHoldNote()
: this(null)
{
@@ -260,7 +262,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
tick.MissForcefully();
}
- ApplyResult(r => r.Type = r.Judgement.MaxResult);
+ ApplyResult(r => r.Type = Tail.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
endHold();
}
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs
index dc74d38cdc..1b67fc2ca9 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs
@@ -4,12 +4,15 @@
#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;
@@ -52,6 +55,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
};
private OsuDistanceSnapGrid grid;
+ private SnappingCursorContainer cursor;
public TestSceneOsuDistanceSnapGrid()
{
@@ -88,8 +92,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray
},
+ cursor = new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position },
grid = new OsuDistanceSnapGrid(new HitCircle { Position = grid_position }),
- new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position }
};
});
@@ -154,6 +158,37 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertSnappedDistance(expectedDistance);
}
+ [Test]
+ public void TestReferenceObjectNotOnSnapGrid()
+ {
+ AddStep("create grid", () =>
+ {
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.SlateGray
+ },
+ cursor = new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position },
+ grid = new OsuDistanceSnapGrid(new HitCircle
+ {
+ Position = grid_position,
+ // This is important. It sets the reference object to a point in time that isn't on the current snap divisor's grid.
+ // We are testing that the grid's display is offset correctly.
+ StartTime = 40,
+ }),
+ };
+ });
+
+ AddStep("move mouse to point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2(beat_length, 0) * 2)));
+
+ AddAssert("Ensure cursor is on a grid line", () =>
+ {
+ return grid.ChildrenOfType().Any(p => Precision.AlmostEquals(p.ScreenSpaceDrawQuad.TopRight.X, grid.ToScreenSpace(cursor.LastSnappedPosition).X));
+ });
+ }
+
[Test]
public void TestLimitedDistance()
{
@@ -166,8 +201,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray
},
+ cursor = new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position },
grid = new OsuDistanceSnapGrid(new HitCircle { Position = grid_position }, new HitCircle { StartTime = 200 }),
- new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position }
};
});
@@ -186,6 +221,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{
public Func GetSnapPosition;
+ public Vector2 LastSnappedPosition { get; private set; }
+
private readonly Drawable cursor;
private InputManager inputManager;
@@ -214,7 +251,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
protected override void Update()
{
base.Update();
- cursor.Position = GetSnapPosition.Invoke(inputManager.CurrentState.Mouse.Position);
+ cursor.Position = LastSnappedPosition = GetSnapPosition.Invoke(inputManager.CurrentState.Mouse.Position);
}
}
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModFlashlight.cs
index 417b59f5d2..d55ce17e6c 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModFlashlight.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModFlashlight.cs
@@ -1,8 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Linq;
using NUnit.Framework;
+using osu.Framework.Testing;
using osu.Game.Rulesets.Taiko.Mods;
+using osu.Game.Rulesets.Taiko.UI;
+using osuTK;
namespace osu.Game.Rulesets.Taiko.Tests.Mods
{
@@ -16,5 +20,37 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods
[Test]
public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new TaikoModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
+
+ [Test]
+ public void TestFlashlightAlwaysHasNonZeroSize()
+ {
+ bool failed = false;
+
+ CreateModTest(new ModTestData
+ {
+ Mod = new TestTaikoModFlashlight { ComboBasedSize = { Value = true } },
+ Autoplay = false,
+ PassCondition = () =>
+ {
+ failed |= this.ChildrenOfType().SingleOrDefault()?.FlashlightSize.Y == 0;
+ return !failed;
+ }
+ });
+ }
+
+ private class TestTaikoModFlashlight : TaikoModFlashlight
+ {
+ protected override Flashlight CreateFlashlight() => new TestTaikoFlashlight(this, Playfield);
+
+ public class TestTaikoFlashlight : TaikoFlashlight
+ {
+ public TestTaikoFlashlight(TaikoModFlashlight modFlashlight, TaikoPlayfield taikoPlayfield)
+ : base(modFlashlight, taikoPlayfield)
+ {
+ }
+
+ public new Vector2 FlashlightSize => base.FlashlightSize;
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs
index 98f954ad29..46569c2495 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs
@@ -27,17 +27,17 @@ namespace osu.Game.Rulesets.Taiko.Mods
public override float DefaultFlashlightSize => 200;
- protected override Flashlight CreateFlashlight() => new TaikoFlashlight(this, playfield);
+ protected override Flashlight CreateFlashlight() => new TaikoFlashlight(this, Playfield);
- private TaikoPlayfield playfield = null!;
+ protected TaikoPlayfield Playfield { get; private set; } = null!;
public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
- playfield = (TaikoPlayfield)drawableRuleset.Playfield;
+ Playfield = (TaikoPlayfield)drawableRuleset.Playfield;
base.ApplyToDrawableRuleset(drawableRuleset);
}
- private class TaikoFlashlight : Flashlight
+ public class TaikoFlashlight : Flashlight
{
private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo);
private readonly TaikoPlayfield taikoPlayfield;
@@ -47,21 +47,28 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
this.taikoPlayfield = taikoPlayfield;
- FlashlightSize = adjustSize(GetSize());
+ FlashlightSize = adjustSizeForPlayfieldAspectRatio(GetSize());
FlashlightSmoothness = 1.4f;
AddLayout(flashlightProperties);
}
- private Vector2 adjustSize(float size)
+ ///
+ /// Returns the aspect ratio-adjusted size of the flashlight.
+ /// This ensures that the size of the flashlight remains independent of taiko-specific aspect ratio adjustments.
+ ///
+ ///
+ /// The size of the flashlight.
+ /// The value provided here should always come from .
+ ///
+ private Vector2 adjustSizeForPlayfieldAspectRatio(float size)
{
- // Preserve flashlight size through the playfield's aspect adjustment.
return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT);
}
protected override void UpdateFlashlightSize(float size)
{
- this.TransformTo(nameof(FlashlightSize), adjustSize(size), FLASHLIGHT_FADE_DURATION);
+ this.TransformTo(nameof(FlashlightSize), adjustSizeForPlayfieldAspectRatio(size), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "CircularFlashlight";
@@ -75,7 +82,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
FlashlightPosition = ToLocalSpace(taikoPlayfield.HitTarget.ScreenSpaceDrawQuad.Centre);
ClearTransforms(targetMember: nameof(FlashlightSize));
- FlashlightSize = adjustSize(Combo.Value);
+ FlashlightSize = adjustSizeForPlayfieldAspectRatio(GetSize());
flashlightProperties.Validate();
}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
index 0e72463d1e..eacaf7f92e 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
@@ -862,52 +862,6 @@ namespace osu.Game.Tests.Visual.SongSelect
AddAssert("Selection was remembered", () => eagerSelectedIDs.Count == 1);
}
- [Test]
- public void TestRandomFallbackOnNonMatchingPrevious()
- {
- List manySets = new List();
-
- AddStep("populate maps", () =>
- {
- manySets.Clear();
-
- for (int i = 0; i < 10; i++)
- {
- manySets.Add(TestResources.CreateTestBeatmapSetInfo(3, new[]
- {
- // all taiko except for first
- rulesets.GetRuleset(i > 0 ? 1 : 0)
- }));
- }
- });
-
- loadBeatmaps(manySets);
-
- for (int i = 0; i < 10; i++)
- {
- AddStep("Reset filter", () => carousel.Filter(new FilterCriteria(), false));
-
- AddStep("select first beatmap", () => carousel.SelectBeatmap(manySets.First().Beatmaps.First()));
-
- AddStep("Toggle non-matching filter", () =>
- {
- carousel.Filter(new FilterCriteria { SearchText = Guid.NewGuid().ToString() }, false);
- });
-
- AddAssert("selection lost", () => carousel.SelectedBeatmapInfo == null);
-
- AddStep("Restore different ruleset filter", () =>
- {
- carousel.Filter(new FilterCriteria { Ruleset = rulesets.GetRuleset(1) }, false);
- eagerSelectedIDs.Add(carousel.SelectedBeatmapSet!.ID);
- });
-
- AddAssert("selection changed", () => !carousel.SelectedBeatmapInfo!.Equals(manySets.First().Beatmaps.First()));
- }
-
- AddAssert("Selection was random", () => eagerSelectedIDs.Count > 2);
- }
-
[Test]
public void TestFilteringByUserStarDifficulty()
{
@@ -955,6 +909,63 @@ namespace osu.Game.Tests.Visual.SongSelect
checkVisibleItemCount(true, 15);
}
+ [Test]
+ public void TestCarouselSelectsNextWhenPreviousIsFiltered()
+ {
+ List sets = new List();
+
+ // 10 sets that go osu! -> taiko -> catch -> osu! -> ...
+ for (int i = 0; i < 10; i++)
+ {
+ var rulesetInfo = rulesets.AvailableRulesets.ElementAt(i % 3);
+ sets.Add(TestResources.CreateTestBeatmapSetInfo(5, new[] { rulesetInfo }));
+ }
+
+ // Sort mode is important to keep the ruleset order
+ loadBeatmaps(sets, () => new FilterCriteria { Sort = SortMode.Title });
+ setSelected(1, 1);
+
+ for (int i = 1; i < 10; i++)
+ {
+ var rulesetInfo = rulesets.AvailableRulesets.ElementAt(i % 3);
+ AddStep($"Set ruleset to {rulesetInfo.ShortName}", () =>
+ {
+ carousel.Filter(new FilterCriteria { Ruleset = rulesetInfo, Sort = SortMode.Title }, false);
+ });
+ waitForSelection(i + 1, 1);
+ }
+ }
+
+ [Test]
+ public void TestCarouselSelectsBackwardsWhenDistanceIsShorter()
+ {
+ List sets = new List();
+
+ // 10 sets that go taiko, osu!, osu!, osu!, taiko, osu!, osu!, osu!, ...
+ for (int i = 0; i < 10; i++)
+ {
+ var rulesetInfo = rulesets.AvailableRulesets.ElementAt(i % 4 == 0 ? 1 : 0);
+ sets.Add(TestResources.CreateTestBeatmapSetInfo(5, new[] { rulesetInfo }));
+ }
+
+ // Sort mode is important to keep the ruleset order
+ loadBeatmaps(sets, () => new FilterCriteria { Sort = SortMode.Title });
+
+ for (int i = 2; i < 10; i += 4)
+ {
+ setSelected(i, 1);
+ AddStep("Set ruleset to taiko", () =>
+ {
+ carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(1), Sort = SortMode.Title }, false);
+ });
+ waitForSelection(i - 1, 1);
+ AddStep("Remove ruleset filter", () =>
+ {
+ carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false);
+ });
+ }
+ }
+
private void loadBeatmaps(List beatmapSets = null, Func initialCriteria = null, Action carouselAdjust = null, int? count = null,
bool randomDifficulties = false)
{
diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs
index 2be328427b..c73936da8a 100644
--- a/osu.Game/Overlays/BeatmapListingOverlay.cs
+++ b/osu.Game/Overlays/BeatmapListingOverlay.cs
@@ -115,6 +115,7 @@ namespace osu.Game.Overlays
{
filterControl.Search(query);
Show();
+ ScrollFlow.ScrollToStart();
}
protected override BeatmapListingHeader CreateHeader() => new BeatmapListingHeader();
diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs
index 98079116cd..6e54e98740 100644
--- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs
@@ -53,9 +53,16 @@ namespace osu.Game.Screens.Edit.Compose.Components
float maxDistance = new Vector2(dx, dy).Length;
int requiredCircles = Math.Min(MaxIntervals, (int)(maxDistance / DistanceBetweenTicks));
+ // We need to offset the drawn lines to the next valid snap for the currently selected divisor.
+ //
+ // Picture the scenario where the user has just placed an object on a 1/2 snap, then changes to
+ // 1/3 snap and expects to be able to place the next object on a valid 1/3 snap, regardless of the
+ // fact that the 1/2 snap reference object is not valid for 1/3 snapping.
+ float offset = SnapProvider.FindSnappedDistance(ReferenceObject, 0);
+
for (int i = 0; i < requiredCircles; i++)
{
- float diameter = (i + 1) * DistanceBetweenTicks * 2;
+ float diameter = (offset + (i + 1) * DistanceBetweenTicks) * 2;
AddInternal(new Ring(ReferenceObject, GetColourForIndexFromPlacement(i))
{
diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs
index a8cb06b888..3b694dbf43 100644
--- a/osu.Game/Screens/Select/BeatmapCarousel.cs
+++ b/osu.Game/Screens/Select/BeatmapCarousel.cs
@@ -1048,7 +1048,7 @@ namespace osu.Game.Screens.Select
protected override void PerformSelection()
{
- if (LastSelected == null || LastSelected.Filtered.Value)
+ if (LastSelected == null)
carousel?.SelectNextRandom();
else
base.PerformSelection();
diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs
index 61109829f3..6366fc8050 100644
--- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs
+++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs
@@ -108,10 +108,35 @@ namespace osu.Game.Screens.Select.Carousel
PerformSelection();
}
+ ///
+ /// Finds the item this group would select next if it attempted selection
+ ///
+ /// An unfiltered item nearest to the last selected one or null if all items are filtered
protected virtual CarouselItem GetNextToSelect()
{
- return Items.Skip(lastSelectedIndex).FirstOrDefault(i => !i.Filtered.Value) ??
- Items.Reverse().Skip(Items.Count - lastSelectedIndex).FirstOrDefault(i => !i.Filtered.Value);
+ if (Items.Count == 0)
+ return null;
+
+ int forwardsIndex = lastSelectedIndex;
+ int backwardsIndex = Math.Min(lastSelectedIndex, Items.Count - 1);
+
+ while (true)
+ {
+ bool hasBackwards = backwardsIndex >= 0 && backwardsIndex < Items.Count;
+ bool hasForwards = forwardsIndex < Items.Count;
+
+ if (!hasBackwards && !hasForwards)
+ return null;
+
+ if (hasForwards && !Items[forwardsIndex].Filtered.Value)
+ return Items[forwardsIndex];
+
+ if (hasBackwards && !Items[backwardsIndex].Filtered.Value)
+ return Items[backwardsIndex];
+
+ forwardsIndex++;
+ backwardsIndex--;
+ }
}
protected virtual void PerformSelection()