1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 11:20:04 +08:00

Merge remote-tracking branch 'nekodex/master' into drawnode-changes

This commit is contained in:
smoogipoo 2019-03-07 18:15:12 +09:00
commit e430b8a640
107 changed files with 2424 additions and 605 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

@ -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

@ -100,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

@ -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,7 +78,15 @@ 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)
{

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

@ -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);

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

@ -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

@ -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

@ -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

@ -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 =>

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;
@ -144,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

@ -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

@ -333,6 +333,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual("hit_2.wav", getTestableSampleInfo(hitObjects[1]).LookupNames.First());
Assert.AreEqual("normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
Assert.AreEqual("hit_1.wav", getTestableSampleInfo(hitObjects[3]).LookupNames.First());
Assert.AreEqual(70, getTestableSampleInfo(hitObjects[3]).Volume);
}
SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);

View File

@ -101,7 +101,7 @@ namespace osu.Game.Tests.Beatmaps.IO
int fireCount = 0;
// ReSharper disable once AccessToModifiedClosure
manager.ItemAdded += (_, __, ___) => fireCount++;
manager.ItemAdded += (_, __) => fireCount++;
manager.ItemRemoved += _ => fireCount++;
var imported = LoadOszIntoOsu(osu);

View File

@ -13,4 +13,4 @@ SampleSet: Normal
255,193,2170,1,0,0:0:0:0:hit_1.wav
256,191,2638,5,0,0:0:0:0:hit_2.wav
255,193,3107,1,0,0:0:0:0:
256,191,3576,1,0,0:0:0:0:hit_1.wav
256,191,3576,1,0,0:0:0:70:hit_1.wav

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.CurrentPausableGameplayContainer.Pause());
waitForDim();
AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed());
AddStep("Unpause", () => player.CurrentPausableGameplayContainer.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 PausableGameplayContainer CurrentPausableGameplayContainer => PausableGameplayContainer;
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

@ -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
{
@ -168,7 +157,7 @@ namespace osu.Game.Tests.Visual
s.Statistics.Add(HitResult.Meh, RNG.Next(2000));
}
anotherScores = new[]
IEnumerable<APIScoreInfo> anotherScores = new[]
{
new APIScoreInfo
{
@ -280,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
{
@ -305,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

@ -0,0 +1,47 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays.Direct;
using osu.Game.Rulesets.Osu;
using osu.Game.Tests.Beatmaps;
using osuTK;
namespace osu.Game.Tests.Visual
{
public class TestCaseDirectPanel : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(DirectGridPanel),
typeof(DirectListPanel),
typeof(IconPill)
};
[BackgroundDependencyLoader]
private void load()
{
var beatmap = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo, null);
beatmap.BeatmapSetInfo.OnlineInfo.HasVideo = true;
beatmap.BeatmapSetInfo.OnlineInfo.HasStoryboard = true;
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Padding = new MarginPadding(20),
Spacing = new Vector2(0, 20),
Children = new Drawable[]
{
new DirectGridPanel(beatmap.BeatmapSetInfo),
new DirectListPanel(beatmap.BeatmapSetInfo)
}
};
}
}
}

View File

@ -17,15 +17,15 @@ namespace osu.Game.Tests.Visual
[Description("player pause/fail screens")]
public class TestCaseGameplayMenuOverlay : ManualInputManagerTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseContainer) };
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(FailOverlay), typeof(PausableGameplayContainer) };
private FailOverlay failOverlay;
private PauseContainer.PauseOverlay pauseOverlay;
private PausableGameplayContainer.PauseOverlay pauseOverlay;
[BackgroundDependencyLoader]
private void load()
{
Add(pauseOverlay = new PauseContainer.PauseOverlay
Add(pauseOverlay = new PausableGameplayContainer.PauseOverlay
{
OnResume = () => Logger.Log(@"Resume"),
OnRetry = () => Logger.Log(@"Retry"),

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,

View File

@ -3,6 +3,7 @@
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.MathUtils;
using osu.Framework.Timing;
@ -19,14 +20,20 @@ namespace osu.Game.Tests.Visual
private readonly StopwatchClock clock;
[Cached]
private readonly GameplayClock gameplayClock;
private readonly FramedClock framedClock;
public TestCaseSongProgress()
{
clock = new StopwatchClock(true);
gameplayClock = new GameplayClock(framedClock = new FramedClock(clock));
Add(progress = new SongProgress
{
RelativeSizeAxes = Axes.X,
AudioClock = new StopwatchClock(true),
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
});
@ -68,8 +75,13 @@ namespace osu.Game.Tests.Visual
progress.Objects = objects;
graph.Objects = objects;
progress.AudioClock = clock;
progress.OnSeek = pos => clock.Seek(pos);
progress.RequestSeek = pos => clock.Seek(pos);
}
protected override void Update()
{
base.Update();
framedClock.ProcessFrame();
}
private class TestSongProgressGraph : SongProgressGraph

View File

@ -11,7 +11,6 @@ using Microsoft.EntityFrameworkCore;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics.Textures;
using osu.Framework.Logging;
using osu.Framework.Platform;
@ -50,11 +49,6 @@ namespace osu.Game.Beatmaps
/// </summary>
public event Action<DownloadBeatmapSetRequest> BeatmapDownloadFailed;
/// <summary>
/// Fired when a beatmap load is requested (into the interactive game UI).
/// </summary>
public Action<BeatmapSetInfo> PresentBeatmap;
/// <summary>
/// A default representation of a WorkingBeatmap to use when no beatmap is available.
/// </summary>
@ -151,8 +145,7 @@ namespace osu.Game.Beatmaps
var downloadNotification = new DownloadNotification
{
CompletionText = $"Imported {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}!",
Text = $"Downloading {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}",
Text = $"Downloading {beatmapSetInfo}",
};
var request = new DownloadBeatmapSetRequest(beatmapSetInfo, noVideo);
@ -165,20 +158,10 @@ namespace osu.Game.Beatmaps
request.Success += filename =>
{
downloadNotification.Text = $"Importing {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}";
Task.Factory.StartNew(() =>
{
// This gets scheduled back to the update thread, but we want the import to run in the background.
var importedBeatmap = Import(filename);
downloadNotification.CompletionClickAction = () =>
{
PresentCompletedImport(importedBeatmap.Yield());
return true;
};
downloadNotification.State = ProgressNotificationState.Completed;
Import(downloadNotification, filename);
currentDownloads.Remove(request);
}, TaskCreationOptions.LongRunning);
};
@ -221,12 +204,6 @@ namespace osu.Game.Beatmaps
return true;
}
protected override void PresentCompletedImport(IEnumerable<BeatmapSetInfo> imported)
{
base.PresentCompletedImport(imported);
PresentBeatmap?.Invoke(imported.LastOrDefault());
}
/// <summary>
/// Get an existing download request if it exists.
/// </summary>

View File

@ -12,7 +12,7 @@ namespace osu.Game.Beatmaps
/// <summary>
/// Handles the storage and retrieval of Beatmaps/BeatmapSets to the database backing
/// </summary>
public class BeatmapStore : MutableDatabaseBackedStore<BeatmapSetInfo>
public class BeatmapStore : MutableDatabaseBackedStoreWithFileIncludes<BeatmapSetInfo, BeatmapSetFileInfo>
{
public event Action<BeatmapInfo> BeatmapHidden;
public event Action<BeatmapInfo> BeatmapRestored;
@ -64,18 +64,17 @@ namespace osu.Game.Beatmaps
protected override IQueryable<BeatmapSetInfo> AddIncludesForDeletion(IQueryable<BeatmapSetInfo> query) =>
base.AddIncludesForDeletion(query)
.Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
.Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
.Include(s => s.Metadata)
.Include(s => s.Beatmaps).ThenInclude(b => b.Scores);
.Include(s => s.Beatmaps).ThenInclude(b => b.Scores)
.Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
.Include(s => s.Beatmaps).ThenInclude(b => b.Metadata);
protected override IQueryable<BeatmapSetInfo> AddIncludesForConsumption(IQueryable<BeatmapSetInfo> query) =>
base.AddIncludesForConsumption(query)
.Include(s => s.Metadata)
.Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset)
.Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
.Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
.Include(s => s.Files).ThenInclude(f => f.FileInfo);
.Include(s => s.Beatmaps).ThenInclude(b => b.Metadata);
protected override void Purge(List<BeatmapSetInfo> items, OsuDbContext context)
{

View File

@ -48,11 +48,11 @@ namespace osu.Game.Beatmaps.Drawables
var rating = beatmap.StarDifficulty;
if (rating < 1.5) return DifficultyRating.Easy;
if (rating < 2.25) return DifficultyRating.Normal;
if (rating < 3.75) return DifficultyRating.Hard;
if (rating < 5.25) return DifficultyRating.Insane;
if (rating < 6.75) return DifficultyRating.Expert;
if (rating < 2.0) return DifficultyRating.Easy;
if (rating < 2.7) return DifficultyRating.Normal;
if (rating < 4.0) return DifficultyRating.Hard;
if (rating < 5.3) return DifficultyRating.Insane;
if (rating < 6.5) return DifficultyRating.Expert;
return DifficultyRating.ExpertPlus;
}

View File

@ -18,9 +18,12 @@ namespace osu.Game.Beatmaps.Drawables
[Resolved]
private BeatmapManager beatmaps { get; set; }
public UpdateableBeatmapBackgroundSprite()
private readonly BeatmapSetCoverType beatmapSetCoverType;
public UpdateableBeatmapBackgroundSprite(BeatmapSetCoverType beatmapSetCoverType = BeatmapSetCoverType.Cover)
{
Beatmap.BindValueChanged(b => Model = b.NewValue);
this.beatmapSetCoverType = beatmapSetCoverType;
}
protected override Drawable CreateDrawable(BeatmapInfo model)
@ -31,10 +34,18 @@ namespace osu.Game.Beatmaps.Drawables
var localBeatmap = beatmaps.GetWorkingBeatmap(model);
if (localBeatmap.BeatmapInfo.ID == 0 && model?.BeatmapSet?.OnlineInfo != null)
drawable = new BeatmapSetCover(model.BeatmapSet);
else
if (model?.BeatmapSet?.OnlineInfo != null)
drawable = new BeatmapSetCover(model.BeatmapSet, beatmapSetCoverType);
else if (localBeatmap.BeatmapInfo.ID != 0)
{
// Fall back to local background if one exists
drawable = new BeatmapBackgroundSprite(localBeatmap);
}
else
{
// Use the default background if somehow an online set does not exist and we don't have a local copy.
drawable = new BeatmapBackgroundSprite(beatmaps.DefaultBeatmap);
}
drawable.RelativeSizeAxes = Axes.Both;
drawable.Anchor = Anchor.Centre;

View File

@ -72,9 +72,6 @@ namespace osu.Game.Configuration
Set(OsuSetting.MenuParallax, true);
Set(OsuSetting.SnakingInSliders, true);
Set(OsuSetting.SnakingOutSliders, true);
// Gameplay
Set(OsuSetting.DimLevel, 0.3, 0, 1, 0.01);
Set(OsuSetting.BlurLevel, 0, 0, 1, 0.01);
@ -150,8 +147,6 @@ namespace osu.Game.Configuration
DisplayStarsMinimum,
DisplayStarsMaximum,
RandomSelectAlgorithm,
SnakingInSliders,
SnakingOutSliders,
ShowFpsDisplay,
ChatDisplayHeight,
Version,

View File

@ -8,6 +8,7 @@ using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using osu.Framework;
using osu.Framework.Extensions;
using osu.Framework.IO.File;
using osu.Framework.Logging;
@ -32,7 +33,7 @@ namespace osu.Game.Database
where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete
where TFileModel : INamedFileInfo, new()
{
public delegate void ItemAddedDelegate(TModel model, bool existing, bool silent);
public delegate void ItemAddedDelegate(TModel model, bool existing);
/// <summary>
/// Set an endpoint for notifications to be posted to.
@ -53,6 +54,8 @@ namespace osu.Game.Database
public virtual string[] HandledExtensions => new[] { ".zip" };
public virtual bool SupportsImportFromStable => RuntimeInfo.IsDesktop;
protected readonly FileStore Files;
protected readonly IDatabaseContextFactory ContextFactory;
@ -105,12 +108,12 @@ namespace osu.Game.Database
a.Invoke();
}
protected ArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, MutableDatabaseBackedStore<TModel> modelStore, IIpcHost importHost = null)
protected ArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, MutableDatabaseBackedStoreWithFileIncludes<TModel, TFileModel> modelStore, IIpcHost importHost = null)
{
ContextFactory = contextFactory;
ModelStore = modelStore;
ModelStore.ItemAdded += (item, silent) => handleEvent(() => ItemAdded?.Invoke(item, false, silent));
ModelStore.ItemAdded += item => handleEvent(() => ItemAdded?.Invoke(item, false));
ModelStore.ItemRemoved += s => handleEvent(() => ItemRemoved?.Invoke(s));
Files = new FileStore(contextFactory, storage);
@ -128,14 +131,18 @@ namespace osu.Game.Database
/// <param name="paths">One or more archive locations on disk.</param>
public void Import(params string[] paths)
{
var notification = new ProgressNotification
{
Text = "Import is initialising...",
Progress = 0,
State = ProgressNotificationState.Active,
};
var notification = new ProgressNotification { State = ProgressNotificationState.Active };
PostNotification?.Invoke(notification);
Import(notification, paths);
}
protected void Import(ProgressNotification notification, params string[] paths)
{
notification.Progress = 0;
notification.Text = "Import is initialising...";
var term = $"{typeof(TModel).Name.Replace("Info", "").ToLower()}";
List<TModel> imported = new List<TModel>();
@ -148,7 +155,18 @@ namespace osu.Game.Database
try
{
notification.Text = $"Importing ({++current} of {paths.Length})\n{Path.GetFileName(path)}";
var text = "Importing ";
if (path.Length > 1)
text += $"{++current} of {paths.Length} {term}s..";
else
text += $"{term}..";
// only show the filename if it isn't a temporary one (as those look ugly).
if (!path.Contains(Path.GetTempPath()))
text += $"\n{Path.GetFileName(path)}";
notification.Text = text;
imported.Add(Import(path));
@ -168,13 +186,20 @@ namespace osu.Game.Database
}
else
{
notification.CompletionText = $"Imported {current} {typeof(TModel).Name.Replace("Info", "").ToLower()}s!";
notification.CompletionClickAction += () =>
notification.CompletionText = imported.Count == 1
? $"Imported {imported.First()}!"
: $"Imported {current} {term}s!";
if (imported.Count > 0 && PresentImport != null)
{
if (imported.Count > 0)
PresentCompletedImport(imported);
return true;
};
notification.CompletionText += " Click to view.";
notification.CompletionClickAction = () =>
{
PresentImport?.Invoke(imported);
return true;
};
}
notification.State = ProgressNotificationState.Completed;
}
}
@ -207,9 +232,10 @@ namespace osu.Game.Database
return import;
}
protected virtual void PresentCompletedImport(IEnumerable<TModel> imported)
{
}
/// <summary>
/// Fired when the user requests to view the resulting import.
/// </summary>
public Action<IEnumerable<TModel>> PresentImport;
/// <summary>
/// Import an item from an <see cref="ArchiveReader"/>.
@ -225,7 +251,7 @@ namespace osu.Game.Database
model.Hash = computeHash(archive);
return Import(model, false, archive);
return Import(model, archive);
}
catch (Exception e)
{
@ -259,9 +285,8 @@ namespace osu.Game.Database
/// Import an item from a <see cref="TModel"/>.
/// </summary>
/// <param name="item">The model to be imported.</param>
/// <param name="silent">Whether the user should be notified fo the import.</param>
/// <param name="archive">An optional archive to use for model population.</param>
public TModel Import(TModel item, bool silent = false, ArchiveReader archive = null)
public TModel Import(TModel item, ArchiveReader archive = null)
{
delayEvents();
@ -281,7 +306,7 @@ namespace osu.Game.Database
{
Undelete(existing);
Logger.Log($"Found existing {typeof(TModel)} for {item} (ID {existing.ID}). Skipping import.", LoggingTarget.Database);
handleEvent(() => ItemAdded?.Invoke(existing, true, silent));
handleEvent(() => ItemAdded?.Invoke(existing, true));
return existing;
}
@ -291,7 +316,7 @@ namespace osu.Game.Database
Populate(item, archive);
// import to store
ModelStore.Add(item, silent);
ModelStore.Add(item);
}
catch (Exception e)
{

View File

@ -16,9 +16,7 @@ namespace osu.Game.Database
public abstract class MutableDatabaseBackedStore<T> : DatabaseBackedStore
where T : class, IHasPrimaryKey, ISoftDelete
{
public delegate void ItemAddedDelegate(T model, bool silent);
public event ItemAddedDelegate ItemAdded;
public event Action<T> ItemAdded;
public event Action<T> ItemRemoved;
protected MutableDatabaseBackedStore(IDatabaseContextFactory contextFactory, Storage storage = null)
@ -35,8 +33,7 @@ namespace osu.Game.Database
/// Add a <see cref="T"/> to the database.
/// </summary>
/// <param name="item">The item to add.</param>
/// <param name="silent">Whether the user should be notified of the addition.</param>
public void Add(T item, bool silent)
public void Add(T item)
{
using (var usage = ContextFactory.GetForWrite())
{
@ -44,7 +41,7 @@ namespace osu.Game.Database
context.Attach(item);
}
ItemAdded?.Invoke(item, silent);
ItemAdded?.Invoke(item);
}
/// <summary>
@ -57,7 +54,7 @@ namespace osu.Game.Database
usage.Context.Update(item);
ItemRemoved?.Invoke(item);
ItemAdded?.Invoke(item, true);
ItemAdded?.Invoke(item);
}
/// <summary>
@ -94,7 +91,7 @@ namespace osu.Game.Database
item.DeletePending = false;
}
ItemAdded?.Invoke(item, true);
ItemAdded?.Invoke(item);
return true;
}

