Skip to content

Commit 1164870

Browse files
authored
Add messaging to player loader screen and user profiles when PP processing is ongoing (#38272)
<img width="2298" height="656" alt="2026-07-11 01 17 52@2x" src="https://github.com/user-attachments/assets/7538bbb3-0d58-4974-ba21-7311bd5e75a1" /> <img width="846" height="356" alt="2026-07-11 01 18 33@2x" src="https://github.com/user-attachments/assets/7092c70b-1899-43ae-97d8-a728308366f5" />
1 parent 134c982 commit 1164870

9 files changed

Lines changed: 160 additions & 3 deletions

File tree

osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ public partial class TestSceneUserProfileHeader : OsuTestScene
4040
[SetUpSteps]
4141
public void SetUpSteps()
4242
{
43+
AddStep("set processing message", () =>
44+
{
45+
dummyAPI.ScoreProcessingNoticeUrl = "https://osu.ppy.sh/home/news/2026-07-03-performance-points-star-rating-updates";
46+
});
47+
4348
AddStep("create header", () =>
4449
{
4550
Child = new OsuScrollContainer(Direction.Vertical)

osu.Game/Online/API/APIAccess.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ public partial class APIAccess : CompositeComponent, IAPIProvider
5353

5454
public string ProvidedUsername { get; private set; }
5555

56+
public string ScoreProcessingNoticeUrl { get; private set; }
57+
5658
public SessionVerificationMethod? SessionVerificationMethod { get; private set; }
5759

5860
public string SecondFactorCode { get; private set; }
@@ -434,6 +436,7 @@ private void attemptConnect()
434436
Debug.Assert(ThreadSafety.IsUpdateThread);
435437

436438
localUserState.SetLocalUser(me);
439+
ScoreProcessingNoticeUrl = me.ScoreProcessingNoticeUrl;
437440
SessionVerificationMethod = me.SessionVerificationMethod;
438441
state.Value = SessionVerificationMethod == null ? APIState.Online : APIState.RequiresSecondFactorAuth;
439442
livenessStopwatch.Restart();

osu.Game/Online/API/DummyAPIAccess.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ public partial class DummyAPIAccess : Component, IAPIProvider
2121
public const int DUMMY_USER_ID = 1001;
2222

2323
public DummyLocalUserState LocalUserState { get; } = new DummyLocalUserState();
24+
25+
public string ScoreProcessingNoticeUrl { get; set; } = string.Empty;
26+
2427
public Bindable<APIUser> LocalUser => LocalUserState.User;
2528

2629
ILocalUserState IAPIProvider.LocalUserState => LocalUserState;

osu.Game/Online/API/IAPIProvider.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ public interface IAPIProvider
2525
/// </summary>
2626
ILocalUserState LocalUserState { get; }
2727

28+
/// <summary>
29+
/// When there's ongoing SR/PP reprocessing, this will be non-empty and contain a URL leading to the news post
30+
/// giving user facing details about the ongoing deployment process.
31+
/// </summary>
32+
string ScoreProcessingNoticeUrl { get; }
33+
2834
/// <summary>
2935
/// The language supplied by this provider to API requests.
3036
/// </summary>

osu.Game/Online/API/Requests/Responses/APIMe.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ public class APIMe : APIUser
1111
{
1212
[JsonProperty("session_verification_method")]
1313
public SessionVerificationMethod? SessionVerificationMethod { get; set; }
14+
15+
/// <summary>
16+
/// Comes from osu-web envvar `SCORE_PROCESSING_NOTICE_URL`
17+
/// </summary>
18+
[JsonProperty("score_processing_notice_url")]
19+
public string ScoreProcessingNoticeUrl { get; set; } = string.Empty;
1420
}
1521

1622
public enum SessionVerificationMethod

osu.Game/Overlays/Profile/ProfileHeader.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ public ProfileHeader()
6464
RelativeSizeAxes = Axes.X,
6565
User = { BindTarget = User },
6666
},
67+
new ProfileProcessingNotice(),
6768
centreHeaderContainer = new CentreHeaderContainer
6869
{
6970
RelativeSizeAxes = Axes.X,
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2+
// See the LICENCE file in the repository root for full licence text.
3+
4+
using osu.Framework.Allocation;
5+
using osu.Framework.Graphics;
6+
using osu.Framework.Graphics.Containers;
7+
using osu.Framework.Graphics.Shapes;
8+
using osu.Framework.Graphics.Sprites;
9+
using osu.Framework.Localisation;
10+
using osu.Game.Graphics;
11+
using osu.Game.Graphics.Containers;
12+
using osu.Game.Online.API;
13+
using osu.Game.Online.Chat;
14+
using osu.Game.Resources.Localisation.Web;
15+
16+
namespace osu.Game.Overlays.Profile
17+
{
18+
public partial class ProfileProcessingNotice : CompositeDrawable
19+
{
20+
private ILocalisedBindableString noticeText = null!;
21+
22+
[BackgroundDependencyLoader]
23+
private void load(IAPIProvider? api, OverlayColourProvider colourProvider, OsuColour colours, LocalisationManager localisation)
24+
{
25+
if (string.IsNullOrEmpty(api?.ScoreProcessingNoticeUrl))
26+
return;
27+
28+
RelativeSizeAxes = Axes.X;
29+
AutoSizeAxes = Axes.Y;
30+
31+
LinkFlowContainer flow;
32+
33+
InternalChildren = new Drawable[]
34+
{
35+
new Box
36+
{
37+
RelativeSizeAxes = Axes.Both,
38+
Colour = colourProvider.Background5
39+
},
40+
new Container
41+
{
42+
RelativeSizeAxes = Axes.X,
43+
AutoSizeAxes = Axes.Y,
44+
Padding = new MarginPadding { Vertical = 10, Horizontal = 50 },
45+
Children = new Drawable[]
46+
{
47+
new Container
48+
{
49+
RelativeSizeAxes = Axes.X,
50+
AutoSizeAxes = Axes.Y,
51+
CornerRadius = 5,
52+
Masking = true,
53+
Children = new Drawable[]
54+
{
55+
new Box
56+
{
57+
Colour = colourProvider.Background4,
58+
RelativeSizeAxes = Axes.Both,
59+
},
60+
flow = new LinkFlowContainer(cp =>
61+
{
62+
cp.Colour = colours.Orange1;
63+
cp.Font = OsuFont.Style.Body;
64+
})
65+
{
66+
RelativeSizeAxes = Axes.X,
67+
AutoSizeAxes = Axes.Y,
68+
Padding = new MarginPadding(10),
69+
}
70+
}
71+
},
72+
}
73+
},
74+
};
75+
76+
var noticeLink = LocalisableString.Interpolate($@"[{UsersStrings.ShowScoreProcessingTitleLink}]({api.ScoreProcessingNoticeUrl})");
77+
var noticeString = LocalisableString.Interpolate($@"{UsersStrings.ShowScoreProcessingTitle(noticeLink)} {UsersStrings.ShowScoreProcessingMessage}");
78+
noticeText = localisation.GetLocalisedBindableString(noticeString);
79+
noticeText.BindValueChanged(t =>
80+
{
81+
flow.Clear();
82+
flow.AddIcon(FontAwesome.Solid.InfoCircle, cp => cp.Padding = new MarginPadding { Right = 5 });
83+
var result = MessageFormatter.FormatText(t.NewValue);
84+
flow.AddLinks(result.Text, result.Links);
85+
}, true);
86+
}
87+
}
88+
}

osu.Game/Screens/Play/PlayerLoader.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@
2727
using osu.Game.Graphics.Containers;
2828
using osu.Game.Input;
2929
using osu.Game.Localisation;
30+
using osu.Game.Online.API;
3031
using osu.Game.Online.Leaderboards;
3132
using osu.Game.Overlays;
3233
using osu.Game.Overlays.Notifications;
3334
using osu.Game.Overlays.Volume;
3435
using osu.Game.Performance;
36+
using osu.Game.Resources.Localisation.Web;
3537
using osu.Game.Screens.Footer;
3638
using osu.Game.Screens.Menu;
3739
using osu.Game.Screens.Play.HUD;
@@ -42,6 +44,7 @@
4244
using osu.Game.Utils;
4345
using osuTK;
4446
using osuTK.Graphics;
47+
using NotificationsStrings = osu.Game.Localisation.NotificationsStrings;
4548

4649
namespace osu.Game.Screens.Play
4750
{
@@ -196,7 +199,7 @@ public PlayerLoader(Func<Player> createPlayer)
196199
}
197200

198201
[BackgroundDependencyLoader]
199-
private void load(SessionStatics sessionStatics, OsuConfigManager config)
202+
private void load(SessionStatics sessionStatics, OsuConfigManager config, IAPIProvider api)
200203
{
201204
muteWarningShownOnce = sessionStatics.GetBindable<bool>(Static.MutedAudioNotificationShownOnce);
202205
batteryWarningShownOnce = sessionStatics.GetBindable<bool>(Static.LowBatteryNotificationShownOnce);
@@ -309,6 +312,18 @@ private void load(SessionStatics sessionStatics, OsuConfigManager config)
309312
disclaimers.Add(epilepsyWarning = new PlayerLoaderDisclaimer(PlayerLoaderStrings.EpilepsyWarningTitle, PlayerLoaderStrings.EpilepsyWarningContent));
310313
}
311314

315+
if (!string.IsNullOrEmpty(api.ScoreProcessingNoticeUrl))
316+
{
317+
disclaimers.Add(new PlayerLoaderDisclaimer(
318+
UsersStrings.ShowScoreProcessingTitle(UsersStrings.ShowScoreProcessingTitleLink),
319+
UsersStrings.ShowScoreProcessingMessage
320+
)
321+
{
322+
Action = () => (Game as OsuGame)?.OpenUrlExternally(api.ScoreProcessingNoticeUrl),
323+
IsImportant = true,
324+
});
325+
}
326+
312327
switch (Beatmap.Value.BeatmapInfo.Status)
313328
{
314329
case BeatmapOnlineStatus.Loved:

osu.Game/Screens/Play/PlayerLoaderDisclaimer.cs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
22
// See the LICENCE file in the repository root for full licence text.
33

4+
using System;
45
using osu.Framework.Allocation;
56
using osu.Framework.Graphics;
67
using osu.Framework.Graphics.Containers;
@@ -20,6 +21,10 @@ public partial class PlayerLoaderDisclaimer : CompositeDrawable
2021

2122
private Box background = null!;
2223

24+
public bool IsImportant { get; init; }
25+
26+
public Action? Action { get; set; }
27+
2328
public PlayerLoaderDisclaimer(LocalisableString title, LocalisableString content)
2429
{
2530
this.title = title;
@@ -34,12 +39,18 @@ private void load(OsuColour colours, OverlayColourProvider colourProvider)
3439
Masking = true;
3540
CornerRadius = 5;
3641

42+
if (IsImportant)
43+
{
44+
BorderColour = colours.Red2;
45+
BorderThickness = 1.5f;
46+
}
47+
3748
InternalChildren = new Drawable[]
3849
{
3950
background = new Box
4051
{
4152
RelativeSizeAxes = Axes.Both,
42-
Colour = colourProvider.Background4,
53+
Colour = IsImportant ? colours.Red3 : colourProvider.Background4,
4354
Alpha = 0.1f,
4455
},
4556
new Container
@@ -54,7 +65,7 @@ private void load(OsuColour colours, OverlayColourProvider colourProvider)
5465
Width = 7,
5566
Height = 15,
5667
Margin = new MarginPadding { Top = 2 },
57-
Colour = colours.Orange1,
68+
Colour = IsImportant ? colours.Red1 : colours.Orange1,
5869
},
5970
new FillFlowContainer
6071
{
@@ -84,6 +95,25 @@ private void load(OsuColour colours, OverlayColourProvider colourProvider)
8495
};
8596
}
8697

98+
protected override void LoadComplete()
99+
{
100+
base.LoadComplete();
101+
102+
if (IsImportant)
103+
{
104+
background.FadeTo(0.6f, 600, Easing.Out)
105+
.Then()
106+
.FadeTo(0.1f, 300, Easing.Out)
107+
.Loop();
108+
}
109+
}
110+
111+
protected override bool OnClick(ClickEvent e)
112+
{
113+
Action?.Invoke();
114+
return true;
115+
}
116+
87117
protected override bool OnHover(HoverEvent e)
88118
{
89119
updateFadeState();

0 commit comments

Comments
 (0)