mirror of
https://github.com/ppy/osu
synced 2025-03-22 19:06:58 +00:00
Merge branch 'master' into map_info_on_mod_settings
This commit is contained in:
commit
0d7157f727
osu.Game.Rulesets.Osu.Tests
osu.Game.Rulesets.Osu
osu.Game.Tests/Visual/Online
osu.Game/Overlays/Profile/Header
@ -506,7 +506,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
[Ignore("Currently broken, first attempt at fixing broke even harder. See https://github.com/ppy/osu/issues/24743.")]
|
|
||||||
public void TestInputDoesNotFallThroughOverlappingSliders()
|
public void TestInputDoesNotFallThroughOverlappingSliders()
|
||||||
{
|
{
|
||||||
const double time_first_slider = 1000;
|
const double time_first_slider = 1000;
|
||||||
@ -550,12 +549,103 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
addJudgementOffsetAssert("first slider head", () => ((Slider)hitObjects[0]).HeadCircle, 0);
|
addJudgementOffsetAssert("first slider head", () => ((Slider)hitObjects[0]).HeadCircle, 0);
|
||||||
addJudgementAssert(hitObjects[1], HitResult.Miss);
|
addJudgementAssert(hitObjects[1], HitResult.Miss);
|
||||||
// the slider head of the first slider prevents the second slider's head from being hit, so the judgement offset should be very late.
|
// the slider head of the first slider prevents the second slider's head from being hit, so the judgement offset should be very late.
|
||||||
|
// this is not strictly done by the hit policy implementation itself (see `OsuModClassic.blockInputToObjectsUnderSliderHead()`),
|
||||||
|
// but we're testing this here anyways to just keep everything related to input handling and note lock in one place.
|
||||||
addJudgementOffsetAssert("second slider head", () => ((Slider)hitObjects[1]).HeadCircle, referenceHitWindows.WindowFor(HitResult.Meh));
|
addJudgementOffsetAssert("second slider head", () => ((Slider)hitObjects[1]).HeadCircle, referenceHitWindows.WindowFor(HitResult.Meh));
|
||||||
addClickActionAssert(0, ClickAction.Hit);
|
addClickActionAssert(0, ClickAction.Hit);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestOverlappingObjectsDontBlockEachOtherWhenFullyFadedOut()
|
public void TestOverlappingSlidersDontBlockEachOtherWhenFullyJudged()
|
||||||
|
{
|
||||||
|
const double time_first_slider = 1000;
|
||||||
|
const double time_second_slider = 1600;
|
||||||
|
Vector2 positionFirstSlider = new Vector2(100, 50);
|
||||||
|
Vector2 positionSecondSlider = new Vector2(100, 80);
|
||||||
|
var midpoint = (positionFirstSlider + positionSecondSlider) / 2;
|
||||||
|
|
||||||
|
var hitObjects = new List<OsuHitObject>
|
||||||
|
{
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = time_first_slider,
|
||||||
|
Position = positionFirstSlider,
|
||||||
|
Path = new SliderPath(PathType.Linear, new[]
|
||||||
|
{
|
||||||
|
Vector2.Zero,
|
||||||
|
new Vector2(25, 0),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = time_second_slider,
|
||||||
|
Position = positionSecondSlider,
|
||||||
|
Path = new SliderPath(PathType.Linear, new[]
|
||||||
|
{
|
||||||
|
Vector2.Zero,
|
||||||
|
new Vector2(25, 0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
performTest(hitObjects, new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame { Time = time_first_slider, Position = midpoint, Actions = { OsuAction.RightButton } },
|
||||||
|
new OsuReplayFrame { Time = time_first_slider + 25, Position = midpoint },
|
||||||
|
// this frame doesn't do anything on lazer, but is REQUIRED for correct playback on stable,
|
||||||
|
// because stable during replay playback only updates game state _when it encounters a replay frame_
|
||||||
|
new OsuReplayFrame { Time = 1250, Position = midpoint },
|
||||||
|
new OsuReplayFrame { Time = time_second_slider + 50, Position = midpoint, Actions = { OsuAction.LeftButton } },
|
||||||
|
new OsuReplayFrame { Time = time_second_slider + 75, Position = midpoint },
|
||||||
|
});
|
||||||
|
|
||||||
|
addJudgementAssert(hitObjects[0], HitResult.Ok);
|
||||||
|
addJudgementOffsetAssert("first slider head", () => ((Slider)hitObjects[0]).HeadCircle, 0);
|
||||||
|
addJudgementAssert(hitObjects[1], HitResult.Ok);
|
||||||
|
addJudgementOffsetAssert("second slider head", () => ((Slider)hitObjects[1]).HeadCircle, 50);
|
||||||
|
addClickActionAssert(0, ClickAction.Hit);
|
||||||
|
addClickActionAssert(1, ClickAction.Hit);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOverlappingHitCirclesDontBlockEachOtherWhenBothVisible()
|
||||||
|
{
|
||||||
|
const double time_first_circle = 1000;
|
||||||
|
const double time_second_circle = 1200;
|
||||||
|
Vector2 positionFirstCircle = new Vector2(100);
|
||||||
|
Vector2 positionSecondCircle = new Vector2(120);
|
||||||
|
var midpoint = (positionFirstCircle + positionSecondCircle) / 2;
|
||||||
|
|
||||||
|
var hitObjects = new List<OsuHitObject>
|
||||||
|
{
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = time_first_circle,
|
||||||
|
Position = positionFirstCircle,
|
||||||
|
},
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = time_second_circle,
|
||||||
|
Position = positionSecondCircle,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
performTest(hitObjects, new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame { Time = time_first_circle, Position = midpoint, Actions = { OsuAction.LeftButton } },
|
||||||
|
new OsuReplayFrame { Time = time_first_circle + 25, Position = midpoint },
|
||||||
|
new OsuReplayFrame { Time = time_first_circle + 50, Position = midpoint, Actions = { OsuAction.RightButton } },
|
||||||
|
});
|
||||||
|
|
||||||
|
addJudgementAssert(hitObjects[0], HitResult.Great);
|
||||||
|
addJudgementOffsetAssert(hitObjects[0], 0);
|
||||||
|
|
||||||
|
addJudgementAssert(hitObjects[1], HitResult.Meh);
|
||||||
|
addJudgementOffsetAssert(hitObjects[1], -150);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOverlappingHitCirclesDontBlockEachOtherWhenFullyFadedOut()
|
||||||
{
|
{
|
||||||
const double time_first_circle = 1000;
|
const double time_first_circle = 1000;
|
||||||
const double time_second_circle = 1200;
|
const double time_second_circle = 1200;
|
||||||
@ -586,8 +676,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
new OsuReplayFrame { Time = time_first_circle, Position = positionFirstCircle, Actions = { OsuAction.LeftButton } },
|
new OsuReplayFrame { Time = time_first_circle, Position = positionFirstCircle, Actions = { OsuAction.LeftButton } },
|
||||||
new OsuReplayFrame { Time = time_first_circle + 50, Position = positionFirstCircle },
|
new OsuReplayFrame { Time = time_first_circle + 50, Position = positionFirstCircle },
|
||||||
|
new OsuReplayFrame { Time = time_second_circle - 50, Position = positionSecondCircle },
|
||||||
new OsuReplayFrame { Time = time_second_circle, Position = positionSecondCircle, Actions = { OsuAction.LeftButton } },
|
new OsuReplayFrame { Time = time_second_circle, Position = positionSecondCircle, Actions = { OsuAction.LeftButton } },
|
||||||
new OsuReplayFrame { Time = time_second_circle + 50, Position = positionSecondCircle },
|
new OsuReplayFrame { Time = time_second_circle + 50, Position = positionSecondCircle },
|
||||||
|
new OsuReplayFrame { Time = time_third_circle - 50, Position = positionFirstCircle },
|
||||||
new OsuReplayFrame { Time = time_third_circle, Position = positionFirstCircle, Actions = { OsuAction.LeftButton } },
|
new OsuReplayFrame { Time = time_third_circle, Position = positionFirstCircle, Actions = { OsuAction.LeftButton } },
|
||||||
new OsuReplayFrame { Time = time_third_circle + 50, Position = positionFirstCircle },
|
new OsuReplayFrame { Time = time_third_circle + 50, Position = positionFirstCircle },
|
||||||
});
|
});
|
||||||
|
@ -74,6 +74,10 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
head.TrackFollowCircle = !NoSliderHeadMovement.Value;
|
head.TrackFollowCircle = !NoSliderHeadMovement.Value;
|
||||||
if (FadeHitCircleEarly.Value && !usingHiddenFading)
|
if (FadeHitCircleEarly.Value && !usingHiddenFading)
|
||||||
applyEarlyFading(head);
|
applyEarlyFading(head);
|
||||||
|
|
||||||
|
if (ClassicNoteLock.Value)
|
||||||
|
blockInputToObjectsUnderSliderHead(head);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DrawableSliderTail tail:
|
case DrawableSliderTail tail:
|
||||||
@ -83,10 +87,27 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
case DrawableHitCircle circle:
|
case DrawableHitCircle circle:
|
||||||
if (FadeHitCircleEarly.Value && !usingHiddenFading)
|
if (FadeHitCircleEarly.Value && !usingHiddenFading)
|
||||||
applyEarlyFading(circle);
|
applyEarlyFading(circle);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// On stable, slider heads that have already been hit block input from reaching objects that may be underneath them
|
||||||
|
/// until the sliders they're part of have been fully judged.
|
||||||
|
/// The purpose of this method is to restore that behaviour.
|
||||||
|
/// In order to avoid introducing yet another confusing config option, this behaviour is roped into the general notion of "note lock".
|
||||||
|
/// </summary>
|
||||||
|
private static void blockInputToObjectsUnderSliderHead(DrawableSliderHead slider)
|
||||||
|
{
|
||||||
|
var oldHitAction = slider.HitArea.Hit;
|
||||||
|
slider.HitArea.Hit = () =>
|
||||||
|
{
|
||||||
|
oldHitAction?.Invoke();
|
||||||
|
return !slider.DrawableSlider.AllJudged;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private void applyEarlyFading(DrawableHitCircle circle)
|
private void applyEarlyFading(DrawableHitCircle circle)
|
||||||
{
|
{
|
||||||
circle.ApplyCustomUpdateState += (dho, state) =>
|
circle.ApplyCustomUpdateState += (dho, state) =>
|
||||||
|
@ -261,7 +261,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
case OsuAction.RightButton:
|
case OsuAction.RightButton:
|
||||||
if (IsHovered && (Hit?.Invoke() ?? false))
|
if (IsHovered && (Hit?.Invoke() ?? false))
|
||||||
{
|
{
|
||||||
HitAction = e.Action;
|
HitAction ??= e.Action;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,5 +110,31 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
}
|
}
|
||||||
}, new OsuRuleset().RulesetInfo));
|
}, new OsuRuleset().RulesetInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPreviousUsernames()
|
||||||
|
{
|
||||||
|
AddStep("Show user w/ previous usernames", () => header.User.Value = new UserProfileData(new APIUser
|
||||||
|
{
|
||||||
|
Id = 727,
|
||||||
|
Username = "SomeoneIndecisive",
|
||||||
|
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg",
|
||||||
|
Groups = new[]
|
||||||
|
{
|
||||||
|
new APIUserGroup { Colour = "#EB47D0", ShortName = "DEV", Name = "Developers" },
|
||||||
|
},
|
||||||
|
Statistics = new UserStatistics
|
||||||
|
{
|
||||||
|
IsRanked = false,
|
||||||
|
// web will sometimes return non-empty rank history even for unranked users.
|
||||||
|
RankHistory = new APIRankHistory
|
||||||
|
{
|
||||||
|
Mode = @"osu",
|
||||||
|
Data = Enumerable.Range(2345, 85).ToArray()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PreviousUsernames = new[] { "tsrk.", "quoicoubeh", "apagnan", "epita" }
|
||||||
|
}, new OsuRuleset().RulesetInfo));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,20 +5,29 @@ using System;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Profile.Header.Components;
|
using osu.Game.Overlays.Profile.Header.Components;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Online
|
namespace osu.Game.Tests.Visual.Online
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public partial class TestSceneUserProfilePreviousUsernames : OsuTestScene
|
public partial class TestSceneUserProfilePreviousUsernamesDisplay : OsuTestScene
|
||||||
{
|
{
|
||||||
private PreviousUsernames container = null!;
|
private PreviousUsernamesDisplay container = null!;
|
||||||
|
private OverlayColourProvider colourProvider = null!;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp() => Schedule(() =>
|
public void SetUp() => Schedule(() =>
|
||||||
{
|
{
|
||||||
Child = container = new PreviousUsernames
|
colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink);
|
||||||
|
Child = new DependencyProvidingContainer
|
||||||
{
|
{
|
||||||
|
Child = container = new PreviousUsernamesDisplay
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
CachedDependencies = new (Type, object)[] { (typeof(OverlayColourProvider), colourProvider) },
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
};
|
};
|
@ -18,12 +18,13 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Overlays.Profile.Header.Components
|
namespace osu.Game.Overlays.Profile.Header.Components
|
||||||
{
|
{
|
||||||
public partial class PreviousUsernames : CompositeDrawable
|
public partial class PreviousUsernamesDisplay : CompositeDrawable
|
||||||
{
|
{
|
||||||
private const int duration = 200;
|
private const int duration = 200;
|
||||||
private const int margin = 10;
|
private const int margin = 10;
|
||||||
private const int width = 310;
|
private const int width = 300;
|
||||||
private const int move_offset = 15;
|
private const int move_offset = 15;
|
||||||
|
private const int base_y_offset = -3; // eye balled to make it look good
|
||||||
|
|
||||||
public readonly Bindable<APIUser?> User = new Bindable<APIUser?>();
|
public readonly Bindable<APIUser?> User = new Bindable<APIUser?>();
|
||||||
|
|
||||||
@ -31,14 +32,15 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
private readonly Box background;
|
private readonly Box background;
|
||||||
private readonly SpriteText header;
|
private readonly SpriteText header;
|
||||||
|
|
||||||
public PreviousUsernames()
|
public PreviousUsernamesDisplay()
|
||||||
{
|
{
|
||||||
HoverIconContainer hoverIcon;
|
HoverIconContainer hoverIcon;
|
||||||
|
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
Width = width;
|
Width = width;
|
||||||
Masking = true;
|
Masking = true;
|
||||||
CornerRadius = 5;
|
CornerRadius = 6;
|
||||||
|
Y = base_y_offset;
|
||||||
|
|
||||||
AddRangeInternal(new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
{
|
{
|
||||||
@ -84,6 +86,9 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Direction = FillDirection.Full,
|
Direction = FillDirection.Full,
|
||||||
|
// Prevents the tooltip of having a sudden size reduction and flickering when the text is being faded out.
|
||||||
|
// Also prevents a potential OnHover/HoverLost feedback loop.
|
||||||
|
AlwaysPresent = true,
|
||||||
Margin = new MarginPadding { Bottom = margin, Top = margin / 2f }
|
Margin = new MarginPadding { Bottom = margin, Top = margin / 2f }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,9 +101,9 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OverlayColourProvider colours)
|
||||||
{
|
{
|
||||||
background.Colour = colours.GreySeaFoamDarker;
|
background.Colour = colours.Background6;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -134,7 +139,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
text.FadeIn(duration, Easing.OutQuint);
|
text.FadeIn(duration, Easing.OutQuint);
|
||||||
header.FadeIn(duration, Easing.OutQuint);
|
header.FadeIn(duration, Easing.OutQuint);
|
||||||
background.FadeIn(duration, Easing.OutQuint);
|
background.FadeIn(duration, Easing.OutQuint);
|
||||||
this.MoveToY(-move_offset, duration, Easing.OutQuint);
|
this.MoveToY(base_y_offset - move_offset, duration, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void hideContent()
|
private void hideContent()
|
||||||
@ -142,7 +147,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
text.FadeOut(duration, Easing.OutQuint);
|
text.FadeOut(duration, Easing.OutQuint);
|
||||||
header.FadeOut(duration, Easing.OutQuint);
|
header.FadeOut(duration, Easing.OutQuint);
|
||||||
background.FadeOut(duration, Easing.OutQuint);
|
background.FadeOut(duration, Easing.OutQuint);
|
||||||
this.MoveToY(0, duration, Easing.OutQuint);
|
this.MoveToY(base_y_offset, duration, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class HoverIconContainer : Container
|
private partial class HoverIconContainer : Container
|
||||||
@ -156,7 +161,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
{
|
{
|
||||||
Margin = new MarginPadding { Top = 6, Left = margin, Right = margin * 2 },
|
Margin = new MarginPadding { Top = 6, Left = margin, Right = margin * 2 },
|
||||||
Size = new Vector2(15),
|
Size = new Vector2(15),
|
||||||
Icon = FontAwesome.Solid.IdCard,
|
Icon = FontAwesome.Solid.AddressCard,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -46,6 +46,7 @@ namespace osu.Game.Overlays.Profile.Header
|
|||||||
private OsuSpriteText userCountryText = null!;
|
private OsuSpriteText userCountryText = null!;
|
||||||
private GroupBadgeFlow groupBadgeFlow = null!;
|
private GroupBadgeFlow groupBadgeFlow = null!;
|
||||||
private ToggleCoverButton coverToggle = null!;
|
private ToggleCoverButton coverToggle = null!;
|
||||||
|
private PreviousUsernamesDisplay previousUsernamesDisplay = null!;
|
||||||
|
|
||||||
private Bindable<bool> coverExpanded = null!;
|
private Bindable<bool> coverExpanded = null!;
|
||||||
|
|
||||||
@ -143,6 +144,11 @@ namespace osu.Game.Overlays.Profile.Header
|
|||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
},
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
// Intentionally use a zero-size container, else the fill flow will adjust to (and cancel) the upwards animation.
|
||||||
|
Child = previousUsernamesDisplay = new PreviousUsernamesDisplay(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
titleText = new OsuSpriteText
|
titleText = new OsuSpriteText
|
||||||
@ -216,6 +222,7 @@ namespace osu.Game.Overlays.Profile.Header
|
|||||||
titleText.Text = user?.Title ?? string.Empty;
|
titleText.Text = user?.Title ?? string.Empty;
|
||||||
titleText.Colour = Color4Extensions.FromHex(user?.Colour ?? "fff");
|
titleText.Colour = Color4Extensions.FromHex(user?.Colour ?? "fff");
|
||||||
groupBadgeFlow.User.Value = user;
|
groupBadgeFlow.User.Value = user;
|
||||||
|
previousUsernamesDisplay.User.Value = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateCoverState()
|
private void updateCoverState()
|
||||||
|
Loading…
Reference in New Issue
Block a user