View File

@ -0,0 +1,27 @@
// 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.Linq;
using Microsoft.EntityFrameworkCore;
using osu.Framework.Platform;
namespace osu.Game.Database
{
public abstract class MutableDatabaseBackedStoreWithFileIncludes<T, U> : MutableDatabaseBackedStore<T>
where T : class, IHasPrimaryKey, ISoftDelete, IHasFiles<U>
where U : INamedFileInfo
{
protected MutableDatabaseBackedStoreWithFileIncludes(IDatabaseContextFactory contextFactory, Storage storage = null)
: base(contextFactory, storage)
{
}
protected override IQueryable<T> AddIncludesForConsumption(IQueryable<T> query) =>
base.AddIncludesForConsumption(query)
.Include(s => s.Files).ThenInclude(f => f.FileInfo);
protected override IQueryable<T> AddIncludesForDeletion(IQueryable<T> query) =>
base.AddIncludesForDeletion(query)
.Include(s => s.Files); // don't include FileInfo. these are handled by the FileStore itself.
}
}

View File

@ -24,7 +24,17 @@ namespace osu.Game.Graphics.Containers
protected override bool BlockNonPositionalInput => true;
private PreviewTrackManager previewTrackManager;
/// <summary>
/// Temporary to allow for overlays in the main screen content to not dim theirselves.
/// Should be eventually replaced by dimming which is aware of the target dim container (traverse parent for certain interface type?).
/// </summary>
protected virtual bool DimMainContent => true;
[Resolved(CanBeNull = true)]
private OsuGame osuGame { get; set; }
[Resolved]
private PreviewTrackManager previewTrackManager { get; set; }
protected readonly Bindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All);
@ -36,10 +46,8 @@ namespace osu.Game.Graphics.Containers
}
[BackgroundDependencyLoader(true)]
private void load(OsuGame osuGame, AudioManager audio, PreviewTrackManager previewTrackManager)
private void load(AudioManager audio)
{
this.previewTrackManager = previewTrackManager;
if (osuGame != null)
OverlayActivationMode.BindTo(osuGame.OverlayActivationMode);
@ -93,6 +101,7 @@ namespace osu.Game.Graphics.Containers
if (OverlayActivationMode.Value != OverlayActivation.Disabled)
{
if (PlaySamplesOnStateChange) samplePopIn?.Play();
if (BlockScreenWideMouse && DimMainContent) osuGame?.AddBlockingOverlay(this);
}
else
State = Visibility.Hidden;
@ -100,6 +109,7 @@ namespace osu.Game.Graphics.Containers
break;
case Visibility.Hidden:
if (PlaySamplesOnStateChange) samplePopOut?.Play();
if (BlockScreenWideMouse) osuGame?.RemoveBlockingOverlay(this);
break;
}
}
@ -109,5 +119,11 @@ namespace osu.Game.Graphics.Containers
base.PopOut();
previewTrackManager.StopAnyPlaying(this);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
osuGame?.RemoveBlockingOverlay(this);
}
}
}

View File

@ -0,0 +1,88 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Configuration;
using osuTK.Graphics;
namespace osu.Game.Graphics.Containers
{
/// <summary>
/// A container that applies user-configured dim levels to its contents.
/// This container specifies behavior that applies to both Storyboards and Backgrounds.
/// </summary>
public class UserDimContainer : Container
{
private const float background_fade_duration = 800;
private Bindable<double> dimLevel { get; set; }
private Bindable<bool> showStoryboard { get; set; }
/// <summary>
/// Whether or not user-configured dim levels should be applied to the container.
/// </summary>
public readonly Bindable<bool> EnableUserDim = new Bindable<bool>();
/// <summary>
/// Whether or not the storyboard loaded should completely hide the background behind it.
/// </summary>
public readonly Bindable<bool> StoryboardReplacesBackground = new Bindable<bool>();
protected Container DimContainer { get; }
protected override Container<Drawable> Content => DimContainer;
private readonly bool isStoryboard;
/// <summary>
/// Creates a new <see cref="UserDimContainer"/>.
/// </summary>
/// <param name="isStoryboard"> Whether or not this instance of UserDimContainer contains a storyboard.
/// <remarks>
/// While both backgrounds and storyboards allow user dim levels to be applied, storyboards can be toggled via <see cref="showStoryboard"/>
/// and can cause backgrounds to become hidden via <see cref="StoryboardReplacesBackground"/>.
/// </remarks>
/// </param>
public UserDimContainer(bool isStoryboard = false)
{
this.isStoryboard = isStoryboard;
AddInternal(DimContainer = new Container { RelativeSizeAxes = Axes.Both });
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
dimLevel = config.GetBindable<double>(OsuSetting.DimLevel);
showStoryboard = config.GetBindable<bool>(OsuSetting.ShowStoryboard);
EnableUserDim.ValueChanged += _ => updateBackgroundDim();
dimLevel.ValueChanged += _ => updateBackgroundDim();
showStoryboard.ValueChanged += _ => updateBackgroundDim();
StoryboardReplacesBackground.ValueChanged += _ => updateBackgroundDim();
}
protected override void LoadComplete()
{
base.LoadComplete();
updateBackgroundDim();
}
private void updateBackgroundDim()
{
if (isStoryboard)
{
DimContainer.FadeTo(!showStoryboard.Value || dimLevel.Value == 1 ? 0 : 1, background_fade_duration, Easing.OutQuint);
}
else
{
// The background needs to be hidden in the case of it being replaced by the storyboard
DimContainer.FadeTo(showStoryboard.Value && StoryboardReplacesBackground.Value ? 0 : 1, background_fade_duration, Easing.OutQuint);
}
DimContainer.FadeColour(EnableUserDim.Value ? OsuColour.Gray(1 - (float)dimLevel.Value) : Color4.White, background_fade_duration, Easing.OutQuint);
}
}
}

View File

