1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 07:42:57 +08:00

Merge branch 'master' into crop-texture-uploads-song-select

This commit is contained in:
Bartłomiej Dach 2023-06-08 12:24:16 +02:00 committed by GitHub
commit e6503c24c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 203 additions and 123 deletions

View File

@ -15,187 +15,175 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements
[Test]
public void TestHitAllDrumRoll()
{
const double hit_time = 1000;
PerformTest(new List<ReplayFrame>
{
new TaikoReplayFrame(0),
new TaikoReplayFrame(1000, TaikoAction.LeftCentre),
new TaikoReplayFrame(1001),
new TaikoReplayFrame(1250, TaikoAction.LeftCentre),
new TaikoReplayFrame(1251),
new TaikoReplayFrame(1500, TaikoAction.LeftCentre),
new TaikoReplayFrame(1501),
new TaikoReplayFrame(1750, TaikoAction.LeftCentre),
new TaikoReplayFrame(1751),
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
new TaikoReplayFrame(2001),
}, CreateBeatmap(new DrumRoll
{
StartTime = hit_time,
Duration = 1000
}));
}, CreateBeatmap(createDrumRoll(false)));
AssertJudgementCount(3);
AssertJudgementCount(6);
AssertResult<DrumRollTick>(0, HitResult.SmallBonus);
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
AssertResult<DrumRollTick>(2, HitResult.SmallBonus);
AssertResult<DrumRollTick>(3, HitResult.SmallBonus);
AssertResult<DrumRollTick>(4, HitResult.SmallBonus);
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
}
[Test]
public void TestHitSomeDrumRoll()
{
const double hit_time = 1000;
PerformTest(new List<ReplayFrame>
{
new TaikoReplayFrame(0),
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
new TaikoReplayFrame(2001),
}, CreateBeatmap(new DrumRoll
{
StartTime = hit_time,
Duration = 1000
}));
}, CreateBeatmap(createDrumRoll(false)));
AssertJudgementCount(3);
AssertJudgementCount(6);
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
AssertResult<DrumRollTick>(1, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(2, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(3, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(4, HitResult.SmallBonus);
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
}
[Test]
public void TestHitNoneDrumRoll()
{
const double hit_time = 1000;
PerformTest(new List<ReplayFrame>
{
new TaikoReplayFrame(0),
}, CreateBeatmap(new DrumRoll
{
StartTime = hit_time,
Duration = 1000
}));
}, CreateBeatmap(createDrumRoll(false)));
AssertJudgementCount(3);
AssertJudgementCount(6);
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(1, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(2, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(3, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(4, HitResult.IgnoreMiss);
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
}
[Test]
public void TestHitAllStrongDrumRollWithOneKey()
{
const double hit_time = 1000;
PerformTest(new List<ReplayFrame>
{
new TaikoReplayFrame(0),
new TaikoReplayFrame(1000, TaikoAction.LeftCentre),
new TaikoReplayFrame(1001),
new TaikoReplayFrame(1250, TaikoAction.LeftCentre),
new TaikoReplayFrame(1251),
new TaikoReplayFrame(1500, TaikoAction.LeftCentre),
new TaikoReplayFrame(1501),
new TaikoReplayFrame(1750, TaikoAction.LeftCentre),
new TaikoReplayFrame(1751),
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
new TaikoReplayFrame(2001),
}, CreateBeatmap(new DrumRoll
}, CreateBeatmap(createDrumRoll(true)));
AssertJudgementCount(12);
for (int i = 0; i < 5; i++)
{
StartTime = hit_time,
Duration = 1000,
IsStrong = true
}));
AssertJudgementCount(6);
AssertResult<DrumRollTick>(0, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(0, HitResult.LargeBonus);
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(1, HitResult.LargeBonus);
AssertResult<DrumRollTick>(i, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(i, HitResult.LargeBonus);
}
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
AssertResult<StrongNestedHitObject>(2, HitResult.IgnoreHit);
AssertResult<StrongNestedHitObject>(5, HitResult.IgnoreHit);
}
[Test]
public void TestHitSomeStrongDrumRollWithOneKey()
{
const double hit_time = 1000;
PerformTest(new List<ReplayFrame>
{
new TaikoReplayFrame(0),
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
new TaikoReplayFrame(2001),
}, CreateBeatmap(new DrumRoll
{
StartTime = hit_time,
Duration = 1000,
IsStrong = true
}));
}, CreateBeatmap(createDrumRoll(true)));
AssertJudgementCount(6);
AssertJudgementCount(12);
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
AssertResult<StrongNestedHitObject>(0, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(1, HitResult.LargeBonus);
AssertResult<DrumRollTick>(4, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(4, HitResult.LargeBonus);
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
AssertResult<StrongNestedHitObject>(2, HitResult.IgnoreHit);
AssertResult<StrongNestedHitObject>(5, HitResult.IgnoreHit);
}
[Test]
public void TestHitAllStrongDrumRollWithBothKeys()
{
const double hit_time = 1000;
PerformTest(new List<ReplayFrame>
{
new TaikoReplayFrame(0),
new TaikoReplayFrame(1000, TaikoAction.LeftCentre, TaikoAction.RightCentre),
new TaikoReplayFrame(1001),
new TaikoReplayFrame(1250, TaikoAction.LeftCentre, TaikoAction.RightCentre),
new TaikoReplayFrame(1251),
new TaikoReplayFrame(1500, TaikoAction.LeftCentre, TaikoAction.RightCentre),
new TaikoReplayFrame(1501),
new TaikoReplayFrame(1750, TaikoAction.LeftCentre, TaikoAction.RightCentre),
new TaikoReplayFrame(1751),
new TaikoReplayFrame(2000, TaikoAction.LeftCentre, TaikoAction.RightCentre),
new TaikoReplayFrame(2001),
}, CreateBeatmap(new DrumRoll
}, CreateBeatmap(createDrumRoll(true)));
AssertJudgementCount(12);
for (int i = 0; i < 5; i++)
{
StartTime = hit_time,
Duration = 1000,
IsStrong = true
}));
AssertJudgementCount(6);
AssertResult<DrumRollTick>(0, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(0, HitResult.LargeBonus);
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(1, HitResult.LargeBonus);
AssertResult<DrumRollTick>(i, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(i, HitResult.LargeBonus);
}
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
AssertResult<StrongNestedHitObject>(2, HitResult.IgnoreHit);
AssertResult<StrongNestedHitObject>(5, HitResult.IgnoreHit);
}
[Test]
public void TestHitSomeStrongDrumRollWithBothKeys()
{
const double hit_time = 1000;
PerformTest(new List<ReplayFrame>
{
new TaikoReplayFrame(0),
new TaikoReplayFrame(2000, TaikoAction.LeftCentre, TaikoAction.RightCentre),
new TaikoReplayFrame(2001),
}, CreateBeatmap(new DrumRoll
{
StartTime = hit_time,
Duration = 1000,
IsStrong = true
}));
}, CreateBeatmap(createDrumRoll(true)));
AssertJudgementCount(6);
AssertJudgementCount(12);
AssertResult<DrumRollTick>(0, HitResult.IgnoreMiss);
AssertResult<StrongNestedHitObject>(0, HitResult.IgnoreMiss);
AssertResult<DrumRollTick>(1, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(1, HitResult.LargeBonus);
AssertResult<DrumRollTick>(4, HitResult.SmallBonus);
AssertResult<StrongNestedHitObject>(4, HitResult.LargeBonus);
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
AssertResult<StrongNestedHitObject>(2, HitResult.IgnoreHit);
AssertResult<StrongNestedHitObject>(5, HitResult.IgnoreHit);
}
private DrumRoll createDrumRoll(bool strong) => new DrumRoll
{
StartTime = 1000,
Duration = 1000,
IsStrong = strong
};
}
}

