diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/Intro/VsSequence.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/Intro/VsSequence.cs index 5b2656de90..0bd7d0e287 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/Intro/VsSequence.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/Intro/VsSequence.cs @@ -137,7 +137,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay.Intro vsText.ScaleTo(0.4f, 1300, Easing.OutExpo); } - delay += 850; + delay += delay_first; impactDelay = delay; @@ -187,9 +187,14 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay.Intro this.Delay(3200).FadeOut(300).Expire(); } - delay += 3350; + delay += delay_final; } + private const double delay_first = 850; + private const double delay_final = 3350; + + public const double INTRO_LENGTH = delay_first + delay_final; + private partial class UserDisplay : CompositeDrawable { public UserDisplay(UserWithRating user, Anchor contentAnchor) diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/RankedPlayScreen.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/RankedPlayScreen.cs index 1f127de542..fbc8eb319b 100644 --- a/osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/RankedPlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/RankedPlayScreen.cs @@ -1,6 +1,7 @@ // 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.Diagnostics; using System.Linq; using osu.Framework.Allocation; @@ -67,6 +68,9 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay [Resolved] private IDialogOverlay dialogOverlay { get; set; } = null!; + [Resolved] + private IOverlayManager overlayManager { get; set; } = null!; + [Resolved] private AudioManager audio { get; set; } = null!; @@ -85,6 +89,9 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay private readonly Container screenContainer; private readonly RankedPlayChatDisplay chat; + private RankedPlayBottomOrnament ornament = null!; + private IDisposable? ornamentOverlayRegistration; + private IBindable stage = null!; private Sample? sampleStart; @@ -158,6 +165,12 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay { stage = matchInfo.Stage.GetBoundCopy(); sampleStart = audio.Samples.Get(@"SongSelect/confirm-selection"); + + LoadComponent(ornament = new RankedPlayBottomOrnament + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }); } protected override void LoadComplete() @@ -168,6 +181,8 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay client.UserStateChanged += onUserStateChanged; client.LoadRequested += onLoadRequested; + Scheduler.AddDelayed(() => ornament.Show(), VsSequence.INTRO_LENGTH); + int localUserId = api.LocalUser.Value.OnlineID; int opponentUserId = ((RankedPlayRoomState)client.Room!.MatchState!).Users.Keys.Single(it => it != localUserId); @@ -195,6 +210,8 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay }, ]); + ornamentOverlayRegistration = overlayManager.RegisterBlockingOverlay(ornament); + stage.BindValueChanged(e => onStageChanged(e.NewValue)); } @@ -356,6 +373,9 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay client.LeaveRoom().FireAndForget(); + ornamentOverlayRegistration?.Dispose(); + ornamentOverlayRegistration = null; + if (retryRequested) controller?.RejoinQueue(); @@ -406,6 +426,9 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay client.UserStateChanged -= onUserStateChanged; client.LoadRequested -= onLoadRequested; + ornamentOverlayRegistration?.Dispose(); + ornamentOverlayRegistration = null; + base.Dispose(isDisposing); } } diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlayBottomOrnament.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlayBottomOrnament.cs new file mode 100644 index 0000000000..6eaa4ae6ff --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlayBottomOrnament.cs @@ -0,0 +1,178 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Lines; +using osu.Framework.Layout; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Localisation; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Screens.OnlinePlay.Matchmaking +{ + /// + /// A small component intended to be always present at the bottom of all ranked play screens + /// to indicate a ranked play session is in progress. + /// + public partial class RankedPlayBottomOrnament : OverlayContainer + { + private const int width = 400; + private const int height = 24; + + protected override bool BlockPositionalInput => false; + + private Path pathLeft = null!; + private Path pathRight = null!; + + private Path pathCenter = null!; + private Path pathCenterWide = null!; + + private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawSize); + + protected override bool StartHidden => true; + + [BackgroundDependencyLoader] + private void load() + { + Width = width; + Height = height; + Alpha = 0; + + Masking = true; + + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4Extensions.FromHex("#15061e").Opacity(0.8f), + Type = EdgeEffectType.Glow, + Radius = height * 2, + Roundness = height * 2, + Offset = new Vector2(0, height / 2f), + }; + + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + Height = 10, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Children = new Drawable[] + { + pathLeft = new SmoothPath + { + AutoSizeAxes = Axes.None, + RelativeSizeAxes = Axes.Both, + PathRadius = 1, + }, + pathCenter = new SmoothPath + { + AutoSizeAxes = Axes.None, + RelativeSizeAxes = Axes.Both, + PathRadius = 1, + }, + pathCenterWide = new SmoothPath + { + AutoSizeAxes = Axes.None, + RelativeSizeAxes = Axes.Both, + PathRadius = 2, + }, + pathRight = new SmoothPath + { + AutoSizeAxes = Axes.None, + RelativeSizeAxes = Axes.Both, + PathRadius = 1, + }, + }, + }, + new OsuSpriteText + { + Y = 4, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Font = OsuFont.Torus.With(size: 10, weight: FontWeight.Bold), + Spacing = new Vector2(3, 0), + Text = ButtonSystemStrings.RankedPlay.ToUpper(), + }, + }; + } + + private void recomputePaths() + { + const int top = 2; // to account for the middle segment being twice as wide + const int bottom = 10; + const int curve_smoothness = 5; + + pathCenter.AddVertex(new Vector2(30, top)); + pathCenter.AddVertex(new Vector2(DrawWidth - 30, top)); + + pathCenterWide.AddVertex(new Vector2(60, top)); + pathCenterWide.AddVertex(new Vector2(DrawWidth - 60, top)); + + const int left_start = 0; + const int left_corner = 10; + const int left_end = 20; + + List vertices = new List(); + var diagonalDirLeft = (new Vector2(left_start, bottom) - new Vector2(left_corner, top)).Normalized(); + + var sliderPathLeft = new SliderPath(new[] + { + new PathControlPoint(new Vector2(left_start, bottom), PathType.LINEAR), + new PathControlPoint(new Vector2(left_corner, top) + diagonalDirLeft * curve_smoothness, PathType.BEZIER), + new PathControlPoint(new Vector2(left_corner, top)), + new PathControlPoint(new Vector2(left_end, top), PathType.LINEAR), + }); + + sliderPathLeft.GetPathToProgress(vertices, 0.0, 1.0); + pathLeft.Vertices = vertices; + + float rightStart = DrawWidth; + float rightCorner = rightStart - 10; + float rightEnd = rightStart - 20; + + var diagonalDirRight = (new Vector2(rightStart, bottom) - new Vector2(rightCorner, top)).Normalized(); + var sliderPathRight = new SliderPath(new[] + { + new PathControlPoint(new Vector2(rightStart, bottom), PathType.LINEAR), + new PathControlPoint(new Vector2(rightCorner, top) + diagonalDirRight * curve_smoothness, PathType.BEZIER), + new PathControlPoint(new Vector2(rightCorner, top)), + new PathControlPoint(new Vector2(rightEnd, top), PathType.LINEAR), + }); + + sliderPathRight.GetPathToProgress(vertices, 0.0, 1.0); + pathRight.Vertices = vertices; + } + + protected override void Update() + { + base.Update(); + + if (!layout.IsValid) + { + recomputePaths(); + layout.Validate(); + } + } + + protected override void PopIn() + { + this.FadeIn(500, Easing.OutQuint); + // TODO: animate this better. + } + + protected override void PopOut() + { + this.FadeOut(500, Easing.OutQuint); + } + } +}