1 Commits

Author SHA1 Message Date
e78c8fa03d (NOT STABLE!!) Added custom mode using music from MainMenu 2025-08-23 22:54:22 +03:00
153 changed files with 1305 additions and 2360 deletions

View File

@@ -148,7 +148,9 @@ jobs:
# https://github.com/dotnet/macios/issues/19157 # https://github.com/dotnet/macios/issues/19157
# https://github.com/actions/runner-images/issues/12758 # https://github.com/actions/runner-images/issues/12758
- name: Use Xcode 16.4 - name: Use Xcode 16.4
run: sudo xcode-select -switch /Applications/Xcode_16.4.app run: |
sudo xcode-select -switch /Applications/Xcode_16.4.app
xcodebuild -downloadPlatform iOS
- name: Build - name: Build
run: dotnet build -c Debug osu.iOS.slnf run: dotnet build -c Debug osu.iOS.slnf

View File

@@ -1,3 +0,0 @@
{
"dotnet.defaultSolution": "osu.Desktop.slnf"
}

View File

@@ -49,7 +49,7 @@
<PackageProjectUrl>https://github.com/ppy/osu</PackageProjectUrl> <PackageProjectUrl>https://github.com/ppy/osu</PackageProjectUrl>
<RepositoryUrl>https://github.com/ppy/osu</RepositoryUrl> <RepositoryUrl>https://github.com/ppy/osu</RepositoryUrl>
<PackageReleaseNotes>Automated release.</PackageReleaseNotes> <PackageReleaseNotes>Automated release.</PackageReleaseNotes>
<Company>ppy Pty Ltd, jvnkosu! team</Company> <Company>ppy Pty Ltd</Company>
<Copyright>Copyright (c) 2025 ppy Pty Ltd</Copyright> <Copyright>Copyright (c) 2025 ppy Pty Ltd</Copyright>
<PackageTags>osu game</PackageTags> <PackageTags>osu game</PackageTags>
</PropertyGroup> </PropertyGroup>

149
README.md
View File

@@ -1,40 +1,147 @@
# jvnkosu! client <p align="center">
<img width="500" alt="osu! logo" src="assets/lazer.png">
</p>
A free-to-win rhythm game based on osu!(lazer). Click is just a *rhythm* away! # osu!
## Disclaimer [![Build status](https://github.com/ppy/osu/actions/workflows/ci.yml/badge.svg?branch=master&event=push)](https://github.com/ppy/osu/actions/workflows/ci.yml)
[![GitHub release](https://img.shields.io/github/release/ppy/osu.svg)](https://github.com/ppy/osu/releases/latest)
[![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu)
[![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/osu-web/localized.svg)](https://crowdin.com/project/osu-web)
*osu!* is a registered trademark of ppy Pty Ltd. A free-to-win rhythm game. Rhythm is just a *click* away!
jvnkosu! is not affiliated with, or endorsed by ppy Pty Ltd., but makes use of its open-source components and resources.
## License 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.
Client source code is licensed under the MIT license, see the [LICENCE](LICENCE) file in repository root for more info.
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. ## Status
Registered trademarks "osu!" and "ppy" are property of ppy Pty Ltd., and protected by trademark law. 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.
## Compiling from source A few resources are available as starting points to getting involved and understanding the project:
Building jvnkosu! from source is pretty much possible (and welcome here).
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. - 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).
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: ## Running osu!
```
git clone https://gitea.jvnko.boats/jvnkosu/client 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:
To **run** the project, switch to project's directory and run the following: ```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 dotnet run --project osu.Desktop
``` ```
To **compile**: 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).
```
dotnet build osu.Desktop 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 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. macOS / Linux:
### See the [original readme](README.original.md) for more info. ```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.

View File

@@ -1,147 +0,0 @@
<p align="center">
<img width="500" alt="osu! logo" src="assets/lazer.png">
</p>
# osu!
[![Build status](https://github.com/ppy/osu/actions/workflows/ci.yml/badge.svg?branch=master&event=push)](https://github.com/ppy/osu/actions/workflows/ci.yml)
[![GitHub release](https://img.shields.io/github/release/ppy/osu.svg)](https://github.com/ppy/osu/releases/latest)
[![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu)
[![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/osu-web/localized.svg)](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.

View File

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

View File

@@ -3,11 +3,11 @@
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Description>A free-to-win rhythm game based on osu!(lazer). Click is just a *rhythm* away!</Description> <Description>A free-to-win rhythm game. Rhythm is just a *click* away!</Description>
<AssemblyName>osu!</AssemblyName> <AssemblyName>osu!</AssemblyName>
<AssemblyTitle>jvnkosu!</AssemblyTitle> <AssemblyTitle>osu!(lazer)</AssemblyTitle>
<Title>jvnkosu!</Title> <Title>osu!</Title>
<Product>jvnkosu!</Product> <Product>osu!(lazer)</Product>
<ApplicationIcon>lazer.ico</ApplicationIcon> <ApplicationIcon>lazer.ico</ApplicationIcon>
<Version>0.0.0</Version> <Version>0.0.0</Version>
<FileVersion>0.0.0</FileVersion> <FileVersion>0.0.0</FileVersion>

View File

@@ -3,19 +3,16 @@
<metadata> <metadata>
<id>osulazer</id> <id>osulazer</id>
<version>0.0.0</version> <version>0.0.0</version>
<title>jvnkosu!</title> <title>osu!</title>
<authors>ppy Pty Ltd., jvnkosu! team</authors> <authors>ppy Pty Ltd</authors>
<owners>Dean Herbert</owners> <owners>Dean Herbert</owners>
<projectUrl>https://osu.jvnko.boats/</projectUrl> <projectUrl>https://osu.ppy.sh/</projectUrl>
<iconUrl>https://github.com/ppy/osu/blob/master/assets/lazer-nuget.png?raw=true</iconUrl> <iconUrl>https://github.com/ppy/osu/blob/master/assets/lazer-nuget.png?raw=true</iconUrl>
<icon>icon.png</icon> <icon>icon.png</icon>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>A free-to-win rhythm game based on osu!(lazer). Click is just a *rhythm* away!</description> <description>A free-to-win rhythm game. Rhythm is just a *click* away!</description>
<releaseNotes>testing</releaseNotes> <releaseNotes>testing</releaseNotes>
<copyright> <copyright>Copyright (c) 2025 ppy Pty Ltd</copyright>
Copyright (c) 2025 ppy Pty Ltd
Copyright (c) 2025 jvnkosu! team
</copyright>
<language>en-AU</language> <language>en-AU</language>
</metadata> </metadata>
<files> <files>

View File

@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Mods
public override BindableBool ComboBasedSize { get; } = new BindableBool(true); public override BindableBool ComboBasedSize { get; } = new BindableBool(true);
public override float DefaultFlashlightSize => 203.125f; public override float DefaultFlashlightSize => 325;
protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield); protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield);

View File

@@ -3,7 +3,6 @@
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@@ -17,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Mods
public override string Acronym => "FF"; public override string Acronym => "FF";
public override LocalisableString Description => "The fruits are... floating?"; public override LocalisableString Description => "The fruits are... floating?";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override IconUsage? Icon => OsuIcon.ModFloatingFruits; public override IconUsage? Icon => FontAwesome.Solid.Cloud;
public void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> drawableRuleset) public void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> drawableRuleset)
{ {

View File

@@ -7,7 +7,6 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@@ -24,7 +23,7 @@ namespace osu.Game.Rulesets.Catch.Mods
public override LocalisableString Description => "Dashing by default, slow down!"; public override LocalisableString Description => "Dashing by default, slow down!";
public override ModType Type => ModType.Fun; public override ModType Type => ModType.Fun;
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override IconUsage? Icon => OsuIcon.ModMovingFast; public override IconUsage? Icon => FontAwesome.Solid.Running;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax) }; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax) };
private DrawableCatchRuleset drawableRuleset = null!; private DrawableCatchRuleset drawableRuleset = null!;

View File

@@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Mania.Tests
} }
[TestCase] [TestCase]
public void TestKeysFilterIntersection() public void TestFilterIntersection()
{ {
var criteria = new ManiaFilterCriteria(); var criteria = new ManiaFilterCriteria();
criteria.TryParseCustomKeywordCriteria("keys", Operator.Greater, "4"); criteria.TryParseCustomKeywordCriteria("keys", Operator.Greater, "4");
@@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Mania.Tests
} }
[TestCase] [TestCase]
public void TestInvalidKeysFilters() public void TestInvalidFilters()
{ {
var criteria = new ManiaFilterCriteria(); var criteria = new ManiaFilterCriteria();
@@ -183,132 +183,5 @@ namespace osu.Game.Rulesets.Mania.Tests
Assert.False(criteria.TryParseCustomKeywordCriteria("keys", Operator.NotEqual, "4,some text")); Assert.False(criteria.TryParseCustomKeywordCriteria("keys", Operator.NotEqual, "4,some text"));
Assert.False(criteria.TryParseCustomKeywordCriteria("keys", Operator.GreaterOrEqual, "4,5,6")); 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"));
}
} }
} }

View File

@@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@@ -20,16 +19,12 @@ namespace osu.Game.Rulesets.Mania
public class ManiaFilterCriteria : IRulesetFilterCriteria public class ManiaFilterCriteria : IRulesetFilterCriteria
{ {
private readonly HashSet<int> includedKeyCounts = Enumerable.Range(1, LegacyBeatmapDecoder.MAX_MANIA_KEY_COUNT).ToHashSet(); 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) public bool Matches(BeatmapInfo beatmapInfo, FilterCriteria criteria)
{ {
int keyCount = ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo), criteria.Mods); int keyCount = ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo), criteria.Mods);
bool keyCountMatch = includedKeyCounts.Contains(keyCount); return 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) public bool TryParseCustomKeywordCriteria(string key, Operator op, string strValues)
@@ -89,10 +84,6 @@ namespace osu.Game.Rulesets.Mania
return false; return false;
} }
} }
case "ln":
case "lns":
return FilterQueryParser.TryUpdateCriteriaRange(ref longNotePercentage, op, strValues);
} }
return false; return false;
@@ -112,18 +103,5 @@ namespace osu.Game.Rulesets.Mania
return false; 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;
}
} }
} }

View File

@@ -4,7 +4,6 @@
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@@ -22,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override LocalisableString Description => "No more tricky speed changes!"; public override LocalisableString Description => "No more tricky speed changes!";
public override IconUsage? Icon => OsuIcon.ModConstantSpeed; public override IconUsage? Icon => FontAwesome.Solid.Equals;
public override ModType Type => ModType.Conversion; public override ModType Type => ModType.Conversion;

View File

@@ -4,10 +4,8 @@
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
@@ -16,7 +14,6 @@ namespace osu.Game.Rulesets.Mania.Mods
{ {
public override string Name => "Cover"; public override string Name => "Cover";
public override string Acronym => "CO"; public override string Acronym => "CO";
public override IconUsage? Icon => OsuIcon.ModCover;
public override LocalisableString Description => @"Decrease the playfield's viewing area."; public override LocalisableString Description => @"Decrease the playfield's viewing area.";

View File

@@ -1,10 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@@ -15,7 +13,6 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string Name => "Dual Stages"; public override string Name => "Dual Stages";
public override string Acronym => "DS"; public override string Acronym => "DS";
public override LocalisableString Description => @"Double the stages, double the fun!"; public override LocalisableString Description => @"Double the stages, double the fun!";
public override IconUsage? Icon => OsuIcon.ModDualStages;
public override ModType Type => ModType.Conversion; public override ModType Type => ModType.Conversion;
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;

View File

@@ -3,9 +3,7 @@
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
@@ -14,7 +12,6 @@ namespace osu.Game.Rulesets.Mania.Mods
{ {
public override string Name => "Fade In"; public override string Name => "Fade In";
public override string Acronym => "FI"; public override string Acronym => "FI";
public override IconUsage? Icon => OsuIcon.ModFadeIn;
public override LocalisableString Description => @"Keys appear out of nowhere!"; public override LocalisableString Description => @"Keys appear out of nowhere!";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override bool ValidForFreestyleAsRequiredMod => false; public override bool ValidForFreestyleAsRequiredMod => false;

View File

@@ -9,7 +9,6 @@ using osu.Game.Rulesets.Mods;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
@@ -24,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override LocalisableString Description => @"Replaces all hold notes with normal notes."; public override LocalisableString Description => @"Replaces all hold notes with normal notes.";
public override IconUsage? Icon => OsuIcon.ModHoldOff; public override IconUsage? Icon => FontAwesome.Solid.DotCircle;
public override ModType Type => ModType.Conversion; public override ModType Type => ModType.Conversion;

View File

@@ -8,7 +8,6 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@@ -24,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override LocalisableString Description => "Hold the keys. To the beat."; public override LocalisableString Description => "Hold the keys. To the beat.";
public override IconUsage? Icon => OsuIcon.ModInvert; public override IconUsage? Icon => FontAwesome.Solid.YinYang;
public override ModType Type => ModType.Conversion; public override ModType Type => ModType.Conversion;

View File

@@ -1,9 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
@@ -12,7 +10,6 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 1; public override int KeyCount => 1;
public override string Name => "One Key"; public override string Name => "One Key";
public override string Acronym => "1K"; public override string Acronym => "1K";
public override IconUsage? Icon => OsuIcon.ModOneKey;
public override LocalisableString Description => @"Play with one key."; public override LocalisableString Description => @"Play with one key.";
public override bool Ranked => false; public override bool Ranked => false;
} }

View File

@@ -1,9 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
@@ -12,7 +10,6 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 10; public override int KeyCount => 10;
public override string Name => "Ten Keys"; public override string Name => "Ten Keys";
public override string Acronym => "10K"; public override string Acronym => "10K";
public override IconUsage? Icon => OsuIcon.ModTenKeys;
public override LocalisableString Description => @"Play with ten keys."; public override LocalisableString Description => @"Play with ten keys.";
public override bool Ranked => false; public override bool Ranked => false;
} }

View File

@@ -1,9 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
@@ -12,7 +10,6 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 2; public override int KeyCount => 2;
public override string Name => "Two Keys"; public override string Name => "Two Keys";
public override string Acronym => "2K"; public override string Acronym => "2K";
public override IconUsage? Icon => OsuIcon.ModTwoKeys;
public override LocalisableString Description => @"Play with two keys."; public override LocalisableString Description => @"Play with two keys.";
public override bool Ranked => false; public override bool Ranked => false;
} }

View File

@@ -1,9 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
@@ -12,7 +10,6 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 3; public override int KeyCount => 3;
public override string Name => "Three Keys"; public override string Name => "Three Keys";
public override string Acronym => "3K"; public override string Acronym => "3K";
public override IconUsage? Icon => OsuIcon.ModThreeKeys;
public override LocalisableString Description => @"Play with three keys."; public override LocalisableString Description => @"Play with three keys.";
public override bool Ranked => false; public override bool Ranked => false;
} }

View File

@@ -1,9 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
@@ -12,7 +10,6 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 4; public override int KeyCount => 4;
public override string Name => "Four Keys"; public override string Name => "Four Keys";
public override string Acronym => "4K"; public override string Acronym => "4K";
public override IconUsage? Icon => OsuIcon.ModFourKeys;
public override LocalisableString Description => @"Play with four keys."; public override LocalisableString Description => @"Play with four keys.";
} }
} }

View File

@@ -1,9 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
@@ -12,7 +10,6 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 5; public override int KeyCount => 5;
public override string Name => "Five Keys"; public override string Name => "Five Keys";
public override string Acronym => "5K"; public override string Acronym => "5K";
public override IconUsage? Icon => OsuIcon.ModFiveKeys;
public override LocalisableString Description => @"Play with five keys."; public override LocalisableString Description => @"Play with five keys.";
} }
} }

View File

@@ -1,9 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
@@ -12,7 +10,6 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 6; public override int KeyCount => 6;
public override string Name => "Six Keys"; public override string Name => "Six Keys";
public override string Acronym => "6K"; public override string Acronym => "6K";
public override IconUsage? Icon => OsuIcon.ModSixKeys;
public override LocalisableString Description => @"Play with six keys."; public override LocalisableString Description => @"Play with six keys.";
} }
} }

View File

