diff --git a/osu.Game.Tests/Visual/RankedPlay/TestSceneRankedPlayUserDisplay.cs b/osu.Game.Tests/Visual/RankedPlay/TestSceneRankedPlayUserDisplay.cs
index f7cc9885c79a..de784157e9b4 100644
--- a/osu.Game.Tests/Visual/RankedPlay/TestSceneRankedPlayUserDisplay.cs
+++ b/osu.Game.Tests/Visual/RankedPlay/TestSceneRankedPlayUserDisplay.cs
@@ -4,6 +4,8 @@
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Utils;
+using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay;
using osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay.Components;
using osu.Game.Tests.Visual.Multiplayer;
@@ -20,11 +22,19 @@ public partial class TestSceneRankedPlayUserDisplay : MultiplayerTestScene
Value = 1_000_000,
};
+ public TestSceneRankedPlayUserDisplay()
+ {
+ AddSliderStep("health", 0, 1_000_000, 1_000_000, value => health.Value = value);
+ }
+
public override void SetUpSteps()
{
base.SetUpSteps();
- AddStep("add display", () => Child = new RankedPlayUserDisplay(2, Anchor.BottomLeft, RankedPlayColourScheme.Blue)
+ AddStep("join room", () => JoinRoom(CreateDefaultRoom(MatchType.RankedPlay)));
+ WaitForJoined();
+
+ AddStep("add display", () => Child = new RankedPlayUserDisplay(1001, Anchor.BottomLeft, RankedPlayColourScheme.Blue)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -36,7 +46,7 @@ public override void SetUpSteps()
[Test]
public void TesUserDisplay()
{
- AddStep("blue color scheme", () => Child = new RankedPlayUserDisplay(2, Anchor.BottomLeft, RankedPlayColourScheme.Blue)
+ AddStep("blue color scheme", () => Child = new RankedPlayUserDisplay(1001, Anchor.BottomLeft, RankedPlayColourScheme.Blue)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -44,15 +54,30 @@ public void TesUserDisplay()
Health = { BindTarget = health }
});
- AddStep("red color scheme", () => Child = new RankedPlayUserDisplay(2, Anchor.BottomLeft, RankedPlayColourScheme.Red)
+ AddStep("red color scheme", () => Child = new RankedPlayUserDisplay(1001, Anchor.BottomLeft, RankedPlayColourScheme.Red)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(256, 72),
Health = { BindTarget = health }
});
+ }
- AddSliderStep("health", 0, 1_000_000, 1_000_000, value => health.Value = value);
+ [Test]
+ public void TestBeatmapState()
+ {
+ float progress = 0;
+
+ AddStep("set unavailable", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.NotDownloaded()));
+ AddStep("set downloading", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(progress = 0)));
+ AddUntilStep("increment progress", () =>
+ {
+ progress += RNG.NextSingle(0.1f);
+ MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(progress));
+ return progress >= 1;
+ });
+ AddStep("set to importing", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.Importing()));
+ AddStep("set to available", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.LocallyAvailable()));
}
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/Components/RankedPlayUserDisplay.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/Components/RankedPlayUserDisplay.cs
index 0195c47945f4..aa5b303aad49 100644
--- a/osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/Components/RankedPlayUserDisplay.cs
+++ b/osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/Components/RankedPlayUserDisplay.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
@@ -17,7 +18,10 @@
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Sprites;
+using osu.Game.Online;
using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Online.Rooms;
using osu.Game.Users.Drawables;
using osuTK;
using osuTK.Graphics;
@@ -42,6 +46,13 @@ public partial class RankedPlayUserDisplay : CompositeDrawable
private BufferedContainer grayScaleContainer = null!;
+ private OsuSpriteText beatmapState = null!;
+
+ private BeatmapAvailability availability = BeatmapAvailability.Unknown();
+
+ [Resolved]
+ private MultiplayerClient client { get; set; } = null!;
+
[Resolved]
private RankedPlayCornerPiece? cornerPiece { get; set; }
@@ -61,6 +72,10 @@ private void load()
? -OsuGame.SHEAR
: OsuGame.SHEAR;
+ var beatmapStateAnchor = (contentAnchor & Anchor.x0) != 0
+ ? Anchor.CentreLeft
+ : Anchor.CentreRight;
+
InternalChildren =
[
new CircularContainer
@@ -103,15 +118,33 @@ private void load()
Anchor = contentAnchor,
Origin = contentAnchor,
},
- new OsuSpriteText
+ new FillFlowContainer
{
- Name = "Username",
- Text = user.Username,
+ Name = "Username/beatmap state container",
+ AutoSizeAxes = Axes.Both,
Anchor = contentAnchor,
Origin = contentAnchor,
+ Direction = FillDirection.Horizontal,
Padding = new MarginPadding { Horizontal = 4, Vertical = 6 },
- Font = OsuFont.GetFont(size: 24, weight: FontWeight.SemiBold),
- UseFullGlyphHeight = false,
+ Spacing = new Vector2(5, 0),
+ Children =
+ [
+ new OsuSpriteText
+ {
+ Name = "Username",
+ Text = user.Username,
+ Anchor = contentAnchor,
+ Origin = contentAnchor,
+ Font = OsuFont.GetFont(size: 24, weight: FontWeight.SemiBold),
+ UseFullGlyphHeight = false,
+ },
+ beatmapState = new OsuSpriteText
+ {
+ Anchor = beatmapStateAnchor,
+ Origin = beatmapStateAnchor,
+ Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold),
+ },
+ ],
},
]
}
@@ -129,6 +162,46 @@ protected override void LoadComplete()
grayScaleContainer.GrayscaleTo(e.NewValue <= 0 ? 1 : 0, 300);
cornerPiece?.OnHealthChanged(e.NewValue);
});
+
+ client.RoomUpdated += onRoomUpdated;
+ }
+
+ private void onRoomUpdated()
+ {
+ var user = client.Room?.Users.SingleOrDefault(u => u.UserID == userId);
+
+ if (user == null || availability == user.BeatmapAvailability)
+ return;
+
+ availability = user.BeatmapAvailability;
+
+ if (availability.State is DownloadState.NotDownloaded or DownloadState.Downloading or DownloadState.Importing)
+ beatmapState.FadeIn(50);
+ else
+ beatmapState.FadeOut(50);
+
+ switch (availability.State)
+ {
+ case DownloadState.NotDownloaded:
+ beatmapState.Text = "Missing Beatmap";
+ break;
+
+ case DownloadState.Downloading:
+ double progress = Math.Clamp(availability.DownloadProgress ?? 0, 0, 1);
+ beatmapState.Text = $"Downloading... ({progress:P0})";
+ break;
+
+ case DownloadState.Importing:
+ beatmapState.Text = "Importing...";
+ break;
+ }
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ client.RoomUpdated -= onRoomUpdated;
}
public partial class HealthBar : CompositeDrawable
diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/EndedScreen.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/EndedScreen.cs
index b76f8a7944fa..746492de712d 100644
--- a/osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/EndedScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/EndedScreen.cs
@@ -4,6 +4,8 @@
using System;
using System.Linq;
using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -36,8 +38,12 @@ public partial class EndedScreen : RankedPlaySubScreen
private OsuTextFlowContainer localRatingText = null!;
private OsuTextFlowContainer opponentRatingText = null!;
+ private Sample winSample = null!;
+ private Sample loseSample = null!;
+ private Sample drawSample = null!;
+
[BackgroundDependencyLoader]
- private void load(OsuColour colours)
+ private void load(OsuColour colours, AudioManager audio)
{
CenterColumn.Child = new FillFlowContainer
{
@@ -172,6 +178,10 @@ private void load(OsuColour colours)
}
};
+ winSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/win");
+ loseSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/lose");
+ drawSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/draw");
+
RankedPlayUserInfo localUser = matchInfo.RoomState.Users[Client.LocalUser!.UserID];
RankedPlayUserInfo otherUser = matchInfo.RoomState.Users.Values.Single(u => u != localUser);
@@ -179,16 +189,19 @@ private void load(OsuColour colours)
{
titleText.Text = "DRAW";
titleText.Colour = titleSeparator.Colour = colours.Orange1;
+ drawSample.Play();
}
else if (matchInfo.RoomState.WinningUserId == Client.LocalUser!.UserID)
{
titleText.Text = "VICTORY";
titleText.Colour = titleSeparator.Colour = colours.Green1;
+ winSample.Play();
}
else
{
titleText.Text = "DEFEAT";
titleText.Colour = titleSeparator.Colour = colours.Red1;
+ loseSample.Play();
}
localRatingText.AddText("Your Rating: ", s => s.Font = OsuFont.Style.Heading1.With(weight: FontWeight.Regular));
diff --git a/osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/ResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/ResultsScreen.cs
index 129ffc61dc7d..5f7298d5167f 100644
--- a/osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/ResultsScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/ResultsScreen.cs
@@ -6,6 +6,8 @@
using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
@@ -205,8 +207,24 @@ private partial class ResultScreenContent : CompositeDrawable
private RankedPlayDamageInfo losingDamageInfo = null!;
+ private Sample resultsAppearSample = null!;
+ private Sample dmgFlySample = null!;
+ private Sample dmgHitSample = null!;
+ private Sample hpDownSample = null!;
+ private Sample playerAppearSample = null!;
+ private Sample pseudoScoreCounterSample = null!;
+ private Sample scoreTickSample = null!;
+ private Sample gradePassSample = null!;
+ private Sample gradePassSsSample = null!;
+ private Sample gradeFailSample = null!;
+ private Sample gradeFailDSample = null!;
+ private SampleChannel? playerScoreTickChannel;
+ private SampleChannel? opponentScoreTickChannel;
+ private readonly BindableDouble playerScoreTickPitch = new BindableDouble();
+ private readonly BindableDouble opponentScoreTickPitch = new BindableDouble();
+
[BackgroundDependencyLoader]
- private void load()
+ private void load(AudioManager audio)
{
// this works under the assumption that only one player can receive damage each round
losingDamageInfo = matchInfo.RoomState.Users
@@ -417,6 +435,18 @@ private void load()
]
}
});
+
+ resultsAppearSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/Results/results-appear");
+ dmgFlySample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/Results/dmg-fly");
+ dmgHitSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/Results/dmg-hit");
+ hpDownSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/Results/hp-down");
+ playerAppearSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/Results/players-appear");
+ pseudoScoreCounterSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/Results/pseudo-score-counter");
+ scoreTickSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/Results/score-tick");
+ gradePassSample = audio.Samples.Get(@"Results/rank-impact-pass");
+ gradePassSsSample = audio.Samples.Get(@"Results/rank-impact-pass-ss");
+ gradeFailSample = audio.Samples.Get(@"Results/rank-impact-fail");
+ gradeFailDSample = audio.Samples.Get(@"Results/rank-impact-fail-d");
}
protected override void LoadComplete()
@@ -436,6 +466,8 @@ protected override void LoadComplete()
private void appear(ref double delay)
{
+ resultsAppearSample.Play();
+
panelScaffold.FadeIn(100)
.ResizeTo(0)
.ResizeTo(cardSize with { Y = 30 }, 600, Easing.OutExpo)
@@ -451,7 +483,11 @@ private void appear(ref double delay)
playerScoreCounter.FadeIn(600);
opponentScoreCounter.FadeIn(600);
- Schedule(() => cornerPieceVisibility.Value = Visibility.Visible);
+ Schedule(() =>
+ {
+ cornerPieceVisibility.Value = Visibility.Visible;
+ playerAppearSample.Play();
+ });
}
using (BeginDelayedSequence(900))
@@ -487,12 +523,57 @@ private void animateCountersAndScoreBars(ref double delay)
playerScoreBar.FadeIn(100);
opponentScoreBar.FadeIn(100);
+ playerScoreTickChannel ??= scoreTickSample.GetChannel();
+ playerScoreTickChannel.Balance.Value = -OsuGameBase.SFX_STEREO_STRENGTH;
+ playerScoreTickChannel.Frequency.BindTarget = playerScoreTickPitch;
+ playerScoreTickPitch.Value = 0.5f;
+ playerScoreTickChannel.Looping = true;
+
+ opponentScoreTickChannel ??= scoreTickSample.GetChannel();
+ opponentScoreTickChannel.Balance.Value = OsuGameBase.SFX_STEREO_STRENGTH;
+ opponentScoreTickChannel.Frequency.BindTarget = opponentScoreTickPitch;
+ opponentScoreTickPitch.Value = 0.5f;
+ opponentScoreTickChannel.Looping = true;
+
+ Schedule(() =>
+ {
+ if (losingDamageInfo.Damage > 0)
+ pseudoScoreCounterSample.Play();
+
+ if (PlayerScore.TotalScore > 0)
+ playerScoreTickChannel.Play();
+
+ if (OpponentScore.TotalScore > 0)
+ opponentScoreTickChannel.Play();
+ });
+
this.TransformBindableTo(scoreBarProgress, maxScorePercent, score_text_duration, new CubicBezierEasingFunction(easeIn: 0.4, easeOut: 1));
+ this.TransformBindableTo(playerScoreTickPitch, 0.5f + playerScorePercent, score_text_duration, Easing.OutCubic);
+ this.TransformBindableTo(opponentScoreTickPitch, 0.5f + opponentScorePercent, score_text_duration, Easing.OutCubic);
+
+ // safety timeout to ensure scoreTicks don't play forever
+ Scheduler.AddDelayed(() =>
+ {
+ if (playerScoreTickChannel != null)
+ playerScoreTickChannel.Looping = false;
+
+ if (opponentScoreTickChannel != null)
+ opponentScoreTickChannel.Looping = false;
+ }, score_text_duration + 500);
scoreBarProgress.BindValueChanged(e =>
{
playerScoreBar.Height = float.Lerp(0.05f, 1f, Math.Min(e.NewValue, playerScorePercent));
opponentScoreBar.Height = float.Lerp(0.05f, 1f, Math.Min(e.NewValue, opponentScorePercent));
+
+ Schedule(() =>
+ {
+ if (playerScoreTickChannel != null && playerScoreBar.Height >= playerScorePercent)
+ playerScoreTickChannel.Looping = false;
+
+ if (opponentScoreTickChannel != null && opponentScoreBar.Height >= opponentScorePercent)
+ opponentScoreTickChannel.Looping = false;
+ });
});
}
@@ -503,6 +584,9 @@ private void updateHealthBars(ref double delay)
{
const double text_movement_duration = 400;
+ bool playerTookDamage = OpponentScore.TotalScore > PlayerScore.TotalScore;
+ double loserPanDirection = playerTookDamage ? -OsuGameBase.SFX_STEREO_STRENGTH : OsuGameBase.SFX_STEREO_STRENGTH;
+
using (BeginDelayedSequence(delay))
{
Schedule(() =>
@@ -522,6 +606,10 @@ private void updateHealthBars(ref double delay)
.ScaleTo(0.9f)
.ScaleTo(1f, 300, Easing.OutElasticHalf);
+ var dmgFlyChannel = dmgFlySample.GetChannel();
+ this.TransformBindableTo(dmgFlyChannel.Balance, loserPanDirection, text_movement_duration, Easing.InCubic);
+ dmgFlyChannel.Play();
+
flyingDamageText.FadeIn()
.MoveTo(position, text_movement_duration, Easing.InCubic)
.ScaleTo(0.75f, text_movement_duration, new CubicBezierEasingFunction(easeIn: 0.35, easeOut: 0.5))
@@ -531,6 +619,10 @@ private void updateHealthBars(ref double delay)
Scheduler.AddDelayed(() =>
{
+ var dmgHitChannel = dmgHitSample.GetChannel();
+ dmgHitChannel.Balance.Value = loserPanDirection;
+ dmgHitChannel.Play();
+
userDisplay.Shake(shakeDuration: 60, shakeMagnitude: 2, maximumLength: 120);
for (int i = 0; i < 10; i++)
@@ -564,6 +656,13 @@ private void updateHealthBars(ref double delay)
{
playerUserDisplay.Health.Value = PlayerDamageInfo.NewLife;
opponentUserDisplay.Health.Value = OpponentDamageInfo.NewLife;
+
+ Scheduler.AddDelayed(() =>
+ {
+ var hpDecreaseChannel = hpDownSample.GetChannel();
+ hpDecreaseChannel.Balance.Value = loserPanDirection;
+ hpDecreaseChannel.Play();
+ }, 900);
});
}
@@ -576,11 +675,45 @@ private void showScoreInfo(ref double delay)
{
playerScoreDetails.FadeIn(300);
opponentScoreDetails.FadeIn(300);
+
+ Schedule(() =>
+ {
+ SampleChannel playerRankChannel = getRankSample(PlayerScore.Rank).GetChannel();
+ playerRankChannel.Balance.Value = -OsuGameBase.SFX_STEREO_STRENGTH;
+ playerRankChannel.Play();
+
+ SampleChannel opponentRankChannel = getRankSample(OpponentScore.Rank).GetChannel();
+ opponentRankChannel.Balance.Value = OsuGameBase.SFX_STEREO_STRENGTH;
+ opponentRankChannel.Play();
+ });
}
delay += 800;
}
+ private Sample getRankSample(ScoreRank rank)
+ {
+ switch (rank)
+ {
+ default:
+ case ScoreRank.D:
+ return gradeFailDSample;
+
+ case ScoreRank.C:
+ case ScoreRank.B:
+ return gradeFailSample;
+
+ case ScoreRank.A:
+ case ScoreRank.S:
+ case ScoreRank.SH:
+ return gradePassSample;
+
+ case ScoreRank.X:
+ case ScoreRank.XH:
+ return gradePassSsSample;
+ }
+ }
+
private static int numDigits(long value)
{
if (value <= 0)
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 0755aeca145e..4c680a5d4a57 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -40,7 +40,7 @@
-
+