Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
5cdf07c
Make grouped friend notifications Transient and not important (#36620)
SupDos Feb 8, 2026
239951e
Fix seeking to previous bookmark not working when song is playing (#3…
Joehuu Feb 8, 2026
7263551
Improve handling of account registration errors (#36600)
bdach Feb 8, 2026
bb04a18
Localise "lounge" & "quick play" in `ButtonSystem` (#36220)
diquoks Feb 8, 2026
0a4497d
Fix local beatmap metadata not being cleared when previously selecte…
Joehuu Feb 9, 2026
bb83f3d
Fix unreliable async loading due to incorrect single-child assumption
peppy Feb 9, 2026
0198203
Allow binding left/right modifier keys separately for gameplay bindin…
failaip12 Feb 9, 2026
60d98f0
Improve adjusting mods settings values with keyboard (#36090)
diquoks Feb 9, 2026
efd4d12
Add info about damage dealt to ranked play user state (#36627)
minetoblend Feb 9, 2026
f39b310
Add missing wait step to fix flaky test in `TestSceneModPresetColumn`
peppy Feb 9, 2026
cae8d3f
Update production endpoints in an attempt to fix Russian player conne…
peppy Feb 9, 2026
b613c1d
Merge pull request #36635 from peppy/update-production-endpoints
bdach Feb 9, 2026
a1136c3
Update framework (#36637)
peppy Feb 9, 2026
dbce888
Rename `Ruleset.GetHitResults{ -> ForDisplay}()`
bdach Feb 9, 2026
3678ea0
Publicise `Ruleset.GetValidHitResults()`
bdach Feb 9, 2026
15c49a7
Adjust contract of `Ruleset.GetValidHitResults()` to always enumerate…
bdach Feb 9, 2026
655d725
Perform extra checks when loading rulesets (#36641)
bdach Feb 10, 2026
c59e5bf
Merge pull request #36639 from bdach/actually-all-valid-hit-results
peppy Feb 10, 2026
56bc80f
Update resources
peppy Feb 10, 2026
50426eb
Refactor UpdateableTeamFlag for use on team overlay (#36286)
LiquidPL Feb 10, 2026
5afd6c6
Add user role to `MultiplayerRoomUser` (#36652)
bdach Feb 11, 2026
860427e
Fix corner radius for user tag glow (#36659)
netineko Feb 13, 2026
94d9de9
Add explanations for scoring mode used in ranked & total score toolti…
bdach Feb 13, 2026
87f323a
Update resources
peppy Feb 15, 2026
2f459dd
Preserve bookmarks when creating a new difficulty from scratch (#36675)
64ArthurAraujo Feb 16, 2026
810edeb
Fix extra lives in Easy mod potentially getting reapplied during game…
bdach Feb 16, 2026
8e26cf4
Restore previous beatmap when leaving scoped mode (#36582)
LiquidPL Feb 16, 2026
fc4e297
Use directory of last-selected sample as initial directory in sample …
bdach Feb 16, 2026
42bf254
Fix osu! editor composer scale handles not updating correctly when se…
bdach Feb 16, 2026
94cec71
Prevent tagging beatmaps when played with conversion mods (#36684)
imvanni Feb 17, 2026
71583bc
Add basic support for `scoreentry-` skinnable text
peppy Feb 17, 2026
6e03d5b
Show initial key placeholder / trigger name using non-skinned font
peppy Feb 17, 2026
cc9a5ed
Update resources
peppy Feb 17, 2026
86ab507
Use default overlap of 1
peppy Feb 17, 2026
7445efa
Ensure state resets back to initial display on rewind
peppy Feb 17, 2026
9fa7812
Merge pull request #36687 from peppy/legacy-key-counter-skinnable-font
bdach Feb 18, 2026
f15e086
Add "spin" keyword for menu cursor rotation
peppy Feb 18, 2026
83587e2
Add ability for `LoadingLayer` to block all keyboard input
peppy Feb 18, 2026
93d1a61
Migrate playlist freestyle select screen to use `SongSelectV2`
peppy Feb 17, 2026
fb8c228
Add test coverage of actual user flows involving freestyle select screen
peppy Feb 18, 2026
110f11f
Fade out header line for now to avoid visual overlap
peppy Feb 18, 2026
d33a6d5
Add ability for `LoadingLayer` to block all keyboard input
peppy Feb 18, 2026
4854473
Migrate multiplayer freestyle select screen to use `SongSelectV2`
peppy Feb 18, 2026
25d6308
Merge pull request #36695 from peppy/loading-layer-block-non-positional
bdach Feb 18, 2026
e15cb08
Privatise setter
bdach Feb 18, 2026
1153d17
Disallow use of scoping button when at freestyle song select
peppy Feb 18, 2026
f9f927f
Disable footer buttons on freestyle song select
bdach Feb 19, 2026
9ae9964
Merge pull request #36694 from peppy/freestyle-select-update
bdach Feb 19, 2026
d3b4836
Merge branch 'master' into multiplayer-freestyle-select
bdach Feb 19, 2026
69c2747
Merge pull request #36696 from peppy/multiplayer-freestyle-select
bdach Feb 19, 2026
1e8b9a7
Adjust nightcore sample playback to not stop randomly and be closer t…
bdach Feb 20, 2026
c144cf1
Fix reversing straight perfect curve sliders positioning them weirdly…
64ArthurAraujo Feb 20, 2026
29a39cb
Fix spectator player cells not having initial shadow edge effect set …
Joehuu Feb 21, 2026
d139f59
Update resources
peppy Feb 22, 2026
a09489d
Move transformation logic local to transient drawable
peppy Feb 23, 2026
4916c87
Move toolbar button text local to single usage in `ToolbarUserButton`
peppy Feb 23, 2026
f39615a
Move spacing local to usages
peppy Feb 23, 2026
e2cbfb9
Merge pull request #36738 from peppy/user-button-animation-fix
bdach Feb 24, 2026
efc9a27
Remove no longer used class
peppy Feb 19, 2026
cb12d35
Combine old classes to provide better starting point for migration
peppy Feb 19, 2026
fabce18
Remove `V2` suffix from migrated classes
peppy Feb 20, 2026
5602281
Merge pull request #36745 from peppy/song-select-cleanup
bdach Feb 24, 2026
ccb0224
Attempt to fix "copy labels from issues" workflow permissions again
bdach Feb 24, 2026
5b1d4ce
Merge pull request #36746 from bdach/fix-thing-again
peppy Feb 24, 2026
ee8d990
Rename one more missed song select v2 class
peppy Feb 24, 2026
99ab245
Tidy up how `SettingsSubsection` headings are created
peppy Feb 25, 2026
cb597c4
Fix flow animating its display initially when it shouldn't
peppy Feb 25, 2026
047ea7c
Fix hidden settings flow content still being interactive
peppy Feb 25, 2026
0dfb362
Use `HotkeyDisplay` for toolbar buttons (#36750)
netineko Feb 25, 2026
32d1040
Merge pull request #36751 from peppy/fix-hidden-controls-settings
bdach Feb 25, 2026
5174d8b
Fix changing normal sample bank via keyboard hotkeys not updating add…
bdach Feb 25, 2026
7e2771c
Improve usability of sample bank toggles (#36753)
bdach Feb 26, 2026
5131b18
Remove redundant footer from `TestScenePlaylistsRoomSubscreen`
LiquidPL Feb 27, 2026
2e659a7
Merge pull request #36767 from LiquidPL/cleanup-online-play-tests
peppy Feb 27, 2026
c72b641
Add pool type to matchmaking room invited event (#36765)
smoogipoo Feb 27, 2026
105342e
Migrate sheared overlay tests to `ScreenTestScene` (#36736)
LiquidPL Feb 27, 2026
9c489aa
Use better text structure for `ScreenshotSaved` notification (#36701)
diquoks Feb 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/pull-request-copy-labels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ on:
types: [opened]

permissions:
issues: write # to read the labels of any linked issue(s), and to put the found labels if any on the PR
# not granting any `pull_requests` permissions because in github's modeling pull requests are a subset of issues. it's confusing.
issues: read # to read the labels of any linked issue(s)
pull-requests: write # to put the found labels if any on the PR

jobs:
copy-labels:
Expand Down
2 changes: 1 addition & 1 deletion osu.Android.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2026.129.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2026.209.0" />
</ItemGroup>
<PropertyGroup>
<!-- Fody does not handle Android build well, and warns when unchanged.
Expand Down
7 changes: 6 additions & 1 deletion osu.Game.Rulesets.Catch/CatchRuleset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,15 +176,20 @@ public override IEnumerable<Mod> GetModsFor(ModType type)

public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch };

protected override IEnumerable<HitResult> GetValidHitResults()
public override IEnumerable<HitResult> GetValidHitResults()
{
return new[]
{
HitResult.Great,
HitResult.Miss,

HitResult.LargeTickHit,
HitResult.LargeTickMiss,
HitResult.SmallTickHit,
HitResult.SmallTickMiss,
HitResult.LargeBonus,
HitResult.IgnoreHit,
HitResult.IgnoreMiss,
};
}

Expand Down
8 changes: 5 additions & 3 deletions osu.Game.Rulesets.Mania/ManiaRuleset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ private PlayfieldType getPlayfieldType(int variant)
return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast<int>().OrderDescending().First(v => variant >= v);
}

protected override IEnumerable<HitResult> GetValidHitResults()
public override IEnumerable<HitResult> GetValidHitResults()
{
return new[]
{
Expand All @@ -392,9 +392,11 @@ protected override IEnumerable<HitResult> GetValidHitResults()
HitResult.Good,
HitResult.Ok,
HitResult.Meh,
HitResult.Miss,

// HitResult.SmallBonus is used for awarding perfect bonus score but is not included here as
// it would be a bit redundant to show this to the user.
HitResult.IgnoreHit,
HitResult.ComboBreak,
HitResult.IgnoreMiss,
};
}

Expand Down
232 changes: 229 additions & 3 deletions osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderReversal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@

using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Tests.Beatmaps;
Expand All @@ -30,6 +33,16 @@ public partial class TestSceneSliderReversal : TestSceneOsuEditor
PathType.LINEAR,
new Vector2(100, 0),
new Vector2(100, 100)
),
createPathSegment(
PathType.PERFECT_CURVE,
new Vector2(100.009f, -50.0009f),
new Vector2(200.0089f, -100)
),
createPathSegment(
PathType.PERFECT_CURVE,
new Vector2(25, -50),
new Vector2(100, 75)
)
};

Expand All @@ -48,9 +61,13 @@ private static PathControlPoint[] createPathSegment(PathType type, params Vector

[TestCase(0, 250)]
[TestCase(0, 200)]
[TestCase(1, 120)]
[TestCase(1, 80)]
public void TestSliderReversal(int pathIndex, double length)
[TestCase(1, 120, false, false)]
[TestCase(1, 80, false, false)]
[TestCase(2, 250)]
[TestCase(2, 190)]
[TestCase(3, 250)]
[TestCase(3, 190)]
public void TestSliderReversal(int pathIndex, double length, bool assertEqualDistances = true, bool assertSliderReduction = true)
{
var controlPoints = paths[pathIndex];

Expand Down Expand Up @@ -90,6 +107,215 @@ public void TestSliderReversal(int pathIndex, double length)
InputManager.ReleaseKey(Key.LControl);
});

if (pathIndex == 2)
{
AddRepeatStep("Reverse slider again", () =>
{
InputManager.PressKey(Key.LControl);
InputManager.Key(Key.G);
InputManager.ReleaseKey(Key.LControl);
}, 2);
}

if (assertEqualDistances)
{
AddAssert("Middle control point has the same distance from start to end", () =>
{
var pathControlPoints = selectedSlider.Path.ControlPoints;
float middleToStart = Vector2.Distance(pathControlPoints[^2].Position, pathControlPoints[0].Position);
float middleToEnd = Vector2.Distance(pathControlPoints[^2].Position, pathControlPoints[^1].Position);

return Precision.AlmostEquals(middleToStart, middleToEnd, 1f);
});
}

AddAssert("Middle control point is not at start or end", () =>
Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position, oldStartPos) > 1 &&
Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position, oldEndPos) > 1
);
Comment on lines +132 to +135

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Coordinate system mismatch in assertion.

ControlPoints[^2].Position is a path-relative position (small values relative to path start), while oldStartPos and oldEndPos are absolute playfield coordinates (~256, ~192). This comparison will always pass trivially due to the large coordinate difference, not because the middle control point is actually positioned correctly.

Proposed fix: Compare against path-relative positions
 AddAssert("Middle control point is not at start or end", () =>
-    Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position, oldStartPos) > 1 &&
-    Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position, oldEndPos) > 1
+    Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position, selectedSlider.Path.ControlPoints[0].Position) > 1 &&
+    Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position, selectedSlider.Path.ControlPoints[^1].Position) > 1
 );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderReversal.cs` around lines
132 - 135, The assertion compares selectedSlider.Path.ControlPoints[^2].Position
(a path-relative coordinate) to oldStartPos/oldEndPos (absolute playfield
coordinates), causing a meaningless large-distance pass; fix it by converting
the absolute coordinates to path-relative before comparing — e.g. obtain
path-relativeStart = selectedSlider.Path.ToPathSpace(oldStartPos) and
path-relativeEnd = selectedSlider.Path.ToPathSpace(oldEndPos) (or the inverse:
convert the control point to playfield space) and then assert
Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position,
path-relativeStart) > 1 &&
Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position,
path-relativeEnd) > 1 so the comparison is done in the same coordinate system.


AddAssert("Slider has correct length", () =>
Precision.AlmostEquals(selectedSlider.Path.Distance, oldDistance));

AddAssert("Slider has correct start position", () =>
Vector2.Distance(selectedSlider.Position, oldEndPos) < 1);

AddAssert("Slider has correct end position", () =>
Vector2.Distance(selectedSlider.EndPosition, oldStartPos) < 1);

AddAssert("Control points have correct types", () =>
{
var newControlPointTypes = selectedSlider.Path.ControlPoints.Select(p => p.Type).ToArray();

return oldControlPointTypes.Take(newControlPointTypes.Length).SequenceEqual(newControlPointTypes);
});

if (assertSliderReduction)
{
AddStep("Move to marker", () =>
{
var marker = this.ChildrenOfType<SliderEndDragMarker>().Single();
var markerPos = (marker.ScreenSpaceDrawQuad.TopRight + marker.ScreenSpaceDrawQuad.BottomRight) / 2;
// sometimes the cursor may miss the marker's hitbox so we
// add a little offset here to be sure it lands in a clickable position.
var position = new Vector2(markerPos.X + 2f, markerPos.Y);
InputManager.MoveMouseTo(position);
});
AddStep("Click", () => InputManager.PressButton(MouseButton.Left));
AddStep("Reduce slider", () =>
{
var middleControlPoint = this.ChildrenOfType<PathControlPointPiece<Slider>>().ToArray()[^2];
InputManager.MoveMouseTo(middleControlPoint);
});
AddStep("Release click", () => InputManager.ReleaseButton(MouseButton.Left));

AddStep("Save half slider info", () =>
{
oldStartPos = selectedSlider.Position;
oldEndPos = selectedSlider.EndPosition;
oldDistance = selectedSlider.Path.Distance;
});

AddStep("Reverse slider", () =>
{
InputManager.PressKey(Key.LControl);
InputManager.Key(Key.G);
InputManager.ReleaseKey(Key.LControl);
});

AddAssert("Middle control point has the same distance from start to end", () =>
{
var pathControlPoints = selectedSlider.Path.ControlPoints;
float middleToStart = Vector2.Distance(pathControlPoints[^2].Position, pathControlPoints[0].Position);
float middleToEnd = Vector2.Distance(pathControlPoints[^2].Position, pathControlPoints[^1].Position);

return Precision.AlmostEquals(middleToStart, middleToEnd, 1f);
});

AddAssert("Middle control point is not at start or end", () =>
Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position, oldStartPos) > 1 &&
Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position, oldEndPos) > 1
);
Comment on lines +195 to +198

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Same coordinate system mismatch.

Same issue as noted above at lines 132-135 — path-relative positions compared to absolute coordinates.

Proposed fix
 AddAssert("Middle control point is not at start or end", () =>
-    Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position, oldStartPos) > 1 &&
-    Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position, oldEndPos) > 1
+    Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position, selectedSlider.Path.ControlPoints[0].Position) > 1 &&
+    Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position, selectedSlider.Path.ControlPoints[^1].Position) > 1
 );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderReversal.cs` around lines
195 - 198, The assertion is comparing a path-relative control point
(selectedSlider.Path.ControlPoints[^2].Position) against absolute coordinates
(oldStartPos/oldEndPos); convert one side so both use the same coordinate space.
Fix by transforming the control point position to the same absolute space as
oldStartPos/oldEndPos (e.g. add the slider's drawable/anchor position or call
the appropriate space-conversion helper on selectedSlider) or alternatively
convert oldStartPos/oldEndPos into the slider/path-relative space, then use
Vector2.Distance as before to assert the middle control point isn't equal to
start or end.


AddAssert("Slider has correct length", () =>
Precision.AlmostEquals(selectedSlider.Path.Distance, oldDistance));

AddAssert("Slider has correct start position", () =>
Vector2.Distance(selectedSlider.Position, oldEndPos) < 1);

AddAssert("Slider has correct end position", () =>
Vector2.Distance(selectedSlider.EndPosition, oldStartPos) < 1);

AddAssert("Control points have correct types", () =>
{
var newControlPointTypes = selectedSlider.Path.ControlPoints.Select(p => p.Type).ToArray();

return oldControlPointTypes.Take(newControlPointTypes.Length).SequenceEqual(newControlPointTypes);
});
}
}

[Test]
public void TestSegmentedSliderReversal()
{
PathControlPoint[] segmentedSliderPath =
[
new PathControlPoint
{
Position = new Vector2(0, 0),
Type = PathType.PERFECT_CURVE
},
new PathControlPoint
{
Position = new Vector2(100, 150),
},
new PathControlPoint
{
Position = new Vector2(75, -50),
Type = PathType.PERFECT_CURVE
},
new PathControlPoint
{
Position = new Vector2(225, -75),
},
new PathControlPoint
{
Position = new Vector2(350, 50),
Type = PathType.PERFECT_CURVE
},
new PathControlPoint
{
Position = new Vector2(500, -75),
},
new PathControlPoint
{
Position = new Vector2(350, -120),
},
];

Vector2 oldStartPos = default;
Vector2 oldEndPos = default;
double oldDistance = default;

var oldControlPointTypes = segmentedSliderPath.Select(p => p.Type);

AddStep("Add slider", () =>
{
var slider = new Slider
{
Position = new Vector2(0, 200),
Path = new SliderPath(segmentedSliderPath)
{
ExpectedDistance = { Value = 1314 }
}
};

EditorBeatmap.Add(slider);

oldStartPos = slider.Position;
oldEndPos = slider.EndPosition;
oldDistance = slider.Path.Distance;
});

AddStep("Select slider", () =>
{
var slider = (Slider)EditorBeatmap.HitObjects[0];
EditorBeatmap.SelectedHitObjects.Add(slider);
});

AddRepeatStep("Reverse slider", () =>
{
InputManager.PressKey(Key.LControl);
InputManager.Key(Key.G);
InputManager.ReleaseKey(Key.LControl);
}, 3);

AddAssert("First arc's control is not at the slider's middle", () =>
Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position, selectedSlider.Path.PositionAt(0.5)) > 1
);

AddAssert("Last arc's control is not at the slider's middle", () =>
Vector2.Distance(selectedSlider.Path.ControlPoints[1].Position, selectedSlider.Path.PositionAt(0.5)) > 1
);

AddAssert("First arc centered middle control point", () =>
{
var pathControlPoints = selectedSlider.Path.ControlPoints;
float middleToStart = Vector2.Distance(pathControlPoints[1].Position, pathControlPoints[0].Position);
float middleToEnd = Vector2.Distance(pathControlPoints[1].Position, pathControlPoints[2].Position);

return Precision.AlmostEquals(middleToStart, middleToEnd, 1f);
});

AddAssert("Last arc centered middle control point", () =>
{
var pathControlPoints = selectedSlider.Path.ControlPoints;
float middleToStart = Vector2.Distance(pathControlPoints[^2].Position, pathControlPoints[^3].Position);
float middleToEnd = Vector2.Distance(pathControlPoints[^2].Position, pathControlPoints[^1].Position);

return Precision.AlmostEquals(middleToStart, middleToEnd, 1f);
});

AddAssert("Slider has correct length", () =>
Precision.AlmostEquals(selectedSlider.Path.Distance, oldDistance));

Expand Down
Loading