@@ -1,9 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
@@ -12,7 +10,6 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 7; public override int KeyCount => 7;
public override string Name => "Seven Keys"; public override string Name => "Seven Keys";
public override string Acronym => "7K"; public override string Acronym => "7K";
public override IconUsage? Icon => OsuIcon.ModSevenKeys;
public override LocalisableString Description => @"Play with seven keys."; public override LocalisableString Description => @"Play with seven keys.";
} }
} }

View File

@@ -1,9 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
@@ -12,7 +10,6 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 8; public override int KeyCount => 8;
public override string Name => "Eight Keys"; public override string Name => "Eight Keys";
public override string Acronym => "8K"; public override string Acronym => "8K";
public override IconUsage? Icon => OsuIcon.ModEightKeys;
public override LocalisableString Description => @"Play with eight keys."; public override LocalisableString Description => @"Play with eight keys.";
} }
} }

View File

@@ -1,9 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
@@ -12,7 +10,6 @@ namespace osu.Game.Rulesets.Mania.Mods
public override int KeyCount => 9; public override int KeyCount => 9;
public override string Name => "Nine Keys"; public override string Name => "Nine Keys";
public override string Acronym => "9K"; public override string Acronym => "9K";
public override IconUsage? Icon => OsuIcon.ModNineKeys;
public override LocalisableString Description => @"Play with nine keys."; public override LocalisableString Description => @"Play with nine keys.";
} }
} }

View File

@@ -4,10 +4,8 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
@@ -28,8 +26,6 @@ namespace osu.Game.Rulesets.Mania.Mods
public override double ScoreMultiplier => 0.9; public override double ScoreMultiplier => 0.9;
public override IconUsage? Icon => OsuIcon.ModNoRelease;
public override ModType Type => ModType.DifficultyReduction; public override ModType Type => ModType.DifficultyReduction;
public override Type[] IncompatibleMods => new[] { typeof(ManiaModHoldOff) }; public override Type[] IncompatibleMods => new[] { typeof(ManiaModHoldOff) };

View File

@@ -32,6 +32,26 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
[Test] [Test]
public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true }); 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] [Test]
public void TestSliderDimsOnlyAfterStartTime() public void TestSliderDimsOnlyAfterStartTime()
{ {

View File

@@ -323,7 +323,7 @@ namespace osu.Game.Rulesets.Osu.HUD
if (PositionDisplayStyle.Value == PositionDisplay.Normalised && lastObjectPosition != null) if (PositionDisplayStyle.Value == PositionDisplay.Normalised && lastObjectPosition != null)
{ {
hitPosition = AccuracyHeatmap.FindRelativeHitPosition(lastObjectPosition.Value, ((OsuHitObject)circleJudgement.HitObject).StackedEndPosition, hitPosition = AccuracyHeatmap.FindRelativeHitPosition(lastObjectPosition.Value, ((OsuHitObject)circleJudgement.HitObject).StackedEndPosition,
circleJudgement.CursorPositionAtHit.Value, objectRadius, 45) * (inner_portion / 2); circleJudgement.CursorPositionAtHit.Value, objectRadius, 45) * 0.5f;
} }
else else
{ {

View File

@@ -5,7 +5,6 @@ using System;
using System.Linq; using System.Linq;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Osu.Mods namespace osu.Game.Rulesets.Osu.Mods
{ {
@@ -14,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Name => @"Alternate"; public override string Name => @"Alternate";
public override string Acronym => @"AL"; public override string Acronym => @"AL";
public override LocalisableString Description => @"Don't use the same key twice in a row!"; public override LocalisableString Description => @"Don't use the same key twice in a row!";
public override IconUsage? Icon => OsuIcon.ModAlternate; public override IconUsage? Icon => FontAwesome.Solid.Keyboard;
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModSingleTap) }).ToArray(); public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModSingleTap) }).ToArray();
protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction != action; protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction != action;

View File

@@ -7,7 +7,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
@@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Acronym => "AD"; public override string Acronym => "AD";
public override LocalisableString Description => "Never trust the approach circles..."; public override LocalisableString Description => "Never trust the approach circles...";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override IconUsage? Icon => OsuIcon.ModApproachDifferent; public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle;
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModFreezeFrame) }; public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModFreezeFrame) };

View File

@@ -11,7 +11,6 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@@ -27,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override LocalisableString Description => "Play with blinds on your screen."; public override LocalisableString Description => "Play with blinds on your screen.";
public override string Acronym => "BL"; public override string Acronym => "BL";
public override IconUsage? Icon => OsuIcon.ModBlinds; public override IconUsage? Icon => FontAwesome.Solid.Adjust;
public override ModType Type => ModType.DifficultyIncrease; public override ModType Type => ModType.DifficultyIncrease;
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;

View File

