Compare commits
114 Commits
experiment
...
65f275106e
| Author | SHA1 | Date | |
|---|---|---|---|
| 65f275106e | |||
| 6e374762fd | |||
| 80646a166c | |||
| 8f0510d903 | |||
| 02e7000ee4 | |||
|
|
c26e669fc5 | ||
| 081355864e | |||
| 6435a835d1 | |||
|
|
e621eed0ba | ||
|
|
41b8033ebd | ||
|
|
1d9de88aaa | ||
|
|
3ca5e20e70 | ||
|
|
404044e8d7 | ||
|
|
d5575b4037 | ||
|
|
04ba5aa575 | ||
|
|
51ed19cb99 | ||
|
|
0a408a3ac4 | ||
|
|
22ba956f25 | ||
|
|
526ee32268 | ||
|
|
df6d6edaca | ||
|
|
24ec43b3b6 | ||
|
|
9e77a5b050 | ||
|
|
12832e9fef | ||
|
|
e83f3d5e77 | ||
|
|
f2f5cf19a2 | ||
|
|
bb9f9e4d35 | ||
|
|
6e1316241a | ||
|
|
3eaa5314ac | ||
|
|
6ba72fa481 | ||
|
|
8dd131f17e | ||
|
|
7e109add96 | ||
|
|
f953d58922 | ||
|
|
47164c61b4 | ||
|
|
311c75aa53 | ||
|
|
0b40f1d0db | ||
|
|
e831d1b6fa | ||
|
|
ed15e1fb88 | ||
|
|
33b99a51b1 | ||
|
|
8a6c857719 | ||
|
|
5abd93eda7 | ||
|
|
4030383276 | ||
|
|
f9c1b24df4 | ||
|
|
197c318180 | ||
|
|
fda40d7fd5 | ||
|
|
be6fb9aa77 | ||
|
|
0e57ee9ba6 | ||
|
|
043235fed2 | ||
|
|
ec21685c25 | ||
| 628181a883 | |||
| 835329efd3 | |||
| 5399943118 | |||
| d07f82f6f4 | |||
|
|
244bad07c7 | ||
|
|
149f18c3f5 | ||
|
|
6a82b7331f | ||
|
|
65253708d8 | ||
|
|
556c2469bf | ||
|
|
f7b0e114a9 | ||
|
|
68677200f3 | ||
|
|
2bea59e65f | ||
|
|
c0fd5637de | ||
|
|
5e7a99c97f | ||
|
|
8f628d16ae | ||
|
|
2ccb65aa65 | ||
|
|
4d851f2527 | ||
|
|
4bafbfb9e4 | ||
|
|
3f179e3903 | ||
|
|
196b28115e | ||
|
|
7660a9ba8e | ||
|
|
e908b80359 | ||
|
|
a2bf8e3988 | ||
| 5b186bb740 | |||
|
|
6e8246b539 | ||
| 6cb99c13c2 | |||
|
|
3cca458c21 | ||
|
|
bc59270f3e | ||
| 96008e06ab | |||
| 590b0a8028 | |||
| 70f7f09a83 | |||
|
|
16343fd7d6 | ||
|
|
acafc06bcc | ||
|
|
c0c3690908 | ||
| 490137405f | |||
| f3c6f53f70 | |||
| 8cb5c682b4 | |||
| c3d79295d3 | |||
|
|
5292d4a04e | ||
|
|
d3ae20dd88 | ||
|
|
c852e5854c | ||
|
|
0756c45d70 | ||
|
|
73624e4e25 | ||
|
|
f374af7ce7 | ||
|
|
7530ad1a7b | ||
|
|
a049f5065d | ||
|
|
4627c8a859 | ||
|
|
30f7da8f71 | ||
|
|
4b8ff481fd | ||
|
|
a7f1795f98 | ||
|
|
c053cfbf9b | ||
|
|
e47a60f303 | ||
|
|
92016a7d9b | ||
|
|
41885c0fc0 | ||
|
|
e75a6b4010 | ||
|
|
ddce11fbc8 | ||
|
|
c894969d17 | ||
|
|
ad6c0c272d | ||
|
|
083365f332 | ||
|
|
62b4999184 | ||
|
|
bb5933ef80 | ||
|
|
62548244bc | ||
|
|
a393b3c6b1 | ||
|
|
a3443f76be | ||
|
|
d998847271 | ||
|
|
62803af1de |
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -148,9 +148,7 @@ jobs:
|
||||
# https://github.com/dotnet/macios/issues/19157
|
||||
# https://github.com/actions/runner-images/issues/12758
|
||||
- name: Use Xcode 16.4
|
||||
run: |
|
||||
sudo xcode-select -switch /Applications/Xcode_16.4.app
|
||||
xcodebuild -downloadPlatform iOS
|
||||
run: sudo xcode-select -switch /Applications/Xcode_16.4.app
|
||||
|
||||
- name: Build
|
||||
run: dotnet build -c Debug osu.iOS.slnf
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -19,6 +19,7 @@ bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Pp]ub/
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
|
||||
36
.vscode/launch.json
vendored
36
.vscode/launch.json
vendored
@@ -7,9 +7,9 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Desktop/bin/Debug/net8.0/osu!.dll"
|
||||
"${workspaceFolder}/osu.Desktop/bin/Debug/net8.0/osu!.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "Build osu! (Debug)",
|
||||
"console": "internalConsole"
|
||||
},
|
||||
@@ -19,9 +19,9 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Desktop/bin/Release/net8.0/osu!.dll"
|
||||
"${workspaceFolder}/osu.Desktop/bin/Release/net8.0/osu!.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "Build osu! (Release)",
|
||||
"console": "internalConsole"
|
||||
},
|
||||
@@ -31,9 +31,9 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Game.Tests/bin/Debug/net8.0/osu.Game.Tests.dll"
|
||||
"${workspaceFolder}/osu.Game.Tests/bin/Debug/net8.0/osu.Game.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "Build tests (Debug)",
|
||||
"console": "internalConsole"
|
||||
},
|
||||
@@ -43,9 +43,9 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Game.Tests/bin/Release/net8.0/osu.Game.Tests.dll"
|
||||
"${workspaceFolder}/osu.Game.Tests/bin/Release/net8.0/osu.Game.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "Build tests (Release)",
|
||||
"console": "internalConsole"
|
||||
},
|
||||
@@ -55,10 +55,10 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Desktop/bin/Debug/net8.0/osu!.dll",
|
||||
"${workspaceFolder}/osu.Desktop/bin/Debug/net8.0/osu!.dll",
|
||||
"--tournament"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "Build osu! (Debug)",
|
||||
"console": "internalConsole"
|
||||
},
|
||||
@@ -68,10 +68,10 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Desktop/bin/Release/net8.0/osu!.dll",
|
||||
"${workspaceFolder}/osu.Desktop/bin/Release/net8.0/osu!.dll",
|
||||
"--tournament"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "Build osu! (Release)",
|
||||
"console": "internalConsole"
|
||||
},
|
||||
@@ -81,10 +81,10 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net8.0/osu.Game.Tournament.Tests.dll",
|
||||
"${workspaceFolder}/osu.Game.Tournament.Tests/bin/Debug/net8.0/osu.Game.Tournament.Tests.dll",
|
||||
"--tournament"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "Build tournament tests (Debug)",
|
||||
"console": "internalConsole"
|
||||
},
|
||||
@@ -94,10 +94,10 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net8.0/osu.Game.Tournament.Tests.dll",
|
||||
"${workspaceFolder}/osu.Game.Tournament.Tests/bin/Debug/net8.0/osu.Game.Tournament.Tests.dll",
|
||||
"--tournament"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "Build tournament tests (Release)",
|
||||
"console": "internalConsole"
|
||||
},
|
||||
@@ -105,12 +105,12 @@
|
||||
"name": "Benchmark",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/osu.Game.Benchmarks/bin/Release/net8.0/osu.Game.Benchmarks.dll",
|
||||
"program": "${workspaceFolder}/osu.Game.Benchmarks/bin/Release/net8.0/osu.Game.Benchmarks.dll",
|
||||
"args": [
|
||||
"--filter",
|
||||
"*"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "Build benchmarks",
|
||||
"console": "internalConsole"
|
||||
}
|
||||
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"dotnet.defaultSolution": "osu.Desktop.slnf"
|
||||
}
|
||||
@@ -49,7 +49,7 @@
|
||||
<PackageProjectUrl>https://github.com/ppy/osu</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/ppy/osu</RepositoryUrl>
|
||||
<PackageReleaseNotes>Automated release.</PackageReleaseNotes>
|
||||
<Company>ppy Pty Ltd</Company>
|
||||
<Company>ppy Pty Ltd, jvnkosu! team</Company>
|
||||
<Copyright>Copyright (c) 2025 ppy Pty Ltd</Copyright>
|
||||
<PackageTags>osu game</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
19
MakeInstaller.ps1
Normal file
19
MakeInstaller.ps1
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env powershell
|
||||
param (
|
||||
[string]$Version,
|
||||
[string]$BuildConfig = "Release"
|
||||
)
|
||||
|
||||
if ($Version -eq "") {
|
||||
Write-Host "Usage: .\MakeInstaller.ps1 <VERSION_NUMBER> [-BuildConfig <BUILD_CONFIG>]"
|
||||
Write-Host "Example: .\MakeInstaller.ps1 2025.823.0 -BuildConfig Debug"
|
||||
exit
|
||||
}
|
||||
|
||||
$tmpPub = ".\pub"
|
||||
if (-not (Test-Path -Path $tmpPub)) {
|
||||
New-Item -ItemType Directory -path $tmpPub
|
||||
}
|
||||
|
||||
dotnet publish -c $BuildConfig osu.Desktop --self-contained -r win-x64 -o $tmpPub -verbosity:m /p:Version=$Version
|
||||
vpk pack --packId jvnkosu.Client --packTitle "jvnkosu!lazer" --packVersion $Version --packDir ./pub --mainExe="osu!.exe"
|
||||
149
README.md
149
README.md
@@ -1,147 +1,40 @@
|
||||
<p align="center">
|
||||
<img width="500" alt="osu! logo" src="assets/lazer.png">
|
||||
</p>
|
||||
# jvnkosu! client
|
||||
|
||||
# osu!
|
||||
A free-to-win rhythm game based on osu!(lazer). Click is just a *rhythm* away!
|
||||
|
||||
[](https://github.com/ppy/osu/actions/workflows/ci.yml)
|
||||
[](https://github.com/ppy/osu/releases/latest)
|
||||
[](https://www.codefactor.io/repository/github/ppy/osu)
|
||||
[](https://discord.gg/ppy)
|
||||
[](https://crowdin.com/project/osu-web)
|
||||
## Disclaimer
|
||||
|
||||
A free-to-win rhythm game. Rhythm is just a *click* away!
|
||||
*osu!* is a registered trademark of ppy Pty Ltd.
|
||||
jvnkosu! is not affiliated with, or endorsed by ppy Pty Ltd., but makes use of its open-source components and resources.
|
||||
|
||||
This is the future – and final – iteration of the [osu!](https://osu.ppy.sh) game client which marks the beginning of an open era! Currently known by and released under the release codename "*lazer*". As in sharper than cutting-edge.
|
||||
## License
|
||||
Client source code is licensed under the MIT license, see the [LICENCE](LICENCE) file in repository root for more info.
|
||||
|
||||
## Status
|
||||
Game assets are included as a NuGet package and licensed under the CC BY-NC 4.0, which prohibits commercial use. See [ppy/osu-resources](https://github.com/ppy/osu-resources) for more info.
|
||||
|
||||
This project is under constant development, but we do our best to keep things in a stable state. Players are encouraged to install from a release alongside their stable *osu!* client. This project will continue to evolve until we eventually reach the point where most users prefer it over the previous "osu!stable" release.
|
||||
Registered trademarks "osu!" and "ppy" are property of ppy Pty Ltd., and protected by trademark law.
|
||||
|
||||
A few resources are available as starting points to getting involved and understanding the project:
|
||||
## Compiling from source
|
||||
Building jvnkosu! from source is pretty much possible (and welcome here).
|
||||
|
||||
- Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer).
|
||||
- You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management).
|
||||
- Track our current efforts [towards improving the game](https://github.com/orgs/ppy/projects/7/views/6).
|
||||
First, you must have a desktop platform with [.NET Core SDK 8](https://dotnet.microsoft.com/download) installed. Windows, Linux, macOS should work well. You can check if you have the SDK installed by running `dotnet --version` in your command prompt/terminal.
|
||||
|
||||
## Running osu!
|
||||
|
||||
If you are just looking to give the game a whirl, you can grab the latest release for your platform:
|
||||
|
||||
### Latest release:
|
||||
|
||||
| [Windows 10+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 12+ ([Intel](https://github.com/ppy/osu/releases/latest/download/osu.app.Intel.zip), [Apple Silicon](https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip)) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 13.4+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) |
|
||||
|--------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| ------------- | ------------- | ------------- |
|
||||
|
||||
You can also generally download a version for your current device from the [osu! site](https://osu.ppy.sh/home/download).
|
||||
|
||||
If your platform is unsupported or not listed above, there is still a chance you can run the release or manually build it by following the instructions below.
|
||||
|
||||
**For iOS/iPadOS users**: The iOS testflight link fills up very fast (Apple has a hard limit of 10,000 users). We reset it occasionally. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements. Our goal is to get the game on mobile app stores very soon so we don't have to live with this limitation.
|
||||
|
||||
## Developing a custom ruleset
|
||||
|
||||
osu! is designed to allow user-created gameplay variations, called "rulesets". Building one of these allows a developer to harness the power of the osu! beatmap library, game engine, and general UX for a new style of gameplay. To get started working on a ruleset, we have some templates available [here](https://github.com/ppy/osu/tree/master/Templates).
|
||||
|
||||
You can see some examples of custom rulesets by visiting the [custom ruleset directory](https://github.com/ppy/osu/discussions/13096).
|
||||
|
||||
## Developing osu!
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Please make sure you have the following prerequisites:
|
||||
|
||||
- A desktop platform with the [.NET 8.0 SDK](https://dotnet.microsoft.com/download) installed.
|
||||
|
||||
When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as the latest version of [Visual Studio](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/), or [Visual Studio Code](https://code.visualstudio.com/) with the [EditorConfig](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) and [C# Dev Kit](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit) plugin installed.
|
||||
|
||||
### Downloading the source code
|
||||
|
||||
Clone the repository:
|
||||
|
||||
```shell
|
||||
git clone https://github.com/ppy/osu
|
||||
cd osu
|
||||
Then, download the source code. You may download it as an archive and unzip it, but using [Git](https://git-scm.com/) instead is recommended:
|
||||
```
|
||||
git clone https://gitea.jvnko.boats/jvnkosu/client
|
||||
```
|
||||
|
||||
To update the source code to the latest commit, run the following command inside the `osu` directory:
|
||||
|
||||
```shell
|
||||
git pull
|
||||
To **run** the project, switch to project's directory and run the following:
|
||||
```
|
||||
|
||||
### Building
|
||||
|
||||
#### From an IDE
|
||||
|
||||
You should load the solution via one of the platform-specific `.slnf` files, rather than the main `.sln`. This will reduce dependencies and hide platforms that you don't care about. Valid `.slnf` files are:
|
||||
|
||||
- `osu.Desktop.slnf` (most common)
|
||||
- `osu.Android.slnf`
|
||||
- `osu.iOS.slnf`
|
||||
|
||||
Run configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `osu! (Tests)` project/configuration. More information on this is provided [below](#contributing).
|
||||
|
||||
To build for mobile platforms, you will likely need to run `sudo dotnet workload restore` if you haven't done so previously. This will install Android/iOS tooling required to complete the build.
|
||||
|
||||
#### From CLI
|
||||
|
||||
You can also build and run *osu!* from the command-line with a single command:
|
||||
|
||||
```shell
|
||||
dotnet run --project osu.Desktop
|
||||
```
|
||||
|
||||
When running locally to do any kind of performance testing, make sure to add `-c Release` to the build command, as the overhead of running with the default `Debug` configuration can be large (especially when testing with local framework modifications as below).
|
||||
|
||||
If the build fails, try to restore NuGet packages with `dotnet restore`.
|
||||
|
||||
### Testing with resource/framework modifications
|
||||
|
||||
Sometimes it may be necessary to cross-test changes in [osu-resources](https://github.com/ppy/osu-resources) or [osu-framework](https://github.com/ppy/osu-framework). This can be quickly achieved using included commands:
|
||||
|
||||
Windows:
|
||||
|
||||
```ps
|
||||
UseLocalFramework.ps1
|
||||
UseLocalResources.ps1
|
||||
To **compile**:
|
||||
```
|
||||
dotnet build osu.Desktop
|
||||
```
|
||||
|
||||
macOS / Linux:
|
||||
To reduce performance overhead in custom builds, it's recommended to build with the `-c Release` flag, that will use the release profile and remove possibly unneeded debugging code.
|
||||
|
||||
```ps
|
||||
UseLocalFramework.sh
|
||||
UseLocalResources.sh
|
||||
```
|
||||
|
||||
Note that these commands assume you have the relevant project(s) checked out in adjacent directories:
|
||||
|
||||
```
|
||||
|- osu // this repository
|
||||
|- osu-framework
|
||||
|- osu-resources
|
||||
```
|
||||
|
||||
### Code analysis
|
||||
|
||||
Before committing your code, please run a code formatter. This can be achieved by running `dotnet format` in the command line, or using the `Format code` command in your IDE.
|
||||
|
||||
We have adopted some cross-platform, compiler integrated analyzers. They can provide warnings when you are editing, building inside IDE or from command line, as-if they are provided by the compiler itself.
|
||||
|
||||
JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it from PowerShell with `.\InspectCode.ps1`. Alternatively, you can install ReSharper or use Rider to get inline support in your IDE of choice.
|
||||
|
||||
## Contributing
|
||||
|
||||
When it comes to contributing to the project, the two main things you can do to help out are reporting issues and submitting pull requests. Please refer to the [contributing guidelines](CONTRIBUTING.md) to understand how to help in the most effective way possible.
|
||||
|
||||
If you wish to help with localisation efforts, head over to [crowdin](https://crowdin.com/project/osu-web).
|
||||
|
||||
We love to reward quality contributions. If you have made a large contribution, or are a regular contributor, you are welcome to [submit an expense via opencollective](https://opencollective.com/ppy/expenses/new). If you have any questions, feel free to [reach out to peppy](mailto:pe@ppy.sh) before doing so.
|
||||
|
||||
## Licence
|
||||
|
||||
*osu!*'s code and framework are licensed under the [MIT licence](https://opensource.org/licenses/MIT). Please see [the licence file](LICENCE) for more information. [tl;dr](https://tldrlegal.com/license/mit-license) you can do whatever you want as long as you include the original copyright and license notice in any copy of the software/source.
|
||||
|
||||
Please note that this *does not cover* the usage of the "osu!" or "ppy" branding in any software, resources, advertising or promotion, as this is protected by trademark law.
|
||||
|
||||
Please also note that game resources are covered by a separate licence. Please see the [ppy/osu-resources](https://github.com/ppy/osu-resources) repository for clarifications.
|
||||
### See the [original readme](README.original.md) for more info.
|
||||
147
README.original.md
Normal file
147
README.original.md
Normal file
@@ -0,0 +1,147 @@
|
||||
<p align="center">
|
||||
<img width="500" alt="osu! logo" src="assets/lazer.png">
|
||||
</p>
|
||||
|
||||
# osu!
|
||||
|
||||
[](https://github.com/ppy/osu/actions/workflows/ci.yml)
|
||||
[](https://github.com/ppy/osu/releases/latest)
|
||||
[](https://www.codefactor.io/repository/github/ppy/osu)
|
||||
[](https://discord.gg/ppy)
|
||||
[](https://crowdin.com/project/osu-web)
|
||||
|
||||
A free-to-win rhythm game. Rhythm is just a *click* away!
|
||||
|
||||
This is the future – and final – iteration of the [osu!](https://osu.ppy.sh) game client which marks the beginning of an open era! Currently known by and released under the release codename "*lazer*". As in sharper than cutting-edge.
|
||||
|
||||
## Status
|
||||
|
||||
This project is under constant development, but we do our best to keep things in a stable state. Players are encouraged to install from a release alongside their stable *osu!* client. This project will continue to evolve until we eventually reach the point where most users prefer it over the previous "osu!stable" release.
|
||||
|
||||
A few resources are available as starting points to getting involved and understanding the project:
|
||||
|
||||
- Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer).
|
||||
- You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management).
|
||||
- Track our current efforts [towards improving the game](https://github.com/orgs/ppy/projects/7/views/6).
|
||||
|
||||
## Running osu!
|
||||
|
||||
If you are just looking to give the game a whirl, you can grab the latest release for your platform:
|
||||
|
||||
### Latest release:
|
||||
|
||||
| [Windows 10+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 12+ ([Intel](https://github.com/ppy/osu/releases/latest/download/osu.app.Intel.zip), [Apple Silicon](https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip)) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 13.4+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) |
|
||||
|--------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| ------------- | ------------- | ------------- |
|
||||
|
||||
You can also generally download a version for your current device from the [osu! site](https://osu.ppy.sh/home/download).
|
||||
|
||||
If your platform is unsupported or not listed above, there is still a chance you can run the release or manually build it by following the instructions below.
|
||||
|
||||
**For iOS/iPadOS users**: The iOS testflight link fills up very fast (Apple has a hard limit of 10,000 users). We reset it occasionally. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements. Our goal is to get the game on mobile app stores very soon so we don't have to live with this limitation.
|
||||
|
||||
## Developing a custom ruleset
|
||||
|
||||
osu! is designed to allow user-created gameplay variations, called "rulesets". Building one of these allows a developer to harness the power of the osu! beatmap library, game engine, and general UX for a new style of gameplay. To get started working on a ruleset, we have some templates available [here](https://github.com/ppy/osu/tree/master/Templates).
|
||||
|
||||
You can see some examples of custom rulesets by visiting the [custom ruleset directory](https://github.com/ppy/osu/discussions/13096).
|
||||
|
||||
## Developing osu!
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Please make sure you have the following prerequisites:
|
||||
|
||||
- A desktop platform with the [.NET 8.0 SDK](https://dotnet.microsoft.com/download) installed.
|
||||
|
||||
When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as the latest version of [Visual Studio](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/), or [Visual Studio Code](https://code.visualstudio.com/) with the [EditorConfig](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) and [C# Dev Kit](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit) plugin installed.
|
||||
|
||||
### Downloading the source code
|
||||
|
||||
Clone the repository:
|
||||
|
||||
```shell
|
||||
git clone https://github.com/ppy/osu
|
||||
cd osu
|
||||
```
|
||||
|
||||
To update the source code to the latest commit, run the following command inside the `osu` directory:
|
||||
|
||||
```shell
|
||||
git pull
|
||||
```
|
||||
|
||||
### Building
|
||||
|
||||
#### From an IDE
|
||||
|
||||
You should load the solution via one of the platform-specific `.slnf` files, rather than the main `.sln`. This will reduce dependencies and hide platforms that you don't care about. Valid `.slnf` files are:
|
||||
|
||||
- `osu.Desktop.slnf` (most common)
|
||||
- `osu.Android.slnf`
|
||||
- `osu.iOS.slnf`
|
||||
|
||||
Run configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `osu! (Tests)` project/configuration. More information on this is provided [below](#contributing).
|
||||
|
||||
To build for mobile platforms, you will likely need to run `sudo dotnet workload restore` if you haven't done so previously. This will install Android/iOS tooling required to complete the build.
|
||||
|
||||
#### From CLI
|
||||
|
||||
You can also build and run *osu!* from the command-line with a single command:
|
||||
|
||||
```shell
|
||||
dotnet run --project osu.Desktop
|
||||
```
|
||||
|
||||
When running locally to do any kind of performance testing, make sure to add `-c Release` to the build command, as the overhead of running with the default `Debug` configuration can be large (especially when testing with local framework modifications as below).
|
||||
|
||||
If the build fails, try to restore NuGet packages with `dotnet restore`.
|
||||
|
||||
### Testing with resource/framework modifications
|
||||
|
||||
Sometimes it may be necessary to cross-test changes in [osu-resources](https://github.com/ppy/osu-resources) or [osu-framework](https://github.com/ppy/osu-framework). This can be quickly achieved using included commands:
|
||||
|
||||
Windows:
|
||||
|
||||
```ps
|
||||
UseLocalFramework.ps1
|
||||
UseLocalResources.ps1
|
||||
```
|
||||
|
||||
macOS / Linux:
|
||||
|
||||
```ps
|
||||
UseLocalFramework.sh
|
||||
UseLocalResources.sh
|
||||
```
|
||||
|
||||
Note that these commands assume you have the relevant project(s) checked out in adjacent directories:
|
||||
|
||||
```
|
||||
|- osu // this repository
|
||||
|- osu-framework
|
||||
|- osu-resources
|
||||
```
|
||||
|
||||
### Code analysis
|
||||
|
||||
Before committing your code, please run a code formatter. This can be achieved by running `dotnet format` in the command line, or using the `Format code` command in your IDE.
|
||||
|
||||
We have adopted some cross-platform, compiler integrated analyzers. They can provide warnings when you are editing, building inside IDE or from command line, as-if they are provided by the compiler itself.
|
||||
|
||||
JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it from PowerShell with `.\InspectCode.ps1`. Alternatively, you can install ReSharper or use Rider to get inline support in your IDE of choice.
|
||||
|
||||
## Contributing
|
||||
|
||||
When it comes to contributing to the project, the two main things you can do to help out are reporting issues and submitting pull requests. Please refer to the [contributing guidelines](CONTRIBUTING.md) to understand how to help in the most effective way possible.
|
||||
|
||||
If you wish to help with localisation efforts, head over to [crowdin](https://crowdin.com/project/osu-web).
|
||||
|
||||
We love to reward quality contributions. If you have made a large contribution, or are a regular contributor, you are welcome to [submit an expense via opencollective](https://opencollective.com/ppy/expenses/new). If you have any questions, feel free to [reach out to peppy](mailto:pe@ppy.sh) before doing so.
|
||||
|
||||
## Licence
|
||||
|
||||
*osu!*'s code and framework are licensed under the [MIT licence](https://opensource.org/licenses/MIT). Please see [the licence file](LICENCE) for more information. [tl;dr](https://tldrlegal.com/license/mit-license) you can do whatever you want as long as you include the original copyright and license notice in any copy of the software/source.
|
||||
|
||||
Please note that this *does not cover* the usage of the "osu!" or "ppy" branding in any software, resources, advertising or promotion, as this is protected by trademark law.
|
||||
|
||||
Please also note that game resources are covered by a separate licence. Please see the [ppy/osu-resources](https://github.com/ppy/osu-resources) repository for clarifications.
|
||||
@@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.808.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.829.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
||||
@@ -115,10 +115,12 @@ namespace osu.Desktop
|
||||
if (IsFirstRun)
|
||||
LocalConfig.SetValue(OsuSetting.ReleaseStream, ReleaseStream.Lazer);
|
||||
|
||||
if (IsPackageManaged)
|
||||
return new NoActionUpdateManager();
|
||||
// if (IsPackageManaged)
|
||||
// return new NoActionUpdateManager();
|
||||
|
||||
return new VelopackUpdateManager();
|
||||
// return new VelopackUpdateManager();
|
||||
|
||||
return new NoActionUpdateManager(); // for now, APIs are useless for actually downloading the releases. TODO: adapt UpdateManager for gitea
|
||||
}
|
||||
|
||||
public override bool RestartAppWhenExited()
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Description>A free-to-win rhythm game. Rhythm is just a *click* away!</Description>
|
||||
<Description>A free-to-win rhythm game based on osu!(lazer). Click is just a *rhythm* away!</Description>
|
||||
<AssemblyName>osu!</AssemblyName>
|
||||
<AssemblyTitle>osu!(lazer)</AssemblyTitle>
|
||||
<Title>osu!</Title>
|
||||
<Product>osu!(lazer)</Product>
|
||||
<AssemblyTitle>jvnkosu!</AssemblyTitle>
|
||||
<Title>jvnkosu!</Title>
|
||||
<Product>jvnkosu!</Product>
|
||||
<ApplicationIcon>lazer.ico</ApplicationIcon>
|
||||
<Version>0.0.0</Version>
|
||||
<FileVersion>0.0.0</FileVersion>
|
||||
|
||||
@@ -3,16 +3,19 @@
|
||||
<metadata>
|
||||
<id>osulazer</id>
|
||||
<version>0.0.0</version>
|
||||
<title>osu!</title>
|
||||
<authors>ppy Pty Ltd</authors>
|
||||
<title>jvnkosu!</title>
|
||||
<authors>ppy Pty Ltd., jvnkosu! team</authors>
|
||||
<owners>Dean Herbert</owners>
|
||||
<projectUrl>https://osu.ppy.sh/</projectUrl>
|
||||
<projectUrl>https://osu.jvnko.boats/</projectUrl>
|
||||
<iconUrl>https://github.com/ppy/osu/blob/master/assets/lazer-nuget.png?raw=true</iconUrl>
|
||||
<icon>icon.png</icon>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>A free-to-win rhythm game. Rhythm is just a *click* away!</description>
|
||||
<description>A free-to-win rhythm game based on osu!(lazer). Click is just a *rhythm* away!</description>
|
||||
<releaseNotes>testing</releaseNotes>
|
||||
<copyright>Copyright (c) 2025 ppy Pty Ltd</copyright>
|
||||
<copyright>
|
||||
Copyright (c) 2025 ppy Pty Ltd
|
||||
Copyright (c) 2025 jvnkosu! team
|
||||
</copyright>
|
||||
<language>en-AU</language>
|
||||
</metadata>
|
||||
<files>
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
|
||||
public override BindableBool ComboBasedSize { get; } = new BindableBool(true);
|
||||
|
||||
public override float DefaultFlashlightSize => 325;
|
||||
public override float DefaultFlashlightSize => 203.125f;
|
||||
|
||||
protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
@@ -16,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
public override string Acronym => "FF";
|
||||
public override LocalisableString Description => "The fruits are... floating?";
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override IconUsage? Icon => FontAwesome.Solid.Cloud;
|
||||
public override IconUsage? Icon => OsuIcon.ModFloatingFruits;
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> drawableRuleset)
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
public override LocalisableString Description => "Dashing by default, slow down!";
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override IconUsage? Icon => FontAwesome.Solid.Running;
|
||||
public override IconUsage? Icon => OsuIcon.ModMovingFast;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax) };
|
||||
|
||||
private DrawableCatchRuleset drawableRuleset = null!;
|
||||
|
||||
@@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void TestFilterIntersection()
|
||||
public void TestKeysFilterIntersection()
|
||||
{
|
||||
var criteria = new ManiaFilterCriteria();
|
||||
criteria.TryParseCustomKeywordCriteria("keys", Operator.Greater, "4");
|
||||
@@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void TestInvalidFilters()
|
||||
public void TestInvalidKeysFilters()
|
||||
{
|
||||
var criteria = new ManiaFilterCriteria();
|
||||
|
||||
@@ -183,5 +183,132 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
Assert.False(criteria.TryParseCustomKeywordCriteria("keys", Operator.NotEqual, "4,some text"));
|
||||
Assert.False(criteria.TryParseCustomKeywordCriteria("keys", Operator.GreaterOrEqual, "4,5,6"));
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void TestLnsEqual()
|
||||
{
|
||||
var criteria = new ManiaFilterCriteria();
|
||||
var filterCriteria = new FilterCriteria
|
||||
{
|
||||
Ruleset = new ManiaRuleset().RulesetInfo
|
||||
};
|
||||
|
||||
criteria.TryParseCustomKeywordCriteria("lns", Operator.Equal, "0");
|
||||
BeatmapInfo beatmapInfo1 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
|
||||
{
|
||||
TotalObjectCount = 0,
|
||||
EndTimeObjectCount = 0
|
||||
};
|
||||
Assert.True(criteria.Matches(beatmapInfo1, filterCriteria));
|
||||
|
||||
criteria.TryParseCustomKeywordCriteria("lns", Operator.Equal, "0");
|
||||
BeatmapInfo beatmapInfo2 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
|
||||
{
|
||||
TotalObjectCount = 100,
|
||||
EndTimeObjectCount = 0
|
||||
};
|
||||
Assert.True(criteria.Matches(beatmapInfo2, filterCriteria));
|
||||
|
||||
criteria.TryParseCustomKeywordCriteria("lns", Operator.Equal, "100");
|
||||
BeatmapInfo beatmapInfo3 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
|
||||
{
|
||||
TotalObjectCount = 100,
|
||||
EndTimeObjectCount = 100
|
||||
};
|
||||
Assert.True(criteria.Matches(beatmapInfo3, filterCriteria));
|
||||
|
||||
criteria.TryParseCustomKeywordCriteria("lns", Operator.Equal, "1");
|
||||
BeatmapInfo beatmapInfo4 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
|
||||
{
|
||||
TotalObjectCount = 100,
|
||||
EndTimeObjectCount = 1
|
||||
};
|
||||
Assert.True(criteria.Matches(beatmapInfo4, filterCriteria));
|
||||
|
||||
criteria.TryParseCustomKeywordCriteria("lns", Operator.Equal, "0.1");
|
||||
BeatmapInfo beatmapInfo5 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
|
||||
{
|
||||
TotalObjectCount = 1000,
|
||||
EndTimeObjectCount = 1
|
||||
};
|
||||
Assert.True(criteria.Matches(beatmapInfo5, filterCriteria));
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void TestLnsGreaterOrEqual()
|
||||
{
|
||||
var criteria = new ManiaFilterCriteria();
|
||||
var filterCriteria = new FilterCriteria
|
||||
{
|
||||
Ruleset = new ManiaRuleset().RulesetInfo
|
||||
};
|
||||
|
||||
criteria.TryParseCustomKeywordCriteria("lns", Operator.GreaterOrEqual, "0");
|
||||
BeatmapInfo beatmapInfo1 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
|
||||
{
|
||||
TotalObjectCount = 0,
|
||||
EndTimeObjectCount = 0
|
||||
};
|
||||
Assert.True(criteria.Matches(beatmapInfo1, filterCriteria));
|
||||
|
||||
criteria.TryParseCustomKeywordCriteria("lns", Operator.GreaterOrEqual, "0");
|
||||
BeatmapInfo beatmapInfo2 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
|
||||
{
|
||||
TotalObjectCount = 100,
|
||||
EndTimeObjectCount = 0
|
||||
};
|
||||
Assert.True(criteria.Matches(beatmapInfo2, filterCriteria));
|
||||
|
||||
criteria.TryParseCustomKeywordCriteria("lns", Operator.GreaterOrEqual, "100");
|
||||
BeatmapInfo beatmapInfo3 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
|
||||
{
|
||||
TotalObjectCount = 100,
|
||||
EndTimeObjectCount = 100
|
||||
};
|
||||
Assert.True(criteria.Matches(beatmapInfo3, filterCriteria));
|
||||
|
||||
criteria.TryParseCustomKeywordCriteria("lns", Operator.GreaterOrEqual, "1");
|
||||
BeatmapInfo beatmapInfo4 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
|
||||
{
|
||||
TotalObjectCount = 100,
|
||||
EndTimeObjectCount = 1
|
||||
};
|
||||
Assert.True(criteria.Matches(beatmapInfo4, filterCriteria));
|
||||
|
||||
criteria.TryParseCustomKeywordCriteria("lns", Operator.GreaterOrEqual, "0.1");
|
||||
BeatmapInfo beatmapInfo5 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
|
||||
{
|
||||
TotalObjectCount = 1000,
|
||||
EndTimeObjectCount = 1
|
||||
};
|
||||
Assert.True(criteria.Matches(beatmapInfo5, filterCriteria));
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void TestLnsNotManiaRuleset()
|
||||
{
|
||||
var criteria = new ManiaFilterCriteria();
|
||||
var filterCriteria = new FilterCriteria
|
||||
{
|
||||
Ruleset = new ManiaRuleset().RulesetInfo
|
||||
};
|
||||
|
||||
criteria.TryParseCustomKeywordCriteria("lns", Operator.LessOrEqual, "100");
|
||||
BeatmapInfo beatmapInfo = new BeatmapInfo
|
||||
{
|
||||
TotalObjectCount = 100,
|
||||
EndTimeObjectCount = 50
|
||||
};
|
||||
Assert.False(criteria.Matches(beatmapInfo, filterCriteria));
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void TestInvalidLnsFilters()
|
||||
{
|
||||
var criteria = new ManiaFilterCriteria();
|
||||
|
||||
Assert.False(criteria.TryParseCustomKeywordCriteria("lns", Operator.Equal, "some text"));
|
||||
Assert.False(criteria.TryParseCustomKeywordCriteria("lns", Operator.GreaterOrEqual, "1some text"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
@@ -19,12 +20,16 @@ namespace osu.Game.Rulesets.Mania
|
||||
public class ManiaFilterCriteria : IRulesetFilterCriteria
|
||||
{
|
||||
private readonly HashSet<int> includedKeyCounts = Enumerable.Range(1, LegacyBeatmapDecoder.MAX_MANIA_KEY_COUNT).ToHashSet();
|
||||
private FilterCriteria.OptionalRange<float> longNotePercentage;
|
||||
|
||||
public bool Matches(BeatmapInfo beatmapInfo, FilterCriteria criteria)
|
||||
{
|
||||
int keyCount = ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo), criteria.Mods);
|
||||
|
||||
return includedKeyCounts.Contains(keyCount);
|
||||
bool keyCountMatch = includedKeyCounts.Contains(keyCount);
|
||||
bool longNotePercentageMatch = !longNotePercentage.HasFilter || (!isConvertedBeatmap(beatmapInfo) && longNotePercentage.IsInRange(calculateLongNotePercentage(beatmapInfo)));
|
||||
|
||||
return keyCountMatch && longNotePercentageMatch;
|
||||
}
|
||||
|
||||
public bool TryParseCustomKeywordCriteria(string key, Operator op, string strValues)
|
||||
@@ -84,6 +89,10 @@ namespace osu.Game.Rulesets.Mania
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
case "ln":
|
||||
case "lns":
|
||||
return FilterQueryParser.TryUpdateCriteriaRange(ref longNotePercentage, op, strValues);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -103,5 +112,18 @@ namespace osu.Game.Rulesets.Mania
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool isConvertedBeatmap(BeatmapInfo beatmapInfo)
|
||||
{
|
||||
return !beatmapInfo.Ruleset.Equals(new ManiaRuleset().RulesetInfo);
|
||||
}
|
||||
|
||||
private static float calculateLongNotePercentage(BeatmapInfo beatmapInfo)
|
||||
{
|
||||
int holdNotes = beatmapInfo.EndTimeObjectCount;
|
||||
int totalNotes = Math.Max(1, beatmapInfo.TotalObjectCount);
|
||||
|
||||
return holdNotes / (float)totalNotes * 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
|
||||
public override LocalisableString Description => "No more tricky speed changes!";
|
||||
|
||||
public override IconUsage? Icon => FontAwesome.Solid.Equals;
|
||||
public override IconUsage? Icon => OsuIcon.ModConstantSpeed;
|
||||
|
||||
public override ModType Type => ModType.Conversion;
|
||||
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
@@ -14,6 +16,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public override string Name => "Cover";
|
||||
public override string Acronym => "CO";
|
||||
public override IconUsage? Icon => OsuIcon.ModCover;
|
||||
|
||||
public override LocalisableString Description => @"Decrease the playfield's viewing area.";
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// 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.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
@@ -13,6 +15,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public override string Name => "Dual Stages";
|
||||
public override string Acronym => "DS";
|
||||
public override LocalisableString Description => @"Double the stages, double the fun!";
|
||||
public override IconUsage? Icon => OsuIcon.ModDualStages;
|
||||
public override ModType Type => ModType.Conversion;
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
@@ -12,6 +14,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public override string Name => "Fade In";
|
||||
public override string Acronym => "FI";
|
||||
public override IconUsage? Icon => OsuIcon.ModFadeIn;
|
||||
public override LocalisableString Description => @"Keys appear out of nowhere!";
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override bool ValidForFreestyleAsRequiredMod => false;
|
||||
|
||||
@@ -9,6 +9,7 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
@@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
|
||||
public override LocalisableString Description => @"Replaces all hold notes with normal notes.";
|
||||
|
||||
public override IconUsage? Icon => FontAwesome.Solid.DotCircle;
|
||||
public override IconUsage? Icon => OsuIcon.ModHoldOff;
|
||||
|
||||
public override ModType Type => ModType.Conversion;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
|
||||
public override LocalisableString Description => "Hold the keys. To the beat.";
|
||||
|
||||
public override IconUsage? Icon => FontAwesome.Solid.YinYang;
|
||||
public override IconUsage? Icon => OsuIcon.ModInvert;
|
||||
|
||||
public override ModType Type => ModType.Conversion;
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// 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.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public override int KeyCount => 1;
|
||||
public override string Name => "One Key";
|
||||
public override string Acronym => "1K";
|
||||
public override IconUsage? Icon => OsuIcon.ModOneKey;
|
||||
public override LocalisableString Description => @"Play with one key.";
|
||||
public override bool Ranked => false;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// 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.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public override int KeyCount => 10;
|
||||
public override string Name => "Ten Keys";
|
||||
public override string Acronym => "10K";
|
||||
public override IconUsage? Icon => OsuIcon.ModTenKeys;
|
||||
public override LocalisableString Description => @"Play with ten keys.";
|
||||
public override bool Ranked => false;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// 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.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public override int KeyCount => 2;
|
||||
public override string Name => "Two Keys";
|
||||
public override string Acronym => "2K";
|
||||
public override IconUsage? Icon => OsuIcon.ModTwoKeys;
|
||||
public override LocalisableString Description => @"Play with two keys.";
|
||||
public override bool Ranked => false;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// 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.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public override int KeyCount => 3;
|
||||
public override string Name => "Three Keys";
|
||||
public override string Acronym => "3K";
|
||||
public override IconUsage? Icon => OsuIcon.ModThreeKeys;
|
||||
public override LocalisableString Description => @"Play with three keys.";
|
||||
public override bool Ranked => false;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// 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.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public override int KeyCount => 4;
|
||||
public override string Name => "Four Keys";
|
||||
public override string Acronym => "4K";
|
||||
public override IconUsage? Icon => OsuIcon.ModFourKeys;
|
||||
public override LocalisableString Description => @"Play with four keys.";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// 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.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public override int KeyCount => 5;
|
||||
public override string Name => "Five Keys";
|
||||
public override string Acronym => "5K";
|
||||
public override IconUsage? Icon => OsuIcon.ModFiveKeys;
|
||||
public override LocalisableString Description => @"Play with five keys.";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// 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.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public override int KeyCount => 6;
|
||||
public override string Name => "Six Keys";
|
||||
public override string Acronym => "6K";
|
||||
public override IconUsage? Icon => OsuIcon.ModSixKeys;
|
||||
public override LocalisableString Description => @"Play with six keys.";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// 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.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public override int KeyCount => 7;
|
||||
public override string Name => "Seven Keys";
|
||||
public override string Acronym => "7K";
|
||||
public override IconUsage? Icon => OsuIcon.ModSevenKeys;
|
||||
public override LocalisableString Description => @"Play with seven keys.";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// 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.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public override int KeyCount => 8;
|
||||
public override string Name => "Eight Keys";
|
||||
public override string Acronym => "8K";
|
||||
public override IconUsage? Icon => OsuIcon.ModEightKeys;
|
||||
public override LocalisableString Description => @"Play with eight keys.";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// 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.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
@@ -10,6 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public override int KeyCount => 9;
|
||||
public override string Name => "Nine Keys";
|
||||
public override string Acronym => "9K";
|
||||
public override IconUsage? Icon => OsuIcon.ModNineKeys;
|
||||
public override LocalisableString Description => @"Play with nine keys.";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
@@ -26,6 +28,8 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
|
||||
public override double ScoreMultiplier => 0.9;
|
||||
|
||||
public override IconUsage? Icon => OsuIcon.ModNoRelease;
|
||||
|
||||
public override ModType Type => ModType.DifficultyReduction;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ManiaModHoldOff) };
|
||||
|
||||
@@ -32,26 +32,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
[Test]
|
||||
public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
|
||||
|
||||
[Test]
|
||||
public void TestPlayfieldBasedSize()
|
||||
{
|
||||
OsuModFlashlight flashlight;
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mods = [flashlight = new OsuModFlashlight(), new OsuModBarrelRoll()],
|
||||
PassCondition = () =>
|
||||
{
|
||||
var flashlightOverlay = Player.DrawableRuleset.Overlays
|
||||
.ChildrenOfType<ModFlashlight<OsuHitObject>.Flashlight>()
|
||||
.First();
|
||||
|
||||
// the combo check is here because the flashlight radius decreases for the first time at 100 combo
|
||||
// and hardcoding it here eliminates the need to meddle in flashlight internals further by e.g. exposing `GetComboScaleFor()`
|
||||
return flashlightOverlay.GetSize() < flashlight.DefaultFlashlightSize && Player.GameplayState.ScoreProcessor.Combo.Value < 100;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSliderDimsOnlyAfterStartTime()
|
||||
{
|
||||
|
||||
@@ -323,7 +323,7 @@ namespace osu.Game.Rulesets.Osu.HUD
|
||||
if (PositionDisplayStyle.Value == PositionDisplay.Normalised && lastObjectPosition != null)
|
||||
{
|
||||
hitPosition = AccuracyHeatmap.FindRelativeHitPosition(lastObjectPosition.Value, ((OsuHitObject)circleJudgement.HitObject).StackedEndPosition,
|
||||
circleJudgement.CursorPositionAtHit.Value, objectRadius, 45) * 0.5f;
|
||||
circleJudgement.CursorPositionAtHit.Value, objectRadius, 45) * (inner_portion / 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
@@ -13,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override string Name => @"Alternate";
|
||||
public override string Acronym => @"AL";
|
||||
public override LocalisableString Description => @"Don't use the same key twice in a row!";
|
||||
public override IconUsage? Icon => FontAwesome.Solid.Keyboard;
|
||||
public override IconUsage? Icon => OsuIcon.ModAlternate;
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModSingleTap) }).ToArray();
|
||||
|
||||
protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction != action;
|
||||
|
||||
@@ -7,6 +7,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
@@ -19,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override string Acronym => "AD";
|
||||
public override LocalisableString Description => "Never trust the approach circles...";
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle;
|
||||
public override IconUsage? Icon => OsuIcon.ModApproachDifferent;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModFreezeFrame) };
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@@ -26,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override LocalisableString Description => "Play with blinds on your screen.";
|
||||
public override string Acronym => "BL";
|
||||
|
||||
public override IconUsage? Icon => FontAwesome.Solid.Adjust;
|
||||
public override IconUsage? Icon => OsuIcon.ModBlinds;
|
||||
public override ModType Type => ModType.DifficultyIncrease;
|
||||
|
||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@@ -21,6 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public override string Name => "Bloom";
|
||||
public override string Acronym => "BM";
|
||||
public override IconUsage? Icon => OsuIcon.ModBloom;
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override LocalisableString Description => "The cursor blooms into.. a larger cursor!";
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
@@ -11,7 +11,9 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
@@ -34,6 +36,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public override IconUsage? Icon => OsuIcon.ModBubbles;
|
||||
|
||||
public override ModType Type => ModType.Fun;
|
||||
|
||||
// Compatibility with these seems potentially feasible in the future, blocked for now because they don't work as one would expect
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
@@ -13,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
public override string Acronym => "DF";
|
||||
|
||||
public override IconUsage? Icon => FontAwesome.Solid.CompressArrowsAlt;
|
||||
public override IconUsage? Icon => OsuIcon.ModDeflate;
|
||||
|
||||
public override LocalisableString Description => "Hit them at the right size!";
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
@@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public override string Name => "Depth";
|
||||
public override string Acronym => "DP";
|
||||
public override IconUsage? Icon => FontAwesome.Solid.Cube;
|
||||
public override IconUsage? Icon => OsuIcon.ModDepth;
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override LocalisableString Description => "3D. Almost.";
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
public override BindableBool ComboBasedSize { get; } = new BindableBool(true);
|
||||
|
||||
public override float DefaultFlashlightSize => 200;
|
||||
public override float DefaultFlashlightSize => 125;
|
||||
|
||||
private OsuFlashlight flashlight = null!;
|
||||
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
@@ -19,6 +21,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
public override string Acronym => "FR";
|
||||
|
||||
public override IconUsage? Icon => OsuIcon.ModFreezeFrame;
|
||||
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public override LocalisableString Description => "Burn the notes into your memory.";
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
@@ -13,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
public override string Acronym => "GR";
|
||||
|
||||
public override IconUsage? Icon => FontAwesome.Solid.ArrowsAltV;
|
||||
public override IconUsage? Icon => OsuIcon.ModGrow;
|
||||
|
||||
public override LocalisableString Description => "Hit them at the right size!";
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ using osu.Framework.Localisation;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
@@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public override string Name => "Magnetised";
|
||||
public override string Acronym => "MG";
|
||||
public override IconUsage? Icon => FontAwesome.Solid.Magnet;
|
||||
public override IconUsage? Icon => OsuIcon.ModMagnetised;
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override LocalisableString Description => "No need to chase the circles – your cursor is a magnet!";
|
||||
public override double ScoreMultiplier => 0.5;
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
@@ -23,6 +25,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public override string Name => "Repel";
|
||||
public override string Acronym => "RP";
|
||||
public override IconUsage? Icon => OsuIcon.ModRepel;
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override LocalisableString Description => "Hit objects run away!";
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
@@ -11,6 +13,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public override string Name => @"Single Tap";
|
||||
public override string Acronym => @"SG";
|
||||
public override IconUsage? Icon => OsuIcon.ModSingleTap;
|
||||
public override LocalisableString Description => @"You must only use one key!";
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAlternate) }).ToArray();
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
@@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public override string Name => "Spin In";
|
||||
public override string Acronym => "SI";
|
||||
public override IconUsage? Icon => FontAwesome.Solid.Undo;
|
||||
public override IconUsage? Icon => OsuIcon.ModSpinIn;
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override LocalisableString Description => "Circles spin in. No approach circles.";
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@@ -24,6 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public override string Name => @"Strict Tracking";
|
||||
public override string Acronym => @"ST";
|
||||
public override IconUsage? Icon => OsuIcon.ModStrictTracking;
|
||||
public override ModType Type => ModType.DifficultyIncrease;
|
||||
public override LocalisableString Description => @"Once you start a slider, follow precisely or get a miss.";
|
||||
public override double ScoreMultiplier => 1.0;
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override string Name => "Target Practice";
|
||||
public override string Acronym => "TP";
|
||||
public override ModType Type => ModType.Conversion;
|
||||
public override IconUsage? Icon => OsuIcon.ModTarget;
|
||||
public override IconUsage? Icon => OsuIcon.ModTargetPractice;
|
||||
public override LocalisableString Description => @"Practice keeping up with the beat of the song.";
|
||||
public override double ScoreMultiplier => 0.1;
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
using System;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
@@ -18,6 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public override string Name => "Traceable";
|
||||
public override string Acronym => "TC";
|
||||
public override IconUsage? Icon => OsuIcon.ModTraceable;
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override LocalisableString Description => "Put your faith in the approach circles...";
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
@@ -18,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public override string Name => "Transform";
|
||||
public override string Acronym => "TR";
|
||||
public override IconUsage? Icon => FontAwesome.Solid.ArrowsAlt;
|
||||
public override IconUsage? Icon => OsuIcon.ModTransform;
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override LocalisableString Description => "Everything rotates. EVERYTHING.";
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
@@ -7,6 +7,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
@@ -19,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public override string Name => "Wiggle";
|
||||
public override string Acronym => "WG";
|
||||
public override IconUsage? Icon => FontAwesome.Solid.Certificate;
|
||||
public override IconUsage? Icon => OsuIcon.ModWiggle;
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override LocalisableString Description => "They just won't stay still...";
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Taiko.Mods;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
using osuTK;
|
||||
@@ -12,6 +15,34 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneTaikoModFlashlight : TaikoModTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestAspectRatios([Values] bool withClassicMod)
|
||||
{
|
||||
if (withClassicMod)
|
||||
CreateModTest(new ModTestData { Mods = new Mod[] { new TaikoModFlashlight(), new TaikoModClassic() }, PassCondition = () => true });
|
||||
else
|
||||
CreateModTest(new ModTestData { Mod = new TaikoModFlashlight(), PassCondition = () => true });
|
||||
|
||||
AddStep("clear dim", () => LocalConfig.SetValue(OsuSetting.DimLevel, 0.0));
|
||||
|
||||
AddStep("reset", () => Stack.FillMode = FillMode.Stretch);
|
||||
AddStep("set to 16:9", () =>
|
||||
{
|
||||
Stack.FillAspectRatio = 16 / 9f;
|
||||
Stack.FillMode = FillMode.Fit;
|
||||
});
|
||||
AddStep("set to 4:3", () =>
|
||||
{
|
||||
Stack.FillAspectRatio = 4 / 3f;
|
||||
Stack.FillMode = FillMode.Fit;
|
||||
});
|
||||
AddSliderStep("aspect ratio", 0.01f, 5f, 1f, v =>
|
||||
{
|
||||
Stack.FillAspectRatio = v;
|
||||
Stack.FillMode = FillMode.Fit;
|
||||
});
|
||||
}
|
||||
|
||||
[TestCase(1f)]
|
||||
[TestCase(0.5f)]
|
||||
[TestCase(1.25f)]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
public override string Acronym => "CS";
|
||||
public override double ScoreMultiplier => 0.9;
|
||||
public override LocalisableString Description => "No more tricky speed changes!";
|
||||
public override IconUsage? Icon => FontAwesome.Solid.Equals;
|
||||
public override IconUsage? Icon => OsuIcon.ModConstantSpeed;
|
||||
public override ModType Type => ModType.Conversion;
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)
|
||||
|
||||
@@ -47,28 +47,15 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
this.taikoPlayfield = taikoPlayfield;
|
||||
|
||||
FlashlightSize = adjustSizeForPlayfieldAspectRatio(GetSize());
|
||||
FlashlightSize = new Vector2(0, GetSize());
|
||||
FlashlightSmoothness = 1.4f;
|
||||
|
||||
AddLayout(flashlightProperties);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the aspect ratio-adjusted size of the flashlight.
|
||||
/// This ensures that the size of the flashlight remains independent of taiko-specific aspect ratio adjustments.
|
||||
/// </summary>
|
||||
/// <param name="size">
|
||||
/// The size of the flashlight.
|
||||
/// The value provided here should always come from <see cref="ModFlashlight{T}.Flashlight.GetSize"/>.
|
||||
/// </param>
|
||||
private Vector2 adjustSizeForPlayfieldAspectRatio(float size)
|
||||
{
|
||||
return new Vector2(0, size * taikoPlayfield.Parent!.Scale.Y);
|
||||
}
|
||||
|
||||
protected override void UpdateFlashlightSize(float size)
|
||||
{
|
||||
this.TransformTo(nameof(FlashlightSize), adjustSizeForPlayfieldAspectRatio(size), FLASHLIGHT_FADE_DURATION);
|
||||
this.TransformTo(nameof(FlashlightSize), new Vector2(0, size), FLASHLIGHT_FADE_DURATION);
|
||||
}
|
||||
|
||||
protected override string FragmentShader => "CircularFlashlight";
|
||||
@@ -82,7 +69,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
FlashlightPosition = ToLocalSpace(taikoPlayfield.HitTarget.ScreenSpaceDrawQuad.Centre);
|
||||
|
||||
ClearTransforms(targetMember: nameof(FlashlightSize));
|
||||
FlashlightSize = adjustSizeForPlayfieldAspectRatio(GetSize());
|
||||
FlashlightSize = new Vector2(0, GetSize());
|
||||
|
||||
flashlightProperties.Validate();
|
||||
}
|
||||
|
||||
@@ -5,10 +5,12 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Taiko.Beatmaps;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
@@ -21,6 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
public override string Acronym => "SR";
|
||||
public override double ScoreMultiplier => 0.6;
|
||||
public override LocalisableString Description => "Simplify tricky rhythms!";
|
||||
public override IconUsage? Icon => OsuIcon.ModSimplifiedRhythm;
|
||||
public override ModType Type => ModType.DifficultyReduction;
|
||||
|
||||
[SettingSource("1/3 to 1/2 conversion", "Converts 1/3 patterns to 1/2 rhythm.")]
|
||||
|
||||
@@ -6,9 +6,11 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
@@ -24,6 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
public override string Name => @"Single Tap";
|
||||
public override string Acronym => @"SG";
|
||||
public override IconUsage? Icon => OsuIcon.ModSingleTap;
|
||||
public override LocalisableString Description => @"One key for dons, one key for kats.";
|
||||
|
||||
public override double ScoreMultiplier => 1.0;
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Taiko.Beatmaps;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
@@ -16,6 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
public override string Name => "Swap";
|
||||
public override string Acronym => "SW";
|
||||
public override LocalisableString Description => @"Dons become kats, kats become dons";
|
||||
public override IconUsage? Icon => OsuIcon.ModSwap;
|
||||
public override ModType Type => ModType.Conversion;
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModRandom)).ToArray();
|
||||
|
||||
@@ -66,6 +66,18 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
AddStep("toggle black background", () => blackBackground?.FadeTo(1 - blackBackground.Alpha, 300, Easing.OutQuint));
|
||||
|
||||
AddSliderStep("leaderboard width", 0, 800, 300, v =>
|
||||
{
|
||||
if (leaderboard.IsNotNull())
|
||||
leaderboard.Width = v;
|
||||
});
|
||||
|
||||
AddSliderStep("leaderboard height", 0, 1000, 300, v =>
|
||||
{
|
||||
if (leaderboard.IsNotNull())
|
||||
leaderboard.Height = v;
|
||||
});
|
||||
|
||||
AddSliderStep("set player score", 50, 1_000_000, 700_000, v => gameplayState.ScoreProcessor.TotalScore.Value = v);
|
||||
}
|
||||
|
||||
@@ -108,6 +120,45 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddUntilStep("wait for 1st spot", () => leaderboard.TrackedScore!.ScorePosition.Value, () => Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLongScores()
|
||||
{
|
||||
AddStep("set scores", () =>
|
||||
{
|
||||
var friend = new APIUser { Username = "Friend", Id = 1337 };
|
||||
|
||||
var api = (DummyAPIAccess)API;
|
||||
|
||||
api.Friends.Clear();
|
||||
api.Friends.Add(new APIRelation
|
||||
{
|
||||
Mutual = true,
|
||||
RelationType = RelationType.Friend,
|
||||
TargetID = friend.OnlineID,
|
||||
TargetUser = friend
|
||||
});
|
||||
|
||||
// this is dodgy but anything less dodgy is a lot of work
|
||||
((Bindable<LeaderboardScores?>)leaderboardManager.Scores).Value = LeaderboardScores.Success(new[]
|
||||
{
|
||||
new ScoreInfo { User = new APIUser { Username = "Top", Id = 2 }, TotalScore = 900_000_000, Accuracy = 0.99, MaxCombo = 999999 },
|
||||
new ScoreInfo { User = new APIUser { Username = "Second", Id = 14 }, TotalScore = 800_000_000, Accuracy = 0.9, MaxCombo = 888888 },
|
||||
new ScoreInfo { User = friend, TotalScore = 700_000_000, Accuracy = 0.88, MaxCombo = 777777 },
|
||||
}, 3, null);
|
||||
});
|
||||
|
||||
createLeaderboard();
|
||||
|
||||
AddStep("set score to 650k", () => gameplayState.ScoreProcessor.TotalScore.Value = 650_000_000);
|
||||
AddUntilStep("wait for 4th spot", () => leaderboard.TrackedScore!.ScorePosition.Value, () => Is.EqualTo(4));
|
||||
AddStep("set score to 750k", () => gameplayState.ScoreProcessor.TotalScore.Value = 750_000_000);
|
||||
AddUntilStep("wait for 3rd spot", () => leaderboard.TrackedScore!.ScorePosition.Value, () => Is.EqualTo(3));
|
||||
AddStep("set score to 850k", () => gameplayState.ScoreProcessor.TotalScore.Value = 850_000_000);
|
||||
AddUntilStep("wait for 2nd spot", () => leaderboard.TrackedScore!.ScorePosition.Value, () => Is.EqualTo(2));
|
||||
AddStep("set score to 950k", () => gameplayState.ScoreProcessor.TotalScore.Value = 950_000_000);
|
||||
AddUntilStep("wait for 1st spot", () => leaderboard.TrackedScore!.ScorePosition.Value, () => Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLayoutWithManyScores()
|
||||
{
|
||||
|
||||
@@ -24,6 +24,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
protected TestReplayPlayer Player = null!;
|
||||
|
||||
[Test]
|
||||
public void TestFailedBeatmapLoad()
|
||||
{
|
||||
loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo, withHitObjects: false));
|
||||
|
||||
AddUntilStep("wait for exit", () => Player.IsCurrentScreen());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPauseViaSpace()
|
||||
{
|
||||
|
||||
@@ -28,6 +28,7 @@ using osu.Game.Storyboards;
|
||||
using osu.Game.Tests.Beatmaps.IO;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
@@ -302,9 +303,69 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddWaitStep("wait a bit", 10);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Explicit("Test relies on timing of arriving frames to exercise assertions which doesn't work headless.")]
|
||||
public void TestMaximisedUserIsAudioSource()
|
||||
{
|
||||
start(new[] { PLAYER_1_ID, PLAYER_2_ID });
|
||||
loadSpectateScreen();
|
||||
|
||||
// With no frames, the synchronisation state will be TooFarAhead.
|
||||
// In this state, all players should be muted.
|
||||
assertMuted(PLAYER_1_ID, true);
|
||||
assertMuted(PLAYER_2_ID, true);
|
||||
|
||||
// Send frames for both players.
|
||||
sendFrames(PLAYER_1_ID, 20);
|
||||
sendFrames(PLAYER_2_ID, 40);
|
||||
|
||||
waitUntilRunning(PLAYER_1_ID);
|
||||
AddStep("maximise player 1", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getInstance(PLAYER_1_ID));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
assertMuted(PLAYER_1_ID, false);
|
||||
assertMuted(PLAYER_2_ID, true);
|
||||
|
||||
waitUntilPaused(PLAYER_1_ID);
|
||||
assertMuted(PLAYER_1_ID, false);
|
||||
assertMuted(PLAYER_2_ID, true);
|
||||
|
||||
AddStep("minimise player 1", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getInstance(PLAYER_1_ID));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
assertMuted(PLAYER_1_ID, true);
|
||||
assertMuted(PLAYER_2_ID, false);
|
||||
|
||||
AddStep("maximise player 2", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getInstance(PLAYER_2_ID));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
assertMuted(PLAYER_1_ID, true);
|
||||
assertMuted(PLAYER_2_ID, false);
|
||||
|
||||
waitUntilPaused(PLAYER_2_ID);
|
||||
sendFrames(PLAYER_1_ID, 60);
|
||||
|
||||
assertMuted(PLAYER_1_ID, true);
|
||||
assertMuted(PLAYER_2_ID, false);
|
||||
|
||||
AddStep("minimise player 2", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getInstance(PLAYER_2_ID));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
assertMuted(PLAYER_1_ID, false);
|
||||
assertMuted(PLAYER_2_ID, true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[FlakyTest]
|
||||
public void TestMostInSyncUserIsAudioSource()
|
||||
public void TestMostInSyncUserIsAudioSourceIfNoneMaximised()
|
||||
{
|
||||
start(new[] { PLAYER_1_ID, PLAYER_2_ID });
|
||||
loadSpectateScreen();
|
||||
|
||||
@@ -181,7 +181,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddUntilStep("beatmap in song select", () =>
|
||||
{
|
||||
var songSelect = (SoloSongSelect)Game.ScreenStack.CurrentScreen;
|
||||
return songSelect.ChildrenOfType<BeatmapCarousel>().Single().GetCarouselItems()!.Any(i => i.Model is BeatmapSetInfo bsi && bsi.MatchesOnlineID(getImport()));
|
||||
return songSelect.ChildrenOfType<BeatmapCarousel>().Single().GetCarouselItems()!.Any(i => i.Model is GroupedBeatmapSet gbs && gbs.BeatmapSet.MatchesOnlineID(getImport()));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -133,6 +133,8 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
{
|
||||
public Bindable<Vector2> AreaOffset { get; } = new Bindable<Vector2>();
|
||||
public Bindable<Vector2> AreaSize { get; } = new Bindable<Vector2>();
|
||||
public Bindable<Vector2> OutputAreaOffset { get; } = new Bindable<Vector2>();
|
||||
public Bindable<Vector2> OutputAreaSize { get; } = new Bindable<Vector2>();
|
||||
|
||||
public Bindable<float> Rotation { get; } = new Bindable<float>();
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
];
|
||||
|
||||
var results = await runGrouping(GroupMode.None, beatmapSets);
|
||||
Assert.That(results.Select(r => r.Model).OfType<BeatmapSetInfo>(), Is.EquivalentTo(beatmapSets));
|
||||
Assert.That(results.Select(r => r.Model).OfType<GroupedBeatmapSet>().Select(groupedSet => groupedSet.BeatmapSet), Is.EquivalentTo(beatmapSets));
|
||||
Assert.That(results.Select(r => r.Model).OfType<BeatmapInfo>(), Is.EquivalentTo(allBeatmaps));
|
||||
assertTotal(results, beatmapSets.Count + allBeatmaps.Length);
|
||||
}
|
||||
@@ -74,11 +74,11 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
addBeatmapSet(applyBeatmap('_'), beatmapSets, out var underscoreBeatmap);
|
||||
|
||||
var results = await runGrouping(mode, beatmapSets);
|
||||
assertGroup(results, 0, "0-9", new[] { fiveBeatmap, fourBeatmap }, ref total);
|
||||
assertGroup(results, 1, "A", new[] { aBeatmap }, ref total);
|
||||
assertGroup(results, 2, "F", new[] { fBeatmap }, ref total);
|
||||
assertGroup(results, 3, "Z", new[] { zBeatmap }, ref total);
|
||||
assertGroup(results, 4, "Other", new[] { dashBeatmap, underscoreBeatmap }, ref total);
|
||||
assertGroup(results, 0, "0-9", fiveBeatmap.Beatmaps.Concat(fourBeatmap.Beatmaps), ref total);
|
||||
assertGroup(results, 1, "A", aBeatmap.Beatmaps, ref total);
|
||||
assertGroup(results, 2, "F", fBeatmap.Beatmaps, ref total);
|
||||
assertGroup(results, 3, "Z", zBeatmap.Beatmaps, ref total);
|
||||
assertGroup(results, 4, "Other", dashBeatmap.Beatmaps.Concat(underscoreBeatmap.Beatmaps), ref total);
|
||||
assertTotal(results, total);
|
||||
}
|
||||
|
||||
@@ -115,12 +115,12 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
addBeatmapSet(s => s.DateAdded = DateTimeOffset.Now.AddMonths(-2).AddDays(-3), beatmapSets, out var twoMonthsAgoBeatmap);
|
||||
|
||||
var results = await runGrouping(GroupMode.DateAdded, beatmapSets);
|
||||
assertGroup(results, 0, "Today", new[] { todayBeatmap }, ref total);
|
||||
assertGroup(results, 1, "Yesterday", new[] { yesterdayBeatmap }, ref total);
|
||||
assertGroup(results, 2, "Last week", new[] { lastWeekBeatmap }, ref total);
|
||||
assertGroup(results, 3, "Last month", new[] { lastMonthBeatmap }, ref total);
|
||||
assertGroup(results, 4, "1 month ago", new[] { oneMonthAgoBeatmap }, ref total);
|
||||
assertGroup(results, 5, "2 months ago", new[] { twoMonthsAgoBeatmap }, ref total);
|
||||
assertGroup(results, 0, "Today", todayBeatmap.Beatmaps, ref total);
|
||||
assertGroup(results, 1, "Yesterday", yesterdayBeatmap.Beatmaps, ref total);
|
||||
assertGroup(results, 2, "Last week", lastWeekBeatmap.Beatmaps, ref total);
|
||||
assertGroup(results, 3, "Last month", lastMonthBeatmap.Beatmaps, ref total);
|
||||
assertGroup(results, 4, "1 month ago", oneMonthAgoBeatmap.Beatmaps, ref total);
|
||||
assertGroup(results, 5, "2 months ago", twoMonthsAgoBeatmap.Beatmaps, ref total);
|
||||
assertTotal(results, total);
|
||||
}
|
||||
|
||||
@@ -139,13 +139,13 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
addBeatmapSet(applyLastPlayed(null), beatmapSets, out var neverBeatmap);
|
||||
|
||||
var results = await runGrouping(GroupMode.LastPlayed, beatmapSets);
|
||||
assertGroup(results, 0, "Today", new[] { todayBeatmap }, ref total);
|
||||
assertGroup(results, 1, "Yesterday", new[] { yesterdayBeatmap }, ref total);
|
||||
assertGroup(results, 2, "Last week", new[] { lastWeekBeatmap }, ref total);
|
||||
assertGroup(results, 3, "Last month", new[] { lastMonthBeatmap }, ref total);
|
||||
assertGroup(results, 4, "1 month ago", new[] { oneMonthAgoBeatmap }, ref total);
|
||||
assertGroup(results, 5, "2 months ago", new[] { twoMonthsBeatmap }, ref total);
|
||||
assertGroup(results, 6, "Never", new[] { neverBeatmap }, ref total);
|
||||
assertGroup(results, 0, "Today", todayBeatmap.Beatmaps, ref total);
|
||||
assertGroup(results, 1, "Yesterday", yesterdayBeatmap.Beatmaps, ref total);
|
||||
assertGroup(results, 2, "Last week", lastWeekBeatmap.Beatmaps, ref total);
|
||||
assertGroup(results, 3, "Last month", lastMonthBeatmap.Beatmaps, ref total);
|
||||
assertGroup(results, 4, "1 month ago", oneMonthAgoBeatmap.Beatmaps, ref total);
|
||||
assertGroup(results, 5, "2 months ago", twoMonthsBeatmap.Beatmaps, ref total);
|
||||
assertGroup(results, 6, "Never", neverBeatmap.Beatmaps, ref total);
|
||||
assertTotal(results, total);
|
||||
}
|
||||
|
||||
@@ -162,7 +162,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
var results = await runGrouping(GroupMode.LastPlayed, beatmapSets);
|
||||
int total = 0;
|
||||
|
||||
assertGroup(results, 0, "Today", new[] { set }, ref total);
|
||||
assertGroup(results, 0, "Today", [set.Beatmaps[2]], ref total);
|
||||
assertGroup(results, 1, "Never", [set.Beatmaps[0], set.Beatmaps[1]], ref total);
|
||||
assertTotal(results, total);
|
||||
}
|
||||
|
||||
@@ -176,8 +177,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
var results = await runGrouping(GroupMode.LastPlayed, beatmapSets);
|
||||
int total = 0;
|
||||
|
||||
assertGroup(results, 0, "Over 5 months ago", new[] { overFiveMonthsBeatmap }, ref total);
|
||||
assertGroup(results, 1, "Never", new[] { neverBeatmap }, ref total);
|
||||
assertGroup(results, 0, "Over 5 months ago", overFiveMonthsBeatmap.Beatmaps, ref total);
|
||||
assertGroup(results, 1, "Never", neverBeatmap.Beatmaps, ref total);
|
||||
assertTotal(results, total);
|
||||
}
|
||||
|
||||
@@ -207,14 +208,14 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
addBeatmapSet(s => s.Status = BeatmapOnlineStatus.LocallyModified, beatmapSets, out var localBeatmap);
|
||||
|
||||
var results = await runGrouping(GroupMode.RankedStatus, beatmapSets);
|
||||
assertGroup(results, 0, "Ranked", new[] { rankedBeatmap, approvedBeatmap }, ref total);
|
||||
assertGroup(results, 1, "Qualified", new[] { qualifiedBeatmap }, ref total);
|
||||
assertGroup(results, 2, "WIP", new[] { wipBeatmap }, ref total);
|
||||
assertGroup(results, 3, "Pending", new[] { pendingBeatmap }, ref total);
|
||||
assertGroup(results, 4, "Graveyard", new[] { graveyardBeatmap }, ref total);
|
||||
assertGroup(results, 5, "Local", new[] { localBeatmap }, ref total);
|
||||
assertGroup(results, 6, "Unknown", new[] { noneBeatmap }, ref total);
|
||||
assertGroup(results, 7, "Loved", new[] { lovedBeatmap }, ref total);
|
||||
assertGroup(results, 0, "Ranked", rankedBeatmap.Beatmaps.Concat(approvedBeatmap.Beatmaps), ref total);
|
||||
assertGroup(results, 1, "Qualified", qualifiedBeatmap.Beatmaps, ref total);
|
||||
assertGroup(results, 2, "WIP", wipBeatmap.Beatmaps, ref total);
|
||||
assertGroup(results, 3, "Pending", pendingBeatmap.Beatmaps, ref total);
|
||||
assertGroup(results, 4, "Graveyard", graveyardBeatmap.Beatmaps, ref total);
|
||||
assertGroup(results, 5, "Local", localBeatmap.Beatmaps, ref total);
|
||||
assertGroup(results, 6, "Unknown", noneBeatmap.Beatmaps, ref total);
|
||||
assertGroup(results, 7, "Loved", lovedBeatmap.Beatmaps, ref total);
|
||||
assertTotal(results, total);
|
||||
}
|
||||
|
||||
@@ -240,12 +241,12 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
addBeatmapSet(applyBPM(330), beatmapSets, out var beatmap330);
|
||||
|
||||
var results = await runGrouping(GroupMode.BPM, beatmapSets);
|
||||
assertGroup(results, 0, "Under 60 BPM", new[] { beatmap30 }, ref total);
|
||||
assertGroup(results, 1, "60 - 70 BPM", new[] { beatmap59, beatmap60 }, ref total);
|
||||
assertGroup(results, 2, "90 - 100 BPM", new[] { beatmap90, beatmap95 }, ref total);
|
||||
assertGroup(results, 3, "270 - 280 BPM", new[] { beatmap269, beatmap270 }, ref total);
|
||||
assertGroup(results, 4, "290 - 300 BPM", new[] { beatmap299 }, ref total);
|
||||
assertGroup(results, 5, "Over 300 BPM", new[] { beatmap300, beatmap330 }, ref total);
|
||||
assertGroup(results, 0, "Under 60 BPM", beatmap30.Beatmaps, ref total);
|
||||
assertGroup(results, 1, "60 - 70 BPM", (beatmap59.Beatmaps.Concat(beatmap60.Beatmaps)), ref total);
|
||||
assertGroup(results, 2, "90 - 100 BPM", (beatmap90.Beatmaps.Concat(beatmap95.Beatmaps)), ref total);
|
||||
assertGroup(results, 3, "270 - 280 BPM", (beatmap269.Beatmaps.Concat(beatmap270.Beatmaps)), ref total);
|
||||
assertGroup(results, 4, "290 - 300 BPM", beatmap299.Beatmaps, ref total);
|
||||
assertGroup(results, 5, "Over 300 BPM", (beatmap300.Beatmaps.Concat(beatmap330.Beatmaps)), ref total);
|
||||
assertTotal(results, total);
|
||||
}
|
||||
|
||||
@@ -272,10 +273,10 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
addBeatmapSet(applyStars(7), beatmapSets, out var beatmap7);
|
||||
|
||||
var results = await runGrouping(GroupMode.Difficulty, beatmapSets);
|
||||
assertGroup(results, 0, "Below 1 Star", new[] { beatmapBelow1 }, ref total);
|
||||
assertGroup(results, 1, "1 Star", new[] { beatmapAbove1, beatmapAlmost2 }, ref total);
|
||||
assertGroup(results, 2, "2 Stars", new[] { beatmap2, beatmapAbove2 }, ref total);
|
||||
assertGroup(results, 3, "7 Stars", new[] { beatmap7 }, ref total);
|
||||
assertGroup(results, 0, "Below 1 Star", beatmapBelow1.Beatmaps, ref total);
|
||||
assertGroup(results, 1, "1 Star", (beatmapAbove1.Beatmaps.Concat(beatmapAlmost2.Beatmaps)), ref total);
|
||||
assertGroup(results, 2, "2 Stars", (beatmap2.Beatmaps.Concat(beatmapAbove2.Beatmaps)), ref total);
|
||||
assertGroup(results, 3, "7 Stars", beatmap7.Beatmaps, ref total);
|
||||
assertTotal(results, total);
|
||||
}
|
||||
|
||||
@@ -304,11 +305,11 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
addBeatmapSet(applyLength(630_000), beatmapSets, out var beatmap10Min30Sec);
|
||||
|
||||
var results = await runGrouping(GroupMode.Length, beatmapSets);
|
||||
assertGroup(results, 0, "1 minute or less", new[] { beatmap30Sec, beatmap1Min }, ref total);
|
||||
assertGroup(results, 1, "2 minutes or less", new[] { beatmap1Min30Sec, beatmap2Min }, ref total);
|
||||
assertGroup(results, 2, "5 minutes or less", new[] { beatmap5Min }, ref total);
|
||||
assertGroup(results, 3, "10 minutes or less", new[] { beatmap6Min, beatmap10Min }, ref total);
|
||||
assertGroup(results, 4, "Over 10 minutes", new[] { beatmap10Min30Sec }, ref total);
|
||||
assertGroup(results, 0, "1 minute or less", (beatmap30Sec.Beatmaps.Concat(beatmap1Min.Beatmaps)), ref total);
|
||||
assertGroup(results, 1, "2 minutes or less", (beatmap1Min30Sec.Beatmaps.Concat(beatmap2Min.Beatmaps)), ref total);
|
||||
assertGroup(results, 2, "5 minutes or less", beatmap5Min.Beatmaps, ref total);
|
||||
assertGroup(results, 3, "10 minutes or less", (beatmap6Min.Beatmaps.Concat(beatmap10Min.Beatmaps)), ref total);
|
||||
assertGroup(results, 4, "Over 10 minutes", beatmap10Min30Sec.Beatmaps, ref total);
|
||||
assertTotal(results, total);
|
||||
}
|
||||
|
||||
@@ -334,10 +335,10 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
addBeatmapSet(s => s.DateRanked = null, beatmapSets, out var beatmapUnranked);
|
||||
|
||||
var results = await runGrouping(GroupMode.DateRanked, beatmapSets);
|
||||
assertGroup(results, 0, "2025", new[] { beatmap2025 }, ref total);
|
||||
assertGroup(results, 1, "2010", new[] { beatmap2010 }, ref total);
|
||||
assertGroup(results, 2, "2007", new[] { beatmapOct2007, beatmapDec2007 }, ref total);
|
||||
assertGroup(results, 3, "Unranked", new[] { beatmapUnranked }, ref total);
|
||||
assertGroup(results, 0, "2025", beatmap2025.Beatmaps, ref total);
|
||||
assertGroup(results, 1, "2010", beatmap2010.Beatmaps, ref total);
|
||||
assertGroup(results, 2, "2007", (beatmapOct2007.Beatmaps.Concat(beatmapDec2007.Beatmaps)), ref total);
|
||||
assertGroup(results, 3, "Unranked", beatmapUnranked.Beatmaps, ref total);
|
||||
assertTotal(results, total);
|
||||
}
|
||||
|
||||
@@ -357,9 +358,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
addBeatmapSet(s => s.Beatmaps[0].Metadata.Source = string.Empty, beatmapSets, out var beatmapUnsourced);
|
||||
|
||||
var results = await runGrouping(GroupMode.Source, beatmapSets);
|
||||
assertGroup(results, 0, "Cool Game", new[] { beatmapCoolGame, beatmapCoolGameB }, ref total);
|
||||
assertGroup(results, 1, "Nice Movie", new[] { beatmapNiceMovie }, ref total);
|
||||
assertGroup(results, 2, "Unsourced", new[] { beatmapUnsourced }, ref total);
|
||||
assertGroup(results, 0, "Cool Game", (beatmapCoolGame.Beatmaps.Concat(beatmapCoolGameB.Beatmaps)), ref total);
|
||||
assertGroup(results, 1, "Nice Movie", beatmapNiceMovie.Beatmaps, ref total);
|
||||
assertGroup(results, 2, "Unsourced", beatmapUnsourced.Beatmaps, ref total);
|
||||
assertTotal(results, total);
|
||||
}
|
||||
|
||||
@@ -375,7 +376,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
return await groupingFilter.Run(beatmapSets.SelectMany(s => s.Beatmaps.Select(b => new CarouselItem(b))).ToList(), CancellationToken.None);
|
||||
}
|
||||
|
||||
private static void assertGroup(List<CarouselItem> items, int index, string expectedTitle, IEnumerable<BeatmapSetInfo> expectedBeatmapSets, ref int totalItems)
|
||||
private static void assertGroup(List<CarouselItem> items, int index, string expectedTitle, IEnumerable<BeatmapInfo> expectedBeatmaps, ref int totalItems)
|
||||
{
|
||||
var groupItem = items.Where(i => i.Model is GroupDefinition).ElementAtOrDefault(index);
|
||||
|
||||
@@ -390,7 +391,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
var groupModel = (GroupDefinition)groupItem.Model;
|
||||
|
||||
Assert.That(groupModel.Title, Is.EqualTo(expectedTitle));
|
||||
Assert.That(itemsInGroup.Select(i => i.Model).OfType<BeatmapInfo>(), Is.EquivalentTo(expectedBeatmapSets.SelectMany(bs => bs.Beatmaps)));
|
||||
Assert.That(itemsInGroup.Select(i => i.Model).OfType<BeatmapInfo>(), Is.EquivalentTo(expectedBeatmaps));
|
||||
|
||||
totalItems += itemsInGroup.Count() + 1;
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
// Using groupingFilter.SetItems.Count alone doesn't work.
|
||||
// When sorting by difficulty, there can be more than one set panel for the same set displayed.
|
||||
return groupingFilter.SetItems.Sum(s => s.Value.Count(i => i.Model is BeatmapSetInfo));
|
||||
return groupingFilter.SetItems.Sum(s => s.Value.Count(i => i.Model is GroupedBeatmapSet));
|
||||
}, () => Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
@@ -440,7 +440,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
public BeatmapInfo? SelectedBeatmapInfo => CurrentSelection as BeatmapInfo;
|
||||
public BeatmapSetInfo? SelectedBeatmapSet => SelectedBeatmapInfo?.BeatmapSet;
|
||||
|
||||
public new BeatmapSetInfo? ExpandedBeatmapSet => base.ExpandedBeatmapSet;
|
||||
public new GroupedBeatmapSet? ExpandedBeatmapSet => base.ExpandedBeatmapSet;
|
||||
public new GroupDefinition? ExpandedGroup => base.ExpandedGroup;
|
||||
|
||||
public TestBeatmapCarousel()
|
||||
|
||||
@@ -22,8 +22,6 @@ using osu.Game.Overlays.Toolbar;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Footer;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
@@ -43,9 +41,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
protected Screens.SelectV2.SongSelect SongSelect { get; private set; } = null!;
|
||||
protected BeatmapCarousel Carousel => SongSelect.ChildrenOfType<BeatmapCarousel>().Single();
|
||||
|
||||
[Cached]
|
||||
protected readonly ScreenFooter Footer;
|
||||
|
||||
[Cached]
|
||||
private readonly OsuLogo logo;
|
||||
|
||||
@@ -72,10 +67,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
State = { Value = Visibility.Visible },
|
||||
},
|
||||
Footer = new ScreenFooter
|
||||
{
|
||||
BackButtonPressed = () => Stack.CurrentScreen.Exit(),
|
||||
},
|
||||
logo = new OsuLogo
|
||||
{
|
||||
Alpha = 0f,
|
||||
@@ -111,14 +102,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
Add(beatmapStore);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Stack.ScreenPushed += updateFooter;
|
||||
Stack.ScreenExited += updateFooter;
|
||||
}
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
@@ -207,38 +190,5 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
}
|
||||
|
||||
protected void WaitForSuspension() => AddUntilStep("wait for not current", () => !SongSelect.AsNonNull().IsCurrentScreen());
|
||||
|
||||
private void updateFooter(IScreen? _, IScreen? newScreen)
|
||||
{
|
||||
if (newScreen is OsuScreen osuScreen && osuScreen.ShowFooter)
|
||||
{
|
||||
Footer.Show();
|
||||
|
||||
if (osuScreen.IsLoaded)
|
||||
updateFooterButtons();
|
||||
else
|
||||
{
|
||||
// ensure the current buttons are immediately disabled on screen change (so they can't be pressed).
|
||||
Footer.SetButtons(Array.Empty<ScreenFooterButton>());
|
||||
|
||||
osuScreen.OnLoadComplete += _ => updateFooterButtons();
|
||||
}
|
||||
|
||||
void updateFooterButtons()
|
||||
{
|
||||
var buttons = osuScreen.CreateFooterButtons();
|
||||
|
||||
osuScreen.LoadComponentsAgainstScreenDependencies(buttons);
|
||||
|
||||
Footer.SetButtons(buttons);
|
||||
Footer.Show();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Footer.Hide();
|
||||
Footer.SetButtons(Array.Empty<ScreenFooterButton>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,7 +396,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
ApplyToFilterAndWaitForFilter("filter first away", c => c.UserStarDifficulty.Min = 3);
|
||||
|
||||
SelectNextPanel();
|
||||
AddAssert("keyboard selected is first set", () => GetKeyboardSelectedPanel()?.Item?.Model, () => Is.EqualTo(BeatmapSets.First()));
|
||||
AddAssert("keyboard selected is first set",
|
||||
() => (GetKeyboardSelectedPanel()?.Item?.Model as GroupedBeatmapSet)?.BeatmapSet,
|
||||
() => Is.EqualTo(BeatmapSets.First()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -413,7 +415,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
ApplyToFilterAndWaitForFilter("filter first away", c => c.UserStarDifficulty.Min = 3);
|
||||
|
||||
SelectPrevPanel();
|
||||
AddAssert("keyboard selected is last set", () => GetKeyboardSelectedPanel()?.Item?.Model, () => Is.EqualTo(BeatmapSets.Last()));
|
||||
AddAssert("keyboard selected is last set",
|
||||
() => (GetKeyboardSelectedPanel()?.Item?.Model as GroupedBeatmapSet)?.BeatmapSet,
|
||||
() => Is.EqualTo(BeatmapSets.Last()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -428,7 +432,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
ApplyToFilterAndWaitForFilter("filter last set away", c => c.SearchText = BeatmapSets.First().Metadata.Title);
|
||||
|
||||
SelectPrevPanel();
|
||||
AddAssert("keyboard selected is first set", () => GetKeyboardSelectedPanel()?.Item?.Model, () => Is.EqualTo(BeatmapSets.First()));
|
||||
AddAssert("keyboard selected is first set",
|
||||
() => (GetKeyboardSelectedPanel()?.Item?.Model as GroupedBeatmapSet)?.BeatmapSet,
|
||||
() => Is.EqualTo(BeatmapSets.First()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -444,7 +450,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
// Single result is automatically selected for us, so we iterate once backwards to the set header.
|
||||
SelectPrevPanel();
|
||||
AddAssert("keyboard selected is second set", () => GetKeyboardSelectedPanel()?.Item?.Model, () => Is.EqualTo(BeatmapSets.Last()));
|
||||
AddAssert("keyboard selected is second set",
|
||||
() => (GetKeyboardSelectedPanel()?.Item?.Model as GroupedBeatmapSet)?.BeatmapSet,
|
||||
() => Is.EqualTo(BeatmapSets.Last()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneBeatmapCarouselSetsSplitApart : BeatmapCarouselTestScene
|
||||
{
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
RemoveAllBeatmaps();
|
||||
CreateCarousel();
|
||||
|
||||
SortAndGroupBy(SortMode.Title, GroupMode.Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSetTraversal()
|
||||
{
|
||||
AddBeatmaps(3, splitApart: true);
|
||||
AddBeatmaps(3, splitApart: false);
|
||||
WaitForDrawablePanels();
|
||||
|
||||
SelectNextSet();
|
||||
WaitForSetSelection(set: 0, diff: 0);
|
||||
|
||||
SelectNextSet();
|
||||
WaitForSetSelection(set: 1, diff: 0);
|
||||
|
||||
SelectPrevSet();
|
||||
WaitForSetSelection(set: 0, diff: 0);
|
||||
|
||||
SelectPrevSet();
|
||||
WaitForSetSelection(set: 5, diff: 0);
|
||||
|
||||
SelectPrevSet();
|
||||
SelectPrevSet();
|
||||
SelectPrevSet();
|
||||
WaitForSetSelection(set: 2, diff: 4);
|
||||
AddAssert("only two beatmap panels visible", () => GetVisiblePanels<PanelBeatmap>().Count(), () => Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBeatmapTraversal()
|
||||
{
|
||||
AddBeatmaps(3, splitApart: true);
|
||||
AddBeatmaps(3, splitApart: false);
|
||||
WaitForDrawablePanels();
|
||||
|
||||
SelectNextSet();
|
||||
WaitForSetSelection(set: 0, diff: 0);
|
||||
|
||||
SelectNextPanel();
|
||||
WaitForSetSelection(set: 0, diff: 1);
|
||||
|
||||
SelectNextPanel(); // header of set 1 in group 0
|
||||
Select();
|
||||
WaitForSetSelection(set: 1, diff: 0);
|
||||
|
||||
SelectPrevPanel(); // header of set 1 in group 0
|
||||
SelectPrevPanel(); // header of set 0 in group 0
|
||||
Select();
|
||||
WaitForSetSelection(set: 0, diff: 0);
|
||||
|
||||
SelectPrevPanel(); // header of set 0 in group 0
|
||||
SelectPrevPanel(); // header of group 0
|
||||
SelectPrevPanel(); // header of group 2
|
||||
Select();
|
||||
SelectNextPanel(); // header of set 0 in group 2
|
||||
Select();
|
||||
WaitForSetSelection(set: 0, diff: 4);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRandomStaysInGroup()
|
||||
{
|
||||
AddBeatmaps(2, splitApart: false);
|
||||
AddBeatmaps(1, splitApart: true);
|
||||
WaitForDrawablePanels();
|
||||
|
||||
SelectPrevSet();
|
||||
SelectPrevSet();
|
||||
WaitForSetSelection(set: 1);
|
||||
WaitForExpandedGroup(2);
|
||||
|
||||
AddStep("select next random", () => Carousel.NextRandom());
|
||||
WaitForExpandedGroup(2);
|
||||
AddStep("select next random", () => Carousel.NextRandom());
|
||||
WaitForExpandedGroup(2);
|
||||
}
|
||||
|
||||
protected void AddBeatmaps(int count, bool splitApart) => AddStep($"add {count} beatmaps ({(splitApart ? "" : "not ")}split apart)", () =>
|
||||
{
|
||||
var beatmapSets = new List<BeatmapSetInfo>();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var beatmapSet = CreateTestBeatmapSetInfo(6, false);
|
||||
|
||||
for (int j = 0; j < beatmapSet.Beatmaps.Count; j++)
|
||||
{
|
||||
beatmapSet.Beatmaps[j].Length = splitApart ? 30_000 * (j + 1) : 180_000;
|
||||
}
|
||||
|
||||
beatmapSets.Add(beatmapSet);
|
||||
}
|
||||
|
||||
BeatmapSets.AddRange(beatmapSets);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -22,12 +22,16 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
private BeatmapSetInfo baseTestBeatmap = null!;
|
||||
|
||||
private const int initial_filter_count = 3;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
RemoveAllBeatmaps();
|
||||
CreateCarousel();
|
||||
WaitForFiltering();
|
||||
AddBeatmaps(1, 3);
|
||||
WaitForFiltering();
|
||||
AddStep("generate and add test beatmap", () =>
|
||||
{
|
||||
baseTestBeatmap = TestResources.CreateTestBeatmapSetInfo(3);
|
||||
@@ -42,8 +46,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
b.Metadata = metadata;
|
||||
BeatmapSets.Add(baseTestBeatmap);
|
||||
});
|
||||
|
||||
WaitForFiltering();
|
||||
|
||||
AddAssert("filter count correct", () => Carousel.FilterCount, () => Is.EqualTo(initial_filter_count));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -81,12 +86,18 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
AddUntilStep("is scrolled to end", () => Carousel.ChildrenOfType<UserTrackingScrollContainer>().Single().IsScrolledToEnd());
|
||||
|
||||
updateBeatmap(b => b.Metadata = new BeatmapMetadata
|
||||
updateBeatmap(b =>
|
||||
{
|
||||
Artist = "updated test",
|
||||
Title = $"beatmap {RNG.Next().ToString()}"
|
||||
// hash will be updated when important metadata changes, such as title, difficulty, author etc.
|
||||
b.Hash = "new hash";
|
||||
b.Metadata = new BeatmapMetadata
|
||||
{
|
||||
Artist = "updated test",
|
||||
Title = $"beatmap {RNG.Next().ToString()}"
|
||||
};
|
||||
});
|
||||
|
||||
assertDidFilter();
|
||||
WaitForFiltering();
|
||||
|
||||
AddAssert("scroll is still at end", () => Carousel.ChildrenOfType<UserTrackingScrollContainer>().Single().IsScrolledToEnd());
|
||||
@@ -113,8 +124,14 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
AddStep("find panel", () => panel = Carousel.ChildrenOfType<PanelBeatmapSet>().Single(p => p.ChildrenOfType<OsuSpriteText>().Any(t => t.Text.ToString() == "beatmap")));
|
||||
|
||||
updateBeatmap(b => b.Metadata = metadata);
|
||||
updateBeatmap(b =>
|
||||
{
|
||||
b.Metadata = metadata;
|
||||
// hash will be updated when important metadata changes, such as title, difficulty, author etc.
|
||||
b.Hash = "new hash";
|
||||
});
|
||||
|
||||
assertDidFilter();
|
||||
WaitForFiltering();
|
||||
|
||||
AddAssert("drawables unchanged", () => Carousel.ChildrenOfType<Panel>(), () => Is.EqualTo(originalDrawables));
|
||||
@@ -123,7 +140,41 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSelectionHeld()
|
||||
public void TestOnlineStatusUpdated()
|
||||
{
|
||||
List<Panel> originalDrawables = new List<Panel>();
|
||||
|
||||
AddStep("store drawable references", () =>
|
||||
{
|
||||
originalDrawables.Clear();
|
||||
originalDrawables.AddRange(Carousel.ChildrenOfType<Panel>().ToList());
|
||||
});
|
||||
|
||||
updateBeatmap(b => b.Status = BeatmapOnlineStatus.Graveyard);
|
||||
|
||||
assertDidFilter();
|
||||
WaitForFiltering();
|
||||
|
||||
AddAssert("drawables unchanged", () => Carousel.ChildrenOfType<Panel>(), () => Is.EqualTo(originalDrawables));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoUpdateTriggeredOnUserTagsChange()
|
||||
{
|
||||
var metadata = new BeatmapMetadata
|
||||
{
|
||||
Artist = "updated test",
|
||||
Title = "new beatmap title",
|
||||
UserTags = { "hi" }
|
||||
};
|
||||
|
||||
updateBeatmap(b => b.Metadata = metadata);
|
||||
assertDidNotFilter();
|
||||
}
|
||||
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void TestSelectionHeld(bool hashChanged)
|
||||
{
|
||||
SelectNextSet();
|
||||
|
||||
@@ -131,7 +182,17 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
|
||||
AddAssert("visible panel is updateable beatmap", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
|
||||
|
||||
updateBeatmap();
|
||||
updateBeatmap(b =>
|
||||
{
|
||||
if (hashChanged)
|
||||
b.Hash = "new hash";
|
||||
});
|
||||
|
||||
if (hashChanged)
|
||||
assertDidFilter();
|
||||
else
|
||||
assertDidNotFilter();
|
||||
|
||||
WaitForFiltering();
|
||||
|
||||
AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
|
||||
@@ -148,6 +209,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
AddAssert("visible panel is updateable beatmap", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
|
||||
|
||||
updateBeatmap(b => b.DifficultyName = "new name");
|
||||
assertDidFilter();
|
||||
WaitForFiltering();
|
||||
|
||||
AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
|
||||
@@ -164,6 +226,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
AddAssert("visible panel is updateable beatmap", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
|
||||
|
||||
updateBeatmap(b => b.OnlineID = b.OnlineID + 1);
|
||||
assertDidFilter();
|
||||
WaitForFiltering();
|
||||
|
||||
AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
|
||||
@@ -339,6 +402,10 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
AddAssert("Order didn't change", () => Carousel.PostFilterBeatmaps.Select(b => b.ID), () => Is.EqualTo(originalOrder));
|
||||
}
|
||||
|
||||
private void assertDidFilter() => AddAssert("did filter", () => Carousel.FilterCount, () => Is.EqualTo(initial_filter_count + 1));
|
||||
|
||||
private void assertDidNotFilter() => AddAssert("did not filter", () => Carousel.FilterCount, () => Is.EqualTo(initial_filter_count));
|
||||
|
||||
private void updateBeatmap(Action<BeatmapInfo>? updateBeatmap = null, Action<BeatmapSetInfo>? updateSet = null)
|
||||
{
|
||||
AddStep("update beatmap with different reference", () =>
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -42,7 +43,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
private DialogOverlay dialogOverlay = null!;
|
||||
|
||||
private LeaderboardManager leaderboardManager = null!;
|
||||
private RealmPopulatingOnlineLookupSource lookupSource = null!;
|
||||
|
||||
private readonly IBindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?> onlineLookupResult = new Bindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?>();
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
{
|
||||
@@ -52,7 +54,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
|
||||
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, API));
|
||||
dependencies.Cache(leaderboardManager = new LeaderboardManager());
|
||||
dependencies.Cache(lookupSource = new RealmPopulatingOnlineLookupSource());
|
||||
dependencies.CacheAs(onlineLookupResult);
|
||||
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
@@ -68,7 +70,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
});
|
||||
|
||||
LoadComponent(leaderboardManager);
|
||||
LoadComponent(lookupSource);
|
||||
|
||||
Child = contentContainer = new OsuContextMenuContainer
|
||||
{
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
|
||||
@@ -18,64 +17,25 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneBeatmapMetadataWedge : SongSelectComponentsTestScene
|
||||
{
|
||||
private APIBeatmapSet? currentOnlineSet;
|
||||
|
||||
private BeatmapMetadataWedge wedge = null!;
|
||||
|
||||
[Cached(typeof(IBindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?>))]
|
||||
private Bindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?> onlineLookupResult = new Bindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?>();
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
var lookupSource = new RealmPopulatingOnlineLookupSource();
|
||||
Child = new DependencyProvidingContainer
|
||||
Child = wedge = new BeatmapMetadataWedge
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies = [(typeof(RealmPopulatingOnlineLookupSource), lookupSource)],
|
||||
Children =
|
||||
[
|
||||
lookupSource,
|
||||
wedge = new BeatmapMetadataWedge
|
||||
{
|
||||
State = { Value = Visibility.Visible },
|
||||
}
|
||||
]
|
||||
State = { Value = Visibility.Visible },
|
||||
};
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
AddStep("register request handling", () =>
|
||||
{
|
||||
((DummyAPIAccess)API).HandleRequest = request =>
|
||||
{
|
||||
switch (request)
|
||||
{
|
||||
case GetBeatmapSetRequest set:
|
||||
if (set.ID == currentOnlineSet?.OnlineID)
|
||||
{
|
||||
set.TriggerSuccess(currentOnlineSet);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestShowHide()
|
||||
{
|
||||
AddStep("all metrics", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
currentOnlineSet = onlineSet;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddStep("all metrics", () => (Beatmap.Value, onlineLookupResult.Value) = createTestBeatmap());
|
||||
|
||||
AddStep("hide wedge", () => wedge.Hide());
|
||||
AddStep("show wedge", () => wedge.Show());
|
||||
@@ -84,67 +44,63 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
[Test]
|
||||
public void TestVariousMetrics()
|
||||
{
|
||||
AddStep("all metrics", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
currentOnlineSet = onlineSet;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddStep("all metrics", () => (Beatmap.Value, onlineLookupResult.Value) = createTestBeatmap());
|
||||
|
||||
AddStep("null beatmap", () => Beatmap.SetDefault());
|
||||
AddStep("no source", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
var (working, online) = createTestBeatmap();
|
||||
|
||||
working.Metadata.Source = string.Empty;
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
onlineLookupResult.Value = online;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddStep("no success rate", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
var (working, online) = createTestBeatmap();
|
||||
|
||||
onlineSet.Beatmaps.Single().PlayCount = 0;
|
||||
onlineSet.Beatmaps.Single().PassCount = 0;
|
||||
online.Result!.Beatmaps.Single().PlayCount = 0;
|
||||
online.Result!.Beatmaps.Single().PassCount = 0;
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
onlineLookupResult.Value = online;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddStep("no user ratings", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
var (working, online) = createTestBeatmap();
|
||||
|
||||
onlineSet.Ratings = Array.Empty<int>();
|
||||
online.Result!.Ratings = Array.Empty<int>();
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
onlineLookupResult.Value = online;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddStep("no fail times", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
var (working, online) = createTestBeatmap();
|
||||
|
||||
onlineSet.Beatmaps.Single().FailTimes = null;
|
||||
online.Result!.Beatmaps.Single().FailTimes = null;
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
onlineLookupResult.Value = online;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddStep("no metrics", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
var (working, online) = createTestBeatmap();
|
||||
|
||||
onlineSet.Ratings = Array.Empty<int>();
|
||||
onlineSet.Beatmaps.Single().FailTimes = null;
|
||||
online.Result!.Ratings = Array.Empty<int>();
|
||||
online.Result!.Beatmaps.Single().FailTimes = null;
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
onlineLookupResult.Value = online;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddStep("local beatmap", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
var (working, _) = createTestBeatmap();
|
||||
|
||||
working.BeatmapInfo.OnlineID = 0;
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
onlineLookupResult.Value = null;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
}
|
||||
@@ -154,16 +110,16 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
AddStep("long text", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
var (working, online) = createTestBeatmap();
|
||||
|
||||
working.BeatmapInfo.Metadata.Author = new RealmUser { Username = "Verrrrryyyy llooonngggggg author" };
|
||||
working.BeatmapInfo.Metadata.Source = "Verrrrryyyy llooonngggggg source";
|
||||
working.BeatmapInfo.Metadata.Tags = string.Join(' ', Enumerable.Repeat(working.BeatmapInfo.Metadata.Tags, 3));
|
||||
onlineSet.Genre = new BeatmapSetOnlineGenre { Id = 12, Name = "Verrrrryyyy llooonngggggg genre" };
|
||||
onlineSet.Language = new BeatmapSetOnlineLanguage { Id = 12, Name = "Verrrrryyyy llooonngggggg language" };
|
||||
onlineSet.Beatmaps.Single().TopTags = Enumerable.Repeat(onlineSet.Beatmaps.Single().TopTags, 3).SelectMany(t => t!).ToArray();
|
||||
online.Result!.Genre = new BeatmapSetOnlineGenre { Id = 12, Name = "Verrrrryyyy llooonngggggg genre" };
|
||||
online.Result!.Language = new BeatmapSetOnlineLanguage { Id = 12, Name = "Verrrrryyyy llooonngggggg language" };
|
||||
online.Result!.Beatmaps.Single().TopTags = Enumerable.Repeat(online.Result!.Beatmaps.Single().TopTags, 3).SelectMany(t => t!).ToArray();
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
onlineLookupResult.Value = online;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
}
|
||||
@@ -171,22 +127,17 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
[Test]
|
||||
public void TestOnlineAvailability()
|
||||
{
|
||||
AddStep("online beatmapset", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
AddStep("online beatmapset", () => (Beatmap.Value, onlineLookupResult.Value) = createTestBeatmap());
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddUntilStep("rating wedge visible", () => wedge.RatingsVisible);
|
||||
AddUntilStep("fail time wedge visible", () => wedge.FailRetryVisible);
|
||||
AddStep("online beatmapset with local diff", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
var (working, lookupResult) = createTestBeatmap();
|
||||
|
||||
working.BeatmapInfo.ResetOnlineInfo();
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
onlineLookupResult.Value = lookupResult;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddUntilStep("rating wedge hidden", () => !wedge.RatingsVisible);
|
||||
@@ -195,7 +146,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
var (working, _) = createTestBeatmap();
|
||||
|
||||
currentOnlineSet = null;
|
||||
onlineLookupResult.Value = null;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddAssert("rating wedge still hidden", () => !wedge.RatingsVisible);
|
||||
@@ -205,21 +156,17 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
[Test]
|
||||
public void TestUserTags()
|
||||
{
|
||||
AddStep("user tags", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
AddStep("user tags", () => (Beatmap.Value, onlineLookupResult.Value) = createTestBeatmap());
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddStep("no user tags", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
var (working, online) = createTestBeatmap();
|
||||
|
||||
onlineSet.Beatmaps.Single().TopTags = null;
|
||||
onlineSet.RelatedTags = null;
|
||||
online.Result!.Beatmaps.Single().TopTags = null;
|
||||
online.Result!.RelatedTags = null;
|
||||
working.BeatmapSetInfo.Beatmaps.Single().Metadata.UserTags.Clear();
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
onlineLookupResult.Value = online;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
}
|
||||
@@ -227,72 +174,60 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
[Test]
|
||||
public void TestLoading()
|
||||
{
|
||||
AddStep("override request handling", () =>
|
||||
{
|
||||
currentOnlineSet = null;
|
||||
|
||||
((DummyAPIAccess)API).HandleRequest = request =>
|
||||
{
|
||||
switch (request)
|
||||
{
|
||||
case GetBeatmapSetRequest set:
|
||||
Scheduler.AddDelayed(() => set.TriggerSuccess(currentOnlineSet!), 500);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("set beatmap", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
var (working, online) = createTestBeatmap();
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.InProgress();
|
||||
Scheduler.AddDelayed(() => onlineLookupResult.Value = online, 500);
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddWaitStep("wait", 5);
|
||||
|
||||
AddStep("set beatmap", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
var (working, online) = createTestBeatmap();
|
||||
|
||||
onlineSet.RelatedTags![0].Name = "other/tag";
|
||||
onlineSet.RelatedTags[1].Name = "another/tag";
|
||||
onlineSet.RelatedTags[2].Name = "some/tag";
|
||||
online.Result!.RelatedTags![0].Name = "other/tag";
|
||||
online.Result!.RelatedTags[1].Name = "another/tag";
|
||||
online.Result!.RelatedTags[2].Name = "some/tag";
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.InProgress();
|
||||
Scheduler.AddDelayed(() => onlineLookupResult.Value = online, 500);
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddWaitStep("wait", 5);
|
||||
|
||||
AddStep("no user tags", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
var (working, online) = createTestBeatmap();
|
||||
|
||||
onlineSet.Beatmaps.Single().TopTags = null;
|
||||
onlineSet.RelatedTags = null;
|
||||
online.Result!.Beatmaps.Single().TopTags = null;
|
||||
online.Result!.RelatedTags = null;
|
||||
working.BeatmapSetInfo.Beatmaps.Single().Metadata.UserTags.Clear();
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.InProgress();
|
||||
Scheduler.AddDelayed(() => onlineLookupResult.Value = online, 500);
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddWaitStep("wait", 5);
|
||||
|
||||
AddStep("no user tags", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
var (working, online) = createTestBeatmap();
|
||||
|
||||
onlineSet.Beatmaps.Single().TopTags = null;
|
||||
onlineSet.RelatedTags = null;
|
||||
online.Result!.Beatmaps.Single().TopTags = null;
|
||||
online.Result!.RelatedTags = null;
|
||||
working.BeatmapSetInfo.Beatmaps.Single().Metadata.UserTags.Clear();
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.InProgress();
|
||||
Scheduler.AddDelayed(() => onlineLookupResult.Value = online, 500);
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddWaitStep("wait", 5);
|
||||
}
|
||||
|
||||
private (WorkingBeatmap, APIBeatmapSet) createTestBeatmap()
|
||||
private (WorkingBeatmap, Screens.SelectV2.SongSelect.BeatmapSetLookupResult) createTestBeatmap()
|
||||
{
|
||||
var working = CreateWorkingBeatmap(Ruleset.Value);
|
||||
var onlineSet = new APIBeatmapSet
|
||||
@@ -346,7 +281,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
working.BeatmapSetInfo.DateSubmitted = DateTimeOffset.Now;
|
||||
working.BeatmapSetInfo.DateRanked = DateTimeOffset.Now;
|
||||
return (working, onlineSet);
|
||||
working.Metadata.UserTags.AddRange(onlineSet.RelatedTags.Select(t => t.Name));
|
||||
return (working, Screens.SelectV2.SongSelect.BeatmapSetLookupResult.Completed(onlineSet));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
@@ -41,10 +42,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
private BeatmapTitleWedge titleWedge = null!;
|
||||
private BeatmapTitleWedge.DifficultyDisplay difficultyDisplay => titleWedge.ChildrenOfType<BeatmapTitleWedge.DifficultyDisplay>().Single();
|
||||
|
||||
private APIBeatmapSet? currentOnlineSet;
|
||||
|
||||
[Cached]
|
||||
private RealmPopulatingOnlineLookupSource lookupSource = new RealmPopulatingOnlineLookupSource();
|
||||
[Cached(typeof(IBindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?>))]
|
||||
private Bindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?> onlineLookupResult = new Bindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(RulesetStore rulesets)
|
||||
@@ -58,7 +57,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
lookupSource,
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@@ -142,44 +140,18 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
[Test]
|
||||
public void TestOnlineAvailability()
|
||||
{
|
||||
AddStep("set up request handler", () =>
|
||||
{
|
||||
((DummyAPIAccess)API).HandleRequest = request =>
|
||||
{
|
||||
switch (request)
|
||||
{
|
||||
case GetBeatmapSetRequest set:
|
||||
if (set.ID == currentOnlineSet?.OnlineID)
|
||||
{
|
||||
set.TriggerSuccess(currentOnlineSet);
|
||||
return true;
|
||||
}
|
||||
AddStep("online beatmapset", () => (Beatmap.Value, onlineLookupResult.Value) = createTestBeatmap());
|
||||
|
||||
return false;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("online beatmapset", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddUntilStep("play count is 10000", () => this.ChildrenOfType<BeatmapTitleWedge.Statistic>().ElementAt(0).Text.ToString(), () => Is.EqualTo("10,000"));
|
||||
AddUntilStep("favourites count is 2345", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("2,345"));
|
||||
AddStep("online beatmapset with local diff", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
var (working, lookupResult) = createTestBeatmap();
|
||||
|
||||
working.BeatmapInfo.ResetOnlineInfo();
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
Beatmap.Value = working;
|
||||
onlineLookupResult.Value = lookupResult;
|
||||
});
|
||||
AddUntilStep("play count is -", () => this.ChildrenOfType<BeatmapTitleWedge.Statistic>().ElementAt(0).Text.ToString(), () => Is.EqualTo("-"));
|
||||
AddUntilStep("favourites count is 2345", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("2,345"));
|
||||
@@ -187,8 +159,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
var (working, _) = createTestBeatmap();
|
||||
|
||||
currentOnlineSet = null;
|
||||
Beatmap.Value = working;
|
||||
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.Completed(null);
|
||||
});
|
||||
AddUntilStep("play count is -", () => this.ChildrenOfType<BeatmapTitleWedge.Statistic>().ElementAt(0).Text.ToString(), () => Is.EqualTo("-"));
|
||||
AddUntilStep("favourites count is -", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("-"));
|
||||
@@ -205,15 +177,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
switch (request)
|
||||
{
|
||||
case GetBeatmapSetRequest set:
|
||||
if (set.ID == currentOnlineSet?.OnlineID)
|
||||
{
|
||||
set.TriggerSuccess(currentOnlineSet);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
case PostBeatmapFavouriteRequest favourite:
|
||||
Task.Run(() =>
|
||||
{
|
||||
@@ -228,13 +191,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("online beatmapset", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
AddStep("online beatmapset", () => (Beatmap.Value, onlineLookupResult.Value) = createTestBeatmap());
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
Beatmap.Value = working;
|
||||
});
|
||||
AddUntilStep("play count is 10000", () => this.ChildrenOfType<BeatmapTitleWedge.Statistic>().ElementAt(0).Text.ToString(), () => Is.EqualTo("10,000"));
|
||||
AddUntilStep("favourites count is 2345", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("2,345"));
|
||||
|
||||
@@ -251,13 +209,13 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
AddStep("click favourite button", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().TriggerClick());
|
||||
AddStep("change to another beatmap", () =>
|
||||
{
|
||||
var (working, onlineSet) = createTestBeatmap();
|
||||
onlineSet.FavouriteCount = 9999;
|
||||
onlineSet.HasFavourited = true;
|
||||
working.BeatmapSetInfo.OnlineID = onlineSet.OnlineID = 99999;
|
||||
var (working, online) = createTestBeatmap();
|
||||
online.Result!.FavouriteCount = 9999;
|
||||
online.Result!.HasFavourited = true;
|
||||
working.BeatmapSetInfo.OnlineID = online.Result!.OnlineID = 99999;
|
||||
|
||||
currentOnlineSet = onlineSet;
|
||||
Beatmap.Value = working;
|
||||
onlineLookupResult.Value = online;
|
||||
});
|
||||
AddStep("allow request to complete", () => resetEvent.Set());
|
||||
AddUntilStep("favourites count is 9999", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("9,999"));
|
||||
@@ -268,15 +226,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
switch (request)
|
||||
{
|
||||
case GetBeatmapSetRequest set:
|
||||
if (set.ID == currentOnlineSet?.OnlineID)
|
||||
{
|
||||
set.TriggerSuccess(currentOnlineSet);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
case PostBeatmapFavouriteRequest favourite:
|
||||
Task.Run(() =>
|
||||
{
|
||||
@@ -350,7 +299,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
});
|
||||
}
|
||||
|
||||
private (WorkingBeatmap, APIBeatmapSet) createTestBeatmap()
|
||||
private (WorkingBeatmap, Screens.SelectV2.SongSelect.BeatmapSetLookupResult) createTestBeatmap()
|
||||
{
|
||||
var working = CreateWorkingBeatmap(Ruleset.Value);
|
||||
var onlineSet = new APIBeatmapSet
|
||||
@@ -371,7 +320,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
working.BeatmapSetInfo.DateSubmitted = DateTimeOffset.Now;
|
||||
working.BeatmapSetInfo.DateRanked = DateTimeOffset.Now;
|
||||
return (working, onlineSet);
|
||||
return (working, Screens.SelectV2.SongSelect.BeatmapSetLookupResult.Completed(onlineSet));
|
||||
}
|
||||
|
||||
private class TestHitObject : ConvertHitObject;
|
||||
|
||||
@@ -75,21 +75,21 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
new PanelBeatmapSet
|
||||
{
|
||||
Item = new CarouselItem(beatmapSet)
|
||||
Item = new CarouselItem(new GroupedBeatmapSet(null, beatmapSet))
|
||||
},
|
||||
new PanelBeatmapSet
|
||||
{
|
||||
Item = new CarouselItem(beatmapSet),
|
||||
Item = new CarouselItem(new GroupedBeatmapSet(null, beatmapSet)),
|
||||
KeyboardSelected = { Value = true }
|
||||
},
|
||||
new PanelBeatmapSet
|
||||
{
|
||||
Item = new CarouselItem(beatmapSet),
|
||||
Item = new CarouselItem(new GroupedBeatmapSet(null, beatmapSet)),
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
new PanelBeatmapSet
|
||||
{
|
||||
Item = new CarouselItem(beatmapSet),
|
||||
Item = new CarouselItem(new GroupedBeatmapSet(null, beatmapSet)),
|
||||
KeyboardSelected = { Value = true },
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
|
||||
@@ -23,7 +23,9 @@ using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Leaderboards;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK.Input;
|
||||
using BeatmapCarousel = osu.Game.Screens.SelectV2.BeatmapCarousel;
|
||||
using FooterButtonMods = osu.Game.Screens.SelectV2.FooterButtonMods;
|
||||
using FooterButtonOptions = osu.Game.Screens.SelectV2.FooterButtonOptions;
|
||||
using FooterButtonRandom = osu.Game.Screens.SelectV2.FooterButtonRandom;
|
||||
@@ -302,6 +304,28 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Last played and rank achieved may have changed, so we want to make sure filtering runs on resume to song select.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestFilteringRunsAfterReturningFromGameplay()
|
||||
{
|
||||
AddStep("import actual beatmap", () => Beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()));
|
||||
LoadSongSelect();
|
||||
|
||||
AddUntilStep("wait for filtered", () => SongSelect.ChildrenOfType<BeatmapCarousel>().Single().FilterCount, () => Is.EqualTo(1));
|
||||
|
||||
AddStep("enter gameplay", () => InputManager.Key(Key.Enter));
|
||||
|
||||
AddUntilStep("wait for player", () => Stack.CurrentScreen is Player);
|
||||
AddUntilStep("wait for fail", () => ((Player)Stack.CurrentScreen).GameplayState.HasFailed);
|
||||
|
||||
AddStep("exit gameplay", () => InputManager.Key(Key.Escape));
|
||||
AddStep("exit gameplay", () => InputManager.Key(Key.Escape));
|
||||
|
||||
AddUntilStep("wait for filtered", () => SongSelect.ChildrenOfType<BeatmapCarousel>().Single().FilterCount, () => Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAutoplayShortcut()
|
||||
{
|
||||
|
||||
@@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
AddAssert("no-collection group present", () =>
|
||||
{
|
||||
var group = grouping.GroupItems.Single(g => g.Key.Title == "Not in collection");
|
||||
return group.Value.Select(i => i.Model).OfType<BeatmapSetInfo>().Single().Equals(beatmapSet);
|
||||
return group.Value.Select(i => i.Model).OfType<GroupedBeatmapSet>().Single().BeatmapSet.Equals(beatmapSet);
|
||||
});
|
||||
|
||||
AddStep("add beatmap to collection", () =>
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
@@ -22,6 +24,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
private FillFlowContainer spreadOutFlow = null!;
|
||||
private ModDisplay modDisplay = null!;
|
||||
|
||||
[Resolved]
|
||||
private RulesetStore rulesetStore { get; set; } = null!;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
@@ -70,9 +75,26 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
[Test]
|
||||
public void TestShowAllMods()
|
||||
{
|
||||
AddStep("create mod icons", () =>
|
||||
createModIconsForRuleset(0);
|
||||
createModIconsForRuleset(1);
|
||||
createModIconsForRuleset(2);
|
||||
createModIconsForRuleset(3);
|
||||
|
||||
AddStep("toggle selected", () =>
|
||||
{
|
||||
addRange(Ruleset.Value.CreateInstance().CreateAllMods().Select(m =>
|
||||
foreach (var icon in this.ChildrenOfType<ModIcon>())
|
||||
icon.Selected.Toggle();
|
||||
});
|
||||
}
|
||||
|
||||
private void createModIconsForRuleset(int rulesetId)
|
||||
{
|
||||
AddStep($"create mod icons for ruleset {rulesetId}", () =>
|
||||
{
|
||||
spreadOutFlow.Clear();
|
||||
modDisplay.Current.Value = [];
|
||||
|
||||
addRange(rulesetStore.GetRuleset(rulesetId)!.CreateInstance().CreateAllMods().Select(m =>
|
||||
{
|
||||
if (m is OsuModFlashlight fl)
|
||||
fl.FollowDelay.Value = 1245;
|
||||
@@ -89,12 +111,6 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
return m;
|
||||
}));
|
||||
});
|
||||
|
||||
AddStep("toggle selected", () =>
|
||||
{
|
||||
foreach (var icon in this.ChildrenOfType<ModIcon>())
|
||||
icon.Selected.Toggle();
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -6,6 +6,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Tournament.Components;
|
||||
using osu.Game.Tournament.IPC;
|
||||
using osu.Game.Tournament.Screens.Gameplay;
|
||||
@@ -66,6 +67,6 @@ namespace osu.Game.Tournament.Tests.Screens
|
||||
() => this.ChildrenOfType<TeamScore>().All(score => score.Alpha == (visible ? 1 : 0)));
|
||||
|
||||
private void toggleWarmup()
|
||||
=> AddStep("toggle warmup", () => this.ChildrenOfType<TourneyButton>().First().TriggerClick());
|
||||
=> AddStep("toggle warmup", () => this.ChildrenOfType<LabelledSwitchButton>().First().ChildrenOfType<SwitchButton>().First().TriggerClick());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Tournament.Components;
|
||||
using osu.Game.Tournament.IPC;
|
||||
@@ -24,7 +24,6 @@ namespace osu.Game.Tournament.Screens.Gameplay
|
||||
private readonly BindableBool warmup = new BindableBool();
|
||||
|
||||
public readonly Bindable<TourneyState> State = new Bindable<TourneyState>();
|
||||
private OsuButton warmupButton = null!;
|
||||
private MatchIPCInfo ipc = null!;
|
||||
|
||||
[Resolved]
|
||||
@@ -40,6 +39,8 @@ namespace osu.Game.Tournament.Screens.Gameplay
|
||||
{
|
||||
this.ipc = ipc;
|
||||
|
||||
LabelledSwitchButton chatToggle;
|
||||
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
new TourneyVideo("gameplay")
|
||||
@@ -95,17 +96,14 @@ namespace osu.Game.Tournament.Screens.Gameplay
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
warmupButton = new TourneyButton
|
||||
new LabelledSwitchButton
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Text = "Toggle warmup",
|
||||
Action = () => warmup.Toggle()
|
||||
Label = "Warmup",
|
||||
Current = warmup,
|
||||
},
|
||||
new TourneyButton
|
||||
chatToggle = new LabelledSwitchButton
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Text = "Toggle chat",
|
||||
Action = () => { State.Value = State.Value == TourneyState.Idle ? TourneyState.Playing : TourneyState.Idle; }
|
||||
Label = "Show chat",
|
||||
},
|
||||
new SettingsSlider<int>
|
||||
{
|
||||
@@ -123,13 +121,12 @@ namespace osu.Game.Tournament.Screens.Gameplay
|
||||
}
|
||||
});
|
||||
|
||||
State.BindValueChanged(state => chatToggle.Current.Value = State.Value == TourneyState.Idle, true);
|
||||
chatToggle.Current.BindValueChanged(v => State.Value = v.NewValue ? TourneyState.Idle : TourneyState.Playing);
|
||||
|
||||
LadderInfo.ChromaKeyWidth.BindValueChanged(width => chroma.Width = width.NewValue, true);
|
||||
|
||||
warmup.BindValueChanged(w =>
|
||||
{
|
||||
warmupButton.Alpha = !w.NewValue ? 0.5f : 1;
|
||||
header.ShowScores = !w.NewValue;
|
||||
}, true);
|
||||
warmup.BindValueChanged(w => header.ShowScores = !w.NewValue, true);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
||||
@@ -13,6 +13,7 @@ using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
@@ -36,8 +37,8 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public ProcessBeatmapDelegate? ProcessBeatmap { private get; set; }
|
||||
|
||||
public BeatmapImporter(Storage storage, RealmAccess realm)
|
||||
: base(storage, realm)
|
||||
public BeatmapImporter(Storage storage, RealmAccess realm, OsuConfigManager? config)
|
||||
: base(storage, realm, config)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.IO.Archives;
|
||||
@@ -59,7 +60,7 @@ namespace osu.Game.Beatmaps
|
||||
}
|
||||
|
||||
public BeatmapManager(Storage storage, RealmAccess realm, IAPIProvider? api, AudioManager audioManager, IResourceStore<byte[]> gameResources, GameHost? host = null,
|
||||
WorkingBeatmap? defaultBeatmap = null, BeatmapDifficultyCache? difficultyCache = null, bool performOnlineLookups = false)
|
||||
WorkingBeatmap? defaultBeatmap = null, BeatmapDifficultyCache? difficultyCache = null, bool performOnlineLookups = false, OsuConfigManager? config = null)
|
||||
: base(storage, realm)
|
||||
{
|
||||
if (performOnlineLookups)
|
||||
@@ -75,7 +76,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
BeatmapTrackStore = audioManager.GetTrackStore(userResources);
|
||||
|
||||
beatmapImporter = CreateBeatmapImporter(storage, realm);
|
||||
beatmapImporter = CreateBeatmapImporter(storage, realm, config);
|
||||
beatmapImporter.ProcessBeatmap = (beatmapSet, scope) => ProcessBeatmap?.Invoke(beatmapSet, scope);
|
||||
beatmapImporter.PostNotification = obj => PostNotification?.Invoke(obj);
|
||||
|
||||
@@ -98,7 +99,7 @@ namespace osu.Game.Beatmaps
|
||||
return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host);
|
||||
}
|
||||
|
||||
protected virtual BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm) => new BeatmapImporter(storage, realm);
|
||||
protected virtual BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, OsuConfigManager? config = null) => new BeatmapImporter(storage, realm, config);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new beatmap set, backed by a <see cref="BeatmapSetInfo"/> model,
|
||||
|
||||
@@ -59,7 +59,10 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
// An interpolating clock is used to ensure precise time values even when the host audio subsystem is not reporting
|
||||
// high precision times (on windows there's generally only 5-10ms reporting intervals, as an example).
|
||||
interpolatedTrack = new InterpolatingFramedClock(decoupledTrack);
|
||||
interpolatedTrack = new InterpolatingFramedClock(decoupledTrack)
|
||||
{
|
||||
DriftRecoveryHalfLife = 80,
|
||||
};
|
||||
|
||||
if (applyOffsets)
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ using osu.Framework.Configuration;
|
||||
using osu.Framework.Configuration.Tracking;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Beatmaps.Drawables.Cards;
|
||||
@@ -41,6 +42,8 @@ namespace osu.Game.Configuration
|
||||
SetDefault(OsuSetting.Ruleset, string.Empty);
|
||||
SetDefault(OsuSetting.Skin, SkinInfo.ARGON_SKIN.ToString());
|
||||
|
||||
SetDefault(OsuSetting.MenuCookieColor, Colour4.FromHex(@"ff66ba"));
|
||||
|
||||
SetDefault(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Local);
|
||||
SetDefault(OsuSetting.BeatmapLeaderboardSortMode, LeaderboardSortMode.Score);
|
||||
SetDefault(OsuSetting.BeatmapDetailModsFilter, false);
|
||||
@@ -65,6 +68,7 @@ namespace osu.Game.Configuration
|
||||
|
||||
SetDefault(OsuSetting.ToolbarClockDisplayMode, ToolbarClockDisplayMode.Full);
|
||||
|
||||
SetDefault(OsuSetting.ForceLegacySongSelect, false);
|
||||
SetDefault(OsuSetting.SongSelectBackgroundBlur, false);
|
||||
|
||||
// Online settings
|
||||
@@ -194,9 +198,12 @@ namespace osu.Game.Configuration
|
||||
|
||||
SetDefault(OsuSetting.MenuBackgroundSource, BackgroundSource.Skin);
|
||||
SetDefault(OsuSetting.SeasonalBackgroundMode, SeasonalBackgroundMode.Never);
|
||||
SetDefault(OsuSetting.UseSeasonalBackgroundsV2, true);
|
||||
|
||||
SetDefault(OsuSetting.DiscordRichPresence, DiscordRichPresenceMode.Full);
|
||||
|
||||
SetDefault(OsuSetting.DeleteImportedArchives, true);
|
||||
|
||||
SetDefault(OsuSetting.EditorDim, 0.25f, 0f, 0.75f, 0.25f);
|
||||
SetDefault(OsuSetting.EditorWaveformOpacity, 0.25f, 0f, 1f, 0.25f);
|
||||
SetDefault(OsuSetting.EditorShowHitMarkers, true);
|
||||
@@ -405,6 +412,7 @@ namespace osu.Game.Configuration
|
||||
ChatDisplayHeight,
|
||||
BeatmapListingCardSize,
|
||||
ToolbarClockDisplayMode,
|
||||
ForceLegacySongSelect,
|
||||
SongSelectBackgroundBlur,
|
||||
Version,
|
||||
ShowFirstRunSetup,
|
||||
@@ -412,6 +420,7 @@ namespace osu.Game.Configuration
|
||||
Skin,
|
||||
ScreenshotFormat,
|
||||
ScreenshotCaptureMenuCursor,
|
||||
MenuCookieColor,
|
||||
BeatmapSkins,
|
||||
BeatmapColours,
|
||||
BeatmapHitsounds,
|
||||
@@ -436,11 +445,13 @@ namespace osu.Game.Configuration
|
||||
MenuBackgroundSource,
|
||||
GameplayDisableWinKey,
|
||||
SeasonalBackgroundMode,
|
||||
UseSeasonalBackgroundsV2, // TODO: add migrations
|
||||
BackgroundCategory,
|
||||
EditorWaveformOpacity,
|
||||
EditorShowHitMarkers,
|
||||
EditorAutoSeekOnPlacement,
|
||||
DiscordRichPresence,
|
||||
DeleteImportedArchives,
|
||||
|
||||
ShowOnlineExplicitContent,
|
||||
LastProcessedMetadataId,
|
||||
|
||||
@@ -558,9 +558,15 @@ namespace osu.Game.Database
|
||||
|
||||
Logger.Log("Querying for beatmap sets that contain missing submission/rank date...");
|
||||
|
||||
// find all ranked beatmap sets with missing date ranked or date submitted that have at least one difficulty ranked as well.
|
||||
// the reason for checking ranked status of the difficulties is that they can be locally modified or unknown too, and for those the lookup is likely to fail.
|
||||
// this is because metadata lookups are primarily based on file hash, so they will fail to match if the beatmap does not match the online version
|
||||
// (which is likely to be the case if the beatmap is locally modified or unknown).
|
||||
// that said, one difficulty in ranked state is enough for the backpopulation to work.
|
||||
HashSet<Guid> beatmapSetIds = realmAccess.Run(r => new HashSet<Guid>(
|
||||
r.All<BeatmapSetInfo>()
|
||||
.Where(b => b.StatusInt > 0 && (b.DateRanked == null || b.DateSubmitted == null))
|
||||
.Filter($@"{nameof(BeatmapSetInfo.StatusInt)} > 0 && ({nameof(BeatmapSetInfo.DateRanked)} == null || {nameof(BeatmapSetInfo.DateSubmitted)} == null) "
|
||||
+ $@"&& ANY {nameof(BeatmapSetInfo.Beatmaps)}.{nameof(BeatmapInfo.StatusInt)} > 0")
|
||||
.AsEnumerable()
|
||||
.Select(b => b.ID)));
|
||||
|
||||
@@ -591,11 +597,7 @@ namespace osu.Game.Database
|
||||
{
|
||||
BeatmapSetInfo beatmapSet = r.Find<BeatmapSetInfo>(id)!;
|
||||
|
||||
// we want any ranked representative of the set.
|
||||
// the reason for checking ranked status of the difficulty is that it can be locally modified,
|
||||
// at which point the lookup will fail - but there might still be another unmodified difficulty on which it will work.
|
||||
if (beatmapSet.Beatmaps.FirstOrDefault(b => b.Status >= BeatmapOnlineStatus.Ranked) is not BeatmapInfo beatmap)
|
||||
return false;
|
||||
var beatmap = beatmapSet.Beatmaps.First(b => b.Status >= BeatmapOnlineStatus.Ranked);
|
||||
|
||||
bool lookupSucceeded = localMetadataSource.TryLookup(beatmap, out var result);
|
||||
|
||||
@@ -725,7 +727,7 @@ namespace osu.Game.Database
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(@$"Failed to update ranked/submitted dates for beatmap set {id}: {e}");
|
||||
Logger.Log(@$"Failed to update user tags for beatmap {id}: {e}");
|
||||
++failedCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,14 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Humanizer;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.IO.Archives;
|
||||
using osu.Game.Models;
|
||||
@@ -77,11 +80,19 @@ namespace osu.Game.Database
|
||||
/// </summary>
|
||||
public Action<Notification>? PostNotification { get; set; }
|
||||
|
||||
protected RealmArchiveModelImporter(Storage storage, RealmAccess realm)
|
||||
private readonly OsuConfigManager? config;
|
||||
|
||||
private Bindable<bool>? deleteImportedArchives;
|
||||
|
||||
protected RealmArchiveModelImporter(Storage storage, RealmAccess realm, OsuConfigManager? config = null)
|
||||
{
|
||||
Realm = realm;
|
||||
|
||||
Files = new RealmFileStore(realm, storage);
|
||||
|
||||
deleteImportedArchives = config?.GetBindable<bool>(OsuSetting.DeleteImportedArchives);
|
||||
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public Task Import(params string[] paths) => Import(paths.Select(p => new ImportTask(p)).ToArray());
|
||||
@@ -241,9 +252,10 @@ namespace osu.Game.Database
|
||||
// e.g. reconstructing/repairing database with items from default storage.
|
||||
// Also, not always a single file, i.e. for LegacyFilesystemReader
|
||||
// TODO: Add a check to prevent files from storage to be deleted.
|
||||
bool allowDelete = deleteImportedArchives?.Value ?? true;
|
||||
try
|
||||
{
|
||||
if (import != null && ShouldDeleteArchive(task.Path))
|
||||
if (import != null && ShouldDeleteArchive(task.Path) && allowDelete)
|
||||
task.DeleteFile();
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -33,19 +33,19 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
private Bindable<SeasonalBackgroundMode> backgroundMode;
|
||||
private Bindable<bool> useSeasonalBackgrounds;
|
||||
private Bindable<string> selectedCategory;
|
||||
private Bindable<APISeasonalBackgrounds> currentBackgrounds;
|
||||
|
||||
private int currentBackgroundIndex;
|
||||
|
||||
private bool shouldShowCustomBackgrounds => backgroundMode.Value != SeasonalBackgroundMode.Never;
|
||||
private bool shouldShowCustomBackgrounds => useSeasonalBackgrounds.Value;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config, SessionStatics sessionStatics)
|
||||
{
|
||||
backgroundMode = config.GetBindable<SeasonalBackgroundMode>(OsuSetting.SeasonalBackgroundMode);
|
||||
backgroundMode.BindValueChanged(_ => BackgroundChanged?.Invoke());
|
||||
useSeasonalBackgrounds = config.GetBindable<bool>(OsuSetting.UseSeasonalBackgroundsV2);
|
||||
useSeasonalBackgrounds.BindValueChanged(_ => BackgroundChanged?.Invoke());
|
||||
|
||||
selectedCategory = config.GetBindable<string>(OsuSetting.BackgroundCategory);
|
||||
selectedCategory.BindValueChanged(_ => fetchBackgroundsForSelectedCategory());
|
||||
@@ -53,18 +53,18 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
currentBackgrounds = sessionStatics.GetBindable<APISeasonalBackgrounds>(Static.SeasonalBackgrounds);
|
||||
|
||||
if (shouldShowCustomBackgrounds)
|
||||
fetchCategories();
|
||||
fetchCategories(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public method to trigger a refresh of categories from the UI.
|
||||
/// </summary>
|
||||
public void RefreshCategories()
|
||||
public void RefreshCategories(bool ignoreSuccess = false)
|
||||
{
|
||||
fetchCategories();
|
||||
fetchCategories(ignoreSuccess);
|
||||
}
|
||||
|
||||
private void fetchCategories()
|
||||
private void fetchCategories(bool ignoreSuccess = false)
|
||||
{
|
||||
if (!shouldShowCustomBackgrounds) return;
|
||||
|
||||
@@ -74,20 +74,28 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
{
|
||||
var serverCategories = response.Categories ?? Enumerable.Empty<string>();
|
||||
|
||||
AvailableCategories.Value = new[] { "Default" }.Concat(serverCategories)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
|
||||
AvailableCategories.Value = serverCategories.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
|
||||
|
||||
if (!AvailableCategories.Value.Any())
|
||||
{
|
||||
selectedCategory.Value = "";
|
||||
return; // we don't have any categories!!!
|
||||
}
|
||||
|
||||
if (!AvailableCategories.Value.Contains(selectedCategory.Value))
|
||||
selectedCategory.Value = "Default";
|
||||
else
|
||||
fetchBackgroundsForSelectedCategory();
|
||||
selectedCategory.Value = AvailableCategories.Value.Contains("Default")
|
||||
? "Default"
|
||||
: AvailableCategories.Value.ElementAt(0);
|
||||
|
||||
OnCategoriesRefreshed?.Invoke();
|
||||
fetchBackgroundsForSelectedCategory();
|
||||
|
||||
if (!ignoreSuccess)
|
||||
OnCategoriesRefreshed?.Invoke();
|
||||
};
|
||||
|
||||
request.Failure += exception =>
|
||||
{
|
||||
AvailableCategories.Value = new[] { "Íå óäàëîñü çàãðóçèòü..." };
|
||||
AvailableCategories.Value = Array.Empty<string>();
|
||||
OnLoadFailure?.Invoke(exception);
|
||||
};
|
||||
|
||||
@@ -98,13 +106,6 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
{
|
||||
if (!shouldShowCustomBackgrounds) return;
|
||||
|
||||
if (AvailableCategories.Value.Count() == 1 && AvailableCategories.Value.First().Contains("Íå óäàëîñü"))
|
||||
{
|
||||
currentBackgrounds.Value = new APISeasonalBackgrounds { Backgrounds = new List<APISeasonalBackground>() };
|
||||
BackgroundChanged?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
string categoryToFetch = selectedCategory.Value == "Default" ? null : selectedCategory.Value;
|
||||
var request = new GetSeasonalBackgroundsRequest(categoryToFetch);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@@ -79,7 +80,7 @@ namespace osu.Game.Graphics.Carousel
|
||||
/// <summary>
|
||||
/// The number of times filter operations have been triggered.
|
||||
/// </summary>
|
||||
internal int FilterCount { get; private set; }
|
||||
public int FilterCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of displayable items currently being tracked (before filtering).
|
||||
@@ -210,6 +211,12 @@ namespace osu.Game.Graphics.Carousel
|
||||
return filterTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when <see cref="Items"/> changes in any way.
|
||||
/// </summary>
|
||||
/// <returns>Whether a re-filter is required.</returns>
|
||||
protected virtual bool HandleItemsChanged(NotifyCollectionChangedEventArgs args) => true;
|
||||
|
||||
/// <summary>
|
||||
/// Fired after a filter operation completed.
|
||||
/// </summary>
|
||||
@@ -301,7 +308,11 @@ namespace osu.Game.Graphics.Carousel
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
|
||||
Items.BindCollectionChanged((_, _) => filterAfterItemsChanged.Invalidate());
|
||||
Items.BindCollectionChanged((_, args) =>
|
||||
{
|
||||
if (HandleItemsChanged(args))
|
||||
filterAfterItemsChanged.Invalidate();
|
||||
});
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
||||
@@ -81,27 +81,6 @@ namespace osu.Game.Graphics
|
||||
public static IconUsage InsaneMania => get(0xe027);
|
||||
public static IconUsage ExpertMania => get(0xe028);
|
||||
|
||||
// mod icons
|
||||
public static IconUsage ModPerfect => get(0xe049);
|
||||
public static IconUsage ModAutopilot => get(0xe03a);
|
||||
public static IconUsage ModAuto => get(0xe03b);
|
||||
public static IconUsage ModCinema => get(0xe03c);
|
||||
public static IconUsage ModDoubleTime => get(0xe03d);
|
||||
public static IconUsage ModEasy => get(0xe03e);
|
||||
public static IconUsage ModFlashlight => get(0xe03f);
|
||||
public static IconUsage ModHalftime => get(0xe040);
|
||||
public static IconUsage ModHardRock => get(0xe041);
|
||||
public static IconUsage ModHidden => get(0xe042);
|
||||
public static IconUsage ModNightcore => get(0xe043);
|
||||
public static IconUsage ModNoFail => get(0xe044);
|
||||
public static IconUsage ModRelax => get(0xe045);
|
||||
public static IconUsage ModSpunOut => get(0xe046);
|
||||
public static IconUsage ModSuddenDeath => get(0xe047);
|
||||
public static IconUsage ModTarget => get(0xe048);
|
||||
|
||||
// Use "Icons/BeatmapDetails/mod-icon" instead
|
||||
// public static IconUsage ModBg => Get(0xe04a);
|
||||
|
||||
#endregion
|
||||
|
||||
#region New single-file-based icons
|
||||
@@ -181,6 +160,88 @@ namespace osu.Game.Graphics
|
||||
public static IconUsage Tortoise => get(OsuIconMapping.Tortoise);
|
||||
public static IconUsage Hare => get(OsuIconMapping.Hare);
|
||||
|
||||
// mod icons
|
||||
|
||||
public static IconUsage ModNoMod => get(OsuIconMapping.ModNoMod);
|
||||
|
||||
/*
|
||||
can be regenerated semi-automatically using osu-web's mod database via
|
||||
|
||||
$ jq -r '.[].Mods[].Name' mods.json | sort | uniq | \
|
||||
sed 's/ //g' | \
|
||||
awk '{print "public static IconUsage Mod" $0 " => get(OsuIconMapping.Mod" $0 ");"}' | pbcopy
|
||||
*/
|
||||
|
||||
public static IconUsage ModAccuracyChallenge => get(OsuIconMapping.ModAccuracyChallenge);
|
||||
public static IconUsage ModAdaptiveSpeed => get(OsuIconMapping.ModAdaptiveSpeed);
|
||||
public static IconUsage ModAlternate => get(OsuIconMapping.ModAlternate);
|
||||
public static IconUsage ModApproachDifferent => get(OsuIconMapping.ModApproachDifferent);
|
||||
public static IconUsage ModAutopilot => get(OsuIconMapping.ModAutopilot);
|
||||
public static IconUsage ModAutoplay => get(OsuIconMapping.ModAutoplay);
|
||||
public static IconUsage ModBarrelRoll => get(OsuIconMapping.ModBarrelRoll);
|
||||
public static IconUsage ModBlinds => get(OsuIconMapping.ModBlinds);
|
||||
public static IconUsage ModBloom => get(OsuIconMapping.ModBloom);
|
||||
public static IconUsage ModBubbles => get(OsuIconMapping.ModBubbles);
|
||||
public static IconUsage ModCinema => get(OsuIconMapping.ModCinema);
|
||||
public static IconUsage ModClassic => get(OsuIconMapping.ModClassic);
|
||||
public static IconUsage ModConstantSpeed => get(OsuIconMapping.ModConstantSpeed);
|
||||
public static IconUsage ModCover => get(OsuIconMapping.ModCover);
|
||||
public static IconUsage ModDaycore => get(OsuIconMapping.ModDaycore);
|
||||
public static IconUsage ModDeflate => get(OsuIconMapping.ModDeflate);
|
||||
public static IconUsage ModDepth => get(OsuIconMapping.ModDepth);
|
||||
public static IconUsage ModDifficultyAdjust => get(OsuIconMapping.ModDifficultyAdjust);
|
||||
public static IconUsage ModDoubleTime => get(OsuIconMapping.ModDoubleTime);
|
||||
public static IconUsage ModDualStages => get(OsuIconMapping.ModDualStages);
|
||||
public static IconUsage ModEasy => get(OsuIconMapping.ModEasy);
|
||||
public static IconUsage ModEightKeys => get(OsuIconMapping.ModEightKeys);
|
||||
public static IconUsage ModFadeIn => get(OsuIconMapping.ModFadeIn);
|
||||
public static IconUsage ModFiveKeys => get(OsuIconMapping.ModFiveKeys);
|
||||
public static IconUsage ModFlashlight => get(OsuIconMapping.ModFlashlight);
|
||||
public static IconUsage ModFloatingFruits => get(OsuIconMapping.ModFloatingFruits);
|
||||
public static IconUsage ModFourKeys => get(OsuIconMapping.ModFourKeys);
|
||||
public static IconUsage ModFreezeFrame => get(OsuIconMapping.ModFreezeFrame);
|
||||
public static IconUsage ModGrow => get(OsuIconMapping.ModGrow);
|
||||
public static IconUsage ModHalfTime => get(OsuIconMapping.ModHalfTime);
|
||||
public static IconUsage ModHardRock => get(OsuIconMapping.ModHardRock);
|
||||
public static IconUsage ModHidden => get(OsuIconMapping.ModHidden);
|
||||
public static IconUsage ModHoldOff => get(OsuIconMapping.ModHoldOff);
|
||||
public static IconUsage ModInvert => get(OsuIconMapping.ModInvert);
|
||||
public static IconUsage ModMagnetised => get(OsuIconMapping.ModMagnetised);
|
||||
public static IconUsage ModMirror => get(OsuIconMapping.ModMirror);
|
||||
public static IconUsage ModMovingFast => get(OsuIconMapping.ModMovingFast);
|
||||
public static IconUsage ModMuted => get(OsuIconMapping.ModMuted);
|
||||
public static IconUsage ModNightcore => get(OsuIconMapping.ModNightcore);
|
||||
public static IconUsage ModNineKeys => get(OsuIconMapping.ModNineKeys);
|
||||
public static IconUsage ModNoFail => get(OsuIconMapping.ModNoFail);
|
||||
public static IconUsage ModNoRelease => get(OsuIconMapping.ModNoRelease);
|
||||
public static IconUsage ModNoScope => get(OsuIconMapping.ModNoScope);
|
||||
public static IconUsage ModOneKey => get(OsuIconMapping.ModOneKey);
|
||||
public static IconUsage ModPerfect => get(OsuIconMapping.ModPerfect);
|
||||
public static IconUsage ModRandom => get(OsuIconMapping.ModRandom);
|
||||
public static IconUsage ModRelax => get(OsuIconMapping.ModRelax);
|
||||
public static IconUsage ModRepel => get(OsuIconMapping.ModRepel);
|
||||
public static IconUsage ModScoreV2 => get(OsuIconMapping.ModScoreV2);
|
||||
public static IconUsage ModSevenKeys => get(OsuIconMapping.ModSevenKeys);
|
||||
public static IconUsage ModSimplifiedRhythm => get(OsuIconMapping.ModSimplifiedRhythm);
|
||||
public static IconUsage ModSingleTap => get(OsuIconMapping.ModSingleTap);
|
||||
public static IconUsage ModSixKeys => get(OsuIconMapping.ModSixKeys);
|
||||
public static IconUsage ModSpinIn => get(OsuIconMapping.ModSpinIn);
|
||||
public static IconUsage ModSpunOut => get(OsuIconMapping.ModSpunOut);
|
||||
public static IconUsage ModStrictTracking => get(OsuIconMapping.ModStrictTracking);
|
||||
public static IconUsage ModSuddenDeath => get(OsuIconMapping.ModSuddenDeath);
|
||||
public static IconUsage ModSwap => get(OsuIconMapping.ModSwap);
|
||||
public static IconUsage ModSynesthesia => get(OsuIconMapping.ModSynesthesia);
|
||||
public static IconUsage ModTargetPractice => get(OsuIconMapping.ModTargetPractice);
|
||||
public static IconUsage ModTenKeys => get(OsuIconMapping.ModTenKeys);
|
||||
public static IconUsage ModThreeKeys => get(OsuIconMapping.ModThreeKeys);
|
||||
public static IconUsage ModTouchDevice => get(OsuIconMapping.ModTouchDevice);
|
||||
public static IconUsage ModTraceable => get(OsuIconMapping.ModTraceable);
|
||||
public static IconUsage ModTransform => get(OsuIconMapping.ModTransform);
|
||||
public static IconUsage ModTwoKeys => get(OsuIconMapping.ModTwoKeys);
|
||||
public static IconUsage ModWiggle => get(OsuIconMapping.ModWiggle);
|
||||
public static IconUsage ModWindDown => get(OsuIconMapping.ModWindDown);
|
||||
public static IconUsage ModWindUp => get(OsuIconMapping.ModWindUp);
|
||||
|
||||
private static IconUsage get(OsuIconMapping glyph) => new IconUsage((char)glyph, FONT_NAME);
|
||||
|
||||
private enum OsuIconMapping
|
||||
@@ -400,6 +461,224 @@ namespace osu.Game.Graphics
|
||||
|
||||
[Description(@"hare")]
|
||||
Hare,
|
||||
|
||||
// mod icons
|
||||
|
||||
[Description(@"Mods/mod-no-mod")]
|
||||
ModNoMod,
|
||||
|
||||
/*
|
||||
rest can be regenerated semi-automatically using osu-web's mod database via
|
||||
$ jq -r '.[].Mods[].Name' mods.json | sort | uniq | \
|
||||
awk '{kebab = $0; gsub(" ", "-", kebab); pascal = $0; gsub(" ", "", pascal); print "[Description(@\"Mods/mod-" tolower(kebab) "\")]\nMod" pascal ",\n" }' | pbcopy
|
||||
*/
|
||||
|
||||
[Description(@"Mods/mod-accuracy-challenge")]
|
||||
ModAccuracyChallenge,
|
||||
|
||||
[Description(@"Mods/mod-adaptive-speed")]
|
||||
ModAdaptiveSpeed,
|
||||
|
||||
[Description(@"Mods/mod-alternate")]
|
||||
ModAlternate,
|
||||
|
||||
[Description(@"Mods/mod-approach-different")]
|
||||
ModApproachDifferent,
|
||||
|
||||
[Description(@"Mods/mod-autopilot")]
|
||||
ModAutopilot,
|
||||
|
||||
[Description(@"Mods/mod-autoplay")]
|
||||
ModAutoplay,
|
||||
|
||||
[Description(@"Mods/mod-barrel-roll")]
|
||||
ModBarrelRoll,
|
||||
|
||||
[Description(@"Mods/mod-blinds")]
|
||||
ModBlinds,
|
||||
|
||||
[Description(@"Mods/mod-bloom")]
|
||||
ModBloom,
|
||||
|
||||
[Description(@"Mods/mod-bubbles")]
|
||||
ModBubbles,
|
||||
|
||||
[Description(@"Mods/mod-cinema")]
|
||||
ModCinema,
|
||||
|
||||
[Description(@"Mods/mod-classic")]
|
||||
ModClassic,
|
||||
|
||||
[Description(@"Mods/mod-constant-speed")]
|
||||
ModConstantSpeed,
|
||||
|
||||
[Description(@"Mods/mod-cover")]
|
||||
ModCover,
|
||||
|
||||
[Description(@"Mods/mod-daycore")]
|
||||
ModDaycore,
|
||||
|
||||
[Description(@"Mods/mod-deflate")]
|
||||
ModDeflate,
|
||||
|
||||
[Description(@"Mods/mod-depth")]
|
||||
ModDepth,
|
||||
|
||||
[Description(@"Mods/mod-difficulty-adjust")]
|
||||
ModDifficultyAdjust,
|
||||
|
||||
[Description(@"Mods/mod-double-time")]
|
||||
ModDoubleTime,
|
||||
|
||||
[Description(@"Mods/mod-dual-stages")]
|
||||
ModDualStages,
|
||||
|
||||
[Description(@"Mods/mod-easy")]
|
||||
ModEasy,
|
||||
|
||||
[Description(@"Mods/mod-eight-keys")]
|
||||
ModEightKeys,
|
||||
|
||||
[Description(@"Mods/mod-fade-in")]
|
||||
ModFadeIn,
|
||||
|
||||
[Description(@"Mods/mod-five-keys")]
|
||||
ModFiveKeys,
|
||||
|
||||
[Description(@"Mods/mod-flashlight")]
|
||||
ModFlashlight,
|
||||
|
||||
[Description(@"Mods/mod-floating-fruits")]
|
||||
ModFloatingFruits,
|
||||
|
||||
[Description(@"Mods/mod-four-keys")]
|
||||
ModFourKeys,
|
||||
|
||||
[Description(@"Mods/mod-freeze-frame")]
|
||||
ModFreezeFrame,
|
||||
|
||||
[Description(@"Mods/mod-grow")]
|
||||
ModGrow,
|
||||
|
||||
[Description(@"Mods/mod-half-time")]
|
||||
ModHalfTime,
|
||||
|
||||
[Description(@"Mods/mod-hard-rock")]
|
||||
ModHardRock,
|
||||
|
||||
[Description(@"Mods/mod-hidden")]
|
||||
ModHidden,
|
||||
|
||||
[Description(@"Mods/mod-hold-off")]
|
||||
ModHoldOff,
|
||||
|
||||
[Description(@"Mods/mod-invert")]
|
||||
ModInvert,
|
||||
|
||||
[Description(@"Mods/mod-magnetised")]
|
||||
ModMagnetised,
|
||||
|
||||
[Description(@"Mods/mod-mirror")]
|
||||
ModMirror,
|
||||
|
||||
[Description(@"Mods/mod-moving-fast")]
|
||||
ModMovingFast,
|
||||
|
||||
[Description(@"Mods/mod-muted")]
|
||||
ModMuted,
|
||||
|
||||
[Description(@"Mods/mod-nightcore")]
|
||||
ModNightcore,
|
||||
|
||||
[Description(@"Mods/mod-nine-keys")]
|
||||
ModNineKeys,
|
||||
|
||||
[Description(@"Mods/mod-no-fail")]
|
||||
ModNoFail,
|
||||
|
||||
[Description(@"Mods/mod-no-release")]
|
||||
ModNoRelease,
|
||||
|
||||
[Description(@"Mods/mod-no-scope")]
|
||||
ModNoScope,
|
||||
|
||||
[Description(@"Mods/mod-one-key")]
|
||||
ModOneKey,
|
||||
|
||||
[Description(@"Mods/mod-perfect")]
|
||||
ModPerfect,
|
||||
|
||||
[Description(@"Mods/mod-random")]
|
||||
ModRandom,
|
||||
|
||||
[Description(@"Mods/mod-relax")]
|
||||
ModRelax,
|
||||
|
||||
[Description(@"Mods/mod-repel")]
|
||||
ModRepel,
|
||||
|
||||
[Description(@"Mods/mod-score-v2")]
|
||||
ModScoreV2,
|
||||
|
||||
[Description(@"Mods/mod-seven-keys")]
|
||||
ModSevenKeys,
|
||||
|
||||
[Description(@"Mods/mod-simplified-rhythm")]
|
||||
ModSimplifiedRhythm,
|
||||
|
||||
[Description(@"Mods/mod-single-tap")]
|
||||
ModSingleTap,
|
||||
|
||||
[Description(@"Mods/mod-six-keys")]
|
||||
ModSixKeys,
|
||||
|
||||
[Description(@"Mods/mod-spin-in")]
|
||||
ModSpinIn,
|
||||
|
||||
[Description(@"Mods/mod-spun-out")]
|
||||
ModSpunOut,
|
||||
|
||||
[Description(@"Mods/mod-strict-tracking")]
|
||||
ModStrictTracking,
|
||||
|
||||
[Description(@"Mods/mod-sudden-death")]
|
||||
ModSuddenDeath,
|
||||
|
||||
[Description(@"Mods/mod-swap")]
|
||||
ModSwap,
|
||||
|
||||
[Description(@"Mods/mod-synesthesia")]
|
||||
ModSynesthesia,
|
||||
|
||||
[Description(@"Mods/mod-target-practice")]
|
||||
ModTargetPractice,
|
||||
|
||||
[Description(@"Mods/mod-ten-keys")]
|
||||
ModTenKeys,
|
||||
|
||||
[Description(@"Mods/mod-three-keys")]
|
||||
ModThreeKeys,
|
||||
|
||||
[Description(@"Mods/mod-touch-device")]
|
||||
ModTouchDevice,
|
||||
|
||||
[Description(@"Mods/mod-traceable")]
|
||||
ModTraceable,
|
||||
|
||||
[Description(@"Mods/mod-transform")]
|
||||
ModTransform,
|
||||
|
||||
[Description(@"Mods/mod-two-keys")]
|
||||
ModTwoKeys,
|
||||
|
||||
[Description(@"Mods/mod-wiggle")]
|
||||
ModWiggle,
|
||||
|
||||
[Description(@"Mods/mod-wind-down")]
|
||||
ModWindDown,
|
||||
|
||||
[Description(@"Mods/mod-wind-up")]
|
||||
ModWindUp,
|
||||
}
|
||||
|
||||
public class OsuIconStore : ITextureStore, ITexturedGlyphLookupStore
|
||||
|
||||
@@ -78,6 +78,20 @@ Your experience will not be perfect, and may even feel subpar compared to games
|
||||
|
||||
Please bear with us as we continue to improve the game for you!");
|
||||
|
||||
/// <summary>
|
||||
/// "Welcome to jvnkosu!lazer!"
|
||||
/// </summary>
|
||||
public static LocalisableString GreetingNotification => new TranslatableString(getKey(@"greeting_notification"), @"Welcome to jvnkosu!lazer!");
|
||||
|
||||
/// <summary>
|
||||
/// "Failed to load backgrounds!\nCheck your internet connection"
|
||||
/// </summary>
|
||||
public static LocalisableString SeasonalBackgroundsFail => new TranslatableString(getKey(@"seasonal_backgrounds_fail"), @"Failed to load backgrounds!\nCheck your internet connection"); // TODO: implement l10n in osu-resources
|
||||
|
||||
/// <summary>
|
||||
/// "Successfully refreshed background categories!"
|
||||
/// </summary>
|
||||
public static LocalisableString SeasonalBackgroundsRefreshed => new TranslatableString(getKey(@"seasonal_backgrounds_refreshed"), @"Successfully refreshed background categories!");
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,16 +64,26 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString BackgroundSource => new TranslatableString(getKey(@"background_source"), @"Background source");
|
||||
|
||||
/// <summary>
|
||||
/// "Use custom backgrounds from server"
|
||||
/// </summary>
|
||||
public static LocalisableString UseSeasonalBackgrounds => new TranslatableString(getKey(@"use_seasonal_backgrounds"), @"Use custom backgrounds from server");
|
||||
|
||||
/// <summary>
|
||||
/// "Seasonal backgrounds"
|
||||
/// </summary>
|
||||
public static LocalisableString SeasonalBackgrounds => new TranslatableString(getKey(@"seasonal_backgrounds"), @"Seasonal backgrounds");
|
||||
|
||||
/*/// <summary>
|
||||
/// "Seasonal backgrounds"
|
||||
/// </summary>*/
|
||||
/* public static LocalisableString SeasonalBackgroundsCategories => new TranslatableString(getKey(@"seasonal_backgrounds_categories"), @"Seasonal backgrounds categories");
|
||||
*/
|
||||
/// <summary>
|
||||
/// "Background categories"
|
||||
/// </summary>
|
||||
public static LocalisableString SeasonalBackgroundsCategories => new TranslatableString(getKey(@"seasonal_backgrounds_categories"), @"Background categories");
|
||||
|
||||
/// <summary>
|
||||
/// "Refresh categories"
|
||||
/// </summary>
|
||||
public static LocalisableString SeasonalBackgroundsRefresh => new TranslatableString(getKey(@"seasonal_backgrounds_refresh"), @"Refresh categories");
|
||||
|
||||
/// <summary>
|
||||
/// "Changes to this setting will only apply with an active osu!supporter tag."
|
||||
/// </summary>
|
||||
@@ -174,6 +184,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString SelectedMods => new TranslatableString(getKey(@"selected_mods"), @"Selected Mods");
|
||||
|
||||
/// <summary>
|
||||
/// "Use legacy song select (SelectV1)"
|
||||
/// </summary>
|
||||
public static LocalisableString ForceLegacySongSelect => new TranslatableString(getKey(@"force_select_v1"), @"Use legacy song select (SelectV1)");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace osu.Game.Online.API.Requests
|
||||
public class GetMenuContentRequest : OsuJsonWebRequest<APIMenuContent>
|
||||
{
|
||||
public GetMenuContentRequest()
|
||||
: base(@"https://assets.ppy.sh/menu-content.json")
|
||||
: base(@"https://osu.jvnko.boats/uploads/menu-content.json") // TODO: backend
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ namespace osu.Game
|
||||
private readonly ScreenshotManager screenshotManager = new ScreenshotManager();
|
||||
|
||||
[Cached]
|
||||
private readonly SeasonalBackgroundLoader backgroundLoader;
|
||||
private SeasonalBackgroundLoader seasonalBackgroundLoader;
|
||||
|
||||
protected SentryLogger SentryLogger;
|
||||
|
||||
@@ -253,10 +253,6 @@ namespace osu.Game
|
||||
|
||||
public OsuGame(string[] args = null)
|
||||
{
|
||||
backgroundLoader = new SeasonalBackgroundLoader();
|
||||
backgroundLoader.OnLoadFailure += handleBackgroundLoadFailure;
|
||||
backgroundLoader.OnCategoriesRefreshed += handleCategoriesRefreshed;
|
||||
|
||||
this.args = args;
|
||||
|
||||
Logger.NewEntry += forwardGeneralLogToNotifications;
|
||||
@@ -271,6 +267,8 @@ namespace osu.Game
|
||||
tabletLogNotifyOnError = true;
|
||||
}, true);
|
||||
});
|
||||
|
||||
initializeSeasonalBackgrounds();
|
||||
}
|
||||
|
||||
#region IOverlayManager
|
||||
@@ -409,17 +407,6 @@ namespace osu.Game
|
||||
}
|
||||
}
|
||||
}
|
||||
private void handleCategoriesRefreshed()
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
Notifications?.Post(new SimpleNotification
|
||||
{
|
||||
Text = "Ñïèñîê êàòåãîðèé ôîíîâ îáíîâëåí.",
|
||||
Icon = FontAwesome.Solid.CheckCircle
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@@ -946,7 +933,7 @@ namespace osu.Game
|
||||
|
||||
protected virtual Loader CreateLoader() => new Loader();
|
||||
|
||||
protected virtual UpdateManager CreateUpdateManager() => new UpdateManager();
|
||||
protected virtual UpdateManager CreateUpdateManager() => new NoActionUpdateManager();
|
||||
|
||||
/// <summary>
|
||||
/// Adjust the globally applied <see cref="DrawSizePreservingFillContainer.TargetDrawSize"/> in every <see cref="ScalingContainer"/>.
|
||||
@@ -1205,8 +1192,8 @@ namespace osu.Game
|
||||
Margin = new MarginPadding(5),
|
||||
}, topMostOverlayContent.Add);
|
||||
|
||||
if (!IsDeployedBuild)
|
||||
loadComponentSingleFile(devBuildBanner = new DevBuildBanner(), ScreenContainer.Add);
|
||||
// if (!IsDeployedBuild) // we're going to have the "developer build" banner for a while
|
||||
loadComponentSingleFile(devBuildBanner = new DevBuildBanner(), ScreenContainer.Add);
|
||||
|
||||
loadComponentSingleFile(osuLogo, _ =>
|
||||
{
|
||||
@@ -1251,7 +1238,7 @@ namespace osu.Game
|
||||
|
||||
loadComponentSingleFile(screenshotManager, Add);
|
||||
|
||||
loadComponentSingleFile(backgroundLoader, Add);
|
||||
loadComponentSingleFile(seasonalBackgroundLoader, Add);
|
||||
|
||||
// dependency on notification overlay, dependent by settings overlay
|
||||
loadComponentSingleFile(CreateUpdateManager(), Add, true);
|
||||
@@ -1354,17 +1341,6 @@ namespace osu.Game
|
||||
handleStartupImport();
|
||||
}
|
||||
|
||||
private void handleBackgroundLoadFailure(Exception exception)
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
Notifications?.Post(new SimpleNotification
|
||||
{
|
||||
Text = "Íå óäàëîñü çàãðóçèòü ôîíû. Ïðîâåðüòå ïîäêëþ÷åíèå ê èíòåðíåòó.",
|
||||
Icon = FontAwesome.Solid.ExclamationTriangle
|
||||
});
|
||||
});
|
||||
}
|
||||
private void handleBackButton()
|
||||
{
|
||||
// TODO: this is SUPER SUPER bad.
|
||||
@@ -1501,6 +1477,40 @@ namespace osu.Game
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeSeasonalBackgrounds()
|
||||
{
|
||||
seasonalBackgroundLoader = new SeasonalBackgroundLoader();
|
||||
seasonalBackgroundLoader.OnCategoriesRefreshed += handleCategoriesRefreshed;
|
||||
seasonalBackgroundLoader.OnLoadFailure += handleBackgroundLoadFailure;
|
||||
}
|
||||
|
||||
private void handleCategoriesRefreshed()
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
Notifications?.Post(new SimpleNotification
|
||||
{
|
||||
Text = ButtonSystemStrings.SeasonalBackgroundsRefreshed,
|
||||
Icon = FontAwesome.Solid.CheckCircle,
|
||||
Transient = true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void handleBackgroundLoadFailure(Exception exception)
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
Notifications?.Post(new SimpleErrorNotification
|
||||
{
|
||||
Text = ButtonSystemStrings.SeasonalBackgroundsFail,
|
||||
Icon = FontAwesome.Solid.ExclamationTriangle,
|
||||
Transient = true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private Task asyncLoadStream;
|
||||
|
||||
/// <summary>
|
||||
@@ -1735,7 +1745,8 @@ namespace osu.Game
|
||||
introScreen = intro;
|
||||
SimpleNotification notification = new SimpleNotification
|
||||
{
|
||||
Text = "Welcome to jvnkosu!lazer!",
|
||||
Text = ButtonSystemStrings.GreetingNotification,
|
||||
Transient = true,
|
||||
};
|
||||
Notifications?.Post(notification);
|
||||
devBuildBanner?.Show();
|
||||
|
||||
@@ -298,7 +298,7 @@ namespace osu.Game
|
||||
|
||||
Audio.Samples.PlaybackConcurrency = SAMPLE_CONCURRENCY;
|
||||
|
||||
dependencies.Cache(SkinManager = new SkinManager(Storage, realm, Host, Resources, Audio, Scheduler));
|
||||
dependencies.Cache(SkinManager = new SkinManager(Storage, realm, Host, Resources, Audio, Scheduler, LocalConfig));
|
||||
dependencies.CacheAs<ISkinSource>(SkinManager);
|
||||
|
||||
EndpointConfiguration endpoints = CreateEndpoints();
|
||||
@@ -322,7 +322,7 @@ namespace osu.Game
|
||||
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
|
||||
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, API, LocalConfig));
|
||||
|
||||
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true));
|
||||
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true, config: LocalConfig));
|
||||
dependencies.CacheAs<IWorkingBeatmapCache>(BeatmapManager);
|
||||
|
||||
dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API));
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace osu.Game.Overlays
|
||||
Origin = Anchor.BottomCentre,
|
||||
Font = OsuFont.Torus.With(size: 12),
|
||||
Colour = colours.GrayF,
|
||||
Text = "jvnkosu! development build",
|
||||
Text = $@"jvnkosu! " + game.Version,
|
||||
Y = -12,
|
||||
},
|
||||
new OsuSpriteText
|
||||
|
||||
@@ -180,7 +180,7 @@ namespace osu.Game.Overlays
|
||||
notification.Closed += () => notificationClosed(notification);
|
||||
|
||||
if (notification is IHasCompletionTarget hasCompletionTarget)
|
||||
hasCompletionTarget.CompletionTarget = Post;
|
||||
hasCompletionTarget.CompletionTarget ??= Post;
|
||||
|
||||
playDebouncedSample(notification.PopInSampleName);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Import;
|
||||
@@ -22,13 +23,19 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
private ISystemFileSelector? selector;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuGameBase game, GameHost host, IPerformFromScreenRunner? performer)
|
||||
private void load(OsuGameBase game, GameHost host, IPerformFromScreenRunner? performer, OsuConfigManager config)
|
||||
{
|
||||
if ((selector = host.CreateSystemFileSelector(game.HandledExtensions.ToArray())) != null)
|
||||
selector.Selected += f => Task.Run(() => game.Import(f.FullName));
|
||||
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Delete archives on import",
|
||||
Current = config.GetBindable<bool>(OsuSetting.DeleteImportedArchives),
|
||||
ClassicDefault = true
|
||||
},
|
||||
new SettingsButton
|
||||
{
|
||||
Text = DebugSettingsStrings.ImportFiles,
|
||||
@@ -44,7 +51,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
{
|
||||
Text = DebugSettingsStrings.RunLatencyCertifier,
|
||||
Action = () => performer?.PerformFromScreen(menu => menu.Push(new LatencyCertifierScreen()))
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ using osu.Game.Graphics.Backgrounds;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays.Settings;
|
||||
|
||||
namespace osu.Game.Overlays.Settings.Sections.UserInterface
|
||||
{
|
||||
@@ -27,57 +26,48 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
|
||||
|
||||
private SettingsEnumDropdown<BackgroundSource> backgroundSourceDropdown;
|
||||
|
||||
private Bindable<bool> useSeasonalBackgrounds;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config, IAPIProvider api)
|
||||
{
|
||||
user = api.LocalUser.GetBoundCopy();
|
||||
|
||||
var backgroundModeBindable = config.GetBindable<SeasonalBackgroundMode>(OsuSetting.SeasonalBackgroundMode);
|
||||
var enabledProxyBindable = new Bindable<bool>();
|
||||
|
||||
backgroundModeBindable.BindValueChanged(mode => enabledProxyBindable.Value = mode.NewValue == SeasonalBackgroundMode.Always, true);
|
||||
enabledProxyBindable.BindValueChanged(enabled => backgroundModeBindable.Value = enabled.NewValue ? SeasonalBackgroundMode.Always : SeasonalBackgroundMode.Never);
|
||||
useSeasonalBackgrounds = config.GetBindable<bool>(OsuSetting.UseSeasonalBackgroundsV2);
|
||||
|
||||
var backgroundToggle = new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Пользовательские фоны",
|
||||
Current = enabledProxyBindable
|
||||
LabelText = UserInterfaceStrings.UseSeasonalBackgrounds,
|
||||
Current = config.GetBindable<bool>(OsuSetting.UseSeasonalBackgroundsV2),
|
||||
ClassicDefault = true
|
||||
};
|
||||
|
||||
var categoryDropdown = new SettingsDropdown<string>
|
||||
{
|
||||
LabelText = "Категория фонов",
|
||||
LabelText = UserInterfaceStrings.SeasonalBackgroundsCategories,
|
||||
Current = config.GetBindable<string>(OsuSetting.BackgroundCategory)
|
||||
};
|
||||
|
||||
var refreshButton = new SettingsButton
|
||||
{
|
||||
Text = "Обновить список категорий",
|
||||
Text = UserInterfaceStrings.SeasonalBackgroundsRefresh,
|
||||
Action = () => backgroundLoader.RefreshCategories()
|
||||
};
|
||||
|
||||
backgroundLoader.AvailableCategories.BindValueChanged(categories => categoryDropdown.Items = categories.NewValue, true);
|
||||
// TODO: the category dropdown disappear if no backgrounds (e.g. when first enabling the setting)
|
||||
refreshButton.CanBeShown.BindTo(useSeasonalBackgrounds);
|
||||
categoryDropdown.CanBeShown.BindTo(useSeasonalBackgrounds);
|
||||
useSeasonalBackgrounds.BindValueChanged(
|
||||
_ => backgroundLoader.RefreshCategories(true)
|
||||
);
|
||||
|
||||
backgroundModeBindable.BindValueChanged(mode =>
|
||||
{
|
||||
if (mode.NewValue == SeasonalBackgroundMode.Always)
|
||||
{
|
||||
categoryDropdown.Show();
|
||||
refreshButton.Show();
|
||||
}
|
||||
else
|
||||
{
|
||||
categoryDropdown.Hide();
|
||||
refreshButton.Hide();
|
||||
}
|
||||
}, true);
|
||||
backgroundLoader.AvailableCategories.BindValueChanged(categories => categoryDropdown.Items = categories.NewValue, true);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = UserInterfaceStrings.ShowMenuTips,
|
||||
Current = config.GetBindable<bool>(OsuSetting.MenuTips)
|
||||
Current = config.GetBindable<bool>(OsuSetting.MenuTips),
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
@@ -104,6 +94,12 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
|
||||
backgroundToggle,
|
||||
categoryDropdown,
|
||||
refreshButton,
|
||||
new SettingsColour
|
||||
{
|
||||
LabelText = @"osu! logo colour",
|
||||
Current = config.GetBindable<Colour4>(OsuSetting.MenuCookieColor),
|
||||
ClassicDefault = Colour4.FromHex(@"ff66ba"),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,12 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = UserInterfaceStrings.ForceLegacySongSelect,
|
||||
Current = config.GetBindable<bool>(OsuSetting.ForceLegacySongSelect),
|
||||
ClassicDefault = false
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = UserInterfaceStrings.ShowConvertedBeatmaps,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user