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/actions/runner-images/issues/12758
- 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
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>
<RepositoryUrl>https://github.com/ppy/osu</RepositoryUrl>
<PackageReleaseNotes>Automated release.</PackageReleaseNotes>
<Company>ppy Pty Ltd, jvnkosu! team</Company>
<Company>ppy Pty Ltd</Company>
<Copyright>Copyright (c) 2025 ppy Pty Ltd</Copyright>
<PackageTags>osu game</PackageTags>
</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.
jvnkosu! is not affiliated with, or endorsed by ppy Pty Ltd., but makes use of its open-source components and resources.
A free-to-win rhythm game. Rhythm is just a *click* away!
## License
Client source code is licensed under the MIT license, see the [LICENCE](LICENCE) file in repository root for more info.
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.
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
Building jvnkosu! from source is pretty much possible (and welcome here).
A few resources are available as starting points to getting involved and understanding the project:
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:
```
git clone https://gitea.jvnko.boats/jvnkosu/client
## 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:
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
```
To **compile**:
```
dotnet build osu.Desktop
When running locally to do any kind of performance testing, make sure to add `-c Release` to the build command, as the overhead of running with the default `Debug` configuration can be large (especially when testing with local framework modifications as below).
If the build fails, try to restore NuGet packages with `dotnet restore`.
### Testing with resource/framework modifications
Sometimes it may be necessary to cross-test changes in [osu-resources](https://github.com/ppy/osu-resources) or [osu-framework](https://github.com/ppy/osu-framework). This can be quickly achieved using included commands:
Windows:
```ps
UseLocalFramework.ps1
UseLocalResources.ps1
```
To 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>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.829.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.808.0" />
</ItemGroup>
<PropertyGroup>
<!-- Fody does not handle Android build well, and warns when unchanged.

View File

@@ -3,11 +3,11 @@
<TargetFramework>net8.0</TargetFramework>
<OutputType>WinExe</OutputType>
<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>
<AssemblyTitle>jvnkosu!</AssemblyTitle>
<Title>jvnkosu!</Title>
<Product>jvnkosu!</Product>
<AssemblyTitle>osu!(lazer)</AssemblyTitle>
<Title>osu!</Title>
<Product>osu!(lazer)</Product>
<ApplicationIcon>lazer.ico</ApplicationIcon>
<Version>0.0.0</Version>
<FileVersion>0.0.0</FileVersion>

View File

@@ -3,19 +3,16 @@
<metadata>
<id>osulazer</id>
<version>0.0.0</version>
<title>jvnkosu!</title>
<authors>ppy Pty Ltd., jvnkosu! team</authors>
<title>osu!</title>
<authors>ppy Pty Ltd</authors>
<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>
<icon>icon.png</icon>
<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>
<copyright>
Copyright (c) 2025 ppy Pty Ltd
Copyright (c) 2025 jvnkosu! team
</copyright>
<copyright>Copyright (c) 2025 ppy Pty Ltd</copyright>
<language>en-AU</language>
</metadata>
<files>

View File

@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Mods
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);

View File

@@ -3,7 +3,6 @@
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
@@ -17,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Mods
public override string Acronym => "FF";
public override LocalisableString Description => "The fruits are... floating?";
public override double ScoreMultiplier => 1;
public override IconUsage? Icon => OsuIcon.ModFloatingFruits;
public override IconUsage? Icon => FontAwesome.Solid.Cloud;
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.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods;
@@ -24,7 +23,7 @@ namespace osu.Game.Rulesets.Catch.Mods
public override LocalisableString Description => "Dashing by default, slow down!";
public override ModType Type => ModType.Fun;
public override double ScoreMultiplier => 1;
public override IconUsage? Icon => OsuIcon.ModMovingFast;
public override IconUsage? Icon => FontAwesome.Solid.Running;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax) };
private DrawableCatchRuleset drawableRuleset = null!;

View File

@@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Mania.Tests
}
[TestCase]
public void TestKeysFilterIntersection()
public void TestFilterIntersection()
{
var criteria = new ManiaFilterCriteria();
criteria.TryParseCustomKeywordCriteria("keys", Operator.Greater, "4");
@@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Mania.Tests
}
[TestCase]
public void TestInvalidKeysFilters()
public void TestInvalidFilters()
{
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.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.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
@@ -20,16 +19,12 @@ namespace osu.Game.Rulesets.Mania
public class ManiaFilterCriteria : IRulesetFilterCriteria
{
private readonly HashSet<int> includedKeyCounts = Enumerable.Range(1, LegacyBeatmapDecoder.MAX_MANIA_KEY_COUNT).ToHashSet();
private FilterCriteria.OptionalRange<float> longNotePercentage;
public bool Matches(BeatmapInfo beatmapInfo, FilterCriteria criteria)
{
int keyCount = ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo), criteria.Mods);
bool keyCountMatch = includedKeyCounts.Contains(keyCount);
bool longNotePercentageMatch = !longNotePercentage.HasFilter || (!isConvertedBeatmap(beatmapInfo) && longNotePercentage.IsInRange(calculateLongNotePercentage(beatmapInfo)));
return keyCountMatch && longNotePercentageMatch;
return includedKeyCounts.Contains(keyCount);
}
public bool TryParseCustomKeywordCriteria(string key, Operator op, string strValues)
@@ -89,10 +84,6 @@ namespace osu.Game.Rulesets.Mania
return false;
}
}
case "ln":
case "lns":
return FilterQueryParser.TryUpdateCriteriaRange(ref longNotePercentage, op, strValues);
}
return false;
@@ -112,18 +103,5 @@ namespace osu.Game.Rulesets.Mania
return false;
}
private static bool isConvertedBeatmap(BeatmapInfo beatmapInfo)
{
return !beatmapInfo.Ruleset.Equals(new ManiaRuleset().RulesetInfo);
}
private static float calculateLongNotePercentage(BeatmapInfo beatmapInfo)
{
int holdNotes = beatmapInfo.EndTimeObjectCount;
int totalNotes = Math.Max(1, beatmapInfo.TotalObjectCount);
return holdNotes / (float)totalNotes * 100;
}
}
}

View File

@@ -4,7 +4,6 @@
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
@@ -22,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Mods
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;

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,6 @@ using osu.Game.Rulesets.Mods;
using osu.Framework.Graphics.Sprites;
using System.Collections.Generic;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Beatmaps;
namespace osu.Game.Rulesets.Mania.Mods
@@ -24,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Mods
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;

View File

@@ -8,7 +8,6 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods;
@@ -24,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Mods
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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -32,6 +32,26 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
[Test]
public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
[Test]
public void TestPlayfieldBasedSize()
{
OsuModFlashlight flashlight;
CreateModTest(new ModTestData
{
Mods = [flashlight = new OsuModFlashlight(), new OsuModBarrelRoll()],
PassCondition = () =>
{
var flashlightOverlay = Player.DrawableRuleset.Overlays
.ChildrenOfType<ModFlashlight<OsuHitObject>.Flashlight>()
.First();
// the combo check is here because the flashlight radius decreases for the first time at 100 combo
// and hardcoding it here eliminates the need to meddle in flashlight internals further by e.g. exposing `GetComboScaleFor()`
return flashlightOverlay.GetSize() < flashlight.DefaultFlashlightSize && Player.GameplayState.ScoreProcessor.Combo.Value < 100;
}
});
}
[Test]
public void TestSliderDimsOnlyAfterStartTime()
{

View File

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

View File

@@ -5,7 +5,6 @@ using System;
using System.Linq;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Osu.Mods
{
@@ -14,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Name => @"Alternate";
public override string Acronym => @"AL";
public override LocalisableString Description => @"Don't use the same key twice in a row!";
public override IconUsage? Icon => OsuIcon.ModAlternate;
public override IconUsage? Icon => FontAwesome.Solid.Keyboard;
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModSingleTap) }).ToArray();
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.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables;
@@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Acronym => "AD";
public override LocalisableString Description => "Never trust the approach circles...";
public override double ScoreMultiplier => 1;
public override IconUsage? Icon => OsuIcon.ModApproachDifferent;
public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle;
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.Utils;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
@@ -27,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override LocalisableString Description => "Play with blinds on your screen.";
public override string Acronym => "BL";
public override IconUsage? Icon => OsuIcon.ModBlinds;
public override IconUsage? Icon => FontAwesome.Solid.Adjust;
public override ModType Type => ModType.DifficultyIncrease;
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;

View File

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

View File

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

View File

@@ -4,7 +4,6 @@
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Osu.Mods
{
@@ -14,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Mods
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!";

View File

@@ -7,7 +7,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
@@ -22,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Depth";
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 LocalisableString Description => "3D. Almost.";
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 float DefaultFlashlightSize => 125;
public override float DefaultFlashlightSize => 200;
private OsuFlashlight flashlight = null!;

View File

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

View File

@@ -4,7 +4,6 @@
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Osu.Mods
{
@@ -14,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Mods
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!";

View File

@@ -9,7 +9,6 @@ using osu.Framework.Localisation;
using osu.Framework.Timing;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
@@ -24,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Magnetised";
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 LocalisableString Description => "No need to chase the circles your cursor is a magnet!";
public override double ScoreMultiplier => 0.5;

View File

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

View File

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

View File

@@ -5,7 +5,6 @@ using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
@@ -18,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Spin In";
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 LocalisableString Description => "Circles spin in. No approach circles.";
public override double ScoreMultiplier => 1;

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,6 @@ using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
@@ -19,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Transform";
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 LocalisableString Description => "Everything rotates. EVERYTHING.";
public override double ScoreMultiplier => 1;

View File

@@ -7,7 +7,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
@@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Wiggle";
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 LocalisableString Description => "They just won't stay still...";
public override double ScoreMultiplier => 1;

View File

@@ -3,10 +3,7 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.Taiko.UI;
using osuTK;
@@ -15,34 +12,6 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods
{
public partial class TestSceneTaikoModFlashlight : TaikoModTestScene
{
[Test]
public void TestAspectRatios([Values] bool withClassicMod)
{
if (withClassicMod)
CreateModTest(new ModTestData { Mods = new Mod[] { new TaikoModFlashlight(), new TaikoModClassic() }, PassCondition = () => true });
else
CreateModTest(new ModTestData { Mod = new TaikoModFlashlight(), PassCondition = () => true });
AddStep("clear dim", () => LocalConfig.SetValue(OsuSetting.DimLevel, 0.0));
AddStep("reset", () => Stack.FillMode = FillMode.Stretch);
AddStep("set to 16:9", () =>
{
Stack.FillAspectRatio = 16 / 9f;
Stack.FillMode = FillMode.Fit;
});
AddStep("set to 4:3", () =>
{
Stack.FillAspectRatio = 4 / 3f;
Stack.FillMode = FillMode.Fit;
});
AddSliderStep("aspect ratio", 0.01f, 5f, 1f, v =>
{
Stack.FillAspectRatio = v;
Stack.FillMode = FillMode.Fit;
});
}
[TestCase(1f)]
[TestCase(0.5f)]
[TestCase(1.25f)]

View File

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

View File

@@ -47,15 +47,28 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
this.taikoPlayfield = taikoPlayfield;
FlashlightSize = new Vector2(0, GetSize());
FlashlightSize = adjustSizeForPlayfieldAspectRatio(GetSize());
FlashlightSmoothness = 1.4f;
AddLayout(flashlightProperties);
}
/// <summary>
/// Returns the aspect ratio-adjusted size of the flashlight.
/// This ensures that the size of the flashlight remains independent of taiko-specific aspect ratio adjustments.
/// </summary>
/// <param name="size">
/// The size of the flashlight.
/// The value provided here should always come from <see cref="ModFlashlight{T}.Flashlight.GetSize"/>.
/// </param>
private Vector2 adjustSizeForPlayfieldAspectRatio(float size)
{
return new Vector2(0, size * taikoPlayfield.Parent!.Scale.Y);
}
protected override void UpdateFlashlightSize(float size)
{
this.TransformTo(nameof(FlashlightSize), new Vector2(0, size), FLASHLIGHT_FADE_DURATION);
this.TransformTo(nameof(FlashlightSize), adjustSizeForPlayfieldAspectRatio(size), FLASHLIGHT_FADE_DURATION);
}
protected override string FragmentShader => "CircularFlashlight";
@@ -69,7 +82,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
FlashlightPosition = ToLocalSpace(taikoPlayfield.HitTarget.ScreenSpaceDrawQuad.Centre);
ClearTransforms(targetMember: nameof(FlashlightSize));
FlashlightSize = new Vector2(0, GetSize());
FlashlightSize = adjustSizeForPlayfieldAspectRatio(GetSize());
flashlightProperties.Validate();
}

View File

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

View File

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

View File

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

View File

@@ -66,18 +66,6 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("toggle black background", () => blackBackground?.FadeTo(1 - blackBackground.Alpha, 300, Easing.OutQuint));
AddSliderStep("leaderboard width", 0, 800, 300, v =>
{
if (leaderboard.IsNotNull())
leaderboard.Width = v;
});
AddSliderStep("leaderboard height", 0, 1000, 300, v =>
{
if (leaderboard.IsNotNull())
leaderboard.Height = v;
});
AddSliderStep("set player score", 50, 1_000_000, 700_000, v => gameplayState.ScoreProcessor.TotalScore.Value = v);
}
@@ -120,45 +108,6 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for 1st spot", () => leaderboard.TrackedScore!.ScorePosition.Value, () => Is.EqualTo(1));
}
[Test]
public void TestLongScores()
{
AddStep("set scores", () =>
{
var friend = new APIUser { Username = "Friend", Id = 1337 };
var api = (DummyAPIAccess)API;
api.Friends.Clear();
api.Friends.Add(new APIRelation
{
Mutual = true,
RelationType = RelationType.Friend,
TargetID = friend.OnlineID,
TargetUser = friend
});
// this is dodgy but anything less dodgy is a lot of work
((Bindable<LeaderboardScores?>)leaderboardManager.Scores).Value = LeaderboardScores.Success(new[]
{
new ScoreInfo { User = new APIUser { Username = "Top", Id = 2 }, TotalScore = 900_000_000, Accuracy = 0.99, MaxCombo = 999999 },
new ScoreInfo { User = new APIUser { Username = "Second", Id = 14 }, TotalScore = 800_000_000, Accuracy = 0.9, MaxCombo = 888888 },
new ScoreInfo { User = friend, TotalScore = 700_000_000, Accuracy = 0.88, MaxCombo = 777777 },
}, 3, null);
});
createLeaderboard();
AddStep("set score to 650k", () => gameplayState.ScoreProcessor.TotalScore.Value = 650_000_000);
AddUntilStep("wait for 4th spot", () => leaderboard.TrackedScore!.ScorePosition.Value, () => Is.EqualTo(4));
AddStep("set score to 750k", () => gameplayState.ScoreProcessor.TotalScore.Value = 750_000_000);
AddUntilStep("wait for 3rd spot", () => leaderboard.TrackedScore!.ScorePosition.Value, () => Is.EqualTo(3));
AddStep("set score to 850k", () => gameplayState.ScoreProcessor.TotalScore.Value = 850_000_000);
AddUntilStep("wait for 2nd spot", () => leaderboard.TrackedScore!.ScorePosition.Value, () => Is.EqualTo(2));
AddStep("set score to 950k", () => gameplayState.ScoreProcessor.TotalScore.Value = 950_000_000);
AddUntilStep("wait for 1st spot", () => leaderboard.TrackedScore!.ScorePosition.Value, () => Is.EqualTo(1));
}
[Test]
public void TestLayoutWithManyScores()
{

View File

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

View File

@@ -28,7 +28,6 @@ using osu.Game.Storyboards;
using osu.Game.Tests.Beatmaps.IO;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
@@ -303,69 +302,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddWaitStep("wait a bit", 10);
}
[Test]
[Explicit("Test relies on timing of arriving frames to exercise assertions which doesn't work headless.")]
public void TestMaximisedUserIsAudioSource()
{
start(new[] { PLAYER_1_ID, PLAYER_2_ID });
loadSpectateScreen();
// With no frames, the synchronisation state will be TooFarAhead.
// In this state, all players should be muted.
assertMuted(PLAYER_1_ID, true);
assertMuted(PLAYER_2_ID, true);
// Send frames for both players.
sendFrames(PLAYER_1_ID, 20);
sendFrames(PLAYER_2_ID, 40);
waitUntilRunning(PLAYER_1_ID);
AddStep("maximise player 1", () =>
{
InputManager.MoveMouseTo(getInstance(PLAYER_1_ID));
InputManager.Click(MouseButton.Left);
});
assertMuted(PLAYER_1_ID, false);
assertMuted(PLAYER_2_ID, true);
waitUntilPaused(PLAYER_1_ID);
assertMuted(PLAYER_1_ID, false);
assertMuted(PLAYER_2_ID, true);
AddStep("minimise player 1", () =>
{
InputManager.MoveMouseTo(getInstance(PLAYER_1_ID));
InputManager.Click(MouseButton.Left);
});
assertMuted(PLAYER_1_ID, true);
assertMuted(PLAYER_2_ID, false);
AddStep("maximise player 2", () =>
{
InputManager.MoveMouseTo(getInstance(PLAYER_2_ID));
InputManager.Click(MouseButton.Left);
});
assertMuted(PLAYER_1_ID, true);
assertMuted(PLAYER_2_ID, false);
waitUntilPaused(PLAYER_2_ID);
sendFrames(PLAYER_1_ID, 60);
assertMuted(PLAYER_1_ID, true);
assertMuted(PLAYER_2_ID, false);
AddStep("minimise player 2", () =>
{
InputManager.MoveMouseTo(getInstance(PLAYER_2_ID));
InputManager.Click(MouseButton.Left);
});
assertMuted(PLAYER_1_ID, false);
assertMuted(PLAYER_2_ID, true);
}
[Test]
[FlakyTest]
public void TestMostInSyncUserIsAudioSourceIfNoneMaximised()
public void TestMostInSyncUserIsAudioSource()
{
start(new[] { PLAYER_1_ID, PLAYER_2_ID });
loadSpectateScreen();

View File

@@ -181,7 +181,7 @@ namespace osu.Game.Tests.Visual.Navigation
AddUntilStep("beatmap in song select", () =>
{
var songSelect = (SoloSongSelect)Game.ScreenStack.CurrentScreen;
return songSelect.ChildrenOfType<BeatmapCarousel>().Single().GetCarouselItems()!.Any(i => i.Model is 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> 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>();

View File

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

View File

@@ -237,7 +237,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
// Using groupingFilter.SetItems.Count alone doesn't work.
// When sorting by difficulty, there can be more than one set panel for the same set displayed.
return groupingFilter.SetItems.Sum(s => s.Value.Count(i => i.Model is GroupedBeatmapSet));
return groupingFilter.SetItems.Sum(s => s.Value.Count(i => i.Model is BeatmapSetInfo));
}, () => Is.EqualTo(expected));
}
@@ -440,7 +440,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
public BeatmapInfo? SelectedBeatmapInfo => CurrentSelection as BeatmapInfo;
public BeatmapSetInfo? SelectedBeatmapSet => SelectedBeatmapInfo?.BeatmapSet;
public new GroupedBeatmapSet? ExpandedBeatmapSet => base.ExpandedBeatmapSet;
public new BeatmapSetInfo? ExpandedBeatmapSet => base.ExpandedBeatmapSet;
public new GroupDefinition? ExpandedGroup => base.ExpandedGroup;
public TestBeatmapCarousel()

View File

@@ -22,6 +22,8 @@ using osu.Game.Overlays.Toolbar;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Footer;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Select.Filter;
using osu.Game.Screens.SelectV2;
@@ -41,6 +43,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
protected Screens.SelectV2.SongSelect SongSelect { get; private set; } = null!;
protected BeatmapCarousel Carousel => SongSelect.ChildrenOfType<BeatmapCarousel>().Single();
[Cached]
protected readonly ScreenFooter Footer;
[Cached]
private readonly OsuLogo logo;
@@ -67,6 +72,10 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{
State = { Value = Visibility.Visible },
},
Footer = new ScreenFooter
{
BackButtonPressed = () => Stack.CurrentScreen.Exit(),
},
logo = new OsuLogo
{
Alpha = 0f,
@@ -102,6 +111,14 @@ namespace osu.Game.Tests.Visual.SongSelectV2
Add(beatmapStore);
}
protected override void LoadComplete()
{
base.LoadComplete();
Stack.ScreenPushed += updateFooter;
Stack.ScreenExited += updateFooter;
}
public override void SetUpSteps()
{
base.SetUpSteps();
@@ -190,5 +207,38 @@ namespace osu.Game.Tests.Visual.SongSelectV2
}
protected void WaitForSuspension() => AddUntilStep("wait for not current", () => !SongSelect.AsNonNull().IsCurrentScreen());
private void updateFooter(IScreen? _, IScreen? newScreen)
{
if (newScreen is OsuScreen osuScreen && osuScreen.ShowFooter)
{
Footer.Show();
if (osuScreen.IsLoaded)
updateFooterButtons();
else
{
// ensure the current buttons are immediately disabled on screen change (so they can't be pressed).
Footer.SetButtons(Array.Empty<ScreenFooterButton>());
osuScreen.OnLoadComplete += _ => updateFooterButtons();
}
void updateFooterButtons()
{
var buttons = osuScreen.CreateFooterButtons();
osuScreen.LoadComponentsAgainstScreenDependencies(buttons);
Footer.SetButtons(buttons);
Footer.Show();
}
}
else
{
Footer.Hide();
Footer.SetButtons(Array.Empty<ScreenFooterButton>());
}
}
}
}

View File

@@ -396,9 +396,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
ApplyToFilterAndWaitForFilter("filter first away", c => c.UserStarDifficulty.Min = 3);
SelectNextPanel();
AddAssert("keyboard selected is first set",
() => (GetKeyboardSelectedPanel()?.Item?.Model as GroupedBeatmapSet)?.BeatmapSet,
() => Is.EqualTo(BeatmapSets.First()));
AddAssert("keyboard selected is first set", () => GetKeyboardSelectedPanel()?.Item?.Model, () => Is.EqualTo(BeatmapSets.First()));
}
[Test]
@@ -415,9 +413,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
ApplyToFilterAndWaitForFilter("filter first away", c => c.UserStarDifficulty.Min = 3);
SelectPrevPanel();
AddAssert("keyboard selected is last set",
() => (GetKeyboardSelectedPanel()?.Item?.Model as GroupedBeatmapSet)?.BeatmapSet,
() => Is.EqualTo(BeatmapSets.Last()));
AddAssert("keyboard selected is last set", () => GetKeyboardSelectedPanel()?.Item?.Model, () => Is.EqualTo(BeatmapSets.Last()));
}
[Test]
@@ -432,9 +428,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
ApplyToFilterAndWaitForFilter("filter last set away", c => c.SearchText = BeatmapSets.First().Metadata.Title);
SelectPrevPanel();
AddAssert("keyboard selected is first set",
() => (GetKeyboardSelectedPanel()?.Item?.Model as GroupedBeatmapSet)?.BeatmapSet,
() => Is.EqualTo(BeatmapSets.First()));
AddAssert("keyboard selected is first set", () => GetKeyboardSelectedPanel()?.Item?.Model, () => Is.EqualTo(BeatmapSets.First()));
}
[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.
SelectPrevPanel();
AddAssert("keyboard selected is second set",
() => (GetKeyboardSelectedPanel()?.Item?.Model as GroupedBeatmapSet)?.BeatmapSet,
() => Is.EqualTo(BeatmapSets.Last()));
AddAssert("keyboard selected is second set", () => GetKeyboardSelectedPanel()?.Item?.Model, () => 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 const int initial_filter_count = 3;
[SetUpSteps]
public void SetUpSteps()
{
RemoveAllBeatmaps();
CreateCarousel();
WaitForFiltering();
AddBeatmaps(1, 3);
WaitForFiltering();
AddStep("generate and add test beatmap", () =>
{
baseTestBeatmap = TestResources.CreateTestBeatmapSetInfo(3);
@@ -46,9 +42,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
b.Metadata = metadata;
BeatmapSets.Add(baseTestBeatmap);
});
WaitForFiltering();
AddAssert("filter count correct", () => Carousel.FilterCount, () => Is.EqualTo(initial_filter_count));
WaitForFiltering();
}
[Test]
@@ -86,18 +81,12 @@ namespace osu.Game.Tests.Visual.SongSelectV2
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",
Title = $"beatmap {RNG.Next().ToString()}"
};
Artist = "updated test",
Title = $"beatmap {RNG.Next().ToString()}"
});
assertDidFilter();
WaitForFiltering();
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")));
updateBeatmap(b =>
{
b.Metadata = metadata;
// hash will be updated when important metadata changes, such as title, difficulty, author etc.
b.Hash = "new hash";
});
updateBeatmap(b => b.Metadata = metadata);
assertDidFilter();
WaitForFiltering();
AddAssert("drawables unchanged", () => Carousel.ChildrenOfType<Panel>(), () => Is.EqualTo(originalDrawables));
@@ -140,41 +123,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
}
[Test]
public void TestOnlineStatusUpdated()
{
List<Panel> originalDrawables = new List<Panel>();
AddStep("store drawable references", () =>
{
originalDrawables.Clear();
originalDrawables.AddRange(Carousel.ChildrenOfType<Panel>().ToList());
});
updateBeatmap(b => b.Status = BeatmapOnlineStatus.Graveyard);
assertDidFilter();
WaitForFiltering();
AddAssert("drawables unchanged", () => Carousel.ChildrenOfType<Panel>(), () => Is.EqualTo(originalDrawables));
}
[Test]
public void TestNoUpdateTriggeredOnUserTagsChange()
{
var metadata = new BeatmapMetadata
{
Artist = "updated test",
Title = "new beatmap title",
UserTags = { "hi" }
};
updateBeatmap(b => b.Metadata = metadata);
assertDidNotFilter();
}
[TestCase(false)]
[TestCase(true)]
public void TestSelectionHeld(bool hashChanged)
public void TestSelectionHeld()
{
SelectNextSet();
@@ -182,17 +131,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
AddAssert("visible panel is updateable beatmap", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
updateBeatmap(b =>
{
if (hashChanged)
b.Hash = "new hash";
});
if (hashChanged)
assertDidFilter();
else
assertDidNotFilter();
updateBeatmap();
WaitForFiltering();
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]));
updateBeatmap(b => b.DifficultyName = "new name");
assertDidFilter();
WaitForFiltering();
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]));
updateBeatmap(b => b.OnlineID = b.OnlineID + 1);
assertDidFilter();
WaitForFiltering();
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));
}
private void assertDidFilter() => AddAssert("did filter", () => Carousel.FilterCount, () => Is.EqualTo(initial_filter_count + 1));
private void assertDidNotFilter() => AddAssert("did not filter", () => Carousel.FilterCount, () => Is.EqualTo(initial_filter_count));
private void updateBeatmap(Action<BeatmapInfo>? updateBeatmap = null, Action<BeatmapSetInfo>? updateSet = null)
{
AddStep("update beatmap with different reference", () =>

View File

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

View File

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

View File

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

View File

@@ -75,21 +75,21 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{
new PanelBeatmapSet
{
Item = new CarouselItem(new GroupedBeatmapSet(null, beatmapSet))
Item = new CarouselItem(beatmapSet)
},
new PanelBeatmapSet
{
Item = new CarouselItem(new GroupedBeatmapSet(null, beatmapSet)),
Item = new CarouselItem(beatmapSet),
KeyboardSelected = { Value = true }
},
new PanelBeatmapSet
{
Item = new CarouselItem(new GroupedBeatmapSet(null, beatmapSet)),
Item = new CarouselItem(beatmapSet),
Expanded = { Value = true }
},
new PanelBeatmapSet
{
Item = new CarouselItem(new GroupedBeatmapSet(null, beatmapSet)),
Item = new CarouselItem(beatmapSet),
KeyboardSelected = { 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.Leaderboards;
using osu.Game.Screens.SelectV2;
using osu.Game.Tests.Resources;
using osuTK.Input;
using BeatmapCarousel = osu.Game.Screens.SelectV2.BeatmapCarousel;
using FooterButtonMods = osu.Game.Screens.SelectV2.FooterButtonMods;
using FooterButtonOptions = osu.Game.Screens.SelectV2.FooterButtonOptions;
using FooterButtonRandom = osu.Game.Screens.SelectV2.FooterButtonRandom;
@@ -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]
public void TestAutoplayShortcut()
{

View File

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

View File

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

View File

@@ -6,7 +6,6 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Tournament.Components;
using osu.Game.Tournament.IPC;
using osu.Game.Tournament.Screens.Gameplay;
@@ -67,6 +66,6 @@ namespace osu.Game.Tournament.Tests.Screens
() => this.ChildrenOfType<TeamScore>().All(score => score.Alpha == (visible ? 1 : 0)));
private void toggleWarmup()
=> AddStep("toggle warmup", () => this.ChildrenOfType<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.Shapes;
using osu.Framework.Threading;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Settings;
using osu.Game.Tournament.Components;
using osu.Game.Tournament.IPC;
@@ -24,6 +24,7 @@ namespace osu.Game.Tournament.Screens.Gameplay
private readonly BindableBool warmup = new BindableBool();
public readonly Bindable<TourneyState> State = new Bindable<TourneyState>();
private OsuButton warmupButton = null!;
private MatchIPCInfo ipc = null!;
[Resolved]
@@ -39,8 +40,6 @@ namespace osu.Game.Tournament.Screens.Gameplay
{
this.ipc = ipc;
LabelledSwitchButton chatToggle;
AddRangeInternal(new Drawable[]
{
new TourneyVideo("gameplay")
@@ -96,14 +95,17 @@ namespace osu.Game.Tournament.Screens.Gameplay
{
Children = new Drawable[]
{
new LabelledSwitchButton
warmupButton = new TourneyButton
{
Label = "Warmup",
Current = warmup,
RelativeSizeAxes = Axes.X,
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>
{
@@ -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);
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()

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.Platform;
using osu.Game.Beatmaps.Formats;
using osu.Game.Configuration;
using osu.Game.Collections;
using osu.Game.Database;
using osu.Game.Extensions;
@@ -37,8 +36,8 @@ namespace osu.Game.Beatmaps
public ProcessBeatmapDelegate? ProcessBeatmap { private get; set; }
public BeatmapImporter(Storage storage, RealmAccess realm, OsuConfigManager? config)
: base(storage, realm, config)
public BeatmapImporter(Storage storage, RealmAccess realm)
: base(storage, realm)
{
}

View File

@@ -17,7 +17,6 @@ using osu.Framework.IO.Stores;
using osu.Framework.Platform;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Formats;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Extensions;
using osu.Game.IO.Archives;
@@ -60,7 +59,7 @@ namespace osu.Game.Beatmaps
}
public BeatmapManager(Storage storage, RealmAccess realm, IAPIProvider? api, AudioManager audioManager, IResourceStore<byte[]> gameResources, GameHost? host = null,
WorkingBeatmap? defaultBeatmap = null, BeatmapDifficultyCache? difficultyCache = null, bool performOnlineLookups = false, OsuConfigManager? config = null)
WorkingBeatmap? defaultBeatmap = null, BeatmapDifficultyCache? difficultyCache = null, bool performOnlineLookups = false)
: base(storage, realm)
{
if (performOnlineLookups)
@@ -76,7 +75,7 @@ namespace osu.Game.Beatmaps
BeatmapTrackStore = audioManager.GetTrackStore(userResources);
beatmapImporter = CreateBeatmapImporter(storage, realm, config);
beatmapImporter = CreateBeatmapImporter(storage, realm);
beatmapImporter.ProcessBeatmap = (beatmapSet, scope) => ProcessBeatmap?.Invoke(beatmapSet, scope);
beatmapImporter.PostNotification = obj => PostNotification?.Invoke(obj);
@@ -99,7 +98,7 @@ namespace osu.Game.Beatmaps
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>
/// 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
// high precision times (on windows there's generally only 5-10ms reporting intervals, as an example).
interpolatedTrack = new InterpolatingFramedClock(decoupledTrack)
{
DriftRecoveryHalfLife = 80,
};
interpolatedTrack = new InterpolatingFramedClock(decoupledTrack);
if (applyOffsets)
{

View File

@@ -8,7 +8,6 @@ using osu.Framework.Configuration;
using osu.Framework.Configuration.Tracking;
using osu.Framework.Extensions;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Game.Beatmaps.Drawables.Cards;
@@ -42,8 +41,6 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.Ruleset, string.Empty);
SetDefault(OsuSetting.Skin, SkinInfo.ARGON_SKIN.ToString());
SetDefault(OsuSetting.MenuCookieColor, Colour4.FromHex(@"ff66ba"));
SetDefault(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Local);
SetDefault(OsuSetting.BeatmapLeaderboardSortMode, LeaderboardSortMode.Score);
SetDefault(OsuSetting.BeatmapDetailModsFilter, false);
@@ -68,7 +65,6 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.ToolbarClockDisplayMode, ToolbarClockDisplayMode.Full);
SetDefault(OsuSetting.ForceLegacySongSelect, false);
SetDefault(OsuSetting.SongSelectBackgroundBlur, false);
// Online settings
@@ -198,12 +194,9 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.MenuBackgroundSource, BackgroundSource.Skin);
SetDefault(OsuSetting.SeasonalBackgroundMode, SeasonalBackgroundMode.Never);
SetDefault(OsuSetting.UseSeasonalBackgroundsV2, true);
SetDefault(OsuSetting.DiscordRichPresence, DiscordRichPresenceMode.Full);
SetDefault(OsuSetting.DeleteImportedArchives, true);
SetDefault(OsuSetting.EditorDim, 0.25f, 0f, 0.75f, 0.25f);
SetDefault(OsuSetting.EditorWaveformOpacity, 0.25f, 0f, 1f, 0.25f);
SetDefault(OsuSetting.EditorShowHitMarkers, true);
@@ -220,6 +213,8 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.MultiplayerShowInProgressFilter, true);
SetDefault(OsuSetting.LastProcessedMetadataId, -1);
SetDefault(OsuSetting.WelcomeMusicMode, WelcomeMusicMode.Default);
SetDefault(OsuSetting.WelcomeMusicCategory, "Default");
SetDefault(OsuSetting.ComboColourNormalisationAmount, 0.2f, 0f, 1f, 0.01f);
SetDefault(OsuSetting.UserOnlineStatus, UserStatus.Online);
@@ -389,6 +384,8 @@ namespace osu.Game.Configuration
AudioOffset,
VolumeInactive,
WelcomeMusicMode,
WelcomeMusicCategory,
MenuMusic,
MenuVoice,
MenuTips,
@@ -412,7 +409,6 @@ namespace osu.Game.Configuration
ChatDisplayHeight,
BeatmapListingCardSize,
ToolbarClockDisplayMode,
ForceLegacySongSelect,
SongSelectBackgroundBlur,
Version,
ShowFirstRunSetup,
@@ -420,7 +416,6 @@ namespace osu.Game.Configuration
Skin,
ScreenshotFormat,
ScreenshotCaptureMenuCursor,
MenuCookieColor,
BeatmapSkins,
BeatmapColours,
BeatmapHitsounds,
@@ -445,13 +440,11 @@ namespace osu.Game.Configuration
MenuBackgroundSource,
GameplayDisableWinKey,
SeasonalBackgroundMode,
UseSeasonalBackgroundsV2, // TODO: add migrations
BackgroundCategory,
EditorWaveformOpacity,
EditorShowHitMarkers,
EditorAutoSeekOnPlacement,
DiscordRichPresence,
DeleteImportedArchives,
ShowOnlineExplicitContent,
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...");
// find all ranked beatmap sets with missing date ranked or date submitted that have at least one difficulty ranked as well.
// the reason for checking ranked status of the difficulties is that they can be locally modified or unknown too, and for those the lookup is likely to fail.
// this is because metadata lookups are primarily based on file hash, so they will fail to match if the beatmap does not match the online version
// (which is likely to be the case if the beatmap is locally modified or unknown).
// that said, one difficulty in ranked state is enough for the backpopulation to work.
HashSet<Guid> beatmapSetIds = realmAccess.Run(r => new HashSet<Guid>(
r.All<BeatmapSetInfo>()
.Filter($@"{nameof(BeatmapSetInfo.StatusInt)} > 0 && ({nameof(BeatmapSetInfo.DateRanked)} == null || {nameof(BeatmapSetInfo.DateSubmitted)} == null) "
+ $@"&& ANY {nameof(BeatmapSetInfo.Beatmaps)}.{nameof(BeatmapInfo.StatusInt)} > 0")
.Where(b => b.StatusInt > 0 && (b.DateRanked == null || b.DateSubmitted == null))
.AsEnumerable()
.Select(b => b.ID)));
@@ -597,7 +591,11 @@ namespace osu.Game.Database
{
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);
@@ -727,7 +725,7 @@ namespace osu.Game.Database
}
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;
}
}

View File

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

View File

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

View File

@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Threading;
@@ -80,7 +79,7 @@ namespace osu.Game.Graphics.Carousel
/// <summary>
/// The number of times filter operations have been triggered.
/// </summary>
public int FilterCount { get; private set; }
internal int FilterCount { get; private set; }
/// <summary>
/// The number of displayable items currently being tracked (before filtering).
@@ -211,12 +210,6 @@ namespace osu.Game.Graphics.Carousel
return filterTask;
}
/// <summary>
/// Called when <see cref="Items"/> changes in any way.
/// </summary>
/// <returns>Whether a re-filter is required.</returns>
protected virtual bool HandleItemsChanged(NotifyCollectionChangedEventArgs args) => true;
/// <summary>
/// Fired after a filter operation completed.
/// </summary>
@@ -308,11 +301,7 @@ namespace osu.Game.Graphics.Carousel
RelativeSizeAxes = Axes.Both,
};
Items.BindCollectionChanged((_, args) =>
{
if (HandleItemsChanged(args))
filterAfterItemsChanged.Invalidate();
});
Items.BindCollectionChanged((_, _) => filterAfterItemsChanged.Invalidate());
}
[BackgroundDependencyLoader]

View File

@@ -81,6 +81,27 @@ namespace osu.Game.Graphics
public static IconUsage InsaneMania => get(0xe027);
public static IconUsage ExpertMania => get(0xe028);
// mod icons
public static IconUsage ModPerfect => get(0xe049);
public static IconUsage ModAutopilot => get(0xe03a);
public static IconUsage ModAuto => get(0xe03b);
public static IconUsage ModCinema => get(0xe03c);
public static IconUsage ModDoubleTime => get(0xe03d);
public static IconUsage ModEasy => get(0xe03e);
public static IconUsage ModFlashlight => get(0xe03f);
public static IconUsage ModHalftime => get(0xe040);
public static IconUsage ModHardRock => get(0xe041);
public static IconUsage ModHidden => get(0xe042);
public static IconUsage ModNightcore => get(0xe043);
public static IconUsage ModNoFail => get(0xe044);
public static IconUsage ModRelax => get(0xe045);
public static IconUsage ModSpunOut => get(0xe046);
public static IconUsage ModSuddenDeath => get(0xe047);
public static IconUsage ModTarget => get(0xe048);
// Use "Icons/BeatmapDetails/mod-icon" instead
// public static IconUsage ModBg => Get(0xe04a);
#endregion
#region New single-file-based icons
@@ -160,88 +181,6 @@ namespace osu.Game.Graphics
public static IconUsage Tortoise => get(OsuIconMapping.Tortoise);
public static IconUsage Hare => get(OsuIconMapping.Hare);
// mod icons
public static IconUsage ModNoMod => get(OsuIconMapping.ModNoMod);
/*
can be regenerated semi-automatically using osu-web's mod database via
$ jq -r '.[].Mods[].Name' mods.json | sort | uniq | \
sed 's/ //g' | \
awk '{print "public static IconUsage Mod" $0 " => get(OsuIconMapping.Mod" $0 ");"}' | pbcopy
*/
public static IconUsage ModAccuracyChallenge => get(OsuIconMapping.ModAccuracyChallenge);
public static IconUsage ModAdaptiveSpeed => get(OsuIconMapping.ModAdaptiveSpeed);
public static IconUsage ModAlternate => get(OsuIconMapping.ModAlternate);
public static IconUsage ModApproachDifferent => get(OsuIconMapping.ModApproachDifferent);
public static IconUsage ModAutopilot => get(OsuIconMapping.ModAutopilot);
public static IconUsage ModAutoplay => get(OsuIconMapping.ModAutoplay);
public static IconUsage ModBarrelRoll => get(OsuIconMapping.ModBarrelRoll);
public static IconUsage ModBlinds => get(OsuIconMapping.ModBlinds);
public static IconUsage ModBloom => get(OsuIconMapping.ModBloom);
public static IconUsage ModBubbles => get(OsuIconMapping.ModBubbles);
public static IconUsage ModCinema => get(OsuIconMapping.ModCinema);
public static IconUsage ModClassic => get(OsuIconMapping.ModClassic);
public static IconUsage ModConstantSpeed => get(OsuIconMapping.ModConstantSpeed);
public static IconUsage ModCover => get(OsuIconMapping.ModCover);
public static IconUsage ModDaycore => get(OsuIconMapping.ModDaycore);
public static IconUsage ModDeflate => get(OsuIconMapping.ModDeflate);
public static IconUsage ModDepth => get(OsuIconMapping.ModDepth);
public static IconUsage ModDifficultyAdjust => get(OsuIconMapping.ModDifficultyAdjust);
public static IconUsage ModDoubleTime => get(OsuIconMapping.ModDoubleTime);
public static IconUsage ModDualStages => get(OsuIconMapping.ModDualStages);
public static IconUsage ModEasy => get(OsuIconMapping.ModEasy);
public static IconUsage ModEightKeys => get(OsuIconMapping.ModEightKeys);
public static IconUsage ModFadeIn => get(OsuIconMapping.ModFadeIn);
public static IconUsage ModFiveKeys => get(OsuIconMapping.ModFiveKeys);
public static IconUsage ModFlashlight => get(OsuIconMapping.ModFlashlight);
public static IconUsage ModFloatingFruits => get(OsuIconMapping.ModFloatingFruits);
public static IconUsage ModFourKeys => get(OsuIconMapping.ModFourKeys);
public static IconUsage ModFreezeFrame => get(OsuIconMapping.ModFreezeFrame);
public static IconUsage ModGrow => get(OsuIconMapping.ModGrow);
public static IconUsage ModHalfTime => get(OsuIconMapping.ModHalfTime);
public static IconUsage ModHardRock => get(OsuIconMapping.ModHardRock);
public static IconUsage ModHidden => get(OsuIconMapping.ModHidden);
public static IconUsage ModHoldOff => get(OsuIconMapping.ModHoldOff);
public static IconUsage ModInvert => get(OsuIconMapping.ModInvert);
public static IconUsage ModMagnetised => get(OsuIconMapping.ModMagnetised);
public static IconUsage ModMirror => get(OsuIconMapping.ModMirror);
public static IconUsage ModMovingFast => get(OsuIconMapping.ModMovingFast);
public static IconUsage ModMuted => get(OsuIconMapping.ModMuted);
public static IconUsage ModNightcore => get(OsuIconMapping.ModNightcore);
public static IconUsage ModNineKeys => get(OsuIconMapping.ModNineKeys);
public static IconUsage ModNoFail => get(OsuIconMapping.ModNoFail);
public static IconUsage ModNoRelease => get(OsuIconMapping.ModNoRelease);
public static IconUsage ModNoScope => get(OsuIconMapping.ModNoScope);
public static IconUsage ModOneKey => get(OsuIconMapping.ModOneKey);
public static IconUsage ModPerfect => get(OsuIconMapping.ModPerfect);
public static IconUsage ModRandom => get(OsuIconMapping.ModRandom);
public static IconUsage ModRelax => get(OsuIconMapping.ModRelax);
public static IconUsage ModRepel => get(OsuIconMapping.ModRepel);
public static IconUsage ModScoreV2 => get(OsuIconMapping.ModScoreV2);
public static IconUsage ModSevenKeys => get(OsuIconMapping.ModSevenKeys);
public static IconUsage ModSimplifiedRhythm => get(OsuIconMapping.ModSimplifiedRhythm);
public static IconUsage ModSingleTap => get(OsuIconMapping.ModSingleTap);
public static IconUsage ModSixKeys => get(OsuIconMapping.ModSixKeys);
public static IconUsage ModSpinIn => get(OsuIconMapping.ModSpinIn);
public static IconUsage ModSpunOut => get(OsuIconMapping.ModSpunOut);
public static IconUsage ModStrictTracking => get(OsuIconMapping.ModStrictTracking);
public static IconUsage ModSuddenDeath => get(OsuIconMapping.ModSuddenDeath);
public static IconUsage ModSwap => get(OsuIconMapping.ModSwap);
public static IconUsage ModSynesthesia => get(OsuIconMapping.ModSynesthesia);
public static IconUsage ModTargetPractice => get(OsuIconMapping.ModTargetPractice);
public static IconUsage ModTenKeys => get(OsuIconMapping.ModTenKeys);
public static IconUsage ModThreeKeys => get(OsuIconMapping.ModThreeKeys);
public static IconUsage ModTouchDevice => get(OsuIconMapping.ModTouchDevice);
public static IconUsage ModTraceable => get(OsuIconMapping.ModTraceable);
public static IconUsage ModTransform => get(OsuIconMapping.ModTransform);
public static IconUsage ModTwoKeys => get(OsuIconMapping.ModTwoKeys);
public static IconUsage ModWiggle => get(OsuIconMapping.ModWiggle);
public static IconUsage ModWindDown => get(OsuIconMapping.ModWindDown);
public static IconUsage ModWindUp => get(OsuIconMapping.ModWindUp);
private static IconUsage get(OsuIconMapping glyph) => new IconUsage((char)glyph, FONT_NAME);
private enum OsuIconMapping
@@ -461,224 +400,6 @@ namespace osu.Game.Graphics
[Description(@"hare")]
Hare,
// mod icons
[Description(@"Mods/mod-no-mod")]
ModNoMod,
/*
rest can be regenerated semi-automatically using osu-web's mod database via
$ jq -r '.[].Mods[].Name' mods.json | sort | uniq | \
awk '{kebab = $0; gsub(" ", "-", kebab); pascal = $0; gsub(" ", "", pascal); print "[Description(@\"Mods/mod-" tolower(kebab) "\")]\nMod" pascal ",\n" }' | pbcopy
*/
[Description(@"Mods/mod-accuracy-challenge")]
ModAccuracyChallenge,
[Description(@"Mods/mod-adaptive-speed")]
ModAdaptiveSpeed,
[Description(@"Mods/mod-alternate")]
ModAlternate,
[Description(@"Mods/mod-approach-different")]
ModApproachDifferent,
[Description(@"Mods/mod-autopilot")]
ModAutopilot,
[Description(@"Mods/mod-autoplay")]
ModAutoplay,
[Description(@"Mods/mod-barrel-roll")]
ModBarrelRoll,
[Description(@"Mods/mod-blinds")]
ModBlinds,
[Description(@"Mods/mod-bloom")]
ModBloom,
[Description(@"Mods/mod-bubbles")]
ModBubbles,
[Description(@"Mods/mod-cinema")]
ModCinema,
[Description(@"Mods/mod-classic")]
ModClassic,
[Description(@"Mods/mod-constant-speed")]
ModConstantSpeed,
[Description(@"Mods/mod-cover")]
ModCover,
[Description(@"Mods/mod-daycore")]
ModDaycore,
[Description(@"Mods/mod-deflate")]
ModDeflate,
[Description(@"Mods/mod-depth")]
ModDepth,
[Description(@"Mods/mod-difficulty-adjust")]
ModDifficultyAdjust,
[Description(@"Mods/mod-double-time")]
ModDoubleTime,
[Description(@"Mods/mod-dual-stages")]
ModDualStages,
[Description(@"Mods/mod-easy")]
ModEasy,
[Description(@"Mods/mod-eight-keys")]
ModEightKeys,
[Description(@"Mods/mod-fade-in")]
ModFadeIn,
[Description(@"Mods/mod-five-keys")]
ModFiveKeys,
[Description(@"Mods/mod-flashlight")]
ModFlashlight,
[Description(@"Mods/mod-floating-fruits")]
ModFloatingFruits,
[Description(@"Mods/mod-four-keys")]
ModFourKeys,
[Description(@"Mods/mod-freeze-frame")]
ModFreezeFrame,
[Description(@"Mods/mod-grow")]
ModGrow,
[Description(@"Mods/mod-half-time")]
ModHalfTime,
[Description(@"Mods/mod-hard-rock")]
ModHardRock,
[Description(@"Mods/mod-hidden")]
ModHidden,
[Description(@"Mods/mod-hold-off")]
ModHoldOff,
[Description(@"Mods/mod-invert")]
ModInvert,
[Description(@"Mods/mod-magnetised")]
ModMagnetised,
[Description(@"Mods/mod-mirror")]
ModMirror,
[Description(@"Mods/mod-moving-fast")]
ModMovingFast,
[Description(@"Mods/mod-muted")]
ModMuted,
[Description(@"Mods/mod-nightcore")]
ModNightcore,
[Description(@"Mods/mod-nine-keys")]
ModNineKeys,
[Description(@"Mods/mod-no-fail")]
ModNoFail,
[Description(@"Mods/mod-no-release")]
ModNoRelease,
[Description(@"Mods/mod-no-scope")]
ModNoScope,
[Description(@"Mods/mod-one-key")]
ModOneKey,
[Description(@"Mods/mod-perfect")]
ModPerfect,
[Description(@"Mods/mod-random")]
ModRandom,
[Description(@"Mods/mod-relax")]
ModRelax,
[Description(@"Mods/mod-repel")]
ModRepel,
[Description(@"Mods/mod-score-v2")]
ModScoreV2,
[Description(@"Mods/mod-seven-keys")]
ModSevenKeys,
[Description(@"Mods/mod-simplified-rhythm")]
ModSimplifiedRhythm,
[Description(@"Mods/mod-single-tap")]
ModSingleTap,
[Description(@"Mods/mod-six-keys")]
ModSixKeys,
[Description(@"Mods/mod-spin-in")]
ModSpinIn,
[Description(@"Mods/mod-spun-out")]
ModSpunOut,
[Description(@"Mods/mod-strict-tracking")]
ModStrictTracking,
[Description(@"Mods/mod-sudden-death")]
ModSuddenDeath,
[Description(@"Mods/mod-swap")]
ModSwap,
[Description(@"Mods/mod-synesthesia")]
ModSynesthesia,
[Description(@"Mods/mod-target-practice")]
ModTargetPractice,
[Description(@"Mods/mod-ten-keys")]
ModTenKeys,
[Description(@"Mods/mod-three-keys")]
ModThreeKeys,
[Description(@"Mods/mod-touch-device")]
ModTouchDevice,
[Description(@"Mods/mod-traceable")]
ModTraceable,
[Description(@"Mods/mod-transform")]
ModTransform,
[Description(@"Mods/mod-two-keys")]
ModTwoKeys,
[Description(@"Mods/mod-wiggle")]
ModWiggle,
[Description(@"Mods/mod-wind-down")]
ModWindDown,
[Description(@"Mods/mod-wind-up")]
ModWindUp,
}
public class OsuIconStore : ITextureStore, ITexturedGlyphLookupStore

View File

@@ -184,11 +184,6 @@ namespace osu.Game.Localisation
/// </summary>
public static LocalisableString SelectedMods => new TranslatableString(getKey(@"selected_mods"), @"Selected Mods");
/// <summary>
/// "Use legacy song select (SelectV1)"
/// </summary>
public static LocalisableString ForceLegacySongSelect => new TranslatableString(getKey(@"force_select_v1"), @"Use legacy song select (SelectV1)");
private static string getKey(string key) => $@"{prefix}:{key}";
}
}

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

View File

@@ -298,7 +298,7 @@ namespace osu.Game
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);
EndpointConfiguration endpoints = CreateEndpoints();
@@ -322,7 +322,7 @@ namespace osu.Game
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, API, LocalConfig));
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true, config: LocalConfig));
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true));
dependencies.CacheAs<IWorkingBeatmapCache>(BeatmapManager);
dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API));

View File

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

View File

@@ -8,7 +8,6 @@ using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Game.Configuration;
using osu.Game.Localisation;
using osu.Game.Screens;
using osu.Game.Screens.Import;
@@ -23,19 +22,13 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
private ISystemFileSelector? selector;
[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)
selector.Selected += f => Task.Run(() => game.Import(f.FullName));
AddRange(new Drawable[]
{
new SettingsCheckbox
{
LabelText = "Delete archives on import",
Current = config.GetBindable<bool>(OsuSetting.DeleteImportedArchives),
ClassicDefault = true
},
new SettingsButton
{
Text = DebugSettingsStrings.ImportFiles,
@@ -51,7 +44,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
Text = DebugSettingsStrings.RunLatencyCertifier,
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.Graphics;
using osu.Framework.Localisation;
using osu.Game.Audio;
using osu.Game.Configuration;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Localisation;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Dialog;
namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
@@ -21,53 +23,108 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
[Resolved]
private SeasonalBackgroundLoader backgroundLoader { get; set; }
[Resolved]
private WelcomeMusicManager musicManager { get; set; }
[Resolved]
private DialogOverlay dialogOverlay { get; set; }
private IBindable<APIUser> user;
private SettingsEnumDropdown<BackgroundSource> backgroundSourceDropdown;
private Bindable<bool> useSeasonalBackgrounds;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, IAPIProvider api)
{
AutoSizeAxes = Axes.Y;
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
{
LabelText = UserInterfaceStrings.UseSeasonalBackgrounds,
Current = config.GetBindable<bool>(OsuSetting.UseSeasonalBackgroundsV2),
ClassicDefault = true
Current = enabledProxyBindable
};
var categoryDropdown = new SettingsDropdown<string>
{
LabelText = UserInterfaceStrings.SeasonalBackgroundsCategories,
Current = config.GetBindable<string>(OsuSetting.BackgroundCategory)
};
var refreshButton = new SettingsButton
{
Text = UserInterfaceStrings.SeasonalBackgroundsRefresh,
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);
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[]
{
new SettingsCheckbox
{
LabelText = UserInterfaceStrings.ShowMenuTips,
Current = config.GetBindable<bool>(OsuSetting.MenuTips),
Current = config.GetBindable<bool>(OsuSetting.MenuTips)
},
new SettingsCheckbox
{
@@ -81,6 +138,9 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
LabelText = UserInterfaceStrings.OsuMusicTheme,
Current = config.GetBindable<bool>(OsuSetting.MenuMusic)
},
musicModeDropdown,
musicCategoryDropdown,
refreshMusicButton,
new SettingsEnumDropdown<IntroSequence>
{
LabelText = UserInterfaceStrings.IntroSequence,
@@ -94,12 +154,6 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
backgroundToggle,
categoryDropdown,
refreshButton,
new SettingsColour
{
LabelText = @"osu! logo colour",
Current = config.GetBindable<Colour4>(OsuSetting.MenuCookieColor),
ClassicDefault = Colour4.FromHex(@"ff66ba"),
},
};
}

View File

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

View File

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

View File

@@ -6,10 +6,8 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Localisation.HUD;
using osu.Game.Overlays.Settings;
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 IconUsage? Icon => OsuIcon.ModAccuracyChallenge;
public override ModType Type => ModType.DifficultyIncrease;
public override double ScoreMultiplier => 1.0;

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