@@ -3,11 +3,9 @@
using System; using System;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@@ -23,7 +21,6 @@ namespace osu.Game.Rulesets.Osu.Mods
{ {
public override string Name => "Bloom"; public override string Name => "Bloom";
public override string Acronym => "BM"; public override string Acronym => "BM";
public override IconUsage? Icon => OsuIcon.ModBloom;
public override ModType Type => ModType.Fun; public override ModType Type => ModType.Fun;
public override LocalisableString Description => "The cursor blooms into.. a larger cursor!"; public override LocalisableString Description => "The cursor blooms into.. a larger cursor!";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;

View File

@@ -11,9 +11,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Pooling;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
@@ -36,8 +34,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override IconUsage? Icon => OsuIcon.ModBubbles;
public override ModType Type => ModType.Fun; 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 // Compatibility with these seems potentially feasible in the future, blocked for now because they don't work as one would expect

View File

@@ -4,7 +4,6 @@
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Osu.Mods namespace osu.Game.Rulesets.Osu.Mods
{ {
@@ -14,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Acronym => "DF"; public override string Acronym => "DF";
public override IconUsage? Icon => OsuIcon.ModDeflate; public override IconUsage? Icon => FontAwesome.Solid.CompressArrowsAlt;
public override LocalisableString Description => "Hit them at the right size!"; public override LocalisableString Description => "Hit them at the right size!";

View File

@@ -7,7 +7,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
@@ -22,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{ {
public override string Name => "Depth"; public override string Name => "Depth";
public override string Acronym => "DP"; public override string Acronym => "DP";
public override IconUsage? Icon => OsuIcon.ModDepth; public override IconUsage? Icon => FontAwesome.Solid.Cube;
public override ModType Type => ModType.Fun; public override ModType Type => ModType.Fun;
public override LocalisableString Description => "3D. Almost."; public override LocalisableString Description => "3D. Almost.";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;

View File

@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override BindableBool ComboBasedSize { get; } = new BindableBool(true); public override BindableBool ComboBasedSize { get; } = new BindableBool(true);
public override float DefaultFlashlightSize => 125; public override float DefaultFlashlightSize => 200;
private OsuFlashlight flashlight = null!; private OsuFlashlight flashlight = null!;

View File

@@ -4,10 +4,8 @@
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
@@ -21,8 +19,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Acronym => "FR"; public override string Acronym => "FR";
public override IconUsage? Icon => OsuIcon.ModFreezeFrame;
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override LocalisableString Description => "Burn the notes into your memory."; public override LocalisableString Description => "Burn the notes into your memory.";

View File

@@ -4,7 +4,6 @@
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Osu.Mods namespace osu.Game.Rulesets.Osu.Mods
{ {
@@ -14,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Acronym => "GR"; public override string Acronym => "GR";
public override IconUsage? Icon => OsuIcon.ModGrow; public override IconUsage? Icon => FontAwesome.Solid.ArrowsAltV;
public override LocalisableString Description => "Hit them at the right size!"; public override LocalisableString Description => "Hit them at the right size!";

View File

@@ -9,7 +9,6 @@ using osu.Framework.Localisation;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
@@ -24,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{ {
public override string Name => "Magnetised"; public override string Name => "Magnetised";
public override string Acronym => "MG"; public override string Acronym => "MG";
public override IconUsage? Icon => OsuIcon.ModMagnetised; public override IconUsage? Icon => FontAwesome.Solid.Magnet;
public override ModType Type => ModType.Fun; public override ModType Type => ModType.Fun;
public override LocalisableString Description => "No need to chase the circles your cursor is a magnet!"; public override LocalisableString Description => "No need to chase the circles your cursor is a magnet!";
public override double ScoreMultiplier => 0.5; public override double ScoreMultiplier => 0.5;

View File

@@ -4,12 +4,10 @@
using System; using System;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
@@ -25,7 +23,6 @@ namespace osu.Game.Rulesets.Osu.Mods
{ {
public override string Name => "Repel"; public override string Name => "Repel";
public override string Acronym => "RP"; public override string Acronym => "RP";
public override IconUsage? Icon => OsuIcon.ModRepel;
public override ModType Type => ModType.Fun; public override ModType Type => ModType.Fun;
public override LocalisableString Description => "Hit objects run away!"; public override LocalisableString Description => "Hit objects run away!";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;

View File

@@ -3,9 +3,7 @@
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Osu.Mods namespace osu.Game.Rulesets.Osu.Mods
{ {
@@ -13,7 +11,6 @@ namespace osu.Game.Rulesets.Osu.Mods
{ {
public override string Name => @"Single Tap"; public override string Name => @"Single Tap";
public override string Acronym => @"SG"; public override string Acronym => @"SG";
public override IconUsage? Icon => OsuIcon.ModSingleTap;
public override LocalisableString Description => @"You must only use one key!"; public override LocalisableString Description => @"You must only use one key!";
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAlternate) }).ToArray(); public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAlternate) }).ToArray();

View File

@@ -5,7 +5,6 @@ using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
@@ -18,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{ {
public override string Name => "Spin In"; public override string Name => "Spin In";
public override string Acronym => "SI"; public override string Acronym => "SI";
public override IconUsage? Icon => OsuIcon.ModSpinIn; public override IconUsage? Icon => FontAwesome.Solid.Undo;
public override ModType Type => ModType.Fun; public override ModType Type => ModType.Fun;
public override LocalisableString Description => "Circles spin in. No approach circles."; public override LocalisableString Description => "Circles spin in. No approach circles.";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;

View File

@@ -4,10 +4,8 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
@@ -26,7 +24,6 @@ namespace osu.Game.Rulesets.Osu.Mods
{ {
public override string Name => @"Strict Tracking"; public override string Name => @"Strict Tracking";
public override string Acronym => @"ST"; public override string Acronym => @"ST";
public override IconUsage? Icon => OsuIcon.ModStrictTracking;
public override ModType Type => ModType.DifficultyIncrease; public override ModType Type => ModType.DifficultyIncrease;
public override LocalisableString Description => @"Once you start a slider, follow precisely or get a miss."; public override LocalisableString Description => @"Once you start a slider, follow precisely or get a miss.";
public override double ScoreMultiplier => 1.0; public override double ScoreMultiplier => 1.0;

View File

@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Name => "Target Practice"; public override string Name => "Target Practice";
public override string Acronym => "TP"; public override string Acronym => "TP";
public override ModType Type => ModType.Conversion; public override ModType Type => ModType.Conversion;
public override IconUsage? Icon => OsuIcon.ModTargetPractice; public override IconUsage? Icon => OsuIcon.ModTarget;
public override LocalisableString Description => @"Practice keeping up with the beat of the song."; public override LocalisableString Description => @"Practice keeping up with the beat of the song.";
public override double ScoreMultiplier => 0.1; public override double ScoreMultiplier => 0.1;

View File

@@ -4,9 +4,7 @@
using System; using System;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
@@ -20,7 +18,6 @@ namespace osu.Game.Rulesets.Osu.Mods
{ {
public override string Name => "Traceable"; public override string Name => "Traceable";
public override string Acronym => "TC"; public override string Acronym => "TC";
public override IconUsage? Icon => OsuIcon.ModTraceable;
public override ModType Type => ModType.Fun; public override ModType Type => ModType.Fun;
public override LocalisableString Description => "Put your faith in the approach circles..."; public override LocalisableString Description => "Put your faith in the approach circles...";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;

View File

@@ -6,7 +6,6 @@ using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
@@ -19,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{ {
public override string Name => "Transform"; public override string Name => "Transform";
public override string Acronym => "TR"; public override string Acronym => "TR";
public override IconUsage? Icon => OsuIcon.ModTransform; public override IconUsage? Icon => FontAwesome.Solid.ArrowsAlt;
public override ModType Type => ModType.Fun; public override ModType Type => ModType.Fun;
public override LocalisableString Description => "Everything rotates. EVERYTHING."; public override LocalisableString Description => "Everything rotates. EVERYTHING.";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;

View File

@@ -7,7 +7,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
@@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{ {
public override string Name => "Wiggle"; public override string Name => "Wiggle";
public override string Acronym => "WG"; public override string Acronym => "WG";
public override IconUsage? Icon => OsuIcon.ModWiggle; public override IconUsage? Icon => FontAwesome.Solid.Certificate;
public override ModType Type => ModType.Fun; public override ModType Type => ModType.Fun;
public override LocalisableString Description => "They just won't stay still..."; public override LocalisableString Description => "They just won't stay still...";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;

View File

@@ -3,10 +3,7 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.Taiko.UI;
using osuTK; using osuTK;
@@ -15,34 +12,6 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods
{ {
public partial class TestSceneTaikoModFlashlight : TaikoModTestScene 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(1f)]
[TestCase(0.5f)] [TestCase(0.5f)]
[TestCase(1.25f)] [TestCase(1.25f)]

View File

@@ -4,7 +4,6 @@
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@@ -18,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
public override string Acronym => "CS"; public override string Acronym => "CS";
public override double ScoreMultiplier => 0.9; public override double ScoreMultiplier => 0.9;
public override LocalisableString Description => "No more tricky speed changes!"; public override LocalisableString Description => "No more tricky speed changes!";
public override IconUsage? Icon => OsuIcon.ModConstantSpeed; public override IconUsage? Icon => FontAwesome.Solid.Equals;
public override ModType Type => ModType.Conversion; public override ModType Type => ModType.Conversion;
public void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset) public void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)

View File

@@ -47,15 +47,28 @@ namespace osu.Game.Rulesets.Taiko.Mods
{ {
this.taikoPlayfield = taikoPlayfield; this.taikoPlayfield = taikoPlayfield;
FlashlightSize = new Vector2(0, GetSize()); FlashlightSize = adjustSizeForPlayfieldAspectRatio(GetSize());
FlashlightSmoothness = 1.4f; FlashlightSmoothness = 1.4f;
AddLayout(flashlightProperties); 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) protected override void UpdateFlashlightSize(float size)
{ {
this.TransformTo(nameof(FlashlightSize), new Vector2(0, size), FLASHLIGHT_FADE_DURATION); this.TransformTo(nameof(FlashlightSize), adjustSizeForPlayfieldAspectRatio(size), FLASHLIGHT_FADE_DURATION);
} }
protected override string FragmentShader => "CircularFlashlight"; protected override string FragmentShader => "CircularFlashlight";
@@ -69,7 +82,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
FlashlightPosition = ToLocalSpace(taikoPlayfield.HitTarget.ScreenSpaceDrawQuad.Centre); FlashlightPosition = ToLocalSpace(taikoPlayfield.HitTarget.ScreenSpaceDrawQuad.Centre);
ClearTransforms(targetMember: nameof(FlashlightSize)); ClearTransforms(targetMember: nameof(FlashlightSize));
FlashlightSize = new Vector2(0, GetSize()); FlashlightSize = adjustSizeForPlayfieldAspectRatio(GetSize());
flashlightProperties.Validate(); flashlightProperties.Validate();
} }

View File

@@ -5,12 +5,10 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
@@ -23,7 +21,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
public override string Acronym => "SR"; public override string Acronym => "SR";
public override double ScoreMultiplier => 0.6; public override double ScoreMultiplier => 0.6;
public override LocalisableString Description => "Simplify tricky rhythms!"; public override LocalisableString Description => "Simplify tricky rhythms!";
public override IconUsage? Icon => OsuIcon.ModSimplifiedRhythm;
public override ModType Type => ModType.DifficultyReduction; public override ModType Type => ModType.DifficultyReduction;
[SettingSource("1/3 to 1/2 conversion", "Converts 1/3 patterns to 1/2 rhythm.")] [SettingSource("1/3 to 1/2 conversion", "Converts 1/3 patterns to 1/2 rhythm.")]

View File

@@ -6,11 +6,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Timing;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
@@ -26,7 +24,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
{ {
public override string Name => @"Single Tap"; public override string Name => @"Single Tap";
public override string Acronym => @"SG"; 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 LocalisableString Description => @"One key for dons, one key for kats.";
public override double ScoreMultiplier => 1.0; public override double ScoreMultiplier => 1.0;

View File

@@ -3,10 +3,8 @@
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
@@ -18,7 +16,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
public override string Name => "Swap"; public override string Name => "Swap";
public override string Acronym => "SW"; public override string Acronym => "SW";
public override LocalisableString Description => @"Dons become kats, kats become dons"; public override LocalisableString Description => @"Dons become kats, kats become dons";
public override IconUsage? Icon => OsuIcon.ModSwap;
public override ModType Type => ModType.Conversion; public override ModType Type => ModType.Conversion;
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModRandom)).ToArray(); public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModRandom)).ToArray();

View File

@@ -66,18 +66,6 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("toggle black background", () => blackBackground?.FadeTo(1 - blackBackground.Alpha, 300, Easing.OutQuint)); 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); AddSliderStep("set player score", 50, 1_000_000, 700_000, v => gameplayState.ScoreProcessor.TotalScore.Value = v);
} }
@@ -120,45 +108,6 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for 1st spot", () => leaderboard.TrackedScore!.ScorePosition.Value, () => Is.EqualTo(1)); 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] [Test]
public void TestLayoutWithManyScores() public void TestLayoutWithManyScores()
{ {

View File

@@ -24,14 +24,6 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
protected TestReplayPlayer Player = null!; protected TestReplayPlayer Player = null!;
[Test]
public void TestFailedBeatmapLoad()
{
loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo, withHitObjects: false));
AddUntilStep("wait for exit", () => Player.IsCurrentScreen());
}
[Test] [Test]
public void TestPauseViaSpace() public void TestPauseViaSpace()
{ {

View File

@@ -28,7 +28,6 @@ using osu.Game.Storyboards;
using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Beatmaps.IO;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer namespace osu.Game.Tests.Visual.Multiplayer
{ {
@@ -303,69 +302,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddWaitStep("wait a bit", 10); 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] [Test]
[FlakyTest] [FlakyTest]
public void TestMostInSyncUserIsAudioSourceIfNoneMaximised() public void TestMostInSyncUserIsAudioSource()
{ {
start(new[] { PLAYER_1_ID, PLAYER_2_ID }); start(new[] { PLAYER_1_ID, PLAYER_2_ID });
loadSpectateScreen(); loadSpectateScreen();

View File

@@ -181,7 +181,7 @@ namespace osu.Game.Tests.Visual.Navigation
AddUntilStep("beatmap in song select", () => AddUntilStep("beatmap in song select", () =>
{ {
var songSelect = (SoloSongSelect)Game.ScreenStack.CurrentScreen; var songSelect = (SoloSongSelect)Game.ScreenStack.CurrentScreen;
return songSelect.ChildrenOfType<BeatmapCarousel>().Single().GetCarouselItems()!.Any(i => i.Model is GroupedBeatmapSet gbs && gbs.BeatmapSet.MatchesOnlineID(getImport())); return songSelect.ChildrenOfType<BeatmapCarousel>().Single().GetCarouselItems()!.Any(i => i.Model is BeatmapSetInfo bsi && bsi.MatchesOnlineID(getImport()));
}); });
} }

View File

@@ -133,8 +133,6 @@ namespace osu.Game.Tests.Visual.Settings
{ {
public Bindable<Vector2> AreaOffset { get; } = new Bindable<Vector2>(); public Bindable<Vector2> AreaOffset { get; } = new Bindable<Vector2>();
public Bindable<Vector2> AreaSize { 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>(); public Bindable<float> Rotation { get; } = new Bindable<float>();

View File

@@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
]; ];
var results = await runGrouping(GroupMode.None, beatmapSets); var results = await runGrouping(GroupMode.None, 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<BeatmapSetInfo>(), Is.EquivalentTo(beatmapSets));
Assert.That(results.Select(r => r.Model).OfType<BeatmapInfo>(), Is.EquivalentTo(allBeatmaps)); Assert.That(results.Select(r => r.Model).OfType<BeatmapInfo>(), Is.EquivalentTo(allBeatmaps));
assertTotal(results, beatmapSets.Count + allBeatmaps.Length); assertTotal(results, beatmapSets.Count + allBeatmaps.Length);
} }
@@ -74,11 +74,11 @@ namespace osu.Game.Tests.Visual.SongSelectV2
addBeatmapSet(applyBeatmap('_'), beatmapSets, out var underscoreBeatmap); addBeatmapSet(applyBeatmap('_'), beatmapSets, out var underscoreBeatmap);
var results = await runGrouping(mode, beatmapSets); var results = await runGrouping(mode, beatmapSets);
assertGroup(results, 0, "0-9", fiveBeatmap.Beatmaps.Concat(fourBeatmap.Beatmaps), ref total); assertGroup(results, 0, "0-9", new[] { fiveBeatmap, fourBeatmap }, ref total);
assertGroup(results, 1, "A", aBeatmap.Beatmaps, ref total); assertGroup(results, 1, "A", new[] { aBeatmap }, ref total);
assertGroup(results, 2, "F", fBeatmap.Beatmaps, ref total); assertGroup(results, 2, "F", new[] { fBeatmap }, ref total);
assertGroup(results, 3, "Z", zBeatmap.Beatmaps, ref total); assertGroup(results, 3, "Z", new[] { zBeatmap }, ref total);
assertGroup(results, 4, "Other", dashBeatmap.Beatmaps.Concat(underscoreBeatmap.Beatmaps), ref total); assertGroup(results, 4, "Other", new[] { dashBeatmap, underscoreBeatmap }, ref total);
assertTotal(results, 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); addBeatmapSet(s => s.DateAdded = DateTimeOffset.Now.AddMonths(-2).AddDays(-3), beatmapSets, out var twoMonthsAgoBeatmap);
var results = await runGrouping(GroupMode.DateAdded, beatmapSets); var results = await runGrouping(GroupMode.DateAdded, beatmapSets);
assertGroup(results, 0, "Today", todayBeatmap.Beatmaps, ref total); assertGroup(results, 0, "Today", new[] { todayBeatmap }, ref total);
assertGroup(results, 1, "Yesterday", yesterdayBeatmap.Beatmaps, ref total); assertGroup(results, 1, "Yesterday", new[] { yesterdayBeatmap }, ref total);
assertGroup(results, 2, "Last week", lastWeekBeatmap.Beatmaps, ref total); assertGroup(results, 2, "Last week", new[] { lastWeekBeatmap }, ref total);
assertGroup(results, 3, "Last month", lastMonthBeatmap.Beatmaps, ref total); assertGroup(results, 3, "Last month", new[] { lastMonthBeatmap }, ref total);
assertGroup(results, 4, "1 month ago", oneMonthAgoBeatmap.Beatmaps, ref total); assertGroup(results, 4, "1 month ago", new[] { oneMonthAgoBeatmap }, ref total);
assertGroup(results, 5, "2 months ago", twoMonthsAgoBeatmap.Beatmaps, ref total); assertGroup(results, 5, "2 months ago", new[] { twoMonthsAgoBeatmap }, ref total);
assertTotal(results, total); assertTotal(results, total);
} }
@@ -139,13 +139,13 @@ namespace osu.Game.Tests.Visual.SongSelectV2
addBeatmapSet(applyLastPlayed(null), beatmapSets, out var neverBeatmap); addBeatmapSet(applyLastPlayed(null), beatmapSets, out var neverBeatmap);
var results = await runGrouping(GroupMode.LastPlayed, beatmapSets); var results = await runGrouping(GroupMode.LastPlayed, beatmapSets);
assertGroup(results, 0, "Today", todayBeatmap.Beatmaps, ref total); assertGroup(results, 0, "Today", new[] { todayBeatmap }, ref total);
assertGroup(results, 1, "Yesterday", yesterdayBeatmap.Beatmaps, ref total); assertGroup(results, 1, "Yesterday", new[] { yesterdayBeatmap }, ref total);
assertGroup(results, 2, "Last week", lastWeekBeatmap.Beatmaps, ref total); assertGroup(results, 2, "Last week", new[] { lastWeekBeatmap }, ref total);
assertGroup(results, 3, "Last month", lastMonthBeatmap.Beatmaps, ref total); assertGroup(results, 3, "Last month", new[] { lastMonthBeatmap }, ref total);
assertGroup(results, 4, "1 month ago", oneMonthAgoBeatmap.Beatmaps, ref total); assertGroup(results, 4, "1 month ago", new[] { oneMonthAgoBeatmap }, ref total);
assertGroup(results, 5, "2 months ago", twoMonthsBeatmap.Beatmaps, ref total); assertGroup(results, 5, "2 months ago", new[] { twoMonthsBeatmap }, ref total);
assertGroup(results, 6, "Never", neverBeatmap.Beatmaps, ref total); assertGroup(results, 6, "Never", new[] { neverBeatmap }, ref total);
assertTotal(results, total); assertTotal(results, total);
} }
@@ -162,8 +162,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
var results = await runGrouping(GroupMode.LastPlayed, beatmapSets); var results = await runGrouping(GroupMode.LastPlayed, beatmapSets);
int total = 0; int total = 0;
assertGroup(results, 0, "Today", [set.Beatmaps[2]], ref total); assertGroup(results, 0, "Today", new[] { set }, ref total);
assertGroup(results, 1, "Never", [set.Beatmaps[0], set.Beatmaps[1]], ref total);
assertTotal(results, total); assertTotal(results, total);
} }
@@ -177,8 +176,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
var results = await runGrouping(GroupMode.LastPlayed, beatmapSets); var results = await runGrouping(GroupMode.LastPlayed, beatmapSets);
int total = 0; int total = 0;
assertGroup(results, 0, "Over 5 months ago", overFiveMonthsBeatmap.Beatmaps, ref total); assertGroup(results, 0, "Over 5 months ago", new[] { overFiveMonthsBeatmap }, ref total);
assertGroup(results, 1, "Never", neverBeatmap.Beatmaps, ref total); assertGroup(results, 1, "Never", new[] { neverBeatmap }, ref total);
assertTotal(results, total); assertTotal(results, total);
} }
@@ -208,14 +207,14 @@ namespace osu.Game.Tests.Visual.SongSelectV2
addBeatmapSet(s => s.Status = BeatmapOnlineStatus.LocallyModified, beatmapSets, out var localBeatmap); addBeatmapSet(s => s.Status = BeatmapOnlineStatus.LocallyModified, beatmapSets, out var localBeatmap);
var results = await runGrouping(GroupMode.RankedStatus, beatmapSets); var results = await runGrouping(GroupMode.RankedStatus, beatmapSets);
assertGroup(results, 0, "Ranked", rankedBeatmap.Beatmaps.Concat(approvedBeatmap.Beatmaps), ref total); assertGroup(results, 0, "Ranked", new[] { rankedBeatmap, approvedBeatmap }, ref total);
assertGroup(results, 1, "Qualified", qualifiedBeatmap.Beatmaps, ref total); assertGroup(results, 1, "Qualified", new[] { qualifiedBeatmap }, ref total);
assertGroup(results, 2, "WIP", wipBeatmap.Beatmaps, ref total); assertGroup(results, 2, "WIP", new[] { wipBeatmap }, ref total);
assertGroup(results, 3, "Pending", pendingBeatmap.Beatmaps, ref total); assertGroup(results, 3, "Pending", new[] { pendingBeatmap }, ref total);
assertGroup(results, 4, "Graveyard", graveyardBeatmap.Beatmaps, ref total); assertGroup(results, 4, "Graveyard", new[] { graveyardBeatmap }, ref total);
assertGroup(results, 5, "Local", localBeatmap.Beatmaps, ref total); assertGroup(results, 5, "Local", new[] { localBeatmap }, ref total);
assertGroup(results, 6, "Unknown", noneBeatmap.Beatmaps, ref total); assertGroup(results, 6, "Unknown", new[] { noneBeatmap }, ref total);
assertGroup(results, 7, "Loved", lovedBeatmap.Beatmaps, ref total); assertGroup(results, 7, "Loved", new[] { lovedBeatmap }, ref total);
assertTotal(results, total); assertTotal(results, total);
} }
@@ -241,12 +240,12 @@ namespace osu.Game.Tests.Visual.SongSelectV2
addBeatmapSet(applyBPM(330), beatmapSets, out var beatmap330); addBeatmapSet(applyBPM(330), beatmapSets, out var beatmap330);
var results = await runGrouping(GroupMode.BPM, beatmapSets); var results = await runGrouping(GroupMode.BPM, beatmapSets);
assertGroup(results, 0, "Under 60 BPM", beatmap30.Beatmaps, ref total); assertGroup(results, 0, "Under 60 BPM", new[] { beatmap30 }, ref total);
assertGroup(results, 1, "60 - 70 BPM", (beatmap59.Beatmaps.Concat(beatmap60.Beatmaps)), ref total); assertGroup(results, 1, "60 - 70 BPM", new[] { beatmap59, beatmap60 }, ref total);
assertGroup(results, 2, "90 - 100 BPM", (beatmap90.Beatmaps.Concat(beatmap95.Beatmaps)), ref total); assertGroup(results, 2, "90 - 100 BPM", new[] { beatmap90, beatmap95 }, ref total);
assertGroup(results, 3, "270 - 280 BPM", (beatmap269.Beatmaps.Concat(beatmap270.Beatmaps)), ref total); assertGroup(results, 3, "270 - 280 BPM", new[] { beatmap269, beatmap270 }, ref total);
assertGroup(results, 4, "290 - 300 BPM", beatmap299.Beatmaps, ref total); assertGroup(results, 4, "290 - 300 BPM", new[] { beatmap299 }, ref total);
assertGroup(results, 5, "Over 300 BPM", (beatmap300.Beatmaps.Concat(beatmap330.Beatmaps)), ref total); assertGroup(results, 5, "Over 300 BPM", new[] { beatmap300, beatmap330 }, ref total);
assertTotal(results, total); assertTotal(results, total);
} }
@@ -273,10 +272,10 @@ namespace osu.Game.Tests.Visual.SongSelectV2
addBeatmapSet(applyStars(7), beatmapSets, out var beatmap7); addBeatmapSet(applyStars(7), beatmapSets, out var beatmap7);
var results = await runGrouping(GroupMode.Difficulty, beatmapSets); var results = await runGrouping(GroupMode.Difficulty, beatmapSets);
assertGroup(results, 0, "Below 1 Star", beatmapBelow1.Beatmaps, ref total); assertGroup(results, 0, "Below 1 Star", new[] { beatmapBelow1 }, ref total);
assertGroup(results, 1, "1 Star", (beatmapAbove1.Beatmaps.Concat(beatmapAlmost2.Beatmaps)), ref total); assertGroup(results, 1, "1 Star", new[] { beatmapAbove1, beatmapAlmost2 }, ref total);
assertGroup(results, 2, "2 Stars", (beatmap2.Beatmaps.Concat(beatmapAbove2.Beatmaps)), ref total); assertGroup(results, 2, "2 Stars", new[] { beatmap2, beatmapAbove2 }, ref total);
assertGroup(results, 3, "7 Stars", beatmap7.Beatmaps, ref total); assertGroup(results, 3, "7 Stars", new[] { beatmap7 }, ref total);
assertTotal(results, total); assertTotal(results, total);
} }
@@ -305,11 +304,11 @@ namespace osu.Game.Tests.Visual.SongSelectV2
addBeatmapSet(applyLength(630_000), beatmapSets, out var beatmap10Min30Sec); addBeatmapSet(applyLength(630_000), beatmapSets, out var beatmap10Min30Sec);
var results = await runGrouping(GroupMode.Length, beatmapSets); var results = await runGrouping(GroupMode.Length, beatmapSets);
assertGroup(results, 0, "1 minute or less", (beatmap30Sec.Beatmaps.Concat(beatmap1Min.Beatmaps)), ref total); assertGroup(results, 0, "1 minute or less", new[] { beatmap30Sec, beatmap1Min }, ref total);
assertGroup(results, 1, "2 minutes or less", (beatmap1Min30Sec.Beatmaps.Concat(beatmap2Min.Beatmaps)), ref total); assertGroup(results, 1, "2 minutes or less", new[] { beatmap1Min30Sec, beatmap2Min }, ref total);
assertGroup(results, 2, "5 minutes or less", beatmap5Min.Beatmaps, ref total); assertGroup(results, 2, "5 minutes or less", new[] { beatmap5Min }, ref total);
assertGroup(results, 3, "10 minutes or less", (beatmap6Min.Beatmaps.Concat(beatmap10Min.Beatmaps)), ref total); assertGroup(results, 3, "10 minutes or less", new[] { beatmap6Min, beatmap10Min }, ref total);
assertGroup(results, 4, "Over 10 minutes", beatmap10Min30Sec.Beatmaps, ref total); assertGroup(results, 4, "Over 10 minutes", new[] { beatmap10Min30Sec }, ref total);
assertTotal(results, total); assertTotal(results, total);
} }
@@ -335,10 +334,10 @@ namespace osu.Game.Tests.Visual.SongSelectV2
addBeatmapSet(s => s.DateRanked = null, beatmapSets, out var beatmapUnranked); addBeatmapSet(s => s.DateRanked = null, beatmapSets, out var beatmapUnranked);
var results = await runGrouping(GroupMode.DateRanked, beatmapSets); var results = await runGrouping(GroupMode.DateRanked, beatmapSets);
assertGroup(results, 0, "2025", beatmap2025.Beatmaps, ref total); assertGroup(results, 0, "2025", new[] { beatmap2025 }, ref total);
assertGroup(results, 1, "2010", beatmap2010.Beatmaps, ref total); assertGroup(results, 1, "2010", new[] { beatmap2010 }, ref total);
assertGroup(results, 2, "2007", (beatmapOct2007.Beatmaps.Concat(beatmapDec2007.Beatmaps)), ref total); assertGroup(results, 2, "2007", new[] { beatmapOct2007, beatmapDec2007 }, ref total);
assertGroup(results, 3, "Unranked", beatmapUnranked.Beatmaps, ref total); assertGroup(results, 3, "Unranked", new[] { beatmapUnranked }, ref total);
assertTotal(results, total); assertTotal(results, total);
} }
@@ -358,9 +357,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
addBeatmapSet(s => s.Beatmaps[0].Metadata.Source = string.Empty, beatmapSets, out var beatmapUnsourced); addBeatmapSet(s => s.Beatmaps[0].Metadata.Source = string.Empty, beatmapSets, out var beatmapUnsourced);
var results = await runGrouping(GroupMode.Source, beatmapSets); var results = await runGrouping(GroupMode.Source, beatmapSets);
assertGroup(results, 0, "Cool Game", (beatmapCoolGame.Beatmaps.Concat(beatmapCoolGameB.Beatmaps)), ref total); assertGroup(results, 0, "Cool Game", new[] { beatmapCoolGame, beatmapCoolGameB }, ref total);
assertGroup(results, 1, "Nice Movie", beatmapNiceMovie.Beatmaps, ref total); assertGroup(results, 1, "Nice Movie", new[] { beatmapNiceMovie }, ref total);
assertGroup(results, 2, "Unsourced", beatmapUnsourced.Beatmaps, ref total); assertGroup(results, 2, "Unsourced", new[] { beatmapUnsourced }, ref total);
assertTotal(results, total); assertTotal(results, total);
} }
@@ -376,7 +375,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
return await groupingFilter.Run(beatmapSets.SelectMany(s => s.Beatmaps.Select(b => new CarouselItem(b))).ToList(), CancellationToken.None); 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<BeatmapInfo> expectedBeatmaps, ref int totalItems) private static void assertGroup(List<CarouselItem> items, int index, string expectedTitle, IEnumerable<BeatmapSetInfo> expectedBeatmapSets, ref int totalItems)
{ {
var groupItem = items.Where(i => i.Model is GroupDefinition).ElementAtOrDefault(index); var groupItem = items.Where(i => i.Model is GroupDefinition).ElementAtOrDefault(index);
@@ -391,7 +390,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
var groupModel = (GroupDefinition)groupItem.Model; var groupModel = (GroupDefinition)groupItem.Model;
Assert.That(groupModel.Title, Is.EqualTo(expectedTitle)); Assert.That(groupModel.Title, Is.EqualTo(expectedTitle));
Assert.That(itemsInGroup.Select(i => i.Model).OfType<BeatmapInfo>(), Is.EquivalentTo(expectedBeatmaps)); Assert.That(itemsInGroup.Select(i => i.Model).OfType<BeatmapInfo>(), Is.EquivalentTo(expectedBeatmapSets.SelectMany(bs => bs.Beatmaps)));
totalItems += itemsInGroup.Count() + 1; totalItems += itemsInGroup.Count() + 1;
} }

