675 Commits

Author SHA1 Message Date
7d38e6c8eb bump to upstream 2026.307.0-tachyon, some fixups 2026-03-08 09:28:33 +03:00
Dean Herbert
c4402e9ce5 Fix potential crash due to handling scores in leaderboard post-disposal (#36860)
Closes https://github.com/ppy/osu/issues/36858. Classic.
2026-03-07 04:40:25 +09:00
Dean Herbert
de9cf751d2 Fix LoadingLayer centering
oops.
2026-03-07 02:51:50 +09:00
Dan Balasescu
0988552567 Implement ranked play (#36819)
I don't really have much to say here. Instead, I'll give a brief history
rundown that lists many pages of documentation you can read, if
interested.

- Started off as BTMC + Happy24 (Vivi)'s ["The
Vision"](https://docs.google.com/document/d/1p1IpPmd2RICp8G4OqkCSs7u8Ug8FbFv8qqP0mfSrHf0/edit?tab=t.0#heading=h.fol093d9f9xi)
- Initial
[designs](https://www.figma.com/design/f5qqC57t9jFlgpzhRqUNVX/The-Vision?node-id=0-1&p=f)
were led by Vivi.
- Designs
[morphed](https://www.figma.com/design/vtFmLrXKvWNyYiRjTezFTM/Untitled--Copy-?node-id=0-1&p=f)
during development into what's currently present, led by @minetoblend.
- There is some more ongoing work creating a [game design
document](https://docs.google.com/document/d/1iffJFCsIBfYF0D4ogItSBEj6YBmbp-rdCpItAeaJiTA/edit?tab=t.0).

**tl;dr:** Create something with the mechanics of a trading card game
within osu!. The name of this is "ranked play".

---

To be frank, a lot of stuff is missing here. Some of it I don't want to
mention, because the point of this exercise is to get the system into
the hands of players, gather feedback especially around mechanics, and
discuss any further direction with the team.

I am expecting a blanket approval on all of the new code, with
particular attention to changes in existing components that I'll point
out in a self review.

There is also some [ongoing
work](https://github.com/smoogipoo/osu/pulls) that may arrive in this
branch prior to being merged.

---------

Co-authored-by: maarvin <minetoblend@gmail.com>
Co-authored-by: Marvin <m.schuerz@hautzy.com>
Co-authored-by: Jamie Taylor <me@nekodex.net>
Co-authored-by: ArijanJ <arijanj@proton.me>
Co-authored-by: Dean Herbert <pe@ppy.sh>
Co-authored-by: Tim Oliver <git@tim.dev>
Co-authored-by: Joseph Madamba <madamba.joehu@outlook.com>
Co-authored-by: Bartłomiej Dach <dach.bartlomiej@gmail.com>
Co-authored-by: nil <25884226+voidstar0@users.noreply.github.com>
Co-authored-by: Ботников Максим <mr.botnikoff@ya.ru>
Co-authored-by: Denis Titovets <den232titovets@yandex.ru>
Co-authored-by: Michael Middlezong <119022671+mmiddlezong@users.noreply.github.com>
Co-authored-by: SupDos <6813986+SupDos@users.noreply.github.com>
Co-authored-by: failaip12 <86018517+failaip12@users.noreply.github.com>
2026-03-07 02:30:50 +09:00
复予
0fd0bd3580 Consider comments in wiki's front matter YAML (#36758)
This PR makes front matter items with comments able to be parsed
correctly by the client, for example:

```markdown
---
tags:
  - keyboard
  - tap
  - hybrid
  - play style
needs_cleanup: true  # https://github.com/ppy/osu-wiki/issues/9919
---

# Hybrid

...
```

The front matter YAML used by osu!wiki not complicated, so simply
splitting with `#` is possible.

---------

Co-authored-by: Dean Herbert <pe@ppy.sh>
2026-03-07 01:41:59 +09:00
Bartłomiej Dach
16bc1de9fd Allow changing addition bank button state when objects are selected even if the selection has no addition sounds (#36808)
Before:


https://github.com/user-attachments/assets/d87bd7e3-37f8-4634-9e6a-5859d5bade57

After:


https://github.com/user-attachments/assets/4de940af-1e30-4266-9aac-5ccd12f38742

---

The title is convoluted but basically I'm angling to close
https://github.com/ppy/osu/issues/36705 with this.

The point is that on current `master`, the keyboard-hotkey-based toggles
on the left of the screen get disabled if you select a range of objects
which contains no addition samples. The report linked above finds this
annoying because it means you basically always need to add an addition
sound *first* and *then* pick a bank.

This is not necessary, and this commit changes the behaviour such that
the bank selection toggles are no longer blocked when you select a range
of objects without additions. Choosing an addition bank when there are
no additions still does nothing to the selected object, *but* adding a
sound *after* that bank preselection will use the preselected bank
rather than auto.
2026-03-07 00:47:35 +09:00
Bartłomiej Dach
0d74983551 Fix rotation of certain objects breaking change states (#36852)
Closes https://github.com/ppy/osu/issues/36830.

This is a regression from https://github.com/ppy/osu/pull/36681.

Due to the aforementioned pull request's changes, rotating an object
that could not scale on the X or Y axis (due to having that dimension
zero) would trigger `CanScale{X,Y}` to change as said rotated object's
width or height became not zero. This in turn would cause `SelectionBox`
to *fully recreate* all of its handles and buttons, *including* the
rotation handle that initiated the rotation operation, therefore
dropping the ongoing rotation operation completely and leaving the
editor in a half-broken state.

The suggested solution here is to recreate handles more granularly to
prevent this from happening. (I've probably not improved it as much as I
could have, but this is as far as I'm willing to go for now unless
review finds it unpalatable.)
2026-03-07 00:20:10 +09:00
Dean Herbert
b46656e7d3 Fix loading spinner not being centered correctly (#36849)
One of those things you can't un-see.

Closes https://github.com/ppy/osu/issues/36832.
2026-03-06 12:11:16 +01:00
Dean Herbert
482d31d598 Fix "no tablet" settings display not updating after language change (#36848)
Closes https://github.com/ppy/osu/issues/36833.

Schedule is unfortunate but I'm not really wanting to go down a rabbit
hole right now.
2026-03-06 12:11:04 +01:00
Bartłomiej Dach
f9e863af01 Remove "copy labels from linked issues" github workflow (#36854)
[It still doesn't
work.](https://github.com/ppy/osu/actions/runs/22759488243/job/66012293202)

Looking at the [job
output](https://github.com/ppy/osu/actions/runs/22759488243/job/66012293202#step:1:21)
it appears that the permissions of the `GITHUB_TOKEN` are
*automatically* constrained to `read` even if you request more scopes.
Would be nice if that was *actually documented* somewhere!

Also given supply-chain attacks that people are running on github [via
*issue titles* these
days](https://grith.ai/blog/clinejection-when-your-ai-tool-installs-another)
I'm not sure we want any automation near where it can reach code. Sure,
much of the fault in the aforementioned attack was the fault of meatbags
trusting clankers *WAY* too much, which is a mistake we *would not* do,
but given everpresent software degradation *from unknown sources and for
unknown reasons* let's not ~~COPILOT~~ *ahem* tempt fate...
2026-03-06 19:54:44 +09:00
Dean Herbert
04767f44bb Allow editor 100% background dim (#36847)
Whatever at this point.

https://github.com/ppy/osu/discussions/30421
2026-03-06 19:32:34 +09:00
Dean Herbert
8fe63ade31 Adjust padding and text size in editor inspector (#36843)
It was taking up way too much vertical space previously.

I'm using the same font specs that are in the new dropdown header, which
seem to work quite well. Negative padding because without it everything
is way too vertically sparse.

Why? I was looking at [this
discussion](https://github.com/ppy/osu/discussions/36708) and like "we
need to have SV visible here" but there's really not much room with all
this text in the way.
2026-03-06 19:32:19 +09:00
Eeli Ogata
c1555af620 Only play nightcore hat sounds when tick rate is a multiple of 2 (#36459)
Closes #13513

Matches stable behavior where hat only plays when the slider tick rate
is a multiple of 2.

---------

Co-authored-by: Dean Herbert <pe@ppy.sh>
2026-03-06 17:56:14 +09:00
Eeli Ogata
e911217980 Fix crash when clicking panels during recycling (#36457)
Closes #36246

This PR overrides `HandlePositionalInput` to reject all positional input
when `Item` is null.

---------

Co-authored-by: Dean Herbert <pe@ppy.sh>
2026-03-06 09:36:39 +01:00
Bartłomiej Dach
ba2ae3218e Show first two slider repeats with the rest of the combo in Freeze Frame mod (#36427)
Before:


https://github.com/user-attachments/assets/95b19bdb-12c5-4ee7-b7e2-f9098aecd2fa

After:


https://github.com/user-attachments/assets/937081c5-92cb-4bea-a774-e53afbdbb1fb

---

This is a subjective diff and can be closed on the spot with no
discussion if deemed incorrect. I mostly just want to get it off my list
of unresolved forum threads.

The catalyst for this change is [this
thread](https://osu.ppy.sh/community/forums/topics/2171493?n=1), wherein
the complaint is that you can get jumpscared by a slider repeat in a
combo because the slider repeats do not fade in with the rest of the
combo. Which means that you don't immediately know whether a slider will
need to be repeated or not and you have to watch out for potential
slider repeats fading in, which could happen at an *arbitrary* time
because it's still dependent on the map's original AR.

This seems pretty anti-user so I made it fade in with the rest of the
combo. The original comment cited "broken layering" which I guess refers
to the fact that sliders can repeat *more* than once and the second
repeat will show *under the slider head*. It's pretty broken, sure, but
I don't think it's *that* bad, and I *do* think that it's better than
the current behaviour.

The reason it shows only the first two repeats is that there's no point
in showing more for reasons that are hopefully obvious.
2026-03-06 15:29:47 +09:00
Krzysztof Gutkowski
b76a7e1bb6 Refactor ShearedButton to allow easier relative sizing (#36802)
This commit rearranges the contents of `ShearedButtons` to be more
independent of each other in regards to sizing. Thanks to that, the
custom logic related to enabling autosizing is no longer necessary.
Witdh and height are no longer set via the constructor, and can be
freely configured using the initializer syntax.

Additionally, this allows the button to use relative sizing without
having to resort to any hackery with `Size` (this will come in handy for
me when implementing the new footer on multiplayer screens).

Given that most of the `ShearedButton`s currently in use set their width
explicitly, I did not set `AutoSizeAxes = Axes.X` like it would be by
default previously. Instead it is set on the only two such buttons (show
converts/selected mods on ssv2). I suppose it might be a good idea to
have it set that by default if no `Width` is specified, as right now
it'll just not show anything.

Also I've set the margin on the text field by default in all cases
instead of only when autosizing like how it was previously, since
otherwise it would be a pain to set that on each button instance when
needed. I've checked all affected components and could not find any text
overflowing issues that this could cause.

---------

Co-authored-by: Dean Herbert <pe@ppy.sh>
2026-03-06 02:09:05 +09:00
Bartłomiej Dach
6b10ef8709 Disable presenting scores in online song selects (#36826)
RFC. Closes https://github.com/ppy/osu/issues/36815 I guess.

Song select V1 completely disabled the ability to view a score outside
of solo play in ways that are very easy to let fly under the radar:


5fc836d1f0/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs (L26)
46db3ad96d/osu.Game/Screens/Select/PlaySongSelect.cs (L47-L53)

therefore the issue this is trying to close would never even manifest
there.

The direct cause of the issue is that the results screen is pushed to
the relevant online screen's *substack* of screens rather than the
game-global parent stack. That means that when the back button is
pressed, the following logic fires:


6fa4a7152f/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs (L174-L189)

This logic fires *on the parent screen* even though *the child screen*
is the one the user is attempting to back out of. And none of the
exemptions for the screen substack inside of the above method fire
because the subscreen is not an `IOnlinePlaySubScreen` (it's
`SoloResultsScreen` in this case).

Now the direct cause here is probably fixable, although possibly not
without some significant pulling of footer-shaped teeth. *However*, I
kind of question as to why viewing scores should be permitted on online
song selects in the first place - it kind of distracts from the primary
purpose of the screens which is to *just pick a map already*.
2026-03-06 00:19:10 +09:00
Bartłomiej Dach
43897457fb Fix subscription leak from composer reloads (#36828)
The reproduction scenario for the subscription leak is as follows:

1. Switch to a scrolling ruleset (anything but osu! from the standard
ones).
2. Select a beatmap to edit.
3. Load the composer.
4. Go to timing tab.
5. Change a timing point.
6. Go back to the composer.

At this point, `EditorChangeHandler.OnStateChange` will have multiple of
the same delegate in the invocation list.

<img width="691" height="311" alt="Screenshot 2026-03-05 at 11 15 55"
src="https://github.com/user-attachments/assets/57788341-9573-48f1-b360-f21036891081"
/>

That in turn is caused by the fact that changing a timing point *does*
incur a full reload of the composer via the following flow:


15b6e28ebe/osu.Game/Rulesets/Edit/ScrollingHitObjectComposer.cs (L145)
64a29313a8/osu.Game/Screens/Edit/Editor.cs (L1137-L1145)

This flow is my "fault"; see https://github.com/ppy/osu/pull/28444. The
reason why a full composer reload is used is not clear to my
recollection at this time, but it is likely because it's just the least
likely to fail. A smarter solution that wouldn't require a full reload
would also entail checking that there exists a safe insertion point that
allows replacing timing points in a way that will reflect everywhere it
must. Including all of the `IScrollingAlgorithm` machinery and such.

In general it is not uncommon in the codebase to not bother to clean up
some event callbacks if it is implicitly or explicitly guaranteed that
both objects bound by the callback will always get disposed in tandem at
the same time. This *was* true with this particular flow to a point,
which was until that full composer reload was implemented.

<details>
<summary>To address the elephant in the room</summary>

Someone will inevitably notice https://github.com/ppy/osu/pull/36824
which was a clanked pull request pointing out this leak. And then
someone will inevitably call this "AI discrimination"! *Gasp!*

So first of all, let me stop you right there. Yes, as far as I am
_personally_ concerned, it is "AI discrimination". I invoke the full
force of the Butlerian Jihad.

The clank army's goal is to eradicate my job and make me work in an
Amazon warehouse instead. Or, if not that, at least my job is to be rid
of all remnants of fun I still get from it and for me to be reduced to
that one guy from the meme "i guess we're doin circles now". You know
the one.

I resent this. You attack me directly. I do not perceive the need to
meet you halfway or be civil.

That said, I have too much respect for the users of this software to
leave reports of potentially real issues unchecked. So I did check, and
it was real. And you know what? Good job to the clanker. It did what it
was designed to do: it parsed a code file, recognised a hole in a
pattern it was designed to recognise, and invoked forms of language
given to it to communicate this to the meatbag that opened that PR.

And here's the thing: my primary issue is with that meatbag that opened
that PR. That meatbag served no functional purpose in any of this. The
meatbag took a hose that spews 90% water and 10% raw sewage at random
intervals and pointed it at my house directly, claiming that they just
want to clean it. At no point did the meatbag appear to have the common
decency to pull out a container, pour some magic liquid out, check if
there's sewage in it, and filter it out if there is any. But no, that
would take *effort* and *thought*, would it not? The *effort* and
*thought* that is required of *me* to *review* the clanker's work?

The PR had no reproduction scenario, and had testing checkboxes that
were presumably meant for *me* to check off. Why is it *my* job to
figure all of this out rather than the submitter meatbag's?

I do *not* have obligations towards spew-hose-pointing meatbags. Point
that hose at your own backyard at your peril.

If you *actually manage* to get the clanker to filter out *all* of the
spew without fail itself, my only win condition is gone. But it is not
yet that time. So at least have the decency to check for the spew
yourself, rather than telling the clanker to put checkboxes in the PR
descriptions telling *me* to check for it.
</details>
2026-03-05 22:37:50 +09:00
Dean Herbert
bb289363a2 Update resources 2026-03-05 14:58:12 +09:00
Denis Titovets
28a18f768c Use web localisation on LeaderboardScopeSelector to match osu-web (#36814)
- regressed in
1f052bb195
- also see https://github.com/ppy/osu/pull/34074#discussion_r2211853662

did it by analogy with
a0ecbd7c87/osu.Game/Overlays/BeatmapSet/Scores/NoScoresPlaceholder.cs (L27-L49)

| master | pr | `osu-web` |
|-|-|-|
| <img width="1625" height="188" alt="osu_2026-03-04_18-26-44"
src="https://github.com/user-attachments/assets/9bd2690a-2ddf-4f03-9ca6-22003efbd895"
/> |
![osu_2026-03-04_18-36-12](https://github.com/user-attachments/assets/1498c4a9-7188-4e97-a6bd-3553b9befa5e)
| <img width="990" height="139" alt="Снимок экрана 2026-03-04 182558"
src="https://github.com/user-attachments/assets/1bf14bee-3097-478d-936b-6ecb7d912be2"
/> |

---------

Co-authored-by: Dean Herbert <pe@ppy.sh>
2026-03-05 13:40:31 +09:00
Denis Titovets
45945e3bb1 Return tooltips to footer buttons on MultiplayerMatchSongSelect (#36817)
- reported by @Loreos7 in
https://discord.com/channels/188630481301012481/188630652340404224/1478809956076491006
- probably regressed in https://github.com/ppy/osu/pull/36752
2026-03-05 13:01:33 +09:00
Dean Herbert
cab50e94c8 Reduce online user list panel churn on initial display (#36811)
Yes this is a funny way of doing it, but it works and is better than
what we have for the initial release.

Don't expect this to stay around forever.
2026-03-05 00:44:03 +09:00
Dan Balasescu
ae032622ac Unimplement legacy compatibility method (#36812)
The spectator server invokes both legacy and non-legacy methods to keep
compatibility with older clients:


fe0dad3245/osu.Server.Spectator/Hubs/Multiplayer/Matchmaking/Queue/MatchmakingQueueBackgroundService.cs (L292-L299)

I thought that it'd be a good idea to implement the legacy method here,
but it turns out to not be such a good idea because it means ranked-play
clients will assume they're receiving a quick play invitation.
2026-03-05 00:31:43 +09:00
Dean Herbert
a0ecbd7c87 Merge pull request #36798 from bdach/tags-broken
Fix a bunch of breakage around user tags
2026-03-04 21:16:38 +09:00
Dean Herbert
459d5e8a45 Merge branch 'master' into tags-broken 2026-03-04 19:50:33 +09:00
Bartłomiej Dach
9b55e1dfdb Only show user tags above threshold by default 2026-03-04 10:34:35 +01:00
Bartłomiej Dach
4c873787c1 Merge pull request #36752 from peppy/song-select-v1-salute
Remove remnants of song select v1 and move v2 to final resting location
2026-03-04 10:27:07 +01:00
Bartłomiej Dach
43af89f75c Remove outdated xmldoc 2026-03-04 08:44:31 +01:00
Bartłomiej Dach
f74a21c1e1 Merge branch 'master' into song-select-v1-salute 2026-03-04 08:39:58 +01:00
Bartłomiej Dach
a028f0ba4a Fix loose spacers at top of leaderboard score context menu (#36799)
Closes https://github.com/ppy/osu/issues/36777.
2026-03-03 23:40:08 +09:00
Bartłomiej Dach
1aa42a73b1 Fix code quality 2026-03-03 13:38:23 +01:00
Bartłomiej Dach
d9e182230d Add bool flag for checking tag vote threshold & utilise as required
Closes https://github.com/ppy/osu/issues/36453.

My omission was in assuming that web was going to start filtering out
the tags below the threshold from API responses, which is not the case.

Whether or not the consumers want or not to display tags below threshold
is subjective. I figured that the matchmaking card tooltip might want to
display below threshold but I dunno.
2026-03-03 13:03:33 +01:00
Bartłomiej Dach
65b49137ee Move constant closer to helper 2026-03-03 13:03:33 +01:00
Bartłomiej Dach
2f96e96576 Migrate realm populating online lookup source to use helper method for retrieving tags
To reduce logic duplication.
2026-03-03 13:03:27 +01:00
Bartłomiej Dach
e64e0f0ca9 Fix beatmap set overlay not showing user tags at all
Regressed in 60d9c358b8.

In general an `APIBeatmap`'s `BeatmapSet` need not be present. In the
usage site of `osu.Game.Overlays.BeatmapSet.Info`, `Beatmap` and
`BeatmapSet` were actually two separate bindables. Moving the logic to a
helper, and therefore implicitly moving `BeatmapSet` from tracking said
separate bindable to instead refer to `Beatmap.BeatmapSet` broke this.
2026-03-03 12:44:29 +01:00
Bartłomiej Dach
d37f59430b Merge pull request #36796 from Joehuu/standardise-play-favourite-order
Standardise display order of playcount / favourites
2026-03-03 11:22:51 +01:00
Bartłomiej Dach
d748470754 Merge pull request #36795 from peppy/carousel-scrollbar-state-capture
Fix song select carousel state capture
2026-03-03 10:40:25 +01:00
Bartłomiej Dach
2539bd90fa Merge pull request #36794 from peppy/fix-tooltips-not-displaying-custom
Fix offset slider no longer showing explanatory tooltip correctly
2026-03-03 10:32:15 +01:00
Dean Herbert
317be21569 Final attempt to make copy labels work 2026-03-03 17:24:29 +09:00
Dean Herbert
b1044b6b2d Update framework 2026-03-03 17:20:14 +09:00
Dean Herbert
c28c64940a Move v2 files to final location
This contains only renames and namespace updates.
2026-03-03 16:45:15 +09:00
Dean Herbert
b4f40639a1 Add special not regarding collection dropdown 2026-03-03 16:45:15 +09:00
Dean Herbert
7e7421e1ea Update TestSceneDeleteLocalScore to use newer leaderboard 2026-03-03 16:45:14 +09:00
Dean Herbert
1a8d2855d4 Remove all song select v1 files
This contains no code changes (that would need review), only file
deletion and extraction where required in a few odd cases.
2026-03-03 16:45:14 +09:00
Dean Herbert
54a1417dd8 Move leaderboards classes to gameplay namespace for now 2026-03-03 16:45:13 +09:00
Dean Herbert
ce542b03db Move components only used by beatmap set overlay local to namespace 2026-03-03 16:45:13 +09:00
Dean Herbert
f20d182775 Update filter matching tests to be independent of old song select classes 2026-03-03 16:45:12 +09:00
Dean Herbert
a7e6286396 Fix a few remaining references to old song select 2026-03-03 16:45:12 +09:00
Dean Herbert
521a40d860 Remove now unused footer buttons 2026-03-03 16:25:58 +09:00
Joseph Madamba
564b6ebd0c Add tooltips to beatmap info overlay statistics 2026-03-02 22:53:09 -08:00
Joseph Madamba
7dfbab212e Add nominations statistic to beatmap info overlay
See 2198adfa68/resources/js/beatmapsets-show/header.tsx (L116-L123).
2026-03-02 22:47:10 -08:00
Joseph Madamba
b831bcbcd4 Change beatmap card play count and date icon to solid to match web 2026-03-02 22:40:16 -08:00
Joseph Madamba
c372f9b8f5 Standardise display order of playcount / favourites 2026-03-02 22:37:32 -08:00
Dean Herbert
b0759ff34d Fix song select carousel state capture
Closes #36776.
2026-03-03 15:36:46 +09:00
Dean Herbert
d60a12a351 Fix ordering of skins not working as expected (#36772)
As mentioned
[here](https://github.com/ppy/osu/pull/11536#issuecomment-3977314909).

Realm ordering is weird/broken so let's just do it at our end.
2026-03-03 14:03:55 +09:00
Dean Herbert
d27d0f40c1 Switch multiplayer to use song select v2 (#36747)
Tests pass and seems to work. Need to do a bit more self-testing for
higher confidence, but in theory..

Closes https://github.com/ppy/osu/issues/34035.

---------

Co-authored-by: Bartłomiej Dach <dach.bartlomiej@gmail.com>
2026-03-03 13:41:48 +09:00
Dean Herbert
ee9f8d5e92 Fix custom tooltip formats no longer displaying in settings
Closes #36793.
2026-03-03 12:59:23 +09:00
Bartłomiej Dach
9c8dfaf386 Apply more sanity checks when handling files from archives 2026-03-02 13:11:01 +01:00
Linus Genz
033e13cb3b Fix song select navigation with page up/down (#36293)
Resolves #36099 

This PR fixes keyboard navigation in the beatmap select carousel for
lazer by implementing page-wise traversal with the Page Up and Page Down
keys and changing it from only scrolling to actually selecting items.

**Changes:**
- Added handling for `TraversalType.Page` in the keyboard traversal
switch.
- Implemented `traverseKeyboardPage(int direction)` method to move the
selection by approximately one "page" of visible items, accounting for
partially obscured items like the search bar. Also it does not wrap
around (like the current PageUp/Down functionality).
- Added new key bindings:
    - `PageUp` → SelectPreviousPage
    - `PageDown` → SelectNextPage

The code may be very explicit for the scroll logic with the page keys,
so I would appreciate some feedback when the PR is reviewed.
The naming of the keybinds may need to be adjusted. `Next page` and
`previous page` may be somewhat misleading.

**Behavior after the change:**
- Pressing Page Up/Down now moves the selection by a page of items.
- After navigating, pressing Left/Right selects the navigated song
instead of moving relative to the previous position.

**See:**
https://www.youtube.com/watch?v=JXmKAhhKiCc

---------

Signed-off-by: Linus Genz <linuslinuxgenz@gmail.com>
Co-authored-by: Dean Herbert <pe@ppy.sh>
2026-03-02 02:08:19 +09:00
Krzysztof Gutkowski
b88cba0829 Refactor TestSceneScreenFooter to test entire OsuScreens (#36718)
Part of the `ScreenFooter` refactor, which intends to move the footer
content handling to `OsuScreen`. This commit updates the `ScreenFooter`
test to operate on entire `OsuScreen`s, in order to better test the
entire flow of pushing a screen, and having it create and add its own
content to the footer.

This should be 80-90% identical to the original test case structure
wise, just that instead of manually manipulating the footer with
`SetButtons()`, various screens with the appropriate buttons are being
moved around the screen stack.

Additionally this adds some more tests handling common use cases, as
well as removes `TestLoadOverlayAfterFooterIsDisplayed()`, since as far
as I understand the behaviour described in it doesn't actually happen in
production code. From what I can see, Screens instantiate their overlays
in `load()`, and then register them in `LoadComplete()`. There seems to
be no case where a `ShearedOverlayContainer` is created in the middle of
a screen's lifecycle.
2026-03-01 22:36:07 +09:00
Denis Titovets
9c489aacf8 Use better text structure for ScreenshotSaved notification (#36701)
reads a bit better when filename isn't in main text

also added "click to view" text by analogy with `LogsExportFinished`

| master | pr |
|-|-|
| <img width="336" height="114" alt="image"
src="https://github.com/user-attachments/assets/2555390c-1299-43ae-9be5-cb8d091b3387"
/> | <img width="336" height="108" alt="image"
src="https://github.com/user-attachments/assets/a8f18d9f-fa11-4d8f-82af-c88b0f82576c"
/> |
2026-02-28 22:58:07 +09:00
Krzysztof Gutkowski
105342e5bf Migrate sheared overlay tests to ScreenTestScene (#36736)
Part of the screen footer refactor.

Once footer content is being managed by `OsuScreen`, the current tests
which simply create the tested overlay and `ScreenFooter` in a container
will no longer work.

This PR refactors them to use `ScreenTestScene` with the setup being
creating a dedicated testing `OsuScreen` which does the bare minimum to
create the tested overlay and necessary components (eg.
`FooterButtonFreeModsV2` for `TestSceneFreeModsOverlay`).

Most of the changes here can be described as
`%s/<...>Overlay/screen.Overlay/g`, with some minor touchups as
necessary, given that we're now testing a more complete flow which
checks more things that were previously not handled by the tests.

## [Move footer to front in
ScreenTestScene](f8740e0403)

Self-explanatory. Without it the footer would show below the actual
overlay, breaking tests depending on manual input. For the sake of tests
not breaking in CI, both #36718 and this have this included - would
prefer the former to be merged first since it was already reviewed
there.

## `TestSceneModSelectOverlay`

There were a few tests (`TestColumnHidingOnIsValidChange`,
`TestColumnHidingOnTextFilterChange`, and
`TestHidingOverlayClearsTextSearch`) that would create a custom overlay
instance instead of the globally provided one. I've tested both and the
tests run fine with the default overlay, so they're now using that
instead.

## `TestSceneFreeModSelectOverlay`

Updated to use footer v2.

---------

Co-authored-by: Dean Herbert <pe@ppy.sh>
2026-02-28 04:22:58 +09:00
Dan Balasescu
c72b6412ea Add pool type to matchmaking room invited event (#36765)
Rebase of https://github.com/smoogipoo/osu/pull/193

Going forward, the client will have to know the type of pool being
invited to so that it can enter the appropriate screen when clicking the
notification.

Unfortunately, SignalR does not support overloading methods, or even
adding parameters to them, so this PR deprecates the
`MatchmakingRoomInvited` event and adds its replacement
`MatchmakingRoomInvitedWithParams` with a complex `invitation` parameter
that we _can_ extend in the future if required (such as potentially
adding the name of the pool).

This also prepares the notification by extracting some code to a
`Complete` method receiving said `invitation` parameter. This part of
code will be further modified to enter the correct screen:


0a4018045b/osu.Game/Screens/OnlinePlay/Matchmaking/Queue/QueueController.cs (L200)

In particular, I have tested that new clients continue to work with the
old server (dev.ppy.sh) in quick play

|         | Old Server           | New Server  |
| ------------- |:-------------:| :-----:|
| Old Client      | 🟢  | 🟢 |
| New Client      | 🟢  | 🟢 |
2026-02-28 00:42:44 +09:00
Dean Herbert
2e659a79ec Merge pull request #36767 from LiquidPL/cleanup-online-play-tests
Remove redundant footer from `TestScenePlaylistsRoomSubscreen`
2026-02-27 20:13:21 +09:00
Krzysztof Gutkowski
5131b188d1 Remove redundant footer from TestScenePlaylistsRoomSubscreen
`ScreenTestScene` already provides one.
2026-02-27 11:12:30 +01:00
Bartłomiej Dach
7e2771c3f0 Improve usability of sample bank toggles (#36753)
- [x] Depends on https://github.com/ppy/osu/pull/36741 for merge
conflict avoidance

RFC, cc @OliBomby

## [Adjust behaviour of automatic bank assignment during
placement](547f55e9b3)

Diatribe time!

This is fallout of the discussion about auto bank in
https://github.com/ppy/osu/issues/36705.

Auto bank in lazer as written before this commit is confused. On stable,
auto bank is closer to "no bank", as in "go look up the current sample
timing point, get the bank of that, and use that". lazer has no timing
points anymore, but people still want auto bank. So what do?

Auto bank for normal samples is somewhat sane still. It only works
during placement, and will just copy the normal bank of the previous
object - if one exists. That said, one *might not* exist, but the
resulting object will still have its normal sample created with
`editorAutoBank: true`. That is largely cosmetic and without
consequences, but this commit fixes that.

Auto bank for *addition* samples, however... Hoo boy.

- For placed objects, auto bank means "take the normal sample, read its
bank, and use that". Simple enough, right?
- Hoooooowever. During placement, auto bank before this commit used to
mean "look at the *previous object*, check if it has an addition sound
and then use its bank, if not use *the previous object's* normal sample
and then use its bank" which is a completely different thing with its
own implications. Like, say, what happens if the previous object uses
the auto addition bank too? What should be copied over? Should it be the
notion of "auto bank" in that the addition bank should match the normal
bank, or should it be the literal bank that the previous object is
using?

This change attempts to define this unambiguously. "Auto additions bank"
means "the same bank as the normal bank of this object", full stop.

## [Do not touch sample toggle state if there are no selected
objects](052cde5987)

Fixes issue described in
https://github.com/ppy/osu/issues/36705#issuecomment-3953917163 wherein
opening a sample popover will disable addition bank toggles and toggle
off all addition samples.

---------

Co-authored-by: Dean Herbert <pe@ppy.sh>
2026-02-26 15:18:29 +09:00
Bartłomiej Dach
5174d8b918 Fix changing normal sample bank via keyboard hotkeys not updating addition bank if set to auto (#36741)
Closes https://github.com/ppy/osu/issues/36703.

It was only broken via keyboard hotkeys and not via the sample popover
because the sample popover has a separate copy of the logic that didn't
have the bug. Compare:


13aeed15f9/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs (L473-L475)

I considered splitting a helper to have one copy of the logic but it's
not very simple to do compared to a two-liner fix so I gave up.
2026-02-26 02:47:45 +09:00
Bartłomiej Dach
32d10406c9 Merge pull request #36751 from peppy/fix-hidden-controls-settings
Fix input settings being interactive even when collapsed
2026-02-25 10:38:10 +01:00
Neti
0dfb362b2d Use HotkeyDisplay for toolbar buttons (#36750)
Uses `HotkeyDisplay` for toolbar button tooltips rather than
`SpriteText`

<img width="433" height="160" alt="image"
src="https://github.com/user-attachments/assets/d74c4dd2-27fd-4e7c-881e-3c6152982dd6"
/>

---------

Co-authored-by: Dean Herbert <pe@ppy.sh>
2026-02-25 16:31:36 +09:00
Dean Herbert
047ea7c09d Fix hidden settings flow content still being interactive
`AlwaysPresent` is a code smell here. Rather than doing this, let's just
using masking as we usually do.

Closes https://github.com/ppy/osu/issues/36748.
2026-02-25 14:35:24 +09:00
Dean Herbert
cb597c4120 Fix flow animating its display initially when it shouldn't 2026-02-25 14:34:50 +09:00
Dean Herbert
99ab245cf0 Tidy up how SettingsSubsection headings are created
Previously we were always making header content in the base class then
overwriting it, which felt ick.
2026-02-25 14:34:49 +09:00
Dean Herbert
ee8d99034c Rename one more missed song select v2 class 2026-02-25 02:18:18 +09:00
Dean Herbert
5b1d4ceb9d Merge pull request #36746 from bdach/fix-thing-again 2026-02-24 21:48:06 +09:00
Bartłomiej Dach
ccb0224a1b Attempt to fix "copy labels from issues" workflow permissions again
It's still broken:

https://github.com/ppy/osu/actions/runs/22310482792/job/64540909415

and I'm not sure what else it could be other than this, so I'm giving it
one more honest try before I throw up hands.

For better or worse,
https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#permissions
explicitly lists that the `pull-requests` permission allows tagging PRs,
so I'm holding out hope that this is it. Still terrible marks for
documentation here, both to the github action, as well as everything to
do with `GITHUB_TOKEN`.
2026-02-24 13:34:06 +01:00
Bartłomiej Dach
5602281bd9 Merge pull request #36745 from peppy/song-select-cleanup
Various song select class cleanup
2026-02-24 13:24:18 +01:00
Dean Herbert
fabce18552 Remove V2 suffix from migrated classes 2026-02-24 20:23:29 +09:00
Dean Herbert
cb12d35d8f Combine old classes to provide better starting point for migration 2026-02-24 20:23:28 +09:00
Dean Herbert
efc9a27d2d Remove no longer used class 2026-02-24 20:23:28 +09:00
Bartłomiej Dach
e2cbfb970e Merge pull request #36738 from peppy/user-button-animation-fix
Fix transient user stats animation changing speed after first display
2026-02-24 08:19:02 +01:00
Dean Herbert
f39615a374 Move spacing local to usages 2026-02-23 15:59:09 +09:00
Dean Herbert
4916c87f14 Move toolbar button text local to single usage in ToolbarUserButton 2026-02-23 15:58:57 +09:00
Dean Herbert
a09489d4c0 Move transformation logic local to transient drawable 2026-02-23 15:47:01 +09:00
Dean Herbert
d139f5997c Update resources 2026-02-23 03:37:40 +09:00
Joseph Madamba
29a39cbfb7 Fix spectator player cells not having initial shadow edge effect set (#36729)
- Fixes https://github.com/ppy/osu/issues/36727

Copies/sets the non-maximised edge effect initially:


c144cf188a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.Cell.cs (L83-L88)
2026-02-21 23:19:32 +09:00
Arthur Araujo
c144cf188a Fix reversing straight perfect curve sliders positioning them weirdly (#36698) 2026-02-20 16:57:42 +01:00
Bartłomiej Dach
1e8b9a7639 Adjust nightcore sample playback to not stop randomly and be closer to stable (#36702)
- Closes https://github.com/ppy/osu/issues/30293
- Fixes https://osu.ppy.sh/community/forums/topics/2179339?n=1

Aside from fixing the off-by-one error that I mentioned in
https://github.com/ppy/osu/issues/30293#issuecomment-2413801663, this
also:

- Brings back the behaviour wherein if timing points are arranged very
weird and nightcore would play e.g. two first beats in a timing point
back-to-back, the second timing point is silent.
- Brings back the behaviour wherein the finish sample only plays if
`OmitFirstBarLine` on the timing point is disabled.

However:

- This does not bring back the behaviour wherein hat samples only play
if the slider tick rate is even because that only kind of makes sense in
common time, and if common time is mixed with waltz time or other time
signatures, it just gets weird.
- Also stable has zero attempt for compensating for waltz time anyway,
lazer's behaviour is bespoke, so that is not going to match any way you
cut it.

My testing procedure essentially consisted of getting stable to log when
it was playing nightcore samples and cross-checking the first 30sec or
so of https://osu.ppy.sh/beatmapsets/534385#osu/1131956 (check out the
timing of that beatmap, for something ranked it is DEEPLY messed up).

I guess I can add test cases if deemed required but I already wasted
much more time than I would have liked here...
2026-02-20 12:53:12 +09:00
Bartłomiej Dach
69c2747883 Merge pull request #36696 from peppy/multiplayer-freestyle-select
Migrate multiplayer freestyle select screen to use `SongSelectV2`
2026-02-19 12:16:51 +01:00
Bartłomiej Dach
d3b48364c5 Merge branch 'master' into multiplayer-freestyle-select 2026-02-19 10:55:30 +01:00
Bartłomiej Dach
9ae99648fb Merge pull request #36694 from peppy/freestyle-select-update
Migrate playlist freestyle select screen to use `SongSelectV2`
2026-02-19 10:55:07 +01:00
Bartłomiej Dach
f9f927fa45 Disable footer buttons on freestyle song select
- The mods button should not be visible because it is not hooked up
  properly to actually work with selecting user mods. As written it
  would show (and feign the ability to modify) required mods on the
  playlist item.

  You could probably adjust the logic to make user mods controllable via
  this button but this is simpler for now.

- The random and options buttons could perhaps remain, but they are of
  very limited use on this screen anyway.

The previous iteration disabled all footer buttons altogether, so I'm
just following precedent here mostly.
2026-02-19 10:07:51 +01:00
Dean Herbert
1153d17153 Disallow use of scoping button when at freestyle song select 2026-02-19 01:47:03 +09:00
Bartłomiej Dach
e15cb08b44 Privatise setter 2026-02-18 14:42:04 +01:00
Bartłomiej Dach
25d630898b Merge pull request #36695 from peppy/loading-layer-block-non-positional
Add ability for `LoadingLayer` to block all keyboard input
2026-02-18 14:40:31 +01:00
Dean Herbert
48544733c3 Migrate multiplayer freestyle select screen to use SongSelectV2 2026-02-18 20:50:17 +09:00
Dean Herbert
d33a6d5512 Add ability for LoadingLayer to block all keyboard input 2026-02-18 20:50:14 +09:00
Dean Herbert
110f11fa5e Fade out header line for now to avoid visual overlap 2026-02-18 20:48:17 +09:00
Dean Herbert
fb8c228d32 Add test coverage of actual user flows involving freestyle select screen
Of note, this test scene still doesn't use the new footer properly.
Should these even be here? Maybe it's better to leave these real world
flows to a `Navigation` style test so we're not dealing with weird test
setups that kinda do something but don't... or maybe do it in three
different ways (two footers present, three back buttons etc.)

Dunno. But this kind of covers what I want (except it doesn't cover the
new footer usage).
2026-02-18 20:48:16 +09:00
Dean Herbert
93d1a61dc2 Migrate playlist freestyle select screen to use SongSelectV2 2026-02-18 20:30:14 +09:00
Dean Herbert
83587e2265 Add ability for LoadingLayer to block all keyboard input 2026-02-18 20:01:38 +09:00
Dean Herbert
f15e086bb0 Add "spin" keyword for menu cursor rotation 2026-02-18 17:21:03 +09:00
Bartłomiej Dach
9fa78123a6 Merge pull request #36687 from peppy/legacy-key-counter-skinnable-font
Fix legacy key counter not using skin's font
2026-02-18 08:26:21 +01:00
Dean Herbert
7445efaecd Ensure state resets back to initial display on rewind 2026-02-18 02:25:09 +09:00
Dean Herbert
86ab507e2e Use default overlap of 1
Matches closer to stable spec.
2026-02-18 02:22:00 +09:00
Dean Herbert
cc9a5ede29 Update resources 2026-02-17 16:44:26 +09:00
Dean Herbert
6e03d5b00e Show initial key placeholder / trigger name using non-skinned font
In stable, there was support for mixed fonts (some coming from the
user's skin, with fallback missing characters generated from TTF
backend).

We don't really have support for this so this is a simple in-between for
the time being.
2026-02-17 16:02:13 +09:00
Dean Herbert
71583bc06f Add basic support for scoreentry- skinnable text 2026-02-17 15:54:31 +09:00
Vanni
94cec71502 Prevent tagging beatmaps when played with conversion mods (#36684)
Closes #36553.
Supersedes #36614.

As discussed in #36614, the only exception to this is `ModClassic`.
2026-02-17 14:49:59 +09:00
Bartłomiej Dach
42bf25442b Fix osu! editor composer scale handles not updating correctly when selected object is changed (#36681)
Before:


https://github.com/user-attachments/assets/d0a0373d-2d46-48a9-9ea5-bac82a612f32

After:


https://github.com/user-attachments/assets/f5785b54-d7fc-4ce0-86b0-60c96ff22bc3

---

Closes https://github.com/ppy/osu/issues/36677.

Kinda shocking this went by unnoticed for this long.
2026-02-17 00:50:16 +09:00
Bartłomiej Dach
fc4e29793d Use directory of last-selected sample as initial directory in sample set add controls (#36680)
Closes https://github.com/ppy/osu/issues/36470.

Applied directly to relevant control for now.
2026-02-17 00:46:30 +09:00
Krzysztof Gutkowski
8e26cf4e1b Restore previous beatmap when leaving scoped mode (#36582)
Resolves #36288.

If the current selection is still available after leaving scoped mode,
it's left as is. If it's not, the selection from before entering scoped
mode is restored.


https://github.com/user-attachments/assets/b1ac3de1-7c7f-4949-82a9-1dd0459f3f61

---------

Co-authored-by: Bartłomiej Dach <dach.bartlomiej@gmail.com>
2026-02-17 00:40:29 +09:00
Bartłomiej Dach
810edebe87 Fix extra lives in Easy mod potentially getting reapplied during gameplay (#36678)
Closes https://github.com/ppy/osu/issues/36676.
2026-02-16 19:02:59 +09:00
Arthur Araujo
2f459dd94e Preserve bookmarks when creating a new difficulty from scratch (#36675)
Closes #33395

Copies the bookmarks from `referenceWorkingBeatmap` while creating a new
difficulty from scratch. I adapted the tests in
`TestSceneEditorBeatmapCreation` to include the bookmark checks.

---------

Co-authored-by: Dean Herbert <pe@ppy.sh>
2026-02-16 12:50:04 +09:00
Dean Herbert
87f323a2e5 Update resources 2026-02-15 23:25:46 +09:00
Bartłomiej Dach
94d9de955f Add explanations for scoring mode used in ranked & total score tooltips (#36663)
Because people get confused by how this works.

Shows on results screen where the post-play statistics updates go.


https://github.com/user-attachments/assets/ef3a91d1-86dd-4029-8f0f-bdf0b727ca6c
2026-02-14 01:24:42 +09:00
Neti
860427e4ae Fix corner radius for user tag glow (#36659)
Fixes incorrect glow corner radius around user tag buttons

Before:
<img width="267" height="123" alt="image"
src="https://github.com/user-attachments/assets/3e602e84-bb13-46f7-942c-85ddf3954946"
/>
After:
<img width="227" height="102" alt="image"
src="https://github.com/user-attachments/assets/4dca2e3d-80e2-4b6c-988e-d14f371cfbe8"
/>
2026-02-13 14:48:07 +09:00
Bartłomiej Dach
5afd6c6835 Add user role to MultiplayerRoomUser (#36652)
- Related to https://github.com/ppy/osu-server-spectator/issues/406

Adding this field to this model has several vague reasons that I can't
fully formulate yet, but I can't really see myself going forward
*without* this.

- People were very excited about having referees displayed on the room
participants' list, and so adding the referees as real
`MultiplayerRoomUser`s helps this. Having the role could even be used
client-side to show a special icon or other status on the participants
list. (Which isn't done yet, could be as an aesthetic follow-up after
the basics are in place.)
- Server-side, having this field is convenient for things like
permission checks or just plain logic, as with two hubs you just need to
do different *stuff* on a `MultiplayerRoomUser`.
2026-02-12 00:57:03 +09:00
Krzysztof Gutkowski
50426eb3c8 Refactor UpdateableTeamFlag for use on team overlay (#36286)
Part of #32584.

Very much inspired by the respective component for displaying profile
pictures on the user overlay

* allow disabling interactivity/tooltips
* add option to show placeholder on null team instead of hiding
component entirely
* move setting corner radius out to respective parent components to
allow for easier overriding
2026-02-10 23:18:36 +09:00
Dean Herbert
56bc80ffb9 Update resources 2026-02-10 22:08:07 +09:00
Dean Herbert
c59e5bf335 Merge pull request #36639 from bdach/actually-all-valid-hit-results
Refactor hit result methods on `Ruleset`
2026-02-10 17:07:04 +09:00
Bartłomiej Dach
655d725f0c Perform extra checks when loading rulesets (#36641) 2026-02-10 13:34:43 +09:00
Bartłomiej Dach
15c49a729e Adjust contract of Ruleset.GetValidHitResults() to always enumerate all results used 2026-02-09 12:48:53 +01:00
Bartłomiej Dach
3678ea0d0f Publicise Ruleset.GetValidHitResults() 2026-02-09 12:00:30 +01:00
Bartłomiej Dach
dbce8886f8 Rename Ruleset.GetHitResults{ -> ForDisplay}() 2026-02-09 12:00:15 +01:00
Dean Herbert
a1136c3cea Update framework (#36637) 2026-02-09 19:39:30 +09:00
Bartłomiej Dach
b613c1dcd5 Merge pull request #36635 from peppy/update-production-endpoints
Update production endpoints in an attempt to fix Russian player connections
2026-02-09 09:13:07 +01:00
Dean Herbert
cae8d3f4d4 Update production endpoints in an attempt to fix Russian player connections 2026-02-09 13:29:21 +09:00
Dean Herbert
f39b310eb5 Add missing wait step to fix flaky test in TestSceneModPresetColumn
See
https://github.com/ppy/osu/actions/runs/21799682256/job/62893024859?pr=36627
2026-02-09 12:50:40 +09:00
maarvin
efd4d12c21 Add info about damage dealt to ranked play user state (#36627)
Adds a `DamageInfo` property to `RankedPlayDamageInfo` to be used by the
result screen.

The issue this is trying to solve is that once the result screen
initializes, the HP value of each player has already been updated in the
room state so the previous values are no longer accessible. Doing this
without the state exposing it would require some kinda setup to keep the
previous MatchState's HP values around on the client which would
introduce a lot of unnecessary weirdness.
2026-02-09 12:39:27 +09:00
Denis Titovets
60d98f0afd Improve adjusting mods settings values with keyboard (#36090)
- closes https://github.com/ppy/osu/issues/36016

Co-authored-by: Dean Herbert <pe@ppy.sh>
2026-02-09 11:35:01 +09:00
failaip12
01982030ec Allow binding left/right modifier keys separately for gameplay bindings (#36585)
Addresses #36583.

---------

Co-authored-by: Dean Herbert <pe@ppy.sh>
2026-02-09 10:15:34 +09:00
Dean Herbert
bb83f3dcf2 Fix unreliable async loading due to incorrect single-child assumption
See https://github.com/ppy/osu/actions/runs/21807684945/job/62913744850.
2026-02-09 10:05:13 +09:00
Joseph Madamba
0a4497db3c Fix local beatmap metadata not being cleared when previously selected beatmap is online (#36632)
- Closes https://github.com/ppy/osu/issues/36584

The last two commits could be either fixes to the issue above, but in a
code quality perspective, the scheduler in `setLink()` seems unnecessary
as the other set methods don't have it (other than making it run last)
and the other commit is self explanatory.
2026-02-09 09:55:36 +09:00
Denis Titovets
bb04a18060 Localise "lounge" & "quick play" in ButtonSystem (#36220) 2026-02-09 01:52:43 +09:00
Bartłomiej Dach
7263551aa8 Improve handling of account registration errors (#36600)
## [Specify `Accept` header in registration
request](28edb788f7)

The lack of it meant that in specific scenarios web would respond with a
chunk of HTML instead of JSON.

## [Allow showing registration error message even if no redirect is
given](6ad49941ff)

There are scenarios where this can happen, and if it did, previously the
strict requirement to have both would cause the specific message to be
discarded and replaced with the generic "something happened" one.
2026-02-09 00:51:31 +09:00
Joseph Madamba
239951eda9 Fix seeking to previous bookmark not working when song is playing (#36616)
- Closes https://github.com/ppy/osu/issues/35389

Same as:

2efe0c95e6/osu.Game/Screens/Edit/Editor.cs (L1173-L1180)

There's also seeking hit objects and sample points, but the seeks are
relatively close to each other and probably useless when playing(?). If
we want to make those cases not stuck at the same point in time, I
believe the leniency should be lower than 1000 ms.

With the above, that is why I just copy-pasted the code, as we may want
to have different leniencies.

Edit: forgot the automated label thing, will not label next time
2026-02-08 23:57:22 +09:00
SupDos
5cdf07c7e1 Make grouped friend notifications Transient and not important (#36620)
Grouped notifications for more than 1 person were added in
https://github.com/ppy/osu/pull/36180 but it looks like they forgot to
add the Transient and IsImportant flags, which means the grouped
notifications would still stay in the notification list/flash the
taskbar.

Before:


https://github.com/user-attachments/assets/8a34bbc0-2b5c-4086-b2ee-1daa6d1e6e10



After:


https://github.com/user-attachments/assets/03c25ba6-7c8e-464c-bbb1-688ab9da6bb6
2026-02-08 23:16:39 +09:00
Bartłomiej Dach
2efe0c95e6 Fix "copy labels from issues" workflow not having the appropriate permissions to do what it claims to do (#36602) 2026-02-06 20:22:53 +09:00
Denis Titovets
a616fa61c9 Localise some more strings in settings (#36590) 2026-02-06 19:23:02 +09:00
Ботников Максим
e77e08c049 Localize "Effect, Master, Music" in VolumeOverlay (#36594) 2026-02-06 19:22:06 +09:00
Michael Middlezong
5231cfa1c7 Change difficulty range slider colors to match star rating more closely (#36564)
my attempt at #36452: changes difficulty range slider in song select V2
to use the new star difficulty text gradient colors from #36292.

closes #36405


https://github.com/user-attachments/assets/134ace54-a8f8-4024-a32e-f1604c868232

to match star rating more closely for sr <= 7.5 (where i don't think the
colors are so intense that they break harmony with the rest of the
design), i removed the Lighten by 0.4f on color update

also improved the transition from 7.9 to 8.0 which is quite abrupt in
the current version

https://github.com/ppy/osu/pull/33425#issuecomment-2951914385 is
relevant here
2026-02-06 19:15:57 +09:00
Denis Titovets
40b331654e Localise some strings on SSV2 (#36591) 2026-02-06 19:12:07 +09:00
Bartłomiej Dach
5e01dda411 Fix star rating no longer updating from mod setting changes after re-entering song select (#36601) 2026-02-06 19:00:20 +09:00
Ботников Максим
5d4bb1bd37 Localise "running" in DigitalClockDisplay (#36589) 2026-02-05 23:22:25 +09:00
Dan Balasescu
1230da33a5 Implement sorting and display styles for currently online users (#33649)
- Adds sorting and display styles.
- Saves sort/display modes to the config.
- Improves performance, especially on the 2nd+ time opening the overlay.


https://github.com/user-attachments/assets/e32b50d0-58a1-4eef-b18c-988fb497e545

---

Coming off some recent feedback in
https://github.com/ppy/osu/discussions/33426#discussioncomment-13431275,
I decided to take a bit of a detour and get a little bit more
functionality in.

Sorting by rank, although it should technically work, doesn't work right
now. This is because the osu!web API doesn't return user rank on
`/user/` lookups - it's only returned for the friends request. I'm
leaving this open as a discussion topic.
- We can make osu!web return the rank and osu! will require no further
changes to work correctly, or
- We can try to implement additional paths through
`osu-server-spectator` which would blow this PR out of proportion and is
best left for a task of its own.

For simplicity, I've re-implemented this display mostly as its own
component for now, lifting code from `FriendDisplay` which was recently
overhauled. These implementations should eventually be combined somehow
but that's dependent on:
1. Figuring out the styling - friends can display offline users for
which it makes no sense to display the "spectate" button.
2. Figuring out how to handle the different users/presence pathways.
It's mostly a code complexity issue.

---------

Co-authored-by: Dean Herbert <pe@ppy.sh>
Co-authored-by: Bartłomiej Dach <dach.bartlomiej@gmail.com>
2026-02-05 01:22:36 +09:00
Bartłomiej Dach
57222ea0c7 Merge pull request #36580 from peppy/fix-revert-button-wrong-size
Fix revert to default button not resizing correctly after changing languages
2026-02-04 07:48:26 +01:00
Dean Herbert
892e34b1f8 Add automation to copy labels from issue when PR is opened
https://github.com/marketplace/actions/copy-issue-labels
2026-02-04 14:37:35 +09:00
Dean Herbert
b8b6327624 Fix revert to default button not resizing correctly after changing languages
Closes #36519.
2026-02-03 18:17:53 +09:00
nil
85b8480e46 Fix two mod presets having key binding of 1 (#36563)
Fixes #36562 

Currently there exists a bug where the mod preset hotkeys will wrap
outside of the intended bounds.

Fix is making sure the preset index is < 10

Before:
<img width="486" height="358" alt="image"
src="https://github.com/user-attachments/assets/77f1ca9e-4fd0-4b29-b9f5-f53e652db42d"
/>

After:
<img width="1007" height="1295" alt="image"
src="https://github.com/user-attachments/assets/ca81cc39-2f86-462c-a26b-002aceed77f4"
/>
2026-02-02 00:42:49 +09:00
Dan Balasescu
9e9b5cfc50 Invert quick play queue backgrounding and canceling flow (#36247)
- Exiting while queueing will now background the search.
- The "queue in background" button changed to "stop queueing".

Exiting while in the "pending accept"/"waiting for players" states will
exit from the queue. There is also a small period during the in-room
state (where the matchmaking screen hasn't been pushed yet but "good
luck" is displaying) where the user cannot exit from the screen. I've
removed the exit confirmation dialogs to streamline the process and
align with this.


https://github.com/user-attachments/assets/8c172502-0624-42cd-ae0c-bb710068267c
2026-02-01 02:19:20 +09:00
Bartłomiej Dach
c9a50b40d0 Merge pull request #36387 from ArijanJ/cycle-skins
Add skin cycling with shortcuts for next and previous skin
2026-01-30 08:57:23 +01:00
Bartłomiej Dach
8c21a5ba06 Merge pull request #36536 from peppy/silence-timeouts
Fix unobserved timeouts still showing to user
2026-01-30 08:34:13 +01:00
Joseph Madamba
2ab7492759 Fix initial solo gameplay leaderboard position and color (#36496)
- Closes https://github.com/ppy/osu/issues/35743

Matches playlists. Multiplayer seems to not have it, but I think that's
expected (i.e. there's no past scores, so having the initial position be
"-" and no initial green color seems right).

| Before | After |
| --- | --- |
| ![Kapture 2026-01-27 at 15 49
29](https://github.com/user-attachments/assets/39ad2cda-87e4-49e4-a872-4588ec07cae1)
| ![Kapture 2026-01-27 at 15 47
30](https://github.com/user-attachments/assets/1b8f1e42-5468-4634-8397-ba455c453761)
|
| ![Kapture 2026-01-27 at 15 51
57](https://github.com/user-attachments/assets/29b0a4b9-551a-4ac0-9b83-c8eb853422ae)
| ![Kapture 2026-01-27 at 15 52
45](https://github.com/user-attachments/assets/7b4b4c9b-4353-4845-9be1-8467326d3f6b)
|
2026-01-30 15:45:02 +09:00
Dean Herbert
cf09ed3e0d Fix unobserved timeouts still showing to user
As reported in https://osu.ppy.sh/community/forums/topics/2174881?n=1.
2026-01-30 15:37:01 +09:00
Tim Oliver
a774f19aef Updated iOS 26 app icon assets with newer version (#36535)
After testing out the icon on device for a bit, we decided the osu! logo
was a little too large.

This PR replaces the app icons with a new version where the sizing of
the logo itself is aligned to Apple's recommended boundary.

<img width="198" height="169" alt="Screenshot 2026-01-30 at 2 33 24 PM"
src="https://github.com/user-attachments/assets/c66fb016-9c19-4ff5-814a-6f302ec459ca"
/>
2026-01-30 14:40:25 +09:00
Denis Titovets
14d8cf7275 Add localisation support for DirectorySelector (#36371)
- depends on https://github.com/ppy/osu-framework/pull/6700
- closes https://github.com/ppy/osu/issues/36340
- supersedes and closes https://github.com/ppy/osu/pull/36352

<img width="676" height="451" alt="image"
src="https://github.com/user-attachments/assets/4f11c761-175b-495a-8b24-16fb6c481a15"
/>

---------

Co-authored-by: Dean Herbert <pe@ppy.sh>
2026-01-29 19:13:02 +09:00
Bartłomiej Dach
7d476b4b7c Fix some text boxes no longer having borders (#36526)
Regressed in d6bf4fd90d.

One very visible instance of this regression is the login form.


https://github.com/user-attachments/assets/5ba10ac5-4cb1-49af-b55c-89cf58ca0b44

The `CommentEditor` usage was discovered with one of my favourite tricks
which is doing

```diff
diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs
index fefe776b01..c17cca726b 100644
--- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs
+++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs
@@ -42,6 +42,12 @@ public partial class OsuTextBox : BasicTextBox
             Margin = new MarginPadding { Left = 2 },
         };
 
+        public new bool Masking
+        {
+            get => base.Masking;
+            set => base.Masking = value;
+        }
+
         protected bool DrawBorder { get; init; } = true;
 
         private OsuCaret? caret;

```

and then looking for usages of the setter. That's all due diligence
you're getting here, I'm not auditing every single text box in the game.

And yes, the `CommentEditor` usage is OMEGA dodgy but the change applied
here is the only one that preserves its visual appearance. I'm not
putting in time to fix it.
2026-01-29 19:11:10 +09:00
Bartłomiej Dach
de97660fa4 Fix hitsounds becoming loud in editor after entering setup section (#36512)
Probably closes https://github.com/ppy/osu/issues/36492.

This is dumb but it's in large part my stupidity.

To begin with, the immediate direct offending call that causes the
observed symptoms is


a401c7d5e9/osu.Game/Screens/Edit/Components/FormSampleSet.cs (L296)

The reason why this "invalidation" affects sample volume is that in the
framework implementation, the call [removes the relevant sample factory
from the sample store which is an audio
component](5b716dcbef/osu.Framework/Audio/Sample/SampleStore.cs (L65-L72)).
In the process it also [unbinds audio
adjustments](5b716dcbef/osu.Framework/Audio/AudioCollectionManager.cs (L37-L38)),
which *would* have the effect of resetting the sample volume to 100%,
effectively (and I've pretty much confirmed that that's what happens).

Now for why this call sometimes does the right thing and sometimes
doesn't: Sometimes the call is made in response to an *actual* change to
the beatmap skin, which is correct and expected, if very indirect, but
sometimes it is made *over-eagerly* when there is no reason to recycle
anything yet.

One such circumstance is entering the setup screen, which will still
"invalidate" (read: remove) the samples, but the compose tab hasn't seen
any change to the beatmap skin, so when it is returned to, it has no
reason to retrieve the sample again, and as such it will try to play
samples which are for better or worse in a completely undefined state
because they're not supposed to be *in use* anymore.

Therefore, the right thing here would seem to be to take the
responsibility of invalidation from a random component, and move it to a
place that's *actually* correlated to every other component needing to
recycle samples, e.g. `EditorBeatmapSkin` responding to changes in the
beatmap resources via raising `BeatmapSkinChanged`.

Unfortunately, because of the structure of everything, this recycle
needs to go from targeted to individual samples, to nuking the entire
store. The reason for this is that `RealmBackedResourceStore` does not
provide information as to *what* resources changed, it just says that
*the set of them* did.

For the recycle to be smarter, `EditorBeatmapSkin` would need to know
not only which samples were added or replaced, but also which ones were
*removed*, so that users don't hear phantom samples that no longer exist
in the editor later. That would however be a lot of hassle for nothing,
so I just recycle everything here and hope it won't matter.

As to why I could only reproduce this on this one beatmap - I'm not
super sure. The failure does not seem to be specific to beatmaps, but it
may be exacerbated by certain patterns of accessing samples which means
that beatmaps with high BPM like the one I managed to reproduce this on
may just be more susceptible to this.

As a final note, I do realise that this is not fundamentally improving
the surrounding systems and it's still a pretty rickety thing to do.
It's still on the consumers to know and respond to the sample store
recycle and this is likely to fail if a consumer ever doesn't. That
said, I have no brighter ideas at this point in time that won't involve
me spending a week refactoring audio.
2026-01-29 19:10:48 +09:00
Bartłomiej Dach
5468215de8 Merge pull request #36523 from smoogipoo/fix-team-display
Fix multiplayer team display becoming inconsistent
2026-01-29 10:20:21 +01:00
Bartłomiej Dach
1885ab86f5 Make Difficulty Adjust and Target Practice mods incompatible (#36524)
Closes https://github.com/ppy/osu/issues/36490.

While I'm out here already taking heat for deleting mod combinations let
me do more of that.

The main problem with the mod combination here, again, is that the
application of the mods does not commute.

- The current behaviour is that TP is applied first, then DA. This is,
again, "enforced" by the mod select overlay implicitly enforcing order
of the mod instances in the global mod bindable to match the display
order of mods inside it.

Even this doesn't "work" correctly as is, because as the bug reporter
points out, if they throw on DA with no changes expecting the map's
default AR to be applied, it still gets halved. This is because DA works
in a way wherein if you don't touch the AR slider, DA does not touch AR.
Which means that the DA slider should *really* be at *half* of the map's
base AR by default in this case because TP is active. How do you program
this?

- The *alternative* behaviour would be that DA is applied first, then
TP. This in turn would mean that the effective range of AR adjustment
offered by DA when TP is active would be halved to [0, 5] (or [-5, 5.5]
with extended ranges). How do you program this?

The above is just client-side concerns, while leaving out the other
giant concern, which is "how do you get every single place in the game
that may want to apply mods to a beatmap to apply them *in the same
order*?". Then extend that to server-side components, then extend that
to every external library that may want to re-implement SR/PP
calculations, etc. etc.

One additional remark:
What the bug reporter *did not* say however, but I am saying, is that
there's an elephant in the room, and that is the Easy mod, which *also*
changes AR, but also *happens* to apply commutatively with Target
Practice simply because both mods are implemented to halve the AR, which
means that the order of application doesn't matter. If I were *really*
bent on being a bad guy and just deleting mod combinations
indiscriminately, I'd delete that one as well. But I'm not doing that.
2026-01-29 17:59:28 +09:00
Dan Balasescu
586b2987a7 Fix multiplayer team display becoming inconsistent 2026-01-29 17:01:52 +09:00
Bartłomiej Dach
9bf52034d7 Make Hidden and Freeze Frame mods incompatible (#36515)
Effectively closing https://github.com/ppy/osu/issues/21471 as a
wontfix.

The issue is demonstrated by the following test case:

<details>
<summary>patch</summary>

```diff
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFreezeFrame.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFreezeFrame.cs
index 31498295da..36b4fe5122 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFreezeFrame.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFreezeFrame.cs
@@ -55,5 +55,49 @@ public void TestSkipToFirstSpinnerNotSuppressed()
                 PassCondition = () => Player.GameplayClockContainer.GameplayStartTime > 0
             });
         }
+
+        [Test]
+        public void TestFreezeFrameAppliedBeforeHidden()
+        {
+            CreateModTest(new ModTestData
+            {
+                Mods =
+                [
+                    new OsuModFreezeFrame(),
+                    new OsuModHidden(),
+                ],
+                CreateBeatmap = () => new OsuBeatmap
+                {
+                    HitObjects =
+                    {
+                        new HitCircle { StartTime = 3000, Position = OsuPlayfield.BASE_SIZE / 2, NewCombo = true },
+                        new HitCircle { StartTime = 5000, Position = OsuPlayfield.BASE_SIZE / 2 },
+                    }
+                },
+                PassCondition = () => ((HitCircle)Player.GameplayState.Beatmap.HitObjects[1]).TimeFadeIn == 480
+            });
+        }
+
+        [Test]
+        public void TestFreezeFrameAppliedAfterHidden()
+        {
+            CreateModTest(new ModTestData
+            {
+                Mods =
+                [
+                    new OsuModHidden(),
+                    new OsuModFreezeFrame(),
+                ],
+                CreateBeatmap = () => new OsuBeatmap
+                {
+                    HitObjects =
+                    {
+                        new HitCircle { StartTime = 3000, Position = OsuPlayfield.BASE_SIZE / 2, NewCombo = true },
+                        new HitCircle { StartTime = 5000, Position = OsuPlayfield.BASE_SIZE / 2 },
+                    }
+                },
+                PassCondition = () => ((HitCircle)Player.GameplayState.Beatmap.HitObjects[1]).TimeFadeIn == 480
+            });
+        }
     }
 }

```

</details>

The reason that this is happening is a data dependency. Freeze Frame
modifies `TimePreempt` of hitobjects:


54c0b2c20c/osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs (L53)

while Hidden uses `TimePreempt` to set `TimeFadeIn`:


54c0b2c20c/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs (L45)

Therefore the final value of `TimeFadeIn` with these two mods active
depends on the order of application.

The reason why I'm bothering to do this is that I was pinged from the PP
development server about this again in the context of some ongoing
'rework' and I wish to get ahead of this before it becomes a bigger
problem than it already is.

The current order of application of these mods as done in `Player` is
constant, but essentially undefined. I'm not even sure what even
enforces the current order. It's currently Hidden, then Freeze Frame. If
I were to guess, the thing "enforcing" (insert 400 tonne solid lead air
quotes) it is probably the mod select overlay with its "I need to own
all of the mod instances in the global bindable" substitution logic.

I'm already getting pushback for this from the PP server crowd who are
attempting to justify the current "behaviour" by saying that the player
base wants this or by saying that it's already broken so it should
remain that way forever. I am however not willing to accept
[stable-taiko-tier stupidity
again](https://github.com/ppy/osu/pull/27136#issuecomment-1957055402)
and am not willing to accept the complexity this would invite everywhere
else.

I do not see any other easy way of fixing this problem at this point in
time. I had hoped that I could inline the `TimePreempt` read in
`OsuModHidden`, but it's not that easy because it's set in multiple
places.

Existing HDFF scores would probably need to be delisted from the
leaderboards. I'm not even sure I can get myself to pretend to care.
2026-01-29 16:35:13 +09:00
Bartłomiej Dach
a53ba5e4ef Merge pull request #36517 from YHSabc233/localisation/editor-show-storyboard
Localise "Show storyboard" in `Editor`
2026-01-29 08:35:01 +01:00
Bartłomiej Dach
35b9c74cf8 Merge pull request #36516 from peppy/fix-beatmap-invalidation-F-A-I-L
Fix star ratings sometimes not updating after editing a beatmap
2026-01-29 08:31:56 +01:00
YHSabc233
64a29313a8 Localise "Show storyboard" in Editor 2026-01-29 02:07:46 +08:00
Dean Herbert
d5ef0d75fc Reorder strings and enums too for consistency 2026-01-29 02:42:28 +09:00
Dean Herbert
3b3bb266fc Rewrite everything for code quality's sake 2026-01-29 02:40:24 +09:00
Dean Herbert
487347dac6 Merge branch 'master' into cycle-skins 2026-01-29 02:19:36 +09:00
Dean Herbert
d7648afe96 Fix back-to-front ordering of keys in displays 2026-01-29 02:19:21 +09:00
Dean Herbert
c86f76b972 Fix obliteration of key bindings due to not reading above comment 2026-01-29 02:18:31 +09:00
Tim Oliver
8e5eed63e1 Update iOS app icon to iOS 26 Liquid Glass variants (#36510)
Removes the previous `AppIcon.appiconset` bundle and replaces it with
all of the necessary asset slices to enable all of the Liquid Glass
variants of the app icon on iOS 26, while also preserving backwards
compatibility with older iOS versions.

<img width="1164" height="388" alt="LiquidGlass"
src="https://github.com/user-attachments/assets/7d6e7a90-3fe5-4853-9c63-35144359f49e"
/>

---------

Co-authored-by: Dean Herbert <pe@ppy.sh>
2026-01-29 02:12:41 +09:00
Dean Herbert
b705313d3f Fix BeatmapDifficultyCache's bindable tracking not correctly updating BeatmapInfo metadata on invalidation 2026-01-29 01:47:13 +09:00
Dean Herbert
5dd34f165f Add failing test coverage 2026-01-29 01:47:13 +09:00
Dean Herbert
10651512a5 Avoid triggering bindable updates if mods haven't actually changed 2026-01-29 00:28:58 +09:00
Dean Herbert
4c2ffa0f79 More aggressively cancel debounced tracked bindable updates
If we've run an update since we can cancel the scheduled debounce run.
2026-01-29 00:28:58 +09:00
Dean Herbert
6997f89698 Add assertion that we always get the up-to-date beatmap file in a WorkingBeatmap 2026-01-29 00:28:58 +09:00
Bartłomiej Dach
0ed4a8dbe5 Merge pull request #36511 from MayoCollector/localisation/sample-set-chooser
Localise "Custom sample sets" caption in `FormSampleSetChooser` dropdown
2026-01-28 14:48:37 +01:00
BoomboxRapsody
c47f9c5259 Localise "Custom sample sets" in FormSampleSetChooser dropdown 2026-01-28 22:06:54 +09:00
Bartłomiej Dach
a401c7d5e9 Merge pull request #36498 from smoogipoo/ranked-play-models
Add server-side models for ranked play
2026-01-28 11:31:57 +01:00
Bartłomiej Dach
6185a81bd5 Merge pull request #36504 from peppy/fix-double-checking-sounds
Fix `FormCheckbox` playing sounds twice
2026-01-28 11:07:17 +01:00
Dan Balasescu
6d54d20943 Apply default value changes from review 2026-01-28 17:45:15 +09:00
Bartłomiej Dach
02fbcaba23 Merge pull request #36500 from peppy/playlist-ssv2-all-mods-display
Fix "ALL MODS" display not displaying in new playlist song select
2026-01-28 09:39:35 +01:00
Dean Herbert
7b7777bb56 Fix FormCheckbox playing sounds twice
This removes the disabled sound, but I think that's fine. If we want
that, it should be handled by `HoverClickSounds` (which I'm currently
intentionally not using because it can be a bit noisy).

Closes #36503.
2026-01-28 16:55:43 +09:00
Dean Herbert
ca7b850c8c Remove leaking knowledge of freestyle in normal song select component 2026-01-28 16:37:39 +09:00
Bartłomiej Dach
88e5ac4834 Merge pull request #36499 from peppy/fix-dropdown-animations
Fix dropdown margins and animations being weird
2026-01-28 08:09:40 +01:00
Dean Herbert
b9d5606fd4 Limit maximum size of mod tooltip to avoid it going offscreen 2026-01-28 16:08:02 +09:00
Dean Herbert
c8c91cedfa Combine implementation of mods text drawable
I don't want to have to update things in multiple places with different
code in each place.

This also closes https://github.com/ppy/osu/issues/36412.
2026-01-28 16:02:14 +09:00
Dean Herbert
0c90cf3cef Fix mod count overlay flashing when switching freestyle on and off 2026-01-28 15:01:23 +09:00
Dean Herbert
1a0193001d Fix "ALL MODS" display not displaying in new playlist song select
Closes https://github.com/ppy/osu/issues/36411.
2026-01-28 14:42:41 +09:00
Dean Herbert
6bdb0e1cbb Fix dropdown margins and animations being weird
This was attempted to be fixed by frenzibyte using some hack workaround
logic, but this is the true fix.

Things were never matching due to `UpdateSize` spamming `Resize`
transforms every frame, causing the fade out to complete before
transforms have reached a final state.
2026-01-28 14:22:41 +09:00
Dean Herbert
0d3b7ab6be Remove workaround for dropdowns animating weirdly 2026-01-28 14:22:31 +09:00
Dan Balasescu
8202ad18d0 Add comments 2026-01-28 14:00:44 +09:00
Dan Balasescu
fa5a278cf3 Add ranked play match type 2026-01-28 13:42:59 +09:00
Dan Balasescu
70321e2f89 Add ranked play models 2026-01-28 13:42:59 +09:00
Dan Balasescu
c1fc1edd9c Add type parameter to pool retrieval method 2026-01-28 13:42:58 +09:00
Dan Balasescu
40e55ecb6e Add type to matchmaking pools 2026-01-28 13:38:40 +09:00
ArijanJ
4a5dbea2a2 Use ctrl-shift-E/T for shortcuts 2026-01-27 19:49:38 +01:00
Bartłomiej Dach
cffeb92248 Merge pull request #36486 from peppy/ui-tweaking
First pass adjustments to new settings design
2026-01-27 14:33:25 +01:00
Eeli Ogata
2c56419fe9 Fix panel expansion not being set on initial song select load (#36456)
Closes #36445.

Fixes a regression from #36404

---------

Co-authored-by: Dean Herbert <pe@ppy.sh>
2026-01-27 14:32:51 +01:00
Bartłomiej Dach
add67b95c7 Apply extracted background in one more location 2026-01-27 12:09:31 +01:00
Dean Herbert
6fad798227 Add insult to injury for autosize workaround
Co-authored-by: Bartłomiej Dach <dach.bartlomiej@gmail.com>
2026-01-27 19:58:45 +09:00
Dean Herbert
60690cbccc Add localisation support for "Device:" prefix 2026-01-27 19:03:23 +09:00
Dean Herbert
6329e3ee1b Add back FinishTransforms at what cost 2026-01-27 19:02:28 +09:00
Dean Herbert
fa22d1f202 Use enum for background states 2026-01-27 18:58:03 +09:00
Dean Herbert
12eeab581e Remove unnecessary depth change 2026-01-27 18:51:04 +09:00
Dean Herbert
b5e93b98c9 Fix mismatching spacing in dropdown form menus 2026-01-27 18:49:53 +09:00
Dean Herbert
786ea35274 Fix revert button not always matching control's size 2026-01-27 18:36:49 +09:00
Dean Herbert
d6bf4fd90d Fix border showing for inner text boxes incorrectly
I turned masking back on on these for better visuals (text masking
aligns with rest of elements) but turns out this implicitly makes
borders draw.
2026-01-27 18:36:37 +09:00
Dean Herbert
c407e07514 Adjust paddings to give more width to settings 2026-01-27 17:43:28 +09:00
Dean Herbert
ed0051d119 Adjust size and icon of revert-to-default button 2026-01-27 17:06:15 +09:00
Dean Herbert
99ea19d55c Make binding configuration button bigger
It's one of the more important sections so I want to make it easier to
find.
2026-01-27 17:06:14 +09:00
Dean Herbert
dba17b1fa6 Standardise hover sounds for form elements
Closes #36444.
2026-01-27 17:06:14 +09:00
Dean Herbert
fdf28474bf Add prefix for input device header 2026-01-27 17:06:13 +09:00
Dean Herbert
42dc73d849 Standardise animations 2026-01-27 17:06:13 +09:00
Dean Herbert
6983227240 Various metrics and visual tweaks 2026-01-27 17:06:13 +09:00
Dean Herbert
585ce682ef Fix button spacing being weirdly small 2026-01-27 17:06:12 +09:00
Dean Herbert
3db3b603bc Fix revert button height 2026-01-27 17:06:12 +09:00
Dean Herbert
1c537a3278 Make switch buttons look more like old version 2026-01-27 17:06:11 +09:00
Dean Herbert
c88f10ca1b Standardise background across form controls 2026-01-27 17:06:11 +09:00
Dean Herbert
7fef2ce1de Increase size of caption text 2026-01-27 03:04:45 +09:00
Dean Herbert
9a5fd0addb Remove enabled/disabled text from FormCheckbox 2026-01-27 03:04:45 +09:00
Dean Herbert
9c0b1a4914 Adjust tooltip response speed and avoid using per-frame transforms 2026-01-27 03:04:45 +09:00
Dean Herbert
acdb4477f7 Fix logs failing to export after multiple failure attempts on file locks (#36469)
Even if one file fails, we usually don't care and still want the archive
to finish exporting.

Closes https://github.com/ppy/osu/issues/36446

---------

Co-authored-by: Bartłomiej Dach <dach.bartlomiej@gmail.com>
2026-01-26 10:10:04 +01:00
Dean Herbert
4ff29339ed Merge pull request #36471 from peppy/fix-previous-username-depth
Fix previous usernames display showing underneath other elements
2026-01-26 10:09:39 +01:00
Dean Herbert
f1f4a0001b Apply new inspections from 2026.1EAP1
Nothing really egregious here so not bothering with PR review. One dodgy
bug which has been
[reported](https://youtrack.jetbrains.com/issue/RIDER-135036/Incorrect-recursive-on-all-execution-paths-inspection)
and temporarily ignored.
2026-01-26 15:12:52 +09:00
StanR
df1dc46603 Move Traceable to difficulty increasing mods section (#35500)
TC is a mod that always increases difficulty and is quite similar to HD.
Given we even have diffcalc/pp considerations for it it's time to move
it to the category it belongs.

This doesn't cover any other mods that might need reshuffling too
because TC is the only one that has actual impact on difficulty-based
leaderboards (as in pp) and some people are actively playing it for the
difficulty increase and not just as a fun gimmick.

After a quick search turns out it was difficulty increasing from the
start but was moved to fun in review
https://github.com/ppy/osu/pull/3569#discussion_r300085523 but I don't
really agree with that.

Also as far as I know multimods don't do anything anymore?.. I've put it
into HD multimod for consistency, but can move it to be separate if you
want

Co-authored-by: Dan Balasescu <smoogipoo@smgi.me>
2026-01-25 20:30:01 +09:00
Denis Titovets
84ec4bb866 Fix score tooltips being displayed when hovering freemods status bar on PlaylistsSSV2 (#36413)
- similar to https://github.com/ppy/osu/pull/36332
2026-01-24 00:10:04 +09:00
Dean Herbert
9f0f9ef229 Merge pull request #36438 from bdach/bypass-test-fails-slider-bar
Work around flaky `TestSceneFirstRunSetupOverlay` tests
2026-01-24 00:01:56 +09:00
Dean Herbert
290b141d8f Merge pull request #36439 from bdach/late-firing-realm-subs
Locally schedule beatmap skin change callbacks to ensure they fire at valid times
2026-01-23 23:58:13 +09:00
Bartłomiej Dach
7cc025d8f4 Merge pull request #36436 from peppy/legacy-health-wrong-flash-texture
Fix legacy health display using incorrect bulge texture when at low HP
2026-01-23 14:01:58 +01:00
Bartłomiej Dach
978fc91b09 Locally schedule beatmap skin change callbacks to ensure they fire at valid times
See CI failures like
https://github.com/ppy/osu/actions/runs/21238110652/job/61110112412?pr=36404#step:5:21.

Hopefully works this time.
2026-01-23 13:41:44 +01:00
Bartłomiej Dach
259c5708f1 Add some more extra test coverage 2026-01-23 13:18:48 +01:00
Bartłomiej Dach
b7559f93f2 Work around flaky TestSceneFirstRunSetupOverlay tests
See inline commentary. I don't really have any energy left to provide
anything else, other than maybe a demonstration of how this dies in
framework:

diff --git a/osu.Framework.Tests/Visual/UserInterface/TestSceneScreenStackUnbindOnExit.cs b/osu.Framework.Tests/Visual/UserInterface/TestSceneScreenStackUnbindOnExit.cs
new file mode 100644
index 000000000..c74ce6636
--- /dev/null
+++ b/osu.Framework.Tests/Visual/UserInterface/TestSceneScreenStackUnbindOnExit.cs
@@ -0,0 +1,59 @@
+// 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 NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Screens;
+
+namespace osu.Framework.Tests.Visual.UserInterface
+{
+    public partial class TestSceneScreenStackUnbindOnExit : FrameworkTestScene
+    {
+        [Cached]
+        private ScreenStack screenStack = new ScreenStack();
+
+        [Test]
+        public void TestScreenExitUnbindDoesNotInterruptLoadComplete()
+        {
+            AddStep("set up the scenario", () =>
+            {
+                Child = screenStack;
+                screenStack.Push(new Screen());
+                screenStack.Push(new BrokenScreen());
+            });
+            AddUntilStep("wait to get to target screen", () => screenStack.CurrentScreen, Is.InstanceOf<Screen>);
+        }
+
+        private partial class BrokenSlider : BasicSliderBar<float>
+        {
+            [Resolved]
+            private ScreenStack screenStack { get; set; } = null!;
+
+            protected override void LoadComplete()
+            {
+                // exiting the current screen provokes the behaviour of unbinding all bindables in the screen's subtree
+                screenStack.CurrentScreen.Exit();
+
+                // ...but the following calls should still take correct effect inside `SliderBar`
+                // (namely one consisting of propagating `{Min,Max}Value` into `currentNumberInstantaneous`)
+                // so that it doesn't have its internal invariants violated
+                CurrentNumber.MinValue = -10;
+                CurrentNumber.MaxValue = 10;
+
+                // this notably calls `Scheduler.AddOnce(updateValue)` inside, which will happen *in the imminent future, in the same frame as `LoadComplete()` here.
+                // if the above mutations of `{Min,Max}Value` don't correctly propagate inside the slider bar due to an overly eager unbind, this will cause a crash.
+                base.LoadComplete();
+            }
+        }
+
+        private partial class BrokenScreen : Screen
+        {
+            [BackgroundDependencyLoader]
+            private void load()
+            {
+                InternalChild = new BrokenSlider();
+            }
+        }
+    }
+}

I attempted to address what I perceive to be the root issue here which
is that `ScreenStack` is allowed to arbitrarily unbind bindables under
drawables which are by all means still in the scene graph. The attempt
consisted of scheduling the unbind until after children of the screen
stack, but that caused 150 game-side tests to fail, seemingly on
something relevant to bindable leases, so I give up.
2026-01-23 13:07:42 +01:00
Dean Herbert
c2604e22c7 Update resources 2026-01-23 17:44:17 +09:00
Dean Herbert
dddd75b7f9 Merge pull request #36435 from bdach/sigh
Fix broken percentage formatting in form slider bars
2026-01-23 17:17:20 +09:00
Dean Herbert
d3c9548b52 Fix legacy health display using incorrect bulge texture when at low HP
Closes #36432.
2026-01-23 17:10:35 +09:00
Bartłomiej Dach
225f4d8814 Fix broken percentage formatting in form slider bars
closes https://github.com/ppy/osu/issues/36428

this one's my bad:
https://github.com/ppy/osu/pull/36195#discussion_r2698388405
2026-01-23 08:01:41 +01:00
Bartłomiej Dach
d80934c344 Add failing tests 2026-01-23 07:50:20 +01:00
Dean Herbert
6f038bff48 Merge pull request #36392 from iwa/feat/numeric-hotkeys-for-presets-mod-select
Mod Select: Support numeric row hotkeys to quick select Presets
2026-01-23 03:28:52 +09:00
Dean Herbert
98c6f6afcd Add test coverage 2026-01-23 01:13:49 +09:00
Bartłomiej Dach
ff28839a09 Merge pull request #36426 from peppy/disable-sentry-envvar
Add envvar to disable error reporting
2026-01-22 13:07:19 +01:00
Bartłomiej Dach
52d5c986ef Fix wrong type in enabled check 2026-01-22 12:57:47 +01:00
Dean Herbert
dd9e70b29c Merge pull request #36193 from frenzibyte/new-settings/overlay
Update settings to use new "form" style controls
2026-01-22 20:27:16 +09:00
Dean Herbert
04e378c09f Add visual hinting of keyboard shortcuts 2026-01-22 20:24:10 +09:00
Dean Herbert
d4cf46e3cd Fix some intermittent test failures 2026-01-22 19:25:25 +09:00
Dean Herbert
42302c45ec Merge branch 'master' into new-settings/overlay 2026-01-22 18:38:17 +09:00
Denis Titovets
e2dd4d86b4 Add localisation support for PlaylistsSongSelectV2 (#36410)
| master | pr |
|-|-|
| <img width="691" height="84" alt="osu_2026-01-20_18-16-38 — копия"
src="https://github.com/user-attachments/assets/24e24131-525b-4603-b6f7-dbdd6e8be188"
/> | <img width="694" height="76" alt="osu_2026-01-20_18-12-45 — копия"
src="https://github.com/user-attachments/assets/d880b9c8-8a69-494e-863f-170f904a71b2"
/> |
| <img width="581" height="191" alt="osu_2026-01-20_18-16-38"
src="https://github.com/user-attachments/assets/01256367-3275-40c3-9da2-0073e4e33a1d"
/> | <img width="570" height="184" alt="osu_2026-01-20_18-12-45"
src="https://github.com/user-attachments/assets/a9b2ce0a-b571-4f94-bf0e-7869bd32e6ae"
/> |
2026-01-22 18:18:02 +09:00
Dean Herbert
6693307064 Add envvar to disble error reporting
https://github.com/ppy/osu/discussions/31832#discussioncomment-15555080
2026-01-22 18:12:27 +09:00
Bartłomiej Dach
f0e7817dda Fix beatmap samples with incorrect 1 sample set suffix in the filename being shown in setup tab as belonging to custom sample bank 1 (#36425)
To explain on the wordy and probably incoherent title: The patient zero
for this was the following discord report:
https://discord.com/channels/188630481301012481/1097318920991559880/1463029073998774375

After information extraction, it turned out that the user manually used
the "Edit externally..." feature to rename a sample to
`normal-hitnormal1.mp3`, thinking (logically, to be fair), that the 1
bank works the same way that all the others do.

It doesn't, samples in the 1 bank do not have a numerical suffix in the
filename - but the logic retrieving the sample sets to display in the
setup tab was not checking that, leading to confusion wherein a sample
would only "work" in the setup tab but not in the actual editor
composer.

Can be tested with [the beatmap provided by the
user](https://discord.com/channels/188630481301012481/1097318920991559880/1463110516573470774).
2026-01-22 18:05:29 +09:00
Bartłomiej Dach
a1d325370c Merge pull request #36423 from peppy/standardise-ruleset-failure-logging
Standardise ruleset error logging to always include exception in logs
2026-01-22 08:59:18 +01:00
Dean Herbert
1e8ffdf128 Fix weird typo brain turned off 2026-01-22 16:43:46 +09:00
Bartłomiej Dach
ea0b281f48 Merge pull request #36404 from peppy/fix-carousel-thing
Fix random selection not showing selection when all groups are collapsed
2026-01-22 08:24:03 +01:00
Dean Herbert
fdd13cd7e1 Standardise ruleset error logging to always include exception in logs
Supersedes and closes https://github.com/ppy/osu/pull/36420.
2026-01-22 15:48:51 +09:00
Dean Herbert
6354286452 Simplify logic further and fix regressing case 2026-01-22 14:59:42 +09:00
Dean Herbert
6f146a5ce9 Add failing test case showing visual state desync 2026-01-22 14:59:41 +09:00
Dean Herbert
5d651628bc Merge branch 'master' into fix-carousel-thing 2026-01-22 01:55:28 +09:00
Dean Herbert
10dc1314f6 Fix carousel items becoming incorrectly selected due to bindable leakage (#36414)
As discussed in discord. Fixed from multiple angles.

---------

Co-authored-by: Bartłomiej Dach <dach.bartlomiej@gmail.com>
2026-01-22 01:06:40 +09:00
iwa
ce2d54f118 refactor: move presets hotkey logic inside ModPresetColumn class 2026-01-20 22:22:10 +01:00
iwa
042f716b2a revert: changes made for preset hotkeys in SequentialModHotkeyHandler 2026-01-20 22:21:21 +01:00
iwa
0ea060c612 revert: remove IPresetHotkeyHandler 2026-01-20 22:20:54 +01:00
ArijanJ
ea8788a509 Simplify cycleSkins, fix some logic and naming mistakes 2026-01-20 21:47:34 +01:00
Dean Herbert
46b2d5374e Refactor HandleFilterCompleted to (hopefully) be easier to parse
This is not required to fix the issue, but it is required for my brain
to follow the method to some degress.
2026-01-20 21:12:49 +09:00
Dean Herbert
8860eec9f9 Handle expand set logic locally rather than calling HandleItemSelected
This avoids the implicit group expansion happening that the added code
was attempting to fix.
2026-01-20 21:12:49 +09:00
Dean Herbert
8c86ff36a2 Revert "Do not forcibly re-expand carousel groups on refilters if the user manually collapsed them"
This reverts commit bc4d5d07d7.
2026-01-20 21:12:49 +09:00
Dean Herbert
fdf9092f41 Revert "Fix current beatmap set being incorrectly expanded after collapsing group with current selection"
This reverts commit 9ba9966078.
2026-01-20 21:12:48 +09:00
Dean Herbert
76f05a4837 Add failing test showing incorrect behaviour on random select with collapsed groups 2026-01-20 21:12:48 +09:00
Bartłomiej Dach
ae1402c9cd Merge pull request #36401 from peppy/fix-skin-save-crash
Fix skin saving crashing if hashable files are not present
2026-01-20 11:04:23 +01:00
Dean Herbert
741620d89c Fix skin saving crashing if hashable files are not present
This ends up with a bit of an undefined behaviour, but it's already a
bit of an edge case (files missing in the `files` folder that are
references in the database).

First and foremost, let's stop the exception. If we allow it to throw,
it's impossible to exit the skin editor in this state.

Closes https://github.com/ppy/osu/issues/36135.
2026-01-20 17:41:10 +09:00
Bartłomiej Dach
4ee4aac708 Fix changing combo colours in beatmap without custom samples opening new sample set popover (#36400)
Reported [on reddit of all
places](https://www.reddit.com/r/osugame/comments/1qhnfdn/every_time_i_make_an_adjustment_to_the_hitcircle/).

The reason that this is happening is as follows:

- Changing a combo colour raises `BeatmapSkinChanged`
- `BeatmapSkinChanged` getting raised triggers the sample chooser
dropdown to repopulate its items (as intended and correctly)
- Setting items of a dropdown can also change its `Current.Value`,
because the relevant logic attempts to ensure that `Current.Value` is
valid in line with the new items
- Therefore the `Current.Value` of the dropdown changes from `null` to
sample set `-1` which corresponds to the "Add new..." item as it's the
only item in the dropdown...
- which is indistinguishable from the sequence of events that happens if
the user manually opens the dropdown and clicks the "Add new..." item.

This change sidesteps this entire car crash by just ensuring that even
beatmaps without custom samples have at least set number 1 initialised
(even if it's empty and has no samples). This means that the initial
value of the dropdown is never `null`, and that every time that the
value changes to the set `-1` it actually is due to user action.

Should have known better than to play these dumb games.
2026-01-20 17:10:40 +09:00
Linus Genz
dfa7ac2082 Specialise mod setting hover text in song select scoreboard (#36391)
- resolves #35992

**Changes**

Introduced GetSettingTooltipText, which returns the value of the
respective setting. The function can be overwritten, allowing to
implement specific behavior. This allows you to define that a value of 0
for the “Muted” mod will instead display “always muted.”
In the same step, I also adjusted the "NoScope" mod, where we had the
same problem.
Wherever we want to implement specialization like this, we can simply
overwrite GetSettingTooltipText to customize the behavior for the mod
settings of the mod.

**Result**
<img width="2560" height="1440" alt="2026-01-19-122307_hyprshot"
src="https://github.com/user-attachments/assets/20e3aa9a-aa6f-4284-9cf1-3092f52c021d"
/>
<img width="2560" height="1440" alt="2026-01-19-122324_hyprshot"
src="https://github.com/user-attachments/assets/6f3fac5b-2a5d-4dd9-a56c-4b09c02cbcca"
/>
<img width="2560" height="1440" alt="2026-01-19-155307_hyprshot"
src="https://github.com/user-attachments/assets/f9ee75d3-c200-4536-9ee9-d20ddbd9fa44"
/>

Signed-off-by: Linus Genz <linuslinuxgenz@gmail.com>
2026-01-20 16:51:23 +09:00
iwa
2a0bafde8c fix: align OnKeyDown condition with existing from ModColumn 2026-01-19 22:07:32 +01:00
iwa
91ee5eebc9 feat: add logic to handle numeric hotkeys to select presets from mod select overlay 2026-01-19 17:15:15 +01:00
iwa
6bf95068ae feat: expose a public Toggle method for ModPresetPanel 2026-01-19 17:14:34 +01:00
iwa
6d999a8d8e refactor: rename HandleHotkeyPressed to HandleModHostkeyPressed to differentiate with presets handler 2026-01-19 17:14:02 +01:00
Bartłomiej Dach
c1d9de7e83 Fix sample set index entry box crashing on bad input (#36390)
closes https://github.com/ppy/osu/issues/36374
2026-01-19 22:21:20 +09:00
ArijanJ
763dbe1494 Add blank line for codeinspect 2026-01-19 13:34:25 +01:00
ArijanJ
65f7037e84 Add skin cycling with shortcuts for next and previous skin 2026-01-19 12:59:21 +01:00
Dan Balasescu
e62a01cf77 Add metadata endpoint to refresh friend listing (#36386)
Alternative to / closes https://github.com/ppy/osu/pull/36341
See also: https://github.com/ppy/osu-server-spectator/pull/415

This is a simple solution by adding a spectator endpoint to refresh the
friend listing. A more complicated form of this is to make
adding/removing friends only via spectator, but that would require
osu-web changes too.
2026-01-19 18:34:00 +09:00
Bartłomiej Dach
a60f262679 Remove mention of no-longer-existent "good first issue" label
It doesn't work in practice, because the skill baseline of an incoming
"contributor" is so wildly variant that any issue that isn't explicitly
a one-liner is automatically not suitable for *some* non-trivial subset
of "new contributor".

The label is also largely used by incoming contributors whose interests
are not necessarily aligned with the project but instead appear to have
other ulterior motives.
2026-01-19 09:26:22 +01:00
Dean Herbert
c0b644c905 Fix osu! logo appearing in at new playlist song select screen after opening mod select (#36385)
Closes https://github.com/ppy/osu/issues/36363.
2026-01-19 16:55:39 +09:00
MayoCollector
cf73f8f9e6 Localise "hold for menu" & "press for menu" in HoldForMenuButton (#36381) 2026-01-19 14:39:21 +09:00
Joseph Madamba
7608ed4d87 Remove now unnecessary mobile hold hack when song select v2 wasn't default (#36384)
This reverts commit 3931ae3499.
2026-01-19 13:31:48 +09:00
Denis Titovets
eee88a3955 Fix score tooltips being displayed when hovering mod status bar (#36332)
- addresses https://github.com/ppy/osu/discussions/34079
- supersedes and closes https://github.com/ppy/osu/pull/34090
2026-01-17 23:15:46 +09:00
Salman Alshamrani
676b7a36da Improve input handling in percentage-based slider bars (#36195)
Reported in
https://github.com/ppy/osu/pull/36193#issuecomment-3703406223.

Preview:


https://github.com/user-attachments/assets/ac2b86cf-ad92-496b-b3dd-19ad47753a9b

---------

Co-authored-by: Dean Herbert <pe@ppy.sh>
2026-01-17 16:39:28 +09:00
Salman Alshamrani
b5a989d31e Fix form dropdown open animation not being smooth (#36358)
Preview:


https://github.com/user-attachments/assets/07cb58b7-5980-4fed-bb4c-2443ba1e9635
2026-01-17 02:54:12 +09:00
Dean Herbert
a6489cd758 Merge pull request #36190 from bdach/add-custom-samples-via-setup
Add way to add/remove custom beatmap samples to setup screen
2026-01-17 01:55:59 +09:00
Krzysztof Gutkowski
952fd0d493 Ensure diffcalc runs after mods get replaced during a ruleset change (#36359) 2026-01-16 16:38:00 +01:00
Denis Titovets
06b89919be Add localisation support for some more notifications (#36353)
Co-authored-by: Dean Herbert <pe@ppy.sh>
2026-01-16 22:58:20 +09:00
Dean Herbert
e1bde264bc Merge branch 'master' into add-custom-samples-via-setup 2026-01-16 20:11:28 +09:00
De4n
e05b6f44b9 Update editor slider controls to new design (#36346)
(partially) Closes: #36233
Surpasses: #36244 

This PR meant to be one of the last steps that finally make editor use
the new forms. Initially it meant to only change one
SliderWithTextBoxInput in "Effects section" in timing screen, however
soon after it was obvious that there's many other places that still
using it. This currently won't affect
IndeterminateSliderWithTextBoxInput that is being used in hitsounds, for
example, since I think it needs more consideration.
Anyways, with this PR, SliderWithTextBoxInput, will no longer be used at
all, as it's going to be replaced with modern FormSliderBar

Comparison:
|master|this PR|
|:---:|:---:|
|<img width="510" height="316"
alt="532203751-eb965923-d3a8-441d-a7c8-5c364a6328ad"
src="https://github.com/user-attachments/assets/268b45b8-e235-494f-91a5-d00db057dba8"
/>|<img width="540" height="321"
alt="535466527-3a700a8b-bc3c-4610-998f-a4e55ee03eed"
src="https://github.com/user-attachments/assets/20cd4b58-b0bd-49bc-8c48-7de5cf8556b3"
/>|
|<img width="694" height="639"
alt="534509844-f00e4da4-53c4-45e8-80ea-1be62da6c83b"
src="https://github.com/user-attachments/assets/398c4484-a867-4df1-9de3-0940aa748a01"
/>|<img width="720" height="433" alt="изображение"
src="https://github.com/user-attachments/assets/b6359443-a224-4a55-b171-07e8f013cf46"
/>|
|<img width="715" height="353"
alt="534509421-a6ac950f-16e8-4a16-bca6-1a781f82135f"
src="https://github.com/user-attachments/assets/4854312b-772f-4b81-a800-89e58d4c715d"
/>|<img width="710" height="296" alt="изображение"
src="https://github.com/user-attachments/assets/a7fed53e-e006-4285-92c9-bb84cb603f60"
/>|
|<img width="717" height="374"
alt="534509478-80222623-7766-481d-8682-088276d415ee"
src="https://github.com/user-attachments/assets/8143b6dc-4599-45d5-bd3b-f059caf3d93d"
/>|<img width="718" height="328" alt="изображение"
src="https://github.com/user-attachments/assets/bffa04de-983c-45ae-a1ec-373701ea0e49"
/>|
|<img width="702" height="446"
alt="534509935-58954060-7ac1-4392-8754-a58f909e86aa"
src="https://github.com/user-attachments/assets/2bb67a2d-3f57-42a1-96ce-b30b4891e1a4"
/>|<img width="722" height="386" alt="изображение"
src="https://github.com/user-attachments/assets/01b7fff4-7f31-4aac-90c9-353b15f4964e"
/>|
2026-01-16 20:08:52 +09:00
Joseph Madamba
a8ada50549 Fix double-clicking form slider bar not propagating default to other bindables when TransferValueOnCommit is true (#36354)
- Addresses https://github.com/ppy/osu/pull/36346#discussion_r2692322683

The inner slider bar binds its `Current` to
`currentNumberInstantaneous`:


1add946db4/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs (L225-L236)

But the current bindable of the form component doesn't update because of
this.


1add946db4/osu.Game/Graphics/UserInterfaceV2/FormSliderBar.cs (L285-L293)

Fixed it by just moving the `ResetToDefault` action up another level.

---------

Co-authored-by: Dean Herbert <pe@ppy.sh>
2026-01-16 19:48:36 +09:00
Bartłomiej Dach
1add946db4 Merge pull request #36350 from peppy/toast-adjust
Fix toasts showing "no key bound" for operations which can't have keys bound
2026-01-15 13:06:33 +01:00
Bartłomiej Dach
a2ca650a9f Merge pull request #36349 from peppy/spectator-connection-disconnect-notify
Fix notification spam on websocket connection handshake failures
2026-01-15 13:06:19 +01:00
Dean Herbert
cd433fea50 Fix weirdly defined constant 2026-01-15 19:01:32 +09:00
Dean Herbert
a6756157d1 Update tablet area selection to match new design 2026-01-15 18:54:01 +09:00
Dean Herbert
367d133d2f Avoid requiring private variables to set ApplyClassicDefault settings 2026-01-15 18:42:57 +09:00
Dean Herbert
1313883394 Scope player handling to specific screens which have issues 2026-01-15 18:09:04 +09:00
Dean Herbert
e8154080b3 Stop websocket handshake failures from being shown to users 2026-01-15 18:09:04 +09:00
Dean Herbert
b4ba327c1c Fix toasts showing "no key bound" for operations which can't have keys bound
Supersedes and closes https://github.com/ppy/osu/pull/35781.
Closes https://github.com/ppy/osu/issues/36294.
2026-01-15 17:52:30 +09:00
Dean Herbert
c646b4e5ec Alert when spectator server disconnects during gameplay
This can cause issues liek loss of replays, so it's worth notifying the
user and keeping things visible.
2026-01-15 17:14:56 +09:00
Dean Herbert
15cb3b7a27 Use same message for multiplayer disconnects to simplify things further 2026-01-15 17:11:44 +09:00
Dean Herbert
0551c75c6b Adjust API disconnection text to be usable in more scenarios 2026-01-15 17:11:42 +09:00
Dean Herbert
2bf0dcf398 Adjust friend notification logic to fix a few flaws (#36348)
Just a couple of things I noticed in passing:

- When changing the configuration setting, things were not reset.
Likewise, if the setting was off the queues would still be added to but
never flushed.
- When the setting is toggled, a stale next notification time was still
present due to the `??=` and lack of resetting. This should no longer be
the case.
2026-01-15 16:31:15 +09:00
De4n
3302e2e180 Use new star rating text gradient for the difficulty name, "mapped by" text and difficulty bars (#36345)
Fixes: #36312

I think that's exactly what needed to be done to fix this issue.

|master|this PR|
|:---:|:---:|
|<img width="943" height="272" alt="изображение"
src="https://github.com/user-attachments/assets/e6fb57bd-8393-4476-ac73-a8b559365e6d"
/>|<img width="950" height="270" alt="изображение"
src="https://github.com/user-attachments/assets/e09de911-0068-4a8b-9abb-f2aa04a87886"
/>|
|<img width="943" height="274" alt="изображение"
src="https://github.com/user-attachments/assets/637a86f5-ad26-441d-8fa2-bdf113bb636d"
/>|<img width="936" height="272" alt="изображение"
src="https://github.com/user-attachments/assets/d72f52a1-6c17-4156-a4eb-cca36e74f644"
/>|
|<img width="940" height="271" alt="изображение"
src="https://github.com/user-attachments/assets/cf6a5f18-34c8-4f51-867a-ba1fc30b64a5"
/>|<img width="941" height="270" alt="изображение"
src="https://github.com/user-attachments/assets/e21cda45-1a69-4623-b9a7-add3eb7c7013"
/>
|<img width="946" height="275" alt="изображение"
src="https://github.com/user-attachments/assets/e29647bb-c4aa-4d03-a46d-3bbcd43181b6"
/>|<img width="932" height="271" alt="изображение"
src="https://github.com/user-attachments/assets/1f777db2-d0f9-4c2a-91fa-e005b9704c9a"
/>

---------

Co-authored-by: Dean Herbert <pe@ppy.sh>
2026-01-15 16:00:27 +09:00
Dean Herbert
79f1d77fed Fix copy paste failure in MultiplayerMatchSettingsOverlay 2026-01-14 20:02:11 +09:00
Dean Herbert
91718447db Merge pull request #36324 from frenzibyte/better-form-button
Update form button UI/UX and support text wrapping
2026-01-14 19:47:50 +09:00
Dean Herbert
3f92b451d9 Use foreach isntead of ForEach 2026-01-14 19:37:51 +09:00
Dean Herbert
ed091dc9cd Adjust toggleable subsection colour to always be bright when enabled 2026-01-14 02:15:10 -05:00
Dean Herbert
029d544720 Adjust switch buttons to make the state more obvious 2026-01-14 02:15:10 -05:00
Salman Alshamrani
876dcd060c Reduce supporter note to informational 2026-01-14 02:15:10 -05:00
Salman Alshamrani
b9a37453f9 Shorten "increase first object visibility" label text 2026-01-14 02:15:10 -05:00
Salman Alshamrani
c5a30d3244 Remove unnecessary classic default specification
Background blur is disabled by default.
2026-01-14 02:15:10 -05:00
Salman Alshamrani
96c414be86 Adjust audio offset controls to match design language 2026-01-14 02:15:10 -05:00
Salman Alshamrani
30a60d83e2 Allow configuring settings note text anchor 2026-01-14 02:15:10 -05:00
Salman Alshamrani
ed760e6389 Update keybind settings to use new revert button 2026-01-14 02:15:10 -05:00
Salman Alshamrani
5d9ea9d699 Rewrite input settings and use new form controls 2026-01-14 02:15:10 -05:00
Salman Alshamrani
874e3adcb7 Update layout settings section to use new controls 2026-01-14 02:15:10 -05:00
Salman Alshamrani
e548cd40dc Update game settings to use new form controls 2026-01-14 02:15:10 -05:00
Salman Alshamrani
7ea1bbf91e Introduce and use constants and classes for new settings overlay 2026-01-14 02:15:07 -05:00
Salman Alshamrani
27abce74ce Allow dynamically configuring dropdown hint text
Used for "confine mouse mode" dropdown tooltip text.
2026-01-14 02:12:53 -05:00
Salman Alshamrani
8a581f5a90 Fix sliders not playing samples on adjust
Should've been true by default, minor mistake from a previous PR.
2026-01-14 02:12:28 -05:00
Salman Alshamrani
3f4d1b798e Update button background colour in update function 2026-01-14 01:34:20 -05:00
Dean Herbert
d1a231cacf Merge pull request #36328 from bdach/hub-connector-fluff
Clean up `HubClientConnector` configuration
2026-01-13 20:17:41 +09:00
Bartłomiej Dach
9c8d6e63a7 Simplify proxy configuration
More clean-up.
2026-01-13 11:16:20 +01:00
Bartłomiej Dach
5abc0f93fe Remove no longer needed header alias for version hash
Clean-up from an old change (see
https://github.com/ppy/osu/pull/28892#discussion_r1681136250).
2026-01-13 11:12:03 +01:00
Bartłomiej Dach
d5aa5b61ad Replace manual specification of Authorization header with SignalR-provided provider property
The property used here is listed in SignalR documentation:

	https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-10.0#bearer-token-authentication

While in practice this probably has zero bearing on anything,
theoretically the way proposed in this commit is more correct.
As the documentation states, with some transports the token may need to
be renewed if it expires, which providing it via a header as done
previously would not achieve, while going through `API.AccessToken`
every time will perform a token refresh if one is needed.
2026-01-13 11:03:59 +01:00
Dean Herbert
f9e70f06b3 Merge pull request #35117 from smoogipoo/new-playlists-song-select
Use new song select (v2) for playlists
2026-01-13 17:38:03 +09:00
Dean Herbert
363b7cf3cb Merge branch 'master' into new-playlists-song-select 2026-01-13 17:04:42 +09:00
Dean Herbert
eeb4680795 Remove unused button 2026-01-13 16:57:50 +09:00
Salman Alshamrani
f892d5fbaa Improve form dropdown UX (#36325)
Addresses concerns in
https://github.com/ppy/osu/pull/36193#issuecomment-3728177951 (excluding
the last part, that is too involved and I can't imagine any workaround
for it due to how strict the `Dropdown` structure is). Also adds
truncation/padding to header label and search bar.

Preview:


https://github.com/user-attachments/assets/8885cb90-44dc-42ee-af21-cb33f7723e63
2026-01-13 16:20:03 +09:00
Salman Alshamrani
0ba4e9e2a4 Fix mod footer button with unranked badge not resizing on localisation changes (#33810)
- Closes https://github.com/ppy/osu/issues/33789
2026-01-13 16:13:00 +09:00
Dean Herbert
8de5726aa0 Merge pull request #36301 from diquoks/localisation/osu-game-notifications
Localise some more notification/updater strings
2026-01-13 16:11:46 +09:00
Dean Herbert
d3ed93280a Make error message string construction actually understandable 2026-01-13 16:10:57 +09:00
Denis Titovets
37257a7a02 Make edits based on reviews 2026-01-13 07:47:25 +03:00
Salman Alshamrani
df1304af9e Add visual test 2026-01-12 23:38:17 -05:00
Salman Alshamrani
18fd4758d7 Update form button UI/UX and support text wrapping 2026-01-12 23:38:17 -05:00
Dean Herbert
9acc632788 Merge pull request #36319 from Joehuu/fix-plural-skins-keyword
Fix skin section buttons disappearing when searching for plural "skins"
2026-01-13 10:21:52 +09:00
Dean Herbert
eb1417696a Merge pull request #36252 from UltraDrakon/hide-cursor-during-background-reveal
Hide cursor during background reveal in song select
2026-01-13 09:41:03 +09:00
Joseph Madamba
5de23e41cf Fix skin section buttons disappearing when searching for plural "skins" 2026-01-12 14:23:19 -08:00
UltraDrakon
5899800293 Merge branch 'master' into hide-cursor-during-background-reveal 2026-01-12 18:30:35 +01:00
Denis Titovets
b15b4e70a5 Merge branch 'localisation/perform-from-menu-runner-notification' into localisation/osu-game-notifications
# Conflicts:
#	osu.Game/Localisation/NotificationsStrings.cs
2026-01-12 19:01:06 +03:00
Denis Titovets
40d8fb6316 Merge branch 'localisation/update-manager' into localisation/osu-game-notifications
# Conflicts:
#	osu.Game/Localisation/NotificationsStrings.cs
2026-01-12 19:00:19 +03:00
Denis Titovets
a8989eb117 Expand settings in ReplayPlayer by default (#36308)
~i recently saw an suggestion to do this, but don't remember where~

- addresses https://github.com/ppy/osu/discussions/36189

i think it's logical, since the settings have been displayed in a
separate overlay for more than six months, and not on top of the
gameplay itself, and for example, it can be difficult to expand section
on phones

| master | pr |
|-|-|
| <img width="1920" height="1080" alt="osu_2026-01-12_09-40-23"
src="https://github.com/user-attachments/assets/35d15ce6-ce12-4a9a-be4e-d72043dfb91a"
/> | <img width="1920" height="1080" alt="osu_2026-01-12_09-39-40"
src="https://github.com/user-attachments/assets/225d75db-1719-48dc-a65f-16272cca8295"
/> |
2026-01-13 00:03:20 +09:00
Linus Genz
a81a77c60f Fix underline size at song select details panel not matching after changing language (#36303)
Fixes #36274 

**Change:**
Registered a `BindValueChanged` callback on each TabItem’s
`Text.Current` to ensure the underline indicator (strip) updates
whenever the displayed tab text changes and calling `updateDisplay`
accordingly to update the underline.

**Result:**


https://github.com/user-attachments/assets/6fb95a46-c768-46ec-a68a-a5e394e08a78

---------

Signed-off-by: Linus Genz <linuslinuxgenz@gmail.com>
Co-authored-by: Dean Herbert <pe@ppy.sh>
2026-01-12 23:40:13 +09:00
Dean Herbert
4726489dba Merge pull request #36311 from bdach/freeze-frame-suppresses-skips
Fix Freeze Frame mod suppressing skip if the first object is a spinner
2026-01-12 21:52:55 +09:00
Bartłomiej Dach
38c89a914b Fix Freeze Frame mod suppressing skip if the first object is a spinner
Fixes https://osu.ppy.sh/community/forums/topics/2169899?n=1.

`TimePreempt` doesn't affect the appearance of a spinner, but it *will*
affect the value of `GameplayStartTime`:

	4bf90a5571/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs (L82-L91)

While at parsing it is enforced that every object following a spinner
has `NewCombo` set, it is *not* enforced that every spinner has
`NewCombo` set. See also: https://github.com/ppy/osu/issues/24156.

But that's sort of orthogonal to the entire issue as well because why
touch the preempt of an object that's not visually affected by preempt
to begin with.
2026-01-12 11:11:38 +01:00
Bartłomiej Dach
57f16646a3 Add failing tests 2026-01-12 11:11:11 +01:00
Dean Herbert
9bad2c634b Merge pull request #36309 from bdach/broken-formatting
Fix broken date formatting in some languages on several overlays
2026-01-12 18:32:37 +09:00
Bartłomiej Dach
ae284fcf05 Fix broken date formatting in some languages on several overlays
Fixes https://osu.ppy.sh/community/forums/topics/2169438?n=1.
2026-01-12 09:34:53 +01:00
Denis Titovets
8a21c6814c Localise notification in OsuGame 2026-01-11 16:53:44 +03:00
Denis Titovets
f320592229 Localise notification in PerformFromMenuRunner 2026-01-11 16:17:09 +03:00
Denis Titovets
ee5cf25628 Localise notifications in MobileUpdateNotifier and NoActionUpdateManager 2026-01-11 15:58:59 +03:00
StanR
3e7f0f4c61 Add star rating text gradient (#36292)
Implements https://github.com/ppy/osu-web/pull/12363

There's one less colour in the spectrum than in the [web
code](https://github.com/ppy/osu-web/pull/12363/changes#diff-a9bdefd7233ca98f7f89cd76213aba5d869ae0424c8e79d1e322abd3e43462fbR31)
because the spectrum was actually defined incorrectly and has [one less
domain entry than it
should](https://github.com/ppy/osu-web/pull/12363/changes#diff-a9bdefd7233ca98f7f89cd76213aba5d869ae0424c8e79d1e322abd3e43462fbR29).
I've chose to not add it because of consistency with the web and because
it looked pretty ugly (it was pretty much unreadable)

<img width="1361" height="814" alt="image"
src="https://github.com/user-attachments/assets/5f9d7a93-3e28-4b8c-952c-0abd6f8c2cc3"
/>
<img width="805" height="647" alt="image"
src="https://github.com/user-attachments/assets/b060cba7-3beb-4bb5-8d50-6210e8417715"
/>
2026-01-11 00:30:48 +09:00
Dean Herbert
dda1c7f75b Fix failing test case due to mod overlay being moved to game level 2026-01-10 01:44:07 +09:00
Dean Herbert
7652c91a53 Merge pull request #36242 from frenzibyte/form-controls-text-wrapping
Support text wrapping in form controls
2026-01-09 18:06:24 +09:00
Dean Herbert
1c1a3fbe8d Simplify logic 2026-01-09 18:01:35 +09:00
Bartłomiej Dach
7c1b1f4548 Merge pull request #36249 from peppy/global-rank-shortcut-support
Add support for global rank parsing in /users/ batch lookups
2026-01-09 09:51:00 +01:00
Bartłomiej Dach
22082097f8 Merge pull request #36282 from peppy/online-lookup-cache-exception-safety
Add safeties against exceptions in `OnlineLookupCache`
2026-01-09 09:47:50 +01:00
Bartłomiej Dach
d35658985a Merge pull request #36281 from peppy/fix-button-colour-wrong-layer
Fix now playing overlay buttons not showing toggle colour correctly
2026-01-09 09:28:36 +01:00
Dean Herbert
576be6793b Add proper logging of failed scenario in OnlineLookupCache 2026-01-09 16:52:04 +09:00
Dean Herbert
5e4e28ef00 Fix online lookup cache not recovering from faulted tasks 2026-01-09 16:42:19 +09:00
Dean Herbert
a2d2c3287b Remove unnecessary and incorrect colour application 2026-01-09 16:27:33 +09:00
Dean Herbert
23c68cbfea Add support for global rank parsing in /users/ batch lookups
See https://github.com/ppy/osu-web/pull/12651 for web-side
implementation.
To be used to fix
https://github.com/ppy/osu/pull/33649/changes#diff-32784a778b34c671e1f72c00e1f4161a5e774e849aae5631ee71b31fc32e5d42R218
(have tested this works there).

Use simple set-get rather than transforming to `APIUser.Statistics`
2026-01-09 16:19:52 +09:00
Dean Herbert
3f577aae60 Fix now playing overlay buttons not showing toggle colour correctly
Closes https://github.com/ppy/osu/issues/36280.
2026-01-09 15:18:29 +09:00
Dean Herbert
2963ebae96 Update resources 2026-01-08 02:04:18 +09:00
Dean Herbert
3157e82f51 Update framework 2026-01-08 01:45:02 +09:00
Dean Herbert
b40ffd633f Merge pull request #36263 from bdach/log-version-hash-to-sentry
Log version hash to sentry
2026-01-08 00:05:24 +09:00
Bartłomiej Dach
48de70e719 Log version hash to sentry
This is the counterpart to
https://github.com/ppy/osu-server-spectator/pull/413. The goal is to
log the value which is seemingly failing to work correctly client-side
as well.

The reason for doing that is two-fold:

- To eliminate possibility of freakazoid issues wherein some computers
  miscalculate the version hash somehow
- To eliminate possibility of the version hash somehow getting lost in
  transit (e.g. present client-side but no longer present server-side).
2026-01-07 13:03:20 +01:00
Dean Herbert
4f44d13e67 Merge pull request #36262 from bdach/stupid 2026-01-07 20:55:18 +09:00
Bartłomiej Dach
9b26b83d55 Fix beatmap scope dismiss bar showing on top of filter control dropdowns
closes https://github.com/ppy/osu/issues/36259
2026-01-07 11:13:33 +01:00
Bartłomiej Dach
7f99650a04 Add test covering desired behaviour of dismissing beatmap set scope 2026-01-07 11:08:51 +01:00
Bartłomiej Dach
0e8a294400 Merge pull request #36253 from peppy/fix-replay-overlay-vis
Fix replay settings overlay appearing momentarily during gameplay retry
2026-01-07 10:25:04 +01:00
Bartłomiej Dach
3ae98fd664 Fix broken transition of player loader right side content (#36261)
before:


https://github.com/user-attachments/assets/dd9bfef0-d653-4aa6-b48d-fe01774d3f89

after:


https://github.com/user-attachments/assets/bb3541f9-65f2-4c34-9943-ba2b63b7dbf8

regressed in 2b90cbf59f
2026-01-07 18:21:04 +09:00
Bartłomiej Dach
8c8ba2f3bc Merge pull request #35931 from smoogipoo/mp-require-hold-to-exit
Require hold-to-exit during multiplayer load
2026-01-07 07:10:04 +01:00
Dean Herbert
163ca25907 Fix settings toggle for replay settings overlay not being obeyed in multiplayer spectator
Noticed in passing.
2026-01-06 23:25:22 +09:00
Dean Herbert
85747f8866 Fix replay settings appearing momentarily when replay is not loaded
Closes #36237.
2026-01-06 23:24:54 +09:00
Dean Herbert
d05acc3d4f Fix tray showing first item too far to left 2026-01-06 22:54:27 +09:00
UltraDrakon
8cbcb0e74f Hide cursor during background reveal in song select
- Implement IProvideCursor in SongSelect to hide cursor when background is revealed
- Cursor reappears on mouse movement and hides again after 1 second of inactivity
- Fix MenuCursorContainer to preserve drag rotation state during hide/show cycles
2026-01-06 14:51:07 +01:00
Dean Herbert
d39020038b Merge pull request #36251 from jonasschips/cosmetic/fpscounter
Visual addition of whitespaces to the FPS counter
2026-01-06 22:13:32 +09:00
Jonas Schips
86a3c19547 feat: added whitespaces between "fps"/"ms" and it's value in FPSCounterTooltip.cs 2026-01-06 13:37:10 +01:00
Jonas Schips
53a5157a2f feat: added whitespace between "fps"/"ms" and it's value to prevent clipping 2026-01-06 13:10:34 +01:00
Dan Balasescu
679c6d0229 Remove Masking definition
Co-authored-by: Bartłomiej Dach <dach.bartlomiej@gmail.com>
2026-01-06 17:25:08 +09:00
Dan Balasescu
86220622fb Remove HoldForMenuButton player dependency 2026-01-06 17:20:28 +09:00
Bartłomiej Dach
38360fde7b Merge pull request #36235 from smoogipoo/adjust-beatmap-query
Adjust online beatmap query to fix potential crash
2026-01-06 09:01:34 +01:00
Dean Herbert
61d0b5a03c Update resources 2026-01-06 15:01:28 +09:00
Salman Alshamrani
08364a7b97 Support text wrapping in form controls 2026-01-05 16:13:56 -05:00
Salman Alshamrani
465ec88371 Expose switch button width 2026-01-05 16:13:37 -05:00
Salman Alshamrani
4f2dfccb0f Add test cases requiring text wrapping 2026-01-05 16:13:37 -05:00
Bartłomiej Dach
43e5aec16e Merge pull request #36232 from peppy/fix-editor-undo-crash
Fix editor crashing on undoing after hit object placement
2026-01-05 10:28:46 +01:00
Dan Balasescu
59b3afe5aa Hide add button when mod selects are shown
The important one is the required mod selection, whose overlay contents
overlap with the add button. The free mod selection would otherwise be
fine, if not for consistency.
2026-01-05 18:24:50 +09:00
Dan Balasescu
fad53d9134 Hide mods wedge when empty 2026-01-05 18:04:01 +09:00
Dan Balasescu
61c4a3dba0 Fix footer button overlap
The scheduled events don't fire because the content is hidden.
2026-01-05 17:56:45 +09:00
Dan Balasescu
8b49572c44 Fix title overlap
Uses the same method of displaying the mod selection as the old song
select (as an overlay of the entire game).
2026-01-05 17:44:49 +09:00
Dan Balasescu
78a5654e1f Adjust beatmap query to fix potential crash 2026-01-05 17:16:27 +09:00
Dan Balasescu
2b90cbf59f Attempt to fix display issues at high UI scales 2026-01-05 16:46:29 +09:00
pishifat
ea289460b0 remove map from list (#36218) 2026-01-05 16:16:04 +09:00
Denis Titovets
ff3397b6b7 Localise notifications in OnlineStatusNotifier (#36223)
Co-authored-by: Dean Herbert <pe@ppy.sh>
2026-01-05 16:15:01 +09:00
Denis Titovets
a4fe0c924d Localise DownloadNotification (#36224)
Co-authored-by: Dean Herbert <pe@ppy.sh>
2026-01-05 16:12:20 +09:00
Dean Herbert
33e49442c6 Merge pull request #36225 from diquoks/localisation/legacy-collection-importer
Localise notifications in `LegacyCollectionImporter`
2026-01-05 16:03:28 +09:00
Dean Herbert
d0ac5cdeae Merge pull request #36231 from smoogipoo/adjust-qp-metrics
Adjust quick play player panels for long usernames
2026-01-05 15:57:49 +09:00
Dan Balasescu
23c595a136 Merge branch 'master' into mp-require-hold-to-exit 2026-01-05 15:30:52 +09:00
Dean Herbert
9916e5bb1c Fix editor crashing on undoing after hit object placement
Happens when initial timing is undone.

Regressed in
12170df80a.
Closes https://github.com/ppy/osu/issues/36228.
2026-01-05 15:26:14 +09:00
Dean Herbert
25dab7258c Add test coverage of editor placement scenarios 2026-01-05 15:24:15 +09:00
Dan Balasescu
b7b3e028ab Adjust quick play player panels for long usernames 2026-01-05 15:22:06 +09:00
Dan Balasescu
88f80799c3 Add test 2026-01-05 15:21:49 +09:00
Dean Herbert
5839209d6f Merge pull request #36227 from minetoblend/fix-editor-broken-for-spartans
Fix `OsuAutoGenerator` failing to alternate when objects are exactly 50ms apart
2026-01-05 14:54:50 +09:00
Jul
ff820f730e Fix play button starting wrong beatmap before selection loads (#36104)
* Fix play button starting wrong beatmap before selection loads

When clicking the osu! cookie (play button) before a newly selected beatmap finishes loading, the previous beatmap would be played instead of the currently selected one. 

This was caused by the cookie reading from the global beatmap state which is debounced by 150ms, while the Enter key correctly used the carousel's current selection.

The fix makes the cookie use the same beatmap source as Enter - the carousel's current selection - which is always up-to-date regardless of debounce timing.

Closes #36074

* Use ensureGlobalBeatmapValid() for logo and Enter key actions

* Add test for beatmap selection timing bug

Tests the fix for issue #36074 where clicking the play button immediately after selecting a different difficulty would start the wrong beatmap due to the 150ms selection debounce.
2026-01-05 14:54:25 +09:00
Dan Balasescu
593cc37ea6 Adjust styling of freestyle button 2026-01-05 14:35:34 +09:00
Dan Balasescu
ebf9c19ad3 Revert no longer necessary change
Additionally to fix the options button, I could either cache the
interface in PlaylistsSongSelectV2 or make the interface cache itself. I
went with the latter option.
2026-01-05 14:10:26 +09:00
Dean Herbert
af08416880 Add test coverage 2026-01-05 13:17:40 +09:00
Marvin
3bd2050954 Fix interpolation not being applied when previous frame-up frame is at the same time as current frame 2026-01-05 03:23:01 +01:00
Marvin
351a717795 Alternate buttons in OsuAutoPlay generator even when time difference is exactly zero.
Previously, there if two consecutive hitobjects were 50ms apart both mechanisms to make sure that the input buttons are alternated would fail.
This produced a replay frame which lifts the current button, followed by a frame which presses the same button again, at the same time as it was lifted.
The key-up frame would always get skipped without frame-stability, leading to hitsounds and hit animations not getting played in the editor.
2026-01-05 03:23:01 +01:00
Denis Titovets
00560cbfba Localise notifications in LegacyCollectionImporter 2026-01-04 20:54:50 +03:00
Dean Herbert
b6dc64668e Merge pull request #36198 from diquoks/localisation/friend-presence-notifications
Localise friend presence notifications
2026-01-04 02:22:13 +09:00
Dean Herbert
e0c4592dc7 Merge pull request #36160 from smoogipoo/qp-beatmap-attributes
Add beatmap attributes to quick play panels
2026-01-02 23:05:02 +09:00
Dean Herbert
1fcae16be9 Update framework 2026-01-02 16:39:37 +09:00
Denis Titovets
212973bd63 Localise friend presence notifications 2026-01-01 21:26:48 +03:00
Dean Herbert
5cba990940 Merge pull request #36194 from frenzibyte/fix-casing
Fix casing in random selection algorithm dropdown
2026-01-02 02:58:32 +09:00
Dean Herbert
974d3fa5e1 Merge pull request #36197 from diquoks/quick-fix/incorrect-button-dim-colour
Fix incorrect `OsuAnimatedButton`'s `DimColour` for child classes without override
2026-01-02 02:56:21 +09:00
Denis Titovets
2fc621b4f3 Fix incorrect OsuAnimatedButton's DimColour
for child classes without override
2026-01-01 20:48:38 +03:00
Dean Herbert
ff4c281bd9 Merge pull request #36180 from diquoks/quick-fix/multiple-friends-notifications
Display an old-style notification if multiple users should be displayed
2026-01-02 00:36:15 +09:00
Salman Alshamrani
833617e279 Fix casing in random selection algorithm dropdown 2026-01-01 08:42:41 -05:00
Dean Herbert
e539660b14 Attempt to fix flaky test 2026-01-01 19:41:14 +09:00
Dean Herbert
d204b79342 Merge pull request #36188 from bdach/random-branch-name-i-cant-even-whatever
Fix things breaking when attempting to exit external beatmap edit screen when it's finishing up
2026-01-01 16:38:10 +09:00
Dean Herbert
54f9360814 Merge pull request #36191 from frenzibyte/fix-account-button-spacing
Fix button spacing in account creation overlay
2026-01-01 16:15:15 +09:00
Salman Alshamrani
19cfe9abe6 Fix button spacing in account creation overlay 2025-12-31 11:28:19 -05:00
Dean Herbert
0a2fc12061 Fix/remove tests broken by screen behaviour change 2026-01-01 00:29:04 +09:00
Dean Herbert
e1ce1e3188 Merge branch 'master' into new-playlists-song-select 2026-01-01 00:29:02 +09:00
Dean Herbert
464cc23393 Merge pull request #36175 from bdach/show-all-difficulties-set-panel
Add ability to view other beatmaps in set when difficulties are grouped by set
2025-12-31 23:36:40 +09:00
Dean Herbert
338b987ea9 Merge pull request #36172 from ILW8/feature-upstream/last-year-seed-string
Change last year placing from integer to string in tournament overlay
2025-12-31 22:49:54 +09:00
Bartłomiej Dach
04bd381ee3 Fix number conversion setter hack used by LastYearPlacing 2025-12-31 12:33:31 +00:00
Bartłomiej Dach
affe295f50 Fix missing disposal 2025-12-31 13:03:36 +01:00
Bartłomiej Dach
bd29c46bd7 Fix tests 2025-12-31 12:53:59 +01:00
Bartłomiej Dach
28a6d6211c Manually fire editor save operations on skin change
This is invariably a test fix but also a valid change to make in
isolation.

In short, the problem is thus: Now that the beatmap skin is hooked up to
realm to receive notifications about changed files (which is required
for correctly handling custom samples), tests started failing, because
of the following sequence of events

- Saving a brand new `.osu` to a beatmap set causes
  `RealmBackedResourceStore` to fire subscription callbacks because a
  new file was added to the set
- This fires `RealmBackedResourceStore.CacheInvalidated`
- Which fires `EditorBeatmapSkin.BeatmapSkinChanged`
- Which would previously fire `EditorBeatmap.SaveState()` and as such
  mark the beatmap dirty / modified.

In this scenario this is gratuitous. There's no need to be raising save
states here, a new `.osu` was added to the set that is in a consistent
saved state and nothing actually changed in the beatmap.

However it does not appear sane to attempt to circumvent this with
conditional guards or something, because in cases where files are
added/removed from the set, *there isn't really any reason to take save
states anyway*. The change handler only deals with the `.osu`, any
modifications to any of the other files cannot be undone anyway.

Therefore, only keep the state save to the one change to beatmap skin
that *can* actually be sanely undone which is changing combo colours.
2025-12-31 12:14:32 +01:00
Bartłomiej Dach
334a54e6f7 Add way to add/remove custom beatmap samples to setup screen
Among others, this features a scary-looking change wherein
`WorkingBeatmap` now exposes a `RealmAccess` via
`IStorageResourceProvider`. This is necessary to make
`RealmBackedResourceStore` actually start firing callbacks when the
edited beatmap's skin is modified by adding new samples to it.
2025-12-31 12:14:28 +01:00
Bartłomiej Dach
a6545bea68 Remove dim effect from disabled spread displays on song select panels 2025-12-31 10:05:59 +01:00
Bartłomiej Dach
7e823af70b Hide back button when finishing external edit 2025-12-31 09:26:08 +01:00
Bartłomiej Dach
9f40d630dc Do not allow multiple concurrent finishes of external beatmap edit 2025-12-31 09:26:00 +01:00
Bartłomiej Dach
1cf14952de Privatise setter of weirdly exposed field 2025-12-31 09:25:42 +01:00
Bartłomiej Dach
2d119f11ec Merge pull request #36177 from peppy/debounce-seeks
Fix editor seeks not being debounced enough
2025-12-31 08:15:35 +01:00
Denis Titovets
dad138223b Small refactoring 2025-12-30 19:36:43 +03:00
Denis Titovets
a23024d80e Display an old-style notification if multiple users should be displayed 2025-12-30 18:54:28 +03:00
Dean Herbert
9f57be7410 Merge pull request #36176 from bdach/scoped-beatmap-set-display-click-anywhere
Allow clicking anywhere on the scoped beatmap set indicator to dismiss it
2025-12-30 23:23:13 +09:00
Dean Herbert
853836a48b Fix editor seeks not being debounced enough
Could lead to huge slowdowns when running multi-threaded and performing
a long drag.
2025-12-30 23:17:47 +09:00
Bartłomiej Dach
24a0de1020 Allow clicking anywhere on the scoped beatmap set indicator to dismiss it 2025-12-30 13:40:27 +01:00
Bartłomiej Dach
ebb898f67c Add test case 2025-12-30 12:40:06 +01:00
Bartłomiej Dach
e2ed5208fe Bring back ruleset grouping & overflow handling to spread display 2025-12-30 12:39:50 +01:00
Bartłomiej Dach
e07b828f30 Add support for showing which beatmaps in a set are currently filtered out 2025-12-30 12:39:32 +01:00
Bartłomiej Dach
e2a245b049 Add minimal spread display control for beatmap set panels 2025-12-30 12:39:18 +01:00
Bartłomiej Dach
57cbe20c12 Merge pull request #36162 from peppy/update-framework
Update framework
2025-12-30 08:59:18 +01:00
Bartłomiej Dach
646e6de5df Merge pull request #36113 from diquoks/localisation/settings
Localise various strings in settings
2025-12-30 07:35:22 +01:00
ILW8
191ec072a8 Add conversion from 0 last year placing value to new default "N/A" 2025-12-30 02:48:41 +00:00
ILW8
1cff386b3a Adjust seed and last year placing textbox positioning in team editor 2025-12-30 02:48:40 +00:00
ILW8
71be574c2b Update seeding screen tests to include last year placing 2025-12-30 01:38:39 +00:00
ILW8
6e41332ea3 Change last year placing from integer to string 2025-12-30 01:37:44 +00:00
Dean Herbert
3c084c60aa Fix iOS compilation 2025-12-30 01:27:08 +09:00
Bartłomiej Dach
3b9b030aad Merge pull request #36161 from peppy/editor-no-smooth-seeking
Change editor to not seek smoothly when performing distant seeks
2025-12-29 12:43:45 +01:00
Dean Herbert
d421678d34 Update framework 2025-12-29 20:18:18 +09:00
Denis Titovets
056d832371 Move "Random" string to UserInterfaceStrings 2025-12-29 14:12:02 +03:00
Bartłomiej Dach
4e83814d5a Merge pull request #36114 from diquoks/localisation/play-v2
Localise various strings on `Play` screen (again)
2025-12-29 11:57:33 +01:00
Dan Balasescu
8ff2329e22 Fix tests 2025-12-29 19:13:39 +09:00
Bartłomiej Dach
6bf25d700c Merge pull request #36159 from peppy/fix-now-playing-overlay-weird
Fix a couple of edge cases when interacting with the now playing overlay using keyboard control
2025-12-29 10:22:43 +01:00
Bartłomiej Dach
5163b8f5f3 Merge pull request #36158 from peppy/reduce-audio-debounce-time
Reduce audio seek debounce time downwards
2025-12-29 10:08:50 +01:00
Dan Balasescu
2e324fe856 Show vote count in tag tooltip 2025-12-29 17:47:35 +09:00
Dean Herbert
8347f83f9c Change editor to not seek smoothly when performing distant seeks
This reverts to the way stable does things. Smooth seeking is nice and
all, but slows things down and doesn't give the instant reponse that
mappers are used to.

This should fix some performance issues regarding seeking as it no
longer tries to render large portions of the map during the seek
operation.

Note that I've also forced the summary timeline to always non-smooth
seek. It was bugging out in weird ways when doing smooth seeks and I
don't want to attempt to fix it.
2025-12-29 17:44:03 +09:00
Dan Balasescu
e34d266987 Fix code style 2025-12-29 17:37:57 +09:00
Dan Balasescu
a6e1713514 Fix panel depth 2025-12-29 17:29:21 +09:00
Dan Balasescu
10ebb5286f Adjust metrics 2025-12-29 17:29:21 +09:00
Dan Balasescu
69fee16eee Add top tag and difficulty attributes 2025-12-29 16:20:14 +09:00
Dean Herbert
f3fa1122c2 End christmas 2025-12-29 16:07:14 +09:00
Dean Herbert
0f018988b5 Fix double seek when using keyboard with now playing overlay seek bar 2025-12-29 16:04:24 +09:00
Dean Herbert
4a60d7fe71 Fix now playing overlay responding to key events when seek bar is not visually focused 2025-12-29 16:04:08 +09:00
Dean Herbert
69250b3260 Reduce audio seek debounce time downwards
I found this way too high in the editor, not giving enough feedback when
seeking.

To recap, this was put in place to avoid glitchy sounding audio. Such
glitchiness only occurs with *very* fast seeks. This is still well
within sane range.
2025-12-29 16:03:20 +09:00
Dan Balasescu
60d9c358b8 Move top user tags enumeration to helper 2025-12-29 15:43:43 +09:00
Dean Herbert
ef3338ba93 Merge pull request #36140 from nbvdkamp/add-fps-settings-keywords
Add keywords to FPS related settings
2025-12-27 23:11:58 +09:00
Nathan van der Kamp
99b00ab72f Add keywords to FPS related settings 2025-12-27 00:29:25 +01:00
Dean Herbert
56ef5eae14 Revert "Merge pull request #36102 from bdach/move-check-to-better-place"
This reverts commit 2cb2167765, reversing
changes made to 0bcb3c5839.
2025-12-26 22:36:16 +09:00
Dean Herbert
2cb2167765 Merge pull request #36102 from bdach/move-check-to-better-place
Perform online ID checks during import in more proper sequence
2025-12-26 22:35:02 +09:00
Dean Herbert
206578bf10 Always flash button when adding a new item to playlist 2025-12-25 21:39:17 +09:00
Dean Herbert
36de88f399 Merge branch 'master' into new-playlists-song-select 2025-12-25 20:26:35 +09:00
Dean Herbert
0bcb3c5839 Merge pull request #36103 from bdach/form-sample-set
Implement form control for adding/removing custom samples in editor
2025-12-25 15:11:33 +09:00
Dean Herbert
2acc2a84db Merge pull request #36116 from frenzibyte/new-settings/form-visuals
Prepare form controls for use in settings
2025-12-25 03:16:12 +09:00
Dean Herbert
64d16776be Merge pull request #36118 from bdach/zip-magic 2025-12-25 02:29:18 +09:00
Dean Herbert
2d06c2b1a6 Merge pull request #36121 from smoogipoo/mp-fix-availability-tracker
Fix potentially incorrect online play beatmap availability
2025-12-24 20:32:46 +09:00
Dan Balasescu
81f03957d8 Reduce code duplication
Co-authored-by: Dean Herbert <pe@ppy.sh>
2025-12-24 19:30:04 +09:00
Dan Balasescu
ecaf3e05d4 Fix tests 2025-12-24 16:53:08 +09:00
Salman Alshamrani
2d85fe8133 Fix tournament screens not having colour provider cached 2025-12-24 02:22:59 -05:00
Dan Balasescu
0af07b97c5 Fix potentially incorrect online play beatmap availability 2025-12-24 15:08:37 +09:00
Dean Herbert
03f44705b3 Adjust naming 2025-12-24 14:57:17 +09:00
Salman Alshamrani
033a73e969 Update switch button visuals 2025-12-23 20:18:59 -05:00
Dean Herbert
1340e18d93 Merge pull request #36101 from bdach/guest-difficulty-export
Add explicit menu item for exporting guest difficulties from editor
2025-12-24 00:45:19 +09:00
Denis Titovets
43e217120d Fix offset not changing using current culture 2025-12-23 14:51:31 +03:00
Denis Titovets
dc53198577 Make edits based on reviews 2025-12-23 14:36:12 +03:00
Salman Alshamrani
95cf050298 Make setter protected to fix build error 2025-12-23 06:31:26 -05:00
Denis Titovets
0bfff2bf88 Make edits based on reviews 2025-12-23 14:27:34 +03:00
Salman Alshamrani
fa32be37d6 Expose constant for max decimal digits display 2025-12-23 06:14:48 -05:00
Salman Alshamrani
e4d7bc3896 Seal tooltip text overrides 2025-12-23 06:11:05 -05:00
Salman Alshamrani
520baf6c50 Add explanatory note 2025-12-23 06:11:05 -05:00
Salman Alshamrani
a1d4d44151 Revert unintended change 2025-12-23 06:11:05 -05:00
Salman Alshamrani
a3a5b2ca92 Fix tests not having colour providers 2025-12-23 05:58:57 -05:00
Salman Alshamrani
cb32d2da3e Make sound playback consistent across all disabled controls 2025-12-23 05:56:17 -05:00
Salman Alshamrani
4ee7e6f787 Add label and tooltip formatting to form sliders 2025-12-23 05:56:17 -05:00
Salman Alshamrani
c2c1fa4d4c Add right margin to form dropdown chevron 2025-12-23 05:56:17 -05:00
Salman Alshamrani
339aad42a7 Use switch button for form checkbox controls 2025-12-23 05:56:17 -05:00
Salman Alshamrani
a2872ee870 Update "disabled" visual feedback of form controls 2025-12-23 05:56:17 -05:00
Salman Alshamrani
c6a8d9a150 Add extra form controls in test scene 2025-12-23 05:56:17 -05:00
Dean Herbert
6eec2b0ff8 Merge pull request #36081 from bdach/esdfiopj
Fix cursor scale affecting trail spacing
2025-12-23 19:48:34 +09:00
Dean Herbert
0999e3f048 Merge pull request #36047 from bdach/reveal-other-difficulties-thing
Add ability to view other beatmaps in set when difficulties are split apart
2025-12-23 19:43:47 +09:00
Bartłomiej Dach
71386fe465 Merge pull request #36056 from peppy/fix-stacking-woes
Fix osu! stacking not matching stable in very specific edge cases
2025-12-23 08:45:40 +01:00
Bartłomiej Dach
f58e4e4862 Use sane initial value for cursor scale to avoid infinite loops 2025-12-23 08:43:07 +01:00
Bartłomiej Dach
0bebabba94 Replace spread operator usage with manual linq
`dotnet format` expects me to put a space between the spread `..` and
the expression after which is UTTERLY STUPID AND UGLY AND WRONG AND
CANNOT BE `.editorconfig`'d BECAUSE [INSERT EXCESSIVELY LONG BLEEP].

God I hate when good features are kneecapped by this sort of stupidity.
2025-12-23 08:35:20 +01:00
Bartłomiej Dach
95233fc638 Fix replay files potentially getting misclassified as zip archives
Resolves https://github.com/ppy/osu/discussions/36107.

The replay linked in the aforementioned discussion happens to contain
the magic sequence for ZIP headers in the binary stream (50 4b 05 06)
which led to it getting classified as a ZIP with no entries and
completely bogus everything in the header at

	c8b18acd4d/osu.Game/Database/ImportTask.cs (L51-L56)

and then finally dying of

	2025-12-23 07:26:45 [error]: [?????] Model creation of replay-osu_2040232_4828629841.osr failed.
	2025-12-23 07:26:45 [error]: System.InvalidOperationException: Sequence contains no matching element
	2025-12-23 07:26:45 [error]: at System.Linq.ThrowHelper.ThrowNoMatchException()
	2025-12-23 07:26:45 [error]: at System.Linq.Enumerable.First[TSource](IEnumerable`1 source, Func`2 predicate)
	2025-12-23 07:26:45 [error]: at osu.Game.Scoring.ScoreImporter.CreateModel(ArchiveReader archive, ImportParameters parameters)
	2025-12-23 07:26:45 [error]: at osu.Game.Database.RealmArchiveModelImporter`1.importFromArchive(ArchiveReader archive, ImportParameters parameters, CancellationToken cancellationToken)

at

	554961036e/osu.Game/Scoring/ScoreImporter.cs (L46)
2025-12-23 08:23:23 +01:00
Denis Titovets
277e00c74e Localise various strings on Play screen (again) 2025-12-23 03:21:08 +03:00
Denis Titovets
a25f1cde25 Localise various strings in settings 2025-12-23 02:36:54 +03:00
Dean Herbert
2ed37ecbbc Merge branch 'master' into esdfiopj 2025-12-23 02:33:08 +09:00
Dean Herbert
554961036e Merge pull request #36080 from bdach/form-slider-bar-disabled
Fix disabled form slider bars being half-baked
2025-12-23 02:26:34 +09:00
Dean Herbert
dbcc7cc09d Merge branch 'master' into form-slider-bar-disabled 2025-12-22 23:44:33 +09:00
Dean Herbert
d9dad4c3a8 Merge pull request #36055 from frenzibyte/new-settings/item
Implement new settings item component
2025-12-22 22:55:53 +09:00
Dean Herbert
0787345a84 Adjust colour of inactive slider bar 2025-12-22 22:06:39 +09:00
Bartłomiej Dach
9d07ad2761 Implement form control for adding/removing custom samples in editor
The test scene doesn't exercise the custom sample playback, but I hope I
can be forgiven for this as setting up a custom editor beatmap just for
this to work is rather cumbersome.
2025-12-22 13:48:31 +01:00
Bartłomiej Dach
1ab922af12 Fix test 2025-12-22 12:48:53 +01:00
Bartłomiej Dach
44df9047ae Only perform replace-on-import based on online beatmap set ID after it's validated
Even without the ID resetting logic before `Populate()` getting broken
and annotated with a TODO, this was kinda stupid. Why was purging logic
allowed to run *using a not-yet-completely-validated online ID of the
set*?

This is the other part of hopefully fixing scenarios like
https://osu.ppy.sh/community/forums/topics/2162457?n=8 (hopefully with
no babies poured out in the mean time, but only time will tell).
2025-12-22 12:27:29 +01:00
Bartłomiej Dach
17143ca0a8 Move online ID validation check into its proper location
The end of `Populate()` is too early to do any validation of online IDs,
as population happens in `PostImport()` via `ProcessBeatmap`.

Additionally, this modifies the check to also include the set's ID. This
is one part of curtailing issues like
https://osu.ppy.sh/community/forums/topics/2162457?n=8 - the featured
artist template beatmap in question has a single difficulty with a bogus
positive beatmap set ID and a beatmap ID of 0, and my opinion is that
this sort of situation should for all intents and purposes cause the
beatmap set's ID to be reset.
2025-12-22 12:22:04 +01:00
Bartłomiej Dach
3712093158 Add explicit menu item for exporting guest difficulties from editor
A few facts of life:

- Guest difficulties are at this point a staple of mapping.

- People are very much used to flinging `.osu`s around (because there's
  no better alternative).

- Currently there are two ways to get an `.osu` out of lazer. You can:

  - Export the beatmap as "compatibility" to an `.osz`, then
    transmogrify the `.osz` to a `.zip`, then extract the `.zip`, then
    pluck out the `.osu`. This is the "correct" way to make sure stable
    works, but is also stupidly arcane.

  - Use "edit externally" to mount the beatmap files to disk, then
    copy-paste out the `.osu`. This is the *wrong* way to make sure
    stable works, because the mounting process exposes the raw "for
    editing" format with features stable doesn't support, but it the
    actual easy one.

- Reports about guest difficulties exported from lazer "working wrong on
  stable" are prevalent. Probably mostly because of the preceding point.

What this PR does is introduce a *third* method to export an `.osu`,
which is designed to be both the easiest one yet *and* correct. I am
hoping this will curb the complaints until support for direct submission
of guest difficulties is added - which I still hope to see, but it will
be a significant effort *client-side* (the server side has been ready
for years now).

And yes, you will notice that much of the code added in
`LegacyBeatmapExporter` related to manipulation of the path is
copy-pasted from `LegacyExporter`. I don't care enough to invent
protected / abstract / whatever else OOP faff for something that may not
survive review and is mostly a weird semi-temporary wart.
2025-12-22 11:36:23 +01:00
Bartłomiej Dach
df524d68fd Move disable to better place 2025-12-22 10:04:29 +01:00
Bartłomiej Dach
daaea093f0 Add another failing test 2025-12-22 10:04:18 +01:00
Dean Herbert
469aa7b303 Merge pull request #36021 from tsunyoku/rank-mania-cover
Set `Ranked` to `true` for `ManiaModCover`
2025-12-22 13:45:21 +09:00
Bartłomiej Dach
e094eefafb Fix cursor scale affecting trail spacing
Closes https://github.com/ppy/osu/issues/36061.

Regressed in https://github.com/ppy/osu/pull/35802.

It's either this or revert of aforementioned pull. I dunno.
2025-12-19 14:27:55 +01:00
Bartłomiej Dach
4cce6aa123 Address review concerns 2025-12-19 13:59:26 +01:00
Bartłomiej Dach
bba4329ab5 Remove now-redundant tests
They all pass along with the last stacking threshold fix, so I see no
reason to keep them in.
2025-12-19 13:16:14 +01:00
Bartłomiej Dach
25ba3f2dc6 Fix yet another incorrect threshold check in stacking code 2025-12-19 13:16:12 +01:00
Bartłomiej Dach
ecc51d4701 Fix disabled form slider bars being half-baked
In order of severity:

- You could actually click on the textbox portion of a disabled textbox,
  focus it, select text, input stuff, and commit, which would die
  on the spot.

- The slider part had no visual indication that it's not interactable
  anymore.
2025-12-19 12:17:46 +01:00
Bartłomiej Dach
32454caaa9 Add failing test 2025-12-19 12:17:06 +01:00
Bartłomiej Dach
0f53886784 Fix stack threshold being calculated in a different way than stable 2025-12-19 10:19:20 +01:00
Bartłomiej Dach
f598d8b108 Add extended conversion mapping coverage for more failing cases
Some are not immediately relevant to the stacking issue because they
fail both before and after it, just less so after the stacking issue
(half-)fix, and as such have been commented out for the time being.
2025-12-19 10:19:20 +01:00
Salman Alshamrani
f273163e58 Improve revert button animation 2025-12-19 02:27:04 -05:00
Bartłomiej Dach
6de1f0cd4d Merge pull request #36060 from peppy/collection-dialog-improve
Improve manage collections dialog usability
2025-12-19 07:53:45 +01:00
Salman Alshamrani
c475b4bc4b Add explanatory note for ClearTransforms usage 2025-12-18 20:04:02 -05:00
Salman Alshamrani
276757315f Simplify classic default flow 2025-12-18 20:03:38 -05:00
Salman Alshamrani
a82d0c3b51 Rename property to have consistent name 2025-12-18 20:01:36 -05:00
Salman Alshamrani
4fee1547e3 Use menu item labels as filter terms instead 2025-12-18 19:48:42 -05:00
Salman Alshamrani
a4215e0442 Add tooltip to revert-to-default button 2025-12-18 18:56:18 -05:00
Bartłomiej Dach
577cf7d1e7 Remove invalid xmldoc reference 2025-12-18 14:49:30 +01:00
Dean Herbert
09178b5dc4 Add helper method and update some other similar preempt calculations 2025-12-18 22:28:50 +09:00
Dean Herbert
16c4967ee4 Fix added item not scrolling into view immediately 2025-12-18 22:03:19 +09:00
Dean Herbert
e526de7f40 Move "Create new collection" out of scroll content 2025-12-18 22:03:19 +09:00
Dean Herbert
93a64dad89 Adjust corner radius and design slightly 2025-12-18 21:39:10 +09:00
Dean Herbert
897cb2c1a9 Merge pull request #35430 from bdach/custom-sample-set-selection
Add sample set displays & selection controls to editor
2025-12-18 21:01:19 +09:00
Dean Herbert
de0c191c47 Update resources 2025-12-18 20:53:42 +09:00
Salman Alshamrani
61874e59e0 Extend classic default test coverage 2025-12-18 06:17:48 -05:00
Dean Herbert
13aeed15f9 Remove button sound from sample toggle buttons
The new ones added in this change don't play the UI sounds, likely
because they make it nigh impossible to actually hear what the gameplay
samples will sound like.

This removes the same sounds from the existing buttons to match.
2025-12-18 20:17:46 +09:00
Salman Alshamrani
094860bfea Define delegate for applying classic defaults 2025-12-18 06:17:07 -05:00
Dean Herbert
44f535dcef Fix osu! stacking not matching stable in very specific edge case
Closes https://github.com/ppy/osu/issues/36052.

Not much more to say here. Until now the `PreEmpt` typing discrepancy
has likely gone unnoticed since it's usually only used in visual usages.
2025-12-18 20:08:56 +09:00
Salman Alshamrani
f5bd888078 Remove Value from {Is,Set}ValueDefault 2025-12-18 05:50:35 -05:00
Salman Alshamrani
90d7615432 Limit form dropdown height by default 2025-12-18 05:19:54 -05:00
Salman Alshamrani
7a32e0eebf Add test coverage 2025-12-18 05:19:54 -05:00
Salman Alshamrani
78043fa782 Implement new settings item component 2025-12-18 05:19:54 -05:00
Salman Alshamrani
729cd722bd Expose common interface for form controls 2025-12-18 05:15:01 -05:00
Salman Alshamrani
8da1fde981 Add osu!-style undo icon 2025-12-18 04:08:16 -05:00
Dean Herbert
1de97712f1 Merge pull request #36025 from smoogipoo/qp-ui-scale
Remove UI scaling in quick play
2025-12-18 16:01:39 +09:00
Dean Herbert
83ddcf7b02 Fix github side inspection 2025-12-18 16:00:23 +09:00
Dan Balasescu
2e7edf8c1f Also apply to queue screen 2025-12-18 14:07:58 +09:00
Dean Herbert
61ecd18d24 Merry christmas 2025-12-18 02:20:13 +09:00
Bartłomiej Dach
9e67cf503b Merge pull request #36043 from peppy/more-stable-editor-seeking
Adjust editor seeking to be fairer when track is playing
2025-12-17 13:57:47 +01:00
Bartłomiej Dach
62fd61def4 Merge pull request #36042 from peppy/remove-beat-sync-break-overlay
Remove beat sync from skip / break overlay
2025-12-17 13:34:04 +01:00
Bartłomiej Dach
c120f0ebab Fix license header 2025-12-17 13:20:51 +01:00
Bartłomiej Dach
5bf6a75b50 Add ability to view other beatmaps in set when difficulties are split apart
First part of https://github.com/ppy/osu/issues/36039, see also
https://github.com/ppy/osu/discussions/33784.

The goal is to add this back to the set panels too, but that one is more
complicated than this if you can believe it (because it requires being
able to tell which other difficulties of the set are filtered out to
fade them out). I also foresee the display logic to vary significantly
there.

Of note, for this to work in beatmaps-split-apart mode, this requires a
change of behaviour in the filtering logic. Old song select when given a
"selected beatmap set" would *include* that in results regardless of the
filter, but that doesn't work for beatmaps-split-apart for reasons that
are hopefully obvious, so this changes the behaviour to *entirely
bypass* the filter and just show the set. Unsure how angry will people
be with that.

Also of note, when in the scoped mode, altering any filter criteria will
dismiss the scope.
2025-12-17 12:28:20 +01:00
Dean Herbert
b71a26fcec Adjust tray to feel better 2025-12-17 20:19:19 +09:00
Dean Herbert
5f9639fc3f Fix inspections 2025-12-17 19:32:07 +09:00
Dean Herbert
61bf68f336 Merge branch 'master' into custom-sample-set-selection 2025-12-17 19:30:24 +09:00
Dean Herbert
f581f85eb2 Adjust editor seeking to be fairer when track is playing
The previous multiplier was supposed to account for seeking backwards
being "slower" because the track is playing forwards, but it really
didn't work amazingly.

Rather than trying to pull off some magic, let's just ensure that seeks
in both directions feel correct, even if that means that time gradually
moves forwards.

Closes https://github.com/ppy/osu/issues/35998 (mostly).

Of note, this still doesn't match stable completely. I attempted to
implement the full stable seeking algorithm but it's objectively worse
in other scenarios, so I'd rather just tweak what we have until the
majority of users are happy.
2025-12-17 18:50:14 +09:00
Dean Herbert
b07cd2daa4 Remove beat sync from skip / break overlay
This didn't end up feeling as good as I hoped. Will revise at a later
stage, adding beat sync in another way that isn't jank transforms on the
progress bars.

Also closes https://github.com/ppy/osu/issues/35972.
2025-12-17 16:38:46 +09:00
AV
3b635f6919 Prevent mod track adjustments from modifying BGM speed in queue (#36027)
* Fix(Matchmaking): Prevent mod track adjustments from applying BGM speed in queue

* replaced modification of MusicController directly with a property change.

* formatted
2025-12-17 14:40:08 +09:00
Dean Herbert
07817dce70 Expose notification main content for external use 2025-12-17 14:28:08 +09:00
Dan Balasescu
67e0348fa8 Merge branch 'master' into new-playlists-song-select 2025-12-17 13:33:34 +09:00
Dean Herbert
734c6f933d Merge pull request #36020 from bdach/fail-indicator-right-sound
Fix replay fail indicator not using fail sample from beatmap skin
2025-12-17 01:15:22 +09:00
Dean Herbert
ac213c90cb Merge pull request #36023 from bdach/report-message-pm
Adjust message on successful report to match bancho
2025-12-16 23:52:31 +09:00
Bartłomiej Dach
74ca87c252 Merge pull request #36009 from frenzibyte/fix-skip-button
Fix skip overlay potentially not allowing skipping
2025-12-16 14:58:43 +01:00
Dan Balasescu
190b5535d7 Remove UI scaling in quick play 2025-12-16 21:21:52 +09:00
Bartłomiej Dach
032912e62b Adjust message on successful report to match bancho
Addresses https://github.com/ppy/osu/discussions/36004.

Not adding localisation because the previous implementation was
`.ToString()`ing anyway.

Would have made the abuse e-mail a link but `mailto:` doesn't work with
`MessageFormatter` and I don't want to go into that right now.

The message *almost* matches stable. The "almost" is because it doesn't
mention the `/ignore` chat command. I was just going to implement the
command, but I went to check what it does, and backed away slowly
because it has like weird scoping to chat, highlights, and PMs, so
`nope.avi`.
2025-12-16 11:20:54 +01:00
James Wilson
83f9eba1d1 Set Ranked to true for ManiaModCover 2025-12-16 09:28:30 +00:00
Bartłomiej Dach
1e79c56240 Fix replay fail indicator not using fail sample from beatmap skin
Closes https://github.com/ppy/osu/issues/36003.

The duplicated `RulesetSkinProvidingContainer` is unfortunate but it's
either this or I start doing proxy shenanigans.
2025-12-16 09:48:10 +01:00
Dean Herbert
82256ae2de Merge pull request #34807 from mcendu/legacy-pp-counter
Add "legacy" pp counter
2025-12-15 20:24:04 +09:00
Dean Herbert
1142be45ec Update resources 2025-12-15 19:22:11 +09:00
Dean Herbert
dcb6d71287 Adjust constant and documentation slightly 2025-12-15 19:07:46 +09:00
Salman Alshamrani
881a35b382 Fix skip overlay potentially not allowing skipping 2025-12-15 03:54:09 -05:00
Dean Herbert
89d8b402af Merge branch 'master' into legacy-pp-counter 2025-12-15 16:55:51 +09:00
Dean Herbert
1d351002df Merge pull request #36005 from smoogipoo/update-packages
Update localisation analyser packages
2025-12-15 16:15:37 +09:00
Dean Herbert
2606f3a0b5 Merge pull request #35980 from smoogipoo/qp-ux-fixes
Various minor quick play UX fixes
2025-12-12 19:20:17 +09:00
Dan Balasescu
1c463aa060 Automatically accept invitation in queue screen 2025-12-12 18:26:52 +09:00
Dan Balasescu
7853abe8aa Move to queue screen when clicking notification 2025-12-12 18:13:00 +09:00
Dan Balasescu
1aff418981 Reword waiting text 2025-12-12 17:57:50 +09:00
Dean Herbert
62e92bb242 Merge pull request #35971 from smoogipoo/fix-mp-screen-leave
Forcefully leave room on multiplayer exit
2025-12-12 17:06:20 +09:00
Dan Balasescu
79151ae5b4 Remove mention of exception that doesn't exist 2025-12-12 15:46:00 +09:00
Dan Balasescu
c17db2cdd0 Forcefully leave room on multiplayer exit 2025-12-11 20:07:44 +09:00
Dan Balasescu
bbdd70c843 Always perform leave room sequence 2025-12-11 20:07:36 +09:00
Bartłomiej Dach
4250a54245 Merge pull request #35969 from peppy/delay-loading-animation
Slightly delay song select leaderboard's loading placeholder to avoid flashing during local score retrieval
2025-12-11 11:54:27 +01:00
Bartłomiej Dach
40fdb8662e Merge pull request #35966 from peppy/fix-muting-after-gameplay-with-bad-network
Fix audio track potentially muting after gameplay with bad network
2025-12-11 10:57:45 +01:00
Dean Herbert
6ce8b0a4bc Slightly delay song select leaderboard's loading placeholder to avoid flashing during local score retrieval
Closes #35893.
2025-12-11 18:54:38 +09:00
Dean Herbert
b30047def6 Remove audio adjustments immediately on gameplay hotkey overlays
Closes #22164.
2025-12-11 17:57:07 +09:00
Bartłomiej Dach
0ffb86262f Merge pull request #35965 from peppy/debounce-seek-only-when-playing
Fix editor not seeking smoothly when paused
2025-12-11 08:54:36 +01:00
Dean Herbert
f71eb4b980 Debounce track seeks only when track is playing
This fixes the editor no longer seeking smoothly when paused.

Closes https://github.com/ppy/osu/issues/35963.
2025-12-11 16:00:13 +09:00
Dan Balasescu
1faf02e860 Update localisation analyser packages 2025-12-11 13:53:09 +09:00
Dean Herbert
d700375e55 Merge pull request #35843 from SollyBunny/master
Make tracked leaderboard score yellow again
2025-12-11 13:50:59 +09:00
Bartłomiej Dach
095a67c24e Fix dragging volume meter to adjust volume closing overlays if mouse is released outside of overlay content (#35940)
* Add failing test

* Fix dragging volume meter to adjust volume closing overlays if mouse is released outside of overlay content

Fixes https://osu.ppy.sh/community/forums/topics/2159553.
2025-12-11 13:47:08 +09:00
Bartłomiej Dach
86054497d0 Disable save replay on fail overlay when spectating (#35942)
"Closes" https://github.com/ppy/osu/issues/35920.

The button can't easily work anyway since it's not guaranteed that the
spectating user has all of the frames of the replay (think entering
spectate midway through a play).

This matches the results screen in spectator too.
2025-12-11 13:42:15 +09:00
Bartłomiej Dach
c4f7dee82b Fix skin editor sometimes dropping anchor/origin specification on paste (#35957)
* Add failing test for copy->paste not being idempotent

* Ensure all elements on default skins use fixed anchors

`UsesFixedAnchor` defaults to false, i.e. closest anchors. Combined with
manual anchor / origin specs on some drawables, this would get default
skins into impossible states wherein a drawable would use "closest
anchor" but also explicitly specify anchor / origin that aren't closest,
which horribly fails on attempting to copy and paste.

Frankly shocked this has gone unnoticed for this long, and I regret not
vetoing this "feature" more every time I see its tentacles spread to
produce breakage of levels yet unseen.

Does this commit contain major levels of suck? For sure. Do I have any
better ideas that wouldn't consist of a multi-day rewrite or deletion of
this "feature"? No.

* Fix skin editor always applying closest anchor / origin on paste regardless of whether the component uses fixed anchor

Self-explanatory. Should close https://github.com/ppy/osu/issues/29111
along with previous commit.
2025-12-11 13:40:48 +09:00
Bartłomiej Dach
22825f6509 Merge pull request #35958 from bdach/fix-storyboard-samples-not-playing-in-editor
Fix storyboard samples not playing in editor
2025-12-10 14:33:30 +01:00
Bartłomiej Dach
691e8bcd05 Fix storyboard samples not playing in editor
Closes https://github.com/ppy/osu/issues/35954.
2025-12-10 11:20:50 +01:00
Bartłomiej Dach
e68bab4f4b Merge pull request #35802 from Xiragi/gameplay-cursor-size-change
Fix cursor trail detaching from cursor when adjusting cursor scale
2025-12-10 07:34:08 +01:00
Bartłomiej Dach
9430a62af4 Merge pull request #35925 from rrex971/storyboard-alpha-overshoot-handling
Adjust alpha handling for values exceeding 1 for storyboard sprite transforms to match stable behavior.
2025-12-10 07:26:50 +01:00
Dean Herbert
bdac75e542 Merge pull request #31141 from DanielPower/screen-scaling-tablet-output
Scale tablet output size when UI Scaling mode is "Everything"
2025-12-10 01:40:46 +09:00
Dean Herbert
5c2df50714 Add test coverage of weird storyboard sprite behaviour 2025-12-09 19:53:02 +09:00
Bartłomiej Dach
887d280bfa Merge branch 'master' into gameplay-cursor-size-change 2025-12-09 11:51:06 +01:00
Dean Herbert
84db289779 Use modulus instead of previous solution to match stable more closely 2025-12-09 18:57:49 +09:00
Dean Herbert
4c0522b795 Update comment and fix formatting 2025-12-09 18:00:32 +09:00
Bartłomiej Dach
07ea9fe2a4 Merge branch 'master' into screen-scaling-tablet-output 2025-12-09 08:05:20 +01:00
Dan Balasescu
4b0017dfea Require hold-to-exit during multiplayer load 2025-12-08 16:33:55 +09:00
rrex971
f73307876e Also apply alpha logic to StoryboardAnimation sprites too. 2025-12-08 02:19:29 +05:30
rrex971
4e4aa44a02 Override sprite update method to handle alpha values > 1 like stable.
If alpha exceeds 1 during a sprite's alpha transform like in a FadeTo(), it will set it to 0 mimicking stable's behavior.
2025-12-08 02:17:35 +05:30
Chirag Mahesh
a6c001244f Redundant string interpolation 2025-12-06 11:18:18 +00:00
Chirag Mahesh
107098314a Move and refactor TestSceneGameplayCursorSizeChange into Ruleset Osu Tests 2025-12-05 17:23:31 +00:00
Chirag Mahesh
d1d76a76ba Refactor trail scale update logic into a dedicated method 2025-12-05 17:23:29 +00:00
Solly
2a7e71d7fd Merge branch 'master' into master 2025-12-03 09:15:21 +00:00
SollyBunny
0b4f96efc8 Make tracked leaderboard score yellow again 2025-11-29 03:23:13 +00:00
Chirag Mahesh
78c6973298 move cursorScale persistance into OsuCursor and use skinnableCursorScale 2025-11-27 16:39:51 +00:00
Chirag Mahesh
d8d7c80832 Persist cursorScale on skin refresh 2025-11-27 16:09:50 +00:00
Chirag Mahesh
ae33690632 Implement CursorTrail Scaling
- Add CursorScale property to CursorTrail and adjust for scaling
2025-11-27 16:09:50 +00:00
Chirag Mahesh
9e2ea63e70 Revert Changes to Trail Position Calculation
- Revert changes to CursorTrail.cs made during 79bfe7880a
2025-11-27 16:09:49 +00:00
Chirag Mahesh
79bfe7880a Move LocalSpacePosition calculation until the time of render
Would address #35734
2025-11-25 07:37:23 +00:00
Bartłomiej Dach
80fbcd5fbd Move application of scaling to tablet output area to scaling container
It's the safest place for it to be there, really.
2025-11-18 14:49:01 +01:00
Bartłomiej Dach
9c2319b989 Use existing bindables instead of refetching 2025-11-18 14:40:47 +01:00
Bartłomiej Dach
a040143825 Merge branch 'master' into screen-scaling-tablet-output 2025-11-18 14:24:03 +01:00
Dan Balasescu
bb017ade64 Add simple tray 2025-11-18 17:26:34 +09:00
Dan Balasescu
549cc08bfe Remove add button from playlist popup 2025-11-18 16:21:11 +09:00
Dan Balasescu
96dd95940f Add simple "Add to playlist" button in the footer 2025-11-18 16:21:08 +09:00
Dan Balasescu
a30f08be80 Merge branch 'master' into new-playlists-song-select 2025-11-18 14:08:12 +09:00
Bartłomiej Dach
8755a01622 Fix playing sample multiple times 2025-10-31 08:55:55 +01:00
Bartłomiej Dach
901044d690 Remove stuff that was not even supposed to exist 2025-10-31 08:49:26 +01:00
Bartłomiej Dach
90d1725603 Fix code quality
God does `dotnet format` make the `..` spread syntax just absolutely
unusable with its stupid inspections.
2025-10-24 13:49:55 +02:00
Bartłomiej Dach
78eaad13ae Fix tests 2025-10-24 12:09:38 +02:00
Bartłomiej Dach
511921c1f7 Inherit sample set info from previous object on placement 2025-10-24 12:09:30 +02:00
Bartłomiej Dach
e2681c4163 Add sample set selection controls to sample popovers
- Below 20 custom sample sets, they are shown as ternary buttons.
- Above 20 custom sample sets, they are shown in a dropdown (yes there
  are actual cases of this as I've been informed by the NAT; one example
  being https://osu.ppy.sh/beatmapsets/1018061#osu/2197383)

As a bonus, to make determining what the heck is actually changing when
adjusting these controls, the full set of applicable sounds now plays on
adding/removing additions, changing their banks, as well as changing the
custom set (if any).

For now there are no user-facing controls to add the samples to the map
yourself, you have to know how to name the `.wav`s and edit-externally
them in yourself. *For now.*
2025-10-24 12:09:27 +02:00
Bartłomiej Dach
1f2f86b016 Make bank selection controls dropdowns instead of text boxes
Longstanding complaint. Text boxes make no sense if anything that isn't
the three legacy values gets deleted on save anyway.
2025-10-24 11:52:45 +02:00
Bartłomiej Dach
fa7bd482d2 Show sample suffix on timeline pieces 2025-10-24 11:52:45 +02:00
Dan Balasescu
c8762762f9 Fix CI inspections 2025-10-17 09:19:27 +09:00
Dan Balasescu
9333ef361b Remove old implementation 2025-10-17 09:18:05 +09:00
Dan Balasescu
1cf9c04756 Fix playlist with >1 item getting cleared 2025-10-17 09:18:05 +09:00
Dan Balasescu
67849ca418 Adjust button styling 2025-10-17 09:18:05 +09:00
Dan Balasescu
0777bdbfc5 Add screen title 2025-10-17 09:18:05 +09:00
Dan Balasescu
798f6bdcc7 Fix padding not considered in sizing 2025-10-17 09:18:05 +09:00
Dan Balasescu
f38162c550 Fix padding as subscreen 2025-10-17 09:18:05 +09:00
Dan Balasescu
b663fe17ab Fix tests 2025-10-17 09:18:04 +09:00
Dan Balasescu
3c1d45b896 Integrate v2 playlists song select with game 2025-10-17 09:16:47 +09:00
Dan Balasescu
259bc53699 Add initial playlist button 2025-10-17 09:16:46 +09:00
Dan Balasescu
87834d1693 Add overall mods/freemods/freestyle hookups 2025-10-17 09:16:46 +09:00
Dan Balasescu
f1539167eb Add initial freestyle button 2025-10-17 09:16:46 +09:00
Dan Balasescu
ff6f797a3e Add initial freemods button 2025-10-17 09:16:46 +09:00
Dan Balasescu
bfa45a23ce Add initial screen + test structure 2025-10-17 09:16:46 +09:00
Dan Balasescu
c80fe7ab82 Use new implementation in ScreenTestScene 2025-10-16 20:37:24 +09:00
Dan Balasescu
d85c2ee623 Isolate footer behaviour to ScreenStackFooter, support subscreens 2025-10-16 20:37:24 +09:00
Dan Balasescu
b4b26e3a1d Remove song select dependency from FooterButtonOptions 2025-10-16 20:28:26 +09:00
Bartłomiej Dach
1867aad1a6 Merge branch 'master' into screen-scaling-tablet-output 2025-10-02 08:14:15 +02:00
Du Yijie
b1bc5cae87 Merge branch 'master' into legacy-pp-counter 2025-09-15 14:41:45 +08:00
Daniel Power
038bf3fdda "Conform to aspect ratio" uses scaled area 2025-08-27 20:11:28 -02:30
Daniel Power
61c3aad537 Fix conflict 2025-08-27 20:00:56 -02:30
Daniel Power
3aad0868af Remove duplicated declarations 2025-08-27 19:57:58 -02:30
Daniel Power
973c4c8319 Merge branch 'master' of github.com:ppy/osu into screen-scaling-tablet-output 2025-08-27 19:46:18 -02:30
Du Yijie
ac21f8b960 Implement "legacy" pp counter
There is no pp counter in osu!(stable). However, a "legacy" pp counter
allows skinners to more easily fit a pp counter into their skin's theme.
2025-08-27 11:07:53 +08:00
Daniel Power
0b3b6468a5 Reflect tablet output area changes in osu-framework 2025-08-16 23:02:28 -02:30
Daniel Power
7d1c54f045 Merge branch 'master' of github.com:ppy/osu into screen-scaling-tablet-output 2025-08-16 22:53:41 -02:30
Daniel Power
fd504e5641 Minor cleanup 2024-12-16 23:17:22 -03:30
Daniel Power
93ed0483b6 Fix TestSceneTabletSettings 2024-12-16 22:59:04 -03:30
Daniel Power
4dd0672aa5 Address screen scale positioning 2024-12-16 21:04:08 -03:30
Daniel Power
66eff14d2b Initial proof of concept for tablet output scaling 2024-12-16 01:17:35 -03:30
848 changed files with 34993 additions and 19196 deletions

View File

@@ -21,7 +21,7 @@
] ]
}, },
"ppy.localisationanalyser.tools": { "ppy.localisationanalyser.tools": {
"version": "2024.802.0", "version": "2025.1208.0",
"commands": [ "commands": [
"localisation" "localisation"
] ]

View File

@@ -55,9 +55,7 @@ When in doubt, it's probably best to start with a discussion first. We will esca
While pull requests from unaffiliated contributors are welcome, please note that due to significant community interest and limited review throughput, the core team's primary focus is on the issues which are currently [on the roadmap](https://github.com/orgs/ppy/projects/7/views/6). Reviewing PRs that fall outside of the scope of the roadmap is done on a best-effort basis, so please be aware that it may take a while before a core maintainer gets around to review your change. While pull requests from unaffiliated contributors are welcome, please note that due to significant community interest and limited review throughput, the core team's primary focus is on the issues which are currently [on the roadmap](https://github.com/orgs/ppy/projects/7/views/6). Reviewing PRs that fall outside of the scope of the roadmap is done on a best-effort basis, so please be aware that it may take a while before a core maintainer gets around to review your change.
The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues to start with. We also have a [`good first issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) label, although from experience it is not used very often, as it is relatively rare that we can spot an issue that will definitively be a good first issue for a new contributor regardless of their programming experience. The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues to start with. In the case of simple issues, a direct PR is okay. However, if you decide to work on an existing issue which doesn't seem trivial, **please ask us first**. This way we can try to estimate if it is a good fit for you and provide the correct direction on how to address it. In addition, note that while we do not rule out external contributors from working on roadmapped issues, we will generally prefer to handle them ourselves unless they're not very time sensitive.
In the case of simple issues, a direct PR is okay. However, if you decide to work on an existing issue which doesn't seem trivial, **please ask us first**. This way we can try to estimate if it is a good fit for you and provide the correct direction on how to address it. In addition, note that while we do not rule out external contributors from working on roadmapped issues, we will generally prefer to handle them ourselves unless they're not very time sensitive.
If you'd like to propose a subjective change to one of the visual aspects of the game, or there is a bigger task you'd like to work on, but there is no corresponding issue or discussion thread yet for it, **please open a discussion or issue first** to avoid wasted effort. This in particular applies if you want to work on [one of the available designs from the osu! Figma master library](https://www.figma.com/file/VIkXMYNPMtQem2RJg9k2iQ/Master-Library). If you'd like to propose a subjective change to one of the visual aspects of the game, or there is a bigger task you'd like to work on, but there is no corresponding issue or discussion thread yet for it, **please open a discussion or issue first** to avoid wasted effort. This in particular applies if you want to work on [one of the available designs from the osu! Figma master library](https://www.figma.com/file/VIkXMYNPMtQem2RJg9k2iQ/Master-Library).

View File

@@ -10,7 +10,7 @@
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk> <EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.1209.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2026.303.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

@@ -7,6 +7,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Localisation;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
@@ -32,7 +33,7 @@ namespace osu.Desktop.Security
{ {
public ElevatedPrivilegesNotification() public ElevatedPrivilegesNotification()
{ {
Text = $"Running osu! as {(RuntimeInfo.IsUnix ? "root" : "administrator")} does not improve performance, may break integrations and poses a security risk. Please run the game as a normal user."; Text = NotificationsStrings.ElevatedPrivileges(RuntimeInfo.IsUnix ? "root" : "Administrator");
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]

View File

@@ -5,7 +5,7 @@ using BenchmarkDotNet.Attributes;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Carousel; using osu.Game.Tests.NonVisual.Filtering;
namespace osu.Game.Benchmarks namespace osu.Game.Benchmarks
{ {
@@ -42,7 +42,7 @@ namespace osu.Game.Benchmarks
Status = BeatmapOnlineStatus.Loved Status = BeatmapOnlineStatus.Loved
}; };
private CarouselBeatmap carouselBeatmap = null!; private FilterMatchingTest.CarouselBeatmap carouselBeatmap = null!;
private FilterCriteria criteria1 = null!; private FilterCriteria criteria1 = null!;
private FilterCriteria criteria2 = null!; private FilterCriteria criteria2 = null!;
private FilterCriteria criteria3 = null!; private FilterCriteria criteria3 = null!;
@@ -55,7 +55,7 @@ namespace osu.Game.Benchmarks
var beatmap = getExampleBeatmap(); var beatmap = getExampleBeatmap();
beatmap.OnlineID = 20201010; beatmap.OnlineID = 20201010;
beatmap.BeatmapSet = new BeatmapSetInfo { OnlineID = 1535 }; beatmap.BeatmapSet = new BeatmapSetInfo { OnlineID = 1535 };
carouselBeatmap = new CarouselBeatmap(beatmap); carouselBeatmap = new FilterMatchingTest.CarouselBeatmap(beatmap);
criteria1 = new FilterCriteria(); criteria1 = new FilterCriteria();
criteria2 = new FilterCriteria criteria2 = new FilterCriteria
{ {

View File

@@ -35,11 +35,9 @@
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
</array> </array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/AppIcon.appiconset</string>
<key>UIApplicationSupportsIndirectInputEvents</key> <key>UIApplicationSupportsIndirectInputEvents</key>
<true/> <true/>
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
</dict> </dict>
</plist> </plist>

View File

@@ -176,15 +176,20 @@ namespace osu.Game.Rulesets.Catch
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch }; public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch };
protected override IEnumerable<HitResult> GetValidHitResults() public override IEnumerable<HitResult> GetValidHitResults()
{ {
return new[] return new[]
{ {
HitResult.Great, HitResult.Great,
HitResult.Miss,
HitResult.LargeTickHit, HitResult.LargeTickHit,
HitResult.LargeTickMiss,
HitResult.SmallTickHit, HitResult.SmallTickHit,
HitResult.SmallTickMiss,
HitResult.LargeBonus, HitResult.LargeBonus,
HitResult.IgnoreHit,
HitResult.IgnoreMiss,
}; };
} }
@@ -300,7 +305,7 @@ namespace osu.Game.Rulesets.Catch
Description = "Affects how early fruits fade in on the screen.", Description = "Affects how early fruits fade in on the screen.",
AdditionalMetrics = AdditionalMetrics =
[ [
new RulesetBeatmapAttribute.AdditionalMetric("Fade-in time", LocalisableString.Interpolate($@"{IBeatmapDifficultyInfo.DifficultyRange(effectiveDifficulty.ApproachRate, CatchHitObject.PREEMPT_RANGE):#,0.##} ms")) new RulesetBeatmapAttribute.AdditionalMetric("Fade-in time", LocalisableString.Interpolate($@"{IBeatmapDifficultyInfo.DifficultyRangeInt(effectiveDifficulty.ApproachRate, CatchHitObject.PREEMPT_RANGE):#,0.##} ms"))
] ]
}; };
yield return new RulesetBeatmapAttribute(SongSelectStrings.HPDrain, @"HP", originalDifficulty.DrainRate, effectiveDifficulty.DrainRate, 10) yield return new RulesetBeatmapAttribute(SongSelectStrings.HPDrain, @"HP", originalDifficulty.DrainRate, effectiveDifficulty.DrainRate, 10)

View File

@@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Catch.Objects
{ {
base.ApplyDefaultsToSelf(controlPointInfo, difficulty); base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, PREEMPT_RANGE); TimePreempt = IBeatmapDifficultyInfo.DifficultyRangeInt(difficulty.ApproachRate, PREEMPT_RANGE);
Scale = LegacyRulesetExtensions.CalculateScaleFromCircleSize(difficulty.CircleSize); Scale = LegacyRulesetExtensions.CalculateScaleFromCircleSize(difficulty.CircleSize);
} }

View File

@@ -72,6 +72,9 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
leaderboard.Origin = Anchor.CentreLeft; leaderboard.Origin = Anchor.CentreLeft;
leaderboard.X = 10; leaderboard.X = 10;
} }
foreach (var d in container.OfType<ISerialisableDrawable>())
d.UsesFixedAnchor = true;
}) })
{ {
Children = new Drawable[] Children = new Drawable[]

View File

@@ -35,11 +35,9 @@
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
</array> </array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/AppIcon.appiconset</string>
<key>UIApplicationSupportsIndirectInputEvents</key> <key>UIApplicationSupportsIndirectInputEvents</key>
<true/> <true/>
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
</dict> </dict>
</plist> </plist>

View File

@@ -383,7 +383,7 @@ namespace osu.Game.Rulesets.Mania
return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast<int>().OrderDescending().First(v => variant >= v); 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[] return new[]
{ {
@@ -392,9 +392,11 @@ namespace osu.Game.Rulesets.Mania
HitResult.Good, HitResult.Good,
HitResult.Ok, HitResult.Ok,
HitResult.Meh, HitResult.Meh,
HitResult.Miss,
// HitResult.SmallBonus is used for awarding perfect bonus score but is not included here as HitResult.IgnoreHit,
// it would be a bit redundant to show this to the user. HitResult.ComboBreak,
HitResult.IgnoreMiss,
}; };
} }

View File

@@ -7,7 +7,7 @@ using osu.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Localisation; using osu.Game.Localisation;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Configuration;
@@ -31,47 +31,45 @@ namespace osu.Game.Rulesets.Mania
Children = new Drawable[] Children = new Drawable[]
{ {
new SettingsEnumDropdown<ManiaScrollingDirection> new SettingsItemV2(new FormEnumDropdown<ManiaScrollingDirection>
{ {
LabelText = RulesetSettingsStrings.ScrollingDirection, Caption = RulesetSettingsStrings.ScrollingDirection,
Current = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection) Current = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection)
}, }),
new SettingsSlider<double, ManiaScrollSlider> new SettingsItemV2(new FormSliderBar<double>
{ {
LabelText = RulesetSettingsStrings.ScrollSpeed, Caption = RulesetSettingsStrings.ScrollSpeed,
Current = config.GetBindable<double>(ManiaRulesetSetting.ScrollSpeed), Current = config.GetBindable<double>(ManiaRulesetSetting.ScrollSpeed),
KeyboardStep = 1 KeyboardStep = 1,
}, LabelFormat = v => RulesetSettingsStrings.ScrollSpeedTooltip((int)DrawableManiaRuleset.ComputeScrollTime(v), v),
new SettingsCheckbox }),
new SettingsItemV2(new FormCheckBox
{
Caption = RulesetSettingsStrings.TimingBasedColouring,
Current = config.GetBindable<bool>(ManiaRulesetSetting.TimingBasedNoteColouring),
})
{ {
Keywords = new[] { "color" }, Keywords = new[] { "color" },
LabelText = RulesetSettingsStrings.TimingBasedColouring,
Current = config.GetBindable<bool>(ManiaRulesetSetting.TimingBasedNoteColouring),
}, },
}; };
Add(new SettingsCheckbox Add(new SettingsItemV2(new FormCheckBox
{ {
LabelText = RulesetSettingsStrings.TouchOverlay, Caption = RulesetSettingsStrings.TouchOverlay,
Current = config.GetBindable<bool>(ManiaRulesetSetting.TouchOverlay) Current = config.GetBindable<bool>(ManiaRulesetSetting.TouchOverlay)
}); }));
if (RuntimeInfo.IsMobile) if (RuntimeInfo.IsMobile)
{ {
Add(new SettingsEnumDropdown<ManiaMobileLayout> Add(new SettingsItemV2(new FormEnumDropdown<ManiaMobileLayout>
{ {
LabelText = RulesetSettingsStrings.MobileLayout, Caption = RulesetSettingsStrings.MobileLayout,
Current = config.GetBindable<ManiaMobileLayout>(ManiaRulesetSetting.MobileLayout), Current = config.GetBindable<ManiaMobileLayout>(ManiaRulesetSetting.MobileLayout),
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
Items = Enum.GetValues<ManiaMobileLayout>().Where(l => l != ManiaMobileLayout.LandscapeWithOverlay), Items = Enum.GetValues<ManiaMobileLayout>().Where(l => l != ManiaMobileLayout.LandscapeWithOverlay),
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
}); }));
} }
} }
private partial class ManiaScrollSlider : RoundedSliderBar<double>
{
public override LocalisableString TooltipText => RulesetSettingsStrings.ScrollSpeedTooltip((int)DrawableManiaRuleset.ComputeScrollTime(Current.Value), Current.Value);
}
} }
} }

View File

@@ -57,6 +57,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
if (spectatorList != null) if (spectatorList != null)
spectatorList.Position = new Vector2(36, -66); spectatorList.Position = new Vector2(36, -66);
foreach (var d in container.OfType<ISerialisableDrawable>())
d.UsesFixedAnchor = true;
}) })
{ {
new DrawableGameplayLeaderboard(), new DrawableGameplayLeaderboard(),

View File

@@ -122,6 +122,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
leaderboard.Origin = Anchor.CentreLeft; leaderboard.Origin = Anchor.CentreLeft;
leaderboard.X = 10; leaderboard.X = 10;
} }
foreach (var d in container.OfType<ISerialisableDrawable>())
d.UsesFixedAnchor = true;
}) })
{ {
new LegacyManiaComboCounter(), new LegacyManiaComboCounter(),

View File

@@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Mania.UI
var hitWindows = new ManiaHitWindows(); var hitWindows = new ManiaHitWindows();
AddInternal(judgementPooler = new JudgementPooler<DrawableManiaJudgement>(Enum.GetValues<HitResult>().Where(r => hitWindows.IsHitResultAllowed(r)))); AddInternal(judgementPooler = new JudgementPooler<DrawableManiaJudgement>(Enum.GetValues<HitResult>().Where(hitWindows.IsHitResultAllowed)));
RegisterPool<BarLine, DrawableBarLine>(50, 200); RegisterPool<BarLine, DrawableBarLine>(50, 200);
} }

View File

@@ -35,11 +35,9 @@
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
</array> </array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/AppIcon.appiconset</string>
<key>UIApplicationSupportsIndirectInputEvents</key> <key>UIApplicationSupportsIndirectInputEvents</key>
<true/> <true/>
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
</dict> </dict>
</plist> </plist>

View File

@@ -3,10 +3,13 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Testing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; 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.Objects;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps;
@@ -30,6 +33,16 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
PathType.LINEAR, PathType.LINEAR,
new Vector2(100, 0), new Vector2(100, 0),
new Vector2(100, 100) 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)
) )
}; };
@@ -48,9 +61,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
[TestCase(0, 250)] [TestCase(0, 250)]
[TestCase(0, 200)] [TestCase(0, 200)]
[TestCase(1, 120)] [TestCase(1, 120, false, false)]
[TestCase(1, 80)] [TestCase(1, 80, false, false)]
public void TestSliderReversal(int pathIndex, double length) [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]; var controlPoints = paths[pathIndex];
@@ -90,6 +107,215 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
InputManager.ReleaseKey(Key.LControl); 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
);
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
);
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", () => AddAssert("Slider has correct length", () =>
Precision.AlmostEquals(selectedSlider.Path.Distance, oldDistance)); Precision.AlmostEquals(selectedSlider.Path.Distance, oldDistance));

View File

@@ -0,0 +1,88 @@
// 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.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests.Mods
{
public partial class TestSceneOsuModEasy : OsuModTestScene
{
protected override bool AllowFail => true;
[Test]
public void TestMultipleApplication()
{
bool reapplied = false;
CreateModTest(new ModTestData
{
Mods = [new OsuModEasy { Retries = { Value = 1 } }],
Autoplay = false,
CreateBeatmap = () =>
{
// do stuff to speed up fails
var b = new TestBeatmap(new OsuRuleset().RulesetInfo)
{
Difficulty = { DrainRate = 10 }
};
foreach (var ho in b.HitObjects)
ho.StartTime /= 4;
return b;
},
PassCondition = () =>
{
if (((ModEasyTestPlayer)Player).FailuresSuppressed > 0 && !reapplied)
{
try
{
foreach (var mod in Player.GameplayState.Mods.OfType<IApplicableToDifficulty>())
mod.ApplyToDifficulty(new BeatmapDifficulty());
foreach (var mod in Player.GameplayState.Mods.OfType<IApplicableToPlayer>())
mod.ApplyToPlayer(Player);
}
catch
{
// don't care if this fails. in fact a failure here is probably better than the alternative.
}
finally
{
reapplied = true;
}
}
return Player.GameplayState.HasFailed && ((ModEasyTestPlayer)Player).FailuresSuppressed <= 1;
}
});
}
protected override TestPlayer CreateModPlayer(Ruleset ruleset) => new ModEasyTestPlayer(CurrentTestData, AllowFail);
private partial class ModEasyTestPlayer : ModTestPlayer
{
public int FailuresSuppressed { get; private set; }
public ModEasyTestPlayer(ModTestData data, bool allowFail)
: base(data, allowFail)
{
}
protected override bool CheckModsAllowFailure()
{
bool failureAllowed = GameplayState.Mods.OfType<IApplicableFailOverride>().All(m => m.PerformFail());
if (!failureAllowed)
FailuresSuppressed++;
return failureAllowed;
}
}
}
}

View File

@@ -2,7 +2,10 @@
// 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 NUnit.Framework; using NUnit.Framework;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
namespace osu.Game.Rulesets.Osu.Tests.Mods namespace osu.Game.Rulesets.Osu.Tests.Mods
{ {
@@ -18,5 +21,39 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
Autoplay = false, Autoplay = false,
}); });
} }
[Test]
public void TestSkipToFirstCircleNotSuppressed()
{
CreateModTest(new ModTestData
{
Mod = new OsuModFreezeFrame(),
CreateBeatmap = () => new OsuBeatmap
{
HitObjects =
{
new HitCircle { StartTime = 5000, Position = OsuPlayfield.BASE_SIZE / 2 }
}
},
PassCondition = () => Player.GameplayClockContainer.GameplayStartTime > 0
});
}
[Test]
public void TestSkipToFirstSpinnerNotSuppressed()
{
CreateModTest(new ModTestData
{
Mod = new OsuModFreezeFrame(),
CreateBeatmap = () => new OsuBeatmap
{
HitObjects =
{
new Spinner { StartTime = 5000, Position = OsuPlayfield.BASE_SIZE / 2 }
}
},
PassCondition = () => Player.GameplayClockContainer.GameplayStartTime > 0
});
}
} }
} }

View File

@@ -27,6 +27,9 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestCase("multi-segment-slider")] [TestCase("multi-segment-slider")]
[TestCase("nan-slider")] [TestCase("nan-slider")]
[TestCase("1124896")] [TestCase("1124896")]
[TestCase("1341554")]
[TestCase("2593923")]
[TestCase("801165")]
public void Test(string name) => base.Test(name); public void Test(string name) => base.Test(name);
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject) protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,941 @@
osu file format v14
[General]
AudioLeadIn: 0
PreviewTime: 76429
Countdown: 0
SampleSet: Soft
StackLeniency: 0.2
Mode: 0
LetterboxInBreaks: 0
WidescreenStoryboard: 1
[Difficulty]
HPDrainRate:5
CircleSize:4.3
OverallDifficulty:8
ApproachRate:9.3
SliderMultiplier:2.99999995231628
SliderTickRate:1
[Events]
//Background and Video events
//Break Periods
//Storyboard Layer 0 (Background)
//Storyboard Layer 1 (Fail)
//Storyboard Layer 2 (Pass)
//Storyboard Layer 3 (Foreground)
//Storyboard Sound Samples
[TimingPoints]
763,444.444444444444,4,2,1,60,1,0
763,-111.111111111111,4,2,1,60,0,0
1929,-100,4,2,1,5,0,0
1985,-100,4,2,1,60,0,0
2040,-100,4,2,1,5,0,0
2096,-153.846153846153,4,2,1,60,0,0
2429,-133.333333333333,4,2,1,5,0,0
2540,-71.4285714285714,4,2,1,70,0,1
2985,-100,4,2,1,70,0,1
4485,-100,4,2,1,5,0,1
4540,-100,4,2,1,70,0,1
4707,-100,4,2,1,5,0,1
4762,-100,4,2,1,70,0,1
4929,-100,4,2,1,5,0,1
4985,-100,4,2,1,70,0,1
5096,-83.3333333333333,4,2,1,70,0,1
5429,-133.333333333333,4,2,1,70,0,1
5596,-133.333333333333,4,2,1,70,0,1
5652,-133.333333333333,4,2,1,70,0,1
5818,-133.333333333333,4,2,1,70,0,1
5874,-133.333333333333,4,2,1,70,0,1
6040,-133.333333333333,4,2,1,70,0,1
6096,-100,4,2,1,70,0,1
6540,-100,4,2,1,70,0,1
8040,-100,4,2,1,5,0,1
8096,-100,4,2,1,70,0,1
8262,-100,4,2,1,5,0,1
8318,-100,4,2,1,70,0,1
8485,-100,4,2,1,5,0,1
8540,-133.333333333333,4,2,1,70,0,1
8874,-100,4,2,1,5,0,1
8985,-100,4,2,1,70,0,1
9651,-100,4,2,1,70,0,1
10096,-100,4,2,1,70,0,1
11596,-100,4,2,1,5,0,1
11651,-100,4,2,1,70,0,1
11818,-100,4,2,1,5,0,1
11873,-100,4,2,1,70,0,1
11874,-80,4,2,1,70,0,1
12040,-80,4,2,1,5,0,1
12096,-80,4,2,1,70,0,1
12207,-133.333333333333,4,2,1,70,0,1
12429,-100,4,2,1,5,0,1
12540,-100,4,2,1,70,0,1
12707,-100,4,2,1,70,0,1
12763,-100,4,2,1,70,0,1
12929,-100,4,2,1,70,0,1
12985,-100,4,2,1,70,0,1
13429,-300,4,2,1,70,0,1
13651,-83.3333333333333,4,2,1,70,0,1
13874,-100,4,2,1,70,0,1
15151,-100,4,2,1,5,0,1
15207,-100,4,2,1,70,0,1
15373,-100,4,2,1,5,0,1
15429,-100,4,2,1,70,0,1
15596,-100,4,2,1,5,0,1
15651,-100,4,2,1,70,0,1
15985,-100,4,2,1,5,0,1
16096,-100,4,2,1,70,0,1
16262,-100,4,2,1,70,0,1
16318,-83.3333333333333,4,2,1,70,0,1
16651,-100,4,2,1,70,0,1
16762,-133.333333333333,4,2,1,60,0,0
17096,-133.333333333333,4,2,1,5,0,0
17207,-200,4,2,1,60,0,0
18096,-66.6666666666667,4,2,1,60,0,0
18262,-66.6666666666667,4,2,1,5,0,0
18318,-66.6666666666667,4,2,1,60,0,0
18540,-100,4,2,1,60,0,0
18874,-100,4,2,1,5,0,0
18985,-100,4,2,1,60,0,0
19985,-100,4,2,1,60,0,0
20485,-100,4,2,1,5,0,0
20540,-100,4,2,1,60,0,0
20707,-100,4,2,1,5,0,0
20762,-200,4,2,1,60,0,0
20985,-100,4,2,1,60,0,0
21095,-100,4,2,1,60,0,0
21374,-100,4,2,1,5,0,0
21429,-100,4,2,1,60,0,0
21596,-100,4,2,1,5,0,0
21651,-100,4,2,1,60,0,0
21818,-100,4,2,1,5,0,0
21874,-66.6666666666667,4,2,1,60,0,0
21985,-100,4,2,1,60,0,0
22096,-100,4,2,1,60,0,0
22985,-200,4,2,1,60,0,0
23318,-100,4,2,1,60,0,0
23429,-100,4,2,1,60,0,0
23540,-100,4,2,1,60,0,0
23651,-100,4,2,1,60,0,0
23762,-100,4,2,1,60,0,0
23874,-133.333333333333,4,2,1,60,0,0
24208,-133.333333333333,4,2,1,5,0,0
24318,-200,4,2,1,5,0,0
24319,-200,4,2,1,60,0,0
24540,-100,4,2,1,60,0,0
24651,-66.6666666666667,4,2,1,60,0,0
24874,-100,4,2,1,60,0,0
25374,-100,4,2,1,5,0,0
25429,-100,4,2,1,60,0,0
27096,-100,4,2,1,60,0,0
27596,-100,4,2,1,5,0,0
27651,-100,4,2,1,60,0,0
27818,-100,4,2,1,5,0,0
27873,-133.333333333333,4,2,1,60,0,0
28096,-100,4,2,1,60,0,0
28206,-100,4,2,1,60,0,0
28485,-100,4,2,1,5,0,0
28540,-100,4,2,1,60,0,0
28707,-100,4,2,1,5,0,0
28762,-100,4,2,1,60,0,0
28929,-100,4,2,1,5,0,0
28985,-66.6666666666667,4,2,1,60,0,0
29151,-100,4,2,1,60,0,0
29207,-100,4,2,1,60,0,0
29651,-100,4,2,1,60,0,0
30429,-100,4,2,1,60,0,0
30540,-58.8235294117647,4,2,1,60,0,0
30874,-58.8235294117647,4,2,1,5,0,0
30985,-58.8235294117647,4,2,1,60,0,0
31040,-100,4,2,1,5,0,0
31429,-100,4,2,1,60,0,0
32485,-100,4,2,1,60,0,0
32540,-100,4,2,1,60,0,0
32707,-100,4,2,1,60,0,0
32762,-100,4,2,1,60,0,0
32985,-100,4,2,1,60,0,0
34318,-50,4,2,1,60,0,0
34485,-100,4,2,1,5,0,0
34540,-100,4,2,1,60,0,0
35151,-100,4,2,1,5,0,0
35207,-100,4,2,1,60,0,0
35374,-100,4,2,1,5,0,0
35430,-100,4,2,1,60,0,0
35818,-100,4,2,1,5,0,0
35874,-200,4,2,1,60,0,0
36429,-100,4,2,1,60,0,0
37818,-100,4,2,1,5,0,0
37874,-100,4,2,1,60,0,0
38040,-100,4,2,1,5,0,0
38096,-50,4,2,1,60,0,0
38151,-100,4,2,1,5,0,0
38540,-100,4,2,1,60,0,0
39596,-100,4,2,1,5,0,0
39651,-100,4,2,1,60,0,0
39818,-100,4,2,1,60,0,0
39873,-100,4,2,1,60,0,0
40096,-100,4,2,1,60,0,0
41429,-50,4,2,1,60,0,0
41596,-100,4,2,1,5,0,0
41651,-100,4,2,1,60,0,0
41818,-100,4,2,1,5,0,0
41874,-100,4,2,1,60,0,0
42040,-100,4,2,1,5,0,0
42096,-100,4,2,1,60,0,0
44318,-100,4,2,1,60,0,0
44762,-83.3333333333333,4,2,1,60,0,0
45207,-66.6666666666667,4,2,1,45,0,0
45651,-133.333333333333,4,2,1,45,0,0
51540,-133.333333333333,4,2,1,50,0,0
51651,-133.333333333333,4,2,1,45,0,0
52318,-133.333333333333,4,2,1,45,0,0
58540,-76.9230769230769,4,2,1,45,0,0
58818,-100,4,2,1,45,0,0
58874,-111.111111111111,4,2,1,45,0,0
59318,-111.111111111111,4,2,1,45,0,0
59429,-83.3333333333333,4,2,1,60,0,0
59540,-83.3333333333333,4,2,1,5,0,0
59874,-100,4,2,1,60,0,0
60096,-100,4,2,1,5,0,0
60207,-100,4,2,1,60,0,0
60707,-100,4,2,1,5,0,0
60763,-100,4,2,1,60,0,0
60818,-100,4,2,1,5,0,0
60874,-100,4,2,1,60,0,0
60929,-100,4,2,1,5,0,0
60985,-100,4,2,1,60,0,0
61040,-100,4,2,1,5,0,0
61096,-100,4,2,1,60,0,0
61151,-100,4,2,1,5,0,0
61207,-100,4,2,1,60,0,0
61596,-100,4,2,1,5,0,0
61651,-100,4,2,1,60,0,0
61762,-83.3333333333333,4,2,1,60,0,0
61985,-100,4,2,1,5,0,0
62096,-100,4,2,1,60,0,0
62151,-100,4,2,1,5,0,0
62207,-100,4,2,1,60,0,0
62262,-100,4,2,1,5,0,0
62318,-100,4,2,1,60,0,0
62374,-100,4,2,1,5,0,0
62430,-100,4,2,1,60,0,0
62485,-100,4,2,1,5,0,0
62540,-100,4,2,1,60,0,0
62596,-100,4,2,1,5,0,0
62651,-100,4,2,1,60,0,0
62707,-100,4,2,1,5,0,0
62762,-100,4,2,1,60,0,0
62818,-100,4,2,1,5,0,0
62874,-100,4,2,1,60,0,0
62929,-100,4,2,1,60,0,0
62930,-100,4,2,1,5,0,0
62985,-100,4,2,1,60,0,0
63707,-100,4,2,1,5,0,0
63762,-100,4,2,1,60,0,0
64262,-100,4,2,1,5,0,0
64318,-100,4,2,1,60,0,0
64485,-100,4,2,1,5,0,0
64540,-100,4,2,1,60,0,0
64596,-100,4,2,1,5,0,0
64651,-100,4,2,1,60,0,0
64707,-100,4,2,1,5,0,0
64762,-71.4285714285714,4,2,1,60,0,0
64929,-71.4285714285714,4,2,1,5,0,0
64984,-133.333333333333,4,2,1,60,0,0
65151,-133.333333333333,4,2,1,5,0,0
65206,-71.4285714285714,4,2,1,60,0,0
65374,-71.4285714285714,4,2,1,5,0,0
65429,-133.333333333333,4,2,1,60,0,0
65596,-133.333333333333,4,2,1,5,0,0
65651,-100,4,2,1,60,0,0
66540,-66.6666666666667,4,2,1,60,0,0
66596,-66.6666666666667,4,2,1,5,0,0
66929,-100,4,2,1,5,0,0
66985,-200,4,2,1,60,0,0
67207,-200,4,2,1,5,0,0
67318,-100,4,2,1,60,0,0
67818,-100,4,2,1,5,0,0
67874,-100,4,2,1,60,0,0
67929,-100,4,2,1,5,0,0
67985,-100,4,2,1,60,0,0
68040,-100,4,2,1,5,0,0
68096,-100,4,2,1,60,0,0
68151,-100,4,2,1,5,0,0
68207,-100,4,2,1,60,0,0
68262,-100,4,2,1,5,0,0
68318,-100,4,2,1,60,0,0
68874,-83.3333333333333,4,2,1,60,0,0
69096,-100,4,2,1,60,0,0
69097,-100,4,2,1,5,0,0
69207,-100,4,2,1,60,0,0
69263,-100,4,2,1,5,0,0
69319,-100,4,2,1,60,0,0
69374,-100,4,2,1,5,0,0
69430,-100,4,2,1,60,0,0
69486,-100,4,2,1,5,0,0
69542,-100,4,2,1,60,0,0
69597,-100,4,2,1,5,0,0
69651,-100,4,2,1,60,0,0
69707,-100,4,2,1,5,0,0
69762,-100,4,2,1,60,0,0
69818,-100,4,2,1,5,0,0
69874,-100,4,2,1,60,0,0
69929,-100,4,2,1,5,0,0
69985,-100,4,2,1,60,0,0
70040,-100,4,2,1,60,0,0
70041,-100,4,2,1,5,0,0
70096,-100,4,2,1,60,0,0
70818,-100,4,2,1,5,0,0
70873,-100,4,2,1,60,0,0
71207,-71.4285714285714,4,2,1,60,0,0
71429,-100,4,2,1,60,0,0
71874,-71.4285714285714,4,2,1,60,0,0
72041,-71.4285714285714,4,2,1,5,0,0
72096,-133.333333333333,4,2,1,60,0,0
72263,-133.333333333333,4,2,1,5,0,0
72318,-71.4285714285714,4,2,1,60,0,0
72485,-71.4285714285714,4,2,1,5,0,0
72540,-133.333333333333,4,2,1,60,0,0
72985,-66.6666666666667,4,2,1,60,0,0
73207,-100,4,2,1,60,0,0
73651,-133.333333333333,4,2,1,45,0,0
75318,-133.333333333333,4,2,1,5,0,0
75429,-133.333333333333,4,2,1,45,0,0
76762,-100,4,2,1,45,0,0
77096,-100,4,2,1,5,0,0
77207,-100,4,2,1,70,0,1
77818,-100,4,2,1,5,0,1
77874,-100,4,2,1,70,0,1
78262,-100,4,2,1,5,0,1
78318,-100,4,2,1,70,0,1
78540,-83.3333333333333,4,2,1,70,0,1
78985,-100,4,2,1,70,0,1
79596,-100,4,2,1,5,0,1
79651,-100,4,2,1,70,0,1
80040,-100,4,2,1,5,0,1
80096,-100,4,2,1,70,0,1
80318,-83.3333333333333,4,2,1,70,0,1
84318,-100,4,2,1,70,0,1
84929,-100,4,2,1,5,0,1
84985,-100,4,2,1,70,0,1
85207,-100,4,2,1,70,0,1
85374,-100,4,2,1,5,0,1
85429,-100,4,2,1,70,0,1
85651,-83.3333333333333,4,2,1,70,0,1
86096,-100,4,2,1,70,0,1
86707,-100,4,2,1,5,0,1
86762,-100,4,2,1,70,0,1
88818,-100,4,2,1,5,0,1
88874,-100,4,2,1,70,0,1
88929,-100,4,2,1,5,0,1
88985,-100,4,2,1,70,0,1
89040,-100,4,2,1,5,0,1
89096,-100,4,2,1,70,0,1
92040,-100,4,2,1,5,0,1
92096,-100,4,2,1,70,0,1
92485,-100,4,2,1,5,0,1
92540,-100,4,2,1,70,0,1
97651,-200,4,2,1,70,0,1
97818,-200,4,2,1,5,0,1
97874,-66.6666666666667,4,2,1,70,0,1
97985,-66.6666666666667,4,2,1,70,0,1
98040,-66.6666666666667,4,2,1,5,0,1
98096,-133.333333333333,4,2,1,70,0,1
98262,-133.333333333333,4,2,1,5,0,1
98318,-66.6666666666667,4,2,1,70,0,1
98540,-100,4,2,1,70,0,1
99151,-100,4,2,1,5,0,1
99207,-100,4,2,1,70,0,1
99596,-100,4,2,1,5,0,1
99651,-100,4,2,1,70,0,1
103040,-100,4,2,1,5,0,1
103096,-100,4,2,1,70,0,1
103151,-100,4,2,1,5,0,1
103207,-100,4,2,1,70,0,1
103262,-100,4,2,1,5,0,1
103318,-100,4,2,1,70,0,1
105207,-83.3333333333333,4,2,1,70,0,1
105540,-83.3333333333333,4,2,1,70,0,1
105651,-133.333333333333,4,2,1,60,0,0
105985,-133.333333333333,4,2,1,5,0,0
106096,-200,4,2,1,60,0,0
106985,-66.6666666666667,4,2,1,60,0,0
107151,-66.6666666666667,4,2,1,5,0,0
107207,-66.6666666666667,4,2,1,60,0,0
107429,-100,4,2,1,60,0,0
107763,-100,4,2,1,5,0,0
107874,-100,4,2,1,60,0,0
108874,-100,4,2,1,60,0,0
109374,-100,4,2,1,5,0,0
109429,-100,4,2,1,60,0,0
109596,-100,4,2,1,5,0,0
109651,-200,4,2,1,60,0,0
109929,-100,4,2,1,60,0,0
109984,-100,4,2,1,60,0,0
110262,-100,4,2,1,5,0,0
110318,-100,4,2,1,60,0,0
110485,-100,4,2,1,5,0,0
110540,-100,4,2,1,60,0,0
110707,-100,4,2,1,5,0,0
110762,-66.6666666666667,4,2,1,60,0,0
110929,-100,4,2,1,60,0,0
110985,-133.333333333333,4,2,1,60,0,0
111429,-133.333333333333,4,2,1,60,0,0
111596,-133.333333333333,4,2,1,60,0,0
111651,-133.333333333333,4,2,1,60,0,0
111818,-133.333333333333,4,2,1,60,0,0
111874,-100,4,2,1,60,0,1
112318,-83.3333333333333,4,2,1,60,0,1
112429,-100,4,2,1,5,0,0
[Colours]
Combo1 : 112,75,180
Combo2 : 0,255,255
Combo3 : 255,15,117
Combo4 : 255,135,15
[HitObjects]
309,230,763,37,0,3:0:0:0:
485,146,985,2,0,L|406:167,1,67.4999968671799,8|0,3:0|0:0,0:0:0:0:
374,249,1207,2,0,L|299:227,1,67.4999968671799,8|0,3:0|0:0,0:0:0:0:
196,91,1429,2,0,L|191:44,3,33.7499984335899,0|0|0|0,3:0|3:0|3:0|3:0,0:0:0:0:
124,173,1651,2,0,L|131:222,2,44.9999979114532,0|0|0,3:0|3:0|3:0,0:0:0:0:
221,284,1874,2,0,L|213:208,1,67.4999968671799,0|0,3:0|3:0,0:0:0:0:
292,86,2096,38,0,L|310:234,1,146.249990980625,12|0,3:0|0:0,0:0:0:0:
314,328,2540,38,0,B|280:359|280:359|230:320|252:242|313:230,1,209.999990253448,0|0,3:0|0:0,0:0:0:0:
421,300,2874,1,0,0:0:0:0:
421,300,2985,2,0,P|461:288|491:253,1,74.999998807907,8|0,3:0|0:0,0:0:0:0:
309,231,3207,2,0,P|297:190|305:153,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
394,22,3429,5,0,3:0:0:0:
461,72,3540,2,0,B|477:103|477:103|461:148,1,74.999998807907,0|4,0:0|0:0,0:0:0:0:
378,183,3762,2,0,L|206:157,1,149.999997615814,0|0,0:0|0:0,0:0:0:0:
229,161,4096,2,0,P|227:202|211:250,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
61,384,4318,38,0,P|101:359|134:322,1,74.999998807907,0|0,3:0|0:0,0:0:0:0:
317,310,4540,2,0,P|267:305|226:288,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
141,110,4762,2,0,B|121:175|152:226|152:226|152:202|161:183,1,149.999997615814,8|4,3:0|0:0,0:0:0:0:
155,196,5096,6,0,P|67:211|79:286,1,179.999991645813,0|0,0:0|0:0,0:0:0:0:
212,366,5429,38,0,P|207:335|174:281,1,56.2500012516975,4|0,0:0|0:0,0:0:0:0:
206,286,5651,2,0,P|236:297|299:295,1,56.2500012516975,8|0,3:0|0:0,0:0:0:0:
281,321,5874,2,0,P|257:340|227:396,1,56.2500012516975,4|0,0:0|0:0,0:0:0:0:
124,246,6096,6,0,P|198:198|277:232,1,149.999997615814,0|0,3:0|0:0,0:0:0:0:
253,211,6429,1,0,0:0:0:0:
276,99,6540,2,0,P|335:139|369:215,1,149.999997615814,8|4,3:0|0:0,0:0:0:0:
368,208,6874,1,0,0:0:0:0:
430,96,6985,37,0,3:0:0:0:
497,147,7096,2,0,P|507:189|488:244,1,74.999998807907,0|4,0:0|0:0,0:0:0:0:
414,379,7318,2,0,B|383:322|421:267|421:267|421:308,1,149.999997615814,0|0,0:0|0:0,0:0:0:0:
421,298,7651,2,0,P|378:312|336:304,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
270,170,7874,6,0,P|275:228|236:278,1,74.999998807907,0|0,3:0|0:0,0:0:0:0:
94,300,8096,2,0,P|133:263|208:274,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
261,374,8318,2,0,L|176:365,1,74.999998807907,8|0,3:0|0:0,0:0:0:0:
38,377,8540,2,0,L|55:197,1,168.750003755093,4|0,0:0|0:0,0:0:0:0:
123,25,8985,38,0,L|132:110,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
217,242,9207,2,0,L|237:168,1,74.999998807907,8|0,3:0|0:0,0:0:0:0:
48,92,9429,5,4,0:0:0:0:
63,176,9540,1,0,0:0:0:0:
83,259,9651,38,0,P|167:223|231:255,1,149.999997615814,0|0,3:0|0:0,0:0:0:0:
274,312,9985,1,0,0:0:0:0:
274,312,10096,2,0,L|354:292,1,74.999998807907,8|0,3:0|0:0,0:0:0:0:
459,225,10318,2,0,L|375:204,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
269,107,10540,1,0,3:0:0:0:
276,54,10651,1,0,0:0:0:0:
313,17,10762,1,4,0:0:0:0:
363,9,10874,1,0,0:0:0:0:
363,9,11096,5,0,0:0:0:0:
432,68,11207,2,0,P|444:107|425:154,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
309,252,11429,38,0,P|297:195|321:158,1,74.999998807907,0|0,3:0|0:0,0:0:0:0:
450,316,11651,2,0,L|361:312,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
160,341,11874,2,0,B|187:380|187:380|233:309|177:235,1,187.499997019767,8|4,3:0|0:0,0:0:0:0:
116,200,12207,6,0,P|52:224|122:264,1,168.750003755093,0|4,0:0|0:0,0:0:0:0:
297,91,12762,37,8,3:0:0:0:
276,44,12874,1,0,0:0:0:0:
226,27,12985,1,4,0:0:0:0:
187,63,13096,1,0,0:0:0:0:
196,115,13207,1,0,0:0:0:0:
376,144,13429,2,0,L|378:121,2,16.6666664017571,0|0|0,0:0|0:0|0:0,0:0:0:0:
436,220,13651,6,0,B|395:211|373:164|373:164|332:208|264:185,1,179.999991645813,8|4,3:0|0:0,0:0:0:0:
276,44,13985,1,0,0:0:0:0:
196,115,14096,38,0,L|139:124,4,37.4999994039535,0|0|0|0|4,3:0|0:0|0:0|0:0|0:0,0:0:0:0:
82,69,14429,1,0,0:0:0:0:
106,190,14540,2,0,L|126:276,1,74.999998807907,8|0,3:0|0:0,0:0:0:0:
218,383,14762,2,0,L|234:309,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
26,231,14985,5,0,3:0:0:0:
253,202,15207,37,0,0:0:0:0:
331,271,15318,1,0,0:0:0:0:
233,309,15429,1,8,3:0:0:0:
389,73,15651,6,0,P|410:22|447:112,1,224.999996423721,4|0,3:0|0:0,0:0:0:0:
391,165,16096,1,0,0:0:0:0:
377,177,16207,1,0,0:0:0:0:
365,187,16318,38,0,B|253:261|221:119|94:192,1,269.999987468719,0|0,0:0|0:0,0:0:0:0:
73,319,16762,22,0,P|133:336|116:236,1,168.750003755093,4|0,3:0|0:0,0:0:0:0:
139,258,17207,6,0,P|138:315|69:283,1,112.500002503395,8|0,3:0|0:0,0:0:0:0:
92,323,17762,37,0,0:0:0:0:
43,245,17874,1,4,0:0:0:0:
4,322,17985,1,0,0:0:0:0:
133,245,18096,1,0,3:0:0:0:
29,105,18318,6,0,L|38:40,3,56.2500012516975,4|0|0|0,3:0|0:0|0:0|0:0,0:0:0:0:
50,30,18540,38,0,P|111:56|193:25,1,149.999997615814,0|0,3:0|0:0,0:0:0:0:
240,120,18985,2,0,P|328:91|394:125,1,149.999997615814,8|4,3:0|0:0,0:0:0:0:
409,213,19318,2,0,B|377:226|377:226|243:200,1,149.999997615814,0|0,0:0|0:0,0:0:0:0:
119,187,19651,2,0,L|127:286,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
179,338,19874,1,8,3:0:0:0:
45,307,19985,6,0,L|3:297,2,37.4999994039535,0|0|4,0:0|0:0|0:0,0:0:0:0:
103,380,20207,1,0,3:0:0:0:
212,257,20318,38,0,P|233:218|231:171,1,74.999998807907,0|0,3:0|0:0,0:0:0:0:
111,118,20540,1,4,0:0:0:0:
111,118,20762,6,0,L|197:109,1,74.999998807907,8|4,3:0|0:0,0:0:0:0:
256,18,21096,37,0,0:0:0:0:
337,121,21207,2,0,P|350:60|403:16,1,112.49999821186,0|0,3:0|0:0,0:0:0:0:
384,26,21429,2,0,P|406:86|465:122,1,112.49999821186,0|0,0:0|0:0,0:0:0:0:
443,114,21651,2,0,P|377:105|327:131,1,112.49999821186,8|0,3:0|0:0,0:0:0:0:
352,223,21874,6,0,B|369:230|369:230|391:228|391:228|416:239|416:239|440:235|440:235|462:244|462:244|489:249,1,112.500002503395,4|0,0:0|0:0,0:0:0:0:
322,343,22096,37,0,3:0:0:0:
259,270,22207,2,0,P|223:276|182:263,2,74.999998807907,0|4|0,0:0|0:0|0:0,0:0:0:0:
86,360,22540,5,8,3:0:0:0:
15,295,22651,2,0,L|0:201,2,74.999998807907,0|4|0,0:0|0:0|0:0,0:0:0:0:
94,384,22985,38,0,P|118:328|112:277,1,112.49999821186,0|0,3:0|0:0,0:0:0:0:
0,211,23429,22,0,L|76:196,1,74.999998807907,12|0,3:0|0:0,0:0:0:0:
215,134,23651,2,0,L|114:110,1,74.999998807907,12|0,3:0|0:0,0:0:0:0:
33,124,23874,22,0,L|43:2,1,112.49999821186,0|0,3:0|0:0,0:0:0:0:
150,269,24318,2,0,L|162:194,1,74.999998807907,0|4,3:0|0:0,0:0:0:0:
229,134,24651,6,0,L|386:164,1,112.500002503395,12|0,3:0|0:0,0:0:0:0:
486,268,24874,37,0,0:0:0:0:
410,119,24985,1,4,0:0:0:0:
381,213,25096,1,0,0:0:0:0:
512,120,25207,1,0,3:0:0:0:
247,36,25429,6,0,L|191:25,3,37.4999994039535,4|0|0|0,3:0|0:0|0:0|0:0,0:0:0:0:
185,24,25651,2,0,B|145:72|145:72|174:164,1,149.999997615814,0|0,3:0|0:0,0:0:0:0:
253,219,26096,2,0,B|281:311|281:311|228:382,1,149.999997615814,8|4,3:0|0:0,0:0:0:0:
100,363,26429,38,0,L|259:354,1,149.999997615814,0|0,0:0|0:0,0:0:0:0:
404,262,26762,1,4,0:0:0:0:
390,352,26874,1,0,0:0:0:0:
314,295,26985,1,8,3:0:0:0:
425,256,27096,6,0,L|492:246,2,37.4999994039535,0|0|4,0:0|0:0|0:0,0:0:0:0:
329,216,27318,1,0,3:0:0:0:
193,177,27429,38,0,L|266:161,1,74.999998807907,0|0,3:0|0:0,0:0:0:0:
322,107,27651,1,4,0:0:0:0:
322,107,27874,2,0,L|310:238,1,112.500002503395,8|4,3:0|0:0,0:0:0:0:
110,299,28207,5,0,0:0:0:0:
164,231,28318,2,0,B|168:303|168:303|121:338,1,112.49999821186,0|0,3:0|0:0,0:0:0:0:
30,284,28540,2,0,B|90:244|90:244|144:267,1,112.49999821186,0|0,0:0|0:0,0:0:0:0:
148,371,28762,2,0,B|83:338|83:338|76:280,1,112.49999821186,8|0,3:0|0:0,0:0:0:0:
194,201,28985,38,0,B|207:210|207:210|227:210|227:210|243:217|243:217|265:218|265:218|282:227|282:227|305:225|305:225|325:238,1,112.500002503395,4|0,0:0|0:0,0:0:0:0:
492,114,29207,6,0,P|445:136|410:138,1,74.999998807907,0|0,3:0|0:0,0:0:0:0:
324,102,29429,2,0,P|291:68|280:29,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
418,17,29651,1,8,3:0:0:0:
495,201,29874,1,4,3:2:0:0:
221,136,30096,37,0,3:0:0:0:
299,188,30207,2,0,B|316:251|316:251|271:352,1,149.999997615814,4|0,3:0|0:0,0:0:0:0:
115,334,30540,6,0,P|11:252|167:266,1,382.500001215934,0|0,0:0|0:0,0:0:0:0:
216,326,30985,38,0,L|304:331,1,63.7500002026557,4|0,3:0|0:0,0:0:0:0:
280,330,31429,6,0,L|293:241,1,74.999998807907,8|0,3:0|0:0,0:0:0:0:
426,252,31651,2,0,L|439:163,1,74.999998807907,0|0,0:0|0:0,0:0:0:0:
253,158,31874,37,0,3:0:0:0:
258,132,31985,1,0,0:0:0:0:
337,111,32096,5,4,0:0:0:0:
341,85,32207,1,0,0:0:0:0:
271,30,32318,38,0,B|212:42|212:42|141:19,1,112.49999821186,8|0,3:0|0:0,0:0:0:0:
163,26,32540,2,0,L|144:181,1,149.999997615814,4|0,0:0|3:0,0:0:0:0:
445,343,32985,22,0,B|439:234|439:234|384:269,1,149.999997615814,4|8,0:0|3:0,0:0:0:0:
240,257,33429,2,0,B|263:148|263:148|291:205,1,149.999997615814,4|0,0:0|3:0,0:0:0:0:
68,333,33874,2,0,B|83:233|83:233|41:256,1,149.999997615814,4|8,0:0|3:0,0:0:0:0:
344,347,34318,22,0,B|368:372|368:372|455:355|455:355|472:308,1,149.999997615814,4|0,0:0|0:0,0:0:0:0:
452,255,34540,2,0,B|389:212|389:212|332:273,1,149.999997615814,0|4,3:0|0:0,0:0:0:0:
256,220,34874,5,0,0:0:0:0:
256,220,34985,2,0,B|256:128,1,74.999998807907,8|0,3:0|0:0,0:0:0:0:
256,70,35207,2,0,B|256:162,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
112,312,35429,37,0,3:0:0:0:
60,255,35540,2,0,B|123:212|123:212|180:273,1,149.999997615814,0|0,0:0|0:0,0:0:0:0:
169,350,35874,6,0,B|144:375|144:375|57:358|57:358|40:311,1,149.999997615814,8|0,3:0|3:0,0:0:0:0:
62,169,36429,6,0,L|76:267,2,74.999998807907,0|4|0,0:0|0:0|0:0,0:0:0:0:
134,61,36762,1,8,3:0:0:0:
201,113,36874,2,0,L|215:211,2,74.999998807907,0|4|0,0:0|0:0|0:0,0:0:0:0:
298,272,37207,6,0,L|315:184,1,74.999998807907,0|0,3:0|0:0,0:0:0:0:
330,114,37429,1,4,0:0:0:0:
446,176,37540,2,0,B|404:214|404:214|307:197,1,149.999997615814,4|0,3:0|0:0,0:0:0:0:
231,240,37874,2,0,P|223:199|231:162,1,74.999998807907,4|0,3:0|0:0,0:0:0:0:
325,285,38096,6,0,L|154:300,1,149.999997615814,4|0,3:0|0:0,0:0:0:0:
175,298,38540,6,0,L|163:396,1,74.999998807907,8|0,3:0|0:0,0:0:0:0:
75,208,38762,2,0,L|63:306,1,74.999998807907,0|0,0:0|0:0,0:0:0:0:
233,74,38985,37,0,3:0:0:0:
231,98,39096,1,0,0:0:0:0:
156,139,39207,5,4,0:0:0:0:
155,165,39318,1,0,0:0:0:0:
227,215,39429,38,0,P|282:209|352:230,1,112.49999821186,8|0,3:0|0:0,0:0:0:0:
336,222,39651,2,0,L|366:67,1,149.999997615814,4|0,0:0|3:0,0:0:0:0:
81,35,40096,22,0,B|82:105|82:105|118:136|118:136|132:89,1,149.999997615814,4|8,0:0|3:0,0:0:0:0:
272,158,40540,2,0,B|270:228|270:228|234:259|234:259|220:212,1,149.999997615814,4|0,0:0|3:0,0:0:0:0:
423,36,40985,2,0,B|400:102|400:102|423:143|423:143|453:104,1,149.999997615814,4|8,0:0|3:0,0:0:0:0:
512,278,41429,6,0,P|415:258|361:293,1,149.999997615814,4|0,0:0|0:0,0:0:0:0:
359,302,41651,6,0,B|320:264|320:264|310:187,1,112.49999821186,0|0,3:0|0:0,0:0:0:0:
322,190,41874,2,0,L|449:171,1,112.49999821186,4|0,0:0|0:0,0:0:0:0:
443,159,42096,1,8,3:0:0:0:
240,52,42318,6,0,B|255:79|255:79|241:135,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
177,166,42540,1,0,3:0:0:0:
163,151,42651,2,0,B|161:207|161:207|192:240|192:240|189:299,1,149.999997615814,0|0,0:0|0:0,0:0:0:0:
131,365,42985,2,0,P|198:322|280:345,1,149.999997615814,8|4,3:0|0:0,0:0:0:0:
335,377,43318,1,0,0:0:0:0:
442,239,43429,38,0,B|456:178|456:178|422:136|422:136|427:68|427:68|449:112,1,224.999996423721,0|0,3:0|0:0,0:0:0:0:
444,103,43874,2,0,P|402:118|356:120,1,74.999998807907,0|0,0:0|0:0,0:0:0:0:
249,28,44096,2,0,P|295:35|324:48,1,74.999998807907,0|0,0:0|0:0,0:0:0:0:
364,201,44318,5,0,0:0:0:0:
332,195,44429,1,0,0:0:0:0:
251,135,44540,37,0,0:0:0:0:
281,123,44651,1,0,0:0:0:0:
332,195,44762,6,0,B|356:269|324:333|324:333|303:293,1,179.999991645813,4|0,0:3|0:0,0:0:0:0:
61,25,45207,38,0,L|88:158,1,112.500002503395,0|0,3:0|0:0,0:0:0:0:
84,136,45651,1,8,3:0:0:0:
84,136,46096,1,0,3:0:0:0:
176,33,46207,2,0,L|164:103,1,56.2500012516975,0|0,0:0|0:0,0:0:0:0:
219,207,46429,2,0,L|232:152,1,56.2500012516975,0|8,0:0|3:0,0:0:0:0:
312,65,46651,1,0,0:0:0:0:
312,65,46762,2,0,L|398:94,1,56.2500012516975,0|0,0:0|0:0,0:0:0:0:
512,176,46985,5,0,3:0:0:0:
421,192,47096,1,0,0:0:0:0:
421,192,47429,1,8,3:0:0:0:
402,357,47651,37,0,0:0:0:0:
394,277,47762,1,0,0:0:0:0:
328,324,47874,1,0,3:0:0:0:
328,324,48318,1,8,3:0:0:0:
110,357,48540,5,0,0:0:0:0:
118,277,48651,1,0,0:0:0:0:
184,324,48763,1,0,3:0:0:0:
110,357,48874,1,0,0:0:0:0:
110,357,49207,1,8,3:0:0:0:
110,357,49651,1,0,3:0:0:0:
0,283,49762,38,0,P|41:301|97:295,1,56.2500012516975,0|0,0:0|0:0,0:0:0:0:
188,219,49985,2,0,P|168:236|137:246,1,56.2500012516975,0|8,0:0|3:0,0:0:0:0:
49,137,50207,1,0,0:0:0:0:
49,137,50318,2,0,P|65:184|93:205,1,56.2500012516975,0|0,0:0|0:0,0:0:0:0:
107,67,50540,5,0,3:0:0:0:
32,15,50651,1,0,0:0:0:0:
32,15,50985,1,8,3:0:0:0:
265,114,51207,37,0,0:0:0:0:
254,196,51318,1,0,0:0:0:0:
241,279,51429,1,0,3:0:0:0:
241,279,51651,1,0,0:0:0:0:
336,207,51762,6,0,P|397:191|371:274,1,168.750003755093,0|0,0:0|0:0,0:0:0:0:
83,206,52318,5,0,3:0:0:0:
83,206,52429,2,0,L|101:260,1,56.2500012516975,0|0,0:0|0:0,0:0:0:0:
40,383,52651,2,0,P|70:355|90:324,1,56.2500012516975,0|8,0:0|3:0,0:0:0:0:
214,334,52874,1,0,0:0:0:0:
214,334,52985,2,0,P|171:322|140:304,1,56.2500012516975,0|0,0:0|0:0,0:0:0:0:
151,160,53207,5,0,3:0:0:0:
188,135,53318,1,0,0:0:0:0:
232,129,53429,1,0,0:0:0:0:
273,146,53540,1,0,0:0:0:0:
339,198,53651,37,8,3:0:0:0:
383,199,53762,1,0,0:0:0:0:
426,185,53874,1,0,0:0:0:0:
450,147,53985,1,0,0:0:0:0:
444,61,54096,6,0,P|414:28|377:15,1,56.2500012516975,0|0,3:0|0:0,0:0:0:0:
301,28,54318,2,0,P|268:48|255:77,1,56.2500012516975,0|0,0:0|0:0,0:0:0:0:
189,271,54540,38,0,P|209:222|204:198,1,56.2500012516975,8|0,3:0|0:0,0:0:0:0:
186,114,54762,2,0,P|152:74|124:68,1,56.2500012516975,0|0,0:0|0:0,0:0:0:0:
27,137,54985,5,0,3:0:0:0:
34,167,55096,1,0,0:0:0:0:
122,204,55207,37,0,0:0:0:0:
116,178,55318,1,0,0:0:0:0:
48,249,55429,5,8,3:0:0:0:
54,274,55540,1,0,0:0:0:0:
124,329,55651,38,0,P|157:326|200:310,1,56.2500012516975,0|0,0:0|0:0,0:0:0:0:
320,185,55874,5,0,3:0:0:0:
287,175,55985,1,0,0:0:0:0:
254,181,56096,2,0,P|258:221|264:241,1,56.2500012516975,0|0,0:0|0:0,0:0:0:0:
337,347,56318,2,0,P|348:321|350:293,1,56.2500012516975,8|0,3:0|0:0,0:0:0:0:
418,197,56540,37,0,0:0:0:0:
418,197,56651,2,0,L|492:180,1,56.2500012516975,0|0,0:0|3:0,0:0:0:0:
329,114,56874,2,0,L|262:94,1,56.2500012516975,0|0,0:0|0:0,0:0:0:0:
436,59,57096,6,0,L|413:126,1,56.2500012516975,0|8,0:0|3:0,0:0:0:0:
332,194,57318,2,0,L|353:259,2,56.2500012516975,0|0|0,0:0|0:0|0:0,0:0:0:0:
202,194,57651,37,0,3:0:0:0:
224,233,57762,1,0,0:0:0:0:
222,279,57874,1,0,0:0:0:0:
193,314,57985,1,0,0:0:0:0:
144,244,58096,5,0,0:0:0:0:
127,214,58207,1,0,0:0:0:0:
126,180,58318,1,0,0:0:0:0:
139,149,58429,1,0,0:0:0:0:
224,113,58540,38,0,B|262:88|235:70|189:83|189:83|224:138|194:193,1,194.999987974167
299,319,58874,1,0,0:0:0:0:
299,319,58985,2,0,B|316:283|314:237|314:237|278:226|278:226|320:243|359:227,1,202.49999060154,4|0,0:0|0:0,0:0:0:0:
428,181,59429,22,0,P|454:129|399:4,1,179.999991645813,0|0,3:0|0:0,0:0:0:0:
418,18,59874,2,0,L|373:15,6,24.9999996026357,8|0|0|0|0|0|4,3:0|0:0|0:0|0:0|0:0|0:0|0:0,0:0:0:0:
428,181,60207,5,0,0:0:0:0:
352,209,60318,1,0,3:0:0:0:
278,177,60429,1,0,0:0:0:0:
208,225,60540,2,0,L|222:267,2,37.4999994039535,4|0|0,0:0|0:0|0:0,0:0:0:0:
71,144,60762,38,0,L|65:109,3,24.9999996026357,8|0|0|0,3:0|0:0|0:0|0:0,0:0:0:0:
145,86,60985,1,4,0:0:0:0:
163,127,61096,1,0,0:0:0:0:
161,171,61207,1,0,3:0:0:0:
136,208,61318,1,0,0:0:0:0:
99,231,61429,2,0,B|91:279|91:279|117:314,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
177,378,61651,5,8,3:0:0:0:
177,378,61762,2,0,B|231:371|231:371|272:326|272:326|345:319,1,179.999991645813
417,293,62096,1,0,3:0:0:0:
438,263,62207,1,0,0:0:0:0:
436,225,62318,1,4,0:0:0:0:
412,196,62429,1,0,0:0:0:0:
320,172,62540,6,0,P|307:192|291:204,1,37.4999994039535,8|0,3:0|0:0,0:0:0:0:
291,147,62651,2,0,P|274:156|245:153,1,37.4999994039535,0|0,0:0|0:0,0:0:0:0:
276,114,62762,2,0,P|250:107|234:94,1,37.4999994039535,4|0,0:0|0:0,0:0:0:0:
283,81,62874,2,0,P|265:61|260:45,1,37.4999994039535,0|0,0:0|0:0,0:0:0:0:
365,31,62985,38,0,P|398:44|442:49,1,74.999998807907,0|0,3:0|0:0,0:0:0:0:
512,169,63207,2,0,P|466:163|421:176,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
350,107,63429,1,8,3:0:0:0:
293,237,63540,38,0,L|276:158,1,74.999998807907,0|0,0:0|0:0,0:0:0:0:
428,269,63762,2,0,B|373:275|373:275|338:249|338:249|267:255,1,149.999997615814,0|0,0:0|0:0,0:0:0:0:
191,318,64096,2,0,B|182:355|182:355|212:395,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
192,186,64318,5,8,3:0:0:0:
135,253,64429,2,0,L|56:270,2,74.999998807907,0|4|0,0:0|0:0|0:0,0:0:0:0:
24,136,64762,38,0,P|69:76|158:75,1,157.499992690086,0|0,3:0|0:0,0:0:0:0:
160,80,64985,6,0,P|193:102|255:102,1,84.3750018775463,4|0,0:0|0:0,0:0:0:0:
276,34,65207,38,0,L|290:212,1,157.499992690086,8|0,3:0|0:0,0:0:0:0:
291,219,65429,6,0,L|311:132,1,84.3750018775463,4|0,0:0|0:0,0:0:0:0:
381,111,65651,38,0,B|418:126|418:126|460:126,1,74.999998807907,0|0,3:0|0:0,0:0:0:0:
221,163,65874,2,0,B|186:143|186:143|139:153,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
41,231,66096,1,8,3:0:0:0:
49,267,66207,1,0,0:0:0:0:
56,303,66318,1,4,0:0:0:0:
67,288,66429,1,4,0:0:0:0:
77,270,66540,6,0,P|171:255|72:350,1,337.500007510185,0|0,0:0|0:0,0:0:0:0:
95,356,66985,38,0,L|185:343,1,74.999998807907,8|4,3:0|0:0,0:0:0:0:
274,286,67318,6,0,B|289:324|289:324|268:378,1,74.999998807907,0|0,0:0|3:0,0:0:0:0:
191,227,67540,1,0,0:0:0:0:
255,168,67651,2,0,L|264:116,2,37.4999994039535,4|0|0,0:0|0:0|0:0,0:0:0:0:
147,83,67874,2,0,L|154:108,3,24.9999996026357,8|0|0|0,3:0|0:0|0:0|0:0,0:0:0:0:
80,148,68096,38,0,L|98:224,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
125,356,68318,1,0,3:0:0:0:
0,319,68429,1,0,0:0:0:0:
0,319,68540,2,0,L|76:294,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
277,219,68762,5,8,3:0:0:0:
277,219,68874,2,0,B|327:199|327:199|293:138|197:173,1,179.999991645813,0|0,0:0|0:0,0:0:0:0:
157,273,69207,37,0,3:0:0:0:
175,316,69318,1,0,0:0:0:0:
212,334,69429,1,4,0:0:0:0:
254,333,69540,1,0,0:0:0:0:
332,268,69651,38,0,P|333:237|343:213,1,37.4999994039535,8|0,3:0|0:0,0:0:0:0:
373,265,69762,2,0,P|386:239|404:232,1,37.4999994039535,0|0,0:0|0:0,0:0:0:0:
413,284,69874,2,0,P|430:269|454:269,1,37.4999994039535,4|0,0:0|0:0,0:0:0:0:
433,318,69985,2,0,P|452:320|474:337,1,37.4999994039535,4|0,0:0|0:0,0:0:0:0:
401,384,70096,6,0,P|353:378|319:346,1,74.999998807907,0|0,3:0|0:0,0:0:0:0:
251,251,70318,2,0,P|240:196|260:154,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
401,18,70540,1,8,3:0:0:0:
401,18,70651,2,0,P|409:54|398:90,1,74.999998807907,0|0,0:0|0:0,0:0:0:0:
327,193,70874,2,0,L|304:45,1,149.999997615814,0|0,0:0|0:0,0:0:0:0:
290,26,71207,6,0,L|308:144,1,104.999995126724,0|0,0:0|0:0,0:0:0:0:
272,302,71429,2,0,L|187:288,1,74.999998807907,8|0,3:0|0:0,0:0:0:0:
33,217,71651,37,4,0:0:0:0:
27,187,71762,1,4,0:0:0:0:
20,157,71874,2,0,B|103:140|103:140|162:58,1,157.499992690086,0|0,3:0|0:0,0:0:0:0:
145,82,72096,6,0,L|218:75,1,56.2500012516975,0|0,0:0|0:0,0:0:0:0:
336,136,72318,38,0,P|331:213|231:208,1,157.499992690086
263,232,72540,6,0,L|278:300,1,56.2500012516975,0|0,0:0|0:0,0:0:0:0:
183,384,72762,2,0,L|172:307,1,56.2500012516975,0|0,0:0|0:0,0:0:0:0:
37,140,72985,38,0,B|10:168|10:168|17:204|17:204|54:220|54:220|89:196|89:196|87:157|87:157|57:138,1,225.00000500679
275,372,73651,6,0,P|320:352|387:369,1,112.500002503395
380,364,74096,2,0,L|436:358,1,56.2500012516975,0|0,0:0|0:0,0:0:0:0:
495,271,74318,2,0,L|424:282,1,56.2500012516975,0|0,0:0|0:0,0:0:0:0:
339,270,74540,1,0,0:0:0:0:
339,270,74651,1,0,0:0:0:0:
339,270,74762,2,0,L|329:196,1,56.2500012516975,0|0,0:0|0:0,0:0:0:0:
408,46,74985,38,0,L|392:120,1,56.2500012516975
220,230,75207,2,0,L|209:156,1,56.2500012516975,0|0,0:0|0:0,0:0:0:0:
282,7,75429,37,0,0:0:0:0:
300,98,75540,1,0,0:0:0:0:
197,25,75651,5,0,0:0:0:0:
222,103,75762,1,0,0:0:0:0:
126,69,75874,5,0,0:0:0:0:
153,134,75985,1,0,0:0:0:0:
76,145,76096,5,0,0:0:0:0:
116,179,76207,1,0,0:0:0:0:
70,222,76318,5,0,0:0:0:0:
111,222,76429,1,0,0:0:0:0:
134,253,76540,6,0,P|135:298|126:314,1,56.2500012516975,0|0,0:0|0:0,0:0:0:0:
21,384,76762,2,0,P|124:354|260:391,1,224.999996423721,0|0,0:0|0:0,0:0:0:0:
384,366,77207,22,0,L|394:268,1,74.999998807907,0|0,3:0|0:0,0:0:0:0:
499,62,77429,2,0,L|486:135,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
507,237,77651,2,0,P|450:231|388:184,1,112.49999821186,8|0,3:0|0:0,0:0:0:0:
404,203,77874,2,0,L|313:217,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
113,212,78096,6,0,P|128:267|111:328,1,112.49999821186,0|0,3:0|0:0,0:0:0:0:
115,319,78318,2,0,L|213:340,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
274,371,78540,38,0,L|257:186,1,179.999991645813,8|0,3:0|0:0,0:0:0:0:
128,139,78874,1,0,0:0:0:0:
128,139,78985,6,0,L|230:128,1,74.999998807907,0|0,3:0|0:0,0:0:0:0:
365,34,79207,37,4,0:0:0:0:
430,114,79318,1,0,0:0:0:0:
361,184,79429,2,0,P|304:170|277:110,1,112.49999821186,8|0,3:0|0:0,0:0:0:0:
278,126,79651,2,0,L|189:133,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
64,263,79874,6,0,B|37:230|37:230|50:143,1,112.49999821186,0|0,3:0|0:0,0:0:0:0:
66,119,80096,2,0,L|80:210,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
71,361,80318,38,0,B|135:350|135:350|182:305|182:305|243:297,1,179.999991645813,8|0,3:0|0:0,0:0:0:0:
302,247,80651,1,0,0:0:0:0:
222,211,80762,1,0,3:0:0:0:
478,344,80985,5,4,0:0:0:0:
491,309,81096,5,0,0:0:0:0:
498,265,81207,5,8,3:0:0:0:
485,223,81318,5,0,0:0:0:0:
458,179,81429,5,4,0:0:0:0:
418,147,81540,5,0,0:0:0:0:
352,126,81651,5,0,3:0:0:0:
281,149,81762,5,0,0:0:0:0:
239,221,81874,5,4,0:0:0:0:
159,262,81985,5,0,0:0:0:0:
66,234,82096,5,8,3:0:0:0:
11,145,82207,5,0,0:0:0:0:
55,33,82318,5,4,0:0:0:0:
273,44,82540,37,0,3:0:0:0:
320,103,82651,1,0,0:0:0:0:
394,118,82762,1,4,0:0:0:0:
468,100,82874,1,0,0:0:0:0:
507,36,82985,1,8,3:0:0:0:
495,19,83207,5,4,0:0:0:0:
335,83,83318,1,0,0:0:0:0:
453,81,83429,1,0,3:0:0:0:
283,24,83540,2,0,P|196:37|141:120,1,149.999997615814,0|0,0:0|0:0,0:0:0:0:
60,238,83874,1,8,3:0:0:0:
21,164,83985,2,0,P|59:149|175:193,1,149.999997615814,0|8,0:0|3:0,0:0:0:0:
252,206,84318,38,0,P|271:160|264:125,1,74.999998807907,0|0,3:0|0:0,0:0:0:0:
139,257,84540,2,0,P|131:302|149:340,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
240,379,84762,2,0,B|330:360|330:360|298:344,1,112.49999821186,8|0,3:0|0:0,0:0:0:0:
312,351,84985,2,0,P|279:321|270:287,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
359,165,85207,6,0,B|389:202|389:202|368:282,1,112.49999821186,0|0,3:0|0:0,0:0:0:0:
373,265,85429,2,0,L|454:282,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
498,139,85651,38,0,P|446:120|396:0,1,179.999991645813,8|0,3:0|0:0,0:0:0:0:
394,13,85985,1,0,0:0:0:0:
301,92,86096,6,0,L|214:83,1,74.999998807907,0|0,3:0|0:0,0:0:0:0:
66,66,86318,1,4,0:0:0:0:
13,136,86429,1,0,0:0:0:0:
72,193,86540,2,0,P|120:210|190:178,1,112.49999821186,8|0,3:0|0:0,0:0:0:0:
176,192,86762,2,0,P|154:237|160:288,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
309,370,86985,37,0,3:0:0:0:
359,310,87096,1,0,0:0:0:0:
283,297,87207,2,0,L|203:318,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
4,203,87429,2,0,B|55:211|55:211|82:255|82:255|134:266,1,149.999997615814,8|0,3:0|0:0,0:0:0:0:
238,217,87762,1,0,0:0:0:0:
183,120,87874,6,0,L|89:111,1,74.999998807907,0|0,3:0|0:0,0:0:0:0:
98,33,88096,2,0,L|23:26,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
306,182,88318,38,0,L|400:173,1,74.999998807907,8|0,3:0|0:0,0:0:0:0:
391,95,88540,2,0,L|465:88,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
232,28,88762,2,0,L|220:92,1,37.4999994039535,0|0,3:0|0:0,0:0:0:0:
243,39,88874,2,0,L|231:103,1,37.4999994039535,0|0,0:0|0:0,0:0:0:0:
256,50,88985,2,0,L|251:87,1,37.4999994039535,4|0,0:0|0:0,0:0:0:0:
485,87,89207,6,0,L|493:51,3,37.4999994039535,8|0|0|0,3:0|0:0|0:0|0:0,0:0:0:0:
396,120,89429,2,0,L|411:197,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
471,317,89651,38,0,P|411:299|320:336,1,149.999997615814,0|4,3:0|0:0,0:0:0:0:
61,239,90096,2,0,P|121:221|212:258,1,149.999997615814,8|4,3:0|0:0,0:0:0:0:
367,21,90540,6,0,P|336:57|328:104,1,74.999998807907,0|0,3:0|0:0,0:0:0:0:
163,96,90762,2,0,P|194:132|202:179,1,74.999998807907,0|0,0:0|0:0,0:0:0:0:
190,346,90985,37,8,3:0:0:0:
328,272,91096,1,0,0:0:0:0:
154,272,91207,5,8,3:0:0:0:
365,338,91318,1,0,0:0:0:0:
257,382,91429,38,0,B|290:333|224:286|269:219,1,149.999997615814,4|4,3:0|0:0,0:0:0:0:
325,196,91762,1,0,0:0:0:0:
325,196,91874,2,0,P|365:210|436:184,1,112.49999821186,8|0,3:0|0:0,0:0:0:0:
430,190,92096,2,0,B|418:110,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
313,19,92318,2,0,L|190:36,1,112.49999821186,0|0,3:0|0:0,0:0:0:0:
201,34,92540,2,0,B|214:117,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
209,252,92762,5,8,3:0:0:0:
156,261,92874,1,0,0:0:0:0:
112,231,92985,1,4,0:0:0:0:
60,222,93096,1,0,0:0:0:0:
13,247,93207,38,0,P|4:288|19:328,1,74.999998807907,0|0,3:0|0:0,0:0:0:0:
173,186,93429,1,4,0:0:0:0:
215,120,93540,1,0,0:0:0:0:
162,49,93651,2,0,P|125:39|76:61,1,74.999998807907,8|0,3:0|0:0,0:0:0:0:
234,138,93874,2,0,P|273:157|313:148,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
385,39,94096,5,0,3:0:0:0:
337,286,94318,2,0,L|322:373,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
409,327,94540,2,0,P|418:277|280:230,1,224.999996423721,8|0,3:0|0:0,0:0:0:0:
239,319,94985,2,0,P|218:357|173:373,1,74.999998807907,0|0,3:0|0:0,0:0:0:0:
34,344,95207,37,4,0:0:0:0:
21,309,95318,5,0,0:0:0:0:
14,265,95429,5,8,3:0:0:0:
27,223,95540,5,0,0:0:0:0:
54,179,95651,5,4,0:0:0:0:
94,147,95762,5,0,0:0:0:0:
160,126,95873,5,0,3:0:0:0:
231,149,95984,5,0,0:0:0:0:
273,221,96096,5,4,0:0:0:0:
353,262,96207,5,0,0:0:0:0:
446,234,96318,5,8,3:0:0:0:
501,145,96429,5,0,0:0:0:0:
450,36,96540,5,4,0:0:0:0:
239,44,96762,5,0,3:0:0:0:
192,103,96873,1,0,0:0:0:0:
118,118,96984,1,4,0:0:0:0:
44,100,97096,1,0,0:0:0:0:
5,36,97207,1,8,3:0:0:0:
17,19,97429,37,4,0:0:0:0:
146,51,97540,1,0,0:0:0:0:
29,122,97651,2,0,L|39:193,1,56.2499991059302,0|0,3:0|0:0,0:0:0:0:
44,197,97874,6,0,P|100:231|176:201,1,112.500002503395,4|0,0:0|0:0,0:0:0:0:
301,160,98096,38,0,P|329:140|382:137,1,84.3750018775463,8|0,3:0|0:0,0:0:0:0:
398,147,98318,6,0,B|431:187|431:187|415:279,1,112.500002503395,4|0,0:0|0:0,0:0:0:0:
265,371,98540,38,0,L|180:361,1,74.999998807907,0|0,3:0|0:0,0:0:0:0:
127,202,98762,2,0,L|141:113,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
193,260,98985,2,0,P|144:291|68:278,1,112.49999821186,8|0,3:0|0:0,0:0:0:0:
91,290,99207,2,0,L|79:373,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
20,184,99429,6,0,B|4:141|4:141|27:66,1,112.49999821186,0|0,3:0|0:0,0:0:0:0:
23,78,99651,2,0,L|109:91,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
271,74,99874,2,0,P|254:31|222:12,1,74.999998807907,8|0,3:0|0:0,0:0:0:0:
186,180,100096,2,0,P|232:175|260:147,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
132,63,100318,37,0,3:0:0:0:
253,157,100540,1,4,0:0:0:0:
285,167,100651,1,0,0:0:0:0:
357,129,100762,5,8,3:0:0:0:
389,139,100873,1,0,0:0:0:0:
422,148,100985,2,0,P|407:200|416:233,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
459,377,101207,38,0,P|472:333|459:295,1,74.999998807907,0|0,3:0|0:0,0:0:0:0:
398,242,101429,2,0,L|314:257,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
165,354,101651,2,0,P|116:332|211:264,1,224.999996423721,8|0,3:0|0:0,0:0:0:0:
302,165,102096,6,0,L|292:89,1,74.999998807907,0|0,3:0|0:0,0:0:0:0:
392,91,102318,2,0,L|382:14,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
192,229,102540,38,0,L|212:136,1,74.999998807907,8|0,3:0|0:0,0:0:0:0:
107,172,102762,2,0,L|127:79,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
314,332,102985,6,0,L|305:278,1,37.4999994039535,0|0,3:0|0:0,0:0:0:0:
343,345,103096,2,0,L|334:291,1,37.4999994039535,0|0,0:0|0:0,0:0:0:0:
370,358,103207,2,0,L|361:304,1,37.4999994039535,4|0,0:0|0:0,0:0:0:0:
380,117,103429,38,0,L|374:75,3,37.4999994039535,8|0|0|0,3:0|0:0|0:0|0:0,0:0:0:0:
444,166,103651,2,0,P|417:188|346:191,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
392,2,103874,2,0,P|424:14|462:74,1,74.999998807907,4|0,3:0|0:0,0:0:0:0:
271,129,104096,2,0,P|265:94|298:31,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
505,113,104318,5,8,3:0:0:0:
269,217,104540,38,0,L|216:216,3,37.4999994039535,0|0|0|0,3:0|3:0|3:0|3:0,0:0:0:0:
360,220,104762,1,0,3:0:0:0:
296,384,104874,1,4,3:0:0:0:
102,307,105096,5,0,0:0:0:0:
102,307,105207,2,0,B|206:381|258:244|374:330,1,269.999987468719,12|0,3:0|0:0,0:0:0:0:
439,319,105651,6,0,P|379:336|396:236,1,168.750003755093,0|0,3:0|0:0,0:0:0:0:
373,258,106096,6,0,P|374:315|443:283,1,112.500002503395,8|0,3:0|0:0,0:0:0:0:
420,323,106651,37,0,0:0:0:0:
469,245,106763,1,4,0:0:0:0:
508,322,106874,1,0,0:0:0:0:
379,245,106985,1,8,3:0:0:0:
483,105,107207,6,0,L|474:40,3,56.2500012516975,4|0|0|0,3:0|0:0|0:0|0:0,0:0:0:0:
462,30,107429,38,0,P|401:56|319:25,1,149.999997615814,0|0,3:0|0:0,0:0:0:0:
272,120,107874,2,0,P|184:91|118:125,1,149.999997615814,8|4,3:0|0:0,0:0:0:0:
103,213,108207,2,0,B|128:232|128:232|269:200,1,149.999997615814,0|0,0:0|0:0,0:0:0:0:
393,187,108540,2,0,L|385:286,1,74.999998807907,4|0,0:0|0:0,0:0:0:0:
333,338,108763,1,8,3:0:0:0:
467,307,108874,6,0,L|509:297,2,37.4999994039535,0|0|4,0:0|0:0|0:0,0:0:0:0:
409,380,109096,1,0,0:0:0:0:
300,257,109207,38,0,P|279:218|281:171,1,74.999998807907,0|0,3:0|0:0,0:0:0:0:
401,118,109429,1,4,0:0:0:0:
401,118,109651,6,0,L|315:109,1,74.999998807907,8|4,3:0|0:0,0:0:0:0:
256,15,109985,37,0,0:0:0:0:
175,121,110096,2,0,P|162:60|109:16,1,112.49999821186,0|0,3:0|0:0,0:0:0:0:
128,26,110318,2,0,P|106:86|47:122,1,112.49999821186,0|0,0:0|0:0,0:0:0:0:
69,114,110540,2,0,P|135:105|185:131,1,112.49999821186,8|0,3:0|0:0,0:0:0:0:
160,223,110762,6,0,B|142:230|142:230|120:228|120:228|95:239|95:239|71:235|71:235|49:244|49:244|22:249,1,112.500002503395,4|0,0:0|0:0,0:0:0:0:
193,334,110985,38,0,P|216:310|242:301,1,56.2500012516975,0|0,3:0|0:0,0:0:0:0:
335,325,111207,2,0,P|366:353|378:379,1,56.2500012516975,0|0,0:0|0:0,0:0:0:0:
273,383,111429,2,0,L|304:213,1,168.750003755093,0|0,0:0|0:0,0:0:0:0:
383,255,111874,22,0,B|422:273|422:273|476:273,1,74.999998807907,8|0,3:0|3:0,0:0:0:0:
209,219,112096,2,0,B|169:221|169:221|131:206,1,74.999998807907,0|0,0:0|0:0,0:0:0:0:
403,147,112318,2,0,B|352:114|352:114|337:43|337:43|295:109|295:109|234:115,1,269.999987468719,8|0,3:0|0:0,0:0:0:0:

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.IO; using osu.Game.IO;
@@ -19,23 +18,9 @@ namespace osu.Game.Rulesets.Osu.Tests
public class StackingTest public class StackingTest
{ {
[Test] [Test]
public void TestStacking() public void TestStackingEdgeCaseOne()
{ {
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(beatmap_data))) using (var stream = new MemoryStream(@"
using (var reader = new LineBufferedReader(stream))
{
var beatmap = Decoder.GetDecoder<Beatmap>(reader).Decode(reader);
var converted = new TestWorkingBeatmap(beatmap).GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty<Mod>());
var objects = converted.HitObjects.ToList();
// The last hitobject triggers the stacking
for (int i = 0; i < objects.Count - 1; i++)
Assert.AreEqual(0, ((OsuHitObject)objects[i]).StackHeight);
}
}
private const string beatmap_data = @"
osu file format v14 osu file format v14
[General] [General]
@@ -62,6 +47,65 @@ SliderTickRate:0.5
311,185,218471,2,0,L|325:209,1,25 311,185,218471,2,0,L|325:209,1,25
311,185,218671,2,0,L|304:212,1,25 311,185,218671,2,0,L|304:212,1,25
311,185,240271,5,0,0:0:0:0: 311,185,240271,5,0,0:0:0:0:
"; "u8.ToArray()))
using (var reader = new LineBufferedReader(stream))
{
var beatmap = Decoder.GetDecoder<Beatmap>(reader).Decode(reader);
var converted = new TestWorkingBeatmap(beatmap).GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty<Mod>());
var objects = converted.HitObjects.ToList();
// The last hitobject triggers the stacking
for (int i = 0; i < objects.Count - 1; i++)
Assert.AreEqual(0, ((OsuHitObject)objects[i]).StackHeight);
}
}
[Test]
public void TestStackingEdgeCaseTwo()
{
using (var stream = new MemoryStream(@"
osu file format v14
// extracted from https://osu.ppy.sh/beatmapsets/365006#osu/801165
[General]
StackLeniency: 0.2
[Difficulty]
HPDrainRate:6
CircleSize:4
OverallDifficulty:8
ApproachRate:9.3
SliderMultiplier:2
SliderTickRate:1
[TimingPoints]
5338,444.444444444444,4,2,0,50,1,0
82893,-76.9230769230769,4,2,8,50,0,0
85115,-76.9230769230769,4,2,0,50,0,0
85337,-100,4,2,8,60,0,0
85893,-100,4,2,7,60,0,0
86226,-100,4,2,8,60,0,0
88893,-58.8235294117647,4,1,8,70,0,1
[HitObjects]
427,124,84226,1,0,3:0:0:0:
427,124,84337,1,0,3:0:0:0:
427,124,84449,1,8,0:0:0:0:
"u8.ToArray()))
using (var reader = new LineBufferedReader(stream))
{
var beatmap = Decoder.GetDecoder<Beatmap>(reader).Decode(reader);
var converted = new TestWorkingBeatmap(beatmap).GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty<Mod>());
var objects = converted.HitObjects.ToList();
Assert.That(objects, Has.Count.EqualTo(3));
// The last hitobject triggers the stacking
for (int i = 0; i < objects.Count - 1; i++)
Assert.AreEqual(0, ((OsuHitObject)objects[i]).StackHeight);
}
}
} }
} }

View File

@@ -0,0 +1,63 @@
// 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.Testing;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Replays;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
[HeadlessTest]
public partial class TestSceneAutoGeneration : OsuTestScene
{
[TestCase(-1, true)]
[TestCase(0, false)]
[TestCase(1, false)]
public void TestAlternating(double offset, bool shouldAlternate)
{
const double first_object_time = 1000;
double secondObjectTime = first_object_time + AutoGenerator.KEY_UP_DELAY + OsuAutoGenerator.MIN_FRAME_SEPARATION_FOR_ALTERNATING + offset;
var beatmap = new OsuBeatmap();
beatmap.HitObjects.Add(new HitCircle { StartTime = first_object_time });
beatmap.HitObjects.Add(new HitCircle { StartTime = secondObjectTime });
var generated = new OsuAutoGenerator(beatmap, []).Generate();
var frames = generated.Frames.OfType<OsuReplayFrame>().ToList();
Assert.That(frames.Exists(f => f.Time == first_object_time && f.Actions.SingleOrDefault() == OsuAction.LeftButton));
Assert.That(frames.Exists(f => f.Time == first_object_time + AutoGenerator.KEY_UP_DELAY && !f.Actions.Any()));
Assert.That(frames.Exists(f => f.Time == secondObjectTime && f.Actions.SingleOrDefault() == (shouldAlternate ? OsuAction.RightButton : OsuAction.LeftButton)));
Assert.That(frames.Exists(f => f.Time == secondObjectTime + AutoGenerator.KEY_UP_DELAY && !f.Actions.Any()));
}
[TestCase(300)]
[TestCase(600)]
[TestCase(1200)]
public void TestAlternatingSpecificBPM(double bpm)
{
const double first_object_time = 1000;
double secondObjectTime = first_object_time + 60000 / bpm;
var beatmap = new OsuBeatmap();
beatmap.HitObjects.Add(new HitCircle { StartTime = first_object_time });
beatmap.HitObjects.Add(new HitCircle { StartTime = secondObjectTime });
var generated = new OsuAutoGenerator(beatmap, []).Generate();
var frames = generated.Frames.OfType<OsuReplayFrame>().ToList();
Assert.That(frames.Exists(f => f.Time == first_object_time && f.Actions.SingleOrDefault() == OsuAction.LeftButton));
Assert.That(frames.Exists(f => f.Time == first_object_time + AutoGenerator.KEY_UP_DELAY && !f.Actions.Any()));
Assert.That(frames.Exists(f => f.Time == secondObjectTime && f.Actions.SingleOrDefault() == OsuAction.RightButton));
Assert.That(frames.Exists(f => f.Time == secondObjectTime + AutoGenerator.KEY_UP_DELAY && !f.Actions.Any()));
}
}
}

View File

@@ -0,0 +1,52 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Tests
{
public partial class TestSceneGameplayCursorSizeChange : PlayerTestScene
{
private const float initial_cursor_size = 1f;
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
[Resolved]
private SkinManager? skins { get; set; }
[BackgroundDependencyLoader]
private void load()
{
if (skins != null) skins.CurrentSkinInfo.Value = skins.DefaultClassicSkin.SkinInfo;
}
[SetUpSteps]
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("Set gameplay cursor size: 1", () => LocalConfig.SetValue(OsuSetting.GameplayCursorSize, initial_cursor_size));
AddStep("resume player", () => Player.GameplayClockContainer.Start());
AddUntilStep("clock running", () => Player.GameplayClockContainer.IsRunning);
}
[Test]
public void TestPausedChangeCursorSize()
{
AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre));
AddStep("move cursor to top left", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft));
AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre));
AddStep("move cursor to top right", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopRight));
AddStep("press escape", () => InputManager.Key(Key.Escape));
AddSliderStep("cursor size", 0.1f, 2f, 1f, v => LocalConfig.SetValue(OsuSetting.GameplayCursorSize, v));
}
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, false);
}
}

View File

@@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Tests
Debug.Assert(drawableHitObject.HitObject.HitWindows != null); Debug.Assert(drawableHitObject.HitObject.HitWindows != null);
double delay = drawableHitObject.HitObject.StartTime - (drawableHitObject.HitObject.HitWindows.WindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current; double delay = drawableHitObject.HitObject.StartTime - (drawableHitObject.HitObject.HitWindows.WindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current;
scheduledTasks.Add(Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(), delay)); scheduledTasks.Add(Scheduler.AddDelayed(drawableHitObject.TriggerJudgement, delay));
return drawableHitObject; return drawableHitObject;
} }

View File

@@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
continue; continue;
double endTime = stackBaseObject.GetEndTime(); double endTime = stackBaseObject.GetEndTime();
double stackThreshold = objectN.TimePreempt * beatmap.StackLeniency; float stackThreshold = calculateStackThreshold(beatmap, objectN);
if (objectN.StartTime - endTime > stackThreshold) if (objectN.StartTime - endTime > stackThreshold)
// We are no longer within stacking range of the next object. // We are no longer within stacking range of the next object.
@@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
OsuHitObject objectI = hitObjects[i]; OsuHitObject objectI = hitObjects[i];
if (objectI.StackHeight != 0 || objectI is Spinner) continue; if (objectI.StackHeight != 0 || objectI is Spinner) continue;
double stackThreshold = objectI.TimePreempt * beatmap.StackLeniency; float stackThreshold = calculateStackThreshold(beatmap, objectI);
/* If this object is a hitcircle, then we enter this "special" case. /* If this object is a hitcircle, then we enter this "special" case.
* It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider. * It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider.
@@ -151,7 +151,10 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
double endTime = objectN.GetEndTime(); double endTime = objectN.GetEndTime();
if (objectI.StartTime - endTime > stackThreshold) // truncation to integer is required to match stable
// compare https://github.com/peppy/osu-stable-reference/blob/08e3dafd525934cf48880b08e91c24ce4ad8b761/osu!/GameplayElements/HitObjectManager.cs#L1725
// - both quantities being subtracted there are integers
if ((int)objectI.StartTime - (int)endTime > stackThreshold)
// We are no longer within stacking range of the previous object. // We are no longer within stacking range of the previous object.
break; break;
@@ -232,7 +235,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
for (int j = i + 1; j < hitObjects.Count; j++) for (int j = i + 1; j < hitObjects.Count; j++)
{ {
double stackThreshold = hitObjects[i].TimePreempt * beatmap.StackLeniency; float stackThreshold = calculateStackThreshold(beatmap, hitObjects[i]);
if (hitObjects[j].StartTime - stackThreshold > startTime) if (hitObjects[j].StartTime - stackThreshold > startTime)
break; break;
@@ -264,5 +267,17 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
} }
} }
} }
/// <remarks>
/// Truncation of <see cref="OsuHitObject.TimePreempt"/> to <see cref="int"/>, as well as keeping the result as <see cref="float"/>, are both done
/// <a href="https://github.com/peppy/osu-stable-reference/blob/08e3dafd525934cf48880b08e91c24ce4ad8b761/osu!/GameplayElements/HitObjectManager.cs#L1652">
/// for the purposes of stable compatibility
/// </a>.
/// Note that for top-level objects <see cref="OsuHitObject.TimePreempt"/> is supposed to be integral anyway;
/// see <see cref="OsuHitObject.ApplyDefaultsToSelf"/> using <see cref="IBeatmapDifficultyInfo.DifficultyRangeInt"/> when calculating it.
/// Slider ticks and end circles are the exception to that, but they do not matter for stacking.
/// </remarks>
private static float calculateStackThreshold(IBeatmap beatmap, OsuHitObject hitObject)
=> (int)hitObject.TimePreempt * beatmap.StackLeniency;
} }
} }

View File

@@ -7,6 +7,7 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
using osu.Framework.Utils; using osu.Framework.Utils;
@@ -37,13 +38,16 @@ namespace osu.Game.Rulesets.Osu.Edit
[Resolved] [Resolved]
private IEditorChangeHandler? changeHandler { get; set; } private IEditorChangeHandler? changeHandler { get; set; }
[Resolved]
private EditorBeatmap editorBeatmap { get; set; } = null!;
[Resolved(CanBeNull = true)] [Resolved(CanBeNull = true)]
private IDistanceSnapProvider? snapProvider { get; set; } private IDistanceSnapProvider? snapProvider { get; set; }
private BindableList<HitObject> selectedItems { get; } = new BindableList<HitObject>(); private BindableList<HitObject> selectedItems { get; } = new BindableList<HitObject>();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(EditorBeatmap editorBeatmap) private void load()
{ {
selectedItems.BindTo(editorBeatmap.SelectedHitObjects); selectedItems.BindTo(editorBeatmap.SelectedHitObjects);
} }
@@ -53,15 +57,22 @@ namespace osu.Game.Rulesets.Osu.Edit
base.LoadComplete(); base.LoadComplete();
selectedItems.CollectionChanged += (_, __) => updateState(); selectedItems.CollectionChanged += (_, __) => updateState();
editorBeatmap.HitObjectUpdated += hitObjectUpdated;
updateState(); updateState();
} }
private void hitObjectUpdated(HitObject hitObject)
{
if (selectedMovableObjects.Contains(hitObject))
updateState();
}
private void updateState() private void updateState()
{ {
var quad = GeometryUtils.GetSurroundingQuad(selectedMovableObjects); var quad = GeometryUtils.GetSurroundingQuad(selectedMovableObjects);
CanScaleX.Value = quad.Width > 0; CanScaleX.Value = Precision.DefinitelyBigger(quad.Width, 0);
CanScaleY.Value = quad.Height > 0; CanScaleY.Value = Precision.DefinitelyBigger(quad.Height, 0);
CanScaleDiagonally.Value = CanScaleX.Value && CanScaleY.Value; CanScaleDiagonally.Value = CanScaleX.Value && CanScaleY.Value;
CanScaleFromPlayfieldOrigin.Value = selectedMovableObjects.Any(); CanScaleFromPlayfieldOrigin.Value = selectedMovableObjects.Any();
IsScalingSlider.Value = selectedMovableObjects.Count() == 1 && selectedMovableObjects.First() is Slider; IsScalingSlider.Value = selectedMovableObjects.Count() == 1 && selectedMovableObjects.First() is Slider;
@@ -339,5 +350,13 @@ namespace osu.Game.Rulesets.Osu.Edit
PathControlPointTypes = (hitObject as IHasPath)?.Path.ControlPoints.Select(p => p.Type).ToArray(); PathControlPointTypes = (hitObject as IHasPath)?.Path.ControlPoints.Select(p => p.Type).ToArray();
} }
} }
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (editorBeatmap.IsNotNull())
editorBeatmap.HitObjectUpdated -= hitObjectUpdated;
}
} }
} }

View File

@@ -25,10 +25,10 @@ namespace osu.Game.Rulesets.Osu.Edit
{ {
public partial class PolygonGenerationPopover : OsuPopover public partial class PolygonGenerationPopover : OsuPopover
{ {
private SliderWithTextBoxInput<double> distanceSnapInput = null!; private FormSliderBar<double> distanceSnapInput { get; set; } = null!;
private SliderWithTextBoxInput<int> offsetAngleInput = null!; private FormSliderBar<int> offsetAngleInput { get; set; } = null!;
private SliderWithTextBoxInput<int> repeatCountInput = null!; private FormSliderBar<int> repeatCountInput { get; set; } = null!;
private SliderWithTextBoxInput<int> pointInput = null!; private FormSliderBar<int> pointInput { get; set; } = null!;
private RoundedButton commitButton = null!; private RoundedButton commitButton = null!;
private readonly List<HitCircle> insertedCircles = new List<HitCircle>(); private readonly List<HitCircle> insertedCircles = new List<HitCircle>();
@@ -64,11 +64,12 @@ namespace osu.Game.Rulesets.Osu.Edit
{ {
Width = 220, Width = 220,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Spacing = new Vector2(20), Spacing = new Vector2(5),
Children = new Drawable[] Children = new Drawable[]
{ {
distanceSnapInput = new SliderWithTextBoxInput<double>("Distance snap:") distanceSnapInput = new FormSliderBar<double>
{ {
Caption = "Distance snap",
Current = new BindableNumber<double>(1) Current = new BindableNumber<double>(1)
{ {
MinValue = 0.1, MinValue = 0.1,
@@ -76,37 +77,40 @@ namespace osu.Game.Rulesets.Osu.Edit
Precision = 0.1, Precision = 0.1,
Value = ((OsuHitObjectComposer)composer).DistanceSnapProvider.DistanceSpacingMultiplier.Value, Value = ((OsuHitObjectComposer)composer).DistanceSnapProvider.DistanceSpacingMultiplier.Value,
}, },
Instantaneous = true TabbableContentContainer = this
}, },
offsetAngleInput = new SliderWithTextBoxInput<int>("Offset angle:") offsetAngleInput = new FormSliderBar<int>
{ {
Caption = "Offset angle",
Current = new BindableNumber<int> Current = new BindableNumber<int>
{ {
MinValue = 0, MinValue = 0,
MaxValue = 180, MaxValue = 180,
Precision = 1 Precision = 1
}, },
Instantaneous = true TabbableContentContainer = this
}, },
repeatCountInput = new SliderWithTextBoxInput<int>("Repeats:") repeatCountInput = new FormSliderBar<int>
{ {
Caption = "Repeats",
Current = new BindableNumber<int>(1) Current = new BindableNumber<int>(1)
{ {
MinValue = 1, MinValue = 1,
MaxValue = 10, MaxValue = 10,
Precision = 1 Precision = 1
}, },
Instantaneous = true TabbableContentContainer = this
}, },
pointInput = new SliderWithTextBoxInput<int>("Vertices:") pointInput = new FormSliderBar<int>
{ {
Caption = "Vertices",
Current = new BindableNumber<int>(3) Current = new BindableNumber<int>(3)
{ {
MinValue = 3, MinValue = 3,
MaxValue = 32, MaxValue = 32,
Precision = 1, Precision = 1,
}, },
Instantaneous = true TabbableContentContainer = this
}, },
commitButton = new RoundedButton commitButton = new RoundedButton
{ {

View File

@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Edit
private BindableNumber<float> xBindable = null!; private BindableNumber<float> xBindable = null!;
private BindableNumber<float> yBindable = null!; private BindableNumber<float> yBindable = null!;
private SliderWithTextBoxInput<float> xInput = null!; private FormSliderBar<float> xInput { get; set; } = null!;
private OsuCheckbox relativeCheckbox = null!; private OsuCheckbox relativeCheckbox = null!;
public PreciseMovementPopover() public PreciseMovementPopover()
@@ -52,31 +52,31 @@ namespace osu.Game.Rulesets.Osu.Edit
{ {
Width = 220, Width = 220,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Spacing = new Vector2(20), Spacing = new Vector2(5),
Children = new Drawable[] Children = new Drawable[]
{ {
xInput = new SliderWithTextBoxInput<float>("X:") xInput = new FormSliderBar<float>
{ {
Caption = "X",
Current = xBindable = new BindableNumber<float> Current = xBindable = new BindableNumber<float>
{ {
Precision = 1, Precision = 1,
}, },
Instantaneous = true, TabbableContentContainer = this
TabbableContentContainer = this,
}, },
new SliderWithTextBoxInput<float>("Y:") new FormSliderBar<float>
{ {
Caption = "Y",
Current = yBindable = new BindableNumber<float> Current = yBindable = new BindableNumber<float>
{ {
Precision = 1, Precision = 1,
}, },
Instantaneous = true, TabbableContentContainer = this
TabbableContentContainer = this,
}, },
relativeCheckbox = new OsuCheckbox(false) relativeCheckbox = new OsuCheckbox(false)
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
LabelText = "Relative movement", LabelText = "Relative movement"
} }
} }
}; };

View File

@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Edit
private readonly Bindable<PreciseRotationInfo> rotationInfo = new Bindable<PreciseRotationInfo>(new PreciseRotationInfo(0, EditorOrigin.GridCentre)); private readonly Bindable<PreciseRotationInfo> rotationInfo = new Bindable<PreciseRotationInfo>(new PreciseRotationInfo(0, EditorOrigin.GridCentre));
private SliderWithTextBoxInput<float> angleInput = null!; private FormSliderBar<float> angleInput { get; set; } = null!;
private EditorRadioButtonCollection rotationOrigin = null!; private EditorRadioButtonCollection rotationOrigin = null!;
private RadioButton gridCentreButton = null!; private RadioButton gridCentreButton = null!;
@@ -54,11 +54,12 @@ namespace osu.Game.Rulesets.Osu.Edit
{ {
Width = 220, Width = 220,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Spacing = new Vector2(20), Spacing = new Vector2(5),
Children = new Drawable[] Children = new Drawable[]
{ {
angleInput = new SliderWithTextBoxInput<float>("Angle (degrees):") angleInput = new FormSliderBar<float>
{ {
Caption = "Angle (degrees)",
Current = new BindableNumber<float> Current = new BindableNumber<float>
{ {
MinValue = -360, MinValue = -360,
@@ -66,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Edit
Precision = 1 Precision = 1
}, },
KeyboardStep = 1f, KeyboardStep = 1f,
Instantaneous = true TabbableContentContainer = this
}, },
rotationOrigin = new EditorRadioButtonCollection rotationOrigin = new EditorRadioButtonCollection
{ {

View File

@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Edit
private readonly Bindable<PreciseScaleInfo> scaleInfo = new Bindable<PreciseScaleInfo>(new PreciseScaleInfo(1, EditorOrigin.GridCentre, true, true)); private readonly Bindable<PreciseScaleInfo> scaleInfo = new Bindable<PreciseScaleInfo>(new PreciseScaleInfo(1, EditorOrigin.GridCentre, true, true));
private SliderWithTextBoxInput<float> scaleInput = null!; private FormSliderBar<float> scaleInput { get; set; } = null!;
private BindableNumber<float> scaleInputBindable = null!; private BindableNumber<float> scaleInputBindable = null!;
private EditorRadioButtonCollection scaleOrigin = null!; private EditorRadioButtonCollection scaleOrigin = null!;
@@ -66,11 +66,12 @@ namespace osu.Game.Rulesets.Osu.Edit
{ {
Width = 220, Width = 220,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Spacing = new Vector2(20), Spacing = new Vector2(5),
Children = new Drawable[] Children = new Drawable[]
{ {
scaleInput = new SliderWithTextBoxInput<float>("Scale:") scaleInput = new FormSliderBar<float>
{ {
Caption = "Scale",
Current = scaleInputBindable = new BindableNumber<float> Current = scaleInputBindable = new BindableNumber<float>
{ {
MinValue = 0.05f, MinValue = 0.05f,
@@ -80,7 +81,7 @@ namespace osu.Game.Rulesets.Osu.Edit
Default = 1, Default = 1,
}, },
KeyboardStep = 0.01f, KeyboardStep = 0.01f,
Instantaneous = true TabbableContentContainer = this
}, },
scaleOrigin = new EditorRadioButtonCollection scaleOrigin = new EditorRadioButtonCollection
{ {

View File

@@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Mods
[SettingSource( [SettingSource(
"Max size at combo", "Max size at combo",
"The combo count at which the cursor reaches its maximum size", "The combo count at which the cursor reaches its maximum size",
SettingControlType = typeof(SettingsSlider<int, RoundedSliderBar<int>>) SettingControlType = typeof(SettingsSlider<int, MaxSizeComboSlider>)
)] )]
public BindableInt MaxSizeComboCount { get; } = new BindableInt(50) public BindableInt MaxSizeComboCount { get; } = new BindableInt(50)
{ {
@@ -85,4 +85,12 @@ namespace osu.Game.Rulesets.Osu.Mods
cursor.ModScaleAdjust.Value = (float)Interpolation.Lerp(cursor.ModScaleAdjust.Value, currentSize, Math.Clamp(cursor.Time.Elapsed / TRANSITION_DURATION, 0, 1)); cursor.ModScaleAdjust.Value = (float)Interpolation.Lerp(cursor.ModScaleAdjust.Value, currentSize, Math.Clamp(cursor.Time.Elapsed / TRANSITION_DURATION, 0, 1));
} }
} }
public partial class MaxSizeComboSlider : RoundedSliderBar<int>
{
public MaxSizeComboSlider()
{
KeyboardStep = 1;
}
}
} }

View File

@@ -1,7 +1,9 @@
// 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 System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@@ -82,6 +84,8 @@ namespace osu.Game.Rulesets.Osu.Mods
} }
} }
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModTargetPractice)).ToArray();
protected override void ApplySettings(BeatmapDifficulty difficulty) protected override void ApplySettings(BeatmapDifficulty difficulty)
{ {
base.ApplySettings(difficulty); base.ApplySettings(difficulty);

View File

@@ -27,8 +27,10 @@ namespace osu.Game.Rulesets.Osu.Mods
public override LocalisableString Description => "Burn the notes into your memory."; public override LocalisableString Description => "Burn the notes into your memory.";
//Alters the transforms of the approach circles, breaking the effects of these mods. /// <remarks>
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModApproachDifferent), typeof(OsuModTransform), typeof(OsuModDepth) }).ToArray(); /// Incompatible with all mods that directly modify or indirectly depend on <see cref="OsuHitObject.TimePreempt"/>, or alter the behaviour of approach circles.
/// </remarks>
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModApproachDifferent), typeof(OsuModTransform), typeof(OsuModDepth), typeof(OsuModHidden) }).ToArray();
public override ModType Type => ModType.Fun; public override ModType Type => ModType.Fun;
@@ -57,16 +59,25 @@ namespace osu.Game.Rulesets.Osu.Mods
void applyFadeInAdjustment(OsuHitObject osuObject) void applyFadeInAdjustment(OsuHitObject osuObject)
{ {
osuObject.TimePreempt += osuObject.StartTime - lastNewComboTime; if (osuObject is not Spinner)
osuObject.TimePreempt += osuObject.StartTime - lastNewComboTime;
int repeatCount = 0;
foreach (var nested in osuObject.NestedHitObjects.OfType<OsuHitObject>()) foreach (var nested in osuObject.NestedHitObjects.OfType<OsuHitObject>())
{ {
switch (nested) switch (nested)
{ {
//Freezing the SliderTicks doesnt play well with snaking sliders // Freezing the SliderTicks doesnt play well with snaking sliders
case SliderTick: case SliderTick:
//SliderRepeat wont layer correctly if preempt is changed. break;
case SliderRepeat: case SliderRepeat:
if (repeatCount > 2)
break;
applyFadeInAdjustment(nested);
repeatCount++;
break; break;
default: default:

View File

@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override LocalisableString Description => @"Play with no approach circles and fading circles/sliders."; public override LocalisableString Description => @"Play with no approach circles and fading circles/sliders.";
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1; public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModDepth) }; public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModDepth), typeof(OsuModFreezeFrame) };
public const double FADE_IN_DURATION_MULTIPLIER = 0.4; public const double FADE_IN_DURATION_MULTIPLIER = 0.4;
public const double FADE_OUT_DURATION_MULTIPLIER = 0.3; public const double FADE_OUT_DURATION_MULTIPLIER = 0.3;

View File

@@ -48,7 +48,8 @@ namespace osu.Game.Rulesets.Osu.Mods
typeof(OsuModSpunOut), typeof(OsuModSpunOut),
typeof(OsuModStrictTracking), typeof(OsuModStrictTracking),
typeof(OsuModSuddenDeath), typeof(OsuModSuddenDeath),
typeof(OsuModDepth) typeof(OsuModDepth),
typeof(OsuModDifficultyAdjust),
}).ToArray(); }).ToArray();
[SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))] [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))]

View File

@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Name => "Traceable"; public override string Name => "Traceable";
public override string Acronym => "TC"; public override string Acronym => "TC";
public override IconUsage? Icon => OsuIcon.ModTraceable; public override IconUsage? Icon => OsuIcon.ModTraceable;
public override ModType Type => ModType.Fun; public override ModType Type => ModType.DifficultyIncrease;
public override LocalisableString Description => "Put your faith in the approach circles..."; public override LocalisableString Description => "Put your faith in the approach circles...";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override bool Ranked => true; public override bool Ranked => true;

View File

@@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Osu.Objects
{ {
base.ApplyDefaultsToSelf(controlPointInfo, difficulty); base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, PREEMPT_RANGE); TimePreempt = IBeatmapDifficultyInfo.DifficultyRangeInt(difficulty.ApproachRate, PREEMPT_RANGE);
// Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR. // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR.
// This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above. // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above.

View File

@@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Osu
new OsuModHardRock(), new OsuModHardRock(),
new MultiMod(new OsuModSuddenDeath(), new OsuModPerfect()), new MultiMod(new OsuModSuddenDeath(), new OsuModPerfect()),
new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()), new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()),
new OsuModHidden(), new MultiMod(new OsuModHidden(), new OsuModTraceable()),
new MultiMod(new OsuModFlashlight(), new OsuModBlinds()), new MultiMod(new OsuModFlashlight(), new OsuModBlinds()),
new OsuModStrictTracking(), new OsuModStrictTracking(),
new OsuModAccuracyChallenge(), new OsuModAccuracyChallenge(),
@@ -209,7 +209,6 @@ namespace osu.Game.Rulesets.Osu
new OsuModSpinIn(), new OsuModSpinIn(),
new MultiMod(new OsuModGrow(), new OsuModDeflate()), new MultiMod(new OsuModGrow(), new OsuModDeflate()),
new MultiMod(new ModWindUp(), new ModWindDown()), new MultiMod(new ModWindUp(), new ModWindDown()),
new OsuModTraceable(),
new OsuModBarrelRoll(), new OsuModBarrelRoll(),
new OsuModApproachDifferent(), new OsuModApproachDifferent(),
new OsuModMuted(), new OsuModMuted(),
@@ -287,19 +286,24 @@ namespace osu.Game.Rulesets.Osu
public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new OsuRulesetConfigManager(settings, RulesetInfo); public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new OsuRulesetConfigManager(settings, RulesetInfo);
protected override IEnumerable<HitResult> GetValidHitResults() public override IEnumerable<HitResult> GetValidHitResults()
{ {
return new[] return new[]
{ {
HitResult.Great, HitResult.Great,
HitResult.Ok, HitResult.Ok,
HitResult.Meh, HitResult.Meh,
HitResult.Miss,
HitResult.LargeTickHit, HitResult.LargeTickHit,
HitResult.LargeTickMiss,
HitResult.SmallTickHit, HitResult.SmallTickHit,
HitResult.SmallTickMiss,
HitResult.SliderTailHit, HitResult.SliderTailHit,
HitResult.SmallBonus, HitResult.SmallBonus,
HitResult.LargeBonus, HitResult.LargeBonus,
HitResult.IgnoreHit,
HitResult.IgnoreMiss,
}; };
} }
@@ -421,7 +425,8 @@ namespace osu.Game.Rulesets.Osu
Description = "Affects how early objects appear on screen relative to their hit time.", Description = "Affects how early objects appear on screen relative to their hit time.",
AdditionalMetrics = AdditionalMetrics =
[ [
new RulesetBeatmapAttribute.AdditionalMetric("Approach time", LocalisableString.Interpolate($@"{IBeatmapDifficultyInfo.DifficultyRange(effectiveDifficulty.ApproachRate, OsuHitObject.PREEMPT_RANGE):#,0.##} ms")) new RulesetBeatmapAttribute.AdditionalMetric("Approach time",
LocalisableString.Interpolate($@"{IBeatmapDifficultyInfo.DifficultyRangeInt(effectiveDifficulty.ApproachRate, OsuHitObject.PREEMPT_RANGE):#,0.##} ms"))
] ]
}; };

View File

@@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Osu.Replays
{ {
public class OsuAutoGenerator : OsuAutoGeneratorBase public class OsuAutoGenerator : OsuAutoGeneratorBase
{ {
public const double MIN_FRAME_SEPARATION_FOR_ALTERNATING = 266;
public new OsuBeatmap Beatmap => (OsuBeatmap)base.Beatmap; public new OsuBeatmap Beatmap => (OsuBeatmap)base.Beatmap;
#region Parameters #region Parameters
@@ -245,7 +247,7 @@ namespace osu.Game.Rulesets.Osu.Replays
double timeDifference = ApplyModsToTimeDelta(lastFrame.Time, h.StartTime); double timeDifference = ApplyModsToTimeDelta(lastFrame.Time, h.StartTime);
OsuReplayFrame? lastLastFrame = Frames.Count >= 2 ? (OsuReplayFrame)Frames[^2] : null; OsuReplayFrame? lastLastFrame = Frames.Count >= 2 ? (OsuReplayFrame)Frames[^2] : null;
if (timeDifference > 0) if (timeDifference >= 0)
{ {
// If the last frame is a key-up frame and there has been no wait period, adjust the last frame's position such that it begins eased movement instantaneously. // If the last frame is a key-up frame and there has been no wait period, adjust the last frame's position such that it begins eased movement instantaneously.
if (lastLastFrame != null && lastFrame is OsuKeyUpReplayFrame && !hasWaited) if (lastLastFrame != null && lastFrame is OsuKeyUpReplayFrame && !hasWaited)
@@ -266,7 +268,7 @@ namespace osu.Game.Rulesets.Osu.Replays
} }
// Start alternating once the time separation is too small (faster than ~225BPM). // Start alternating once the time separation is too small (faster than ~225BPM).
if (timeDifference > 0 && timeDifference < 266) if (timeDifference >= 0 && timeDifference < MIN_FRAME_SEPARATION_FOR_ALTERNATING)
buttonIndex++; buttonIndex++;
else else
buttonIndex = 0; buttonIndex = 0;

View File

@@ -103,6 +103,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
leaderboard.Origin = Anchor.BottomLeft; leaderboard.Origin = Anchor.BottomLeft;
leaderboard.Position = pos; leaderboard.Position = pos;
} }
foreach (var d in container.OfType<ISerialisableDrawable>())
d.UsesFixedAnchor = true;
}) })
{ {
Children = new Drawable[] Children = new Drawable[]

View File

@@ -49,6 +49,18 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
/// </summary> /// </summary>
protected bool AllowPartRotation { get; set; } protected bool AllowPartRotation { get; set; }
private Vector2 cursorScale = Vector2.One;
public Vector2 CursorScale
{
get => cursorScale;
set
{
cursorScale = value;
Invalidate(Invalidation.DrawNode);
}
}
/// <summary> /// <summary>
/// The trail part texture origin. /// The trail part texture origin.
/// </summary> /// </summary>
@@ -186,7 +198,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
float distance = diff.Length; float distance = diff.Length;
Vector2 direction = diff / distance; Vector2 direction = diff / distance;
float interval = Texture.DisplayWidth / 2.5f * IntervalMultiplier; float interval = Texture.DisplayWidth * CursorScale.X / 2.5f * IntervalMultiplier;
float stopAt = distance - (AvoidDrawingNearCursor ? interval : 0); float stopAt = distance - (AvoidDrawingNearCursor ? interval : 0);
for (float d = interval; d < stopAt; d += interval) for (float d = interval; d < stopAt; d += interval)
@@ -233,6 +245,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private float time; private float time;
private float fadeExponent; private float fadeExponent;
private float angle; private float angle;
private Vector2 cursorScale;
private readonly TrailPart[] parts = new TrailPart[max_sprites]; private readonly TrailPart[] parts = new TrailPart[max_sprites];
private Vector2 originPosition; private Vector2 originPosition;
@@ -253,6 +266,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
time = Source.time; time = Source.time;
fadeExponent = Source.FadeExponent; fadeExponent = Source.FadeExponent;
angle = Source.AllowPartRotation ? float.DegreesToRadians(Source.PartRotation) : 0; angle = Source.AllowPartRotation ? float.DegreesToRadians(Source.PartRotation) : 0;
cursorScale = Source.cursorScale;
originPosition = Vector2.Zero; originPosition = Vector2.Zero;
@@ -307,7 +321,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
vertexBatch.Add(new TexturedTrailVertex vertexBatch.Add(new TexturedTrailVertex
{ {
Position = rotateAround( Position = rotateAround(
new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X * part.Scale.X, part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y) * part.Scale.Y), new Vector2(
part.Position.X - texture.DisplayWidth * originPosition.X * part.Scale.X * cursorScale.X,
part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y) * part.Scale.Y * cursorScale.Y),
part.Position, sin, cos), part.Position, sin, cos),
TexturePosition = textureRect.BottomLeft, TexturePosition = textureRect.BottomLeft,
TextureRect = new Vector4(0, 0, 1, 1), TextureRect = new Vector4(0, 0, 1, 1),
@@ -318,8 +334,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
vertexBatch.Add(new TexturedTrailVertex vertexBatch.Add(new TexturedTrailVertex
{ {
Position = rotateAround( Position = rotateAround(
new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X) * part.Scale.X, new Vector2(
part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y) * part.Scale.Y), part.Position, sin, cos), part.Position.X + texture.DisplayWidth * (1 - originPosition.X) * part.Scale.X * cursorScale.X,
part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y) * part.Scale.Y * cursorScale.Y),
part.Position, sin, cos),
TexturePosition = textureRect.BottomRight, TexturePosition = textureRect.BottomRight,
TextureRect = new Vector4(0, 0, 1, 1), TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.BottomRight.Linear, Colour = DrawColourInfo.Colour.BottomRight.Linear,
@@ -329,7 +347,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
vertexBatch.Add(new TexturedTrailVertex vertexBatch.Add(new TexturedTrailVertex
{ {
Position = rotateAround( Position = rotateAround(
new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X) * part.Scale.X, part.Position.Y - texture.DisplayHeight * originPosition.Y * part.Scale.Y), new Vector2(
part.Position.X + texture.DisplayWidth * (1 - originPosition.X) * part.Scale.X * cursorScale.X,
part.Position.Y - texture.DisplayHeight * originPosition.Y * part.Scale.Y * cursorScale.Y),
part.Position, sin, cos), part.Position, sin, cos),
TexturePosition = textureRect.TopRight, TexturePosition = textureRect.TopRight,
TextureRect = new Vector4(0, 0, 1, 1), TextureRect = new Vector4(0, 0, 1, 1),
@@ -340,7 +360,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
vertexBatch.Add(new TexturedTrailVertex vertexBatch.Add(new TexturedTrailVertex
{ {
Position = rotateAround( Position = rotateAround(
new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X * part.Scale.X, part.Position.Y - texture.DisplayHeight * originPosition.Y * part.Scale.Y), new Vector2(
part.Position.X - texture.DisplayWidth * originPosition.X * part.Scale.X * cursorScale.X,
part.Position.Y - texture.DisplayHeight * originPosition.Y * part.Scale.Y * cursorScale.Y),
part.Position, sin, cos), part.Position, sin, cos),
TexturePosition = textureRect.TopLeft, TexturePosition = textureRect.TopLeft,
TextureRect = new Vector4(0, 0, 1, 1), TextureRect = new Vector4(0, 0, 1, 1),

View File

@@ -64,8 +64,14 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
var newScale = new Vector2(e.NewValue); var newScale = new Vector2(e.NewValue);
rippleVisualiser.CursorScale = newScale; rippleVisualiser.CursorScale = newScale;
cursorTrail.Scale = newScale; updateTrailScale();
}, true); }, true);
cursorTrail.OnSkinChanged += updateTrailScale;
}
private void updateTrailScale()
{
if (cursorTrail.Drawable is CursorTrail trail) trail.CursorScale = new Vector2(ActiveCursor.CursorScale.Value);
} }
private int downCount; private int downCount;

View File

@@ -3,7 +3,9 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Localisation; using osu.Game.Localisation;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Configuration;
@@ -27,32 +29,34 @@ namespace osu.Game.Rulesets.Osu.UI
Children = new Drawable[] Children = new Drawable[]
{ {
new SettingsCheckbox new SettingsItemV2(new FormCheckBox
{ {
LabelText = RulesetSettingsStrings.SnakingInSliders, Caption = RulesetSettingsStrings.SnakingInSliders,
Current = config.GetBindable<bool>(OsuRulesetSetting.SnakingInSliders) Current = config.GetBindable<bool>(OsuRulesetSetting.SnakingInSliders)
}, }),
new SettingsCheckbox new SettingsItemV2(new FormCheckBox
{ {
ClassicDefault = false, Caption = RulesetSettingsStrings.SnakingOutSliders,
LabelText = RulesetSettingsStrings.SnakingOutSliders,
Current = config.GetBindable<bool>(OsuRulesetSetting.SnakingOutSliders) Current = config.GetBindable<bool>(OsuRulesetSetting.SnakingOutSliders)
}, })
new SettingsCheckbox
{ {
LabelText = RulesetSettingsStrings.CursorTrail, ApplyClassicDefault = c => ((IHasCurrentValue<bool>)c).Current.Value = false,
},
new SettingsItemV2(new FormCheckBox
{
Caption = RulesetSettingsStrings.CursorTrail,
Current = config.GetBindable<bool>(OsuRulesetSetting.ShowCursorTrail) Current = config.GetBindable<bool>(OsuRulesetSetting.ShowCursorTrail)
}, }),
new SettingsCheckbox new SettingsItemV2(new FormCheckBox
{ {
LabelText = RulesetSettingsStrings.CursorRipples, Caption = RulesetSettingsStrings.CursorRipples,
Current = config.GetBindable<bool>(OsuRulesetSetting.ShowCursorRipples) Current = config.GetBindable<bool>(OsuRulesetSetting.ShowCursorRipples)
}, }),
new SettingsEnumDropdown<PlayfieldBorderStyle> new SettingsItemV2(new FormEnumDropdown<PlayfieldBorderStyle>
{ {
LabelText = RulesetSettingsStrings.PlayfieldBorderStyle, Caption = RulesetSettingsStrings.PlayfieldBorderStyle,
Current = config.GetBindable<PlayfieldBorderStyle>(OsuRulesetSetting.PlayfieldBorderStyle), Current = config.GetBindable<PlayfieldBorderStyle>(OsuRulesetSetting.PlayfieldBorderStyle),
}, }),
}; };
} }
} }

View File

@@ -35,11 +35,9 @@
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
</array> </array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/AppIcon.appiconset</string>
<key>UIApplicationSupportsIndirectInputEvents</key> <key>UIApplicationSupportsIndirectInputEvents</key>
<true/> <true/>
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
</dict> </dict>
</plist> </plist>

View File

@@ -51,6 +51,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
spectatorList.Anchor = Anchor.BottomLeft; spectatorList.Anchor = Anchor.BottomLeft;
spectatorList.Origin = Anchor.TopLeft; spectatorList.Origin = Anchor.TopLeft;
} }
foreach (var d in container.OfType<ISerialisableDrawable>())
d.UsesFixedAnchor = true;
}) })
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,

View File

@@ -51,6 +51,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
spectatorList.Origin = Anchor.TopLeft; spectatorList.Origin = Anchor.TopLeft;
spectatorList.Position = new Vector2(320, -280); spectatorList.Position = new Vector2(320, -280);
} }
foreach (var d in container.OfType<ISerialisableDrawable>())
d.UsesFixedAnchor = true;
}) })
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,

View File

@@ -79,6 +79,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
spectatorList.Origin = Anchor.TopLeft; spectatorList.Origin = Anchor.TopLeft;
spectatorList.Position = pos; spectatorList.Position = pos;
} }
foreach (var d in container.OfType<ISerialisableDrawable>())
d.UsesFixedAnchor = true;
}) })
{ {
new LegacyDefaultComboCounter(), new LegacyDefaultComboCounter(),

View File

@@ -222,15 +222,18 @@ namespace osu.Game.Rulesets.Taiko
public override RulesetSettingsSubsection CreateSettings() => new TaikoSettingsSubsection(this); public override RulesetSettingsSubsection CreateSettings() => new TaikoSettingsSubsection(this);
protected override IEnumerable<HitResult> GetValidHitResults() public override IEnumerable<HitResult> GetValidHitResults()
{ {
return new[] return new[]
{ {
HitResult.Great, HitResult.Great,
HitResult.Ok, HitResult.Ok,
HitResult.Miss,
HitResult.SmallBonus, HitResult.SmallBonus,
HitResult.LargeBonus, HitResult.LargeBonus,
HitResult.IgnoreHit,
HitResult.IgnoreMiss,
}; };
} }

View File

@@ -4,6 +4,7 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Localisation; using osu.Game.Localisation;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Taiko.Configuration; using osu.Game.Rulesets.Taiko.Configuration;
@@ -26,11 +27,11 @@ namespace osu.Game.Rulesets.Taiko
Children = new Drawable[] Children = new Drawable[]
{ {
new SettingsEnumDropdown<TaikoTouchControlScheme> new SettingsItemV2(new FormEnumDropdown<TaikoTouchControlScheme>
{ {
LabelText = RulesetSettingsStrings.TouchControlScheme, Caption = RulesetSettingsStrings.TouchControlScheme,
Current = config.GetBindable<TaikoTouchControlScheme>(TaikoRulesetSetting.TouchControlScheme) Current = config.GetBindable<TaikoTouchControlScheme>(TaikoRulesetSetting.TouchControlScheme)
} })
}; };
} }
} }

View File

@@ -194,7 +194,7 @@ namespace osu.Game.Rulesets.Taiko.UI
var hitWindows = new TaikoHitWindows(); var hitWindows = new TaikoHitWindows();
HitResult[] usableHitResults = Enum.GetValues<HitResult>().Where(r => hitWindows.IsHitResultAllowed(r)).ToArray(); HitResult[] usableHitResults = Enum.GetValues<HitResult>().Where(hitWindows.IsHitResultAllowed).ToArray();
AddInternal(judgementPooler = new JudgementPooler<DrawableTaikoJudgement>(usableHitResults)); AddInternal(judgementPooler = new JudgementPooler<DrawableTaikoJudgement>(usableHitResults));

View File

@@ -35,8 +35,31 @@
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
</array> </array>
<key>XSAppIconAssets</key> <key>CFBundleIcons~ipad</key>
<string>Assets.xcassets/AppIcon.appiconset</string> <dict>
<key>CFBundlePrimaryIcon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>AppIcon60x60</string>
</array>
<key>CFBundleIconName</key>
<string>AppIcon</string>
</dict>
</dict>
<key>CFBundleIcons</key>
<dict>
<key>CFBundlePrimaryIcon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>AppIcon60x60</string>
<string>AppIcon76x76</string>
</array>
<key>CFBundleIconName</key>
<string>AppIcon</string>
</dict>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key> <key>UIApplicationSupportsIndirectInputEvents</key>
<true/> <true/>
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>

View File

@@ -4,6 +4,7 @@
#nullable disable #nullable disable
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -15,6 +16,7 @@ using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Beatmaps.IO;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
@@ -34,6 +36,12 @@ namespace osu.Game.Tests.Beatmaps
private IBindable<StarDifficulty> starDifficultyBindable; private IBindable<StarDifficulty> starDifficultyBindable;
[Resolved]
private BeatmapManager beatmapManager { get; set; }
[Resolved]
private BeatmapDifficultyCache actualDifficultyCache { get; set; }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuGameBase osu) private void load(OsuGameBase osu)
{ {
@@ -55,6 +63,36 @@ namespace osu.Game.Tests.Beatmaps
AddUntilStep($"star difficulty -> {BASE_STARS}", () => starDifficultyBindable.Value.Stars == BASE_STARS); AddUntilStep($"star difficulty -> {BASE_STARS}", () => starDifficultyBindable.Value.Stars == BASE_STARS);
} }
[Test]
public void TestInvalidationFlow()
{
BeatmapInfo postEditBeatmapInfo = null;
BeatmapInfo preEditBeatmapInfo = null;
IBindable<StarDifficulty> bindableDifficulty = null;
AddStep("get bindable stars", () =>
{
preEditBeatmapInfo = importedSet.Beatmaps.First();
bindableDifficulty = actualDifficultyCache.GetBindableDifficulty(preEditBeatmapInfo);
});
AddUntilStep("wait for stars retrieved", () => bindableDifficulty.Value.Stars, () => Is.GreaterThan(0));
AddStep("remove all hitobjects", () =>
{
var working = beatmapManager.GetWorkingBeatmap(preEditBeatmapInfo);
((IList<HitObject>)working.Beatmap.HitObjects).Clear();
beatmapManager.Save(working.BeatmapInfo, working.Beatmap);
postEditBeatmapInfo = working.BeatmapInfo;
});
AddAssert("stars is now zero", () => actualDifficultyCache.GetDifficultyAsync(postEditBeatmapInfo).GetResultSafely()!.Value.Stars, () => Is.Zero);
AddUntilStep("bindable stars is now zero", () => bindableDifficulty.Value.Stars, () => Is.Zero);
}
[Test] [Test]
public void TestStarDifficultyChangesOnModSettings() public void TestStarDifficultyChangesOnModSettings()
{ {
@@ -76,6 +114,30 @@ namespace osu.Game.Tests.Beatmaps
AddUntilStep($"star difficulty -> {BASE_STARS + 1.75}", () => starDifficultyBindable.Value.Stars == BASE_STARS + 1.75); AddUntilStep($"star difficulty -> {BASE_STARS + 1.75}", () => starDifficultyBindable.Value.Stars == BASE_STARS + 1.75);
} }
[Test]
public void TestStarDifficultyChangesOnModSettingsCorrectlyTrackAcrossReferenceChanges()
{
OsuModDoubleTime dt = null;
AddStep("set computation function", () => difficultyCache.ComputeDifficulty = lookup =>
{
var modRateAdjust = (ModRateAdjust)lookup.OrderedMods.SingleOrDefault(mod => mod is ModRateAdjust);
return new StarDifficulty(BASE_STARS + modRateAdjust?.SpeedChange.Value ?? 0, 0);
});
AddStep("change selected mod to DT", () => SelectedMods.Value = new[] { dt = new OsuModDoubleTime { SpeedChange = { Value = 1.5 } } });
AddUntilStep($"star difficulty -> {BASE_STARS + 1.5}", () => starDifficultyBindable.Value.Stars == BASE_STARS + 1.5);
AddStep("change DT speed to 1.25", () => dt.SpeedChange.Value = 1.25);
AddUntilStep($"star difficulty -> {BASE_STARS + 1.25}", () => starDifficultyBindable.Value.Stars == BASE_STARS + 1.25);
AddStep("reconstruct DT mod with same settings", () => SelectedMods.Value = new[] { dt = (OsuModDoubleTime)dt.DeepClone() });
AddUntilStep($"star difficulty -> {BASE_STARS + 1.25}", () => starDifficultyBindable.Value.Stars == BASE_STARS + 1.25);
AddStep("change DT speed to 1.25", () => dt.SpeedChange.Value = 2);
AddUntilStep($"star difficulty -> {BASE_STARS + 2}", () => starDifficultyBindable.Value.Stars == BASE_STARS + 2);
}
[Test] [Test]
public void TestStarDifficultyAdjustHashCodeConflict() public void TestStarDifficultyAdjustHashCodeConflict()
{ {
@@ -122,8 +184,10 @@ namespace osu.Game.Tests.Beatmaps
[Test] [Test]
public void TestKeyDoesntEqualWithDifferentModSettings() public void TestKeyDoesntEqualWithDifferentModSettings()
{ {
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } }); var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 },
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.9 } } }); new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } });
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 },
new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.9 } } });
Assert.That(key1, Is.Not.EqualTo(key2)); Assert.That(key1, Is.Not.EqualTo(key2));
Assert.That(key1.GetHashCode(), Is.Not.EqualTo(key2.GetHashCode())); Assert.That(key1.GetHashCode(), Is.Not.EqualTo(key2.GetHashCode()));
@@ -132,8 +196,10 @@ namespace osu.Game.Tests.Beatmaps
[Test] [Test]
public void TestKeyEqualWithMatchingModSettings() public void TestKeyEqualWithMatchingModSettings()
{ {
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } }); var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 },
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } }); new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 },
new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
Assert.That(key1, Is.EqualTo(key2)); Assert.That(key1, Is.EqualTo(key2));
Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode())); Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode()));

View File

@@ -247,7 +247,7 @@ namespace osu.Game.Tests.Beatmaps
AddStep("change all start times", () => AddStep("change all start times", () =>
{ {
editorBeatmap.HitObjectUpdated += h => updatedObjects.Add(h); editorBeatmap.HitObjectUpdated += updatedObjects.Add;
for (int i = 0; i < 10; i++) for (int i = 0; i < 10; i++)
allHitObjects[i].StartTime += 10; allHitObjects[i].StartTime += 10;
@@ -282,7 +282,7 @@ namespace osu.Game.Tests.Beatmaps
AddStep("change start time twice", () => AddStep("change start time twice", () =>
{ {
editorBeatmap.HitObjectUpdated += h => updatedObjects.Add(h); editorBeatmap.HitObjectUpdated += updatedObjects.Add;
editorBeatmap.HitObjects[0].StartTime = 10; editorBeatmap.HitObjects[0].StartTime = 10;
editorBeatmap.HitObjects[0].StartTime = 20; editorBeatmap.HitObjects[0].StartTime = 20;

View File

@@ -104,10 +104,7 @@ namespace osu.Game.Tests.Database
Assert.AreNotEqual(detachedBeatmapSet.Status, BeatmapOnlineStatus.Ranked); Assert.AreNotEqual(detachedBeatmapSet.Status, BeatmapOnlineStatus.Ranked);
detachedBeatmapSet.Status = BeatmapOnlineStatus.Ranked; detachedBeatmapSet.Status = BeatmapOnlineStatus.Ranked;
beatmapSet.PerformWrite(s => beatmapSet.PerformWrite(detachedBeatmapSet.CopyChangesToRealm);
{
detachedBeatmapSet.CopyChangesToRealm(s);
});
beatmapSet.PerformRead(s => beatmapSet.PerformRead(s =>
{ {

View File

@@ -7,6 +7,7 @@ using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
@@ -116,6 +117,69 @@ namespace osu.Game.Tests.Database
}); });
} }
[Test]
public void TestFakedRulesetIdIsDetected()
{
RunTestWithRealm((realm, storage) =>
{
LoadTestRuleset.HasImplementations = true;
LoadTestRuleset.Version = Ruleset.CURRENT_RULESET_API_VERSION;
var ruleset = new LoadTestRuleset();
string rulesetShortName = ruleset.RulesetInfo.ShortName;
realm.Write(r => r.Add(new RulesetInfo(rulesetShortName, ruleset.RulesetInfo.Name, ruleset.RulesetInfo.InstantiationInfo, 0)
{
Available = true,
}));
Assert.That(realm.Run(r => r.Find<RulesetInfo>(rulesetShortName)!.Available), Is.True);
// Availability is updated on construction of a RealmRulesetStore
using var _ = new RealmRulesetStore(realm, storage);
Assert.That(realm.Run(r => r.Find<RulesetInfo>(rulesetShortName)!.Available), Is.False);
});
}
[Test]
public void TestMultipleRulesetWithSameOnlineIdsAreDetected()
{
RunTestWithRealm((realm, storage) =>
{
LoadTestRuleset.HasImplementations = true;
LoadTestRuleset.Version = Ruleset.CURRENT_RULESET_API_VERSION;
LoadTestRuleset.OnlineID = 2;
var first = new LoadTestRuleset();
var second = new CatchRuleset();
realm.Write(r => r.Add(new RulesetInfo(first.ShortName, first.RulesetInfo.Name, first.RulesetInfo.InstantiationInfo, first.RulesetInfo.OnlineID)
{
Available = true,
}));
realm.Write(r => r.Add(new RulesetInfo(second.ShortName, second.RulesetInfo.Name, second.RulesetInfo.InstantiationInfo, second.RulesetInfo.OnlineID)
{
Available = true,
}));
Assert.That(realm.Run(r => r.Find<RulesetInfo>(first.ShortName)!.Available), Is.True);
Assert.That(realm.Run(r => r.Find<RulesetInfo>(second.ShortName)!.Available), Is.True);
// Availability is updated on construction of a RealmRulesetStore
using var _ = new RealmRulesetStore(realm, storage);
Assert.That(realm.Run(r => r.Find<RulesetInfo>(first.ShortName)!.Available), Is.False);
Assert.That(realm.Run(r => r.Find<RulesetInfo>(second.ShortName)!.Available), Is.False);
realm.Write(r => r.Remove(r.Find<RulesetInfo>(first.ShortName)!));
using var __ = new RealmRulesetStore(realm, storage);
Assert.That(realm.Run(r => r.Find<RulesetInfo>(second.ShortName)!.Available), Is.True);
});
}
private class LoadTestRuleset : Ruleset private class LoadTestRuleset : Ruleset
{ {
public override string RulesetAPIVersionSupported => Version; public override string RulesetAPIVersionSupported => Version;
@@ -124,6 +188,13 @@ namespace osu.Game.Tests.Database
public static string Version { get; set; } = CURRENT_RULESET_API_VERSION; public static string Version { get; set; } = CURRENT_RULESET_API_VERSION;
public static int OnlineID { get; set; } = -1;
public LoadTestRuleset()
{
RulesetInfo.OnlineID = OnlineID;
}
public override IEnumerable<Mod> GetModsFor(ModType type) public override IEnumerable<Mod> GetModsFor(ModType type)
{ {
if (!HasImplementations) if (!HasImplementations)

View File

@@ -11,7 +11,6 @@ using osu.Game.Rulesets;
using osu.Game.Rulesets.Filter; using osu.Game.Rulesets.Filter;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Carousel;
using osu.Game.Screens.Select.Filter; using osu.Game.Screens.Select.Filter;
namespace osu.Game.Tests.NonVisual.Filtering namespace osu.Game.Tests.NonVisual.Filtering
@@ -588,6 +587,26 @@ namespace osu.Game.Tests.NonVisual.Filtering
Assert.That(visibleBeatmaps, Is.EqualTo(expectedBeatmapIndexes)); Assert.That(visibleBeatmaps, Is.EqualTo(expectedBeatmapIndexes));
} }
// This is a temporary class that emulates what these tests originally used from song select v1.
// If anyone ever ends up tidying up these test, here's a starting point:
// https://gist.github.com/peppy/67fda38f6483fd1dd01ef845ed5bf932
public class CarouselBeatmap
{
public readonly BeatmapInfo BeatmapInfo;
public BindableBool Filtered = new BindableBool();
public CarouselBeatmap(BeatmapInfo beatmapInfo)
{
BeatmapInfo = beatmapInfo;
}
public void Filter(FilterCriteria criteria)
{
Filtered.Value = !BeatmapCarouselFilterMatching.CheckCriteriaMatch(BeatmapInfo, criteria);
}
}
private class CustomCriteria : IRulesetFilterCriteria private class CustomCriteria : IRulesetFilterCriteria
{ {
private readonly bool match; private readonly bool match;

View File

@@ -10,7 +10,6 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Filter; using osu.Game.Rulesets.Filter;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Carousel;
using osu.Game.Screens.Select.Filter; using osu.Game.Screens.Select.Filter;
namespace osu.Game.Tests.NonVisual.Filtering namespace osu.Game.Tests.NonVisual.Filtering
@@ -509,7 +508,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
("Another One", "diff ]with [[ brackets]]]"), ("Another One", "diff ]with [[ brackets]]]"),
("Diff in title", "a"), ("Diff in title", "a"),
("a", "Diff in diff"), ("a", "Diff in diff"),
}).Select(info => new CarouselBeatmap(new BeatmapInfo }).Select(info => new FilterMatchingTest.CarouselBeatmap(new BeatmapInfo
{ {
Metadata = new BeatmapMetadata Metadata = new BeatmapMetadata
{ {

View File

@@ -44,7 +44,13 @@ namespace osu.Game.Tests.Online
availableBeatmap = importedSet.Beatmaps[0]; availableBeatmap = importedSet.Beatmaps[0];
unavailableBeatmap = importedSet.Beatmaps[1]; unavailableBeatmap = importedSet.Beatmaps[1];
Realm.Write(r => r.Remove(r.Find<BeatmapInfo>(unavailableBeatmap.ID)!)); Realm.Write(r =>
{
BeatmapInfo available = r.Find<BeatmapInfo>(availableBeatmap.ID)!;
available.OnlineMD5Hash = available.MD5Hash;
r.Remove(r.Find<BeatmapInfo>(unavailableBeatmap.ID)!);
});
} }
public override void SetUpSteps() public override void SetUpSteps()

View File

@@ -28,6 +28,7 @@ using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.OnlinePlay.Playlists;
using osu.Game.Tests.Resources; using osu.Game.Tests.Resources;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
using Realms;
namespace osu.Game.Tests.Online namespace osu.Game.Tests.Online
{ {
@@ -230,6 +231,14 @@ namespace osu.Game.Tests.Online
return testBeatmapManager.CurrentImport = base.ImportModel(item, archive, parameters, cancellationToken); return testBeatmapManager.CurrentImport = base.ImportModel(item, archive, parameters, cancellationToken);
} }
protected override void PostImport(BeatmapSetInfo model, Realm realm, ImportParameters parameters)
{
foreach (var beatmap in model.Beatmaps)
beatmap.OnlineMD5Hash = beatmap.MD5Hash;
base.PostImport(model, realm, parameters);
}
} }
} }

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -526,7 +526,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
// ReSharper disable once MemberHidesStaticFromOuterClass // ReSharper disable once MemberHidesStaticFromOuterClass
private class TestRuleset : Ruleset private class TestRuleset : Ruleset
{ {
protected override IEnumerable<HitResult> GetValidHitResults() => new[] { HitResult.Great }; public override IEnumerable<HitResult> GetValidHitResults() => new[] { HitResult.Great };
public override IEnumerable<Mod> GetModsFor(ModType type) => throw new NotImplementedException(); public override IEnumerable<Mod> GetModsFor(ModType type) => throw new NotImplementedException();

View File

@@ -79,6 +79,8 @@ namespace osu.Game.Tests.Skins
"Archives/modified-argon-20250424.osk", "Archives/modified-argon-20250424.osk",
// Covers "Argon" unstable rate counter // Covers "Argon" unstable rate counter
"Archives/modified-argon-20250809.osk", "Archives/modified-argon-20250809.osk",
// Covers legacy style performance points counter
"Archives/modified-classic-20250827.osk",
// Covers "Argon" judgement counter // Covers "Argon" judgement counter
"Archives/modified-argon-20250308.osk", "Archives/modified-argon-20250308.osk",
// Covers "Argon" clicks/s counter, longest combo counter, skinnable SR display and beatmap status pill // Covers "Argon" clicks/s counter, longest combo counter, skinnable SR display and beatmap status pill

View File

@@ -32,7 +32,7 @@ using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking;
using osu.Game.Screens.SelectV2; using osu.Game.Screens.Select;
using osu.Game.Storyboards.Drawables; using osu.Game.Storyboards.Drawables;
using osu.Game.Tests.Resources; using osu.Game.Tests.Resources;
using osuTK; using osuTK;

View File

@@ -7,7 +7,9 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osuTK; using osuTK;
@@ -31,7 +33,7 @@ namespace osu.Game.Tests.Visual.Colours
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal, Direction = FillDirection.Horizontal,
Spacing = new Vector2(5f), Spacing = new Vector2(5f),
ChildrenEnumerable = Enumerable.Range(0, 10).Select(i => new FillFlowContainer ChildrenEnumerable = Enumerable.Range(0, 15).Select(i => new FillFlowContainer
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
@@ -40,7 +42,9 @@ namespace osu.Game.Tests.Visual.Colours
Spacing = new Vector2(10f), Spacing = new Vector2(10f),
ChildrenEnumerable = Enumerable.Range(0, 10).Select(j => ChildrenEnumerable = Enumerable.Range(0, 10).Select(j =>
{ {
var colour = colours.ForStarDifficulty(1f * i + 0.1f * j); float difficulty = 1f * i + 0.1f * j;
var colour = colours.ForStarDifficulty(difficulty);
var textColour = colours.ForStarDifficultyText(difficulty);
return new FillFlowContainer return new FillFlowContainer
{ {
@@ -48,36 +52,27 @@ namespace osu.Game.Tests.Visual.Colours
Origin = Anchor.Centre, Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Spacing = new Vector2(0f, 10f), Spacing = new Vector2(0f, 5f),
Children = new Drawable[] Children = new Drawable[]
{ {
new CircularContainer new OsuSpriteText
{ {
Masking = true,
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
Size = new Vector2(75f, 25f), Font = FontUsage.Default.With(size: 10),
Children = new Drawable[] Text = $"BG: {colour.ToHex()}",
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colour,
},
new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Colour = OsuColour.ForegroundTextColourFor(colour),
Text = colour.ToHex(),
},
}
}, },
new OsuSpriteText new OsuSpriteText
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
Text = $"*{(1f * i + 0.1f * j):0.00}", Font = FontUsage.Default.With(size: 10),
Text = $"Text: {textColour.ToHex()}",
},
new StarRatingDisplay(new StarDifficulty(difficulty, 0))
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
} }
} }
}; };

View File

@@ -15,7 +15,7 @@ using osu.Game.Online.Rooms;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.SelectV2; using osu.Game.Screens.Select;
using osu.Game.Tests.Resources; using osu.Game.Tests.Resources;
using osu.Game.Tests.Visual.Metadata; using osu.Game.Tests.Visual.Metadata;
using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.OnlinePlay;

View File

@@ -1,45 +1,64 @@
// 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 osu.Framework.Graphics; using System;
using osu.Framework.Graphics.Containers; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Overlays;
using osu.Game.Screens;
using osu.Game.Screens.Edit.Submission; using osu.Game.Screens.Edit.Submission;
using osu.Game.Screens.Footer;
namespace osu.Game.Tests.Visual.Editing namespace osu.Game.Tests.Visual.Editing
{ {
public partial class TestSceneBeatmapSubmissionOverlay : OsuTestScene public partial class TestSceneBeatmapSubmissionOverlay : ScreenTestScene
{ {
private ScreenFooter footer = null!; private TestBeatmapSubmissionOverlayScreen screen = null!;
[Cached]
private readonly BeatmapSubmissionSettings beatmapSubmissionSettings = new BeatmapSubmissionSettings();
[SetUpSteps] [SetUpSteps]
public void SetUpSteps() public override void SetUpSteps()
{ {
AddStep("add overlay", () => base.SetUpSteps();
{
var receptor = new ScreenFooter.BackReceptor();
footer = new ScreenFooter(receptor);
Child = new DependencyProvidingContainer AddStep("push screen", () => LoadScreen(screen = new TestBeatmapSubmissionOverlayScreen()));
{ AddUntilStep("wait until screen is loaded", () => screen.IsLoaded, () => Is.True);
RelativeSizeAxes = Axes.Both, AddStep("show overlay", () => screen.Overlay.Show());
CachedDependencies = new[] }
{
(typeof(ScreenFooter), (object)footer), private partial class TestBeatmapSubmissionOverlayScreen : OsuScreen
(typeof(BeatmapSubmissionSettings), new BeatmapSubmissionSettings()), {
}, public override bool ShowFooter => true;
Children = new Drawable[]
{ public BeatmapSubmissionOverlay Overlay = null!;
receptor,
new BeatmapSubmissionOverlay private IDisposable? overlayRegistration;
{
State = { Value = Visibility.Visible, }, [Resolved]
}, private IOverlayManager? overlayManager { get; set; }
footer,
} [Cached]
}; private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
});
[BackgroundDependencyLoader]
private void load()
{
LoadComponent(Overlay = new BeatmapSubmissionOverlay());
}
protected override void LoadComplete()
{
base.LoadComplete();
overlayRegistration = overlayManager?.RegisterBlockingOverlay(Overlay);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
overlayRegistration?.Dispose();
}
} }
} }
} }

View File

@@ -132,6 +132,7 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName); AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName);
AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 })); AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }));
AddStep("add effect point", () => EditorBeatmap.ControlPointInfo.Add(500, new EffectControlPoint { KiaiMode = true })); AddStep("add effect point", () => EditorBeatmap.ControlPointInfo.Add(500, new EffectControlPoint { KiaiMode = true }));
AddStep("add bookmarks", () => EditorBeatmap.Bookmarks.AddRange([500, 1000]));
AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[]
{ {
new HitCircle new HitCircle
@@ -185,6 +186,7 @@ namespace osu.Game.Tests.Visual.Editing
var effectPoint = EditorBeatmap.ControlPointInfo.EffectPoints.Single(); var effectPoint = EditorBeatmap.ControlPointInfo.EffectPoints.Single();
return effectPoint.Time == 500 && effectPoint.KiaiMode && effectPoint.ScrollSpeedBindable.IsDefault; return effectPoint.Time == 500 && effectPoint.KiaiMode && effectPoint.ScrollSpeedBindable.IsDefault;
}); });
AddAssert("created difficulty has bookmarks", () => EditorBeatmap.Bookmarks.Count == 2);
AddAssert("created difficulty has no objects", () => EditorBeatmap.HitObjects.Count == 0); AddAssert("created difficulty has no objects", () => EditorBeatmap.HitObjects.Count == 0);
AddAssert("status is modified", () => EditorBeatmap.BeatmapInfo.Status == BeatmapOnlineStatus.LocallyModified); AddAssert("status is modified", () => EditorBeatmap.BeatmapInfo.Status == BeatmapOnlineStatus.LocallyModified);
@@ -223,6 +225,7 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = previousDifficultyName = Guid.NewGuid().ToString()); AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = previousDifficultyName = Guid.NewGuid().ToString());
AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 })); AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }));
AddStep("add bookmarks", () => EditorBeatmap.Bookmarks.AddRange([500, 1000]));
AddStep("add effect points", () => AddStep("add effect points", () =>
{ {
EditorBeatmap.ControlPointInfo.Add(250, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.05 }); EditorBeatmap.ControlPointInfo.Add(250, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.05 });
@@ -253,6 +256,8 @@ namespace osu.Game.Tests.Visual.Editing
return timingPoint.Time == 0 && timingPoint.BeatLength == 1000; return timingPoint.Time == 0 && timingPoint.BeatLength == 1000;
}); });
AddAssert("created difficulty has bookmarks", () => EditorBeatmap.Bookmarks.Count == 2);
AddAssert("created difficulty has effect points", () => AddAssert("created difficulty has effect points", () =>
{ {
return EditorBeatmap.ControlPointInfo.EffectPoints.SequenceEqual(new[] return EditorBeatmap.ControlPointInfo.EffectPoints.SequenceEqual(new[]
@@ -284,6 +289,7 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName); AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName);
AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 })); AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }));
AddStep("add bookmarks", () => EditorBeatmap.Bookmarks.AddRange([500, 1000]));
AddStep("add effect points", () => AddStep("add effect points", () =>
{ {
EditorBeatmap.ControlPointInfo.Add(250, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.05 }); EditorBeatmap.ControlPointInfo.Add(250, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.05 });
@@ -311,6 +317,8 @@ namespace osu.Game.Tests.Visual.Editing
return timingPoint.Time == 0 && timingPoint.BeatLength == 1000; return timingPoint.Time == 0 && timingPoint.BeatLength == 1000;
}); });
AddAssert("created difficulty has bookmarks", () => EditorBeatmap.Bookmarks.Count == 2);
AddAssert("created difficulty has effect points", () => AddAssert("created difficulty has effect points", () =>
{ {
// since this difficulty is on another ruleset, scroll speed specifications are completely reset, // since this difficulty is on another ruleset, scroll speed specifications are completely reset,
@@ -344,6 +352,7 @@ namespace osu.Game.Tests.Visual.Editing
StartTime = 1000 StartTime = 1000
} }
})); }));
AddStep("add bookmarks", () => EditorBeatmap.Bookmarks.AddRange([500, 1000]));
AddStep("set approach rate", () => EditorBeatmap.Difficulty.ApproachRate = 4); AddStep("set approach rate", () => EditorBeatmap.Difficulty.ApproachRate = 4);
AddStep("set combo colours", () => AddStep("set combo colours", () =>
{ {
@@ -394,6 +403,7 @@ namespace osu.Game.Tests.Visual.Editing
return timingPoint.Time == 0 && timingPoint.BeatLength == 1000; return timingPoint.Time == 0 && timingPoint.BeatLength == 1000;
}); });
AddAssert("created difficulty has objects", () => EditorBeatmap.HitObjects.Count == 2); AddAssert("created difficulty has objects", () => EditorBeatmap.HitObjects.Count == 2);
AddAssert("created difficulty has bookmarks", () => EditorBeatmap.Bookmarks.Count == 2);
AddAssert("approach rate correctly copied", () => EditorBeatmap.Difficulty.ApproachRate == 4); AddAssert("approach rate correctly copied", () => EditorBeatmap.Difficulty.ApproachRate == 4);
AddAssert("combo colours correctly copied", () => EditorBeatmap.BeatmapSkin.AsNonNull().ComboColours.Count == 2); AddAssert("combo colours correctly copied", () => EditorBeatmap.BeatmapSkin.AsNonNull().ComboColours.Count == 2);

View File

@@ -15,7 +15,7 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osu.Game.Screens.SelectV2; using osu.Game.Screens.Select;
using osuTK.Input; using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing namespace osu.Game.Tests.Visual.Editing

View File

@@ -0,0 +1,42 @@
// 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 osu.Framework.Graphics;
using osu.Framework.Graphics.Cursor;
using osu.Game.Graphics.Cursor;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components;
using osu.Game.Tests.Visual.UserInterface;
namespace osu.Game.Tests.Visual.Editing
{
public partial class TestSceneFormSampleSet : ThemeComparisonTestScene
{
public TestSceneFormSampleSet()
: base(false)
{
}
protected override Drawable CreateContent() => new PopoverContainer
{
RelativeSizeAxes = Axes.Both,
Child = new OsuContextMenuContainer
{
RelativeSizeAxes = Axes.Both,
Child = new FormSampleSet
{
Current =
{
Value = new EditorBeatmapSkin.SampleSet(3, "Custom set #3")
{
Filenames = ["normal-hitwhistle3.wav"]
}
},
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 0.4f,
}
}
};
}
}

View File

@@ -5,7 +5,6 @@ using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using Humanizer; using Humanizer;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Input;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Audio; using osu.Game.Audio;
@@ -20,6 +19,7 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osu.Game.Screens.Edit.Components.TernaryButtons; using osu.Game.Screens.Edit.Components.TernaryButtons;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osu.Game.Screens.Edit.Timing; using osu.Game.Screens.Edit.Timing;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps;
@@ -113,6 +113,77 @@ namespace osu.Game.Tests.Visual.Editing
hitObjectHasSampleBank(1, HitSampleInfo.BANK_DRUM); hitObjectHasSampleBank(1, HitSampleInfo.BANK_DRUM);
} }
[Test]
public void TestAutoAdditionsBankMatchesNormalBankWhenChangedViaPopover()
{
clickSamplePiece(0);
setBankViaPopover(HitSampleInfo.BANK_SOFT);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_SOFT);
toggleAdditionViaPopover(1);
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_SOFT);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT);
setBankViaPopover(HitSampleInfo.BANK_DRUM);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
setAdditionBankViaPopover(HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_NORMAL);
setAdditionBankViaPopover(EditorSelectionHandler.HIT_BANK_AUTO);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
}
[Test]
public void TestAutoAdditionsBankMatchesNormalBankWhenChangedViaHotkeys()
{
AddStep("select first object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[0]));
AddStep("set soft normal bank", () =>
{
InputManager.PressKey(Key.ShiftLeft);
InputManager.Key(Key.E);
InputManager.ReleaseKey(Key.ShiftLeft);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_SOFT);
AddStep("toggle finish", () => InputManager.Key(Key.E));
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_SOFT);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT);
AddStep("set drum normal bank", () =>
{
InputManager.PressKey(Key.ShiftLeft);
InputManager.Key(Key.R);
InputManager.ReleaseKey(Key.ShiftLeft);
});
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
AddStep("set normal addition bank", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.W);
InputManager.ReleaseKey(Key.AltLeft);
});
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_NORMAL);
AddStep("set auto addition bank", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.Q);
InputManager.ReleaseKey(Key.AltLeft);
});
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
}
[Test] [Test]
public void TestUndo() public void TestUndo()
{ {
@@ -196,11 +267,6 @@ namespace osu.Game.Tests.Visual.Editing
clickSamplePiece(1); clickSamplePiece(1);
samplePopoverHasSingleBank(HitSampleInfo.BANK_SOFT); samplePopoverHasSingleBank(HitSampleInfo.BANK_SOFT);
setBankViaPopover(string.Empty);
hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT);
hitObjectHasSampleBank(1, HitSampleInfo.BANK_SOFT);
samplePopoverHasSingleBank(HitSampleInfo.BANK_SOFT);
setBankViaPopover(HitSampleInfo.BANK_DRUM); setBankViaPopover(HitSampleInfo.BANK_DRUM);
hitObjectHasSampleBank(0, HitSampleInfo.BANK_DRUM); hitObjectHasSampleBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleBank(1, HitSampleInfo.BANK_DRUM); hitObjectHasSampleBank(1, HitSampleInfo.BANK_DRUM);
@@ -219,11 +285,6 @@ namespace osu.Game.Tests.Visual.Editing
clickSamplePiece(1); clickSamplePiece(1);
samplePopoverHasIndeterminateBank(); samplePopoverHasIndeterminateBank();
setBankViaPopover(string.Empty);
hitObjectHasSampleBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleBank(1, HitSampleInfo.BANK_SOFT);
samplePopoverHasIndeterminateBank();
setBankViaPopover(HitSampleInfo.BANK_NORMAL); setBankViaPopover(HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleBank(0, HitSampleInfo.BANK_NORMAL); hitObjectHasSampleBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleBank(1, HitSampleInfo.BANK_NORMAL); hitObjectHasSampleBank(1, HitSampleInfo.BANK_NORMAL);
@@ -357,7 +418,7 @@ namespace osu.Game.Tests.Visual.Editing
{ {
for (int i = 0; i < h.Samples.Count; i++) for (int i = 0; i < h.Samples.Count; i++)
{ {
h.Samples[i] = h.Samples[i].With(newBank: HitSampleInfo.BANK_SOFT); h.Samples[i] = h.Samples[i].With(newBank: HitSampleInfo.BANK_SOFT, newEditorAutoBank: false);
} }
} }
}); });
@@ -365,7 +426,7 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("add whistle addition", () => AddStep("add whistle addition", () =>
{ {
foreach (var h in EditorBeatmap.HitObjects) foreach (var h in EditorBeatmap.HitObjects)
h.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, HitSampleInfo.BANK_SOFT)); h.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, HitSampleInfo.BANK_SOFT, editorAutoBank: false));
}); });
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
@@ -534,6 +595,172 @@ namespace osu.Game.Tests.Visual.Editing
() => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name != HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(expected)); () => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name != HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(expected));
} }
[Test]
public void TestNonAutoBankHotkeysDuringPlacementPersistAfterPlacement()
{
AddStep("Clear all objects", () => EditorBeatmap.Clear());
AddStep("Enter placement mode", () => InputManager.Key(Key.Number2));
AddStep("Move mouse to centre", () => InputManager.MoveMouseTo(Editor.ChildrenOfType<HitObjectComposer>().First().ScreenSpaceDrawQuad.Centre));
AddStep("Move to 3000", () => EditorClock.Seek(3000));
AddStep("Press drum bank shortcut", () =>
{
InputManager.PressKey(Key.ShiftLeft);
InputManager.Key(Key.R);
InputManager.ReleaseKey(Key.ShiftLeft);
});
AddAssert($"Placement sample is {HitSampleInfo.BANK_DRUM}",
() => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name == HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(HitSampleInfo.BANK_DRUM));
AddStep("Press normal addition bank shortcut", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.W);
InputManager.ReleaseKey(Key.AltLeft);
});
AddStep("Press finish sample shortcut", () =>
{
InputManager.Key(Key.E);
});
AddAssert($"Placement sample addition is {HitSampleInfo.BANK_NORMAL}",
() => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name != HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(HitSampleInfo.BANK_NORMAL));
AddStep("Finish placement", () => InputManager.Click(MouseButton.Left));
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasAutoNormalBankFlag(0, false);
hitObjectHasAutoAdditionBankFlag(0, false);
clickSamplePiece(0);
samplePopoverIsOpen();
samplePopoverHasSingleAdditionBank(HitSampleInfo.BANK_NORMAL);
}
[Test]
public void TestAutoAdditionBankHotkeyDuringPlacementPersistsAfterPlacement()
{
AddStep("Clear all objects", () => EditorBeatmap.Clear());
AddStep("Enter placement mode", () => InputManager.Key(Key.Number2));
AddStep("Move mouse to centre", () => InputManager.MoveMouseTo(Editor.ChildrenOfType<HitObjectComposer>().First().ScreenSpaceDrawQuad.Centre));
AddStep("Move to 3000", () => EditorClock.Seek(3000));
AddStep("Press drum bank shortcut", () =>
{
InputManager.PressKey(Key.ShiftLeft);
InputManager.Key(Key.R);
InputManager.ReleaseKey(Key.ShiftLeft);
});
AddAssert($"Placement sample is {HitSampleInfo.BANK_DRUM}",
() => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name == HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(HitSampleInfo.BANK_DRUM));
AddStep("Press normal addition bank shortcut", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.W);
InputManager.ReleaseKey(Key.AltLeft);
});
AddStep("Press finish sample shortcut", () =>
{
InputManager.Key(Key.E);
});
AddStep("Press auto addition bank shortcut", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.Q);
InputManager.ReleaseKey(Key.AltLeft);
});
AddAssert($"Placement sample addition is {HitSampleInfo.BANK_DRUM}",
() => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name != HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(HitSampleInfo.BANK_DRUM));
AddStep("Finish placement", () => InputManager.Click(MouseButton.Left));
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasAutoNormalBankFlag(0, false);
hitObjectHasAutoAdditionBankFlag(0, true);
clickSamplePiece(0);
samplePopoverIsOpen();
samplePopoverHasSingleAdditionBank(EditorSelectionHandler.HIT_BANK_AUTO);
}
[Test]
public void TestFullAutoBankHotkeyDuringPlacementPersistsAfterPlacement()
{
AddStep("Clear all objects", () => EditorBeatmap.Clear());
AddStep("Enter placement mode", () => InputManager.Key(Key.Number2));
AddStep("Move mouse to centre", () => InputManager.MoveMouseTo(Editor.ChildrenOfType<HitObjectComposer>().First().ScreenSpaceDrawQuad.Centre));
AddStep("Move to 3000", () => EditorClock.Seek(3000));
AddStep("Press auto normal bank shortcut", () =>
{
InputManager.PressKey(Key.ShiftLeft);
InputManager.Key(Key.Q);
InputManager.ReleaseKey(Key.ShiftLeft);
});
AddAssert($"Placement sample is {HitSampleInfo.BANK_NORMAL}",
() => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name == HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(HitSampleInfo.BANK_NORMAL));
AddStep("Press finish sample shortcut", () =>
{
InputManager.Key(Key.E);
});
AddStep("Press auto addition bank shortcut", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.Q);
InputManager.ReleaseKey(Key.AltLeft);
});
AddAssert($"Placement sample addition is {HitSampleInfo.BANK_NORMAL}",
() => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name != HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(HitSampleInfo.BANK_NORMAL));
AddStep("Finish placement", () => InputManager.Click(MouseButton.Left));
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasAutoNormalBankFlag(0, false); // it's the first object - nothing to inherit bank from
hitObjectHasAutoAdditionBankFlag(0, true);
clickSamplePiece(0);
samplePopoverIsOpen();
samplePopoverHasSingleBank(HitSampleInfo.BANK_NORMAL);
samplePopoverHasSingleAdditionBank(EditorSelectionHandler.HIT_BANK_AUTO);
dismissPopover();
AddStep("Move to 5000", () => EditorClock.Seek(5000));
AddStep("Enter placement mode", () => InputManager.Key(Key.Number2));
AddStep("Move mouse to centre", () => InputManager.MoveMouseTo(Editor.ChildrenOfType<HitObjectComposer>().First().ScreenSpaceDrawQuad.Centre));
AddStep("Finish placement", () => InputManager.Click(MouseButton.Left));
hitObjectHasSamples(1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH); // finish is still implied, continuing from first placement
hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_NORMAL);
hitObjectHasAutoNormalBankFlag(1, true);
hitObjectHasAutoAdditionBankFlag(1, true);
clickSamplePiece(1);
samplePopoverIsOpen();
samplePopoverHasSingleBank(HitSampleInfo.BANK_NORMAL);
samplePopoverHasSingleAdditionBank(EditorSelectionHandler.HIT_BANK_AUTO);
}
[Test] [Test]
public void PopoverForMultipleSelectionChangesAllSamples() public void PopoverForMultipleSelectionChangesAllSamples()
{ {
@@ -610,19 +837,19 @@ namespace osu.Game.Tests.Visual.Editing
Path = new SliderPath(new[] { new PathControlPoint(Vector2.Zero), new PathControlPoint(new Vector2(250, 0)) }), Path = new SliderPath(new[] { new PathControlPoint(Vector2.Zero), new PathControlPoint(new Vector2(250, 0)) }),
Samples = Samples =
{ {
new HitSampleInfo(HitSampleInfo.HIT_NORMAL) new HitSampleInfo(HitSampleInfo.HIT_NORMAL, editorAutoBank: false)
}, },
NodeSamples = new List<IList<HitSampleInfo>> NodeSamples = new List<IList<HitSampleInfo>>
{ {
new List<HitSampleInfo> new List<HitSampleInfo>
{ {
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_DRUM), new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_DRUM, editorAutoBank: false),
new HitSampleInfo(HitSampleInfo.HIT_CLAP, bank: HitSampleInfo.BANK_DRUM), new HitSampleInfo(HitSampleInfo.HIT_CLAP, bank: HitSampleInfo.BANK_DRUM, editorAutoBank: false),
}, },
new List<HitSampleInfo> new List<HitSampleInfo>
{ {
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_SOFT), new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_SOFT, editorAutoBank: false),
new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, bank: HitSampleInfo.BANK_SOFT), new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, bank: HitSampleInfo.BANK_SOFT, editorAutoBank: false),
}, },
} }
}); });
@@ -819,6 +1046,174 @@ namespace osu.Game.Tests.Visual.Editing
} }
} }
[Test]
public void TestAddSoundBeforeSettingNonAutoAdditionBankOnSelectedObject()
{
AddStep("select first object", () =>
{
EditorBeatmap.SelectedHitObjects.Clear();
EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[0]);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
AddStep("add finish sound", () => InputManager.Key(Key.E));
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasAutoAdditionBankFlag(0, true);
AddStep("set drum addition bank", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.R);
InputManager.ReleaseKey(Key.AltLeft);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasAutoAdditionBankFlag(0, false);
}
[Test]
public void TestAddSoundAfterSettingNonAutoAdditionBankOnSelectedObject()
{
AddStep("select first object", () =>
{
EditorBeatmap.SelectedHitObjects.Clear();
EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[0]);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
AddStep("set drum addition bank", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.R);
InputManager.ReleaseKey(Key.AltLeft);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
AddStep("add finish sound", () => InputManager.Key(Key.E));
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasAutoAdditionBankFlag(0, false);
}
[Test]
public void TestSwitchSoundAfterSettingNonAutoAdditionBankOnSelectedObject()
{
AddStep("select first object", () =>
{
EditorBeatmap.SelectedHitObjects.Clear();
EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[0]);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
AddStep("set drum addition bank", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.R);
InputManager.ReleaseKey(Key.AltLeft);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
AddStep("add finish sound", () => InputManager.Key(Key.E));
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasAutoAdditionBankFlag(0, false);
AddStep("remove finish sound", () => InputManager.Key(Key.E));
AddStep("add whistle sound", () => InputManager.Key(Key.W));
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasAutoAdditionBankFlag(0, false);
}
[Test]
public void TestAddSoundBeforeSettingAutoAdditionBankOnSelectedObject()
{
AddStep("select first object", () =>
{
EditorBeatmap.SelectedHitObjects.Clear();
EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[0]);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
AddStep("add finish sound", () => InputManager.Key(Key.E));
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasAutoAdditionBankFlag(0, true);
AddStep("set auto addition bank", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.Q);
InputManager.ReleaseKey(Key.AltLeft);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasAutoAdditionBankFlag(0, true);
AddStep("set drum normal bank", () =>
{
InputManager.PressKey(Key.ShiftLeft);
InputManager.Key(Key.R);
InputManager.ReleaseKey(Key.ShiftLeft);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasAutoAdditionBankFlag(0, true);
}
[Test]
public void TestAddSoundAfterSettingAutoAdditionBankOnSelectedObject()
{
AddStep("select first object", () =>
{
EditorBeatmap.SelectedHitObjects.Clear();
EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[0]);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
AddStep("set auto addition bank", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.Q);
InputManager.ReleaseKey(Key.AltLeft);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
AddStep("add finish sound", () => InputManager.Key(Key.E));
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasAutoAdditionBankFlag(0, true);
AddStep("set drum normal bank", () =>
{
InputManager.PressKey(Key.ShiftLeft);
InputManager.Key(Key.R);
InputManager.ReleaseKey(Key.ShiftLeft);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasAutoAdditionBankFlag(0, true);
}
private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} sample piece", () => private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} sample piece", () =>
{ {
var samplePiece = this.ChildrenOfType<SamplePointPiece>().Single(piece => piece is not NodeSamplePointPiece && piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)); var samplePiece = this.ChildrenOfType<SamplePointPiece>().Single(piece => piece is not NodeSamplePointPiece && piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex));
@@ -878,17 +1273,25 @@ namespace osu.Game.Tests.Visual.Editing
private void samplePopoverHasSingleBank(string bank) => AddUntilStep($"sample popover has bank {bank}", () => private void samplePopoverHasSingleBank(string bank) => AddUntilStep($"sample popover has bank {bank}", () =>
{ {
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault(); var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault();
var textBox = popover?.ChildrenOfType<OsuTextBox>().First(); var dropdown = popover?.ChildrenOfType<LabelledDropdown<string>>().First();
return textBox?.Current.Value == bank && string.IsNullOrEmpty(textBox.PlaceholderText.ToString()); return dropdown?.Current.Value == bank;
}); });
private void samplePopoverHasIndeterminateBank() => AddUntilStep("sample popover has indeterminate bank", () => private void samplePopoverHasIndeterminateBank() => AddUntilStep("sample popover has indeterminate bank", () =>
{ {
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault(); var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault();
var textBox = popover?.ChildrenOfType<OsuTextBox>().First(); var dropdown = popover?.ChildrenOfType<LabelledDropdown<string>>().First();
return textBox != null && string.IsNullOrEmpty(textBox.Current.Value) && !string.IsNullOrEmpty(textBox.PlaceholderText.ToString()); return dropdown?.Current.Value == "(multiple)";
});
private void samplePopoverHasSingleAdditionBank(string bank) => AddUntilStep($"sample popover has bank {bank}", () =>
{
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault();
var dropdown = popover?.ChildrenOfType<LabelledDropdown<string>>().ElementAt(1);
return dropdown?.Current.Value == bank;
}); });
private void dismissPopover() private void dismissPopover()
@@ -920,23 +1323,15 @@ namespace osu.Game.Tests.Visual.Editing
private void setBankViaPopover(string bank) => AddStep($"set bank {bank} via popover", () => private void setBankViaPopover(string bank) => AddStep($"set bank {bank} via popover", () =>
{ {
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().Single(); var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().Single();
var textBox = popover.ChildrenOfType<LabelledTextBox>().First(); var textBox = popover.ChildrenOfType<LabelledDropdown<string>>().First();
textBox.Current.Value = bank; textBox.Current.Value = bank;
// force a commit via keyboard.
// this is needed when testing attempting to set empty bank - which should revert to the previous value, but only on commit.
((IFocusManager)InputManager).ChangeFocus(textBox);
InputManager.Key(Key.Enter);
}); });
private void setAdditionBankViaPopover(string bank) => AddStep($"set addition bank {bank} via popover", () => private void setAdditionBankViaPopover(string bank) => AddStep($"set addition bank {bank} via popover", () =>
{ {
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().Single(); var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().Single();
var textBox = popover.ChildrenOfType<LabelledTextBox>().ToArray()[1]; var textBox = popover.ChildrenOfType<LabelledDropdown<string>>().ToArray()[1];
textBox.Current.Value = bank; textBox.Current.Value = bank;
// force a commit via keyboard.
// this is needed when testing attempting to set empty bank - which should revert to the previous value, but only on commit.
((IFocusManager)InputManager).ChangeFocus(textBox);
InputManager.Key(Key.Enter);
}); });
private void toggleAdditionViaPopover(int index) => AddStep($"toggle addition {index} via popover", () => private void toggleAdditionViaPopover(int index) => AddStep($"toggle addition {index} via popover", () =>
@@ -972,6 +1367,18 @@ namespace osu.Game.Tests.Visual.Editing
return h.Samples.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank); return h.Samples.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank);
}); });
private void hitObjectHasAutoNormalBankFlag(int objectIndex, bool autoBank) => AddAssert($"{objectIndex.ToOrdinalWords()} has auto normal bank {(autoBank ? "on" : "off")}", () =>
{
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
return h.Samples.Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(o => o.EditorAutoBank == autoBank);
});
private void hitObjectHasAutoAdditionBankFlag(int objectIndex, bool autoBank) => AddAssert($"{objectIndex.ToOrdinalWords()} has auto addition bank {(autoBank ? "on" : "off")}", () =>
{
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
return h.Samples.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.EditorAutoBank == autoBank);
});
private void hitObjectNodeHasSamples(int objectIndex, int nodeIndex, params string[] samples) => AddAssert( private void hitObjectNodeHasSamples(int objectIndex, int nodeIndex, params string[] samples) => AddAssert(
$"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has samples {string.Join(',', samples)}", () => $"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has samples {string.Join(',', samples)}", () =>
{ {

View File

@@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Editing
SaveEditor(); SaveEditor();
ReloadEditorToSameBeatmap(); ReloadEditorToSameBeatmap();
AddAssert("beatmap marked as locally modified", () => EditorBeatmap.BeatmapInfo.Status, () => Is.EqualTo(BeatmapOnlineStatus.LocallyModified)); AddUntilStep("beatmap marked as locally modified", () => EditorBeatmap.BeatmapInfo.Status, () => Is.EqualTo(BeatmapOnlineStatus.LocallyModified));
AddAssert("beatmap hash changed", () => EditorBeatmap.BeatmapInfo.MD5Hash, () => Is.Not.EqualTo(initialHash)); AddAssert("beatmap hash changed", () => EditorBeatmap.BeatmapInfo.MD5Hash, () => Is.Not.EqualTo(initialHash));
} }
} }

View File

@@ -14,7 +14,7 @@ using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
using osu.Game.Screens.SelectV2; using osu.Game.Screens.Select;
using osu.Game.Tests.Resources; using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Visual.Editing namespace osu.Game.Tests.Visual.Editing

View File

@@ -37,6 +37,42 @@ namespace osu.Game.Tests.Visual.Editing
private GlobalActionContainer globalActionContainer => this.ChildrenOfType<GlobalActionContainer>().Single(); private GlobalActionContainer globalActionContainer => this.ChildrenOfType<GlobalActionContainer>().Single();
[Test]
public void TestPlaceThenUndo()
{
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType<Playfield>().Single()));
AddStep("place circle", () => InputManager.Click(MouseButton.Left));
AddAssert("one circle added", () => EditorBeatmap.HitObjects, () => Has.One.Items);
AddStep("undo", () => Editor.Undo());
AddAssert("circle removed", () => EditorBeatmap.HitObjects, () => Is.Empty);
}
[Test]
public void TestTimingLost()
{
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType<Playfield>().Single()));
AddAssert("placement ready", () => this.ChildrenOfType<ComposeBlueprintContainer>().Single().CurrentPlacement, () => Is.Not.Null);
AddStep("nuke timing", () => EditorBeatmap.ControlPointInfo.Clear());
AddAssert("placement not available", () => this.ChildrenOfType<ComposeBlueprintContainer>().Single().CurrentPlacement, () => Is.Null);
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
AddAssert("placement not available", () => this.ChildrenOfType<ComposeBlueprintContainer>().Single().CurrentPlacement, () => Is.Null);
AddStep("add back timing", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint()));
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
AddAssert("placement ready", () => this.ChildrenOfType<ComposeBlueprintContainer>().Single().CurrentPlacement, () => Is.Not.Null);
}
[Test] [Test]
public void TestDeleteUsingMiddleMouse() public void TestDeleteUsingMiddleMouse()
{ {
@@ -236,8 +272,8 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("circle has 2 samples", () => EditorBeatmap.HitObjects[1].Samples, () => Has.Count.EqualTo(2)); AddAssert("circle has 2 samples", () => EditorBeatmap.HitObjects[1].Samples, () => Has.Count.EqualTo(2));
AddAssert("normal sample has soft bank", () => EditorBeatmap.HitObjects[1].Samples.Single(s => s.Name == HitSampleInfo.HIT_NORMAL).Bank, AddAssert("normal sample has soft bank", () => EditorBeatmap.HitObjects[1].Samples.Single(s => s.Name == HitSampleInfo.HIT_NORMAL).Bank,
() => Is.EqualTo(HitSampleInfo.BANK_SOFT)); () => Is.EqualTo(HitSampleInfo.BANK_SOFT));
AddAssert("clap sample has drum bank", () => EditorBeatmap.HitObjects[1].Samples.Single(s => s.Name == HitSampleInfo.HIT_CLAP).Bank, AddAssert("clap sample has soft bank", () => EditorBeatmap.HitObjects[1].Samples.Single(s => s.Name == HitSampleInfo.HIT_CLAP).Bank,
() => Is.EqualTo(HitSampleInfo.BANK_DRUM)); () => Is.EqualTo(HitSampleInfo.BANK_SOFT));
AddAssert("circle inherited volume", () => EditorBeatmap.HitObjects[1].Samples.All(s => s.Volume == 70)); AddAssert("circle inherited volume", () => EditorBeatmap.HitObjects[1].Samples.All(s => s.Volume == 70));
AddStep("seek to 1000", () => EditorClock.Seek(1000)); // previous object is the one at time 500, which has no additions AddStep("seek to 1000", () => EditorClock.Seek(1000)); // previous object is the one at time 500, which has no additions

View File

@@ -19,7 +19,7 @@ using osu.Game.Screens.Menu;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osuTK; using osuTK;
namespace osu.Game.Tests.Visual.SongSelect namespace osu.Game.Tests.Visual.Gameplay
{ {
public partial class TestSceneBeatmapMetadataDisplay : OsuTestScene public partial class TestSceneBeatmapMetadataDisplay : OsuTestScene
{ {

View File

@@ -8,7 +8,7 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Select.Leaderboards; using osu.Game.Screens.Play.Leaderboards;
using osuTK.Graphics; using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay

View File

@@ -50,6 +50,34 @@ namespace osu.Game.Tests.Visual.Gameplay
sprites.All(sprite => sprite.ChildrenOfType<Sprite>().All(s => s.Texture == null))); sprites.All(sprite => sprite.ChildrenOfType<Sprite>().All(s => s.Texture == null)));
} }
[Test]
public void TestSpriteFadeOverflowBehaviour()
{
AddStep("create sprite", () => SetContents(_ =>
{
var layer = storyboard.GetLayer("Background");
var sprite = new StoryboardSprite(lookup_name, Anchor.TopLeft, new Vector2(256, 192));
sprite.Commands.AddAlpha(Easing.None, Time.Current, Time.Current + 2000, 0, 2);
layer.Elements.Clear();
layer.Add(sprite);
return new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
storyboard.CreateDrawable()
}
};
}));
AddUntilStep("sprite reached high opacity once", () => sprites.All(sprite => sprite.ChildrenOfType<Sprite>().All(s => s.Alpha > 0.8f)));
AddUntilStep("sprite reset to low opacity", () => sprites.All(sprite => sprite.ChildrenOfType<Sprite>().All(s => s.Alpha < 0.2f)));
AddUntilStep("sprite reached high opacity twice", () => sprites.All(sprite => sprite.ChildrenOfType<Sprite>().All(s => s.Alpha > 0.8f)));
}
[Test] [Test]
public void TestLookupFromStoryboard() public void TestLookupFromStoryboard()
{ {

View File

@@ -21,7 +21,7 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Select.Leaderboards; using osu.Game.Screens.Play.Leaderboards;
using osu.Game.Tests.Gameplay; using osu.Game.Tests.Gameplay;
using osuTK.Graphics; using osuTK.Graphics;

View File

@@ -270,7 +270,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
AddStep("create overlay", () => AddStep("create overlay", () =>
{ {
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>()); hudOverlay = new HUDOverlay(null, Array.Empty<Mod>(), new PlayerConfiguration());
// Add any key just to display the key counter visually. // Add any key just to display the key counter visually.
hudOverlay.InputCountController.Add(new KeyCounterKeyboardTrigger(Key.Space)); hudOverlay.InputCountController.Add(new KeyCounterKeyboardTrigger(Key.Space));

Some files were not shown because too many files have changed in this diff Show More