As far as I can tell this matches stable expectations. As with most
things editor, it doesn't make sense to skin a beatmap and then want to
edit the beatmap without that skin applied, ever.
---
As mentioned in https://github.com/ppy/osu/discussions/37607.
Could probably be implemented in five different ways, this is just the
simplest that came to me. Well aware this is adding even more faff on
top of the config/disable/toggles for this stuff, but feels required for
editor sanity.
---------
Co-authored-by: Bartłomiej Dach <dach.bartlomiej@gmail.com>
Matches stable and significantly improves UX when mapping. In addition,
current behavior makes it too easy to place stacked objects which is
something we should not encourage.
---------
Co-authored-by: Bartłomiej Dach <dach.bartlomiej@gmail.com>
The "Key Count" metric in mania is very useless since you are already
expected to play maps with a specific Key Count when you are queueing.
This PR inserts the proportion of LNs (Long Notes) in the place of that
metric since it is one of the ways players can gudge their skillsets
(This idea comes from reddit)
Also improved the test suite for other skillsets by making the
architecture more minor ruleset friendly
Addresses https://github.com/ppy/osu/discussions/37568.
---------
Co-authored-by: Dan Balasescu <smoogipoo@smgi.me>
Co-authored-by: Dean Herbert <pe@ppy.sh>
Kao Li Chin (Gao Li Jin)
·
2026-05-08 18:04:29 +09:00
When editing metadata, it's annoying that the editor returns to compose
mode when switching between difficulties. This fixes that fallacy.
Supersedes and closes https://github.com/ppy/osu/pull/36724.
Can reproduce the failure via something like
diff --git a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs
index fe5c437e40..92c58a7bde 100644
--- a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs
@@ -187,13 +187,13 @@ private void updateKeyCount(ValueChangedEvent<int> keyCount)
{
if (!t.GetResultSafely())
{
- Schedule(() =>
+ Scheduler.AddDelayed(() =>
{
changeHandler!.RestoreState(-1);
Beatmap.Difficulty.CircleSize = keyCount.OldValue;
setStateFromActualKeyCount(keyCount.OldValue);
updatingKeyCount = false;
- });
+ }, 1000);
}
else
{
so I'm banking on this being just a case of CI fuzzing the test by being
slow.
If the test fails again on this same assert after this change there are
bigger problems at hand. But given the lack of local reproduction I'd
rather start with this than spend an hour staring at it.
Closes https://github.com/ppy/osu/issues/37553.
You can probably tell by the title that this is going to be a good one.
As previously mentioned in https://github.com/ppy/osu/pull/35395,
framework-side `TextBox` uses a bunch of `NumberFormat` properties from
`CurrentCulture` to contextually allow decimal points or minus signs in
a textbox.
In some languages, namely (of the ones we support): Finnish, Croatian,
Lithuanian, Norsk, Slovenian, and Swedish, `NumberFormat.NegativeSign`
is not `U+002D HYPHEN MINUS`, but instead `U+2212 MINUS SIGN`.
Therefore, in `FormSliderBar`, when `ToStandardFormattedString()` is
attempted to be used to set the textbox value, the hardcoded `U+002D
HYPHEN MINUS` is rejected on cultures that expect `U+2212 MINUS SIGN`,
and thus due to a feedback loop, all negative values are no longer
settable.
This applies the obvious fix of applying `NumberFormat.NegativeSign`.
Intends to close https://github.com/ppy/osu/issues/37395.
In the past there have been many discussions about this dual stages
wart. Whether it will continue to be "dual stages", or just a single
stage with 12/14/16/18K, whether all of the skinning weirdness related
to it (https://github.com/ppy/osu/issues/23620) will be supported, etc.
This isn't that, I don't want to get into that, and I'm not promising
anything. All I want to make sure here is that users cannot get their
data lost by editing an existing >10K beatmap, and be understandably
upset about it.
To that end this mostly apes how stable does things.
Until now the queue screen basically did nothing to let the user knowing
they were disconnected from the server. Now the various components will
correctly clear state and show a roughly competent "i'm trying to
reconnect" state.
https://github.com/user-attachments/assets/bff1b241-a6a2-445a-9ffa-b5682f2a3656
---
Can be tested using the following patch (hit `F7` to reconnect, with a 5
second delay to show the disconnected state too):
```diff
diff --git a/osu.Game/Online/PersistentEndpointClientConnector.cs b/osu.Game/Online/PersistentEndpointClientConnector.cs
index 7064906be4..ae539aba8d 100644
--- a/osu.Game/Online/PersistentEndpointClientConnector.cs
+++ b/osu.Game/Online/PersistentEndpointClientConnector.cs
@@ -99,6 +99,8 @@ private async Task connect()
// this will also create a new cancellation token source.
await disconnect(false).ConfigureAwait(false);
+ await Task.Delay(5000).ConfigureAwait(false);
+
// this token will be valid for the scope of this connection.
// if cancelled, we can be sure that a disconnect or reconnect is handled elsewhere.
var cancellationToken = connectCancelSource.Token;
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 703444a92f..fb467472d3 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -22,6 +22,7 @@
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
+using osu.Framework.Input.Events;
using osu.Framework.Input.Handlers;
using osu.Framework.Input.Handlers.Joystick;
using osu.Framework.Input.Handlers.Midi;
@@ -65,6 +66,7 @@
using osu.Game.Scoring;
using osu.Game.Skinning;
using osu.Game.Utils;
+using osuTK.Input;
using RuntimeInfo = osu.Framework.RuntimeInfo;
namespace osu.Game
@@ -104,7 +106,7 @@ public partial class OsuGameBase : Framework.Game, ICanAcceptFiles, IBeatSyncPro
/// </summary>
private const double global_track_volume_adjust = 0.8;
- public virtual bool UseDevelopmentServer => DebugUtils.IsDebugBuild;
+ public virtual bool UseDevelopmentServer => false;
public virtual EndpointConfiguration CreateEndpoints() =>
UseDevelopmentServer ? new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration();
@@ -466,6 +468,20 @@ private void addFilesWarning()
}
}
+ protected override bool OnKeyDown(KeyDownEvent e)
+ {
+ if (e.Key == Key.F7)
+ {
+ Logger.Log("Forcing reconnect!", level: LogLevel.Important);
+
+ ((IStatefulUserHubClient)MultiplayerClient).ServerShuttingDown();
+ ((IStatefulUserHubClient)SpectatorClient).ServerShuttingDown();
+ ((IStatefulUserHubClient)metadataClient).ServerShuttingDown();
+ }
+
+ return base.OnKeyDown(e);
+ }
+
private void onTrackChanged(WorkingBeatmap beatmap, TrackChangeDirection direction) => beatmapClock.ChangeSource(beatmap.Track);
protected virtual void InitialiseFonts()
```
Fixes a bug that allowed for selecting the same mod twice in multiplayer
if the playlist entry has non-default settings for a required mod.
This happens due to a strict equality mod check in the mod set
compatibility check function, which only considers mods duplicates if
their settings are exactly the same. Replacing with a more lenient
`Type` check fixes this.
Adds a regression test for this behavior
Fixes https://github.com/ppy/osu/issues/37625.
---------
Co-authored-by: Bartłomiej Dach <dach.bartlomiej@gmail.com>
This PR refactors the report popover to have an optional confirmation
message after completing the request. This was initially meant for
#32584, but then I realized I can first test it out on the user
profiles, so I've implemented it here as well. Can be reviewed commit by
commit.
https://github.com/user-attachments/assets/cd59c560-c824-4a5e-bab6-5ecbf5125af1
---------
Co-authored-by: Dean Herbert <pe@ppy.sh>
I've seen this suggested quite a bit and is a pretty easy implementation
all things considered.
For now, while on the queue screen, you can open up the dashboard
overlay and select another player to duel. This will bring you into an
unranked lobby.
https://github.com/user-attachments/assets/712897a9-9350-4741-899d-59662c722e43
Song preview keeps playing when clicking "Start Watching" on
`SoloSpectatorScreen`. Making `SoloSpectatorScreen` suspends the screen
rather than exiting it, so neither `clearDisplay()` nor `OnExiting()`
fire.
Closes: https://github.com/ppy/osu/issues/36987
- depends on https://github.com/ppy/osu-framework/pull/6737
Adds simple input settings section for pens that allows disabling the
handler and adjusting sensitivity. The section appears in-between Tablet
and Touch, and only on SDL3 (desktop and mobile). The pen sensitivity is
completely independent from mouse sensitivity.
<img width="537" height="149" alt="image"
src="https://github.com/user-attachments/assets/448eebba-84ea-4daf-8428-3bd07739bd6f"
/>
<br>
Keep in mind that the "Confine mouse cursor to window" mouse setting
also affects pens, feel free to suggest UX improvements. Also, toggling
"High precision mouse" might affect pens on certain configurations.
Edit: added image with updated header. Previously, it was "Device: Pen".
---------
Co-authored-by: Dean Herbert <pe@ppy.sh>
Based off of https://github.com/ppy/osu/pull/37478 with some
improvements such as:
* Simpler `progress` handling (single adjustable value instead of 2)
* 2 less drawable paths (replaced with circlular containers)
* Reworked remaining paths to have as little texture sizes as possible
---------
Co-authored-by: Krzysztof Gutkowski <krzysio.gutkowski@gmail.com>
Co-authored-by: Dean Herbert <pe@ppy.sh>
In ranked play, scores are always counted regardless of whether they are
a fail or a pass. Beyond that, there's also no concept of revival in
multiplayer right now, so players are just stuck at 0 HP with a red
overlay.
Not doing what `ModNoFail` does, which is to hide the healthbar
altogether, because some user skins use the healthbar to skin a gameplay
border.
Closes#37443
Previously it would select the first pool that matches the user's
current ruleset.
At first I tried passing the `MatchmakingPool` object to the
notification and have it passed back to the screen, but with that method
only clicking the notification would show the correct pool, if you enter
ranked play normally then it would show the "default" pool instead.
This method needed fewer changes too.
Based on internal feedback.
I was going to apply other changes (like always posting to sentry) but
don't want to go too far down a rabbit hole, so just fixed messaging a
bit.
Resolves#37486
Original error log:
```
2026-04-22 21:47:58 [error]: System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')
2026-04-22 21:47:58 [error]: at System.Collections.Generic.List`1.get_Item(Int32 index)
2026-04-22 21:47:58 [error]: at osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay.Hand.PlayerHandOfCards.moveCardFocus(Int32 direction)
2026-04-22 21:47:58 [error]: at osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay.Hand.PlayerHandOfCards.OnKeyDown(KeyDownEvent e)
```
Currently,
```osu.Game\Screens\OnlinePlay\Matchmaking\RankedPlay\Hand\PlayerHandOfCards.cs::moveCardFocus```
does not account for the hand being empty (cards.Count ==0 ), and will
attempt to move the card focus in the given direction regardless, which
causes the above index out of range error.
Added empty hand check to moveCardFocus, and a matching test case to
TestScenePlayerCardHand.cs
New test case before changes, recreating the error:
<img width="986" height="232" alt="image"
src="https://github.com/user-attachments/assets/daa62081-c776-44bd-b0d2-382b2dac7938"
/>
After:
<img width="638" height="223" alt="image"
src="https://github.com/user-attachments/assets/d6dcd8b8-8caf-42e3-9999-93dfe3fb6452"
/>
Thing to make release happen.
Reverts #37453
Reverts #37463
Alternative to #37473
Not that I disagree with any of these but I'm just looking to return to
what works so we can do a release because we're on a clock here for
other reasons.
Test which should work but doesn't, so I'm not adding:
```diff
diff --git a/osu.Game.Tests/Visual/RankedPlay/TestSceneOpponentPickScreen.cs b/osu.Game.Tests/Visual/RankedPlay/TestSceneOpponentPickScreen.cs
index f747004bbd..eb8e360d1e 100644
--- a/osu.Game.Tests/Visual/RankedPlay/TestSceneOpponentPickScreen.cs
+++ b/osu.Game.Tests/Visual/RankedPlay/TestSceneOpponentPickScreen.cs
@@ -1,12 +1,17 @@
// 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.
+using System.Linq;
+using NUnit.Framework;
using osu.Framework.Extensions;
+using osu.Framework.Testing;
using osu.Game.Online.API;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.MatchTypes.RankedPlay;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay;
+using osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay.Card;
+using osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay.Hand;
namespace osu.Game.Tests.Visual.RankedPlay
{
@@ -14,6 +19,8 @@ public partial class TestSceneOpponentPickScreen : RankedPlayTestScene
{
private RankedPlayScreen screen = null!;
+ private readonly BeatmapRequestHandler requestHandler = new BeatmapRequestHandler();
+
public override void SetUpSteps()
{
base.SetUpSteps();
@@ -26,8 +33,6 @@ public override void SetUpSteps()
AddStep("load screen", () => LoadScreen(screen = new RankedPlayScreen(MultiplayerClient.ClientRoom!)));
AddUntilStep("screen loaded", () => screen.IsLoaded);
- var requestHandler = new BeatmapRequestHandler();
-
AddStep("setup request handler", () => ((DummyAPIAccess)API).HandleRequest = requestHandler.HandleRequest);
AddStep("set pick state", () => MultiplayerClient.RankedPlayChangeStage(RankedPlayStage.CardPlay, state => state.ActiveUserId = 2).WaitSafely());
@@ -44,7 +49,11 @@ public override void SetUpSteps()
}).WaitSafely();
}
});
+ }
+ [Test]
+ public void TestBasic()
+ {
AddWaitStep("wait", 15);
AddStep("play beatmap", () => MultiplayerClient.PlayUserCard(2, hand => hand[0]).WaitSafely());
@@ -54,5 +63,29 @@ public override void SetUpSteps()
BeatmapID = requestHandler.Beatmaps[0].OnlineID
}).WaitSafely());
}
+
+ [Test]
+ public void TestPickPreviewPlayedOnOpponentPick()
+ {
+ RankedPlayCard.SongPreviewContainer? originalPreview = null;
+
+ AddStep("hover first card",
+ () => InputManager.MoveMouseTo(this.ChildrenOfType<PlayerHandOfCards>().Single().Cards
+ .First(c => c.Item.PlaylistItem.Value != null && c.Item.PlaylistItem.Value.BeatmapID != requestHandler.Beatmaps[0].OnlineID)));
+ AddUntilStep("preview playing", () => originalPreview = this.ChildrenOfType<RankedPlayCard.SongPreviewContainer>().FirstOrDefault(p => p.IsRunning), () => Is.Not.Null);
+
+ AddStep("play beatmap", () => MultiplayerClient.PlayUserCard(2, hand => hand[0]).WaitSafely());
+ AddStep("reveal card", () => MultiplayerClient.RankedPlayRevealUserCard(2, hand => hand[0], new MultiplayerPlaylistItem
+ {
+ ID = 0,
+ BeatmapID = requestHandler.Beatmaps[0].OnlineID
+ }).WaitSafely());
+
+ AddUntilStep("wait for original preview stopped", () => originalPreview?.IsRunning, () => Is.False);
+
+ AddUntilStep("preview playing is opponent's pick",
+ () => ((RankedPlayCard)this.ChildrenOfType<RankedPlayCard.SongPreviewContainer>().SingleOrDefault(p => p.IsRunning)?.Parent!).Item.PlaylistItem.Value?.BeatmapID,
+ () => Is.EqualTo(requestHandler.Beatmaps[0].OnlineID));
+ }
}
}
```
---------
Co-authored-by: Bartłomiej Dach <dach.bartlomiej@gmail.com>
RFC.
See https://github.com/ppy/osu/pull/37453#issuecomment-4289403735 for
why.
Of note:
- To facilitate mutual exclusivity of playback `PlayerHandOfCards`
maintains a bindable pointing at the currently playing song preview.
- Because of how card drawables are passed between multiple parenting
drawables, some of which are and some of which are not
`PlayerHandOfCards` instances, DI fails horribly at working with this
bindable unless it is manually managed. See relevant overrides in
`PlayerHandOfCards`.
- I renamed one of the overloads of `HandOfCards.RemoveCard()` to
`DetachCard()` because I found the fact that there are two overloads of
one method that do WILDLY DIFFERENT THINGS utterly *asinine*. (One
overload scrapes the `RankedPlayCard` out for you to plop elsewhere. One
*drops it on the floor entirely*.)
This took way too long to write.