View File

@@ -237,7 +237,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
// Using groupingFilter.SetItems.Count alone doesn't work. // 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. // 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 GroupedBeatmapSet)); return groupingFilter.SetItems.Sum(s => s.Value.Count(i => i.Model is BeatmapSetInfo));
}, () => Is.EqualTo(expected)); }, () => Is.EqualTo(expected));
} }
@@ -440,7 +440,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
public BeatmapInfo? SelectedBeatmapInfo => CurrentSelection as BeatmapInfo; public BeatmapInfo? SelectedBeatmapInfo => CurrentSelection as BeatmapInfo;
public BeatmapSetInfo? SelectedBeatmapSet => SelectedBeatmapInfo?.BeatmapSet; public BeatmapSetInfo? SelectedBeatmapSet => SelectedBeatmapInfo?.BeatmapSet;
public new GroupedBeatmapSet? ExpandedBeatmapSet => base.ExpandedBeatmapSet; public new BeatmapSetInfo? ExpandedBeatmapSet => base.ExpandedBeatmapSet;
public new GroupDefinition? ExpandedGroup => base.ExpandedGroup; public new GroupDefinition? ExpandedGroup => base.ExpandedGroup;
public TestBeatmapCarousel() public TestBeatmapCarousel()

View File

@@ -22,6 +22,8 @@ using osu.Game.Overlays.Toolbar;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Footer;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
using osu.Game.Screens.Select.Filter; using osu.Game.Screens.Select.Filter;
using osu.Game.Screens.SelectV2; using osu.Game.Screens.SelectV2;
@@ -41,6 +43,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
protected Screens.SelectV2.SongSelect SongSelect { get; private set; } = null!; protected Screens.SelectV2.SongSelect SongSelect { get; private set; } = null!;
protected BeatmapCarousel Carousel => SongSelect.ChildrenOfType<BeatmapCarousel>().Single(); protected BeatmapCarousel Carousel => SongSelect.ChildrenOfType<BeatmapCarousel>().Single();
[Cached]
protected readonly ScreenFooter Footer;
[Cached] [Cached]
private readonly OsuLogo logo; private readonly OsuLogo logo;
@@ -67,6 +72,10 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{ {
State = { Value = Visibility.Visible }, State = { Value = Visibility.Visible },
}, },
Footer = new ScreenFooter
{
BackButtonPressed = () => Stack.CurrentScreen.Exit(),
},
logo = new OsuLogo logo = new OsuLogo
{ {
Alpha = 0f, Alpha = 0f,
@@ -102,6 +111,14 @@ namespace osu.Game.Tests.Visual.SongSelectV2
Add(beatmapStore); Add(beatmapStore);
} }
protected override void LoadComplete()
{
base.LoadComplete();
Stack.ScreenPushed += updateFooter;
Stack.ScreenExited += updateFooter;
}
public override void SetUpSteps() public override void SetUpSteps()
{ {
base.SetUpSteps(); base.SetUpSteps();
@@ -190,5 +207,38 @@ namespace osu.Game.Tests.Visual.SongSelectV2
} }
protected void WaitForSuspension() => AddUntilStep("wait for not current", () => !SongSelect.AsNonNull().IsCurrentScreen()); 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>());
}
}
} }
} }

View File

@@ -396,9 +396,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
ApplyToFilterAndWaitForFilter("filter first away", c => c.UserStarDifficulty.Min = 3); ApplyToFilterAndWaitForFilter("filter first away", c => c.UserStarDifficulty.Min = 3);
SelectNextPanel(); SelectNextPanel();
AddAssert("keyboard selected is first set", AddAssert("keyboard selected is first set", () => GetKeyboardSelectedPanel()?.Item?.Model, () => Is.EqualTo(BeatmapSets.First()));
() => (GetKeyboardSelectedPanel()?.Item?.Model as GroupedBeatmapSet)?.BeatmapSet,
() => Is.EqualTo(BeatmapSets.First()));
} }
[Test] [Test]
@@ -415,9 +413,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
ApplyToFilterAndWaitForFilter("filter first away", c => c.UserStarDifficulty.Min = 3); ApplyToFilterAndWaitForFilter("filter first away", c => c.UserStarDifficulty.Min = 3);
SelectPrevPanel(); SelectPrevPanel();
AddAssert("keyboard selected is last set", AddAssert("keyboard selected is last set", () => GetKeyboardSelectedPanel()?.Item?.Model, () => Is.EqualTo(BeatmapSets.Last()));
() => (GetKeyboardSelectedPanel()?.Item?.Model as GroupedBeatmapSet)?.BeatmapSet,
() => Is.EqualTo(BeatmapSets.Last()));
} }
[Test] [Test]
@@ -432,9 +428,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
ApplyToFilterAndWaitForFilter("filter last set away", c => c.SearchText = BeatmapSets.First().Metadata.Title); ApplyToFilterAndWaitForFilter("filter last set away", c => c.SearchText = BeatmapSets.First().Metadata.Title);
SelectPrevPanel(); SelectPrevPanel();
AddAssert("keyboard selected is first set", AddAssert("keyboard selected is first set", () => GetKeyboardSelectedPanel()?.Item?.Model, () => Is.EqualTo(BeatmapSets.First()));
() => (GetKeyboardSelectedPanel()?.Item?.Model as GroupedBeatmapSet)?.BeatmapSet,
() => Is.EqualTo(BeatmapSets.First()));
} }
[Test] [Test]
@@ -450,9 +444,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
// Single result is automatically selected for us, so we iterate once backwards to the set header. // Single result is automatically selected for us, so we iterate once backwards to the set header.
SelectPrevPanel(); SelectPrevPanel();
AddAssert("keyboard selected is second set", AddAssert("keyboard selected is second set", () => GetKeyboardSelectedPanel()?.Item?.Model, () => Is.EqualTo(BeatmapSets.Last()));
() => (GetKeyboardSelectedPanel()?.Item?.Model as GroupedBeatmapSet)?.BeatmapSet,
() => Is.EqualTo(BeatmapSets.Last()));
} }
} }
} }

View File

@@ -1,120 +0,0 @@
// 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);
});
}
}

View File

