mirror of
https://github.com/ppy/osu.git
synced 2024-11-06 06:57:39 +08:00
Merge branch 'master' into grid-momentary-shortcuts
This commit is contained in:
commit
f787da892b
@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -76,8 +76,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
performTest(objects, new List<ReplayFrame>());
|
||||
|
||||
addJudgementAssert(objects[0], HitResult.IgnoreHit);
|
||||
addJudgementAssert(objects[1], HitResult.IgnoreHit);
|
||||
addJudgementAssert(objects[0], HitResult.IgnoreMiss);
|
||||
addJudgementAssert(objects[1], HitResult.IgnoreMiss);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -69,6 +69,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -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<CircularProgress>().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<Vector2, Vector2> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,12 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<TestTaikoModFlashlight.TestTaikoFlashlight>().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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<TaikoHitObject> 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)
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="size">
|
||||
/// The size of the flashlight.
|
||||
/// The value provided here should always come from <see cref="ModFlashlight{T}.Flashlight.GetSize"/>.
|
||||
/// </param>
|
||||
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();
|
||||
}
|
||||
|
@ -862,52 +862,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddAssert("Selection was remembered", () => eagerSelectedIDs.Count == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRandomFallbackOnNonMatchingPrevious()
|
||||
{
|
||||
List<BeatmapSetInfo> manySets = new List<BeatmapSetInfo>();
|
||||
|
||||
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<BeatmapSetInfo> sets = new List<BeatmapSetInfo>();
|
||||
|
||||
// 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<BeatmapSetInfo> sets = new List<BeatmapSetInfo>();
|
||||
|
||||
// 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<BeatmapSetInfo> beatmapSets = null, Func<FilterCriteria> initialCriteria = null, Action<BeatmapCarousel> carouselAdjust = null, int? count = null,
|
||||
bool randomDifficulties = false)
|
||||
{
|
||||
|
@ -115,6 +115,7 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
filterControl.Search(query);
|
||||
Show();
|
||||
ScrollFlow.ScrollToStart();
|
||||
}
|
||||
|
||||
protected override BeatmapListingHeader CreateHeader() => new BeatmapListingHeader();
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -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();
|
||||
|
@ -108,10 +108,35 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
PerformSelection();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the item this group would select next if it attempted selection
|
||||
/// </summary>
|
||||
/// <returns>An unfiltered item nearest to the last selected one or null if all items are filtered</returns>
|
||||
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()
|
||||
|
Loading…
Reference in New Issue
Block a user