@ -59,7 +59,6 @@ namespace osu.Game.Graphics.UserInterface
public abstract void Increment(T amount);
public float TextSize
{
get => DisplayedCountSpriteText.Font.Size;

View File

@ -0,0 +1,487 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using osu.Game.Database;
namespace osu.Game.Migrations
{
[DbContext(typeof(OsuDbContext))]
[Migration("20190225062029_AddUserIDColumn")]
partial class AddUserIDColumn
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.2.1-servicing-10028");
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<float>("ApproachRate");
b.Property<float>("CircleSize");
b.Property<float>("DrainRate");
b.Property<float>("OverallDifficulty");
b.Property<double>("SliderMultiplier");
b.Property<double>("SliderTickRate");
b.HasKey("ID");
b.ToTable("BeatmapDifficulty");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("AudioLeadIn");
b.Property<int>("BaseDifficultyID");
b.Property<int>("BeatDivisor");
b.Property<int>("BeatmapSetInfoID");
b.Property<bool>("Countdown");
b.Property<double>("DistanceSpacing");
b.Property<int>("GridSize");
b.Property<string>("Hash");
b.Property<bool>("Hidden");
b.Property<bool>("LetterboxInBreaks");
b.Property<string>("MD5Hash");
b.Property<int?>("MetadataID");
b.Property<int?>("OnlineBeatmapID");
b.Property<string>("Path");
b.Property<int>("RulesetID");
b.Property<bool>("SpecialStyle");
b.Property<float>("StackLeniency");
b.Property<double>("StarDifficulty");
b.Property<int>("Status");
b.Property<string>("StoredBookmarks");
b.Property<double>("TimelineZoom");
b.Property<string>("Version");
b.Property<bool>("WidescreenStoryboard");
b.HasKey("ID");
b.HasIndex("BaseDifficultyID");
b.HasIndex("BeatmapSetInfoID");
b.HasIndex("Hash");
b.HasIndex("MD5Hash");
b.HasIndex("MetadataID");
b.HasIndex("OnlineBeatmapID")
.IsUnique();
b.HasIndex("RulesetID");
b.ToTable("BeatmapInfo");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Artist");
b.Property<string>("ArtistUnicode");
b.Property<string>("AudioFile");
b.Property<string>("AuthorString")
.HasColumnName("Author");
b.Property<string>("BackgroundFile");
b.Property<int>("PreviewTime");
b.Property<string>("Source");
b.Property<string>("Tags");
b.Property<string>("Title");
b.Property<string>("TitleUnicode");
b.HasKey("ID");
b.ToTable("BeatmapMetadata");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("BeatmapSetInfoID");
b.Property<int>("FileInfoID");
b.Property<string>("Filename")
.IsRequired();
b.HasKey("ID");
b.HasIndex("BeatmapSetInfoID");
b.HasIndex("FileInfoID");
b.ToTable("BeatmapSetFileInfo");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<bool>("DeletePending");
b.Property<string>("Hash");
b.Property<int?>("MetadataID");
b.Property<int?>("OnlineBeatmapSetID");
b.Property<bool>("Protected");
b.Property<int>("Status");
b.HasKey("ID");
b.HasIndex("DeletePending");
b.HasIndex("Hash")
.IsUnique();
b.HasIndex("MetadataID");
b.HasIndex("OnlineBeatmapSetID")
.IsUnique();
b.ToTable("BeatmapSetInfo");
});
modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("IntKey")
.HasColumnName("Key");
b.Property<int?>("RulesetID");
b.Property<string>("StringValue")
.HasColumnName("Value");
b.Property<int?>("Variant");
b.HasKey("ID");
b.HasIndex("RulesetID", "Variant");
b.ToTable("Settings");
});
modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Hash");
b.Property<int>("ReferenceCount");
b.HasKey("ID");
b.HasIndex("Hash")
.IsUnique();
b.HasIndex("ReferenceCount");
b.ToTable("FileInfo");
});
modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("IntAction")
.HasColumnName("Action");
b.Property<string>("KeysString")
.HasColumnName("Keys");
b.Property<int?>("RulesetID");
b.Property<int?>("Variant");
b.HasKey("ID");
b.HasIndex("IntAction");
b.HasIndex("RulesetID", "Variant");
b.ToTable("KeyBinding");
});
modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
{
b.Property<int?>("ID")
.ValueGeneratedOnAdd();
b.Property<bool>("Available");
b.Property<string>("InstantiationInfo");
b.Property<string>("Name");
b.Property<string>("ShortName");
b.HasKey("ID");
b.HasIndex("Available");
b.HasIndex("ShortName")
.IsUnique();
b.ToTable("RulesetInfo");
});
modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("FileInfoID");
b.Property<string>("Filename")
.IsRequired();
b.Property<int?>("ScoreInfoID");
b.HasKey("ID");
b.HasIndex("FileInfoID");
b.HasIndex("ScoreInfoID");
b.ToTable("ScoreFileInfo");
});
modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<double>("Accuracy")
.HasColumnType("DECIMAL(1,4)");
b.Property<int>("BeatmapInfoID");
b.Property<int>("Combo");
b.Property<DateTimeOffset>("Date");
b.Property<bool>("DeletePending");
b.Property<string>("Hash");
b.Property<int>("MaxCombo");
b.Property<string>("ModsJson")
.HasColumnName("Mods");
b.Property<long?>("OnlineScoreID");
b.Property<double?>("PP");
b.Property<int>("Rank");
b.Property<int>("RulesetID");
b.Property<string>("StatisticsJson")
.HasColumnName("Statistics");
b.Property<int>("TotalScore");
b.Property<long?>("UserID")
.HasColumnName("UserID");
b.Property<string>("UserString")
.HasColumnName("User");
b.HasKey("ID");
b.HasIndex("BeatmapInfoID");
b.HasIndex("OnlineScoreID")
.IsUnique();
b.HasIndex("RulesetID");
b.ToTable("ScoreInfo");
});
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("FileInfoID");
b.Property<string>("Filename")
.IsRequired();
b.Property<int>("SkinInfoID");
b.HasKey("ID");
b.HasIndex("FileInfoID");
b.HasIndex("SkinInfoID");
b.ToTable("SkinFileInfo");
});
modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Creator");
b.Property<bool>("DeletePending");
b.Property<string>("Hash");
b.Property<string>("Name");
b.HasKey("ID");
b.HasIndex("DeletePending");
b.HasIndex("Hash")
.IsUnique();
b.ToTable("SkinInfo");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
{
b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty")
.WithMany()
.HasForeignKey("BaseDifficultyID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet")
.WithMany("Beatmaps")
.HasForeignKey("BeatmapSetInfoID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
.WithMany("Beatmaps")
.HasForeignKey("MetadataID");
b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
.WithMany()
.HasForeignKey("RulesetID")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
{
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo")
.WithMany("Files")
.HasForeignKey("BeatmapSetInfoID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
.WithMany()
.HasForeignKey("FileInfoID")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
{
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
.WithMany("BeatmapSets")
.HasForeignKey("MetadataID");
});
modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
{
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
.WithMany()
.HasForeignKey("FileInfoID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.Scoring.ScoreInfo")
.WithMany("Files")
.HasForeignKey("ScoreInfoID");
});
modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
{
b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap")
.WithMany("Scores")
.HasForeignKey("BeatmapInfoID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
.WithMany()
.HasForeignKey("RulesetID")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
{
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
.WithMany()
.HasForeignKey("FileInfoID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.Skinning.SkinInfo")
.WithMany("Files")
.HasForeignKey("SkinInfoID")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace osu.Game.Migrations
{
public partial class AddUserIDColumn : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<long>(
name: "UserID",
table: "ScoreInfo",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "UserID",
table: "ScoreInfo");
}
}
}

View File

@ -338,6 +338,9 @@ namespace osu.Game.Migrations
b.Property<long>("TotalScore");
b.Property<long?>("UserID")
.HasColumnName("UserID");
b.Property<string>("UserString")
.HasColumnName("User");

View File

@ -36,11 +36,12 @@ namespace osu.Game.Online.API.Requests
[Description("Ranked & Approved")]
RankedApproved = 0,
Approved = 1,
Qualified = 3,
Loved = 8,
Favourites = 2,
Qualified = 3,
Pending = 4,
[Description("Pending & WIP")]
PendingWIP = 4,
Graveyard = 5,
[Description("My Maps")]

View File

@ -213,12 +213,6 @@ namespace osu.Game.Online.Leaderboards
pendingUpdateScores?.Cancel();
pendingUpdateScores = Schedule(() =>
{
if (api?.IsLoggedIn != true)
{
PlaceholderState = PlaceholderState.NotLoggedIn;
return;
}
PlaceholderState = PlaceholderState.Retrieving;
loading.Show();
@ -231,6 +225,12 @@ namespace osu.Game.Online.Leaderboards
if (getScoresRequest == null)
return;
if (api?.IsLoggedIn != true)
{
PlaceholderState = PlaceholderState.NotLoggedIn;
return;
}
getScoresRequest.Failure += e => Schedule(() =>
{
if (e is OperationCanceledException)
@ -243,6 +243,11 @@ namespace osu.Game.Online.Leaderboards
});
}
/// <summary>
/// Performs a fetch/refresh of scores to be displayed.
/// </summary>
/// <param name="scoresCallback">A callback which should be called when fetching is completed. Scheduling is not required.</param>
/// <returns>An <see cref="APIRequest"/> responsible for the fetch operation. This will be queued and performed automatically.</returns>
protected abstract APIRequest FetchScores(Action<IEnumerable<ScoreInfo>> scoresCallback);
private Placeholder currentPlaceholder;

View File

@ -124,6 +124,7 @@ namespace osu.Game.Online.Multiplayer
if (other.Host.Value != null && Host.Value?.Id != other.Host.Value.Id)
Host.Value = other.Host.Value;
ChannelId.Value = other.ChannelId.Value;
Status.Value = other.Status.Value;
Availability.Value = other.Availability.Value;
Type.Value = other.Type.Value;

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using osu.Framework.Configuration;
using osu.Framework.Screens;
using osu.Game.Configuration;
@ -109,6 +110,8 @@ namespace osu.Game
private readonly List<OverlayContainer> overlays = new List<OverlayContainer>();
private readonly List<OverlayContainer> visibleBlockingOverlays = new List<OverlayContainer>();
// todo: move this to SongSelect once Screen has the ability to unsuspend.
[Cached]
[Cached(Type = typeof(IBindable<IEnumerable<Mod>>))]
@ -127,6 +130,22 @@ namespace osu.Game
public void ToggleDirect() => direct.ToggleVisibility();
private void updateBlockingOverlayFade() =>
screenContainer.FadeColour(visibleBlockingOverlays.Any() ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint);
public void AddBlockingOverlay(OverlayContainer overlay)
{
if (!visibleBlockingOverlays.Contains(overlay))
visibleBlockingOverlays.Add(overlay);
updateBlockingOverlayFade();
}
public void RemoveBlockingOverlay(OverlayContainer overlay)
{
visibleBlockingOverlays.Remove(overlay);
updateBlockingOverlayFade();
}
/// <summary>
/// Close all game-wide overlays.
/// </summary>
@ -148,8 +167,6 @@ namespace osu.Game
{
this.frameworkConfig = frameworkConfig;
ScoreManager.ItemAdded += (score, _, silent) => Schedule(() => LoadScore(score, silent));
if (!Host.IsPrimaryInstance)
{
Logger.Log(@"osu! does not support multiple running instances.", LoggingTarget.Runtime, LogLevel.Error);
@ -198,63 +215,12 @@ namespace osu.Game
externalLinkOpener.OpenUrlExternally(url);
}
private ScheduledDelegate scoreLoad;
/// <summary>
/// Show a beatmap set as an overlay.
/// </summary>
/// <param name="setId">The set to display.</param>
public void ShowBeatmapSet(int setId) => beatmapSetOverlay.FetchAndShowBeatmapSet(setId);
/// <summary>
/// Present a beatmap at song select.
/// </summary>
/// <param name="beatmap">The beatmap to select.</param>
public void PresentBeatmap(BeatmapSetInfo beatmap)
{
if (menuScreen == null)
{
Schedule(() => PresentBeatmap(beatmap));
return;
}
CloseAllOverlays(false);
void setBeatmap()
{
if (Beatmap.Disabled)
{
Schedule(setBeatmap);
return;
}
var databasedSet = beatmap.OnlineBeatmapSetID != null ? BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID) : BeatmapManager.QueryBeatmapSet(s => s.Hash == beatmap.Hash);
if (databasedSet != null)
{
// Use first beatmap available for current ruleset, else switch ruleset.
var first = databasedSet.Beatmaps.Find(b => b.Ruleset == ruleset.Value) ?? databasedSet.Beatmaps.First();
ruleset.Value = first.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(first);
}
}
switch (screenStack.CurrentScreen)
{
case SongSelect _:
break;
default:
// navigate to song select if we are not already there.
menuScreen.MakeCurrent();
menuScreen.LoadToSolo();
break;
}
setBeatmap();
}
/// <summary>
/// Show a user's profile as an overlay.
/// </summary>
@ -267,19 +233,44 @@ namespace osu.Game
/// <param name="beatmapId">The beatmap to show.</param>
public void ShowBeatmap(int beatmapId) => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId);
protected void LoadScore(ScoreInfo score, bool silent)
/// <summary>
/// Present a beatmap at song select immediately.
/// The user should have already requested this interactively.
/// </summary>
/// <param name="beatmap">The beatmap to select.</param>
public void PresentBeatmap(BeatmapSetInfo beatmap)
{
if (silent)
return;
var databasedSet = beatmap.OnlineBeatmapSetID != null
? BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID)
: BeatmapManager.QueryBeatmapSet(s => s.Hash == beatmap.Hash);
scoreLoad?.Cancel();
if (menuScreen == null)
if (databasedSet == null)
{
scoreLoad = Schedule(() => LoadScore(score, false));
Logger.Log("The requested beatmap could not be loaded.", LoggingTarget.Information);
return;
}
performFromMainMenu(() =>
{
// we might already be at song select, so a check is required before performing the load to solo.
if (menuScreen.IsCurrentScreen())
menuScreen.LoadToSolo();
// Use first beatmap available for current ruleset, else switch ruleset.
var first = databasedSet.Beatmaps.Find(b => b.Ruleset == ruleset.Value) ?? databasedSet.Beatmaps.First();
ruleset.Value = first.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(first);
}, $"load {beatmap}", bypassScreenAllowChecks: true, targetScreen: typeof(PlaySongSelect));
}
/// <summary>
/// Present a score's replay immediately.
/// The user should have already requested this interactively.
/// </summary>
/// <param name="beatmap">The beatmap to select.</param>
public void PresentScore(ScoreInfo score)
{
var databasedScore = ScoreManager.GetScore(score);
var databasedScoreInfo = databasedScore.ScoreInfo;
if (databasedScore.Replay == null)
@ -295,14 +286,40 @@ namespace osu.Game
return;
}
if ((screenStack.CurrentScreen as IOsuScreen)?.AllowExternalScreenChange == false)
performFromMainMenu(() =>
{
ruleset.Value = databasedScoreInfo.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap);
Beatmap.Value.Mods.Value = databasedScoreInfo.Mods;
menuScreen.Push(new PlayerLoader(() => new ReplayPlayer(databasedScore)));
}, $"watch {databasedScoreInfo}", bypassScreenAllowChecks: true);
}
private ScheduledDelegate performFromMainMenuTask;
/// <summary>
/// Perform an action only after returning to the main menu.
/// Eagerly tries to exit the current screen until it succeeds.
/// </summary>
/// <param name="action">The action to perform once we are in the correct state.</param>
/// <param name="taskName">The task name to display in a notification (if we can't immediately reach the main menu state).</param>
/// <param name="targetScreen">An optional target screen type. If this screen is already current we can immediately perform the action without returning to the menu.</param>
/// <param name="bypassScreenAllowChecks">Whether checking <see cref="IOsuScreen.AllowExternalScreenChange"/> should be bypassed.</param>
private void performFromMainMenu(Action action, string taskName, Type targetScreen = null, bool bypassScreenAllowChecks = false)
{
performFromMainMenuTask?.Cancel();
// if the current screen does not allow screen changing, give the user an option to try again later.
if (!bypassScreenAllowChecks && (screenStack.CurrentScreen as IOsuScreen)?.AllowExternalScreenChange == false)
{
notifications.Post(new SimpleNotification
{
Text = $"Click here to watch {databasedScoreInfo.User.Username} on {databasedScoreInfo.Beatmap}",
Text = $"Click here to {taskName}",
Activated = () =>
{
loadScore();
performFromMainMenu(action, taskName, targetScreen, true);
return true;
}
});
@ -310,24 +327,26 @@ namespace osu.Game
return;
}
loadScore();
CloseAllOverlays(false);
void loadScore()
// we may already be at the target screen type.
if (targetScreen != null && screenStack.CurrentScreen?.GetType() == targetScreen)
{
if (!menuScreen.IsCurrentScreen() || Beatmap.Disabled)
{
menuScreen.MakeCurrent();
this.Delay(500).Schedule(loadScore, out scoreLoad);
return;
}
ruleset.Value = databasedScoreInfo.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap);
Beatmap.Value.Mods.Value = databasedScoreInfo.Mods;
menuScreen.Push(new PlayerLoader(() => new ReplayPlayer(databasedScore)));
action();
return;
}
// all conditions have been met to continue with the action.
if (menuScreen?.IsCurrentScreen() == true && !Beatmap.Disabled)
{
action();
return;
}
// menuScreen may not be initialised yet (null check required).
menuScreen?.MakeCurrent();
performFromMainMenuTask = Schedule(() => performFromMainMenu(action, taskName));
}
protected override void Dispose(bool isDisposing)
@ -351,8 +370,10 @@ namespace osu.Game
BeatmapManager.PostNotification = n => notifications?.Post(n);
BeatmapManager.GetStableStorage = GetStorageForStableInstall;
BeatmapManager.PresentImport = items => PresentBeatmap(items.First());
BeatmapManager.PresentBeatmap = PresentBeatmap;
ScoreManager.PostNotification = n => notifications?.Post(n);
ScoreManager.PresentImport = items => PresentScore(items.First());
Container logoContainer;
@ -597,20 +618,10 @@ namespace osu.Game
}
private Task asyncLoadStream;
private int visibleOverlayCount;
private void loadComponentSingleFile<T>(T d, Action<T> add)
where T : Drawable
{
if (d is FocusedOverlayContainer focused)
{
focused.StateChanged += s =>
{
visibleOverlayCount += s == Visibility.Visible ? 1 : -1;
screenContainer.FadeColour(visibleOverlayCount > 0 ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint);
};
}
// schedule is here to ensure that all component loads are done after LoadComplete is run (and thus all dependencies are cached).
// with some better organisation of LoadComplete to do construction and dependency caching in one step, followed by calls to loadComponentSingleFile,
// we could avoid the need for scheduling altogether.
@ -628,10 +639,24 @@ namespace osu.Game
{
Logger.Log($"Loading {d}...", level: LogLevel.Debug);
// Since this is running in a separate thread, it is possible for OsuGame to be disposed after LoadComponentAsync has been called
// throwing an exception. To avoid this, the call is scheduled on the update thread, which does not run if IsDisposed = true
Task task = null;
var del = new ScheduledDelegate(() => task = LoadComponentAsync(d, add));
Scheduler.Add(del);
// The delegate won't complete if OsuGame has been disposed in the meantime
while (!IsDisposed && !del.Completed)
await Task.Delay(10);
// Either we're disposed or the load process has started successfully
if (IsDisposed)
return;
await LoadComponentAsync(d, add);
Debug.Assert(task != null);
await task;
Logger.Log($"Loaded {d}!", level: LogLevel.Debug);
}
catch (OperationCanceledException)

View File

@ -75,9 +75,9 @@ namespace osu.Game
private Bindable<bool> fpsDisplayVisible;
protected AssemblyName AssemblyName => Assembly.GetEntryAssembly()?.GetName() ?? new AssemblyName { Version = new Version() };
public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version();
public bool IsDeployedBuild => AssemblyName.Version.Major > 0;
public bool IsDeployedBuild => AssemblyVersion.Major > 0;
public string Version
{
@ -86,8 +86,8 @@ namespace osu.Game
if (!IsDeployedBuild)
return @"local " + (DebugUtils.IsDebug ? @"debug" : @"release");
var assembly = AssemblyName;
return $@"{assembly.Version.Major}.{assembly.Version.Minor}.{assembly.Version.Build}";
var version = AssemblyVersion;
return $@"{version.Major}.{version.Minor}.{version.Build}";
}
}

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -21,30 +22,10 @@ namespace osu.Game.Overlays.BeatmapSet
private const float metadata_width = 225;
private const float spacing = 20;
private readonly MetadataSection source, tags;
private readonly Box successRateBackground;
private readonly SuccessRate successRate;
private BeatmapSetInfo beatmapSet;
public BeatmapSetInfo BeatmapSet
{
get => beatmapSet;
set
{
if (value == beatmapSet) return;
beatmapSet = value;
updateDisplay();
}
}
private void updateDisplay()
{
source.Text = BeatmapSet?.Metadata.Source ?? string.Empty;
tags.Text = BeatmapSet?.Metadata.Tags ?? string.Empty;
}
public readonly Bindable<BeatmapSetInfo> BeatmapSet = new Bindable<BeatmapSetInfo>();
public BeatmapInfo Beatmap
{
@ -54,6 +35,7 @@ namespace osu.Game.Overlays.BeatmapSet
public Info()
{
MetadataSection source, tags;
RelativeSizeAxes = Axes.X;
Height = 220;
Masking = true;
@ -131,14 +113,18 @@ namespace osu.Game.Overlays.BeatmapSet
},
},
};
BeatmapSet.ValueChanged += b =>
{
source.Text = b.NewValue?.Metadata.Source ?? string.Empty;
tags.Text = b.NewValue?.Metadata.Tags ?? string.Empty;
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
successRateBackground.Colour = colours.GrayE;
updateDisplay();
}
private class MetadataSection : FillFlowContainer