@@ -22,16 +22,12 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{ {
private BeatmapSetInfo baseTestBeatmap = null!; private BeatmapSetInfo baseTestBeatmap = null!;
private const int initial_filter_count = 3;
[SetUpSteps] [SetUpSteps]
public void SetUpSteps() public void SetUpSteps()
{ {
RemoveAllBeatmaps(); RemoveAllBeatmaps();
CreateCarousel(); CreateCarousel();
WaitForFiltering();
AddBeatmaps(1, 3); AddBeatmaps(1, 3);
WaitForFiltering();
AddStep("generate and add test beatmap", () => AddStep("generate and add test beatmap", () =>
{ {
baseTestBeatmap = TestResources.CreateTestBeatmapSetInfo(3); baseTestBeatmap = TestResources.CreateTestBeatmapSetInfo(3);
@@ -46,9 +42,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
b.Metadata = metadata; b.Metadata = metadata;
BeatmapSets.Add(baseTestBeatmap); BeatmapSets.Add(baseTestBeatmap);
}); });
WaitForFiltering();
AddAssert("filter count correct", () => Carousel.FilterCount, () => Is.EqualTo(initial_filter_count)); WaitForFiltering();
} }
[Test] [Test]
@@ -86,18 +81,12 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddUntilStep("is scrolled to end", () => Carousel.ChildrenOfType<UserTrackingScrollContainer>().Single().IsScrolledToEnd()); AddUntilStep("is scrolled to end", () => Carousel.ChildrenOfType<UserTrackingScrollContainer>().Single().IsScrolledToEnd());
updateBeatmap(b => updateBeatmap(b => b.Metadata = new BeatmapMetadata
{
// 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", Artist = "updated test",
Title = $"beatmap {RNG.Next().ToString()}" Title = $"beatmap {RNG.Next().ToString()}"
};
}); });
assertDidFilter();
WaitForFiltering(); WaitForFiltering();
AddAssert("scroll is still at end", () => Carousel.ChildrenOfType<UserTrackingScrollContainer>().Single().IsScrolledToEnd()); AddAssert("scroll is still at end", () => Carousel.ChildrenOfType<UserTrackingScrollContainer>().Single().IsScrolledToEnd());
@@ -124,14 +113,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddStep("find panel", () => panel = Carousel.ChildrenOfType<PanelBeatmapSet>().Single(p => p.ChildrenOfType<OsuSpriteText>().Any(t => t.Text.ToString() == "beatmap"))); AddStep("find panel", () => panel = Carousel.ChildrenOfType<PanelBeatmapSet>().Single(p => p.ChildrenOfType<OsuSpriteText>().Any(t => t.Text.ToString() == "beatmap")));
updateBeatmap(b => updateBeatmap(b => b.Metadata = metadata);
{
b.Metadata = metadata;
// hash will be updated when important metadata changes, such as title, difficulty, author etc.
b.Hash = "new hash";
});
assertDidFilter();
WaitForFiltering(); WaitForFiltering();
AddAssert("drawables unchanged", () => Carousel.ChildrenOfType<Panel>(), () => Is.EqualTo(originalDrawables)); AddAssert("drawables unchanged", () => Carousel.ChildrenOfType<Panel>(), () => Is.EqualTo(originalDrawables));
@@ -140,41 +123,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
} }
[Test] [Test]
public void TestOnlineStatusUpdated() public void TestSelectionHeld()
{
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(); SelectNextSet();
@@ -182,17 +131,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0])); 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])); AddAssert("visible panel is updateable beatmap", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
updateBeatmap(b => updateBeatmap();
{
if (hashChanged)
b.Hash = "new hash";
});
if (hashChanged)
assertDidFilter();
else
assertDidNotFilter();
WaitForFiltering(); WaitForFiltering();
AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0])); AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
@@ -209,7 +148,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddAssert("visible panel is updateable beatmap", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0])); AddAssert("visible panel is updateable beatmap", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
updateBeatmap(b => b.DifficultyName = "new name"); updateBeatmap(b => b.DifficultyName = "new name");
assertDidFilter();
WaitForFiltering(); WaitForFiltering();
AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0])); AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
@@ -226,7 +164,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddAssert("visible panel is updateable beatmap", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0])); AddAssert("visible panel is updateable beatmap", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
updateBeatmap(b => b.OnlineID = b.OnlineID + 1); updateBeatmap(b => b.OnlineID = b.OnlineID + 1);
assertDidFilter();
WaitForFiltering(); WaitForFiltering();
AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0])); AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
@@ -402,10 +339,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddAssert("Order didn't change", () => Carousel.PostFilterBeatmaps.Select(b => b.ID), () => Is.EqualTo(originalOrder)); 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) private void updateBeatmap(Action<BeatmapInfo>? updateBeatmap = null, Action<BeatmapSetInfo>? updateSet = null)
{ {
AddStep("update beatmap with different reference", () => AddStep("update beatmap with different reference", () =>

View File

@@ -7,7 +7,6 @@ using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@@ -43,8 +42,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
private DialogOverlay dialogOverlay = null!; private DialogOverlay dialogOverlay = null!;
private LeaderboardManager leaderboardManager = 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) protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{ {
@@ -54,7 +52,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(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(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, API));
dependencies.Cache(leaderboardManager = new LeaderboardManager()); dependencies.Cache(leaderboardManager = new LeaderboardManager());
dependencies.CacheAs(onlineLookupResult); dependencies.Cache(lookupSource = new RealmPopulatingOnlineLookupSource());
Dependencies.Cache(Realm); Dependencies.Cache(Realm);
@@ -70,6 +68,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
}); });
LoadComponent(leaderboardManager); LoadComponent(leaderboardManager);
LoadComponent(lookupSource);
Child = contentContainer = new OsuContextMenuContainer Child = contentContainer = new OsuContextMenuContainer
{ {

View File

@@ -4,12 +4,13 @@
using System; using System;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Graphics;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Extensions;
using osu.Game.Models; 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.Online.API.Requests.Responses;
using osu.Game.Screens.SelectV2; using osu.Game.Screens.SelectV2;
@@ -17,25 +18,64 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{ {
public partial class TestSceneBeatmapMetadataWedge : SongSelectComponentsTestScene public partial class TestSceneBeatmapMetadataWedge : SongSelectComponentsTestScene
{ {
private BeatmapMetadataWedge wedge = null!; private APIBeatmapSet? currentOnlineSet;
[Cached(typeof(IBindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?>))] private BeatmapMetadataWedge wedge = null!;
private Bindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?> onlineLookupResult = new Bindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?>();
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
Child = wedge = new BeatmapMetadataWedge var lookupSource = new RealmPopulatingOnlineLookupSource();
Child = new DependencyProvidingContainer
{
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] [Test]
public void TestShowHide() public void TestShowHide()
{ {
AddStep("all metrics", () => (Beatmap.Value, onlineLookupResult.Value) = createTestBeatmap()); AddStep("all metrics", () =>
{
var (working, onlineSet) = createTestBeatmap();
currentOnlineSet = onlineSet;
Beatmap.Value = working;
});
AddStep("hide wedge", () => wedge.Hide()); AddStep("hide wedge", () => wedge.Hide());
AddStep("show wedge", () => wedge.Show()); AddStep("show wedge", () => wedge.Show());
@@ -44,63 +84,67 @@ namespace osu.Game.Tests.Visual.SongSelectV2
[Test] [Test]
public void TestVariousMetrics() public void TestVariousMetrics()
{ {
AddStep("all metrics", () => (Beatmap.Value, onlineLookupResult.Value) = createTestBeatmap()); AddStep("all metrics", () =>
{
var (working, onlineSet) = createTestBeatmap();
currentOnlineSet = onlineSet;
Beatmap.Value = working;
});
AddStep("null beatmap", () => Beatmap.SetDefault()); AddStep("null beatmap", () => Beatmap.SetDefault());
AddStep("no source", () => AddStep("no source", () =>
{ {
var (working, online) = createTestBeatmap(); var (working, onlineSet) = createTestBeatmap();
working.Metadata.Source = string.Empty; working.Metadata.Source = string.Empty;
onlineLookupResult.Value = online; currentOnlineSet = onlineSet;
Beatmap.Value = working; Beatmap.Value = working;
}); });
AddStep("no success rate", () => AddStep("no success rate", () =>
{ {
var (working, online) = createTestBeatmap(); var (working, onlineSet) = createTestBeatmap();
online.Result!.Beatmaps.Single().PlayCount = 0; onlineSet.Beatmaps.Single().PlayCount = 0;
online.Result!.Beatmaps.Single().PassCount = 0; onlineSet.Beatmaps.Single().PassCount = 0;
onlineLookupResult.Value = online; currentOnlineSet = onlineSet;
Beatmap.Value = working; Beatmap.Value = working;
}); });
AddStep("no user ratings", () => AddStep("no user ratings", () =>
{ {
var (working, online) = createTestBeatmap(); var (working, onlineSet) = createTestBeatmap();
online.Result!.Ratings = Array.Empty<int>(); onlineSet.Ratings = Array.Empty<int>();
onlineLookupResult.Value = online; currentOnlineSet = onlineSet;
Beatmap.Value = working; Beatmap.Value = working;
}); });
AddStep("no fail times", () => AddStep("no fail times", () =>
{ {
var (working, online) = createTestBeatmap(); var (working, onlineSet) = createTestBeatmap();
online.Result!.Beatmaps.Single().FailTimes = null; onlineSet.Beatmaps.Single().FailTimes = null;
onlineLookupResult.Value = online; currentOnlineSet = onlineSet;
Beatmap.Value = working; Beatmap.Value = working;
}); });
AddStep("no metrics", () => AddStep("no metrics", () =>
{ {
var (working, online) = createTestBeatmap(); var (working, onlineSet) = createTestBeatmap();
online.Result!.Ratings = Array.Empty<int>(); onlineSet.Ratings = Array.Empty<int>();
online.Result!.Beatmaps.Single().FailTimes = null; onlineSet.Beatmaps.Single().FailTimes = null;
onlineLookupResult.Value = online; currentOnlineSet = onlineSet;
Beatmap.Value = working; Beatmap.Value = working;
}); });
AddStep("local beatmap", () => AddStep("local beatmap", () =>
{ {
var (working, _) = createTestBeatmap(); var (working, onlineSet) = createTestBeatmap();
working.BeatmapInfo.OnlineID = 0; working.BeatmapInfo.OnlineID = 0;
onlineLookupResult.Value = null; currentOnlineSet = onlineSet;
Beatmap.Value = working; Beatmap.Value = working;
}); });
} }
@@ -110,16 +154,16 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{ {
AddStep("long text", () => AddStep("long text", () =>
{ {
var (working, online) = createTestBeatmap(); var (working, onlineSet) = createTestBeatmap();
working.BeatmapInfo.Metadata.Author = new RealmUser { Username = "Verrrrryyyy llooonngggggg author" }; working.BeatmapInfo.Metadata.Author = new RealmUser { Username = "Verrrrryyyy llooonngggggg author" };
working.BeatmapInfo.Metadata.Source = "Verrrrryyyy llooonngggggg source"; working.BeatmapInfo.Metadata.Source = "Verrrrryyyy llooonngggggg source";
working.BeatmapInfo.Metadata.Tags = string.Join(' ', Enumerable.Repeat(working.BeatmapInfo.Metadata.Tags, 3)); working.BeatmapInfo.Metadata.Tags = string.Join(' ', Enumerable.Repeat(working.BeatmapInfo.Metadata.Tags, 3));
online.Result!.Genre = new BeatmapSetOnlineGenre { Id = 12, Name = "Verrrrryyyy llooonngggggg genre" }; onlineSet.Genre = new BeatmapSetOnlineGenre { Id = 12, Name = "Verrrrryyyy llooonngggggg genre" };
online.Result!.Language = new BeatmapSetOnlineLanguage { Id = 12, Name = "Verrrrryyyy llooonngggggg language" }; onlineSet.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(); onlineSet.Beatmaps.Single().TopTags = Enumerable.Repeat(onlineSet.Beatmaps.Single().TopTags, 3).SelectMany(t => t!).ToArray();
onlineLookupResult.Value = online; currentOnlineSet = onlineSet;
Beatmap.Value = working; Beatmap.Value = working;
}); });
} }
@@ -127,17 +171,22 @@ namespace osu.Game.Tests.Visual.SongSelectV2
[Test] [Test]
public void TestOnlineAvailability() public void TestOnlineAvailability()
{ {
AddStep("online beatmapset", () => (Beatmap.Value, onlineLookupResult.Value) = createTestBeatmap()); AddStep("online beatmapset", () =>
{
var (working, onlineSet) = createTestBeatmap();
currentOnlineSet = onlineSet;
Beatmap.Value = working;
});
AddUntilStep("rating wedge visible", () => wedge.RatingsVisible); AddUntilStep("rating wedge visible", () => wedge.RatingsVisible);
AddUntilStep("fail time wedge visible", () => wedge.FailRetryVisible); AddUntilStep("fail time wedge visible", () => wedge.FailRetryVisible);
AddStep("online beatmapset with local diff", () => AddStep("online beatmapset with local diff", () =>
{ {
var (working, lookupResult) = createTestBeatmap(); var (working, onlineSet) = createTestBeatmap();
working.BeatmapInfo.ResetOnlineInfo(); working.BeatmapInfo.ResetOnlineInfo();
onlineLookupResult.Value = lookupResult; currentOnlineSet = onlineSet;
Beatmap.Value = working; Beatmap.Value = working;
}); });
AddUntilStep("rating wedge hidden", () => !wedge.RatingsVisible); AddUntilStep("rating wedge hidden", () => !wedge.RatingsVisible);
@@ -146,7 +195,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{ {
var (working, _) = createTestBeatmap(); var (working, _) = createTestBeatmap();
onlineLookupResult.Value = null; currentOnlineSet = null;
Beatmap.Value = working; Beatmap.Value = working;
}); });
AddAssert("rating wedge still hidden", () => !wedge.RatingsVisible); AddAssert("rating wedge still hidden", () => !wedge.RatingsVisible);
@@ -156,17 +205,21 @@ namespace osu.Game.Tests.Visual.SongSelectV2
[Test] [Test]
public void TestUserTags() public void TestUserTags()
{ {
AddStep("user tags", () => (Beatmap.Value, onlineLookupResult.Value) = createTestBeatmap()); AddStep("user tags", () =>
{
var (working, onlineSet) = createTestBeatmap();
currentOnlineSet = onlineSet;
Beatmap.Value = working;
});
AddStep("no user tags", () => AddStep("no user tags", () =>
{ {
var (working, online) = createTestBeatmap(); var (working, onlineSet) = createTestBeatmap();
online.Result!.Beatmaps.Single().TopTags = null; onlineSet.Beatmaps.Single().TopTags = null;
online.Result!.RelatedTags = null; onlineSet.RelatedTags = null;
working.BeatmapSetInfo.Beatmaps.Single().Metadata.UserTags.Clear();
onlineLookupResult.Value = online; currentOnlineSet = onlineSet;
Beatmap.Value = working; Beatmap.Value = working;
}); });
} }
@@ -174,60 +227,72 @@ namespace osu.Game.Tests.Visual.SongSelectV2
[Test] [Test]
public void TestLoading() 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", () => AddStep("set beatmap", () =>
{ {
var (working, online) = createTestBeatmap(); var (working, onlineSet) = createTestBeatmap();
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.InProgress(); currentOnlineSet = onlineSet;
Scheduler.AddDelayed(() => onlineLookupResult.Value = online, 500);
Beatmap.Value = working; Beatmap.Value = working;
}); });
AddWaitStep("wait", 5); AddWaitStep("wait", 5);
AddStep("set beatmap", () => AddStep("set beatmap", () =>
{ {
var (working, online) = createTestBeatmap(); var (working, onlineSet) = createTestBeatmap();
online.Result!.RelatedTags![0].Name = "other/tag"; onlineSet.RelatedTags![0].Name = "other/tag";
online.Result!.RelatedTags[1].Name = "another/tag"; onlineSet.RelatedTags[1].Name = "another/tag";
online.Result!.RelatedTags[2].Name = "some/tag"; onlineSet.RelatedTags[2].Name = "some/tag";
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.InProgress(); currentOnlineSet = onlineSet;
Scheduler.AddDelayed(() => onlineLookupResult.Value = online, 500);
Beatmap.Value = working; Beatmap.Value = working;
}); });
AddWaitStep("wait", 5); AddWaitStep("wait", 5);
AddStep("no user tags", () => AddStep("no user tags", () =>
{ {
var (working, online) = createTestBeatmap(); var (working, onlineSet) = createTestBeatmap();
online.Result!.Beatmaps.Single().TopTags = null; onlineSet.Beatmaps.Single().TopTags = null;
online.Result!.RelatedTags = null; onlineSet.RelatedTags = null;
working.BeatmapSetInfo.Beatmaps.Single().Metadata.UserTags.Clear();
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.InProgress(); currentOnlineSet = onlineSet;
Scheduler.AddDelayed(() => onlineLookupResult.Value = online, 500);
Beatmap.Value = working; Beatmap.Value = working;
}); });
AddWaitStep("wait", 5); AddWaitStep("wait", 5);
AddStep("no user tags", () => AddStep("no user tags", () =>
{ {
var (working, online) = createTestBeatmap(); var (working, onlineSet) = createTestBeatmap();
online.Result!.Beatmaps.Single().TopTags = null; onlineSet.Beatmaps.Single().TopTags = null;
online.Result!.RelatedTags = null; onlineSet.RelatedTags = null;
working.BeatmapSetInfo.Beatmaps.Single().Metadata.UserTags.Clear();
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.InProgress(); currentOnlineSet = onlineSet;
Scheduler.AddDelayed(() => onlineLookupResult.Value = online, 500);
Beatmap.Value = working; Beatmap.Value = working;
}); });
AddWaitStep("wait", 5); AddWaitStep("wait", 5);
} }
private (WorkingBeatmap, Screens.SelectV2.SongSelect.BeatmapSetLookupResult) createTestBeatmap() private (WorkingBeatmap, APIBeatmapSet) createTestBeatmap()
{ {
var working = CreateWorkingBeatmap(Ruleset.Value); var working = CreateWorkingBeatmap(Ruleset.Value);
var onlineSet = new APIBeatmapSet var onlineSet = new APIBeatmapSet
@@ -281,8 +346,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
working.BeatmapSetInfo.DateSubmitted = DateTimeOffset.Now; working.BeatmapSetInfo.DateSubmitted = DateTimeOffset.Now;
working.BeatmapSetInfo.DateRanked = DateTimeOffset.Now; working.BeatmapSetInfo.DateRanked = DateTimeOffset.Now;
working.Metadata.UserTags.AddRange(onlineSet.RelatedTags.Select(t => t.Name)); return (working, onlineSet);
return (working, Screens.SelectV2.SongSelect.BeatmapSetLookupResult.Completed(onlineSet));
} }
} }
} }

View File

@@ -11,7 +11,6 @@ using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
@@ -42,8 +41,10 @@ namespace osu.Game.Tests.Visual.SongSelectV2
private BeatmapTitleWedge titleWedge = null!; private BeatmapTitleWedge titleWedge = null!;
private BeatmapTitleWedge.DifficultyDisplay difficultyDisplay => titleWedge.ChildrenOfType<BeatmapTitleWedge.DifficultyDisplay>().Single(); private BeatmapTitleWedge.DifficultyDisplay difficultyDisplay => titleWedge.ChildrenOfType<BeatmapTitleWedge.DifficultyDisplay>().Single();
[Cached(typeof(IBindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?>))] private APIBeatmapSet? currentOnlineSet;
private Bindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?> onlineLookupResult = new Bindable<Screens.SelectV2.SongSelect.BeatmapSetLookupResult?>();
[Cached]
private RealmPopulatingOnlineLookupSource lookupSource = new RealmPopulatingOnlineLookupSource();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(RulesetStore rulesets) private void load(RulesetStore rulesets)
@@ -57,6 +58,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddRange(new Drawable[] AddRange(new Drawable[]
{ {
lookupSource,
new Container new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@@ -140,18 +142,44 @@ namespace osu.Game.Tests.Visual.SongSelectV2
[Test] [Test]
public void TestOnlineAvailability() public void TestOnlineAvailability()
{ {
AddStep("online beatmapset", () => (Beatmap.Value, onlineLookupResult.Value) = createTestBeatmap()); AddStep("set up request handler", () =>
{
((DummyAPIAccess)API).HandleRequest = request =>
{
switch (request)
{
case GetBeatmapSetRequest set:
if (set.ID == currentOnlineSet?.OnlineID)
{
set.TriggerSuccess(currentOnlineSet);
return true;
}
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("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")); AddUntilStep("favourites count is 2345", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("2,345"));
AddStep("online beatmapset with local diff", () => AddStep("online beatmapset with local diff", () =>
{ {
var (working, lookupResult) = createTestBeatmap(); var (working, onlineSet) = createTestBeatmap();
working.BeatmapInfo.ResetOnlineInfo(); working.BeatmapInfo.ResetOnlineInfo();
currentOnlineSet = onlineSet;
Beatmap.Value = working; Beatmap.Value = working;
onlineLookupResult.Value = lookupResult;
}); });
AddUntilStep("play count is -", () => this.ChildrenOfType<BeatmapTitleWedge.Statistic>().ElementAt(0).Text.ToString(), () => Is.EqualTo("-")); 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")); AddUntilStep("favourites count is 2345", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("2,345"));
@@ -159,8 +187,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{ {
var (working, _) = createTestBeatmap(); var (working, _) = createTestBeatmap();
currentOnlineSet = null;
Beatmap.Value = working; 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("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("-")); AddUntilStep("favourites count is -", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("-"));
@@ -177,6 +205,15 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{ {
switch (request) switch (request)
{ {
case GetBeatmapSetRequest set:
if (set.ID == currentOnlineSet?.OnlineID)
{
set.TriggerSuccess(currentOnlineSet);
return true;
}
return false;
case PostBeatmapFavouriteRequest favourite: case PostBeatmapFavouriteRequest favourite:
Task.Run(() => Task.Run(() =>
{ {
@@ -191,8 +228,13 @@ namespace osu.Game.Tests.Visual.SongSelectV2
}; };
}); });
AddStep("online beatmapset", () => (Beatmap.Value, onlineLookupResult.Value) = createTestBeatmap()); 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("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")); AddUntilStep("favourites count is 2345", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("2,345"));
@@ -209,13 +251,13 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddStep("click favourite button", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().TriggerClick()); AddStep("click favourite button", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().TriggerClick());
AddStep("change to another beatmap", () => AddStep("change to another beatmap", () =>
{ {
var (working, online) = createTestBeatmap(); var (working, onlineSet) = createTestBeatmap();
online.Result!.FavouriteCount = 9999; onlineSet.FavouriteCount = 9999;
online.Result!.HasFavourited = true; onlineSet.HasFavourited = true;
working.BeatmapSetInfo.OnlineID = online.Result!.OnlineID = 99999; working.BeatmapSetInfo.OnlineID = onlineSet.OnlineID = 99999;
currentOnlineSet = onlineSet;
Beatmap.Value = working; Beatmap.Value = working;
onlineLookupResult.Value = online;
}); });
AddStep("allow request to complete", () => resetEvent.Set()); AddStep("allow request to complete", () => resetEvent.Set());
AddUntilStep("favourites count is 9999", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("9,999")); AddUntilStep("favourites count is 9999", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("9,999"));
@@ -226,6 +268,15 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{ {
switch (request) switch (request)
{ {
case GetBeatmapSetRequest set:
if (set.ID == currentOnlineSet?.OnlineID)
{
set.TriggerSuccess(currentOnlineSet);
return true;
}
return false;
case PostBeatmapFavouriteRequest favourite: case PostBeatmapFavouriteRequest favourite:
Task.Run(() => Task.Run(() =>
{ {
@@ -299,7 +350,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
}); });
} }
private (WorkingBeatmap, Screens.SelectV2.SongSelect.BeatmapSetLookupResult) createTestBeatmap() private (WorkingBeatmap, APIBeatmapSet) createTestBeatmap()
{ {
var working = CreateWorkingBeatmap(Ruleset.Value); var working = CreateWorkingBeatmap(Ruleset.Value);
var onlineSet = new APIBeatmapSet var onlineSet = new APIBeatmapSet
@@ -320,7 +371,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
working.BeatmapSetInfo.DateSubmitted = DateTimeOffset.Now; working.BeatmapSetInfo.DateSubmitted = DateTimeOffset.Now;
working.BeatmapSetInfo.DateRanked = DateTimeOffset.Now; working.BeatmapSetInfo.DateRanked = DateTimeOffset.Now;
return (working, Screens.SelectV2.SongSelect.BeatmapSetLookupResult.Completed(onlineSet)); return (working, onlineSet);
} }
private class TestHitObject : ConvertHitObject; private class TestHitObject : ConvertHitObject;

View File

@@ -75,21 +75,21 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{ {
new PanelBeatmapSet new PanelBeatmapSet
{ {
Item = new CarouselItem(new GroupedBeatmapSet(null, beatmapSet)) Item = new CarouselItem(beatmapSet)
}, },
new PanelBeatmapSet new PanelBeatmapSet
{ {
Item = new CarouselItem(new GroupedBeatmapSet(null, beatmapSet)), Item = new CarouselItem(beatmapSet),
KeyboardSelected = { Value = true } KeyboardSelected = { Value = true }
}, },
new PanelBeatmapSet new PanelBeatmapSet
{ {
Item = new CarouselItem(new GroupedBeatmapSet(null, beatmapSet)), Item = new CarouselItem(beatmapSet),
Expanded = { Value = true } Expanded = { Value = true }
}, },
new PanelBeatmapSet new PanelBeatmapSet
{ {
Item = new CarouselItem(new GroupedBeatmapSet(null, beatmapSet)), Item = new CarouselItem(beatmapSet),
KeyboardSelected = { Value = true }, KeyboardSelected = { Value = true },
Expanded = { Value = true } Expanded = { Value = true }
}, },

View File

@@ -23,9 +23,7 @@ using osu.Game.Screens.Ranking;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Leaderboards; using osu.Game.Screens.Select.Leaderboards;
using osu.Game.Screens.SelectV2; using osu.Game.Screens.SelectV2;
using osu.Game.Tests.Resources;
using osuTK.Input; using osuTK.Input;
using BeatmapCarousel = osu.Game.Screens.SelectV2.BeatmapCarousel;
using FooterButtonMods = osu.Game.Screens.SelectV2.FooterButtonMods; using FooterButtonMods = osu.Game.Screens.SelectV2.FooterButtonMods;
using FooterButtonOptions = osu.Game.Screens.SelectV2.FooterButtonOptions; using FooterButtonOptions = osu.Game.Screens.SelectV2.FooterButtonOptions;
using FooterButtonRandom = osu.Game.Screens.SelectV2.FooterButtonRandom; using FooterButtonRandom = osu.Game.Screens.SelectV2.FooterButtonRandom;
@@ -304,28 +302,6 @@ 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] [Test]
public void TestAutoplayShortcut() public void TestAutoplayShortcut()
{ {

View File

@@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddAssert("no-collection group present", () => AddAssert("no-collection group present", () =>
{ {
var group = grouping.GroupItems.Single(g => g.Key.Title == "Not in collection"); var group = grouping.GroupItems.Single(g => g.Key.Title == "Not in collection");
return group.Value.Select(i => i.Model).OfType<GroupedBeatmapSet>().Single().BeatmapSet.Equals(beatmapSet); return group.Value.Select(i => i.Model).OfType<BeatmapSetInfo>().Single().Equals(beatmapSet);
}); });
AddStep("add beatmap to collection", () => AddStep("add beatmap to collection", () =>

View File

@@ -4,13 +4,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
@@ -24,9 +22,6 @@ namespace osu.Game.Tests.Visual.UserInterface
private FillFlowContainer spreadOutFlow = null!; private FillFlowContainer spreadOutFlow = null!;
private ModDisplay modDisplay = null!; private ModDisplay modDisplay = null!;
[Resolved]
private RulesetStore rulesetStore { get; set; } = null!;
[SetUpSteps] [SetUpSteps]
public void SetUpSteps() public void SetUpSteps()
{ {
@@ -75,26 +70,9 @@ namespace osu.Game.Tests.Visual.UserInterface
[Test] [Test]
public void TestShowAllMods() public void TestShowAllMods()
{ {
createModIconsForRuleset(0); AddStep("create mod icons", () =>
createModIconsForRuleset(1);
createModIconsForRuleset(2);
createModIconsForRuleset(3);
AddStep("toggle selected", () =>
{ {
foreach (var icon in this.ChildrenOfType<ModIcon>()) addRange(Ruleset.Value.CreateInstance().CreateAllMods().Select(m =>
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) if (m is OsuModFlashlight fl)
fl.FollowDelay.Value = 1245; fl.FollowDelay.Value = 1245;
@@ -111,6 +89,12 @@ namespace osu.Game.Tests.Visual.UserInterface
return m; return m;
})); }));
}); });
AddStep("toggle selected", () =>
{
foreach (var icon in this.ChildrenOfType<ModIcon>())
icon.Selected.Toggle();
});
} }
[Test] [Test]

View File

@@ -6,7 +6,6 @@ using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Tournament.Components; using osu.Game.Tournament.Components;
using osu.Game.Tournament.IPC; using osu.Game.Tournament.IPC;
using osu.Game.Tournament.Screens.Gameplay; using osu.Game.Tournament.Screens.Gameplay;
@@ -67,6 +66,6 @@ namespace osu.Game.Tournament.Tests.Screens
() => this.ChildrenOfType<TeamScore>().All(score => score.Alpha == (visible ? 1 : 0))); () => this.ChildrenOfType<TeamScore>().All(score => score.Alpha == (visible ? 1 : 0)));
private void toggleWarmup() private void toggleWarmup()
=> AddStep("toggle warmup", () => this.ChildrenOfType<LabelledSwitchButton>().First().ChildrenOfType<SwitchButton>().First().TriggerClick()); => AddStep("toggle warmup", () => this.ChildrenOfType<TourneyButton>().First().TriggerClick());
} }
} }

View File

@@ -7,7 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
using osu.Game.Tournament.Components; using osu.Game.Tournament.Components;
using osu.Game.Tournament.IPC; using osu.Game.Tournament.IPC;
@@ -24,6 +24,7 @@ namespace osu.Game.Tournament.Screens.Gameplay
private readonly BindableBool warmup = new BindableBool(); private readonly BindableBool warmup = new BindableBool();
public readonly Bindable<TourneyState> State = new Bindable<TourneyState>(); public readonly Bindable<TourneyState> State = new Bindable<TourneyState>();
private OsuButton warmupButton = null!;
private MatchIPCInfo ipc = null!; private MatchIPCInfo ipc = null!;
[Resolved] [Resolved]
@@ -39,8 +40,6 @@ namespace osu.Game.Tournament.Screens.Gameplay
{ {
this.ipc = ipc; this.ipc = ipc;
LabelledSwitchButton chatToggle;
AddRangeInternal(new Drawable[] AddRangeInternal(new Drawable[]
{ {
new TourneyVideo("gameplay") new TourneyVideo("gameplay")
@@ -96,14 +95,17 @@ namespace osu.Game.Tournament.Screens.Gameplay
{ {
Children = new Drawable[] Children = new Drawable[]
{ {
new LabelledSwitchButton warmupButton = new TourneyButton
{ {
Label = "Warmup", RelativeSizeAxes = Axes.X,
Current = warmup, Text = "Toggle warmup",
Action = () => warmup.Toggle()
}, },
chatToggle = new LabelledSwitchButton new TourneyButton
{ {
Label = "Show chat", RelativeSizeAxes = Axes.X,
Text = "Toggle chat",
Action = () => { State.Value = State.Value == TourneyState.Idle ? TourneyState.Playing : TourneyState.Idle; }
}, },
new SettingsSlider<int> new SettingsSlider<int>
{ {
@@ -121,12 +123,13 @@ 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); LadderInfo.ChromaKeyWidth.BindValueChanged(width => chroma.Width = width.NewValue, true);
warmup.BindValueChanged(w => header.ShowScores = !w.NewValue, true); warmup.BindValueChanged(w =>
{
warmupButton.Alpha = !w.NewValue ? 0.5f : 1;
header.ShowScores = !w.NewValue;
}, true);
} }
protected override void LoadComplete() protected override void LoadComplete()

View File

@@ -0,0 +1,128 @@
// 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.
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Audio
{
[Cached]
public partial class WelcomeMusicManager : Drawable
{
public event Action<Exception> OnLoadFailure;
public event Action OnCategoriesRefreshed;
public readonly Bindable<IEnumerable<string>> AvailableCategories = new Bindable<IEnumerable<string>>();
private ITrack preloadedTrack;
private List<APIWelcomeMusic> currentTracks;
[Resolved]
private IAPIProvider api { get; set; }
[Resolved]
private AudioManager audioManager { get; set; }
[Resolved]
private OsuConfigManager config { get; set; }
[Resolved]
private GameHost host { get; set; }
private Bindable<WelcomeMusicMode> musicMode;
private Bindable<string> selectedCategory;
[BackgroundDependencyLoader]
private void load()
{
musicMode = config.GetBindable<WelcomeMusicMode>(OsuSetting.WelcomeMusicMode);
selectedCategory = config.GetBindable<string>(OsuSetting.WelcomeMusicCategory);
fetchCategories();
}
public void RefreshCategories() => fetchCategories();
private void fetchCategories()
{
var request = new GetMusicCategoriesRequest();
request.Success += response =>
{
var serverCategories = response.Categories ?? Enumerable.Empty<string>();
AvailableCategories.Value = serverCategories.ToList();
OnCategoriesRefreshed?.Invoke();
};
request.Failure += exception =>
{
Logger.Error(exception, "ОШИБКА: Не удалось загрузить категории музыки!");
AvailableCategories.Value = new[] { "Не удалось загрузить..." };
OnLoadFailure?.Invoke(exception);
};
api.PerformAsync(request);
}
public async Task PreloadCurrentTrack()
{
if (musicMode.Value == WelcomeMusicMode.Default)
{
preloadedTrack = audioManager.Tracks.Get("Samples/welcome.ogg");
return;
}
if (string.IsNullOrEmpty(selectedCategory.Value) || selectedCategory.Value.Contains("Не удалось"))
return;
var request = new GetWelcomeMusicRequest(selectedCategory.Value);
var tcs = new TaskCompletionSource<bool>();
request.Success += response =>
{
currentTracks = response;
tcs.SetResult(true);
};
request.Failure += exception =>
{
Logger.Error(exception, "ОШИБКА: Не удалось загрузить список треков!");
tcs.SetResult(false);
};
api.PerformAsync(request);
await tcs.Task;
if (currentTracks?.Any() != true)
return;
var randomTrackInfo = currentTracks[RNG.Next(0, currentTracks.Count)];
try
{
preloadedTrack = audioManager.Tracks.Get(randomTrackInfo.Url);
if (preloadedTrack != null)
preloadedTrack.Looping = false;
}
catch (Exception e)
{
Logger.Error(e, $"ОШИБКА: Не удалось загрузить трек по URL: {randomTrackInfo.Url}");
}
}
public ITrack GetPreloadedTrack() => preloadedTrack;
public void RequestRestart()
{
host.Exit();
}
}
}

View File

@@ -13,7 +13,6 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
using osu.Game.Configuration;
using osu.Game.Collections; using osu.Game.Collections;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Extensions; using osu.Game.Extensions;
@@ -37,8 +36,8 @@ namespace osu.Game.Beatmaps
public ProcessBeatmapDelegate? ProcessBeatmap { private get; set; } public ProcessBeatmapDelegate? ProcessBeatmap { private get; set; }
public BeatmapImporter(Storage storage, RealmAccess realm, OsuConfigManager? config) public BeatmapImporter(Storage storage, RealmAccess realm)
: base(storage, realm, config) : base(storage, realm)
{ {
} }

View File

@@ -17,7 +17,6 @@ using osu.Framework.IO.Stores;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
using osu.Game.Configuration;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Extensions; using osu.Game.Extensions;
using osu.Game.IO.Archives; using osu.Game.IO.Archives;
@@ -60,7 +59,7 @@ namespace osu.Game.Beatmaps
} }
public BeatmapManager(Storage storage, RealmAccess realm, IAPIProvider? api, AudioManager audioManager, IResourceStore<byte[]> gameResources, GameHost? host = null, 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, OsuConfigManager? config = null) WorkingBeatmap? defaultBeatmap = null, BeatmapDifficultyCache? difficultyCache = null, bool performOnlineLookups = false)
: base(storage, realm) : base(storage, realm)
{ {
if (performOnlineLookups) if (performOnlineLookups)
@@ -76,7 +75,7 @@ namespace osu.Game.Beatmaps
BeatmapTrackStore = audioManager.GetTrackStore(userResources); BeatmapTrackStore = audioManager.GetTrackStore(userResources);
beatmapImporter = CreateBeatmapImporter(storage, realm, config); beatmapImporter = CreateBeatmapImporter(storage, realm);
beatmapImporter.ProcessBeatmap = (beatmapSet, scope) => ProcessBeatmap?.Invoke(beatmapSet, scope); beatmapImporter.ProcessBeatmap = (beatmapSet, scope) => ProcessBeatmap?.Invoke(beatmapSet, scope);
beatmapImporter.PostNotification = obj => PostNotification?.Invoke(obj); beatmapImporter.PostNotification = obj => PostNotification?.Invoke(obj);
@@ -99,7 +98,7 @@ namespace osu.Game.Beatmaps
return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host); return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host);
} }
protected virtual BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, OsuConfigManager? config = null) => new BeatmapImporter(storage, realm, config); protected virtual BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm) => new BeatmapImporter(storage, realm);
/// <summary> /// <summary>
/// Create a new beatmap set, backed by a <see cref="BeatmapSetInfo"/> model, /// Create a new beatmap set, backed by a <see cref="BeatmapSetInfo"/> model,

View File

@@ -59,10 +59,7 @@ namespace osu.Game.Beatmaps
// An interpolating clock is used to ensure precise time values even when the host audio subsystem is not reporting // 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). // 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) if (applyOffsets)
{ {

View File

@@ -8,7 +8,6 @@ using osu.Framework.Configuration;
using osu.Framework.Configuration.Tracking; using osu.Framework.Configuration.Tracking;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Beatmaps.Drawables.Cards; using osu.Game.Beatmaps.Drawables.Cards;
@@ -42,8 +41,6 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.Ruleset, string.Empty); SetDefault(OsuSetting.Ruleset, string.Empty);
SetDefault(OsuSetting.Skin, SkinInfo.ARGON_SKIN.ToString()); SetDefault(OsuSetting.Skin, SkinInfo.ARGON_SKIN.ToString());
SetDefault(OsuSetting.MenuCookieColor, Colour4.FromHex(@"ff66ba"));
SetDefault(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Local); SetDefault(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Local);
SetDefault(OsuSetting.BeatmapLeaderboardSortMode, LeaderboardSortMode.Score); SetDefault(OsuSetting.BeatmapLeaderboardSortMode, LeaderboardSortMode.Score);
SetDefault(OsuSetting.BeatmapDetailModsFilter, false); SetDefault(OsuSetting.BeatmapDetailModsFilter, false);
@@ -68,7 +65,6 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.ToolbarClockDisplayMode, ToolbarClockDisplayMode.Full); SetDefault(OsuSetting.ToolbarClockDisplayMode, ToolbarClockDisplayMode.Full);
SetDefault(OsuSetting.ForceLegacySongSelect, false);
SetDefault(OsuSetting.SongSelectBackgroundBlur, false); SetDefault(OsuSetting.SongSelectBackgroundBlur, false);
// Online settings // Online settings
@@ -198,12 +194,9 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.MenuBackgroundSource, BackgroundSource.Skin); SetDefault(OsuSetting.MenuBackgroundSource, BackgroundSource.Skin);
SetDefault(OsuSetting.SeasonalBackgroundMode, SeasonalBackgroundMode.Never); SetDefault(OsuSetting.SeasonalBackgroundMode, SeasonalBackgroundMode.Never);
SetDefault(OsuSetting.UseSeasonalBackgroundsV2, true);
SetDefault(OsuSetting.DiscordRichPresence, DiscordRichPresenceMode.Full); SetDefault(OsuSetting.DiscordRichPresence, DiscordRichPresenceMode.Full);
SetDefault(OsuSetting.DeleteImportedArchives, true);
SetDefault(OsuSetting.EditorDim, 0.25f, 0f, 0.75f, 0.25f); SetDefault(OsuSetting.EditorDim, 0.25f, 0f, 0.75f, 0.25f);
SetDefault(OsuSetting.EditorWaveformOpacity, 0.25f, 0f, 1f, 0.25f); SetDefault(OsuSetting.EditorWaveformOpacity, 0.25f, 0f, 1f, 0.25f);
SetDefault(OsuSetting.EditorShowHitMarkers, true); SetDefault(OsuSetting.EditorShowHitMarkers, true);
@@ -220,6 +213,8 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.MultiplayerShowInProgressFilter, true); SetDefault(OsuSetting.MultiplayerShowInProgressFilter, true);
SetDefault(OsuSetting.LastProcessedMetadataId, -1); SetDefault(OsuSetting.LastProcessedMetadataId, -1);
SetDefault(OsuSetting.WelcomeMusicMode, WelcomeMusicMode.Default);
SetDefault(OsuSetting.WelcomeMusicCategory, "Default");
SetDefault(OsuSetting.ComboColourNormalisationAmount, 0.2f, 0f, 1f, 0.01f); SetDefault(OsuSetting.ComboColourNormalisationAmount, 0.2f, 0f, 1f, 0.01f);
SetDefault(OsuSetting.UserOnlineStatus, UserStatus.Online); SetDefault(OsuSetting.UserOnlineStatus, UserStatus.Online);
@@ -389,6 +384,8 @@ namespace osu.Game.Configuration
AudioOffset, AudioOffset,
VolumeInactive, VolumeInactive,
WelcomeMusicMode,
WelcomeMusicCategory,
MenuMusic, MenuMusic,
MenuVoice, MenuVoice,
MenuTips, MenuTips,
@@ -412,7 +409,6 @@ namespace osu.Game.Configuration
ChatDisplayHeight, ChatDisplayHeight,
BeatmapListingCardSize, BeatmapListingCardSize,
ToolbarClockDisplayMode, ToolbarClockDisplayMode,
ForceLegacySongSelect,
SongSelectBackgroundBlur, SongSelectBackgroundBlur,
Version, Version,
ShowFirstRunSetup, ShowFirstRunSetup,
@@ -420,7 +416,6 @@ namespace osu.Game.Configuration
Skin, Skin,
ScreenshotFormat, ScreenshotFormat,
ScreenshotCaptureMenuCursor, ScreenshotCaptureMenuCursor,
MenuCookieColor,
BeatmapSkins, BeatmapSkins,
BeatmapColours, BeatmapColours,
BeatmapHitsounds, BeatmapHitsounds,
@@ -445,13 +440,11 @@ namespace osu.Game.Configuration
MenuBackgroundSource, MenuBackgroundSource,
GameplayDisableWinKey, GameplayDisableWinKey,
SeasonalBackgroundMode, SeasonalBackgroundMode,
UseSeasonalBackgroundsV2, // TODO: add migrations
BackgroundCategory, BackgroundCategory,
EditorWaveformOpacity, EditorWaveformOpacity,
EditorShowHitMarkers, EditorShowHitMarkers,
EditorAutoSeekOnPlacement, EditorAutoSeekOnPlacement,
DiscordRichPresence, DiscordRichPresence,
DeleteImportedArchives,
ShowOnlineExplicitContent, ShowOnlineExplicitContent,
LastProcessedMetadataId, LastProcessedMetadataId,

View File

@@ -0,0 +1,11 @@
// 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.
namespace osu.Game.Configuration
{
public enum WelcomeMusicMode
{
Default,
Custom
}
}

View File

@@ -558,15 +558,9 @@ namespace osu.Game.Database
Logger.Log("Querying for beatmap sets that contain missing submission/rank date..."); 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>( HashSet<Guid> beatmapSetIds = realmAccess.Run(r => new HashSet<Guid>(
r.All<BeatmapSetInfo>() r.All<BeatmapSetInfo>()
.Filter($@"{nameof(BeatmapSetInfo.StatusInt)} > 0 && ({nameof(BeatmapSetInfo.DateRanked)} == null || {nameof(BeatmapSetInfo.DateSubmitted)} == null) " .Where(b => b.StatusInt > 0 && (b.DateRanked == null || b.DateSubmitted == null))
+ $@"&& ANY {nameof(BeatmapSetInfo.Beatmaps)}.{nameof(BeatmapInfo.StatusInt)} > 0")
.AsEnumerable() .AsEnumerable()
.Select(b => b.ID))); .Select(b => b.ID)));
@@ -597,7 +591,11 @@ namespace osu.Game.Database
{ {
BeatmapSetInfo beatmapSet = r.Find<BeatmapSetInfo>(id)!; BeatmapSetInfo beatmapSet = r.Find<BeatmapSetInfo>(id)!;
var beatmap = beatmapSet.Beatmaps.First(b => b.Status >= BeatmapOnlineStatus.Ranked); // 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;
bool lookupSucceeded = localMetadataSource.TryLookup(beatmap, out var result); bool lookupSucceeded = localMetadataSource.TryLookup(beatmap, out var result);
@@ -727,7 +725,7 @@ namespace osu.Game.Database
} }
catch (Exception e) catch (Exception e)
{ {
Logger.Log(@$"Failed to update user tags for beatmap {id}: {e}"); Logger.Log(@$"Failed to update ranked/submitted dates for beatmap set {id}: {e}");
++failedCount; ++failedCount;
} }
} }

View File

@@ -8,14 +8,11 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Humanizer; using Humanizer;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Game.Configuration;
using osu.Game.Extensions; using osu.Game.Extensions;
using osu.Game.IO.Archives; using osu.Game.IO.Archives;
using osu.Game.Models; using osu.Game.Models;
@@ -80,19 +77,11 @@ namespace osu.Game.Database
/// </summary> /// </summary>
public Action<Notification>? PostNotification { get; set; } public Action<Notification>? PostNotification { get; set; }
private readonly OsuConfigManager? config; protected RealmArchiveModelImporter(Storage storage, RealmAccess realm)
private Bindable<bool>? deleteImportedArchives;
protected RealmArchiveModelImporter(Storage storage, RealmAccess realm, OsuConfigManager? config = null)
{ {
Realm = realm; Realm = realm;
Files = new RealmFileStore(realm, storage); 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()); public Task Import(params string[] paths) => Import(paths.Select(p => new ImportTask(p)).ToArray());
@@ -252,10 +241,9 @@ namespace osu.Game.Database
// e.g. reconstructing/repairing database with items from default storage. // e.g. reconstructing/repairing database with items from default storage.
// Also, not always a single file, i.e. for LegacyFilesystemReader // Also, not always a single file, i.e. for LegacyFilesystemReader
// TODO: Add a check to prevent files from storage to be deleted. // TODO: Add a check to prevent files from storage to be deleted.
bool allowDelete = deleteImportedArchives?.Value ?? true;
try try
{ {
if (import != null && ShouldDeleteArchive(task.Path) && allowDelete) if (import != null && ShouldDeleteArchive(task.Path))
task.DeleteFile(); task.DeleteFile();
} }
catch (Exception e) catch (Exception e)

View File

@@ -33,19 +33,19 @@ namespace osu.Game.Graphics.Backgrounds
[Resolved] [Resolved]
private IAPIProvider api { get; set; } private IAPIProvider api { get; set; }
private Bindable<bool> useSeasonalBackgrounds; private Bindable<SeasonalBackgroundMode> backgroundMode;
private Bindable<string> selectedCategory; private Bindable<string> selectedCategory;
private Bindable<APISeasonalBackgrounds> currentBackgrounds; private Bindable<APISeasonalBackgrounds> currentBackgrounds;
private int currentBackgroundIndex; private int currentBackgroundIndex;
private bool shouldShowCustomBackgrounds => useSeasonalBackgrounds.Value; private bool shouldShowCustomBackgrounds => backgroundMode.Value != SeasonalBackgroundMode.Never;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager config, SessionStatics sessionStatics) private void load(OsuConfigManager config, SessionStatics sessionStatics)
{ {
useSeasonalBackgrounds = config.GetBindable<bool>(OsuSetting.UseSeasonalBackgroundsV2); backgroundMode = config.GetBindable<SeasonalBackgroundMode>(OsuSetting.SeasonalBackgroundMode);
useSeasonalBackgrounds.BindValueChanged(_ => BackgroundChanged?.Invoke()); backgroundMode.BindValueChanged(_ => BackgroundChanged?.Invoke());
selectedCategory = config.GetBindable<string>(OsuSetting.BackgroundCategory); selectedCategory = config.GetBindable<string>(OsuSetting.BackgroundCategory);
selectedCategory.BindValueChanged(_ => fetchBackgroundsForSelectedCategory()); selectedCategory.BindValueChanged(_ => fetchBackgroundsForSelectedCategory());
@@ -53,18 +53,18 @@ namespace osu.Game.Graphics.Backgrounds
currentBackgrounds = sessionStatics.GetBindable<APISeasonalBackgrounds>(Static.SeasonalBackgrounds); currentBackgrounds = sessionStatics.GetBindable<APISeasonalBackgrounds>(Static.SeasonalBackgrounds);
if (shouldShowCustomBackgrounds) if (shouldShowCustomBackgrounds)
fetchCategories(true); fetchCategories();
} }
/// <summary> /// <summary>
/// Public method to trigger a refresh of categories from the UI. /// Public method to trigger a refresh of categories from the UI.
/// </summary> /// </summary>
public void RefreshCategories(bool ignoreSuccess = false) public void RefreshCategories()
{ {
fetchCategories(ignoreSuccess); fetchCategories();
} }
private void fetchCategories(bool ignoreSuccess = false) private void fetchCategories()
{ {
if (!shouldShowCustomBackgrounds) return; if (!shouldShowCustomBackgrounds) return;
@@ -74,28 +74,20 @@ namespace osu.Game.Graphics.Backgrounds
{ {
var serverCategories = response.Categories ?? Enumerable.Empty<string>(); var serverCategories = response.Categories ?? Enumerable.Empty<string>();
AvailableCategories.Value = serverCategories.Distinct(StringComparer.OrdinalIgnoreCase).ToList(); AvailableCategories.Value = new[] { "Default" }.Concat(serverCategories)
.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
if (!AvailableCategories.Value.Any())
{
selectedCategory.Value = "";
return; // we don't have any categories!!!
}
if (!AvailableCategories.Value.Contains(selectedCategory.Value)) if (!AvailableCategories.Value.Contains(selectedCategory.Value))
selectedCategory.Value = AvailableCategories.Value.Contains("Default") selectedCategory.Value = "Default";
? "Default" else
: AvailableCategories.Value.ElementAt(0);
fetchBackgroundsForSelectedCategory(); fetchBackgroundsForSelectedCategory();
if (!ignoreSuccess)
OnCategoriesRefreshed?.Invoke(); OnCategoriesRefreshed?.Invoke();
}; };
request.Failure += exception => request.Failure += exception =>
{ {
AvailableCategories.Value = Array.Empty<string>(); AvailableCategories.Value = new[] { "Íå óäàëîñü çàãðóçèòü..." };
OnLoadFailure?.Invoke(exception); OnLoadFailure?.Invoke(exception);
}; };
@@ -106,6 +98,13 @@ namespace osu.Game.Graphics.Backgrounds
{ {
if (!shouldShowCustomBackgrounds) return; 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; string categoryToFetch = selectedCategory.Value == "Default" ? null : selectedCategory.Value;
var request = new GetSeasonalBackgroundsRequest(categoryToFetch); var request = new GetSeasonalBackgroundsRequest(categoryToFetch);

View File

@@ -3,7 +3,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@@ -80,7 +79,7 @@ namespace osu.Game.Graphics.Carousel
/// <summary> /// <summary>
/// The number of times filter operations have been triggered. /// The number of times filter operations have been triggered.
/// </summary> /// </summary>
public int FilterCount { get; private set; } internal int FilterCount { get; private set; }
/// <summary> /// <summary>
/// The number of displayable items currently being tracked (before filtering). /// The number of displayable items currently being tracked (before filtering).
@@ -211,12 +210,6 @@ namespace osu.Game.Graphics.Carousel
return filterTask; 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> /// <summary>
/// Fired after a filter operation completed. /// Fired after a filter operation completed.
/// </summary> /// </summary>
@@ -308,11 +301,7 @@ namespace osu.Game.Graphics.Carousel
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}; };
Items.BindCollectionChanged((_, args) => Items.BindCollectionChanged((_, _) => filterAfterItemsChanged.Invalidate());
{
if (HandleItemsChanged(args))
filterAfterItemsChanged.Invalidate();
});
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]

View File

@@ -81,6 +81,27 @@ namespace osu.Game.Graphics
public static IconUsage InsaneMania => get(0xe027); public static IconUsage InsaneMania => get(0xe027);
public static IconUsage ExpertMania => get(0xe028); 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 #endregion
#region New single-file-based icons #region New single-file-based icons
@@ -160,88 +181,6 @@ namespace osu.Game.Graphics
public static IconUsage Tortoise => get(OsuIconMapping.Tortoise); public static IconUsage Tortoise => get(OsuIconMapping.Tortoise);
public static IconUsage Hare => get(OsuIconMapping.Hare); 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 static IconUsage get(OsuIconMapping glyph) => new IconUsage((char)glyph, FONT_NAME);
private enum OsuIconMapping private enum OsuIconMapping
@@ -461,224 +400,6 @@ namespace osu.Game.Graphics
[Description(@"hare")] [Description(@"hare")]
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 public class OsuIconStore : ITextureStore, ITexturedGlyphLookupStore

View File

@@ -184,11 +184,6 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString SelectedMods => new TranslatableString(getKey(@"selected_mods"), @"Selected Mods"); 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}"; private static string getKey(string key) => $@"{prefix}:{key}";
} }
} }

View File

@@ -0,0 +1,12 @@
// 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.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class GetMusicCategoriesRequest : APIRequest<APIBackgroundCategories>
{
protected override string Target => @"https://osu.jvnko.boats/welcome-music/categories";
}
}

View File

@@ -0,0 +1,21 @@
// 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.Net;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class GetWelcomeMusicRequest : APIRequest<List<APIWelcomeMusic>>
{
private readonly string category;
public GetWelcomeMusicRequest(string category)
{
this.category = category;
}
protected override string Target => $"https://osu.jvnko.boats/welcome-music/list?category={WebUtility.UrlEncode(category)}";
}
}

View File

@@ -0,0 +1,17 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using Newtonsoft.Json;
namespace osu.Game.Online.API.Requests.Responses
{
// Описывает один трек, как он приходит с сервера
public class APIWelcomeMusic
{
[JsonProperty("name")]
public string? Name { get; set; }
[JsonProperty("url")]
public string? Url { get; set; }
}
}

View File

@@ -32,6 +32,7 @@ using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Collections; using osu.Game.Collections;
using osu.Game.Configuration; using osu.Game.Configuration;
@@ -170,8 +171,11 @@ namespace osu.Game
[Cached] [Cached]
private readonly ScreenshotManager screenshotManager = new ScreenshotManager(); private readonly ScreenshotManager screenshotManager = new ScreenshotManager();
// --- ÍÀØÈ ÍÎÂÛÅ ÊÎÌÏÎÍÅÍÒÛ ---
[Cached] [Cached]
private SeasonalBackgroundLoader seasonalBackgroundLoader; private readonly SeasonalBackgroundLoader backgroundLoader;
[Cached]
private readonly WelcomeMusicManager musicManager;
protected SentryLogger SentryLogger; protected SentryLogger SentryLogger;
@@ -253,6 +257,12 @@ namespace osu.Game
public OsuGame(string[] args = null) public OsuGame(string[] args = null)
{ {
// --- ÑÎÇÄÀÅÌ ÍÀØÈ ÊÎÌÏÎÍÅÍÒÛ È ÏÎÄÏÈÑÛÂÀÅÌÑß ÍÀ ÑÎÁÛÒÈß ---
backgroundLoader = new SeasonalBackgroundLoader();
backgroundLoader.OnLoadFailure += handleBackgroundLoadFailure;
backgroundLoader.OnCategoriesRefreshed += handleCategoriesRefreshed;
musicManager = new WelcomeMusicManager();
this.args = args; this.args = args;
Logger.NewEntry += forwardGeneralLogToNotifications; Logger.NewEntry += forwardGeneralLogToNotifications;
@@ -267,8 +277,6 @@ namespace osu.Game
tabletLogNotifyOnError = true; tabletLogNotifyOnError = true;
}, true); }, true);
}); });
initializeSeasonalBackgrounds();
} }
#region IOverlayManager #region IOverlayManager
@@ -407,6 +415,17 @@ namespace osu.Game
} }
} }
} }
private void handleCategoriesRefreshed()
{
Schedule(() =>
{
Notifications?.Post(new SimpleNotification
{
Text = "Ñïèñîê êàòåãîðèé ôîíîâ îáíîâëåí.",
Icon = FontAwesome.Solid.CheckCircle
});
});
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
@@ -457,7 +476,8 @@ namespace osu.Game
IsActive.BindValueChanged(active => updateActiveState(active.NewValue), true); IsActive.BindValueChanged(active => updateActiveState(active.NewValue), true);
Audio.AddAdjustment(AdjustableProperty.Volume, inactiveVolumeFade); Audio.AddAdjustment(AdjustableProperty.Volume, inactiveVolumeFade);
dependencies.CacheAs(musicManager);
Add(musicManager);
SelectedMods.BindValueChanged(modsChanged); SelectedMods.BindValueChanged(modsChanged);
Beatmap.BindValueChanged(beatmapChanged, true); Beatmap.BindValueChanged(beatmapChanged, true);
configUserActivity.BindValueChanged(_ => updateWindowTitle()); configUserActivity.BindValueChanged(_ => updateWindowTitle());
@@ -1238,7 +1258,7 @@ namespace osu.Game
loadComponentSingleFile(screenshotManager, Add); loadComponentSingleFile(screenshotManager, Add);
loadComponentSingleFile(seasonalBackgroundLoader, Add); loadComponentSingleFile(backgroundLoader, Add);
// dependency on notification overlay, dependent by settings overlay // dependency on notification overlay, dependent by settings overlay
loadComponentSingleFile(CreateUpdateManager(), Add, true); loadComponentSingleFile(CreateUpdateManager(), Add, true);
@@ -1273,7 +1293,12 @@ namespace osu.Game
}, rightFloatingOverlayContent.Add, true); }, rightFloatingOverlayContent.Add, true);
loadComponentSingleFile(new AccountCreationOverlay(), topMostOverlayContent.Add, true); loadComponentSingleFile(new AccountCreationOverlay(), topMostOverlayContent.Add, true);
loadComponentSingleFile<IDialogOverlay>(new DialogOverlay(), topMostOverlayContent.Add, true);
var dialogOverlay = new DialogOverlay();
dependencies.CacheAs<IDialogOverlay>(dialogOverlay);
dependencies.Cache(dialogOverlay);
loadComponentSingleFile(dialogOverlay, topMostOverlayContent.Add);
loadComponentSingleFile(new MedalOverlay(), topMostOverlayContent.Add); loadComponentSingleFile(new MedalOverlay(), topMostOverlayContent.Add);
loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add); loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add);
@@ -1341,6 +1366,18 @@ namespace osu.Game
handleStartupImport(); handleStartupImport();
} }
private void handleBackgroundLoadFailure(Exception exception)
{
Schedule(() =>
{
Notifications?.Post(new SimpleErrorNotification
{
Text = ButtonSystemStrings.SeasonalBackgroundsFail,
Icon = FontAwesome.Solid.ExclamationTriangle,
Transient = true
});
});
}
private void handleBackButton() private void handleBackButton()
{ {
// TODO: this is SUPER SUPER bad. // TODO: this is SUPER SUPER bad.
@@ -1477,40 +1514,6 @@ 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; private Task asyncLoadStream;
/// <summary> /// <summary>

View File

@@ -298,7 +298,7 @@ namespace osu.Game
Audio.Samples.PlaybackConcurrency = SAMPLE_CONCURRENCY; Audio.Samples.PlaybackConcurrency = SAMPLE_CONCURRENCY;
dependencies.Cache(SkinManager = new SkinManager(Storage, realm, Host, Resources, Audio, Scheduler, LocalConfig)); dependencies.Cache(SkinManager = new SkinManager(Storage, realm, Host, Resources, Audio, Scheduler));
dependencies.CacheAs<ISkinSource>(SkinManager); dependencies.CacheAs<ISkinSource>(SkinManager);
EndpointConfiguration endpoints = CreateEndpoints(); 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() // 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(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, API, LocalConfig));
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true, config: LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true));
dependencies.CacheAs<IWorkingBeatmapCache>(BeatmapManager); dependencies.CacheAs<IWorkingBeatmapCache>(BeatmapManager);
dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API)); dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API));

