10 Commits

Author SHA1 Message Date
d76d4d9a35 okay, i messed things up 2025-12-09 20:20:07 +03:00
Bartłomiej Dach
bb7417c099 Filter out more exceptions from being sent to sentry
More or less covers the first page of client sentry issues sorted by
volume, all of which is pretty much useless for anything because it's
client-specific-failure noise.
2025-12-09 20:19:16 +03:00
Dan Balasescu
066e093987 Adjust vote-to-skip to be explicit about states 2025-12-09 20:19:16 +03:00
Dan Balasescu
56e0c3e65d Fix potentially unsafe quick play event handling 2025-12-09 20:19:16 +03:00
Natelytle
89d7726903 Rank swap mod 2025-12-09 20:19:16 +03:00
Dan Balasescu
36f1bfef07 Fix incorrect quick play download progress 2025-12-09 20:19:15 +03:00
Dan Balasescu
118f07878a Allow score panel to animate 2025-12-09 20:19:15 +03:00
Dan Balasescu
e144968893 Remove quick play round results scroll animation 2025-12-09 20:19:15 +03:00
Dan Balasescu
d2ffea41c6 Consider abandon time for user placements 2025-12-09 20:19:15 +03:00
Dan Balasescu
a8be9b1381 Make quick play chat retain focus after posting 2025-12-09 20:19:15 +03:00
19 changed files with 171 additions and 110 deletions

View File

@@ -10,7 +10,7 @@
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk> <EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.1205.1" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2025.1209.0" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<!-- Fody does not handle Android build well, and warns when unchanged. <!-- Fody does not handle Android build well, and warns when unchanged.

View File

@@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
public override ModType Type => ModType.Conversion; public override ModType Type => ModType.Conversion;
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModRandom)).ToArray(); public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModRandom)).ToArray();
public override bool Ranked => true;
public void ApplyToBeatmap(IBeatmap beatmap) public void ApplyToBeatmap(IBeatmap beatmap)
{ {

View File

@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking; using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
@@ -149,5 +150,33 @@ namespace osu.Game.Tests.Online.Matchmaking
Assert.AreEqual(5, state.Users.GetOrAdd(5).Placement); Assert.AreEqual(5, state.Users.GetOrAdd(5).Placement);
Assert.AreEqual(6, state.Users.GetOrAdd(6).Placement); Assert.AreEqual(6, state.Users.GetOrAdd(6).Placement);
} }
[Test]
public void AbandonOrder()
{
var state = new MatchmakingRoomState();
state.AdvanceRound();
state.RecordScores(
[
new SoloScoreInfo { UserID = 1, TotalScore = 1000 },
new SoloScoreInfo { UserID = 2, TotalScore = 500 },
], placement_points);
Assert.AreEqual(1, state.Users.GetOrAdd(1).Placement);
Assert.AreEqual(2, state.Users.GetOrAdd(2).Placement);
state.Users.GetOrAdd(1).AbandonedAt = DateTimeOffset.Now;
state.RecordScores([], placement_points);
Assert.AreEqual(2, state.Users.GetOrAdd(1).Placement);
Assert.AreEqual(1, state.Users.GetOrAdd(2).Placement);
state.Users.GetOrAdd(2).AbandonedAt = DateTimeOffset.Now - TimeSpan.FromMinutes(1);
state.RecordScores([], placement_points);
Assert.AreEqual(1, state.Users.GetOrAdd(1).Placement);
Assert.AreEqual(2, state.Users.GetOrAdd(2).Placement);
}
} }
} }

View File