View File

@ -3,6 +3,7 @@
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -29,32 +30,20 @@ namespace osu.Game.Overlays
public const float RIGHT_WIDTH = 275;
private readonly Header header;
private readonly Info info;
private APIAccess api;
private RulesetStore rulesets;
private readonly ScrollContainer scroll;
private BeatmapSetInfo beatmapSet;
public BeatmapSetInfo BeatmapSet
{
get => beatmapSet;
set
{
if (value == beatmapSet)
return;
header.BeatmapSet.Value = info.BeatmapSet = beatmapSet = value;
}
}
private readonly Bindable<BeatmapSetInfo> beatmapSet = new Bindable<BeatmapSetInfo>();
// receive input outside our bounds so we can trigger a close event on ourselves.
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
public BeatmapSetOverlay()
{
Info info;
ScoresContainer scores;
Waves.FirstWaveColour = OsuColour.Gray(0.4f);
Waves.SecondWaveColour = OsuColour.Gray(0.3f);
@ -101,6 +90,9 @@ namespace osu.Game.Overlays
},
};
header.BeatmapSet.BindTo(beatmapSet);
info.BeatmapSet.BindTo(beatmapSet);
header.Picker.Beatmap.ValueChanged += b =>
{
info.Beatmap = b.NewValue;
@ -124,7 +116,7 @@ namespace osu.Game.Overlays
protected override void PopOut()
{
base.PopOut();
FadeEdgeEffectTo(0, WaveContainer.DISAPPEAR_DURATION, Easing.Out).OnComplete(_ => BeatmapSet = null);
FadeEdgeEffectTo(0, WaveContainer.DISAPPEAR_DURATION, Easing.Out).OnComplete(_ => beatmapSet.Value = null);
}
protected override bool OnClick(ClickEvent e)
@ -135,11 +127,11 @@ namespace osu.Game.Overlays
public void FetchAndShowBeatmap(int beatmapId)
{
BeatmapSet = null;
beatmapSet.Value = null;
var req = new GetBeatmapSetRequest(beatmapId, BeatmapSetLookupType.BeatmapId);
req.Success += res =>
{
BeatmapSet = res.ToBeatmapSet(rulesets);
beatmapSet.Value = res.ToBeatmapSet(rulesets);
header.Picker.Beatmap.Value = header.BeatmapSet.Value.Beatmaps.First(b => b.OnlineBeatmapID == beatmapId);
};
api.Queue(req);
@ -148,16 +140,16 @@ namespace osu.Game.Overlays
public void FetchAndShowBeatmapSet(int beatmapSetId)
{
BeatmapSet = null;
beatmapSet.Value = null;
var req = new GetBeatmapSetRequest(beatmapSetId);
req.Success += res => BeatmapSet = res.ToBeatmapSet(rulesets);
req.Success += res => beatmapSet.Value = res.ToBeatmapSet(rulesets);
api.Queue(req);
Show();
}
public void ShowBeatmapSet(BeatmapSetInfo set)
{
BeatmapSet = set;
beatmapSet.Value = set;
Show();
scroll.ScrollTo(0);
}

View File

@ -137,7 +137,7 @@ namespace osu.Game.Overlays.Chat
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = OsuFont.GetFont(size: TextSize * 0.75f, weight: FontWeight.Bold, fixedWidth: true)
Font = OsuFont.GetFont(size: TextSize * 0.75f, weight: FontWeight.SemiBold, fixedWidth: true)
},
new MessageSender(message.Sender)
{

View File

@ -13,6 +13,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
namespace osu.Game.Overlays.Direct
{
@ -23,6 +24,7 @@ namespace osu.Game.Overlays.Direct
private const float vertical_padding = 5;
private const float height = 70;
private FillFlowContainer statusContainer;
private PlayButton playButton;
private Box progressBar;
@ -108,10 +110,24 @@ namespace osu.Game.Overlays.Direct
},
new FillFlowContainer
{
AutoSizeAxes = Axes.X,
Height = 20,
Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
Children = GetDifficultyIcons(),
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
statusContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Vertical = vertical_padding, Horizontal = 5 },
Spacing = new Vector2(5),
},
new FillFlowContainer
{
AutoSizeAxes = Axes.X,
Height = 20,
Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
Children = GetDifficultyIcons(),
},
},
},
},
},
@ -194,6 +210,23 @@ namespace osu.Game.Overlays.Direct
Colour = colours.Yellow,
},
});
if (SetInfo.OnlineInfo?.HasVideo ?? false)
{
statusContainer.Add(new IconPill(FontAwesome.fa_film) { IconSize = new Vector2(20) });
}
if (SetInfo.OnlineInfo?.HasStoryboard ?? false)
{
statusContainer.Add(new IconPill(FontAwesome.fa_image) { IconSize = new Vector2(20) });
}
statusContainer.Add(new BeatmapSetOnlineStatusPill
{
TextSize = 12,
TextPadding = new MarginPadding { Horizontal = 10, Vertical = 4 },
Status = SetInfo.OnlineInfo?.Status ?? BeatmapSetOnlineStatus.None,
});
}
}
}

View File

@ -118,7 +118,7 @@ namespace osu.Game.Overlays.Direct
private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null));
private void setAdded(BeatmapSetInfo s, bool existing, bool silent) => setDownloadStateFromManager(s, DownloadState.LocallyAvailable);
private void setAdded(BeatmapSetInfo s, bool existing) => setDownloadStateFromManager(s, DownloadState.LocallyAvailable);
private void setRemoved(BeatmapSetInfo s) => setDownloadStateFromManager(s, DownloadState.NotDownloaded);

View File

@ -12,6 +12,14 @@ namespace osu.Game.Overlays.Direct
{
public class IconPill : CircularContainer
{
public Vector2 IconSize
{
get => iconContainer.Size;
set => iconContainer.Size = value;
}
private readonly Container iconContainer;
public IconPill(FontAwesome icon)
{
AutoSizeAxes = Axes.Both;
@ -25,16 +33,16 @@ namespace osu.Game.Overlays.Direct
Colour = Color4.Black,
Alpha = 0.5f,
},
new Container
iconContainer = new Container
{
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding(5),
Size = new Vector2(22),
Padding = new MarginPadding(5),
Child = new SpriteIcon
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Icon = icon,
Size = new Vector2(12),
},
},
};

View File

@ -38,6 +38,8 @@ namespace osu.Game.Overlays.Mods
protected override bool BlockNonPositionalInput => false;
protected override bool DimMainContent => false;
protected readonly FillFlowContainer<ModSection> ModSectionsContainer;
protected readonly Bindable<IEnumerable<Mod>> SelectedMods = new Bindable<IEnumerable<Mod>>(new Mod[] { });

View File