View File

@ -92,6 +92,14 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
}).ToList();
}
// TODO: stable makes the last tick of a drumroll non-required when the next object is too close.
// This probably needs to be reimplemented:
//
// List<HitObject> hitobjects = hitObjectManager.hitObjects;
// int ind = hitobjects.IndexOf(this);
// if (i < hitobjects.Count - 1 && hitobjects[i + 1].HittableStartTime - (EndTime + (int)TickSpacing) <= (int)TickSpacing)
// lastTickHittable = false;
return converted;
}
@ -133,7 +141,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
StartTime = obj.StartTime,
Samples = obj.Samples,
Duration = taikoDuration,
TickRate = beatmap.Difficulty.SliderTickRate == 3 ? 3 : 4,
SliderVelocity = obj is IHasSliderVelocity velocityData ? velocityData.SliderVelocity : 1
};
}

View File

@ -1,8 +1,6 @@
// 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.
#nullable disable
using osu.Game.Rulesets.Objects.Types;
using System.Threading;
using osu.Framework.Bindables;
@ -69,6 +67,8 @@ namespace osu.Game.Rulesets.Taiko.Objects
double scoringDistance = base_distance * difficulty.SliderMultiplier * SliderVelocity;
Velocity = scoringDistance / timingPoint.BeatLength;
TickRate = difficulty.SliderTickRate == 3 ? 3 : 4;
tickSpacing = timingPoint.BeatLength / TickRate;
}

