1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-15 15:12:57 +08:00

Merge branch 'master' into fix-disabled-set-crash

This commit is contained in:
Dan Balasescu 2019-03-05 17:25:13 +09:00 committed by GitHub
commit fd147dae21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
372 changed files with 3325 additions and 1072 deletions

1
.gitignore vendored
View File

@ -14,6 +14,7 @@
tools/** tools/**
build/tools/** build/tools/**
fastlane/report.xml
# Build results # Build results
bin/[Dd]ebug/ bin/[Dd]ebug/

6
Gemfile Normal file
View File

@ -0,0 +1,6 @@
source "https://rubygems.org"
gem "fastlane"
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)

173
Gemfile.lock Normal file
View File

@ -0,0 +1,173 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.0)
addressable (2.6.0)
public_suffix (>= 2.0.2, < 4.0)
atomos (0.1.3)
babosa (1.0.2)
claide (1.0.2)
colored (1.2)
colored2 (3.1.2)
commander-fastlane (4.4.6)
highline (~> 1.7.2)
declarative (0.0.10)
declarative-option (0.1.0)
digest-crc (0.4.1)
domain_name (0.5.20180417)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.1)
emoji_regex (1.0.1)
excon (0.62.0)
faraday (0.15.4)
multipart-post (>= 1.2, < 3)
faraday-cookie_jar (0.0.6)
faraday (>= 0.7.4)
http-cookie (~> 1.0.0)
faraday_middleware (0.13.1)
faraday (>= 0.7.4, < 1.0)
fastimage (2.1.5)
fastlane (2.117.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
babosa (>= 1.0.2, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored
commander-fastlane (>= 4.4.6, < 5.0.0)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 2.0)
excon (>= 0.45.0, < 1.0.0)
faraday (~> 0.9)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 0.9)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-api-client (>= 0.21.2, < 0.24.0)
google-cloud-storage (>= 1.15.0, < 2.0.0)
highline (>= 1.7.2, < 2.0.0)
json (< 3.0.0)
mini_magick (~> 4.5.1)
multi_json
multi_xml (~> 0.5)
multipart-post (~> 2.0.0)
plist (>= 3.1.0, < 4.0.0)
public_suffix (~> 2.0.0)
rubyzip (>= 1.2.2, < 2.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
slack-notifier (>= 2.0.0, < 3.0.0)
terminal-notifier (>= 1.6.2, < 2.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.6.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
fastlane-plugin-clean_testflight_testers (0.2.0)
fastlane-plugin-souyuz (0.8.1)
souyuz (>= 0.8.1)
fastlane-plugin-xamarin (0.6.3)
gh_inspector (1.1.3)
google-api-client (0.23.9)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.5, < 0.7.0)
httpclient (>= 2.8.1, < 3.0)
mime-types (~> 3.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
signet (~> 0.9)
google-cloud-core (1.3.0)
google-cloud-env (~> 1.0)
google-cloud-env (1.0.5)
faraday (~> 0.11)
google-cloud-storage (1.16.0)
digest-crc (~> 0.4)
google-api-client (~> 0.23)
google-cloud-core (~> 1.2)
googleauth (>= 0.6.2, < 0.10.0)
googleauth (0.6.7)
faraday (~> 0.12)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (~> 0.7)
highline (1.7.10)
http-cookie (1.0.3)
domain_name (~> 0.5)
httpclient (2.8.3)
json (2.2.0)
jwt (2.1.0)
memoist (0.16.0)
mime-types (3.2.2)
mime-types-data (~> 3.2015)
mime-types-data (3.2018.0812)
mini_magick (4.5.1)
mini_portile2 (2.4.0)
multi_json (1.13.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
nanaimo (0.2.6)
naturally (2.2.0)
nokogiri (1.10.1)
mini_portile2 (~> 2.4.0)
os (1.0.0)
plist (3.5.0)
public_suffix (2.0.5)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rouge (2.0.7)
rubyzip (1.2.2)
security (0.1.3)
signet (0.11.0)
addressable (~> 2.3)
faraday (~> 0.9)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.5)
CFPropertyList
naturally
slack-notifier (2.3.2)
souyuz (0.8.1)
fastlane (>= 2.29.0)
highline (~> 1.7)
nokogiri (~> 1.7)
terminal-notifier (1.8.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
tty-cursor (0.6.1)
tty-screen (0.6.5)
tty-spinner (0.9.0)
tty-cursor (~> 0.6.0)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.5)
unicode-display_width (1.4.1)
word_wrap (1.0.0)
xcodeproj (1.8.1)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.2.6)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.0)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
ruby
DEPENDENCIES
fastlane
fastlane-plugin-clean_testflight_testers
fastlane-plugin-souyuz
fastlane-plugin-xamarin
BUNDLED WITH
2.0.1

View File

@ -1,22 +1,24 @@
# osu! [![Build status](https://ci.appveyor.com/api/projects/status/u2p01nx7l6og8buh?svg=true)](https://ci.appveyor.com/project/peppy/osu) [![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) # osu!
[![Build status](https://ci.appveyor.com/api/projects/status/u2p01nx7l6og8buh?svg=true)](https://ci.appveyor.com/project/peppy/osu) [![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)
Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename "osu!lazer". Pew pew. Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename "osu!lazer". Pew pew.
# Status ## Status
This project is still heavily under development, but is in a state where users are encouraged to try it out and keep it installed alongside the stable osu! client. It will continue to evolve over the coming months and hopefully bring some new unique features to the table. This project is still heavily under development, but is in a state where users are encouraged to try it out and keep it installed alongside the stable osu! client. It will continue to evolve over the coming months and hopefully bring some new unique features to the table.
We are accepting bug reports (please report with as much detail as possible). Feature requests are welcome as long as you read and understand the contribution guidelines listed below. We are accepting bug reports (please report with as much detail as possible). Feature requests are welcome as long as you read and understand the contribution guidelines listed below.
# Requirements ## Requirements
- A desktop platform with the [.NET Core SDK 2.2](https://www.microsoft.com/net/learn/get-started) or higher installed. - A desktop platform with the [.NET Core SDK 2.2](https://www.microsoft.com/net/learn/get-started) or higher installed.
- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2017+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/). - When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2017+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
- Note that there are **[additional requirements for Windows 7 and Windows 8.1](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** which you may need to manually install if your operating system is not up-to-date. - Note that there are **[additional requirements for Windows 7 and Windows 8.1](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** which you may need to manually install if your operating system is not up-to-date.
# Running osu! ## Running osu!
## Releases ### Releases
If you are not interested in developing the game, please head over to the [releases](https://github.com/ppy/osu/releases) to download a precompiled build with automatic updating enabled. If you are not interested in developing the game, please head over to the [releases](https://github.com/ppy/osu/releases) to download a precompiled build with automatic updating enabled.
@ -26,7 +28,7 @@ If you are not interested in developing the game, please head over to the [relea
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below. If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
## Downloading the source code ### Downloading the source code
Clone the repository **including submodules**: Clone the repository **including submodules**:
@ -41,7 +43,7 @@ To update the source code to the latest commit, run the following command inside
git pull git pull
``` ```
## Building ### Building
Build 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 `VisualTests` project/configuration. More information on this provided below. Build 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 `VisualTests` project/configuration. More information on this provided below.
@ -57,7 +59,7 @@ If you are not interested in debugging osu!, you can add `-c Release` to gain pe
If the build fails, try to restore nuget packages with `dotnet restore`. If the build fails, try to restore nuget packages with `dotnet restore`.
### A note for Linux users #### A note for Linux users
On Linux, the environment variable `LD_LIBRARY_PATH` must point to the build directory, located at `osu.Desktop/bin/Debug/$NETCORE_VERSION`. On Linux, the environment variable `LD_LIBRARY_PATH` must point to the build directory, located at `osu.Desktop/bin/Debug/$NETCORE_VERSION`.
@ -69,15 +71,15 @@ For example, you can run osu! with the following command:
LD_LIBRARY_PATH="$(pwd)/osu.Desktop/bin/Debug/netcoreapp2.2" dotnet run --project osu.Desktop LD_LIBRARY_PATH="$(pwd)/osu.Desktop/bin/Debug/netcoreapp2.2" dotnet run --project osu.Desktop
``` ```
## Testing with resource/framework modifications ### 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 achieved by running some commands as documented on the [osu-resources](https://github.com/ppy/osu-resources/wiki/Testing-local-resources-checkout-with-other-projects) and [osu-framework](https://github.com/ppy/osu-framework/wiki/Testing-local-framework-checkout-with-other-projects) wiki pages. 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 achieved by running some commands as documented on the [osu-resources](https://github.com/ppy/osu-resources/wiki/Testing-local-resources-checkout-with-other-projects) and [osu-framework](https://github.com/ppy/osu-framework/wiki/Testing-local-framework-checkout-with-other-projects) wiki pages.
## Code analysis ### Code analysis
Code analysis can be run with `powershell ./build.ps1` or `build.sh`. This is currently only supported under windows due to [resharper cli shortcomings](https://youtrack.jetbrains.com/issue/RSRP-410004). Alternatively, you can install resharper or use rider to get inline support in your IDE of choice. Code analysis can be run with `powershell ./build.ps1` or `build.sh`. This is currently only supported under windows due to [resharper cli shortcomings](https://youtrack.jetbrains.com/issue/RSRP-410004). Alternatively, you can install resharper or use rider to get inline support in your IDE of choice.
# Contributing ## Contributing
We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention on having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time, to ensure no effort is wasted. We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention on having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time, to ensure no effort is wasted.
@ -87,7 +89,7 @@ Contributions can be made via pull requests to this repository. We hope to credi
Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured; with any libraries we are using; with any processes involved with contributing, *please* bring it up. I welcome all feedback so we can make contributing to this project as pain-free as possible. Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured; with any libraries we are using; with any processes involved with contributing, *please* bring it up. I welcome all feedback so we can make contributing to this project as pain-free as possible.
# Licence ## Licence
The osu! client 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. The osu! client 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.

2
fastlane/Appfile Normal file
View File

@ -0,0 +1,2 @@
app_identifier("sh.ppy.osulazer") # The bundle identifier of your app
apple_id("apple-dev@ppy.sh") # Your Apple email address

65
fastlane/Fastfile Normal file
View File

@ -0,0 +1,65 @@
update_fastlane
default_platform(:ios)
platform :ios do
lane :testflight_prune_dry do
clean_testflight_testers(days_of_inactivity:45, dry_run: true)
end
# Specify a custom number for what's "inactive"
lane :testflight_prune do
clean_testflight_testers(days_of_inactivity: 45) # 120 days, so about 4 months
end
lane :update_version do |options|
options[:plist_path] = '../osu.iOS/Info.plist'
app_version(options)
end
desc 'Deploy to testflight'
lane :beta do |options|
update_version(options)
provision(
type: 'appstore'
)
build(
build_configuration: 'Release',
build_platform: 'iPhone'
)
client = HTTPClient.new
changelog = client.get_content 'https://gist.githubusercontent.com/peppy/ab89c29dcc0dce95f39eb218e8fad197/raw'
changelog.gsub!('$BUILD_ID', options[:build])
pilot(
wait_processing_interval: 900,
changelog: changelog,
ipa: './osu.iOS/bin/iPhone/Release/osu.iOS.ipa'
)
end
desc 'Compile the project'
lane :build do
nuget_restore(
project_path: 'osu.iOS.sln'
)
souyuz(
platform: "ios",
build_target: "osu_iOS",
plist_path: "../osu.iOS/Info.plist"
)
end
desc 'Install provisioning profiles using match'
lane :provision do |options|
if Helper.is_ci?
options[:readonly] = true
end
match(options)
end
end

1
fastlane/Matchfile Normal file
View File

@ -0,0 +1 @@
git_url('https://github.com/peppy/apple-certificates')

7
fastlane/Pluginfile Normal file
View File

@ -0,0 +1,7 @@
# Autogenerated by fastlane
#
# Ensure this file is checked in to source control!
gem 'fastlane-plugin-clean_testflight_testers'
gem 'fastlane-plugin-souyuz'
gem 'fastlane-plugin-xamarin'

54
fastlane/README.md Normal file
View File

@ -0,0 +1,54 @@
fastlane documentation
================
# Installation
Make sure you have the latest version of the Xcode command line tools installed:
```
xcode-select --install
```
Install _fastlane_ using
```
[sudo] gem install fastlane -NV
```
or alternatively using `brew cask install fastlane`
# Available Actions
## iOS
### ios testflight_prune_dry
```
fastlane ios testflight_prune_dry
```
### ios testflight_prune
```
fastlane ios testflight_prune
```
### ios update_version
```
fastlane ios update_version
```
### ios beta
```
fastlane ios beta
```
Deploy to testflight
### ios build
```
fastlane ios build
```
Compile the project
### ios provision
```
fastlane ios provision
```
Install provisioning profiles using match
----
This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run.
More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).

View File

@ -83,8 +83,7 @@ namespace osu.Desktop
public override void SetHost(GameHost host) public override void SetHost(GameHost host)
{ {
base.SetHost(host); base.SetHost(host);
var desktopWindow = host.Window as DesktopGameWindow; if (host.Window is DesktopGameWindow desktopWindow)
if (desktopWindow != null)
{ {
desktopWindow.CursorState |= CursorState.Hidden; desktopWindow.CursorState |= CursorState.Hidden;

View File

@ -34,13 +34,16 @@ namespace osu.Game.Rulesets.Catch.Tests
case JuiceStream stream: case JuiceStream stream:
foreach (var nested in stream.NestedHitObjects) foreach (var nested in stream.NestedHitObjects)
yield return new ConvertValue((CatchHitObject)nested); yield return new ConvertValue((CatchHitObject)nested);
break; break;
case BananaShower shower: case BananaShower shower:
foreach (var nested in shower.NestedHitObjects) foreach (var nested in shower.NestedHitObjects)
yield return new ConvertValue((CatchHitObject)nested); yield return new ConvertValue((CatchHitObject)nested);
break; break;
default: default:
yield return new ConvertValue((CatchHitObject)hitObject); yield return new ConvertValue((CatchHitObject)hitObject);
break; break;
} }
} }

View File

@ -8,7 +8,8 @@ namespace osu.Game.Rulesets.Catch.Tests
[TestFixture] [TestFixture]
public class TestCaseCatchPlayer : Game.Tests.Visual.TestCasePlayer public class TestCaseCatchPlayer : Game.Tests.Visual.TestCasePlayer
{ {
public TestCaseCatchPlayer() : base(new CatchRuleset()) public TestCaseCatchPlayer()
: base(new CatchRuleset())
{ {
} }
} }

View File

@ -1,9 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Screens.Play;
namespace osu.Game.Rulesets.Catch.Tests namespace osu.Game.Rulesets.Catch.Tests
{ {
@ -17,13 +19,30 @@ namespace osu.Game.Rulesets.Catch.Tests
protected override IBeatmap CreateBeatmap(Ruleset ruleset) protected override IBeatmap CreateBeatmap(Ruleset ruleset)
{ {
var beatmap = new Beatmap { BeatmapInfo = { Ruleset = ruleset.RulesetInfo } }; var beatmap = new Beatmap
{
BeatmapInfo =
{
Ruleset = ruleset.RulesetInfo,
BaseDifficulty = new BeatmapDifficulty { CircleSize = 3.6f }
}
};
// Should produce a hperdash
beatmap.HitObjects.Add(new Fruit { StartTime = 816, X = 308 / 512f, NewCombo = true });
beatmap.HitObjects.Add(new Fruit { StartTime = 1008, X = 56 / 512f, });
for (int i = 0; i < 512; i++) for (int i = 0; i < 512; i++)
if (i % 5 < 3) if (i % 5 < 3)
beatmap.HitObjects.Add(new Fruit { X = i % 10 < 5 ? 0.02f : 0.98f, StartTime = i * 100, NewCombo = i % 8 == 0 }); beatmap.HitObjects.Add(new Fruit { X = i % 10 < 5 ? 0.02f : 0.98f, StartTime = 2000 + i * 100, NewCombo = i % 8 == 0 });
return beatmap; return beatmap;
} }
protected override void AddCheckSteps(Func<Player> player)
{
base.AddCheckSteps(player);
AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash);
}
} }
} }

View File

@ -56,6 +56,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
rng.Next(); // osu!stable retrieved a random banana rotation rng.Next(); // osu!stable retrieved a random banana rotation
rng.Next(); // osu!stable retrieved a random banana colour rng.Next(); // osu!stable retrieved a random banana colour
} }
break; break;
case JuiceStream juiceStream: case JuiceStream juiceStream:
foreach (var nested in juiceStream.NestedHitObjects) foreach (var nested in juiceStream.NestedHitObjects)
@ -67,6 +68,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
rng.Next(); // osu!stable retrieved a random droplet rotation rng.Next(); // osu!stable retrieved a random droplet rotation
hitObject.X = MathHelper.Clamp(hitObject.X, 0, 1); hitObject.X = MathHelper.Clamp(hitObject.X, 0, 1);
} }
break; break;
} }
} }
@ -98,7 +100,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
CatchHitObject nextObject = objectWithDroplets[i + 1]; CatchHitObject nextObject = objectWithDroplets[i + 1];
int thisDirection = nextObject.X > currentObject.X ? 1 : -1; int thisDirection = nextObject.X > currentObject.X ? 1 : -1;
double timeToNext = nextObject.StartTime - currentObject.StartTime; double timeToNext = nextObject.StartTime - currentObject.StartTime - 1000f / 60f / 4; // 1/4th of a frame of grace time, taken from osu-stable
double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth); double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth);
float distanceToHyper = (float)(timeToNext * CatcherArea.Catcher.BASE_SPEED - distanceToNext); float distanceToHyper = (float)(timeToNext * CatcherArea.Catcher.BASE_SPEED - distanceToNext);
if (distanceToHyper < 0) if (distanceToHyper < 0)

View File

@ -19,8 +19,10 @@ namespace osu.Game.Rulesets.Catch
{ {
[Description("Move left")] [Description("Move left")]
MoveLeft, MoveLeft,
[Description("Move right")] [Description("Move right")]
MoveRight, MoveRight,
[Description("Engage dash")] [Description("Engage dash")]
Dash, Dash,
} }

View File

@ -68,14 +68,17 @@ namespace osu.Game.Rulesets.Catch.Difficulty
// We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations. // We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations.
case Fruit fruit: case Fruit fruit:
yield return new CatchDifficultyHitObject(fruit, lastObject, clockRate, halfCatchWidth); yield return new CatchDifficultyHitObject(fruit, lastObject, clockRate, halfCatchWidth);
lastObject = hitObject; lastObject = hitObject;
break; break;
case JuiceStream _: case JuiceStream _:
foreach (var nested in hitObject.NestedHitObjects.OfType<CatchHitObject>().Where(o => !(o is TinyDroplet))) foreach (var nested in hitObject.NestedHitObjects.OfType<CatchHitObject>().Where(o => !(o is TinyDroplet)))
{ {
yield return new CatchDifficultyHitObject(nested, lastObject, clockRate, halfCatchWidth); yield return new CatchDifficultyHitObject(nested, lastObject, clockRate, halfCatchWidth);
lastObject = nested; lastObject = nested;
} }
break; break;
} }
} }

View File

@ -33,11 +33,11 @@ namespace osu.Game.Rulesets.Catch.MathUtils
/// <returns>The random value.</returns> /// <returns>The random value.</returns>
public uint NextUInt() public uint NextUInt()
{ {
uint t = _x ^ _x << 11; uint t = _x ^ (_x << 11);
_x = _y; _x = _y;
_y = _z; _y = _z;
_z = _w; _z = _w;
return _w = _w ^ _w >> 19 ^ t ^ t >> 8; return _w = _w ^ (_w >> 19) ^ t ^ (t >> 8);
} }
/// <summary> /// <summary>

View File

@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
public override Color4 AccentColour public override Color4 AccentColour
{ {
get { return base.AccentColour; } get => base.AccentColour;
set set
{ {
base.AccentColour = value; base.AccentColour = value;

View File

@ -23,9 +23,10 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces
} }
private Color4 accentColour; private Color4 accentColour;
public Color4 AccentColour public Color4 AccentColour
{ {
get { return accentColour; } get => accentColour;
set set
{ {
accentColour = value; accentColour = value;

View File

@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.UI
public Container ExplodingFruitTarget public Container ExplodingFruitTarget
{ {
set { MovableCatcher.ExplodingFruitTarget = value; } set => MovableCatcher.ExplodingFruitTarget = value;
} }
public CatcherArea(BeatmapDifficulty difficulty = null) public CatcherArea(BeatmapDifficulty difficulty = null)
@ -158,7 +158,7 @@ namespace osu.Game.Rulesets.Catch.UI
protected bool Dashing protected bool Dashing
{ {
get { return dashing; } get => dashing;
set set
{ {
if (value == dashing) return; if (value == dashing) return;
@ -176,7 +176,7 @@ namespace osu.Game.Rulesets.Catch.UI
/// </summary> /// </summary>
protected bool Trail protected bool Trail
{ {
get { return trail; } get => trail;
set set
{ {
if (value == trail) return; if (value == trail) return;

View File

@ -25,8 +25,8 @@ namespace osu.Game.Rulesets.Mania.Tests
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(RulesetConfigCache configCache) private void load(RulesetConfigCache configCache)
{ {
var config = (ManiaConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
config.BindWith(ManiaSetting.ScrollDirection, direction); config.BindWith(ManiaRulesetSetting.ScrollDirection, direction);
} }
} }
} }

View File

@ -27,6 +27,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) }; protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
public int TargetColumns; public int TargetColumns;
public bool Dual;
public readonly bool IsForCurrentRuleset; public readonly bool IsForCurrentRuleset;
// Internal for testing purposes // Internal for testing purposes
@ -45,7 +46,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
var roundedOverallDifficulty = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); var roundedOverallDifficulty = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
if (IsForCurrentRuleset) if (IsForCurrentRuleset)
{
TargetColumns = (int)Math.Max(1, roundedCircleSize); TargetColumns = (int)Math.Max(1, roundedCircleSize);
if (TargetColumns >= 10)
{
TargetColumns = TargetColumns / 2;
Dual = true;
}
}
else else
{ {
float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasEndTime) / beatmap.HitObjects.Count; float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasEndTime) / beatmap.HitObjects.Count;
@ -70,14 +78,22 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
return base.ConvertBeatmap(original); return base.ConvertBeatmap(original);
} }
protected override Beatmap<ManiaHitObject> CreateBeatmap() => beatmap = new ManiaBeatmap(new StageDefinition { Columns = TargetColumns }); protected override Beatmap<ManiaHitObject> CreateBeatmap()
{
beatmap = new ManiaBeatmap(new StageDefinition { Columns = TargetColumns });
if (Dual)
beatmap.Stages.Add(new StageDefinition { Columns = TargetColumns });
return beatmap;
}
protected override IEnumerable<ManiaHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap) protected override IEnumerable<ManiaHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap)
{ {
var maniaOriginal = original as ManiaHitObject; if (original is ManiaHitObject maniaOriginal)
if (maniaOriginal != null)
{ {
yield return maniaOriginal; yield return maniaOriginal;
yield break; yield break;
} }
@ -92,6 +108,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
private readonly List<double> prevNoteTimes = new List<double>(max_notes_for_density); private readonly List<double> prevNoteTimes = new List<double>(max_notes_for_density);
private double density = int.MaxValue; private double density = int.MaxValue;
private void computeDensity(double newNoteTime) private void computeDensity(double newNoteTime)
{ {
if (prevNoteTimes.Count == max_notes_for_density) if (prevNoteTimes.Count == max_notes_for_density)
@ -104,6 +121,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
private double lastTime; private double lastTime;
private Vector2 lastPosition; private Vector2 lastPosition;
private PatternType lastStair = PatternType.Stair; private PatternType lastStair = PatternType.Stair;
private void recordNote(double time, Vector2 position) private void recordNote(double time, Vector2 position)
{ {
lastTime = time; lastTime = time;

View File

@ -65,6 +65,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (originalPattern.HitObjects.Count() == 1) if (originalPattern.HitObjects.Count() == 1)
{ {
yield return originalPattern; yield return originalPattern;
yield break; yield break;
} }
@ -135,6 +136,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlag(PatternType.LowProbability))
return generateNRandomNotes(HitObject.StartTime, 0.78, 0.3, 0); return generateNRandomNotes(HitObject.StartTime, 0.78, 0.3, 0);
return generateNRandomNotes(HitObject.StartTime, 0.85, 0.36, 0.03); return generateNRandomNotes(HitObject.StartTime, 0.85, 0.36, 0.03);
} }
@ -142,6 +144,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlag(PatternType.LowProbability))
return generateNRandomNotes(HitObject.StartTime, 0.43, 0.08, 0); return generateNRandomNotes(HitObject.StartTime, 0.43, 0.08, 0);
return generateNRandomNotes(HitObject.StartTime, 0.56, 0.18, 0); return generateNRandomNotes(HitObject.StartTime, 0.56, 0.18, 0);
} }
@ -149,11 +152,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlag(PatternType.LowProbability))
return generateNRandomNotes(HitObject.StartTime, 0.3, 0, 0); return generateNRandomNotes(HitObject.StartTime, 0.3, 0, 0);
return generateNRandomNotes(HitObject.StartTime, 0.37, 0.08, 0); return generateNRandomNotes(HitObject.StartTime, 0.37, 0.08, 0);
} }
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlag(PatternType.LowProbability))
return generateNRandomNotes(HitObject.StartTime, 0.17, 0, 0); return generateNRandomNotes(HitObject.StartTime, 0.17, 0, 0);
return generateNRandomNotes(HitObject.StartTime, 0.27, 0, 0); return generateNRandomNotes(HitObject.StartTime, 0.27, 0, 0);
} }

View File

@ -172,6 +172,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
return pattern = generateRandomPatternWithMirrored(0.12, 0.38, 0.12); return pattern = generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
if (ConversionDifficulty > 4) if (ConversionDifficulty > 4)
return pattern = generateRandomPatternWithMirrored(0.12, 0.17, 0); return pattern = generateRandomPatternWithMirrored(0.12, 0.17, 0);
return pattern = generateRandomPatternWithMirrored(0.12, 0, 0); return pattern = generateRandomPatternWithMirrored(0.12, 0, 0);
} }
@ -179,6 +180,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlag(PatternType.LowProbability))
return pattern = generateRandomPattern(0.78, 0.42, 0, 0); return pattern = generateRandomPattern(0.78, 0.42, 0, 0);
return pattern = generateRandomPattern(1, 0.62, 0, 0); return pattern = generateRandomPattern(1, 0.62, 0, 0);
} }
@ -186,6 +188,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlag(PatternType.LowProbability))
return pattern = generateRandomPattern(0.35, 0.08, 0, 0); return pattern = generateRandomPattern(0.35, 0.08, 0, 0);
return pattern = generateRandomPattern(0.52, 0.15, 0, 0); return pattern = generateRandomPattern(0.52, 0.15, 0, 0);
} }
@ -193,6 +196,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
if (convertType.HasFlag(PatternType.LowProbability)) if (convertType.HasFlag(PatternType.LowProbability))
return pattern = generateRandomPattern(0.18, 0, 0, 0); return pattern = generateRandomPattern(0.18, 0, 0, 0);
return pattern = generateRandomPattern(0.45, 0, 0, 0); return pattern = generateRandomPattern(0.45, 0, 0, 0);
} }
@ -250,6 +254,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
} }
else else
last = GetRandomColumn(); last = GetRandomColumn();
return last; return last;
} }
} }

View File

@ -87,6 +87,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
return 4; return 4;
if (val >= 1 - p3) if (val >= 1 - p3)
return 3; return 3;
return val >= 1 - p2 ? 2 : 1; return val >= 1 - p2 ? 2 : 1;
} }

View File

@ -12,51 +12,63 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
internal enum PatternType internal enum PatternType
{ {
None = 0, None = 0,
/// <summary> /// <summary>
/// Keep the same as last row. /// Keep the same as last row.
/// </summary> /// </summary>
ForceStack = 1 << 0, ForceStack = 1 << 0,
/// <summary> /// <summary>
/// Keep different from last row. /// Keep different from last row.
/// </summary> /// </summary>
ForceNotStack = 1 << 1, ForceNotStack = 1 << 1,
/// <summary> /// <summary>
/// Keep as single note at its original position. /// Keep as single note at its original position.
/// </summary> /// </summary>
KeepSingle = 1 << 2, KeepSingle = 1 << 2,
/// <summary> /// <summary>
/// Use a lower random value. /// Use a lower random value.
/// </summary> /// </summary>
LowProbability = 1 << 3, LowProbability = 1 << 3,
/// <summary> /// <summary>
/// Reserved. /// Reserved.
/// </summary> /// </summary>
Alternate = 1 << 4, Alternate = 1 << 4,
/// <summary> /// <summary>
/// Ignore the repeat count. /// Ignore the repeat count.
/// </summary> /// </summary>
ForceSigSlider = 1 << 5, ForceSigSlider = 1 << 5,
/// <summary> /// <summary>
/// Convert slider to circle. /// Convert slider to circle.
/// </summary> /// </summary>
ForceNotSlider = 1 << 6, ForceNotSlider = 1 << 6,
/// <summary> /// <summary>
/// Notes gathered together. /// Notes gathered together.
/// </summary> /// </summary>
Gathered = 1 << 7, Gathered = 1 << 7,
Mirror = 1 << 8, Mirror = 1 << 8,
/// <summary> /// <summary>
/// Change 0 -> 6. /// Change 0 -> 6.
/// </summary> /// </summary>
Reverse = 1 << 9, Reverse = 1 << 9,
/// <summary> /// <summary>
/// 1 -> 5 -> 1 -> 5 like reverse. /// 1 -> 5 -> 1 -> 5 like reverse.
/// </summary> /// </summary>
Cycle = 1 << 10, Cycle = 1 << 10,
/// <summary> /// <summary>
/// Next note will be at column + 1. /// Next note will be at column + 1.
/// </summary> /// </summary>
Stair = 1 << 11, Stair = 1 << 11,
/// <summary> /// <summary>
/// Next note will be at column - 1. /// Next note will be at column - 1.
/// </summary> /// </summary>

View File

@ -8,9 +8,9 @@ using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Configuration namespace osu.Game.Rulesets.Mania.Configuration
{ {
public class ManiaConfigManager : RulesetConfigManager<ManiaSetting> public class ManiaRulesetConfigManager : RulesetConfigManager<ManiaRulesetSetting>
{ {
public ManiaConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) public ManiaRulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null)
: base(settings, ruleset, variant) : base(settings, ruleset, variant)
{ {
} }
@ -19,17 +19,17 @@ namespace osu.Game.Rulesets.Mania.Configuration
{ {
base.InitialiseDefaults(); base.InitialiseDefaults();
Set(ManiaSetting.ScrollTime, 2250.0, 50.0, 10000.0, 50.0); Set(ManiaRulesetSetting.ScrollTime, 2250.0, 50.0, 10000.0, 50.0);
Set(ManiaSetting.ScrollDirection, ManiaScrollingDirection.Down); Set(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down);
} }
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
{ {
new TrackedSetting<double>(ManiaSetting.ScrollTime, v => new SettingDescription(v, "Scroll Time", $"{v}ms")) new TrackedSetting<double>(ManiaRulesetSetting.ScrollTime, v => new SettingDescription(v, "Scroll Time", $"{v}ms"))
}; };
} }
public enum ManiaSetting public enum ManiaRulesetSetting
{ {
ScrollTime, ScrollTime,
ScrollDirection ScrollDirection

View File

@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Mania
{ {
[Description("Special 1")] [Description("Special 1")]
Special1 = 1, Special1 = 1,
[Description("Special 2")] [Description("Special 2")]
Special2, Special2,
@ -26,38 +27,55 @@ namespace osu.Game.Rulesets.Mania
// above at a later time, without breaking replays/configs. // above at a later time, without breaking replays/configs.
[Description("Key 1")] [Description("Key 1")]
Key1 = 10, Key1 = 10,
[Description("Key 2")] [Description("Key 2")]
Key2, Key2,
[Description("Key 3")] [Description("Key 3")]
Key3, Key3,
[Description("Key 4")] [Description("Key 4")]
Key4, Key4,
[Description("Key 5")] [Description("Key 5")]
Key5, Key5,
[Description("Key 6")] [Description("Key 6")]
Key6, Key6,
[Description("Key 7")] [Description("Key 7")]
Key7, Key7,
[Description("Key 8")] [Description("Key 8")]
Key8, Key8,
[Description("Key 9")] [Description("Key 9")]
Key9, Key9,
[Description("Key 10")] [Description("Key 10")]
Key10, Key10,
[Description("Key 11")] [Description("Key 11")]
Key11, Key11,
[Description("Key 12")] [Description("Key 12")]
Key12, Key12,
[Description("Key 13")] [Description("Key 13")]
Key13, Key13,
[Description("Key 14")] [Description("Key 14")]
Key14, Key14,
[Description("Key 15")] [Description("Key 15")]
Key15, Key15,
[Description("Key 16")] [Description("Key 16")]
Key16, Key16,
[Description("Key 17")] [Description("Key 17")]
Key17, Key17,
[Description("Key 18")] [Description("Key 18")]
Key18, Key18,
} }

View File

@ -162,7 +162,7 @@ namespace osu.Game.Rulesets.Mania
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame(); public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame();
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaConfigManager(settings, RulesetInfo); public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaRulesetConfigManager(settings, RulesetInfo);
public override RulesetSettingsSubsection CreateSettings() => new ManiaSettingsSubsection(this); public override RulesetSettingsSubsection CreateSettings() => new ManiaSettingsSubsection(this);
@ -349,6 +349,7 @@ namespace osu.Game.Rulesets.Mania
/// Number of columns in this stage lies at (item - Single). /// Number of columns in this stage lies at (item - Single).
/// </summary> /// </summary>
Single = 0, Single = 0,
/// <summary> /// <summary>
/// Columns are grouped into two stages. /// Columns are grouped into two stages.
/// Overall number of columns lies at (item - Dual), further computation is required for /// Overall number of columns lies at (item - Dual), further computation is required for

View File

@ -22,19 +22,19 @@ namespace osu.Game.Rulesets.Mania
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
var config = (ManiaConfigManager)Config; var config = (ManiaRulesetConfigManager)Config;
Children = new Drawable[] Children = new Drawable[]
{ {
new SettingsEnumDropdown<ManiaScrollingDirection> new SettingsEnumDropdown<ManiaScrollingDirection>
{ {
LabelText = "Scrolling direction", LabelText = "Scrolling direction",
Bindable = config.GetBindable<ManiaScrollingDirection>(ManiaSetting.ScrollDirection) Bindable = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection)
}, },
new SettingsSlider<double, TimeSlider> new SettingsSlider<double, TimeSlider>
{ {
LabelText = "Scroll speed", LabelText = "Scroll speed",
Bindable = config.GetBindable<double>(ManiaSetting.ScrollTime) Bindable = config.GetBindable<double>(ManiaRulesetSetting.ScrollTime)
}, },
}; };
} }

View File

@ -37,11 +37,11 @@ namespace osu.Game.Rulesets.Mania.MathUtils
/// <returns>The random value.</returns> /// <returns>The random value.</returns>
public uint NextUInt() public uint NextUInt()
{ {
uint t = X ^ X << 11; uint t = X ^ (X << 11);
X = Y; X = Y;
Y = Z; Y = Z;
Z = W; Z = W;
return W = W ^ W >> 19 ^ t ^ t >> 8; return W = W ^ (W >> 19) ^ t ^ (t >> 8);
} }
/// <summary> /// <summary>

View File

@ -1,15 +1,13 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
public class ManiaModDualStages : Mod, IPlayfieldTypeMod, IApplicableToBeatmapConverter, IApplicableToBeatmap<ManiaHitObject> public class ManiaModDualStages : Mod, IPlayfieldTypeMod, IApplicableToBeatmapConverter
{ {
public override string Name => "Dual Stages"; public override string Name => "Dual Stages";
public override string Acronym => "DS"; public override string Acronym => "DS";
@ -29,24 +27,7 @@ namespace osu.Game.Rulesets.Mania.Mods
if (isForCurrentRuleset) if (isForCurrentRuleset)
return; return;
mbc.TargetColumns *= 2; mbc.Dual = true;
}
public void ApplyToBeatmap(Beatmap<ManiaHitObject> beatmap)
{
if (isForCurrentRuleset)
return;
var maniaBeatmap = (ManiaBeatmap)beatmap;
var newDefinitions = new List<StageDefinition>();
foreach (var existing in maniaBeatmap.Stages)
{
newDefinitions.Add(new StageDefinition { Columns = existing.Columns / 2 });
newDefinitions.Add(new StageDefinition { Columns = existing.Columns / 2 });
}
maniaBeatmap.Stages = newDefinitions;
} }
public PlayfieldType PlayfieldType => PlayfieldType.Dual; public PlayfieldType PlayfieldType => PlayfieldType.Dual;

View File

@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public override Color4 AccentColour public override Color4 AccentColour
{ {
get { return base.AccentColour; } get => base.AccentColour;
set set
{ {
base.AccentColour = value; base.AccentColour = value;

View File

@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public override Color4 AccentColour public override Color4 AccentColour
{ {
get { return base.AccentColour; } get => base.AccentColour;
set set
{ {
base.AccentColour = value; base.AccentColour = value;

View File

@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public override Color4 AccentColour public override Color4 AccentColour
{ {
get { return base.AccentColour; } get => base.AccentColour;
set set
{ {
base.AccentColour = value; base.AccentColour = value;

View File

@ -77,11 +77,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
public Color4 AccentColour public Color4 AccentColour
{ {
get { return accentColour; } get => accentColour;
set set
{ {
if (accentColour == value) if (accentColour == value)
return; return;
accentColour = value; accentColour = value;
updateAccentColour(); updateAccentColour();
@ -90,7 +91,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
public bool Hitting public bool Hitting
{ {
get { return hitting; } get => hitting;
set set
{ {
hitting = value; hitting = value;

View File

@ -35,13 +35,15 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
} }
private Color4 accentColour; private Color4 accentColour;
public Color4 AccentColour public Color4 AccentColour
{ {
get { return accentColour; } get => accentColour;
set set
{ {
if (accentColour == value) if (accentColour == value)
return; return;
accentColour = value; accentColour = value;
updateGlow(); updateGlow();

View File

@ -78,8 +78,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
public Color4 AccentColour public Color4 AccentColour
{ {
get { return Colour; } get => Colour;
set { Colour = value; } set => Colour = value;
} }
} }
} }

View File

@ -56,13 +56,15 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
} }
private Color4 accentColour; private Color4 accentColour;
public Color4 AccentColour public Color4 AccentColour
{ {
get { return accentColour; } get => accentColour;
set set
{ {
if (accentColour == value) if (accentColour == value)
return; return;
accentColour = value; accentColour = value;
colouredBox.Colour = AccentColour.Lighten(0.9f); colouredBox.Colour = AccentColour.Lighten(0.9f);

View File

@ -17,9 +17,10 @@ namespace osu.Game.Rulesets.Mania.Objects
public double EndTime => StartTime + Duration; public double EndTime => StartTime + Duration;
private double duration; private double duration;
public double Duration public double Duration
{ {
get { return duration; } get => duration;
set set
{ {
duration = value; duration = value;
@ -29,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Objects
public override double StartTime public override double StartTime
{ {
get { return base.StartTime; } get => base.StartTime;
set set
{ {
base.StartTime = value; base.StartTime = value;
@ -40,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.Objects
public override int Column public override int Column
{ {
get { return base.Column; } get => base.Column;
set set
{ {
base.Column = value; base.Column = value;

View File

@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Objects
private static readonly IReadOnlyDictionary<HitResult, (double od0, double od5, double od10)> base_ranges = new Dictionary<HitResult, (double, double, double)> private static readonly IReadOnlyDictionary<HitResult, (double od0, double od5, double od10)> base_ranges = new Dictionary<HitResult, (double, double, double)>
{ {
{ HitResult.Perfect, (44.8, 38.8, 27.8) }, { HitResult.Perfect, (44.8, 38.8, 27.8) },
{ HitResult.Great, (128, 98, 68 ) }, { HitResult.Great, (128, 98, 68) },
{ HitResult.Good, (194, 164, 134) }, { HitResult.Good, (194, 164, 134) },
{ HitResult.Ok, (254, 224, 194) }, { HitResult.Ok, (254, 224, 194) },
{ HitResult.Meh, (302, 272, 242) }, { HitResult.Meh, (302, 272, 242) },

View File

@ -97,13 +97,15 @@ namespace osu.Game.Rulesets.Mania.UI
public override Axes RelativeSizeAxes => Axes.Y; public override Axes RelativeSizeAxes => Axes.Y;
private bool isSpecial; private bool isSpecial;
public bool IsSpecial public bool IsSpecial
{ {
get { return isSpecial; } get => isSpecial;
set set
{ {
if (isSpecial == value) if (isSpecial == value)
return; return;
isSpecial = value; isSpecial = value;
Width = isSpecial ? special_column_width : column_width; Width = isSpecial ? special_column_width : column_width;
@ -111,13 +113,15 @@ namespace osu.Game.Rulesets.Mania.UI
} }
private Color4 accentColour; private Color4 accentColour;
public Color4 AccentColour public Color4 AccentColour
{ {
get { return accentColour; } get => accentColour;
set set
{ {
if (accentColour == value) if (accentColour == value)
return; return;
accentColour = value; accentColour = value;
background.AccentColour = value; background.AccentColour = value;

View File

@ -70,6 +70,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
{ {
if (accentColour == value) if (accentColour == value)
return; return;
accentColour = value; accentColour = value;
updateColours(); updateColours();

View File

@ -73,6 +73,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
{ {
if (accentColour == value) if (accentColour == value)
return; return;
accentColour = value; accentColour = value;
updateColours(); updateColours();

View File

@ -87,6 +87,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
{ {
if (accentColour == value) if (accentColour == value)
return; return;
accentColour = value; accentColour = value;
updateColours(); updateColours();

View File

@ -15,7 +15,6 @@ using osu.Game.Input.Handlers;
using osu.Game.Replays; using osu.Game.Replays;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Replays;
@ -35,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.UI
public IEnumerable<BarLine> BarLines; public IEnumerable<BarLine> BarLines;
protected new ManiaConfigManager Config => (ManiaConfigManager)base.Config; protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config;
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>(); private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>();
@ -75,10 +74,10 @@ namespace osu.Game.Rulesets.Mania.UI
{ {
BarLines.ForEach(Playfield.Add); BarLines.ForEach(Playfield.Add);
Config.BindWith(ManiaSetting.ScrollDirection, configDirection); Config.BindWith(ManiaRulesetSetting.ScrollDirection, configDirection);
configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true); configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true);
Config.BindWith(ManiaSetting.ScrollTime, TimeRange); Config.BindWith(ManiaRulesetSetting.ScrollTime, TimeRange);
} }
/// <summary> /// <summary>
@ -96,7 +95,7 @@ namespace osu.Game.Rulesets.Mania.UI
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this);
public override int Variant => (int)(Mods.OfType<IPlayfieldTypeMod>().FirstOrDefault()?.PlayfieldType ?? PlayfieldType.Single) + Beatmap.TotalColumns; public override int Variant => (int)(Beatmap.Stages.Count == 1 ? PlayfieldType.Single : PlayfieldType.Dual) + Beatmap.TotalColumns;
public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant); public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant);

View File

@ -33,9 +33,11 @@ namespace osu.Game.Rulesets.Osu.Tests
case Slider slider: case Slider slider:
foreach (var nested in slider.NestedHitObjects) foreach (var nested in slider.NestedHitObjects)
yield return createConvertValue(nested); yield return createConvertValue(nested);
break; break;
default: default:
yield return createConvertValue(hitObject); yield return createConvertValue(hitObject);
break; break;
} }

View File

@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
private GameplayCursor cursor; private GameplayCursor cursor;
public override IReadOnlyList<Type> RequiredTypes => new [] { typeof(CursorTrail) }; public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(CursorTrail) };
public CursorContainer Cursor => cursor; public CursorContainer Cursor => cursor;

View File

@ -89,7 +89,8 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
private readonly bool auto; private readonly bool auto;
public TestDrawableHitCircle(HitCircle h, bool auto) : base(h) public TestDrawableHitCircle(HitCircle h, bool auto)
: base(h)
{ {
this.auto = auto; this.auto = auto;
} }

View File

@ -301,6 +301,7 @@ namespace osu.Game.Rulesets.Osu.Tests
} }
private float judgementOffsetDirection = 1; private float judgementOffsetDirection = 1;
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
{ {
var osuObject = judgedObject as DrawableOsuHitObject; var osuObject = judgedObject as DrawableOsuHitObject;

View File

@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public class TestCaseSpinner : OsuTestCase public class TestCaseSpinner : OsuTestCase
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {
typeof(SpinnerDisc), typeof(SpinnerDisc),
typeof(DrawableSpinner), typeof(DrawableSpinner),
typeof(DrawableOsuHitObject) typeof(DrawableOsuHitObject)
@ -67,7 +67,8 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
private bool auto; private bool auto;
public TestDrawableSpinner(Spinner s, bool auto) : base(s) public TestDrawableSpinner(Spinner s, bool auto)
: base(s)
{ {
this.auto = auto; this.auto = auto;
} }

View File

@ -0,0 +1,30 @@
// 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.Configuration;
using osu.Game.Rulesets.Configuration;
namespace osu.Game.Rulesets.Osu.Configuration
{
public class OsuRulesetConfigManager : RulesetConfigManager<OsuRulesetSetting>
{
public OsuRulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null)
: base(settings, ruleset, variant)
{
}
protected override void InitialiseDefaults()
{
base.InitialiseDefaults();
Set(OsuRulesetSetting.SnakingInSliders, true);
Set(OsuRulesetSetting.SnakingOutSliders, true);
}
}
public enum OsuRulesetSetting
{
SnakingInSliders,
SnakingOutSliders
}
}

View File

@ -126,8 +126,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{ {
// Apply object-based bonus for flashlight. // Apply object-based bonus for flashlight.
aimValue *= 1.0f + 0.35f * Math.Min(1.0f, totalHits / 200.0f) + aimValue *= 1.0f + 0.35f * Math.Min(1.0f, totalHits / 200.0f) +
(totalHits > 200 ? 0.3f * Math.Min(1.0f, (totalHits - 200) / 300.0f) + (totalHits > 200
(totalHits > 500 ? (totalHits - 500) / 1200.0f : 0.0f) : 0.0f); ? 0.3f * Math.Min(1.0f, (totalHits - 200) / 300.0f) +
(totalHits > 500 ? (totalHits - 500) / 1200.0f : 0.0f)
: 0.0f);
} }
// Scale the aim value with accuracy _slightly_ // Scale the aim value with accuracy _slightly_

View File

@ -92,6 +92,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
{ {
if (slider.LazyEndPosition != null) if (slider.LazyEndPosition != null)
return; return;
slider.LazyEndPosition = slider.StackedPosition; slider.LazyEndPosition = slider.StackedPosition;
float approxFollowCircleRadius = (float)(slider.Radius * 3); float approxFollowCircleRadius = (float)(slider.Radius * 3);
@ -127,8 +128,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
{ {
Vector2 pos = hitObject.StackedPosition; Vector2 pos = hitObject.StackedPosition;
var slider = hitObject as Slider; if (hitObject is Slider slider)
if (slider != null)
{ {
computeSliderCursorPosition(slider); computeSliderCursorPosition(slider);
pos = slider.LazyEndPosition ?? pos; pos = slider.LazyEndPosition ?? pos;

View File

@ -9,8 +9,10 @@ namespace osu.Game.Rulesets.Osu.Judgements
{ {
[Description(@"")] [Description(@"")]
None, None,
[Description(@"Good")] [Description(@"Good")]
Good, Good,
[Description(@"Amazing")] [Description(@"Amazing")]
Perfect Perfect
} }

View File

@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{ {
foreach (var drawable in drawables) foreach (var drawable in drawables)
{ {
var hitObject = (OsuHitObject) drawable.HitObject; var hitObject = (OsuHitObject)drawable.HitObject;
float appearDistance = (float)(hitObject.TimePreempt - hitObject.TimeFadeIn) / 2; float appearDistance = (float)(hitObject.TimePreempt - hitObject.TimeFadeIn) / 2;
@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Mods
.MoveTo(originalPosition, moveDuration, Easing.InOutSine); .MoveTo(originalPosition, moveDuration, Easing.InOutSine);
} }
theta += (float) hitObject.TimeFadeIn / 1000; theta += (float)hitObject.TimeFadeIn / 1000;
} }
} }
} }

View File

@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
/// <summary> /// <summary>
/// Connects hit objects visually, for example with follow points. /// Connects hit objects visually, for example with follow points.
/// </summary> /// </summary>
public abstract class ConnectionRenderer<T> : Container public abstract class ConnectionRenderer<T> : LifetimeManagementContainer
where T : HitObject where T : HitObject
{ {
/// <summary> /// <summary>

View File

@ -12,39 +12,44 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
public class FollowPointRenderer : ConnectionRenderer<OsuHitObject> public class FollowPointRenderer : ConnectionRenderer<OsuHitObject>
{ {
private int pointDistance = 32; private int pointDistance = 32;
/// <summary> /// <summary>
/// Determines how much space there is between points. /// Determines how much space there is between points.
/// </summary> /// </summary>
public int PointDistance public int PointDistance
{ {
get { return pointDistance; } get => pointDistance;
set set
{ {
if (pointDistance == value) return; if (pointDistance == value) return;
pointDistance = value; pointDistance = value;
update(); update();
} }
} }
private int preEmpt = 800; private int preEmpt = 800;
/// <summary> /// <summary>
/// Follow points to the next hitobject start appearing for this many milliseconds before an hitobject's end time. /// Follow points to the next hitobject start appearing for this many milliseconds before an hitobject's end time.
/// </summary> /// </summary>
public int PreEmpt public int PreEmpt
{ {
get { return preEmpt; } get => preEmpt;
set set
{ {
if (preEmpt == value) return; if (preEmpt == value) return;
preEmpt = value; preEmpt = value;
update(); update();
} }
} }
private IEnumerable<OsuHitObject> hitObjects; private IEnumerable<OsuHitObject> hitObjects;
public override IEnumerable<OsuHitObject> HitObjects public override IEnumerable<OsuHitObject> HitObjects
{ {
get { return hitObjects; } get => hitObjects;
set set
{ {
hitObjects = value; hitObjects = value;
@ -56,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
private void update() private void update()
{ {
Clear(); ClearInternal();
if (hitObjects == null) if (hitObjects == null)
return; return;
@ -86,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
FollowPoint fp; FollowPoint fp;
Add(fp = new FollowPoint AddInternal(fp = new FollowPoint
{ {
Position = pointStartPosition, Position = pointStartPosition,
Rotation = rotation, Rotation = rotation,
@ -107,6 +112,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
fp.Expire(true); fp.Expire(true);
} }
} }
prevHitObject = currHitObject; prevHitObject = currHitObject;
} }
} }

View File

@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public override Color4 AccentColour public override Color4 AccentColour
{ {
get { return base.AccentColour; } get => base.AccentColour;
set set
{ {
base.AccentColour = value; base.AccentColour = value;
@ -139,6 +139,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadeIn * 2, HitObject.TimePreempt)); ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadeIn * 2, HitObject.TimePreempt));
ApproachCircle.ScaleTo(1.1f, HitObject.TimePreempt); ApproachCircle.ScaleTo(1.1f, HitObject.TimePreempt);
ApproachCircle.Expire(true);
} }
protected override void UpdateCurrentState(ArmedState state) protected override void UpdateCurrentState(ArmedState state)

View File

@ -10,8 +10,8 @@ using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Configuration;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -33,6 +33,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly IBindable<float> scaleBindable = new Bindable<float>(); private readonly IBindable<float> scaleBindable = new Bindable<float>();
private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>(); private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>();
[Resolved(CanBeNull = true)]
private OsuRulesetConfigManager config { get; set; }
public DrawableSlider(Slider s) public DrawableSlider(Slider s)
: base(s) : base(s)
{ {
@ -94,10 +97,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager config) private void load()
{ {
config.BindWith(OsuSetting.SnakingInSliders, Body.SnakingIn); config?.BindWith(OsuRulesetSetting.SnakingInSliders, Body.SnakingIn);
config.BindWith(OsuSetting.SnakingOutSliders, Body.SnakingOut); config?.BindWith(OsuRulesetSetting.SnakingOutSliders, Body.SnakingOut);
positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
scaleBindable.BindValueChanged(scale => scaleBindable.BindValueChanged(scale =>
@ -115,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public override Color4 AccentColour public override Color4 AccentColour
{ {
get { return base.AccentColour; } get => base.AccentColour;
set set
{ {
base.AccentColour = value; base.AccentColour = value;

View File

@ -20,7 +20,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public override bool DisplayResult => false; public override bool DisplayResult => false;
public DrawableSliderTick(SliderTick sliderTick) : base(sliderTick) public DrawableSliderTick(SliderTick sliderTick)
: base(sliderTick)
{ {
Size = new Vector2(16) * sliderTick.Scale; Size = new Vector2(16) * sliderTick.Scale;
Origin = Anchor.Centre; Origin = Anchor.Centre;

View File

@ -42,7 +42,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private Color4 normalColour; private Color4 normalColour;
private Color4 completeColour; private Color4 completeColour;
public DrawableSpinner(Spinner s) : base(s) public DrawableSpinner(Spinner s)
: base(s)
{ {
Origin = Anchor.Centre; Origin = Anchor.Centre;
Position = s.Position; Position = s.Position;

View File

@ -12,6 +12,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{ {
public class ApproachCircle : Container public class ApproachCircle : Container
{ {
public override bool RemoveWhenNotAlive => false;
public ApproachCircle() public ApproachCircle()
{ {
Anchor = Anchor.Centre; Anchor = Anchor.Centre;

View File

@ -18,8 +18,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public string Text public string Text
{ {
get { return number.Text; } get => number.Text;
set { number.Text = value; } set => number.Text = value;
} }
public NumberPiece() public NumberPiece()

View File

@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
/// </summary> /// </summary>
public Color4 AccentColour public Color4 AccentColour
{ {
get { return accentColour; } get => accentColour;
set set
{ {
accentColour = value; accentColour = value;
@ -136,11 +136,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public bool Tracking public bool Tracking
{ {
get { return tracking; } get => tracking;
private set private set
{ {
if (value == tracking) if (value == tracking)
return; return;
tracking = value; tracking = value;
FollowCircle.ScaleTo(tracking ? 2f : 1, 300, Easing.OutQuint); FollowCircle.ScaleTo(tracking ? 2f : 1, 300, Easing.OutQuint);

View File

@ -40,6 +40,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{ {
if (path.AccentColour == value) if (path.AccentColour == value)
return; return;
path.AccentColour = value; path.AccentColour = value;
container.ForceRedraw(); container.ForceRedraw();
@ -56,6 +57,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{ {
if (path.BorderColour == value) if (path.BorderColour == value)
return; return;
path.BorderColour = value; path.BorderColour = value;
container.ForceRedraw(); container.ForceRedraw();
@ -105,6 +107,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{ {
if (borderColour == value) if (borderColour == value)
return; return;
borderColour = value; borderColour = value;
InvalidateTexture(); InvalidateTexture();
@ -120,6 +123,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{ {
if (accentColour == value) if (accentColour == value)
return; return;
accentColour = value; accentColour = value;
InvalidateTexture(); InvalidateTexture();

View File

@ -15,10 +15,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public Color4 AccentColour public Color4 AccentColour
{ {
get get => Disc.Colour;
{
return Disc.Colour;
}
set set
{ {
Disc.Colour = value; Disc.Colour = value;

View File

@ -17,8 +17,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public Color4 AccentColour public Color4 AccentColour
{ {
get { return background.AccentColour; } get => background.AccentColour;
set { background.AccentColour = value; } set => background.AccentColour = value;
} }
private readonly SpinnerBackground background; private readonly SpinnerBackground background;
@ -43,12 +43,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
private bool tracking; private bool tracking;
public bool Tracking public bool Tracking
{ {
get { return tracking; } get => tracking;
set set
{ {
if (value == tracking) return; if (value == tracking) return;
tracking = value; tracking = value;
background.FadeTo(tracking ? tracking_alpha : idle_alpha, 100); background.FadeTo(tracking ? tracking_alpha : idle_alpha, 100);
@ -56,12 +58,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
} }
private bool complete; private bool complete;
public bool Complete public bool Complete
{ {
get { return complete; } get => complete;
set set
{ {
if (value == complete) return; if (value == complete) return;
complete = value; complete = value;
updateCompleteTick(); updateCompleteTick();

View File

@ -41,10 +41,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public double SpinsPerMinute public double SpinsPerMinute
{ {
get { return spm; } get => spm;
private set private set
{ {
if (value == spm) return; if (value == spm) return;
spm = value; spm = value;
spmText.Text = Math.Truncate(value).ToString(@"#0"); spmText.Text = Math.Truncate(value).ToString(@"#0");
} }

View File

@ -262,6 +262,7 @@ namespace osu.Game.Rulesets.Osu.Objects
{ {
if (nodeIndex < NodeSamples.Count) if (nodeIndex < NodeSamples.Count)
return NodeSamples[nodeIndex]; return NodeSamples[nodeIndex];
return Samples; return Samples;
} }

View File

@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Osu
protected override bool Handle(UIEvent e) protected override bool Handle(UIEvent e)
{ {
if (!AllowUserPresses) return false; if (!AllowUserPresses) return false;
return base.Handle(e); return base.Handle(e);
} }
} }

View File

@ -16,8 +16,11 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Legacy;
using osu.Game.Configuration;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Difficulty; using osu.Game.Rulesets.Osu.Difficulty;
using osu.Game.Scoring; using osu.Game.Scoring;
@ -121,7 +124,8 @@ namespace osu.Game.Rulesets.Osu
new OsuModAutopilot(), new OsuModAutopilot(),
}; };
case ModType.Fun: case ModType.Fun:
return new Mod[] { return new Mod[]
{
new OsuModTransform(), new OsuModTransform(),
new OsuModWiggle(), new OsuModWiggle(),
new OsuModGrow() new OsuModGrow()
@ -143,12 +147,14 @@ namespace osu.Game.Rulesets.Osu
public override string ShortName => "osu"; public override string ShortName => "osu";
public override RulesetSettingsSubsection CreateSettings() => new OsuSettings(this); public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this);
public override int? LegacyID => 0; public override int? LegacyID => 0;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame(); public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame();
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo);
public OsuRuleset(RulesetInfo rulesetInfo = null) public OsuRuleset(RulesetInfo rulesetInfo = null)
: base(rulesetInfo) : base(rulesetInfo)
{ {

View File

@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Osu.Replays
/// Constants (for spinners). /// Constants (for spinners).
/// </summary> /// </summary>
protected static readonly Vector2 SPINNER_CENTRE = OsuPlayfield.BASE_SIZE / 2; protected static readonly Vector2 SPINNER_CENTRE = OsuPlayfield.BASE_SIZE / 2;
protected const float SPIN_RADIUS = 50; protected const float SPIN_RADIUS = 50;
/// <summary> /// <summary>
@ -46,6 +47,7 @@ namespace osu.Game.Rulesets.Osu.Replays
#endregion #endregion
#region Utilities #region Utilities
protected double ApplyModsToTime(double v) => v; protected double ApplyModsToTime(double v) => v;
protected double ApplyModsToRate(double v) => v; protected double ApplyModsToRate(double v) => v;

View File

@ -255,10 +255,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{ {
[VertexMember(2, VertexAttribPointerType.Float)] [VertexMember(2, VertexAttribPointerType.Float)]
public Vector2 Position; public Vector2 Position;
[VertexMember(4, VertexAttribPointerType.Float)] [VertexMember(4, VertexAttribPointerType.Float)]
public Color4 Colour; public Color4 Colour;
[VertexMember(2, VertexAttribPointerType.Float)] [VertexMember(2, VertexAttribPointerType.Float)]
public Vector2 TexturePosition; public Vector2 TexturePosition;
[VertexMember(1, VertexAttribPointerType.Float)] [VertexMember(1, VertexAttribPointerType.Float)]
public float Time; public float Time;

View File

@ -213,6 +213,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
public void Expand() public void Expand()
{ {
if (!cursorExpand) return; if (!cursorExpand) return;
expandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 100, Easing.OutQuad); expandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 100, Easing.OutQuad);
} }

View File

@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.UI
{ {
public class OsuPlayfield : Playfield public class OsuPlayfield : Playfield
{ {
private readonly Container approachCircles; private readonly ApproachCircleProxyContainer approachCircles;
private readonly JudgementContainer<DrawableOsuJudgement> judgementLayer; private readonly JudgementContainer<DrawableOsuJudgement> judgementLayer;
private readonly ConnectionRenderer<OsuHitObject> connectionLayer; private readonly ConnectionRenderer<OsuHitObject> connectionLayer;
@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.UI
Depth = 1, Depth = 1,
}, },
HitObjectContainer, HitObjectContainer,
approachCircles = new Container approachCircles = new ApproachCircleProxyContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Depth = -1, Depth = -1,
@ -58,13 +58,26 @@ namespace osu.Game.Rulesets.Osu.UI
{ {
h.OnNewResult += onNewResult; h.OnNewResult += onNewResult;
var c = h as IDrawableHitObjectWithProxiedApproach; if (h is IDrawableHitObjectWithProxiedApproach c)
if (c != null) {
approachCircles.Add(c.ProxiedLayer.CreateProxy()); var original = c.ProxiedLayer;
// Hitobjects only have lifetimes set on LoadComplete. For nested hitobjects (e.g. SliderHeads), this only happens when the parenting slider becomes visible.
// This delegation is required to make sure that the approach circles for those not-yet-loaded objects aren't added prematurely.
original.OnLoadComplete += addApproachCircleProxy;
}
base.Add(h); base.Add(h);
} }
private void addApproachCircleProxy(Drawable d)
{
var proxy = d.CreateProxy();
proxy.LifetimeStart = d.LifetimeStart;
proxy.LifetimeEnd = d.LifetimeEnd;
approachCircles.Add(proxy);
}
public override void PostProcess() public override void PostProcess()
{ {
connectionLayer.HitObjects = HitObjectContainer.Objects.Select(d => d.HitObject).OfType<OsuHitObject>(); connectionLayer.HitObjects = HitObjectContainer.Objects.Select(d => d.HitObject).OfType<OsuHitObject>();
@ -86,5 +99,10 @@ namespace osu.Game.Rulesets.Osu.UI
} }
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos);
private class ApproachCircleProxyContainer : LifetimeManagementContainer
{
public void Add(Drawable approachCircleProxy) => AddInternal(approachCircleProxy);
}
} }
} }

View File

@ -8,6 +8,7 @@ using osu.Game.Beatmaps;
using osu.Game.Input.Handlers; using osu.Game.Input.Handlers;
using osu.Game.Replays; using osu.Game.Replays;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.Replays;
@ -20,6 +21,8 @@ namespace osu.Game.Rulesets.Osu.UI
{ {
public class OsuRulesetContainer : RulesetContainer<OsuPlayfield, OsuHitObject> public class OsuRulesetContainer : RulesetContainer<OsuPlayfield, OsuHitObject>
{ {
protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config;
public OsuRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) public OsuRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap) : base(ruleset, beatmap)
{ {

View File

@ -3,34 +3,36 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Configuration;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Osu.Configuration;
namespace osu.Game.Rulesets.Osu.UI namespace osu.Game.Rulesets.Osu.UI
{ {
public class OsuSettings : RulesetSettingsSubsection public class OsuSettingsSubsection : RulesetSettingsSubsection
{ {
protected override string Header => "osu!"; protected override string Header => "osu!";
public OsuSettings(Ruleset ruleset) public OsuSettingsSubsection(Ruleset ruleset)
: base(ruleset) : base(ruleset)
{ {
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager config) private void load()
{ {
var config = (OsuRulesetConfigManager)Config;
Children = new Drawable[] Children = new Drawable[]
{ {
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Snaking in sliders", LabelText = "Snaking in sliders",
Bindable = config.GetBindable<bool>(OsuSetting.SnakingInSliders) Bindable = config.GetBindable<bool>(OsuRulesetSetting.SnakingInSliders)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Snaking out sliders", LabelText = "Snaking out sliders",
Bindable = config.GetBindable<bool>(OsuSetting.SnakingOutSliders) Bindable = config.GetBindable<bool>(OsuRulesetSetting.SnakingOutSliders)
}, },
}; };
} }

View File

@ -229,6 +229,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
// Ensure alternating centre and rim hits // Ensure alternating centre and rim hits
if (lastWasCentre == isCentre) if (lastWasCentre == isCentre)
return false; return false;
lastWasCentre = isCentre; lastWasCentre = isCentre;
UpdateResult(true); UpdateResult(true);

View File

@ -49,6 +49,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected void ProxyContent() protected void ProxyContent()
{ {
if (isProxied) return; if (isProxied) return;
isProxied = true; isProxied = true;
nonProxiedContent.Remove(Content); nonProxiedContent.Remove(Content);
@ -62,6 +63,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected void UnproxyContent() protected void UnproxyContent()
{ {
if (!isProxied) return; if (!isProxied) return;
isProxied = false; isProxied = false;
proxiedContent.Remove(Content); proxiedContent.Remove(Content);

View File

@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
/// </summary> /// </summary>
public override Color4 AccentColour public override Color4 AccentColour
{ {
get { return base.AccentColour; } get => base.AccentColour;
set set
{ {
base.AccentColour = value; base.AccentColour = value;
@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
/// </summary> /// </summary>
public override bool KiaiMode public override bool KiaiMode
{ {
get { return base.KiaiMode; } get => base.KiaiMode;
set set
{ {
base.KiaiMode = value; base.KiaiMode = value;

View File

@ -11,26 +11,25 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
public class TaikoPiece : BeatSyncedContainer, IHasAccentColour public class TaikoPiece : BeatSyncedContainer, IHasAccentColour
{ {
private Color4 accentColour; private Color4 accentColour;
/// <summary> /// <summary>
/// The colour of the inner circle and outer glows. /// The colour of the inner circle and outer glows.
/// </summary> /// </summary>
public virtual Color4 AccentColour public virtual Color4 AccentColour
{ {
get { return accentColour; } get => accentColour;
set { accentColour = value; } set => accentColour = value;
} }
private bool kiaiMode; private bool kiaiMode;
/// <summary> /// <summary>
/// Whether Kiai mode effects are enabled for this circle piece. /// Whether Kiai mode effects are enabled for this circle piece.
/// </summary> /// </summary>
public virtual bool KiaiMode public virtual bool KiaiMode
{ {
get { return kiaiMode; } get => kiaiMode;
set set => kiaiMode = value;
{
kiaiMode = value;
}
} }
public TaikoPiece() public TaikoPiece()

View File

@ -23,9 +23,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
private const float tick_size = 0.35f; private const float tick_size = 0.35f;
private bool filled; private bool filled;
public bool Filled public bool Filled
{ {
get { return filled; } get => filled;
set set
{ {
filled = value; filled = value;

View File

@ -19,7 +19,10 @@ namespace osu.Game.Rulesets.Taiko.Objects
/// </summary> /// </summary>
public int RequiredHits = 10; public int RequiredHits = 10;
public override bool IsStrong { set => throw new NotSupportedException($"{nameof(Swell)} cannot be a strong hitobject."); } public override bool IsStrong
{
set => throw new NotSupportedException($"{nameof(Swell)} cannot be a strong hitobject.");
}
protected override void CreateNestedHitObjects() protected override void CreateNestedHitObjects()
{ {

View File

@ -19,10 +19,13 @@ namespace osu.Game.Rulesets.Taiko
{ {
[Description("Left (rim)")] [Description("Left (rim)")]
LeftRim, LeftRim,
[Description("Left (centre)")] [Description("Left (centre)")]
LeftCentre, LeftCentre,
[Description("Right (centre)")] [Description("Right (centre)")]
RightCentre, RightCentre,
[Description("Right (rim)")] [Description("Right (rim)")]
RightRim RightRim
} }

View File

@ -0,0 +1,414 @@
// 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 System.Threading;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Input.States;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Screens.Select;
using osu.Game.Tests.Resources;
using osu.Game.Users;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual
{
[TestFixture]
public class TestCaseBackgroundScreenBeatmap : ManualInputManagerTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(ScreenWithBeatmapBackground),
typeof(PlayerLoader),
typeof(Player),
typeof(UserDimContainer),
typeof(OsuScreen)
};
private DummySongSelect songSelect;
private DimAccessiblePlayerLoader playerLoader;
private DimAccessiblePlayer player;
private DatabaseContextFactory factory;
private BeatmapManager manager;
private RulesetStore rulesets;
private ScreenStackCacheContainer screenStackContainer;
[BackgroundDependencyLoader]
private void load(GameHost host)
{
factory = new DatabaseContextFactory(LocalStorage);
factory.ResetDatabase();
using (var usage = factory.Get())
usage.Migrate();
factory.ResetDatabase();
using (var usage = factory.Get())
usage.Migrate();
Dependencies.Cache(rulesets = new RulesetStore(factory));
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, null, host, Beatmap.Default));
Dependencies.Cache(new OsuConfigManager(LocalStorage));
Beatmap.SetDefault();
}
[SetUp]
public virtual void SetUp()
{
Schedule(() =>
{
manager.Delete(manager.GetAllUsableBeatmapSets());
var temp = TestResources.GetTestBeatmapForImport();
manager.Import(temp);
Child = screenStackContainer = new ScreenStackCacheContainer { RelativeSizeAxes = Axes.Both };
screenStackContainer.ScreenStack.Push(songSelect = new DummySongSelect());
});
}
/// <summary>
/// Check if <see cref="PlayerLoader"/> properly triggers background dim previews when a user hovers over the visual settings panel.
/// </summary>
[Test]
public void PlayerLoaderSettingsHoverTest()
{
setupUserSettings();
AddStep("Start player loader", () => songSelect.Push(playerLoader = new DimAccessiblePlayerLoader(player = new DimAccessiblePlayer())));
AddUntilStep(() => playerLoader?.IsLoaded ?? false, "Wait for Player Loader to load");
AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent());
AddStep("Trigger background preview", () =>
{
InputManager.MoveMouseTo(playerLoader.ScreenPos);
InputManager.MoveMouseTo(playerLoader.VisualSettingsPos);
});
waitForDim();
AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed());
AddStep("Stop background preview", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
waitForDim();
AddAssert("Screen is undimmed", () => songSelect.IsBackgroundUndimmed());
}
/// <summary>
/// In the case of a user triggering the dim preview the instant player gets loaded, then moving the cursor off of the visual settings:
/// The OnHover of PlayerLoader will trigger, which could potentially trigger an undim unless checked for in PlayerLoader.
/// We need to check that in this scenario, the dim is still properly applied after entering player.
/// </summary>
[Test]
public void PlayerLoaderTransitionTest()
{
performFullSetup();
AddStep("Trigger hover event", () => playerLoader.TriggerOnHover());
AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent());
waitForDim();
AddAssert("Screen is dimmed and unblurred", () => songSelect.IsBackgroundDimmed() && songSelect.IsBackgroundUnblurred());
}
/// <summary>
/// Make sure the background is fully invisible (Alpha == 0) when the background should be disabled by the storyboard.
/// </summary>
[Test]
public void StoryboardBackgroundVisibilityTest()
{
performFullSetup();
createFakeStoryboard();
AddStep("Storyboard Enabled", () =>
{
player.ReplacesBackground.Value = true;
player.StoryboardEnabled.Value = true;
});
waitForDim();
AddAssert("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible());
AddStep("Storyboard Disabled", () =>
{
player.ReplacesBackground.Value = false;
player.StoryboardEnabled.Value = false;
});
waitForDim();
AddAssert("Background is visible, storyboard is invisible", () => songSelect.IsBackgroundVisible() && player.IsStoryboardInvisible());
}
/// <summary>
/// When exiting player, the screen that it suspends/exits to needs to have a fully visible (Alpha == 1) background.
/// </summary>
[Test]
public void StoryboardTransitionTest()
{
performFullSetup();
createFakeStoryboard();
AddStep("Exit to song select", () => player.Exit());
waitForDim();
AddAssert("Background is visible", () => songSelect.IsBackgroundVisible());
}
/// <summary>
/// Check if the <see cref="UserDimContainer"/> is properly accepting user dim changes at all.
/// </summary>
[Test]
public void DisableUserDimTest()
{
performFullSetup();
waitForDim();
AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed());
AddStep("EnableUserDim disabled", () => songSelect.DimEnabled.Value = false);
waitForDim();
AddAssert("Screen is undimmed", () => songSelect.IsBackgroundUndimmed());
AddStep("EnableUserDim enabled", () => songSelect.DimEnabled.Value = true);
waitForDim();
AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed());
}
/// <summary>
/// Check if the fade container retains dim when pausing
/// </summary>
[Test]
public void PauseTest()
{
performFullSetup(true);
AddStep("Pause", () => player.CurrentPauseContainer.Pause());
waitForDim();
AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed());
AddStep("Unpause", () => player.CurrentPauseContainer.Resume());
waitForDim();
AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed());
}
/// <summary>
/// Check if the fade container removes user dim when suspending <see cref="Player"/> for <see cref="SoloResults"/>
/// </summary>
[Test]
public void TransitionTest()
{
performFullSetup();
AddStep("Transition to Results", () => player.Push(new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } })));
waitForDim();
AddAssert("Screen is undimmed and is original background", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent());
}
/// <summary>
/// Check if background gets undimmed when leaving <see cref="Player"/> for <see cref="PlaySongSelect"/>
/// </summary>
[Test]
public void TransitionOutTest()
{
performFullSetup();
AddStep("Exit to song select", () => player.Exit());
waitForDim();
AddAssert("Screen is undimmed", () => songSelect.IsBackgroundUndimmed());
}
private void waitForDim() => AddWaitStep(5, "Wait for dim");
private void createFakeStoryboard() => AddStep("Create storyboard", () =>
{
player.StoryboardEnabled.Value = false;
player.ReplacesBackground.Value = false;
player.CurrentStoryboardContainer.Add(new SpriteText
{
Size = new Vector2(250, 50),
Alpha = 1,
Colour = Color4.Tomato,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "THIS IS A STORYBOARD",
});
});
private void performFullSetup(bool allowPause = false)
{
setupUserSettings();
AddStep("Start player loader", () =>
{
songSelect.Push(playerLoader = new DimAccessiblePlayerLoader(player = new DimAccessiblePlayer
{
AllowPause = allowPause,
Ready = true,
}));
});
AddUntilStep(() => playerLoader.IsLoaded, "Wait for Player Loader to load");
AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
AddUntilStep(() => player.IsLoaded, "Wait for player to load");
}
private void setupUserSettings()
{
AddUntilStep(() => songSelect.Carousel.SelectedBeatmap != null, "Song select has selection");
AddStep("Set default user settings", () =>
{
Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { new OsuModNoFail() });
songSelect.DimLevel.Value = 0.7f;
songSelect.BlurLevel.Value = 0.0f;
});
}
private class DummySongSelect : PlaySongSelect
{
protected override BackgroundScreen CreateBackground()
{
FadeAccessibleBackground background = new FadeAccessibleBackground(Beatmap.Value);
DimEnabled.BindTo(background.EnableUserDim);
return background;
}
public readonly Bindable<bool> DimEnabled = new Bindable<bool>();
public readonly Bindable<double> DimLevel = new Bindable<double>();
public readonly Bindable<double> BlurLevel = new Bindable<double>();
public new BeatmapCarousel Carousel => base.Carousel;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
config.BindWith(OsuSetting.DimLevel, DimLevel);
config.BindWith(OsuSetting.BlurLevel, BlurLevel);
}
public bool IsBackgroundDimmed() => ((FadeAccessibleBackground)Background).CurrentColour == OsuColour.Gray(1 - (float)DimLevel.Value);
public bool IsBackgroundUnblurred() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(0);
public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == Color4.White;
public bool IsBackgroundInvisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 0;
public bool IsBackgroundVisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 1;
/// <summary>
/// Make sure every time a screen gets pushed, the background doesn't get replaced
/// </summary>
/// <returns>Whether or not the original background (The one created in DummySongSelect) is still the current background</returns>
public bool IsBackgroundCurrent() => ((FadeAccessibleBackground)Background).IsCurrentScreen();
}
private class FadeAccessibleResults : SoloResults
{
public FadeAccessibleResults(ScoreInfo score)
: base(score)
{
}
protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
}
private class DimAccessiblePlayer : Player
{
protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
protected override UserDimContainer CreateStoryboardContainer()
{
return new TestUserDimContainer(true)
{
RelativeSizeAxes = Axes.Both,
Alpha = 1,
EnableUserDim = { Value = true }
};
}
public PauseContainer CurrentPauseContainer => PauseContainer;
public UserDimContainer CurrentStoryboardContainer => StoryboardContainer;
// Whether or not the player should be allowed to load.
public bool Ready;
public Bindable<bool> StoryboardEnabled;
public readonly Bindable<bool> ReplacesBackground = new Bindable<bool>();
public readonly Bindable<bool> IsPaused = new Bindable<bool>();
public bool IsStoryboardVisible() => ((TestUserDimContainer)CurrentStoryboardContainer).CurrentAlpha == 1;
public bool IsStoryboardInvisible() => ((TestUserDimContainer)CurrentStoryboardContainer).CurrentAlpha <= 1;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
while (!Ready)
Thread.Sleep(1);
StoryboardEnabled = config.GetBindable<bool>(OsuSetting.ShowStoryboard);
ReplacesBackground.BindTo(Background.StoryboardReplacesBackground);
RulesetContainer.IsPaused.BindTo(IsPaused);
}
}
private class ScreenStackCacheContainer : Container
{
[Cached]
private BackgroundScreenStack backgroundScreenStack;
public readonly ScreenStack ScreenStack;
public ScreenStackCacheContainer()
{
Add(backgroundScreenStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both });
Add(ScreenStack = new ScreenStack { RelativeSizeAxes = Axes.Both });
}
}
private class DimAccessiblePlayerLoader : PlayerLoader
{
public VisualSettings VisualSettingsPos => VisualSettings;
public BackgroundScreen ScreenPos => Background;
public DimAccessiblePlayerLoader(Player player)
: base(() => player)
{
}
public void TriggerOnHover() => OnHover(new HoverEvent(new InputState()));
protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
}
private class FadeAccessibleBackground : BackgroundScreenBeatmap
{
protected override UserDimContainer CreateFadeContainer() => fadeContainer = new TestUserDimContainer { RelativeSizeAxes = Axes.Both };
public Color4 CurrentColour => fadeContainer.CurrentColour;
public float CurrentAlpha => fadeContainer.CurrentAlpha;
public Vector2 CurrentBlur => Background.BlurSigma;
private TestUserDimContainer fadeContainer;
public FadeAccessibleBackground(WorkingBeatmap beatmap)
: base(beatmap)
{
}
}
private class TestUserDimContainer : UserDimContainer
{
public Color4 CurrentColour => DimContainer.Colour;
public float CurrentAlpha => DimContainer.Alpha;
public TestUserDimContainer(bool isStoryboard = false)
: base(isStoryboard)
{
}
}
}
}

View File

@ -141,6 +141,7 @@ namespace osu.Game.Tests.Visual
} }
private SortedList<TimingControlPoint> timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints; private SortedList<TimingControlPoint> timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints;
private TimingControlPoint getNextTimingPoint(TimingControlPoint current) private TimingControlPoint getNextTimingPoint(TimingControlPoint current)
{ {
if (timingPoints[timingPoints.Count - 1] == current) if (timingPoints[timingPoints.Count - 1] == current)
@ -190,7 +191,7 @@ namespace osu.Game.Tests.Visual
public double Value public double Value
{ {
set { valueText.Text = $"{value:G}"; } set => valueText.Text = $"{value:G}";
} }
public InfoString(string header) public InfoString(string header)

View File

@ -150,6 +150,7 @@ namespace osu.Game.Tests.Visual
var currentlySelected = carousel.Items.Find(s => s.Item is CarouselBeatmap && s.Item.State.Value == CarouselItemState.Selected); var currentlySelected = carousel.Items.Find(s => s.Item is CarouselBeatmap && s.Item.State.Value == CarouselItemState.Selected);
if (currentlySelected == null) if (currentlySelected == null)
return true; return true;
return currentlySelected.Item.Visible; return currentlySelected.Item.Visible;
} }
@ -166,8 +167,7 @@ namespace osu.Game.Tests.Visual
carousel.Filter(new FilterCriteria { SearchText = "Dingo" }, false); carousel.Filter(new FilterCriteria { SearchText = "Dingo" }, false);
carousel.Filter(new FilterCriteria(), false); carousel.Filter(new FilterCriteria(), false);
eagerSelectedIDs.Add(carousel.SelectedBeatmapSet.ID); eagerSelectedIDs.Add(carousel.SelectedBeatmapSet.ID);
} });
);
} }
/// <summary> /// <summary>
@ -522,6 +522,7 @@ namespace osu.Game.Tests.Visual
} }
}); });
} }
return toReturn; return toReturn;
} }

View File

@ -23,9 +23,6 @@ namespace osu.Game.Tests.Visual
[System.ComponentModel.Description("in BeatmapOverlay")] [System.ComponentModel.Description("in BeatmapOverlay")]
public class TestCaseBeatmapScoresContainer : OsuTestCase public class TestCaseBeatmapScoresContainer : OsuTestCase
{ {
private readonly IEnumerable<APIScoreInfo> scores;
private readonly IEnumerable<APIScoreInfo> anotherScores;
private readonly APIScoreInfo topScoreInfo;
private readonly Box background; private readonly Box background;
public TestCaseBeatmapScoresContainer() public TestCaseBeatmapScoresContainer()
@ -47,15 +44,7 @@ namespace osu.Game.Tests.Visual
} }
}; };
AddStep("scores pack 1", () => scoresContainer.Scores = scores); IEnumerable<APIScoreInfo> scores = new[]
AddStep("scores pack 2", () => scoresContainer.Scores = anotherScores);
AddStep("only top score", () => scoresContainer.Scores = new[] { topScoreInfo });
AddStep("remove scores", () => scoresContainer.Scores = null);
AddStep("resize to big", () => container.ResizeWidthTo(1, 300));
AddStep("resize to normal", () => container.ResizeWidthTo(0.8f, 300));
AddStep("online scores", () => scoresContainer.Beatmap = new BeatmapInfo { OnlineBeatmapID = 75, Ruleset = new OsuRuleset().RulesetInfo });
scores = new[]
{ {
new APIScoreInfo new APIScoreInfo
{ {
@ -160,14 +149,15 @@ namespace osu.Game.Tests.Visual
Accuracy = 0.6543, Accuracy = 0.6543,
}, },
}; };
foreach(var s in scores)
foreach (var s in scores)
{ {
s.Statistics.Add(HitResult.Great, RNG.Next(2000)); s.Statistics.Add(HitResult.Great, RNG.Next(2000));
s.Statistics.Add(HitResult.Good, RNG.Next(2000)); s.Statistics.Add(HitResult.Good, RNG.Next(2000));
s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); s.Statistics.Add(HitResult.Meh, RNG.Next(2000));
} }
anotherScores = new[] IEnumerable<APIScoreInfo> anotherScores = new[]
{ {
new APIScoreInfo new APIScoreInfo
{ {
@ -279,7 +269,7 @@ namespace osu.Game.Tests.Visual
s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); s.Statistics.Add(HitResult.Meh, RNG.Next(2000));
} }
topScoreInfo = new APIScoreInfo var topScoreInfo = new APIScoreInfo
{ {
User = new User User = new User
{ {
@ -304,6 +294,14 @@ namespace osu.Game.Tests.Visual
topScoreInfo.Statistics.Add(HitResult.Great, RNG.Next(2000)); topScoreInfo.Statistics.Add(HitResult.Great, RNG.Next(2000));
topScoreInfo.Statistics.Add(HitResult.Good, RNG.Next(2000)); topScoreInfo.Statistics.Add(HitResult.Good, RNG.Next(2000));
topScoreInfo.Statistics.Add(HitResult.Meh, RNG.Next(2000)); topScoreInfo.Statistics.Add(HitResult.Meh, RNG.Next(2000));
AddStep("scores pack 1", () => scoresContainer.Scores = scores);
AddStep("scores pack 2", () => scoresContainer.Scores = anotherScores);
AddStep("only top score", () => scoresContainer.Scores = new[] { topScoreInfo });
AddStep("remove scores", () => scoresContainer.Scores = null);
AddStep("resize to big", () => container.ResizeWidthTo(1, 300));
AddStep("resize to normal", () => container.ResizeWidthTo(0.8f, 300));
AddStep("online scores", () => scoresContainer.Beatmap = new BeatmapInfo { OnlineBeatmapID = 75, Ruleset = new OsuRuleset().RulesetInfo });
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]

View File

@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual
var chatManager = new ChannelManager(); var chatManager = new ChannelManager();
BindableList<Channel> availableChannels = (BindableList<Channel>)chatManager.AvailableChannels; BindableList<Channel> availableChannels = (BindableList<Channel>)chatManager.AvailableChannels;
availableChannels.Add(new Channel { Name = "#english"}); availableChannels.Add(new Channel { Name = "#english" });
availableChannels.Add(new Channel { Name = "#japanese" }); availableChannels.Add(new Channel { Name = "#japanese" });
Dependencies.Cache(chatManager); Dependencies.Cache(chatManager);

View File

@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual
{ {
TimingPoints = TimingPoints =
{ {
new TimingControlPoint { Time = 0, BeatLength = 200}, new TimingControlPoint { Time = 0, BeatLength = 200 },
new TimingControlPoint { Time = 100, BeatLength = 400 }, new TimingControlPoint { Time = 100, BeatLength = 400 },
new TimingControlPoint { Time = 175, BeatLength = 800 }, new TimingControlPoint { Time = 175, BeatLength = 800 },
new TimingControlPoint { Time = 350, BeatLength = 200 }, new TimingControlPoint { Time = 350, BeatLength = 200 },

View File

@ -265,6 +265,7 @@ namespace osu.Game.Tests.Visual
pauseOverlay.OnRetry = lastAction; pauseOverlay.OnRetry = lastAction;
lastAction = null; lastAction = null;
} }
return triggered; return triggered;
}); });
AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden); AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden);

View File

@ -20,7 +20,8 @@ namespace osu.Game.Tests.Visual
[Description("PlaySongSelect leaderboard")] [Description("PlaySongSelect leaderboard")]
public class TestCaseLeaderboard : OsuTestCase public class TestCaseLeaderboard : OsuTestCase
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] { public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Placeholder), typeof(Placeholder),
typeof(MessagePlaceholder), typeof(MessagePlaceholder),
typeof(RetrievalFailurePlaceholder), typeof(RetrievalFailurePlaceholder),

View File

@ -4,10 +4,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Screens; using osu.Framework.Platform;
using osu.Game.Screens;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
using osuTK.Graphics; using osuTK.Graphics;
@ -21,8 +21,12 @@ namespace osu.Game.Tests.Visual
typeof(OsuLogo), typeof(OsuLogo),
}; };
public TestCaseOsuGame() [BackgroundDependencyLoader]
private void load(GameHost host)
{ {
OsuGame game = new OsuGame();
game.SetHost(host);
Children = new Drawable[] Children = new Drawable[]
{ {
new Box new Box
@ -30,10 +34,7 @@ namespace osu.Game.Tests.Visual
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.Black, Colour = Color4.Black,
}, },
new ScreenStack(new Loader()) game
{
RelativeSizeAxes = Axes.Both,
}
}; };
} }
} }

View File

@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Size = new Vector2(200,100) Size = new Vector2(200, 100)
}; };
Beatmap.Value = new TestWorkingBeatmap(new Beatmap(), Clock); Beatmap.Value = new TestWorkingBeatmap(new Beatmap(), Clock);

View File

@ -6,6 +6,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Screens;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
@ -13,15 +14,22 @@ namespace osu.Game.Tests.Visual
public class TestCasePlayerLoader : ManualInputManagerTestCase public class TestCasePlayerLoader : ManualInputManagerTestCase
{ {
private PlayerLoader loader; private PlayerLoader loader;
private ScreenStack stack; private readonly ScreenStack stack;
[Cached]
private BackgroundScreenStack backgroundStack;
public TestCasePlayerLoader()
{
InputManager.Add(backgroundStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both });
InputManager.Add(stack = new ScreenStack { RelativeSizeAxes = Axes.Both });
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuGameBase game) private void load(OsuGameBase game)
{ {
Beatmap.Value = new DummyWorkingBeatmap(game); Beatmap.Value = new DummyWorkingBeatmap(game);
InputManager.Add(stack = new ScreenStack { RelativeSizeAxes = Axes.Both });
AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => new Player AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => new Player
{ {
AllowPause = false, AllowPause = false,

View File

@ -0,0 +1,153 @@
// 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 NUnit.Framework;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Graphics;
using osu.Game.Skinning;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual
{
public class TestCaseSkinReloadable : OsuTestCase
{
[Test]
public void TestInitialLoad()
{
var secondarySource = new SecondarySource();
SkinConsumer consumer = null;
AddStep("setup layout", () =>
{
Child = new SkinSourceContainer
{
RelativeSizeAxes = Axes.Both,
Child = new LocalSkinOverrideContainer(secondarySource)
{
RelativeSizeAxes = Axes.Both,
Child = consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)
}
};
});
AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
}
[Test]
public void TestOverride()
{
var secondarySource = new SecondarySource();
SkinConsumer consumer = null;
Container target = null;
AddStep("setup layout", () =>
{
Child = new SkinSourceContainer
{
RelativeSizeAxes = Axes.Both,
Child = target = new LocalSkinOverrideContainer(secondarySource)
{
RelativeSizeAxes = Axes.Both,
}
};
});
AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)));
AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
}
private class NamedBox : Container
{
public NamedBox(string name)
{
Children = new Drawable[]
{
new Box
{
Colour = Color4.Black,
RelativeSizeAxes = Axes.Both,
},
new SpriteText
{
Font = OsuFont.Default.With(size: 40),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = name
}
};
}
}
private class SkinConsumer : SkinnableDrawable
{
public new Drawable Drawable => base.Drawable;
public int SkinChangedCount { get; private set; }
public SkinConsumer(string name, Func<string, Drawable> defaultImplementation, Func<ISkinSource, bool> allowFallback = null, bool restrictSize = true)
: base(name, defaultImplementation, allowFallback, restrictSize)
{
}
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
SkinChangedCount++;
}
}
private class BaseSourceBox : NamedBox
{
public BaseSourceBox()
: base("Base Source")
{
}
}
private class SecondarySourceBox : NamedBox
{
public SecondarySourceBox()
: base("Secondary Source")
{
}
}
private class SecondarySource : ISkinSource
{
public event Action SourceChanged;
public void TriggerSourceChanged() => SourceChanged?.Invoke();
public Drawable GetDrawableComponent(string componentName) => new SecondarySourceBox();
public Texture GetTexture(string componentName) => throw new NotImplementedException();
public SampleChannel GetSample(string sampleName) => throw new NotImplementedException();
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
}
private class SkinSourceContainer : Container, ISkinSource
{
public event Action SourceChanged;
public void TriggerSourceChanged() => SourceChanged?.Invoke();
public Drawable GetDrawableComponent(string componentName) => new BaseSourceBox();
public Texture GetTexture(string componentName) => throw new NotImplementedException();
public SampleChannel GetSample(string sampleName) => throw new NotImplementedException();
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
}
}
}

View File

@ -15,7 +15,7 @@ namespace osu.Game.Tests.Visual
public class TestCaseSongProgress : OsuTestCase public class TestCaseSongProgress : OsuTestCase
{ {
private readonly SongProgress progress; private readonly SongProgress progress;
private readonly SongProgressGraph graph; private readonly TestSongProgressGraph graph;
private readonly StopwatchClock clock; private readonly StopwatchClock clock;
@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
}); });
Add(graph = new SongProgressGraph Add(graph = new TestSongProgressGraph
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = 200, Height = 200,
@ -39,13 +39,24 @@ namespace osu.Game.Tests.Visual
Origin = Anchor.TopLeft, Origin = Anchor.TopLeft,
}); });
AddWaitStep(5);
AddAssert("ensure not created", () => graph.CreationCount == 0);
AddStep("display values", displayNewValues);
AddWaitStep(5);
AddUntilStep(() => graph.CreationCount == 1, "wait for creation count");
AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking); AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking);
AddWaitStep(5); AddWaitStep(5);
AddUntilStep(() => graph.CreationCount == 1, "wait for creation count");
AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking); AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking);
AddWaitStep(2); AddWaitStep(5);
AddUntilStep(() => graph.CreationCount == 1, "wait for creation count");
AddRepeatStep("New Values", displayNewValues, 5); AddRepeatStep("New Values", displayNewValues, 5);
displayNewValues(); AddWaitStep(5);
AddAssert("ensure debounced", () => graph.CreationCount == 2);
} }
private void displayNewValues() private void displayNewValues()
@ -60,5 +71,16 @@ namespace osu.Game.Tests.Visual
progress.AudioClock = clock; progress.AudioClock = clock;
progress.OnSeek = pos => clock.Seek(pos); progress.OnSeek = pos => clock.Seek(pos);
} }
private class TestSongProgressGraph : SongProgressGraph
{
public int CreationCount { get; private set; }
protected override void RecreateGraph()
{
base.RecreateGraph();
CreationCount++;
}
}
} }
} }

View File

@ -50,7 +50,10 @@ namespace osu.Game.Tests.Visual
}); });
AddStep("Restart", restart); AddStep("Restart", restart);
AddToggleStep("Passing", passing => { if (storyboard != null) storyboard.Passing = passing; }); AddToggleStep("Passing", passing =>
{
if (storyboard != null) storyboard.Passing = passing;
});
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]

View File

@ -28,7 +28,7 @@ namespace osu.Game.Audio
private void load() private void load()
{ {
track = GetTrack(); track = GetTrack();
track.Completed += Stop; track.Completed += () => Schedule(Stop);
} }
/// <summary> /// <summary>
@ -63,6 +63,7 @@ namespace osu.Game.Audio
if (hasStarted) if (hasStarted)
return; return;
hasStarted = true; hasStarted = true;
track.Restart(); track.Restart();
@ -81,6 +82,7 @@ namespace osu.Game.Audio
if (!hasStarted) if (!hasStarted)
return; return;
hasStarted = false; hasStarted = false;
track.Stop(); track.Stop();

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