@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Music
[BackgroundDependencyLoader]
private void load(BeatmapManager beatmaps, IBindable<WorkingBeatmap> beatmap)
{
beatmaps.GetAllUsableBeatmapSets().ForEach(b => addBeatmapSet(b, false, false));
beatmaps.GetAllUsableBeatmapSets().ForEach(b => addBeatmapSet(b, false));
beatmaps.ItemAdded += addBeatmapSet;
beatmaps.ItemRemoved += removeBeatmapSet;
@ -83,7 +83,7 @@ namespace osu.Game.Overlays.Music
beatmapBacking.ValueChanged += _ => updateSelectedSet();
}
private void addBeatmapSet(BeatmapSetInfo obj, bool existing, bool silent) => Schedule(() =>
private void addBeatmapSet(BeatmapSetInfo obj, bool existing) => Schedule(() =>
{
if (existing)
return;

View File

@ -212,7 +212,7 @@ namespace osu.Game.Overlays
beatmapSets.Insert(index, beatmapSetInfo);
}
private void handleBeatmapAdded(BeatmapSetInfo obj, bool existing, bool silent)
private void handleBeatmapAdded(BeatmapSetInfo obj, bool existing)
{
if (existing)
return;

View File

@ -1,8 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Platform;
using osu.Game.Configuration;
@ -15,19 +15,20 @@ namespace osu.Game.Overlays.Settings.Sections.General
[BackgroundDependencyLoader]
private void load(Storage storage, OsuConfigManager config)
{
Children = new Drawable[]
Add(new SettingsEnumDropdown<ReleaseStream>
{
new SettingsEnumDropdown<ReleaseStream>
{
LabelText = "Release stream",
Bindable = config.GetBindable<ReleaseStream>(OsuSetting.ReleaseStream),
},
new SettingsButton
LabelText = "Release stream",
Bindable = config.GetBindable<ReleaseStream>(OsuSetting.ReleaseStream),
});
if (RuntimeInfo.IsDesktop)
{
Add(new SettingsButton
{
Text = "Open osu! folder",
Action = storage.OpenInNativeExplorer,
}
};
});
}
}
}
}

View File

@ -11,6 +11,7 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Platform;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
@ -26,10 +27,11 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
private Bindable<ScalingMode> scalingMode;
private Bindable<Size> sizeFullscreen;
private readonly BindableList<WindowMode> windowModes = new BindableList<WindowMode>();
private OsuGameBase game;
private SettingsDropdown<Size> resolutionDropdown;
private SettingsEnumDropdown<WindowMode> windowModeDropdown;
private SettingsDropdown<WindowMode> windowModeDropdown;
private Bindable<float> scalingPositionX;
private Bindable<float> scalingPositionY;
@ -39,7 +41,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
private const int transition_duration = 400;
[BackgroundDependencyLoader]
private void load(FrameworkConfigManager config, OsuConfigManager osuConfig, OsuGameBase game)
private void load(FrameworkConfigManager config, OsuConfigManager osuConfig, OsuGameBase game, GameHost host)
{
this.game = game;
@ -50,14 +52,18 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
scalingPositionX = osuConfig.GetBindable<float>(OsuSetting.ScalingPositionX);
scalingPositionY = osuConfig.GetBindable<float>(OsuSetting.ScalingPositionY);
if (host.Window != null)
windowModes.BindTo(host.Window.SupportedWindowModes);
Container resolutionSettingsContainer;
Children = new Drawable[]
{
windowModeDropdown = new SettingsEnumDropdown<WindowMode>
windowModeDropdown = new SettingsDropdown<WindowMode>
{
LabelText = "Screen mode",
Bindable = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode),
ItemSource = windowModes,
},
resolutionSettingsContainer = new Container
{
@ -150,6 +156,19 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
scalingSettings.ForEach(s => s.TransferValueOnCommit = mode.NewValue == ScalingMode.Everything);
}, true);
windowModes.ItemsAdded += _ => windowModesChanged();
windowModes.ItemsRemoved += _ => windowModesChanged();
windowModesChanged();
}
private void windowModesChanged()
{
if (windowModes.Count > 1)
windowModeDropdown.Show();
else
windowModeDropdown.Hide();
}
/// <summary>

View File

@ -25,9 +25,9 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
[BackgroundDependencyLoader]
private void load(BeatmapManager beatmaps, SkinManager skins, DialogOverlay dialogOverlay)
{
Children = new Drawable[]
if (beatmaps.SupportsImportFromStable)
{
importBeatmapsButton = new SettingsButton
Add(importBeatmapsButton = new SettingsButton
{
Text = "Import beatmaps from stable",
Action = () =>
@ -35,20 +35,25 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
importBeatmapsButton.Enabled.Value = false;
beatmaps.ImportFromStableAsync().ContinueWith(t => Schedule(() => importBeatmapsButton.Enabled.Value = true));
}
},
deleteBeatmapsButton = new DangerousSettingsButton
});
}
Add(deleteBeatmapsButton = new DangerousSettingsButton
{
Text = "Delete ALL beatmaps",
Action = () =>
{
Text = "Delete ALL beatmaps",
Action = () =>
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() =>
{
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() =>
{
deleteBeatmapsButton.Enabled.Value = false;
Task.Run(() => beatmaps.Delete(beatmaps.GetAllUsableBeatmapSets())).ContinueWith(t => Schedule(() => deleteBeatmapsButton.Enabled.Value = true));
}));
}
},
importSkinsButton = new SettingsButton
deleteBeatmapsButton.Enabled.Value = false;
Task.Run(() => beatmaps.Delete(beatmaps.GetAllUsableBeatmapSets())).ContinueWith(t => Schedule(() => deleteBeatmapsButton.Enabled.Value = true));
}));
}
});
if (skins.SupportsImportFromStable)
{
Add(importSkinsButton = new SettingsButton
{
Text = "Import skins from stable",
Action = () =>
@ -56,7 +61,11 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
importSkinsButton.Enabled.Value = false;
skins.ImportFromStableAsync().ContinueWith(t => Schedule(() => importSkinsButton.Enabled.Value = true));
}
},
});
}
AddRange(new Drawable[]
{
deleteSkinsButton = new DangerousSettingsButton
{
Text = "Delete ALL skins",
@ -91,7 +100,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Task.Run(() => beatmaps.Undelete(beatmaps.QueryBeatmapSets(b => b.DeletePending).ToList())).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true));
}
},
};
});
}
}
}

View File

@ -82,7 +82,7 @@ namespace osu.Game.Overlays.Settings.Sections
private void itemRemoved(SkinInfo s) => Schedule(() => skinDropdown.Items = skinDropdown.Items.Where(i => i.ID != s.ID).ToArray());
private void itemAdded(SkinInfo s, bool existing, bool silent)
private void itemAdded(SkinInfo s, bool existing)
{
if (existing)
return;

View File

@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface;
@ -26,11 +27,25 @@ namespace osu.Game.Overlays.Settings
}
}
private IBindableList<T> itemSource;
public IBindableList<T> ItemSource
{
get => itemSource;
set
{
itemSource = value;
if (Control != null)
Control.ItemSource = value;
}
}
public override IEnumerable<string> FilterTerms => base.FilterTerms.Concat(Control.Items.Select(i => i.ToString()));
protected sealed override Drawable CreateControl() => CreateDropdown();
protected virtual OsuDropdown<T> CreateDropdown() => new DropdownControl { Items = Items };
protected virtual OsuDropdown<T> CreateDropdown() => new DropdownControl { Items = Items, ItemSource = ItemSource };
protected class DropdownControl : OsuDropdown<T>
{

View File

@ -139,6 +139,7 @@ namespace osu.Game.Overlays.Toolbar
protected override bool OnClick(ClickEvent e)
{
HoverBackground.FlashColour(Color4.White.Opacity(100), 500, Easing.OutQuint);
tooltipContainer.FadeOut(100);
return base.OnClick(e);
}

View File

@ -301,7 +301,16 @@ namespace osu.Game.Rulesets.Objects.Legacy
{
// Todo: This should return the normal SampleInfos if the specified sample file isn't found, but that's a pretty edge-case scenario
if (!string.IsNullOrEmpty(bankInfo.Filename))
return new List<SampleInfo> { new FileSampleInfo { Filename = bankInfo.Filename } };
{
return new List<SampleInfo>
{
new FileSampleInfo
{
Filename = bankInfo.Filename,
Volume = bankInfo.Volume
}
};
}
var soundTypes = new List<SampleInfo>
{

View File

@ -41,6 +41,7 @@ namespace osu.Game.Rulesets.UI
protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
{
InternalChild = KeyBindingContainer = CreateKeyBindingContainer(ruleset, variant, unique);
gameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock()));
}
#region Action mapping (for replays)
@ -86,22 +87,28 @@ namespace osu.Game.Rulesets.UI
#region Clock control
private ManualClock clock;
private IFrameBasedClock parentClock;
private readonly ManualClock manualClock;
private readonly FramedClock framedClock;
[Cached]
private GameplayClock gameplayClock;
private IFrameBasedClock parentGameplayClock;
[BackgroundDependencyLoader(true)]
private void load(OsuConfigManager config, GameplayClock clock)
{
mouseDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableButtons);
if (clock != null)
parentGameplayClock = clock;
}
protected override void LoadComplete()
{
base.LoadComplete();
//our clock will now be our parent's clock, but we want to replace this to allow manual control.
parentClock = Clock;
ProcessCustomClock = false;
Clock = new FramedClock(clock = new ManualClock
{
CurrentTime = parentClock.CurrentTime,
Rate = parentClock.Rate,
});
setClock();
}
/// <summary>
@ -147,25 +154,28 @@ namespace osu.Game.Rulesets.UI
private void updateClock()
{
if (parentClock == null) return;
if (parentGameplayClock == null)
setClock(); // LoadComplete may not be run yet, but we still want the clock.
clock.Rate = parentClock.Rate;
clock.IsRunning = parentClock.IsRunning;
validState = true;
var newProposedTime = parentClock.CurrentTime;
manualClock.Rate = parentGameplayClock.Rate;
manualClock.IsRunning = parentGameplayClock.IsRunning;
var newProposedTime = parentGameplayClock.CurrentTime;
try
{
if (Math.Abs(clock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f)
if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f)
{
newProposedTime = clock.Rate > 0
? Math.Min(newProposedTime, clock.CurrentTime + sixty_frame_time)
: Math.Max(newProposedTime, clock.CurrentTime - sixty_frame_time);
newProposedTime = manualClock.Rate > 0
? Math.Min(newProposedTime, manualClock.CurrentTime + sixty_frame_time)
: Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time);
}
if (!isAttached)
{
clock.CurrentTime = newProposedTime;
manualClock.CurrentTime = newProposedTime;
}
else
{
@ -177,35 +187,39 @@ namespace osu.Game.Rulesets.UI
validState = false;
requireMoreUpdateLoops = true;
clock.CurrentTime = newProposedTime;
manualClock.CurrentTime = newProposedTime;
return;
}
clock.CurrentTime = newTime.Value;
manualClock.CurrentTime = newTime.Value;
}
requireMoreUpdateLoops = clock.CurrentTime != parentClock.CurrentTime;
requireMoreUpdateLoops = manualClock.CurrentTime != parentGameplayClock.CurrentTime;
}
finally
{
// The manual clock time has changed in the above code. The framed clock now needs to be updated
// to ensure that the its time is valid for our children before input is processed
Clock.ProcessFrame();
framedClock.ProcessFrame();
}
}
private void setClock()
{
// in case a parent gameplay clock isn't available, just use the parent clock.
if (parentGameplayClock == null)
parentGameplayClock = Clock;
Clock = gameplayClock;
ProcessCustomClock = false;
}
#endregion
#region Setting application (disables etc.)
private Bindable<bool> mouseDisabled;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
mouseDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableButtons);
}
protected override bool Handle(UIEvent e)
{
switch (e)

View File

@ -109,7 +109,27 @@ namespace osu.Game.Scoring
public string UserString
{
get => User?.Username;
set => User = new User { Username = value };
set
{
if (User == null)
User = new User();
User.Username = value;
}
}
[JsonIgnore]
[Column("UserID")]
public long? UserID
{
get => User?.Id ?? 1;
set
{
if (User == null)
User = new User();
User.Id = value ?? 1;
}
}
[JsonIgnore]
@ -158,5 +178,7 @@ namespace osu.Game.Scoring
{
public string Acronym { get; set; }
}
public override string ToString() => $"{User} playing {Beatmap}";
}
}

View File