View File

@@ -180,7 +180,7 @@ namespace osu.Game.Overlays
notification.Closed += () => notificationClosed(notification); notification.Closed += () => notificationClosed(notification);
if (notification is IHasCompletionTarget hasCompletionTarget) if (notification is IHasCompletionTarget hasCompletionTarget)
hasCompletionTarget.CompletionTarget ??= Post; hasCompletionTarget.CompletionTarget = Post;
playDebouncedSample(notification.PopInSampleName); playDebouncedSample(notification.PopInSampleName);

View File

@@ -8,7 +8,6 @@ using osu.Framework.Graphics;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.Configuration;
using osu.Game.Localisation; using osu.Game.Localisation;
using osu.Game.Screens; using osu.Game.Screens;
using osu.Game.Screens.Import; using osu.Game.Screens.Import;
@@ -23,19 +22,13 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
private ISystemFileSelector? selector; private ISystemFileSelector? selector;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuGameBase game, GameHost host, IPerformFromScreenRunner? performer, OsuConfigManager config) private void load(OsuGameBase game, GameHost host, IPerformFromScreenRunner? performer)
{ {
if ((selector = host.CreateSystemFileSelector(game.HandledExtensions.ToArray())) != null) if ((selector = host.CreateSystemFileSelector(game.HandledExtensions.ToArray())) != null)
selector.Selected += f => Task.Run(() => game.Import(f.FullName)); selector.Selected += f => Task.Run(() => game.Import(f.FullName));
AddRange(new Drawable[] AddRange(new Drawable[]
{ {
new SettingsCheckbox
{
LabelText = "Delete archives on import",
Current = config.GetBindable<bool>(OsuSetting.DeleteImportedArchives),
ClassicDefault = true
},
new SettingsButton new SettingsButton
{ {
Text = DebugSettingsStrings.ImportFiles, Text = DebugSettingsStrings.ImportFiles,
@@ -51,7 +44,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{ {
Text = DebugSettingsStrings.RunLatencyCertifier, Text = DebugSettingsStrings.RunLatencyCertifier,
Action = () => performer?.PerformFromScreen(menu => menu.Push(new LatencyCertifierScreen())) Action = () => performer?.PerformFromScreen(menu => menu.Push(new LatencyCertifierScreen()))
}, }
}); });
} }
} }

