1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-14 23:22:55 +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/**
build/tools/**
fastlane/report.xml
# Build results
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.
# 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.
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.
- 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.
# 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.
@ -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.
## Downloading the source code
### Downloading the source code
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
```
## 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.
@ -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`.
### 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`.
@ -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
```
## 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.
## 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.
# 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.
@ -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.
# 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.

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)
{
base.SetHost(host);
var desktopWindow = host.Window as DesktopGameWindow;
if (desktopWindow != null)
if (host.Window is DesktopGameWindow desktopWindow)
{
desktopWindow.CursorState |= CursorState.Hidden;

View File

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

View File

@ -8,7 +8,8 @@ namespace osu.Game.Rulesets.Catch.Tests
[TestFixture]
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.
// See the LICENCE file in the repository root for full licence text.
using System;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Screens.Play;
namespace osu.Game.Rulesets.Catch.Tests
{
@ -17,13 +19,30 @@ namespace osu.Game.Rulesets.Catch.Tests
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++)
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;
}
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 colour
}
break;
case JuiceStream juiceStream:
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
hitObject.X = MathHelper.Clamp(hitObject.X, 0, 1);
}
break;
}
}
@ -98,7 +100,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
CatchHitObject nextObject = objectWithDroplets[i + 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);
float distanceToHyper = (float)(timeToNext * CatcherArea.Catcher.BASE_SPEED - distanceToNext);
if (distanceToHyper < 0)

View File

@ -19,8 +19,10 @@ namespace osu.Game.Rulesets.Catch
{
[Description("Move left")]
MoveLeft,
[Description("Move right")]
MoveRight,
[Description("Engage 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.
case Fruit fruit:
yield return new CatchDifficultyHitObject(fruit, lastObject, clockRate, halfCatchWidth);
lastObject = hitObject;
break;
case JuiceStream _:
foreach (var nested in hitObject.NestedHitObjects.OfType<CatchHitObject>().Where(o => !(o is TinyDroplet)))
{
yield return new CatchDifficultyHitObject(nested, lastObject, clockRate, halfCatchWidth);
lastObject = nested;
}
break;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -40,29 +40,29 @@ namespace osu.Game.Rulesets.Mania.Tests
protected override Ruleset CreateRuleset() => new ManiaRuleset();
}
public class ManiaConvertMapping : ConvertMapping<ConvertValue>, IEquatable<ManiaConvertMapping>
{
public uint RandomW;
public uint RandomX;
public uint RandomY;
public uint RandomZ;
public class ManiaConvertMapping : ConvertMapping<ConvertValue>, IEquatable<ManiaConvertMapping>
{
public uint RandomW;
public uint RandomX;
public uint RandomY;
public uint RandomZ;
public ManiaConvertMapping()
{
}
public ManiaConvertMapping()
{
}
public ManiaConvertMapping(IBeatmapConverter converter)
{
var maniaConverter = (ManiaBeatmapConverter)converter;
RandomW = maniaConverter.Random.W;
RandomX = maniaConverter.Random.X;
RandomY = maniaConverter.Random.Y;
RandomZ = maniaConverter.Random.Z;
}
public ManiaConvertMapping(IBeatmapConverter converter)
{
var maniaConverter = (ManiaBeatmapConverter)converter;
RandomW = maniaConverter.Random.W;
RandomX = maniaConverter.Random.X;
RandomY = maniaConverter.Random.Y;
RandomZ = maniaConverter.Random.Z;
}
public bool Equals(ManiaConvertMapping other) => other != null && RandomW == other.RandomW && RandomX == other.RandomX && RandomY == other.RandomY && RandomZ == other.RandomZ;
public override bool Equals(ConvertMapping<ConvertValue> other) => base.Equals(other) && Equals(other as ManiaConvertMapping);
}
public bool Equals(ManiaConvertMapping other) => other != null && RandomW == other.RandomW && RandomX == other.RandomX && RandomY == other.RandomY && RandomZ == other.RandomZ;
public override bool Equals(ConvertMapping<ConvertValue> other) => base.Equals(other) && Equals(other as ManiaConvertMapping);
}
public struct ConvertValue : IEquatable<ConvertValue>
{

View File

@ -25,8 +25,8 @@ namespace osu.Game.Rulesets.Mania.Tests
[BackgroundDependencyLoader]
private void load(RulesetConfigCache configCache)
{
var config = (ManiaConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
config.BindWith(ManiaSetting.ScrollDirection, direction);
var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
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) };
public int TargetColumns;
public bool Dual;
public readonly bool IsForCurrentRuleset;
// Internal for testing purposes
@ -45,7 +46,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
var roundedOverallDifficulty = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
if (IsForCurrentRuleset)
{
TargetColumns = (int)Math.Max(1, roundedCircleSize);
if (TargetColumns >= 10)
{
TargetColumns = TargetColumns / 2;
Dual = true;
}
}
else
{
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);
}
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)
{
var maniaOriginal = original as ManiaHitObject;
if (maniaOriginal != null)
if (original is ManiaHitObject maniaOriginal)
{
yield return maniaOriginal;
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 double density = int.MaxValue;
private void computeDensity(double newNoteTime)
{
if (prevNoteTimes.Count == max_notes_for_density)
@ -104,6 +121,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
private double lastTime;
private Vector2 lastPosition;
private PatternType lastStair = PatternType.Stair;
private void recordNote(double time, Vector2 position)
{
lastTime = time;

View File

@ -65,6 +65,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (originalPattern.HitObjects.Count() == 1)
{
yield return originalPattern;
yield break;
}
@ -135,6 +136,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
if (convertType.HasFlag(PatternType.LowProbability))
return generateNRandomNotes(HitObject.StartTime, 0.78, 0.3, 0);
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))
return generateNRandomNotes(HitObject.StartTime, 0.43, 0.08, 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))
return generateNRandomNotes(HitObject.StartTime, 0.3, 0, 0);
return generateNRandomNotes(HitObject.StartTime, 0.37, 0.08, 0);
}
if (convertType.HasFlag(PatternType.LowProbability))
return generateNRandomNotes(HitObject.StartTime, 0.17, 0, 0);
return generateNRandomNotes(HitObject.StartTime, 0.27, 0, 0);
}

View File

@ -116,10 +116,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
}
if (convertType.HasFlag(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1
// If we convert to 7K + 1, let's not overload the special key
&& (TotalColumns != 8 || lastColumn != 0)
// Make sure the last column was not the centre column
&& (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2))
// If we convert to 7K + 1, let's not overload the special key
&& (TotalColumns != 8 || lastColumn != 0)
// Make sure the last column was not the centre column
&& (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2))
{
// Generate a new pattern by cycling backwards (similar to Reverse but for only one hit object)
int column = RandomStart + TotalColumns - lastColumn - 1;
@ -172,6 +172,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
return pattern = generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
if (ConversionDifficulty > 4)
return pattern = generateRandomPatternWithMirrored(0.12, 0.17, 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))
return pattern = generateRandomPattern(0.78, 0.42, 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))
return pattern = generateRandomPattern(0.35, 0.08, 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))
return pattern = generateRandomPattern(0.18, 0, 0, 0);
return pattern = generateRandomPattern(0.45, 0, 0, 0);
}
@ -250,6 +254,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
}
else
last = GetRandomColumn();
return last;
}
}

View File

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

View File

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

View File

@ -8,9 +8,9 @@ using osu.Game.Rulesets.Mania.UI;
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)
{
}
@ -19,17 +19,17 @@ namespace osu.Game.Rulesets.Mania.Configuration
{
base.InitialiseDefaults();
Set(ManiaSetting.ScrollTime, 2250.0, 50.0, 10000.0, 50.0);
Set(ManiaSetting.ScrollDirection, ManiaScrollingDirection.Down);
Set(ManiaRulesetSetting.ScrollTime, 2250.0, 50.0, 10000.0, 50.0);
Set(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down);
}
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,
ScrollDirection

View File

@ -114,8 +114,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty
// Lots of arbitrary values from testing.
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
double accuracyValue = Math.Max(0.0, 0.2 - (Attributes.GreatHitWindow - 34) * 0.006667)
* strainValue
* Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1);
* strainValue
* Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1);
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer
// accuracyValue *= Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));

View File

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

View File

@ -162,7 +162,7 @@ namespace osu.Game.Rulesets.Mania
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);
@ -349,6 +349,7 @@ namespace osu.Game.Rulesets.Mania
/// Number of columns in this stage lies at (item - Single).
/// </summary>
Single = 0,
/// <summary>
/// Columns are grouped into two stages.
/// 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]
private void load()
{
var config = (ManiaConfigManager)Config;
var config = (ManiaRulesetConfigManager)Config;
Children = new Drawable[]
{
new SettingsEnumDropdown<ManiaScrollingDirection>
{
LabelText = "Scrolling direction",
Bindable = config.GetBindable<ManiaScrollingDirection>(ManiaSetting.ScrollDirection)
Bindable = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection)
},
new SettingsSlider<double, TimeSlider>
{
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>
public uint NextUInt()
{
uint t = X ^ X << 11;
uint t = X ^ (X << 11);
X = Y;
Y = Z;
Z = W;
return W = W ^ W >> 19 ^ t ^ t >> 8;
return W = W ^ (W >> 19) ^ t ^ (t >> 8);
}
/// <summary>

View File

@ -1,15 +1,13 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.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 Acronym => "DS";
@ -29,24 +27,7 @@ namespace osu.Game.Rulesets.Mania.Mods
if (isForCurrentRuleset)
return;
mbc.TargetColumns *= 2;
}
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;
mbc.Dual = true;
}
public PlayfieldType PlayfieldType => PlayfieldType.Dual;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,9 +17,10 @@ namespace osu.Game.Rulesets.Mania.Objects
public double EndTime => StartTime + Duration;
private double duration;
public double Duration
{
get { return duration; }
get => duration;
set
{
duration = value;
@ -29,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Objects
public override double StartTime
{
get { return base.StartTime; }
get => base.StartTime;
set
{
base.StartTime = value;
@ -40,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.Objects
public override int Column
{
get { return base.Column; }
get => base.Column;
set
{
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)>
{
{ HitResult.Perfect, (44.8, 38.8, 27.8) },
{ HitResult.Great, (128, 98, 68 ) },
{ HitResult.Great, (128, 98, 68) },
{ HitResult.Good, (194, 164, 134) },
{ HitResult.Ok, (254, 224, 194) },
{ HitResult.Meh, (302, 272, 242) },

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,6 @@ using osu.Game.Input.Handlers;
using osu.Game.Replays;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Replays;
@ -35,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.UI
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>();
@ -75,10 +74,10 @@ namespace osu.Game.Rulesets.Mania.UI
{
BarLines.ForEach(Playfield.Add);
Config.BindWith(ManiaSetting.ScrollDirection, configDirection);
Config.BindWith(ManiaRulesetSetting.ScrollDirection, configDirection);
configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true);
Config.BindWith(ManiaSetting.ScrollTime, TimeRange);
Config.BindWith(ManiaRulesetSetting.ScrollTime, TimeRange);
}
/// <summary>
@ -96,7 +95,7 @@ namespace osu.Game.Rulesets.Mania.UI
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);

View File

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

View File

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

View File

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

View File

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

View File

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

@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Longer maps are worth more
double lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f);
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f);
aimValue *= lengthBonus;
@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
approachRateFactor += 0.3f * (Attributes.ApproachRate - 10.33f);
else if (Attributes.ApproachRate < 8.0f)
{
approachRateFactor += 0.01f * (8.0f - Attributes.ApproachRate);
approachRateFactor += 0.01f * (8.0f - Attributes.ApproachRate);
}
aimValue *= approachRateFactor;
@ -126,8 +126,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
// Apply object-based bonus for flashlight.
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 > 500 ? (totalHits - 500) / 1200.0f : 0.0f) : 0.0f);
(totalHits > 200
? 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_
@ -144,7 +146,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Longer maps are worth more
speedValue *= 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f);
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f);
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
speedValue *= Math.Pow(0.97f, countMiss);

View File

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

View File

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

View File

@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
foreach (var drawable in drawables)
{
var hitObject = (OsuHitObject) drawable.HitObject;
var hitObject = (OsuHitObject)drawable.HitObject;
float appearDistance = (float)(hitObject.TimePreempt - hitObject.TimeFadeIn) / 2;
@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Mods
.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>
/// Connects hit objects visually, for example with follow points.
/// </summary>
public abstract class ConnectionRenderer<T> : Container
public abstract class ConnectionRenderer<T> : LifetimeManagementContainer
where T : HitObject
{
/// <summary>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
/// </summary>
public Color4 AccentColour
{
get { return accentColour; }
get => accentColour;
set
{
accentColour = value;
@ -136,11 +136,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public bool Tracking
{
get { return tracking; }
get => tracking;
private set
{
if (value == tracking)
return;
tracking = value;
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)
return;
path.AccentColour = value;
container.ForceRedraw();
@ -56,6 +57,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
if (path.BorderColour == value)
return;
path.BorderColour = value;
container.ForceRedraw();
@ -105,6 +107,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
if (borderColour == value)
return;
borderColour = value;
InvalidateTexture();
@ -120,6 +123,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
if (accentColour == value)
return;
accentColour = value;
InvalidateTexture();

View File

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

View File

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

View File

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

View File

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

View File

@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Osu
protected override bool Handle(UIEvent e)
{
if (!AllowUserPresses) return false;
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.Replays.Types;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Configuration;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Difficulty;
using osu.Game.Scoring;
@ -121,7 +124,8 @@ namespace osu.Game.Rulesets.Osu
new OsuModAutopilot(),
};
case ModType.Fun:
return new Mod[] {
return new Mod[]
{
new OsuModTransform(),
new OsuModWiggle(),
new OsuModGrow()
@ -143,12 +147,14 @@ namespace osu.Game.Rulesets.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 IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame();
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo);
public OsuRuleset(RulesetInfo rulesetInfo = null)
: base(rulesetInfo)
{

View File

@ -209,7 +209,7 @@ namespace osu.Game.Rulesets.Osu.Replays
// Only "snap" to hitcircles if they are far enough apart. As the time between hitcircles gets shorter the snapping threshold goes up.
if (timeDifference > 0 && // Sanity checks
((lastPosition - targetPos).Length > h.Radius * (1.5 + 100.0 / timeDifference) || // Either the distance is big enough
timeDifference >= 266)) // ... or the beats are slow enough to tap anyway.
timeDifference >= 266)) // ... or the beats are slow enough to tap anyway.
{
// Perform eased movement
for (double time = lastFrame.Time + FrameDelay; time < h.StartTime; time += FrameDelay)

View File

@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Osu.Replays
/// Constants (for spinners).
/// </summary>
protected static readonly Vector2 SPINNER_CENTRE = OsuPlayfield.BASE_SIZE / 2;
protected const float SPIN_RADIUS = 50;
/// <summary>
@ -46,6 +47,7 @@ namespace osu.Game.Rulesets.Osu.Replays
#endregion
#region Utilities
protected double ApplyModsToTime(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)]
public Vector2 Position;
[VertexMember(4, VertexAttribPointerType.Float)]
public Color4 Colour;
[VertexMember(2, VertexAttribPointerType.Float)]
public Vector2 TexturePosition;
[VertexMember(1, VertexAttribPointerType.Float)]
public float Time;

View File

@ -213,6 +213,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
public void Expand()
{
if (!cursorExpand) return;
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
{
private readonly Container approachCircles;
private readonly ApproachCircleProxyContainer approachCircles;
private readonly JudgementContainer<DrawableOsuJudgement> judgementLayer;
private readonly ConnectionRenderer<OsuHitObject> connectionLayer;
@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.UI
Depth = 1,
},
HitObjectContainer,
approachCircles = new Container
approachCircles = new ApproachCircleProxyContainer
{
RelativeSizeAxes = Axes.Both,
Depth = -1,
@ -58,13 +58,26 @@ namespace osu.Game.Rulesets.Osu.UI
{
h.OnNewResult += onNewResult;
var c = h as IDrawableHitObjectWithProxiedApproach;
if (c != null)
approachCircles.Add(c.ProxiedLayer.CreateProxy());
if (h is IDrawableHitObjectWithProxiedApproach c)
{
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);
}
private void addApproachCircleProxy(Drawable d)
{
var proxy = d.CreateProxy();
proxy.LifetimeStart = d.LifetimeStart;
proxy.LifetimeEnd = d.LifetimeEnd;
approachCircles.Add(proxy);
}
public override void PostProcess()
{
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);
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.Replays;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Replays;
@ -20,6 +21,8 @@ namespace osu.Game.Rulesets.Osu.UI
{
public class OsuRulesetContainer : RulesetContainer<OsuPlayfield, OsuHitObject>
{
protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config;
public OsuRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -19,7 +19,10 @@ namespace osu.Game.Rulesets.Taiko.Objects
/// </summary>
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()
{

View File

@ -19,10 +19,13 @@ namespace osu.Game.Rulesets.Taiko
{
[Description("Left (rim)")]
LeftRim,
[Description("Left (centre)")]
LeftCentre,
[Description("Right (centre)")]
RightCentre,
[Description("Right (rim)")]
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 TimingControlPoint getNextTimingPoint(TimingControlPoint current)
{
if (timingPoints[timingPoints.Count - 1] == current)
@ -190,7 +191,7 @@ namespace osu.Game.Tests.Visual
public double Value
{
set { valueText.Text = $"{value:G}"; }
set => valueText.Text = $"{value:G}";
}
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);
if (currentlySelected == null)
return true;
return currentlySelected.Item.Visible;
}
@ -166,8 +167,7 @@ namespace osu.Game.Tests.Visual
carousel.Filter(new FilterCriteria { SearchText = "Dingo" }, false);
carousel.Filter(new FilterCriteria(), false);
eagerSelectedIDs.Add(carousel.SelectedBeatmapSet.ID);
}
);
});
}
/// <summary>
@ -522,6 +522,7 @@ namespace osu.Game.Tests.Visual
}
});
}
return toReturn;
}

View File

@ -23,9 +23,6 @@ namespace osu.Game.Tests.Visual
[System.ComponentModel.Description("in BeatmapOverlay")]
public class TestCaseBeatmapScoresContainer : OsuTestCase
{
private readonly IEnumerable<APIScoreInfo> scores;
private readonly IEnumerable<APIScoreInfo> anotherScores;
private readonly APIScoreInfo topScoreInfo;
private readonly Box background;
public TestCaseBeatmapScoresContainer()
@ -47,15 +44,7 @@ namespace osu.Game.Tests.Visual
}
};
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 });
scores = new[]
IEnumerable<APIScoreInfo> scores = new[]
{
new APIScoreInfo
{
@ -160,14 +149,15 @@ namespace osu.Game.Tests.Visual
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.Good, RNG.Next(2000));
s.Statistics.Add(HitResult.Meh, RNG.Next(2000));
}
anotherScores = new[]
IEnumerable<APIScoreInfo> anotherScores = new[]
{
new APIScoreInfo
{
@ -279,7 +269,7 @@ namespace osu.Game.Tests.Visual
s.Statistics.Add(HitResult.Meh, RNG.Next(2000));
}
topScoreInfo = new APIScoreInfo
var topScoreInfo = new APIScoreInfo
{
User = new User
{
@ -304,6 +294,14 @@ namespace osu.Game.Tests.Visual
topScoreInfo.Statistics.Add(HitResult.Great, RNG.Next(2000));
topScoreInfo.Statistics.Add(HitResult.Good, 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]

View File

@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual
var chatManager = new ChannelManager();
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" });
Dependencies.Cache(chatManager);

View File

@ -61,10 +61,10 @@ namespace osu.Game.Tests.Visual
// Move box along a square trajectory
container.Loop(c => c
.MoveTo(new Vector2(0, 100), duration).Then()
.MoveTo(new Vector2(100, 100), duration).Then()
.MoveTo(new Vector2(100, 0), duration).Then()
.MoveTo(Vector2.Zero, duration)
.MoveTo(new Vector2(0, 100), duration).Then()
.MoveTo(new Vector2(100, 100), duration).Then()
.MoveTo(new Vector2(100, 0), duration).Then()
.MoveTo(Vector2.Zero, duration)
);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Screens;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual
@ -13,15 +14,22 @@ namespace osu.Game.Tests.Visual
public class TestCasePlayerLoader : ManualInputManagerTestCase
{
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]
private void load(OsuGameBase 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
{
AllowPause = false,

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