@ -8,7 +8,7 @@ using osu.Game.Database;
namespace osu.Game.Scoring
{
public class ScoreStore : MutableDatabaseBackedStore<ScoreInfo>
public class ScoreStore : MutableDatabaseBackedStoreWithFileIncludes<ScoreInfo, ScoreFileInfo>
{
public ScoreStore(IDatabaseContextFactory factory, Storage storage)
: base(factory, storage)
@ -17,7 +17,6 @@ namespace osu.Game.Scoring
protected override IQueryable<ScoreInfo> AddIncludesForConsumption(IQueryable<ScoreInfo> query)
=> base.AddIncludesForConsumption(query)
.Include(s => s.Files).ThenInclude(f => f.FileInfo)
.Include(s => s.Beatmap)
.Include(s => s.Ruleset);
}

View File

@ -2,10 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Containers;
namespace osu.Game.Screens.Backgrounds
{
@ -13,7 +15,18 @@ namespace osu.Game.Screens.Backgrounds
{
private WorkingBeatmap beatmap;
public WorkingBeatmap Beatmap
/// <summary>
/// Whether or not user dim settings should be applied to this Background.
/// </summary>
public readonly Bindable<bool> EnableUserDim = new Bindable<bool>();
public readonly Bindable<bool> StoryboardReplacesBackground = new Bindable<bool>();
private readonly UserDimContainer fadeContainer;
protected virtual UserDimContainer CreateFadeContainer() => new UserDimContainer { RelativeSizeAxes = Axes.Both };
public virtual WorkingBeatmap Beatmap
{
get => beatmap;
set
@ -37,8 +50,9 @@ namespace osu.Game.Screens.Backgrounds
}
b.Depth = newDepth;
AddInternal(Background = b);
fadeContainer.Add(Background = b);
Background.BlurSigma = BlurTarget;
StoryboardReplacesBackground.BindTo(fadeContainer.StoryboardReplacesBackground);
}));
});
}
@ -47,6 +61,8 @@ namespace osu.Game.Screens.Backgrounds
public BackgroundScreenBeatmap(WorkingBeatmap beatmap = null)
{
Beatmap = beatmap;
InternalChild = fadeContainer = CreateFadeContainer();
fadeContainer.EnableUserDim.BindTo(EnableUserDim);
}
public override bool Equals(BackgroundScreen other)
@ -57,7 +73,7 @@ namespace osu.Game.Screens.Backgrounds
return base.Equals(other) && beatmap == otherBeatmapBackground.Beatmap;
}
private class BeatmapBackground : Background
protected class BeatmapBackground : Background
{
private readonly WorkingBeatmap beatmap;

View File

@ -15,6 +15,9 @@ namespace osu.Game.Screens
protected Vector2 BlurTarget;
public TransformSequence<Background> BlurTo(Vector2 sigma, double duration, Easing easing = Easing.None)
=> Background?.BlurTo(BlurTarget = sigma, duration, easing);
{
BlurTarget = sigma;
return Background?.BlurTo(BlurTarget, duration, easing);
}
}
}

View File

@ -91,7 +91,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
RelativeSizeAxes = Axes.Y,
Height = 0.5f,
Icon = FontAwesome.fa_search_plus,
Action = () => timeline.Zoom++
Action = () => changeZoom(1)
},
new TimelineButton
{
@ -100,7 +100,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
RelativeSizeAxes = Axes.Y,
Height = 0.5f,
Icon = FontAwesome.fa_search_minus,
Action = () => timeline.Zoom--
Action = () => changeZoom(-1)
},
}
}
@ -124,5 +124,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
timeline.WaveformVisible.BindTo(waveformCheckbox.Current);
}
private void changeZoom(float change) => timeline.Zoom += change;
}
}

View File

@ -22,6 +22,8 @@ using osu.Game.Screens.Edit.Components.Menus;
using osu.Game.Screens.Edit.Compose;
using osu.Game.Screens.Edit.Design;
using osuTK.Input;
using System.Collections.Generic;
using osu.Framework;
namespace osu.Game.Screens.Edit
{
@ -67,6 +69,15 @@ namespace osu.Game.Screens.Edit
SummaryTimeline timeline;
PlaybackControl playback;
var fileMenuItems = new List<MenuItem>();
if (RuntimeInfo.IsDesktop)
{
fileMenuItems.Add(new EditorMenuItem("Export", MenuItemType.Standard, exportBeatmap));
fileMenuItems.Add(new EditorMenuItemSpacer());
}
fileMenuItems.Add(new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit));
InternalChildren = new[]
{
new Container
@ -94,12 +105,7 @@ namespace osu.Game.Screens.Edit
{
new MenuItem("File")
{
Items = new[]
{
new EditorMenuItem("Export", MenuItemType.Standard, exportBeatmap),
new EditorMenuItemSpacer(),
new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit)
}
Items = fileMenuItems
}
}
}

View File

@ -19,7 +19,7 @@ namespace osu.Game.Screens
/// <summary>
/// Whether a top-level component should be allowed to exit the current screen to, for example,
/// complete an import.
/// complete an import. Note that this can be overridden by a user if they specifically request.
/// </summary>
bool AllowExternalScreenChange { get; }

View File

@ -5,14 +5,17 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Screens;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API;
using osuTK;
using osuTK.Graphics;
using osu.Game.Overlays;
using osu.Game.Users;
namespace osu.Game.Screens.Menu
{
@ -33,13 +36,15 @@ namespace osu.Game.Screens.Menu
private const float icon_y = -85;
private readonly Bindable<User> currentUser = new Bindable<User>();
public Disclaimer()
{
ValidForResume = false;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private void load(OsuColour colours, APIAccess api)
{
InternalChildren = new Drawable[]
{
@ -70,7 +75,7 @@ namespace osu.Game.Screens.Menu
textFlow.AddParagraph("Things may not work as expected", t => t.Font = t.Font.With(size: 20));
textFlow.NewParagraph();
Action<SpriteText> format = t => t.Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold);
Action<SpriteText> format = t => t.Font = OsuFont.GetFont(size: 15, weight: FontWeight.SemiBold);
textFlow.AddParagraph("Detailed bug reports are welcomed via github issues.", format);
textFlow.NewParagraph();
@ -96,6 +101,13 @@ namespace osu.Game.Screens.Menu
}).First());
iconColour = colours.Yellow;
currentUser.BindTo(api.LocalUser);
currentUser.BindValueChanged(e =>
{
if (e.NewValue.IsSupporter)
supporterDrawables.ForEach(d => d.FadeOut(500, Easing.OutQuint).Expire());
}, true);
}
protected override void LoadComplete()

View File

@ -9,6 +9,13 @@ namespace osu.Game.Screens.Multi.Components
{
public class MultiplayerBackgroundSprite : MultiplayerComposite
{
private readonly BeatmapSetCoverType beatmapSetCoverType;
public MultiplayerBackgroundSprite(BeatmapSetCoverType beatmapSetCoverType = BeatmapSetCoverType.Cover)
{
this.beatmapSetCoverType = beatmapSetCoverType;
}
[BackgroundDependencyLoader]
private void load()
{
@ -19,6 +26,6 @@ namespace osu.Game.Screens.Multi.Components
CurrentItem.BindValueChanged(item => sprite.Beatmap.Value = item.NewValue?.Beatmap, true);
}
protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both };
protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(beatmapSetCoverType) { RelativeSizeAxes = Axes.Both };
}
}

View File

@ -11,6 +11,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
@ -137,7 +138,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components
Width = cover_width,
Masking = true,
Margin = new MarginPadding { Left = side_strip_width },
Child = new MultiplayerBackgroundSprite { RelativeSizeAxes = Axes.Both }
Child = new MultiplayerBackgroundSprite(BeatmapSetCoverType.List) { RelativeSizeAxes = Axes.Both }
},
new Container
{

View File

@ -28,13 +28,15 @@ namespace osu.Game.Screens.Multi.Match.Components
{
base.LoadComplete();
roomId.BindValueChanged(_ => updateChannel(), true);
channelId.BindValueChanged(_ => updateChannel(), true);
}
private void updateChannel()
{
if (roomId.Value != null)
Channel.Value = channelManager?.JoinChannel(new Channel { Id = channelId.Value, Type = ChannelType.Multiplayer, Name = $"#mp_{roomId.Value}" });
if (roomId.Value == null || channelId.Value == 0)
return;
Channel.Value = channelManager?.JoinChannel(new Channel { Id = channelId.Value, Type = ChannelType.Multiplayer, Name = $"#lazermp_{roomId.Value}" });
}
}
}

View File

@ -54,7 +54,7 @@ namespace osu.Game.Screens.Multi.Match.Components
hasBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmap.OnlineBeatmapID) != null;
}
private void beatmapAdded(BeatmapSetInfo model, bool existing, bool silent)
private void beatmapAdded(BeatmapSetInfo model, bool existing)
{
if (Beatmap.Value == null)
return;

View File

@ -202,7 +202,7 @@ namespace osu.Game.Screens.Multi.Match
/// <summary>
/// Handle the case where a beatmap is imported (and can be used by this match).
/// </summary>
private void beatmapAdded(BeatmapSetInfo model, bool existing, bool silent) => Schedule(() =>
private void beatmapAdded(BeatmapSetInfo model, bool existing) => Schedule(() =>
{
if (Beatmap.Value != beatmapManager.DefaultBeatmap)
return;

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@ -40,13 +41,7 @@ namespace osu.Game.Screens.Play
private readonly BreakInfo info;
private readonly BreakArrows breakArrows;
public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor)
: this(letterboxing)
{
bindProcessor(scoreProcessor);
}
public BreakOverlay(bool letterboxing)
public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor = null)
{
RelativeSizeAxes = Axes.Both;
Child = fadeContainer = new Container
@ -98,6 +93,14 @@ namespace osu.Game.Screens.Play
}
}
};
if (scoreProcessor != null) bindProcessor(scoreProcessor);
}
[BackgroundDependencyLoader(true)]
private void load(GameplayClock clock)
{
if (clock != null) Clock = clock;
}
protected override void LoadComplete()

View File

@ -0,0 +1,43 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Timing;
namespace osu.Game.Screens.Play
{
/// <summary>
/// A clock which is used for gameplay elements that need to follow audio time 1:1.
/// Exposed via DI by <see cref="PausableGameplayContainer"/>.
/// <remarks>
/// The main purpose of this clock is to stop components using it from accidentally processing the main
/// <see cref="IFrameBasedClock"/>, as this should only be done once to ensure accuracy.
/// </remarks>
/// </summary>
public class GameplayClock : IFrameBasedClock
{
private readonly IFrameBasedClock underlyingClock;
public GameplayClock(IFrameBasedClock underlyingClock)
{
this.underlyingClock = underlyingClock;
}
public double CurrentTime => underlyingClock.CurrentTime;
public double Rate => underlyingClock.Rate;
public bool IsRunning => underlyingClock.IsRunning;
public void ProcessFrame()
{
// we do not want to process the underlying clock.
// this is handled by PauseContainer.
}
public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime;
public double FramesPerSecond => underlyingClock.FramesPerSecond;
public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo;
}
}

View File

@ -40,7 +40,7 @@ namespace osu.Game.Screens.Play
private static bool hasShownNotificationOnce;
public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, WorkingBeatmap working, IClock offsetClock, IAdjustableClock adjustableClock)
public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, WorkingBeatmap working, IAdjustableClock adjustableClock)
{
RelativeSizeAxes = Axes.Both;
@ -81,7 +81,7 @@ namespace osu.Game.Screens.Play
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
KeyCounter = CreateKeyCounter(adjustableClock as IFrameBasedClock),
KeyCounter = CreateKeyCounter(),
HoldToQuit = CreateHoldForMenuButton(),
}
}
@ -91,9 +91,8 @@ namespace osu.Game.Screens.Play
BindRulesetContainer(rulesetContainer);
Progress.Objects = rulesetContainer.Objects;
Progress.AudioClock = offsetClock;
Progress.AllowSeeking = rulesetContainer.HasReplayLoaded.Value;
Progress.OnSeek = pos => adjustableClock.Seek(pos);
Progress.RequestSeek = pos => adjustableClock.Seek(pos);
ModDisplay.Current.BindTo(working.Mods);
@ -202,13 +201,12 @@ namespace osu.Game.Screens.Play
Margin = new MarginPadding { Top = 20 }
};
protected virtual KeyCounterCollection CreateKeyCounter(IFrameBasedClock offsetClock) => new KeyCounterCollection
protected virtual KeyCounterCollection CreateKeyCounter() => new KeyCounterCollection
{
FadeTime = 50,
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
Margin = new MarginPadding(10),
AudioClock = offsetClock
};
protected virtual SongProgress CreateProgress() => new SongProgress

View File