@@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Utils; using osu.Framework.Utils;
@@ -26,8 +27,15 @@ namespace osu.Game.Tests.Visual.Matchmaking
AddStep("join room", () => JoinRoom(CreateDefaultRoom(MatchType.Matchmaking))); AddStep("join room", () => JoinRoom(CreateDefaultRoom(MatchType.Matchmaking)));
WaitForJoined(); WaitForJoined();
}
setupRequestHandler(); [TestCase(2)]
[TestCase(4)]
[TestCase(8)]
[TestCase(16)]
public void TestDisplayScores(int scoreCount)
{
setupRequestHandler(scoreCount);
AddStep("load screen", () => AddStep("load screen", () =>
{ {
@@ -40,7 +48,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
}); });
} }
private void setupRequestHandler() private void setupRequestHandler(int scoreCount)
{ {
AddStep("setup request handler", () => AddStep("setup request handler", () =>
{ {
@@ -71,7 +79,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
case IndexPlaylistScoresRequest index: case IndexPlaylistScoresRequest index:
var result = new IndexedMultiplayerScores(); var result = new IndexedMultiplayerScores();
for (int i = 0; i < 8; ++i) for (int i = 0; i < scoreCount; ++i)
{ {
result.Scores.Add(new MultiplayerScore result.Scores.Add(new MultiplayerScore
{ {

View File

@@ -603,7 +603,7 @@ namespace osu.Game.Online.API
cancellationToken.Cancel(); cancellationToken.Cancel();
} }
private class WebRequestFlushedException : Exception internal class WebRequestFlushedException : Exception
{ {
public WebRequestFlushedException(APIState state) public WebRequestFlushedException(APIState state)
: base($@"Request failed from flush operation (state {state})") : base($@"Request failed from flush operation (state {state})")

View File

@@ -153,7 +153,7 @@ namespace osu.Game.Online.Multiplayer
/// <summary> /// <summary>
/// Signals that a user has requested to skip the beatmap intro. /// Signals that a user has requested to skip the beatmap intro.
/// </summary> /// </summary>
Task UserVotedToSkipIntro(int userId); Task UserVotedToSkipIntro(int userId, bool voted);
/// <summary> /// <summary>
/// Signals that the vote to skip the beatmap intro has passed. /// Signals that the vote to skip the beatmap intro has passed.

View File

@@ -36,5 +36,11 @@ namespace osu.Game.Online.Multiplayer.MatchTypes.Matchmaking
/// </summary> /// </summary>
[Key(3)] [Key(3)]
public MatchmakingRoundList Rounds { get; set; } = new MatchmakingRoundList(); public MatchmakingRoundList Rounds { get; set; } = new MatchmakingRoundList();
/// <summary>
/// The time at which this user abandoned the match.
/// </summary>
[Key(4)]
public DateTimeOffset? AbandonedAt { get; set; }
} }
} }

View File

@@ -23,42 +23,53 @@ namespace osu.Game.Online.Multiplayer.MatchTypes.Matchmaking
ArgumentNullException.ThrowIfNull(x); ArgumentNullException.ThrowIfNull(x);
ArgumentNullException.ThrowIfNull(y); ArgumentNullException.ThrowIfNull(y);
// X appears earlier in the list if it has more points. int compare = compareAbandonedAt(x, y);
if (x.Points > y.Points) if (compare != 0)
return -1; return compare;
// Y appears earlier in the list if it has more points. compare = comparePoints(x, y);
if (y.Points > x.Points) if (compare != 0)
return 1; return compare;
// Tiebreaker 1 (likely): From each user's point-of-view, their earliest and best placement. compare = compareRoundPlacements(x, y);
if (compare != 0)
return compare;
return compareUserIds(x, y);
}
private int compareAbandonedAt(MatchmakingUser x, MatchmakingUser y)
{
DateTimeOffset xAbandonedAt = x.AbandonedAt ?? DateTimeOffset.MaxValue;
DateTimeOffset yAbandonedAt = y.AbandonedAt ?? DateTimeOffset.MaxValue;
return -xAbandonedAt.CompareTo(yAbandonedAt);
}
private int comparePoints(MatchmakingUser x, MatchmakingUser y)
{
return -x.Points.CompareTo(y.Points);
}
private int compareRoundPlacements(MatchmakingUser x, MatchmakingUser y)
{
for (int r = 1; r <= rounds; r++) for (int r = 1; r <= rounds; r++)
{ {
MatchmakingRound? xRound; x.Rounds.RoundsDictionary.TryGetValue(r, out var xRound);
x.Rounds.RoundsDictionary.TryGetValue(r, out xRound); y.Rounds.RoundsDictionary.TryGetValue(r, out var yRound);
MatchmakingRound? yRound; int xPlacement = xRound?.Placement ?? int.MaxValue;
y.Rounds.RoundsDictionary.TryGetValue(r, out yRound); int yPlacement = yRound?.Placement ?? int.MaxValue;
// Nothing to do if both players haven't played this round. int compare = xPlacement.CompareTo(yPlacement);
if (xRound == null && yRound == null)
continue;
// X appears later in the list if it hasn't played this round.
if (xRound == null)
return 1;
// Y appears later in the list if it hasn't played this round.
if (yRound == null)
return -1;
// X appears earlier in the list if it has a better placement in the round.
int compare = xRound.Placement.CompareTo(yRound.Placement);
if (compare != 0) if (compare != 0)
return compare; return compare;
} }
// Tiebreaker 2 (unlikely): User ID. return 0;
}
private int compareUserIds(MatchmakingUser x, MatchmakingUser y)
{
return x.UserId.CompareTo(y.UserId); return x.UserId.CompareTo(y.UserId);
} }
} }

View File

@@ -131,7 +131,7 @@ namespace osu.Game.Online.Multiplayer
public event Action<int, long>? MatchmakingItemDeselected; public event Action<int, long>? MatchmakingItemDeselected;
public event Action<MatchRoomState>? MatchRoomStateChanged; public event Action<MatchRoomState>? MatchRoomStateChanged;
public event Action<int>? UserVotedToSkipIntro; public event Action<int, bool>? UserVotedToSkipIntro;
public event Action? VoteToSkipIntroPassed; public event Action? VoteToSkipIntroPassed;
public event Action<MultiplayerRoomUser, BeatmapAvailability>? BeatmapAvailabilityChanged; public event Action<MultiplayerRoomUser, BeatmapAvailability>? BeatmapAvailabilityChanged;
@@ -854,10 +854,6 @@ namespace osu.Game.Online.Multiplayer
handleRoomRequest(() => handleRoomRequest(() =>
{ {
Debug.Assert(Room != null); Debug.Assert(Room != null);
foreach (var user in Room.Users)
user.VotedToSkipIntro = false;
GameplayStarted?.Invoke(); GameplayStarted?.Invoke();
}); });
@@ -928,7 +924,7 @@ namespace osu.Game.Online.Multiplayer
return Task.CompletedTask; return Task.CompletedTask;
} }
Task IMultiplayerClient.UserVotedToSkipIntro(int userId) Task IMultiplayerClient.UserVotedToSkipIntro(int userId, bool voted)
{ {
handleRoomRequest(() => handleRoomRequest(() =>
{ {
@@ -940,9 +936,8 @@ namespace osu.Game.Online.Multiplayer
if (user == null) if (user == null)
return; return;
user.VotedToSkipIntro = true; user.VotedToSkipIntro = voted;
UserVotedToSkipIntro?.Invoke(userId, voted);
UserVotedToSkipIntro?.Invoke(userId);
}); });
return Task.CompletedTask; return Task.CompletedTask;
@@ -1117,7 +1112,7 @@ namespace osu.Game.Online.Multiplayer
Task IMatchmakingClient.MatchmakingItemSelected(int userId, long playlistItemId) Task IMatchmakingClient.MatchmakingItemSelected(int userId, long playlistItemId)
{ {
Scheduler.Add(() => handleRoomRequest(() =>
{ {
MatchmakingItemSelected?.Invoke(userId, playlistItemId); MatchmakingItemSelected?.Invoke(userId, playlistItemId);
RoomUpdated?.Invoke(); RoomUpdated?.Invoke();
@@ -1128,7 +1123,7 @@ namespace osu.Game.Online.Multiplayer
Task IMatchmakingClient.MatchmakingItemDeselected(int userId, long playlistItemId) Task IMatchmakingClient.MatchmakingItemDeselected(int userId, long playlistItemId)
{ {
Scheduler.Add(() => handleRoomRequest(() =>
{ {
MatchmakingItemDeselected?.Invoke(userId, playlistItemId); MatchmakingItemDeselected?.Invoke(userId, playlistItemId);
RoomUpdated?.Invoke(); RoomUpdated?.Invoke();

View File

@@ -70,7 +70,7 @@ namespace osu.Game.Online.Multiplayer
connection.On<MultiplayerPlaylistItem>(nameof(IMultiplayerClient.PlaylistItemAdded), ((IMultiplayerClient)this).PlaylistItemAdded); connection.On<MultiplayerPlaylistItem>(nameof(IMultiplayerClient.PlaylistItemAdded), ((IMultiplayerClient)this).PlaylistItemAdded);
connection.On<long>(nameof(IMultiplayerClient.PlaylistItemRemoved), ((IMultiplayerClient)this).PlaylistItemRemoved); connection.On<long>(nameof(IMultiplayerClient.PlaylistItemRemoved), ((IMultiplayerClient)this).PlaylistItemRemoved);
connection.On<MultiplayerPlaylistItem>(nameof(IMultiplayerClient.PlaylistItemChanged), ((IMultiplayerClient)this).PlaylistItemChanged); connection.On<MultiplayerPlaylistItem>(nameof(IMultiplayerClient.PlaylistItemChanged), ((IMultiplayerClient)this).PlaylistItemChanged);
connection.On<int>(nameof(IMultiplayerClient.UserVotedToSkipIntro), ((IMultiplayerClient)this).UserVotedToSkipIntro); connection.On<int, bool>(nameof(IMultiplayerClient.UserVotedToSkipIntro), ((IMultiplayerClient)this).UserVotedToSkipIntro);
connection.On(nameof(IMultiplayerClient.VoteToSkipIntroPassed), ((IMultiplayerClient)this).VoteToSkipIntroPassed); connection.On(nameof(IMultiplayerClient.VoteToSkipIntroPassed), ((IMultiplayerClient)this).VoteToSkipIntroPassed);
connection.On(nameof(IMatchmakingClient.MatchmakingQueueJoined), ((IMatchmakingClient)this).MatchmakingQueueJoined); connection.On(nameof(IMatchmakingClient.MatchmakingQueueJoined), ((IMatchmakingClient)this).MatchmakingQueueJoined);

View File

@@ -29,7 +29,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
resetPlaceholderText(); resetPlaceholderText();
TextBox.HoldFocus = false; TextBox.HoldFocus = false;
TextBox.ReleaseFocusOnCommit = true; TextBox.ReleaseFocusOnCommit = false;
TextBox.Focus = () => TextBox.PlaceholderText = ChatStrings.InputPlaceholder; TextBox.Focus = () => TextBox.PlaceholderText = ChatStrings.InputPlaceholder;
TextBox.FocusLost = resetPlaceholderText; TextBox.FocusLost = resetPlaceholderText;

View File

@@ -520,6 +520,9 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match
private void onBeatmapAvailabilityChanged(MultiplayerRoomUser user, BeatmapAvailability availability) => Scheduler.Add(() => private void onBeatmapAvailabilityChanged(MultiplayerRoomUser user, BeatmapAvailability availability) => Scheduler.Add(() =>
{ {
if (!user.Equals(RoomUser))
return;
if (availability.State == DownloadState.Downloading) if (availability.State == DownloadState.Downloading)
downloadProgressBar.FadeIn(200, Easing.OutPow10); downloadProgressBar.FadeIn(200, Easing.OutPow10);
else else

View File

@@ -23,6 +23,7 @@ using osu.Game.Online.Rooms;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking;
using osuTK;
namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.RoundResults namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.RoundResults
{ {
@@ -31,8 +32,6 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.RoundResults
/// </summary> /// </summary>
public partial class SubScreenRoundResults : MatchmakingSubScreen public partial class SubScreenRoundResults : MatchmakingSubScreen
{ {
private const int panel_spacing = 5;
public override PanelDisplayStyle PlayersDisplayStyle => PanelDisplayStyle.Hidden; public override PanelDisplayStyle PlayersDisplayStyle => PanelDisplayStyle.Hidden;
public override Drawable? PlayersDisplayArea => null; public override Drawable? PlayersDisplayArea => null;
@@ -51,7 +50,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.RoundResults
[Resolved] [Resolved]
private RulesetStore rulesets { get; set; } = null!; private RulesetStore rulesets { get; set; } = null!;
private AutoScrollContainer scrollContainer = null!; private PanelContainer panelContainer = null!;
private LoadingSpinner loadingSpinner = null!; private LoadingSpinner loadingSpinner = null!;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@@ -59,7 +58,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.RoundResults
{ {
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
scrollContainer = new AutoScrollContainer panelContainer = new PanelContainer
{ {
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both
}, },
@@ -136,78 +135,57 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.Match.RoundResults
private void setScores(ScoreInfo[] scores) => Scheduler.Add(() => private void setScores(ScoreInfo[] scores) => Scheduler.Add(() =>
{ {
Container panels; panelContainer.ChildrenEnumerable = scores.Select(s => new RoundResultsScorePanel(s)
scrollContainer.Child = panels = new Container
{ {
RelativeSizeAxes = Axes.Y, Anchor = Anchor.Centre,
Width = scores.Length * (ScorePanel.CONTRACTED_WIDTH + panel_spacing), Origin = Anchor.Centre
ChildrenEnumerable = scores.Select(s => new RoundResultsScorePanel(s) });
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
})
};
for (int i = 0; i < panels.Count; i++)
{
panels[i].MoveToX(panels.DrawWidth * 2)
.Delay(i * 100)
.MoveToX((ScorePanel.CONTRACTED_WIDTH + panel_spacing) * i, 500, Easing.OutQuint);
}
}); });
private partial class RoundResultsScorePanel : CompositeDrawable private partial class RoundResultsScorePanel : CompositeDrawable
{ {
public RoundResultsScorePanel(ScoreInfo score) public RoundResultsScorePanel(ScoreInfo score)
{ {
AutoSizeAxes = Axes.Both; Size = new Vector2(ScorePanel.CONTRACTED_WIDTH, ScorePanel.CONTRACTED_HEIGHT);
InternalChild = new InstantSizingScorePanel(score);
InternalChild = new ScorePanel(score);
} }
public override bool PropagateNonPositionalInputSubTree => false; public override bool PropagateNonPositionalInputSubTree => false;
public override bool PropagatePositionalInputSubTree => false; public override bool PropagatePositionalInputSubTree => false;
private partial class InstantSizingScorePanel : ScorePanel
{
public InstantSizingScorePanel(ScoreInfo score, bool isNewLocalScore = false)
: base(score, isNewLocalScore)
{
}
protected override void LoadComplete()
{
base.LoadComplete();
FinishTransforms(true);
}
}
} }
private partial class AutoScrollContainer : UserTrackingScrollContainer private partial class PanelContainer : Container<RoundResultsScorePanel>
{ {
private const float initial_offset = -0.5f; protected override Container<RoundResultsScorePanel> Content => flowContainer;
private const double scroll_duration = 20000;
private double? scrollStartTime; private readonly Container centreingContainer;
private readonly Container<RoundResultsScorePanel> flowContainer;
public AutoScrollContainer() public PanelContainer()
: base(Direction.Horizontal)
{ {
InternalChild = new OsuScrollContainer(Direction.Horizontal)
{
RelativeSizeAxes = Axes.Both,
Child = centreingContainer = new Container
{
RelativeSizeAxes = Axes.Y,
Child = flowContainer = new FillFlowContainer<RoundResultsScorePanel>
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Spacing = new Vector2(5)
}
}
};
} }
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
centreingContainer.Width = Math.Max(DrawWidth, flowContainer.DrawWidth);
if (!UserScrolling && Children.Count > 0)
{
scrollStartTime ??= Time.Current;
double scrollOffset = (Time.Current - scrollStartTime.Value) / scroll_duration;
if (scrollOffset < 1)
ScrollTo(DrawWidth * (initial_offset + scrollOffset), false);
}
} }
} }
} }

View File

@@ -68,7 +68,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
private void onUserStateChanged(MultiplayerRoomUser user, MultiplayerUserState state) => Schedule(updateCount); private void onUserStateChanged(MultiplayerRoomUser user, MultiplayerUserState state) => Schedule(updateCount);
private void onUserVotedToSkipIntro(int userId) => Schedule(() => private void onUserVotedToSkipIntro(int userId, bool voted) => Schedule(() =>
{ {
FadingContent.TriggerShow(); FadingContent.TriggerShow();
updateCount(); updateCount();

View File

@@ -37,7 +37,7 @@ namespace osu.Game.Screens.Ranking
/// <summary> /// <summary>
/// Height of the panel when contracted. /// Height of the panel when contracted.
/// </summary> /// </summary>
private const float contracted_height = 385; public const float CONTRACTED_HEIGHT = 385;
/// <summary> /// <summary>
/// Width of the panel when expanded. /// Width of the panel when expanded.
@@ -280,7 +280,7 @@ namespace osu.Game.Screens.Ranking
break; break;
case PanelState.Contracted: case PanelState.Contracted:
Size = new Vector2(CONTRACTED_WIDTH, contracted_height); Size = new Vector2(CONTRACTED_WIDTH, CONTRACTED_HEIGHT);
topLayerBackground.FadeColour(getColour(contracted_top_layer_colour), RESIZE_DURATION, Easing.OutQuint); topLayerBackground.FadeColour(getColour(contracted_top_layer_colour), RESIZE_DURATION, Easing.OutQuint);
middleLayerBackground.FadeColour(getColour(contracted_middle_layer_colour), RESIZE_DURATION, Easing.OutQuint); middleLayerBackground.FadeColour(getColour(contracted_middle_layer_colour), RESIZE_DURATION, Easing.OutQuint);

View File

@@ -568,7 +568,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public async Task UserVoteToSkipIntro(int userId) public async Task UserVoteToSkipIntro(int userId)
{ {
await ((IMultiplayerClient)this).UserVotedToSkipIntro(userId).ConfigureAwait(false); await ((IMultiplayerClient)this).UserVotedToSkipIntro(userId, true).ConfigureAwait(false);
} }
protected override Task<MultiplayerRoom> CreateRoomInternal(MultiplayerRoom room) protected override Task<MultiplayerRoom> CreateRoomInternal(MultiplayerRoom room)

View File

@@ -2,10 +2,14 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets;
using System.Net.WebSockets;
using System.Threading.Tasks;
using osu.Framework; using osu.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@@ -17,6 +21,7 @@ using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Models; using osu.Game.Models;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@@ -219,20 +224,35 @@ namespace osu.Game.Utils
} }
} }
private static readonly HashSet<int> ignored_io_exception_hresults =
[
// see https://stackoverflow.com/a/9294382 for how these are synthesised
unchecked((int)0x80070020), // ERROR_SHARING_VIOLATION
unchecked((int)0x80070027), // ERROR_HANDLE_DISK_FULL
unchecked((int)0x80070070), // ERROR_DISK_FULL
];
private bool shouldSubmitException(Exception exception) private bool shouldSubmitException(Exception exception)
{ {
switch (exception) switch (exception)
{ {
case IOException ioe: // disk I/O failures, invalid formats, etc.
// disk full exceptions, see https://stackoverflow.com/a/9294382
const int hr_error_handle_disk_full = unchecked((int)0x80070027);
const int hr_error_disk_full = unchecked((int)0x80070070);
if (ioe.HResult == hr_error_handle_disk_full || ioe.HResult == hr_error_disk_full) case IOException ioe:
if (ignored_io_exception_hresults.Contains(ioe.HResult))
return false; return false;
break; break;
case UnauthorizedAccessException:
case SharpCompress.Common.InvalidFormatException:
return false;
// connectivity failures
case TimeoutException te:
return !te.Message.Contains(@"elapsed without receiving a message from the server");
case WebException we: case WebException we:
switch (we.Status) switch (we.Status)
{ {
@@ -242,6 +262,16 @@ namespace osu.Game.Utils
} }
break; break;
case WebSocketException:
case SocketException:
return false;
// stuff that should really never make it to sentry
case APIAccess.WebRequestFlushedException:
case TaskCanceledException:
return false;
} }
return true; return true;

View File

@@ -35,7 +35,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Realm" Version="20.1.0" /> <PackageReference Include="Realm" Version="20.1.0" />
<PackageReference Include="ppy.osu.Framework" Version="2025.1205.1" /> <PackageReference Include="ppy.osu.Framework" Version="2025.1209.0" />
<PackageReference Include="jvnkosu.Resources" Version="2025.1119.0" /> <PackageReference Include="jvnkosu.Resources" Version="2025.1119.0" />
<PackageReference Include="Sentry" Version="5.1.1" /> <PackageReference Include="Sentry" Version="5.1.1" />
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. --> <!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->

View File

@@ -17,6 +17,6 @@
<MtouchInterpreter>-all</MtouchInterpreter> <MtouchInterpreter>-all</MtouchInterpreter>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Framework.iOS" Version="2025.1205.1" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2025.1209.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>