View File

@ -131,7 +131,7 @@ namespace osu.Game.Tests.Editing.Checks
var mock = new Mock<IWorkingBeatmap>();
mock.SetupGet(w => w.Beatmap).Returns(beatmap);
mock.SetupGet(w => w.GetBackground()).Returns(background);
mock.Setup(w => w.GetBackground()).Returns(background);
mock.Setup(w => w.GetStream(It.IsAny<string>())).Returns(stream);
return mock;

View File

@ -80,6 +80,24 @@ namespace osu.Game.Tests.Visual.UserInterface
});
}
[Test]
public void TestCorrectScrollToWhenContentLoads()
{
AddRepeatStep("add many sections", () => append(1f), 3);
AddStep("add section with delayed load content", () =>
{
container.Add(new TestDelayedLoadSection("delayed"));
});
AddStep("add final section", () => append(0.5f));
AddStep("scroll to final section", () => container.ScrollTo(container.Children.Last()));
AddUntilStep("correct section selected", () => container.SelectedSection.Value == container.Children.Last());
AddUntilStep("wait for scroll to section", () => container.ScreenSpaceDrawQuad.AABBFloat.Contains(container.Children.Last().ScreenSpaceDrawQuad.AABBFloat));
}
[Test]
public void TestSelection()
{
@ -196,6 +214,33 @@ namespace osu.Game.Tests.Visual.UserInterface
InputManager.ScrollVerticalBy(direction);
}
private partial class TestDelayedLoadSection : TestSection
{
public TestDelayedLoadSection(string label)
: base(label)
{
BackgroundColour = default_colour;
Width = 300;
AutoSizeAxes = Axes.Y;
}
protected override void LoadComplete()
{
base.LoadComplete();
Box box;
Add(box = new Box
{
Alpha = 0.01f,
RelativeSizeAxes = Axes.X,
});
// Emulate an operation that will be inhibited by IsMaskedAway.
box.ResizeHeightTo(2000, 50);
}
}
private partial class TestSection : TestBox
{
public bool Selected

View File

@ -1,17 +1,16 @@
// 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.
#nullable disable
using System;
using System.Diagnostics;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Layout;
using osu.Framework.Logging;
using osu.Framework.Threading;
using osu.Framework.Utils;
namespace osu.Game.Graphics.Containers
@ -23,11 +22,35 @@ namespace osu.Game.Graphics.Containers
public partial class SectionsContainer<T> : Container<T>
where T : Drawable
{
public Bindable<T> SelectedSection { get; } = new Bindable<T>();
public Bindable<T?> SelectedSection { get; } = new Bindable<T?>();
private T lastClickedSection;
private T? lastClickedSection;
public Drawable ExpandableHeader
protected override Container<T> Content => scrollContentContainer;
private readonly UserTrackingScrollContainer scrollContainer;
private readonly Container headerBackgroundContainer;
private readonly MarginPadding originalSectionsMargin;
private Drawable? fixedHeader;
private Drawable? footer;
private Drawable? headerBackground;
private FlowContainer<T> scrollContentContainer = null!;
private float? headerHeight, footerHeight;
private float? lastKnownScroll;
/// <summary>
/// The percentage of the container to consider the centre-point for deciding the active section (and scrolling to a requested section).
/// </summary>
private const float scroll_y_centre = 0.1f;
private Drawable? expandableHeader;
public Drawable? ExpandableHeader
{
get => expandableHeader;
set
@ -42,11 +65,12 @@ namespace osu.Game.Graphics.Containers
if (value == null) return;
AddInternal(expandableHeader);
lastKnownScroll = null;
}
}
public Drawable FixedHeader
public Drawable? FixedHeader
{
get => fixedHeader;
set
@ -63,7 +87,7 @@ namespace osu.Game.Graphics.Containers
}
}
public Drawable Footer
public Drawable? Footer
{
get => footer;
set
@ -75,16 +99,17 @@ namespace osu.Game.Graphics.Containers
footer = value;
if (value == null) return;
if (footer == null) return;
footer.Anchor |= Anchor.y2;
footer.Origin |= Anchor.y2;
scrollContainer.Add(footer);
lastKnownScroll = null;
}
}
public Drawable HeaderBackground
public Drawable? HeaderBackground
{
get => headerBackground;
set
@ -102,23 +127,6 @@ namespace osu.Game.Graphics.Containers
}
}
protected override Container<T> Content => scrollContentContainer;
private readonly UserTrackingScrollContainer scrollContainer;
private readonly Container headerBackgroundContainer;
private readonly MarginPadding originalSectionsMargin;
private Drawable expandableHeader, fixedHeader, footer, headerBackground;
private FlowContainer<T> scrollContentContainer;
private float? headerHeight, footerHeight;
private float? lastKnownScroll;
/// <summary>
/// The percentage of the container to consider the centre-point for deciding the active section (and scrolling to a requested section).
/// </summary>
private const float scroll_y_centre = 0.1f;
public SectionsContainer()
{
AddRangeInternal(new Drawable[]
@ -150,31 +158,63 @@ namespace osu.Game.Graphics.Containers
footerHeight = null;
}
private ScheduledDelegate? scrollToTargetDelegate;
public void ScrollTo(Drawable target)
{
Logger.Log($"Scrolling to {target}..");
lastKnownScroll = null;
// implementation similar to ScrollIntoView but a bit more nuanced.
float top = scrollContainer.GetChildPosInContent(target);
float scrollTarget = getScrollTargetForDrawable(target);
float bottomScrollExtent = scrollContainer.ScrollableExtent;
float scrollTarget = top - scrollContainer.DisplayableContent * scroll_y_centre;
if (scrollTarget > bottomScrollExtent)
if (scrollTarget > scrollContainer.ScrollableExtent)
scrollContainer.ScrollToEnd();
else
scrollContainer.ScrollTo(scrollTarget);
if (target is T section)
lastClickedSection = section;
// Content may load in as a scroll occurs, changing the scroll target we need to aim for.
// This scheduled operation ensures that we keep trying until actually arriving at the target.
scrollToTargetDelegate?.Cancel();
scrollToTargetDelegate = Scheduler.AddDelayed(() =>
{
if (scrollContainer.UserScrolling)
{
Logger.Log("Scroll operation interrupted by user scroll");
scrollToTargetDelegate?.Cancel();
scrollToTargetDelegate = null;
return;
}
if (Precision.AlmostEquals(scrollContainer.Current, scrollTarget, 1))
{
Logger.Log($"Finished scrolling to {target}!");
scrollToTargetDelegate?.Cancel();
scrollToTargetDelegate = null;
return;
}
if (!Precision.AlmostEquals(getScrollTargetForDrawable(target), scrollTarget, 1))
{
Logger.Log($"Reattempting scroll to {target} due to change in position");
ScrollTo(target);
}
}, 50, true);
}
private float getScrollTargetForDrawable(Drawable target)
{
// implementation similar to ScrollIntoView but a bit more nuanced.
return scrollContainer.GetChildPosInContent(target) - scrollContainer.DisplayableContent * scroll_y_centre;
}
public void ScrollToTop() => scrollContainer.ScrollTo(0);
[NotNull]
protected virtual UserTrackingScrollContainer CreateScrollContainer() => new UserTrackingScrollContainer();
[NotNull]
protected virtual FlowContainer<T> CreateScrollContentContainer() =>
new FillFlowContainer<T>
{

View File

@ -328,7 +328,7 @@ namespace osu.Game.Overlays
base.UpdateAfterChildren();
// no null check because the usage of this class is strict
HeaderBackground.Alpha = -ExpandableHeader.Y / ExpandableHeader.LayoutSize.Y;
HeaderBackground!.Alpha = -ExpandableHeader!.Y / ExpandableHeader.LayoutSize.Y;
}
}
}

View File

@ -120,7 +120,7 @@ namespace osu.Game.Overlays
if (lastSection != section.NewValue)
{
lastSection = section.NewValue;
tabs.Current.Value = lastSection;
tabs.Current.Value = lastSection!;
}
};

View File

@ -65,7 +65,7 @@ namespace osu.Game.Screens.Edit.Setup
{
base.LoadComplete();
sections.SelectedSection.BindValueChanged(section => tabControl.Current.Value = section.NewValue);
sections.SelectedSection.BindValueChanged(section => tabControl.Current.Value = section.NewValue!);
tabControl.Current.BindValueChanged(section =>
{
if (section.NewValue != sections.SelectedSection.Value)