@ -71,9 +71,12 @@ namespace osu.Game.Screens.Play
Name = name;
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
[BackgroundDependencyLoader(true)]
private void load(TextureStore textures, GameplayClock clock)
{
if (clock != null)
Clock = clock;
Children = new Drawable[]
{
buttonSprite = new Sprite

View File

@ -8,7 +8,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Framework.Timing;
using osu.Game.Configuration;
using osuTK;
using osuTK.Graphics;
@ -37,9 +36,6 @@ namespace osu.Game.Screens.Play
key.FadeTime = FadeTime;
key.KeyDownTextColor = KeyDownTextColor;
key.KeyUpTextColor = KeyUpTextColor;
// Use the same clock object as SongProgress for saving KeyCounter state
if (AudioClock != null)
key.Clock = AudioClock;
}
public void ResetCount()
@ -125,8 +121,6 @@ namespace osu.Game.Screens.Play
public override bool HandleNonPositionalInput => receptor == null;
public override bool HandlePositionalInput => receptor == null;
public IFrameBasedClock AudioClock { get; set; }
private Receptor receptor;
public Receptor GetReceptor()

View File

@ -14,10 +14,11 @@ using osuTK.Graphics;
namespace osu.Game.Screens.Play
{
/// <summary>
/// A container which handles pausing children, displaying a pause overlay with choices etc.
/// A container which handles pausing children, displaying a pause overlay with choices and processing the clock.
/// Exposes a <see cref="GameplayClock"/> to children via DI.
/// This alleviates a lot of the intricate pause logic from being in <see cref="Player"/>
/// </summary>
public class PauseContainer : Container
public class PausableGameplayContainer : Container
{
public readonly BindableBool IsPaused = new BindableBool();
@ -43,24 +44,32 @@ namespace osu.Game.Screens.Play
public Action OnRetry;
public Action OnQuit;
private readonly FramedClock framedClock;
private readonly DecoupleableInterpolatingFramedClock decoupledClock;
private readonly FramedClock offsetClock;
private readonly DecoupleableInterpolatingFramedClock adjustableClock;
/// <summary>
/// Creates a new <see cref="PauseContainer"/>.
/// The final clock which is exposed to underlying components.
/// </summary>
/// <param name="framedClock">The gameplay clock. This is the clock that will process frames.</param>
/// <param name="decoupledClock">The seekable clock. This is the clock that will be paused and resumed.</param>
public PauseContainer(FramedClock framedClock, DecoupleableInterpolatingFramedClock decoupledClock)
[Cached]
private readonly GameplayClock gameplayClock;
/// <summary>
/// Creates a new <see cref="PausableGameplayContainer"/>.
/// </summary>
/// <param name="offsetClock">The gameplay clock. This is the clock that will process frames. Includes user/system offsets.</param>
/// <param name="adjustableClock">The seekable clock. This is the clock that will be paused and resumed. Should not be processed (it is processed automatically by <see cref="offsetClock"/>).</param>
public PausableGameplayContainer(FramedClock offsetClock, DecoupleableInterpolatingFramedClock adjustableClock)
{
this.framedClock = framedClock;
this.decoupledClock = decoupledClock;
this.offsetClock = offsetClock;
this.adjustableClock = adjustableClock;
gameplayClock = new GameplayClock(offsetClock);
RelativeSizeAxes = Axes.Both;
AddInternal(content = new Container
{
Clock = this.framedClock,
Clock = this.offsetClock,
ProcessCustomClock = false,
RelativeSizeAxes = Axes.Both
});
@ -84,7 +93,7 @@ namespace osu.Game.Screens.Play
if (IsPaused.Value) return;
// stop the seekable clock (stops the audio eventually)
decoupledClock.Stop();
adjustableClock.Stop();
IsPaused.Value = true;
pauseOverlay.Show();
@ -102,8 +111,8 @@ namespace osu.Game.Screens.Play
// Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time
// This accounts for the audio clock source potentially taking time to enter a completely stopped state
decoupledClock.Seek(decoupledClock.CurrentTime);
decoupledClock.Start();
adjustableClock.Seek(adjustableClock.CurrentTime);
adjustableClock.Start();
pauseOverlay.Hide();
}
@ -123,7 +132,7 @@ namespace osu.Game.Screens.Play
Pause();
if (!IsPaused.Value)
framedClock.ProcessFrame();
offsetClock.ProcessFrame();
base.Update();
}

View File

@ -19,7 +19,6 @@ using osu.Framework.Threading;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Cursor;
using osu.Game.Online.API;
@ -32,7 +31,6 @@ using osu.Game.Scoring;
using osu.Game.Screens.Ranking;
using osu.Game.Skinning;
using osu.Game.Storyboards.Drawables;
using osuTK.Graphics;
namespace osu.Game.Screens.Play
{
@ -57,6 +55,8 @@ namespace osu.Game.Screens.Play
private Bindable<bool> mouseWheelDisabled;
private Bindable<double> userAudioOffset;
private readonly Bindable<bool> storyboardReplacesBackground = new Bindable<bool>();
public int RestartCount;
public CursorContainer Cursor => RulesetContainer.Cursor;
@ -72,7 +72,7 @@ namespace osu.Game.Screens.Play
[Resolved]
private ScoreManager scoreManager { get; set; }
private PauseContainer pauseContainer;
protected PausableGameplayContainer PausableGameplayContainer { get; private set; }
private RulesetInfo ruleset;
@ -80,14 +80,21 @@ namespace osu.Game.Screens.Play
private SampleChannel sampleRestart;
protected ScoreProcessor ScoreProcessor;
protected RulesetContainer RulesetContainer;
protected ScoreProcessor ScoreProcessor { get; private set; }
protected RulesetContainer RulesetContainer { get; private set; }
protected HUDOverlay HUDOverlay;
protected HUDOverlay HUDOverlay { get; private set; }
private FailOverlay failOverlay;
private DrawableStoryboard storyboard;
private Container storyboardContainer;
protected UserDimContainer StoryboardContainer { get; private set; }
protected virtual UserDimContainer CreateStoryboardContainer() => new UserDimContainer(true)
{
RelativeSizeAxes = Axes.Both,
Alpha = 1,
EnableUserDim = { Value = true }
};
public bool LoadedBeatmapSuccessfully => RulesetContainer?.Objects.Any() == true;
@ -168,19 +175,15 @@ namespace osu.Game.Screens.Play
InternalChildren = new Drawable[]
{
pauseContainer = new PauseContainer(offsetClock, adjustableClock)
PausableGameplayContainer = new PausableGameplayContainer(offsetClock, adjustableClock)
{
Retries = RestartCount,
OnRetry = Restart,
OnRetry = restart,
OnQuit = performUserRequestedExit,
CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded.Value,
Children = new[]
Children = new Container[]
{
storyboardContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
},
StoryboardContainer = CreateStoryboardContainer(),
new ScalingContainer(ScalingMode.Gameplay)
{
Child = new LocalSkinOverrideContainer(working.Skin)
@ -193,32 +196,26 @@ namespace osu.Game.Screens.Play
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
ProcessCustomClock = false,
Breaks = beatmap.Breaks
},
new ScalingContainer(ScalingMode.Gameplay)
{
Child = RulesetContainer.Cursor?.CreateProxy() ?? new Container(),
},
HUDOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working, offsetClock, adjustableClock)
HUDOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working, adjustableClock)
{
Clock = Clock, // hud overlay doesn't want to use the audio clock directly
ProcessCustomClock = false,
Anchor = Anchor.Centre,
Origin = Anchor.Centre
},
new SkipOverlay(RulesetContainer.GameplayStartTime)
{
Clock = Clock, // skip button doesn't want to use the audio clock directly
ProcessCustomClock = false,
AdjustableClock = adjustableClock,
FramedClock = offsetClock,
RequestSeek = time => adjustableClock.Seek(time)
},
}
},
failOverlay = new FailOverlay
{
OnRetry = Restart,
OnRetry = restart,
OnQuit = performUserRequestedExit,
},
new HotkeyRetryOverlay
@ -228,7 +225,7 @@ namespace osu.Game.Screens.Play
if (!this.IsCurrentScreen()) return;
fadeOut(true);
Restart();
restart();
},
}
};
@ -236,7 +233,7 @@ namespace osu.Game.Screens.Play
HUDOverlay.HoldToQuit.Action = performUserRequestedExit;
HUDOverlay.KeyCounter.Visible.BindTo(RulesetContainer.HasReplayLoaded);
RulesetContainer.IsPaused.BindTo(pauseContainer.IsPaused);
RulesetContainer.IsPaused.BindTo(PausableGameplayContainer.IsPaused);
if (ShowStoryboard.Value)
initializeStoryboard(false);
@ -265,7 +262,7 @@ namespace osu.Game.Screens.Play
this.Exit();
}
public void Restart()
private void restart()
{
if (!this.IsCurrentScreen()) return;
@ -295,7 +292,7 @@ namespace osu.Game.Screens.Play
var score = CreateScore();
if (RulesetContainer.ReplayScore == null)
scoreManager.Import(score, true);
scoreManager.Import(score);
this.Push(CreateResults(score));
@ -346,6 +343,18 @@ namespace osu.Game.Screens.Play
.Delay(250)
.FadeIn(250);
ShowStoryboard.ValueChanged += enabled =>
{
if (enabled.NewValue) initializeStoryboard(true);
};
Background.EnableUserDim.Value = true;
Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground);
StoryboardContainer.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground);
storyboardReplacesBackground.Value = Beatmap.Value.Storyboard.ReplacesBackground && Beatmap.Value.Storyboard.HasDrawable;
Task.Run(() =>
{
sourceClock.Reset();
@ -357,7 +366,7 @@ namespace osu.Game.Screens.Play
this.Delay(750).Schedule(() =>
{
if (!pauseContainer.IsPaused.Value)
if (!PausableGameplayContainer.IsPaused.Value)
{
adjustableClock.Start();
}
@ -365,8 +374,8 @@ namespace osu.Game.Screens.Play
});
});
pauseContainer.Alpha = 0;
pauseContainer.FadeIn(750, Easing.OutQuint);
PausableGameplayContainer.Alpha = 0;
PausableGameplayContainer.FadeIn(750, Easing.OutQuint);
}
public override void OnSuspending(IScreen next)
@ -384,17 +393,16 @@ namespace osu.Game.Screens.Play
return true;
}
if ((!AllowPause || HasFailed || !ValidForResume || pauseContainer?.IsPaused.Value != false || RulesetContainer?.HasReplayLoaded.Value != false) && (!pauseContainer?.IsResuming ?? true))
if ((!AllowPause || HasFailed || !ValidForResume || PausableGameplayContainer?.IsPaused.Value != false || RulesetContainer?.HasReplayLoaded.Value != false) && (!PausableGameplayContainer?.IsResuming ?? true))
{
// In the case of replays, we may have changed the playback rate.
applyRateFromMods();
fadeOut();
return base.OnExiting(next);
}
if (LoadedBeatmapSuccessfully)
pauseContainer?.Pause();
PausableGameplayContainer?.Pause();
return true;
}
@ -403,14 +411,16 @@ namespace osu.Game.Screens.Play
{
float fadeOutDuration = instant ? 0 : 250;
this.FadeOut(fadeOutDuration);
Background?.FadeColour(Color4.White, fadeOutDuration, Easing.OutQuint);
Background.EnableUserDim.Value = false;
storyboardReplacesBackground.Value = false;
}
protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !pauseContainer.IsPaused.Value;
protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !PausableGameplayContainer.IsPaused.Value;
private void initializeStoryboard(bool asyncLoad)
{
if (storyboardContainer == null)
if (StoryboardContainer == null || storyboard != null)
return;
var beatmap = Beatmap.Value;
@ -419,29 +429,9 @@ namespace osu.Game.Screens.Play
storyboard.Masking = true;
if (asyncLoad)
LoadComponentAsync(storyboard, storyboardContainer.Add);
LoadComponentAsync(storyboard, StoryboardContainer.Add);
else
storyboardContainer.Add(storyboard);
}
protected override void UpdateBackgroundElements()
{
if (!this.IsCurrentScreen()) return;
base.UpdateBackgroundElements();
if (ShowStoryboard.Value && storyboard == null)
initializeStoryboard(true);
var beatmap = Beatmap.Value;
var storyboardVisible = ShowStoryboard.Value && beatmap.Storyboard.HasDrawable;
storyboardContainer?
.FadeColour(OsuColour.Gray(BackgroundOpacity), BACKGROUND_FADE_DURATION, Easing.OutQuint)
.FadeTo(storyboardVisible && BackgroundOpacity > 0 ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint);
if (storyboardVisible && beatmap.Storyboard.ReplacesBackground)
Background?.FadeColour(Color4.Black, BACKGROUND_FADE_DURATION, Easing.OutQuint);
StoryboardContainer.Add(storyboard);
}
protected virtual Results CreateResults(ScoreInfo score) => new SoloResults(score);

View File

@ -17,6 +17,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.PlayerSettings;
using osuTK;
using osuTK.Graphics;
@ -78,7 +79,7 @@ namespace osu.Game.Screens.Play
Margin = new MarginPadding(25),
Children = new PlayerSettingsGroup[]
{
visualSettings = new VisualSettings(),
VisualSettings = new VisualSettings(),
new InputSettings()
}
}
@ -151,23 +152,31 @@ namespace osu.Game.Screens.Play
}
private ScheduledDelegate pushDebounce;
private VisualSettings visualSettings;
protected VisualSettings VisualSettings;
private bool readyForPush => player.LoadState == LoadState.Ready && IsHovered && GetContainingInputManager()?.DraggedDrawable == null;
protected override bool OnHover(HoverEvent e)
{
// restore our screen defaults
InitializeBackgroundElements();
if (this.IsCurrentScreen())
{
InitializeBackgroundElements();
Background.EnableUserDim.Value = false;
}
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
if (GetContainingInputManager()?.HoveredDrawables.Contains(visualSettings) == true)
if (GetContainingInputManager()?.HoveredDrawables.Contains(VisualSettings) == true)
{
// show user setting preview
// Update background elements is only being called here because blur logic still exists in Player.
// Will need to be removed when resolving https://github.com/ppy/osu/issues/4322
UpdateBackgroundElements();
if (this.IsCurrentScreen())
Background.EnableUserDim.Value = true;
}
base.OnHoverLost(e);
@ -241,6 +250,8 @@ namespace osu.Game.Screens.Play
this.FadeOut(150);
cancelLoad();
Background.EnableUserDim.Value = false;
return base.OnExiting(next);
}
@ -286,6 +297,7 @@ namespace osu.Game.Screens.Play
private readonly WorkingBeatmap beatmap;
private LoadingAnimation loading;
private Sprite backgroundSprite;
private ModDisplay modDisplay;
public bool Loading
{
@ -312,7 +324,7 @@ namespace osu.Game.Screens.Play
[BackgroundDependencyLoader]
private void load()
{
var metadata = beatmap?.BeatmapInfo?.Metadata ?? new BeatmapMetadata();
var metadata = beatmap.BeatmapInfo?.Metadata ?? new BeatmapMetadata();
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
@ -381,6 +393,14 @@ namespace osu.Game.Screens.Play
Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
},
new ModDisplay
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = 20 },
Current = beatmap.Mods
}
},
}
};