View File

@@ -7,11 +7,13 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Audio;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Backgrounds;
using osu.Game.Localisation; using osu.Game.Localisation;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Dialog;
namespace osu.Game.Overlays.Settings.Sections.UserInterface namespace osu.Game.Overlays.Settings.Sections.UserInterface
{ {
@@ -21,53 +23,108 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
[Resolved] [Resolved]
private SeasonalBackgroundLoader backgroundLoader { get; set; } private SeasonalBackgroundLoader backgroundLoader { get; set; }
[Resolved]
private WelcomeMusicManager musicManager { get; set; }
[Resolved]
private DialogOverlay dialogOverlay { get; set; }
private IBindable<APIUser> user; private IBindable<APIUser> user;
private SettingsEnumDropdown<BackgroundSource> backgroundSourceDropdown; private SettingsEnumDropdown<BackgroundSource> backgroundSourceDropdown;
private Bindable<bool> useSeasonalBackgrounds;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager config, IAPIProvider api) private void load(OsuConfigManager config, IAPIProvider api)
{ {
AutoSizeAxes = Axes.Y;
user = api.LocalUser.GetBoundCopy(); user = api.LocalUser.GetBoundCopy();
useSeasonalBackgrounds = config.GetBindable<bool>(OsuSetting.UseSeasonalBackgroundsV2);
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);
var backgroundToggle = new SettingsCheckbox var backgroundToggle = new SettingsCheckbox
{ {
LabelText = UserInterfaceStrings.UseSeasonalBackgrounds, LabelText = UserInterfaceStrings.UseSeasonalBackgrounds,
Current = config.GetBindable<bool>(OsuSetting.UseSeasonalBackgroundsV2), Current = enabledProxyBindable
ClassicDefault = true
}; };
var categoryDropdown = new SettingsDropdown<string> var categoryDropdown = new SettingsDropdown<string>
{ {
LabelText = UserInterfaceStrings.SeasonalBackgroundsCategories, LabelText = UserInterfaceStrings.SeasonalBackgroundsCategories,
Current = config.GetBindable<string>(OsuSetting.BackgroundCategory) Current = config.GetBindable<string>(OsuSetting.BackgroundCategory)
}; };
var refreshButton = new SettingsButton var refreshButton = new SettingsButton
{ {
Text = UserInterfaceStrings.SeasonalBackgroundsRefresh, Text = UserInterfaceStrings.SeasonalBackgroundsRefresh,
Action = () => backgroundLoader.RefreshCategories() Action = () => backgroundLoader.RefreshCategories()
}; };
// 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)
);
backgroundLoader.AvailableCategories.BindValueChanged(categories => categoryDropdown.Items = categories.NewValue, true); backgroundLoader.AvailableCategories.BindValueChanged(categories => categoryDropdown.Items = categories.NewValue, true);
backgroundModeBindable.BindValueChanged(mode =>
{
if (mode.NewValue == SeasonalBackgroundMode.Always)
{
categoryDropdown.Show();
refreshButton.Show();
}
else
{
categoryDropdown.Hide();
refreshButton.Hide();
}
}, true);
var musicModeDropdown = new SettingsEnumDropdown<WelcomeMusicMode>
{
LabelText = "Музыкальное приветствие",
Current = config.GetBindable<WelcomeMusicMode>(OsuSetting.WelcomeMusicMode)
};
var musicCategoryDropdown = new SettingsDropdown<string>
{
LabelText = "Категория музыки",
Current = config.GetBindable<string>(OsuSetting.WelcomeMusicCategory)
};
var refreshMusicButton = new SettingsButton
{
Text = "Обновить категории музыки",
Action = () => musicManager.RefreshCategories()
};
musicModeDropdown.Current.BindValueChanged(mode =>
{
if (mode.NewValue == WelcomeMusicMode.Custom)
{
musicCategoryDropdown.Show();
refreshMusicButton.Show();
}
else
{
musicCategoryDropdown.Hide();
refreshMusicButton.Hide();
}
}, true);
musicCategoryDropdown.Current.BindValueChanged(category =>
{
if (category.OldValue != null &&
musicModeDropdown.Current.Value == WelcomeMusicMode.Custom &&
!category.OldValue.Equals(category.NewValue))
{
dialogOverlay.Push(new ConfirmDialog("Для применения этой настройки требуется перезапуск.",
() => musicManager.RequestRestart()));
}
});
musicManager.AvailableCategories.BindValueChanged(categories => musicCategoryDropdown.Items = categories.NewValue, true);
Children = new Drawable[] Children = new Drawable[]
{ {
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = UserInterfaceStrings.ShowMenuTips, LabelText = UserInterfaceStrings.ShowMenuTips,
Current = config.GetBindable<bool>(OsuSetting.MenuTips), Current = config.GetBindable<bool>(OsuSetting.MenuTips)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
@@ -81,6 +138,9 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
LabelText = UserInterfaceStrings.OsuMusicTheme, LabelText = UserInterfaceStrings.OsuMusicTheme,
Current = config.GetBindable<bool>(OsuSetting.MenuMusic) Current = config.GetBindable<bool>(OsuSetting.MenuMusic)
}, },
musicModeDropdown,
musicCategoryDropdown,
refreshMusicButton,
new SettingsEnumDropdown<IntroSequence> new SettingsEnumDropdown<IntroSequence>
{ {
LabelText = UserInterfaceStrings.IntroSequence, LabelText = UserInterfaceStrings.IntroSequence,
@@ -94,12 +154,6 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
backgroundToggle, backgroundToggle,
categoryDropdown, categoryDropdown,
refreshButton, refreshButton,
new SettingsColour
{
LabelText = @"osu! logo colour",
Current = config.GetBindable<Colour4>(OsuSetting.MenuCookieColor),
ClassicDefault = Colour4.FromHex(@"ff66ba"),
},
}; };
} }