View File

@ -6,7 +6,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Screens.Backgrounds;
using osuTK;
@ -16,15 +15,12 @@ namespace osu.Game.Screens.Play
{
protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value);
protected new BackgroundScreenBeatmap Background => base.Background as BackgroundScreenBeatmap;
protected new BackgroundScreenBeatmap Background => (BackgroundScreenBeatmap)base.Background;
protected const float BACKGROUND_FADE_DURATION = 800;
protected float BackgroundOpacity => 1 - (float)DimLevel.Value;
#region User Settings
protected Bindable<double> DimLevel;
protected Bindable<double> BlurLevel;
protected Bindable<bool> ShowStoryboard;
@ -33,7 +29,6 @@ namespace osu.Game.Screens.Play
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
DimLevel = config.GetBindable<double>(OsuSetting.DimLevel);
BlurLevel = config.GetBindable<double>(OsuSetting.BlurLevel);
ShowStoryboard = config.GetBindable<bool>(OsuSetting.ShowStoryboard);
}
@ -41,9 +36,7 @@ namespace osu.Game.Screens.Play
public override void OnEntering(IScreen last)
{
base.OnEntering(last);
DimLevel.ValueChanged += _ => UpdateBackgroundElements();
BlurLevel.ValueChanged += _ => UpdateBackgroundElements();
ShowStoryboard.ValueChanged += _ => UpdateBackgroundElements();
InitializeBackgroundElements();
}
@ -66,7 +59,6 @@ namespace osu.Game.Screens.Play
{
if (!this.IsCurrentScreen()) return;
Background?.FadeColour(OsuColour.Gray(BackgroundOpacity), BACKGROUND_FADE_DURATION, Easing.OutQuint);
Background?.BlurTo(new Vector2((float)BlurLevel.Value * 25), BACKGROUND_FADE_DURATION, Easing.OutQuint);
}
}

View File

@ -9,7 +9,6 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Threading;
using osu.Framework.Timing;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Screens.Ranking;
@ -27,8 +26,7 @@ namespace osu.Game.Screens.Play
{
private readonly double startTime;
public IAdjustableClock AdjustableClock;
public IFrameBasedClock FramedClock;
public Action<double> RequestSeek;
private Button button;
private Box remainingTimeBox;
@ -54,16 +52,13 @@ namespace osu.Game.Screens.Play
Origin = Anchor.Centre;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
[BackgroundDependencyLoader(true)]
private void load(OsuColour colours, GameplayClock clock)
{
var baseClock = Clock;
if (FramedClock != null)
{
Clock = FramedClock;
ProcessCustomClock = false;
}
if (clock != null)
Clock = clock;
Children = new Drawable[]
{
@ -111,7 +106,7 @@ namespace osu.Game.Screens.Play
using (BeginAbsoluteSequence(beginFadeTime))
this.FadeOut(fade_time);
button.Action = () => AdjustableClock?.Seek(startTime - skip_required_cutoff - fade_time);
button.Action = () => RequestSeek?.Invoke(startTime - skip_required_cutoff - fade_time);
displayTime = Time.Current;

View File

@ -10,7 +10,6 @@ using osu.Game.Graphics;
using osu.Framework.Allocation;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Timing;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.UI;
@ -29,18 +28,11 @@ namespace osu.Game.Screens.Play
private readonly SongProgressGraph graph;
private readonly SongProgressInfo info;
public Action<double> OnSeek;
public Action<double> RequestSeek;
public override bool HandleNonPositionalInput => AllowSeeking;
public override bool HandlePositionalInput => AllowSeeking;
private IClock audioClock;
public IClock AudioClock
{
set => audioClock = info.AudioClock = value;
}
private double lastHitTime => ((objects.Last() as IHasEndTime)?.EndTime ?? objects.Last().StartTime) + 1;
private double firstHitTime => objects.First().StartTime;
@ -63,9 +55,14 @@ namespace osu.Game.Screens.Play
private readonly BindableBool replayLoaded = new BindableBool();
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private GameplayClock gameplayClock;
[BackgroundDependencyLoader(true)]
private void load(OsuColour colours, GameplayClock clock)
{
if (clock != null)
gameplayClock = clock;
graph.FillColour = bar.FillColour = colours.BlueLighter;
}
@ -99,7 +96,7 @@ namespace osu.Game.Screens.Play
Alpha = 0,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
OnSeek = position => OnSeek?.Invoke(position),
OnSeek = time => RequestSeek?.Invoke(time),
},
};
}
@ -157,14 +154,11 @@ namespace osu.Game.Screens.Play
if (objects == null)
return;
double position = audioClock?.CurrentTime ?? Time.Current;
double progress = (position - firstHitTime) / (lastHitTime - firstHitTime);
double position = gameplayClock?.CurrentTime ?? Time.Current;
double progress = Math.Min(1, (position - firstHitTime) / (lastHitTime - firstHitTime));
if (progress < 1)
{
bar.CurrentTime = position;
graph.Progress = (int)(graph.ColumnCount * progress);
}
bar.CurrentTime = position;
graph.Progress = (int)(graph.ColumnCount * progress);
}
}
}

View File

@ -4,7 +4,6 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using System;
@ -27,8 +26,6 @@ namespace osu.Game.Screens.Play
private const int margin = 10;
public IClock AudioClock;
public double StartTime
{
set => startTime = value;
@ -39,9 +36,14 @@ namespace osu.Game.Screens.Play
set => endTime = value;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private GameplayClock gameplayClock;
[BackgroundDependencyLoader(true)]
private void load(OsuColour colours, GameplayClock clock)
{
if (clock != null)
gameplayClock = clock;
Children = new Drawable[]
{
timeCurrent = new OsuSpriteText
@ -80,7 +82,9 @@ namespace osu.Game.Screens.Play
{
base.Update();
double songCurrentTime = AudioClock.CurrentTime - startTime;
var time = gameplayClock?.CurrentTime ?? Time.Current;
double songCurrentTime = time - startTime;
int currentPercent = Math.Max(0, Math.Min(100, (int)(songCurrentTime / songLength * 100)));
int currentSecond = (int)Math.Floor(songCurrentTime / 1000.0);
@ -93,7 +97,7 @@ namespace osu.Game.Screens.Play
if (currentSecond != previousSecond && songCurrentTime < songLength)
{
timeCurrent.Text = formatTime(TimeSpan.FromSeconds(currentSecond));
timeLeft.Text = formatTime(TimeSpan.FromMilliseconds(endTime - AudioClock.CurrentTime));
timeLeft.Text = formatTime(TimeSpan.FromMilliseconds(endTime - time));
previousSecond = currentSecond;
}

View File

@ -242,7 +242,11 @@ namespace osu.Game.Screens.Play
// Reverse drawableRows so when iterating through them they start at the bottom
drawableRows.Reverse();
}
protected override void LoadComplete()
{
base.LoadComplete();
fillActive();
}

View File

@ -0,0 +1,48 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Overlays.Dialog;
using osu.Game.Scoring;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace osu.Game.Screens.Select
{
public class BeatmapClearScoresDialog : PopupDialog
{
private ScoreManager scoreManager;
public BeatmapClearScoresDialog(BeatmapInfo beatmap, Action onCompletion)
{
BodyText = $@"{beatmap.Metadata?.Artist} - {beatmap.Metadata?.Title}";
Icon = FontAwesome.fa_eraser;
HeaderText = @"Clearing all local scores. Are you sure?";
Buttons = new PopupDialogButton[]
{
new PopupDialogOkButton
{
Text = @"Yes. Please.",
Action = () =>
{
Task.Run(() => scoreManager.Delete(scoreManager.QueryScores(s => !s.DeletePending && s.Beatmap.ID == beatmap.ID).ToList()))
.ContinueWith(_ => onCompletion);
}
},
new PopupDialogCancelButton
{
Text = @"No, I'm still attached.",
},
};
}
[BackgroundDependencyLoader]
private void load(ScoreManager scoreManager)
{
this.scoreManager = scoreManager;
}
}
}

View File

@ -55,7 +55,7 @@ namespace osu.Game.Screens.Select.Leaderboards
{
if (Scope == BeatmapLeaderboardScope.Local)
{
Scores = scoreManager.QueryScores(s => s.Beatmap.ID == Beatmap.ID).ToArray();
Scores = scoreManager.QueryScores(s => !s.DeletePending && s.Beatmap.ID == Beatmap.ID).ToArray();
PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores;
return null;
}

View File

@ -60,9 +60,12 @@ namespace osu.Game.Screens.Select
if (base.OnExiting(next))
return true;
Beatmap.Value = beatmaps.GetWorkingBeatmap(CurrentItem.Value?.Beatmap);
Beatmap.Value.Mods.Value = selectedMods.Value = CurrentItem.Value?.RequiredMods;
Ruleset.Value = CurrentItem.Value?.Ruleset;
if (CurrentItem.Value != null)
{
Ruleset.Value = CurrentItem.Value.Ruleset;
Beatmap.Value = beatmaps.GetWorkingBeatmap(CurrentItem.Value.Beatmap);
Beatmap.Value.Mods.Value = selectedMods.Value = CurrentItem.Value.RequiredMods;
}
Beatmap.Disabled = true;
Ruleset.Disabled = true;

View File

@ -1,11 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osuTK;
using osuTK.Input;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
@ -13,8 +8,8 @@ using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Logging;
using osu.Framework.Input.Events;
using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
@ -31,7 +26,12 @@ using osu.Game.Screens.Menu;
using osu.Game.Screens.Play;
using osu.Game.Screens.Select.Options;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
using System;
using System.Collections.Generic;
using System.Linq;
namespace osu.Game.Screens.Select
{
@ -226,7 +226,7 @@ namespace osu.Game.Screens.Select
BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.fa_trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue);
BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.fa_times_circle_o, colours.Purple, null, Key.Number1);
BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.fa_eraser, colours.Purple, null, Key.Number2);
BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.fa_eraser, colours.Purple, () => clearScores(Beatmap.Value.BeatmapInfo), Key.Number2);
}
if (this.beatmaps == null)
@ -582,7 +582,7 @@ namespace osu.Game.Screens.Select
}
}
private void onBeatmapSetAdded(BeatmapSetInfo s, bool existing, bool silent) => Carousel.UpdateBeatmapSet(s);
private void onBeatmapSetAdded(BeatmapSetInfo s, bool existing) => Carousel.UpdateBeatmapSet(s);
private void onBeatmapSetRemoved(BeatmapSetInfo s) => Carousel.RemoveBeatmapSet(s);
private void onBeatmapRestored(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID));
private void onBeatmapHidden(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID));
@ -621,6 +621,15 @@ namespace osu.Game.Screens.Select
dialogOverlay?.Push(new BeatmapDeleteDialog(beatmap));
}
private void clearScores(BeatmapInfo beatmap)
{
if (beatmap == null || beatmap.ID <= 0) return;
dialogOverlay?.Push(new BeatmapClearScoresDialog(beatmap, () =>
// schedule done here rather than inside the dialog as the dialog may fade out and never callback.
Schedule(() => BeatmapDetails.Leaderboard.RefreshScores())));
}
public override bool OnPressed(GlobalAction action)
{
if (!this.IsCurrentScreen()) return false;

View File

@ -1,22 +1,16 @@
// 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.Linq;
using Microsoft.EntityFrameworkCore;
using osu.Framework.Platform;
using osu.Game.Database;
namespace osu.Game.Skinning
{
public class SkinStore : MutableDatabaseBackedStore<SkinInfo>
public class SkinStore : MutableDatabaseBackedStoreWithFileIncludes<SkinInfo, SkinFileInfo>
{
public SkinStore(DatabaseContextFactory contextFactory, Storage storage = null)
: base(contextFactory, storage)
{
}
protected override IQueryable<SkinInfo> AddIncludesForConsumption(IQueryable<SkinInfo> query) =>
base.AddIncludesForConsumption(query)
.Include(s => s.Files).ThenInclude(f => f.FileInfo);
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.IO;
using System.Text;
using osu.Game.Beatmaps;
@ -21,6 +22,18 @@ namespace osu.Game.Tests.Beatmaps
HitObjects = baseBeatmap.HitObjects;
BeatmapInfo.Ruleset = ruleset;
BeatmapInfo.BeatmapSet.Metadata = BeatmapInfo.Metadata;
BeatmapInfo.BeatmapSet.Beatmaps = new List<BeatmapInfo> { BeatmapInfo };
BeatmapInfo.BeatmapSet.OnlineInfo = new BeatmapSetOnlineInfo
{
Status = BeatmapSetOnlineStatus.Ranked,
Covers = new BeatmapSetOnlineCovers
{
Cover = "https://assets.ppy.sh/beatmaps/163112/covers/cover.jpg",
Card = "https://assets.ppy.sh/beatmaps/163112/covers/card.jpg",
List = "https://assets.ppy.sh/beatmaps/163112/covers/list.jpg"
}
};
}
private static Beatmap createTestBeatmap()

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