View File

@@ -19,12 +19,6 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
{ {
Children = new Drawable[] Children = new Drawable[]
{ {
new SettingsCheckbox
{
LabelText = UserInterfaceStrings.ForceLegacySongSelect,
Current = config.GetBindable<bool>(OsuSetting.ForceLegacySongSelect),
ClassicDefault = false
},
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = UserInterfaceStrings.ShowConvertedBeatmaps, LabelText = UserInterfaceStrings.ShowConvertedBeatmaps,

View File

@@ -56,9 +56,6 @@ namespace osu.Game.Rulesets.Mods
{ {
var bindable = (IBindable)property.GetValue(this)!; var bindable = (IBindable)property.GetValue(this)!;
if (bindable.IsDefault)
continue;
string valueText; string valueText;
switch (bindable) switch (bindable)
@@ -72,6 +69,7 @@ namespace osu.Game.Rulesets.Mods
break; break;
} }
if (!bindable.IsDefault)
yield return (attr.Label, valueText); yield return (attr.Label, valueText);
} }
} }

View File

@@ -6,10 +6,8 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Localisation.HUD; using osu.Game.Localisation.HUD;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@@ -26,8 +24,6 @@ namespace osu.Game.Rulesets.Mods
public override LocalisableString Description => "Fail if your accuracy drops too low!"; public override LocalisableString Description => "Fail if your accuracy drops too low!";
public override IconUsage? Icon => OsuIcon.ModAccuracyChallenge;
public override ModType Type => ModType.DifficultyIncrease; public override ModType Type => ModType.DifficultyIncrease;
public override double ScoreMultiplier => 1.0; public override double ScoreMultiplier => 1.0;

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