1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 05:02:55 +08:00

Merge branch 'master' into show-mods-on-loader

This commit is contained in:
Dan Balasescu 2019-03-05 17:46:35 +09:00 committed by GitHub
commit bb6c83ab25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 302 additions and 218 deletions

View File

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

View File

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

View File

@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
CatchHitObject nextObject = objectWithDroplets[i + 1]; CatchHitObject nextObject = objectWithDroplets[i + 1];
int thisDirection = nextObject.X > currentObject.X ? 1 : -1; int thisDirection = nextObject.X > currentObject.X ? 1 : -1;
double timeToNext = nextObject.StartTime - currentObject.StartTime; double timeToNext = nextObject.StartTime - currentObject.StartTime - 1000f / 60f / 4; // 1/4th of a frame of grace time, taken from osu-stable
double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth); double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth);
float distanceToHyper = (float)(timeToNext * CatcherArea.Catcher.BASE_SPEED - distanceToNext); float distanceToHyper = (float)(timeToNext * CatcherArea.Catcher.BASE_SPEED - distanceToNext);
if (distanceToHyper < 0) if (distanceToHyper < 0)

View File

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

View File

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

View File

@ -162,7 +162,7 @@ namespace osu.Game.Rulesets.Mania
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame(); public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame();
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaConfigManager(settings, RulesetInfo); public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaRulesetConfigManager(settings, RulesetInfo);
public override RulesetSettingsSubsection CreateSettings() => new ManiaSettingsSubsection(this); public override RulesetSettingsSubsection CreateSettings() => new ManiaSettingsSubsection(this);

View File

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

View File

@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.UI
public IEnumerable<BarLine> BarLines; public IEnumerable<BarLine> BarLines;
protected new ManiaConfigManager Config => (ManiaConfigManager)base.Config; protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config;
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>(); private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>();
@ -74,10 +74,10 @@ namespace osu.Game.Rulesets.Mania.UI
{ {
BarLines.ForEach(Playfield.Add); BarLines.ForEach(Playfield.Add);
Config.BindWith(ManiaSetting.ScrollDirection, configDirection); Config.BindWith(ManiaRulesetSetting.ScrollDirection, configDirection);
configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true); configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true);
Config.BindWith(ManiaSetting.ScrollTime, TimeRange); Config.BindWith(ManiaRulesetSetting.ScrollTime, TimeRange);
} }
/// <summary> /// <summary>

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

View File

@ -16,8 +16,11 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Legacy;
using osu.Game.Configuration;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Difficulty; using osu.Game.Rulesets.Osu.Difficulty;
using osu.Game.Scoring; using osu.Game.Scoring;
@ -144,12 +147,14 @@ namespace osu.Game.Rulesets.Osu
public override string ShortName => "osu"; public override string ShortName => "osu";
public override RulesetSettingsSubsection CreateSettings() => new OsuSettings(this); public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this);
public override int? LegacyID => 0; public override int? LegacyID => 0;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame(); public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame();
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo);
public OsuRuleset(RulesetInfo rulesetInfo = null) public OsuRuleset(RulesetInfo rulesetInfo = null)
: base(rulesetInfo) : base(rulesetInfo)
{ {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -33,7 +33,7 @@ namespace osu.Game.Database
where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete
where TFileModel : INamedFileInfo, new() where TFileModel : INamedFileInfo, new()
{ {
public delegate void ItemAddedDelegate(TModel model, bool existing, bool silent); public delegate void ItemAddedDelegate(TModel model, bool existing);
/// <summary> /// <summary>
/// Set an endpoint for notifications to be posted to. /// Set an endpoint for notifications to be posted to.
@ -113,7 +113,7 @@ namespace osu.Game.Database
ContextFactory = contextFactory; ContextFactory = contextFactory;
ModelStore = modelStore; 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)); ModelStore.ItemRemoved += s => handleEvent(() => ItemRemoved?.Invoke(s));
Files = new FileStore(contextFactory, storage); Files = new FileStore(contextFactory, storage);
@ -131,14 +131,18 @@ namespace osu.Game.Database
/// <param name="paths">One or more archive locations on disk.</param> /// <param name="paths">One or more archive locations on disk.</param>
public void Import(params string[] paths) public void Import(params string[] paths)
{ {
var notification = new ProgressNotification var notification = new ProgressNotification { State = ProgressNotificationState.Active };
{
Text = "Import is initialising...",
Progress = 0,
State = ProgressNotificationState.Active,
};
PostNotification?.Invoke(notification); 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>(); List<TModel> imported = new List<TModel>();
@ -151,7 +155,18 @@ namespace osu.Game.Database
try 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)); imported.Add(Import(path));
@ -171,13 +186,20 @@ namespace osu.Game.Database
} }
else else
{ {
notification.CompletionText = $"Imported {current} {typeof(TModel).Name.Replace("Info", "").ToLower()}s!"; notification.CompletionText = imported.Count == 1
notification.CompletionClickAction += () => ? $"Imported {imported.First()}!"
: $"Imported {current} {term}s!";
if (imported.Count > 0 && PresentImport != null)
{ {
if (imported.Count > 0) notification.CompletionText += " Click to view.";
PresentCompletedImport(imported); notification.CompletionClickAction = () =>
{
PresentImport?.Invoke(imported);
return true; return true;
}; };
}
notification.State = ProgressNotificationState.Completed; notification.State = ProgressNotificationState.Completed;
} }
} }
@ -210,9 +232,10 @@ namespace osu.Game.Database
return import; 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> /// <summary>
/// Import an item from an <see cref="ArchiveReader"/>. /// Import an item from an <see cref="ArchiveReader"/>.
@ -228,7 +251,7 @@ namespace osu.Game.Database
model.Hash = computeHash(archive); model.Hash = computeHash(archive);
return Import(model, false, archive); return Import(model, archive);
} }
catch (Exception e) catch (Exception e)
{ {
@ -262,9 +285,8 @@ namespace osu.Game.Database
/// Import an item from a <see cref="TModel"/>. /// Import an item from a <see cref="TModel"/>.
/// </summary> /// </summary>
/// <param name="item">The model to be imported.</param> /// <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> /// <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(); delayEvents();
@ -284,7 +306,7 @@ namespace osu.Game.Database
{ {
Undelete(existing); Undelete(existing);
Logger.Log($"Found existing {typeof(TModel)} for {item} (ID {existing.ID}). Skipping import.", LoggingTarget.Database); 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; return existing;
} }
@ -294,7 +316,7 @@ namespace osu.Game.Database
Populate(item, archive); Populate(item, archive);
// import to store // import to store
ModelStore.Add(item, silent); ModelStore.Add(item);
} }
catch (Exception e) catch (Exception e)
{ {

View File

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

View File

@ -24,6 +24,12 @@ namespace osu.Game.Graphics.Containers
protected override bool BlockNonPositionalInput => true; protected override bool BlockNonPositionalInput => true;
/// <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)] [Resolved(CanBeNull = true)]
private OsuGame osuGame { get; set; } private OsuGame osuGame { get; set; }
@ -95,7 +101,7 @@ namespace osu.Game.Graphics.Containers
if (OverlayActivationMode.Value != OverlayActivation.Disabled) if (OverlayActivationMode.Value != OverlayActivation.Disabled)
{ {
if (PlaySamplesOnStateChange) samplePopIn?.Play(); if (PlaySamplesOnStateChange) samplePopIn?.Play();
if (BlockScreenWideMouse) osuGame?.AddBlockingOverlay(this); if (BlockScreenWideMouse && DimMainContent) osuGame?.AddBlockingOverlay(this);
} }
else else
State = Visibility.Hidden; State = Visibility.Hidden;

View File

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

View File

@ -167,8 +167,6 @@ namespace osu.Game
{ {
this.frameworkConfig = frameworkConfig; this.frameworkConfig = frameworkConfig;
ScoreManager.ItemAdded += (score, _, silent) => Schedule(() => LoadScore(score, silent));
if (!Host.IsPrimaryInstance) if (!Host.IsPrimaryInstance)
{ {
Logger.Log(@"osu! does not support multiple running instances.", LoggingTarget.Runtime, LogLevel.Error); Logger.Log(@"osu! does not support multiple running instances.", LoggingTarget.Runtime, LogLevel.Error);
@ -217,63 +215,12 @@ namespace osu.Game
externalLinkOpener.OpenUrlExternally(url); externalLinkOpener.OpenUrlExternally(url);
} }
private ScheduledDelegate scoreLoad;
/// <summary> /// <summary>
/// Show a beatmap set as an overlay. /// Show a beatmap set as an overlay.
/// </summary> /// </summary>
/// <param name="setId">The set to display.</param> /// <param name="setId">The set to display.</param>
public void ShowBeatmapSet(int setId) => beatmapSetOverlay.FetchAndShowBeatmapSet(setId); 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> /// <summary>
/// Show a user's profile as an overlay. /// Show a user's profile as an overlay.
/// </summary> /// </summary>
@ -286,19 +233,44 @@ namespace osu.Game
/// <param name="beatmapId">The beatmap to show.</param> /// <param name="beatmapId">The beatmap to show.</param>
public void ShowBeatmap(int beatmapId) => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId); 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) var databasedSet = beatmap.OnlineBeatmapSetID != null
return; ? BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID)
: BeatmapManager.QueryBeatmapSet(s => s.Hash == beatmap.Hash);
scoreLoad?.Cancel(); if (databasedSet == null)
if (menuScreen == null)
{ {
scoreLoad = Schedule(() => LoadScore(score, false)); Logger.Log("The requested beatmap could not be loaded.", LoggingTarget.Information);
return; 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 databasedScore = ScoreManager.GetScore(score);
var databasedScoreInfo = databasedScore.ScoreInfo; var databasedScoreInfo = databasedScore.ScoreInfo;
if (databasedScore.Replay == null) if (databasedScore.Replay == null)
@ -314,14 +286,40 @@ namespace osu.Game
return; 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 notifications.Post(new SimpleNotification
{ {
Text = $"Click here to watch {databasedScoreInfo.User.Username} on {databasedScoreInfo.Beatmap}", Text = $"Click here to {taskName}",
Activated = () => Activated = () =>
{ {
loadScore(); performFromMainMenu(action, taskName, targetScreen, true);
return true; return true;
} }
}); });
@ -329,24 +327,26 @@ namespace osu.Game
return; 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) action();
{
menuScreen.MakeCurrent();
this.Delay(500).Schedule(loadScore, out scoreLoad);
return; return;
} }
ruleset.Value = databasedScoreInfo.Ruleset; // all conditions have been met to continue with the action.
if (menuScreen?.IsCurrentScreen() == true && !Beatmap.Disabled)
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); {
Beatmap.Value.Mods.Value = databasedScoreInfo.Mods; action();
return;
menuScreen.Push(new PlayerLoader(() => new ReplayPlayer(databasedScore)));
} }
// menuScreen may not be initialised yet (null check required).
menuScreen?.MakeCurrent();
performFromMainMenuTask = Schedule(() => performFromMainMenu(action, taskName));
} }
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
@ -370,8 +370,10 @@ namespace osu.Game
BeatmapManager.PostNotification = n => notifications?.Post(n); BeatmapManager.PostNotification = n => notifications?.Post(n);
BeatmapManager.GetStableStorage = GetStorageForStableInstall; 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; Container logoContainer;

View File

@ -137,7 +137,7 @@ namespace osu.Game.Overlays.Chat
{ {
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = 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) new MessageSender(message.Sender)
{ {

View File

@ -118,7 +118,7 @@ namespace osu.Game.Overlays.Direct
private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null)); 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); private void setRemoved(BeatmapSetInfo s) => setDownloadStateFromManager(s, DownloadState.NotDownloaded);

View File

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

View File

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

View File

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

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 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) if (existing)
return; return;

View File

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

View File

@ -178,5 +178,7 @@ namespace osu.Game.Scoring
{ {
public string Acronym { get; set; } public string Acronym { get; set; }
} }
public override string ToString() => $"{User} playing {Beatmap}";
} }
} }

View File

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

View File

@ -19,7 +19,7 @@ namespace osu.Game.Screens
/// <summary> /// <summary>
/// Whether a top-level component should be allowed to exit the current screen to, for example, /// 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> /// </summary>
bool AllowExternalScreenChange { get; } bool AllowExternalScreenChange { get; }

View File

@ -5,14 +5,17 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Online.API;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Users;
namespace osu.Game.Screens.Menu namespace osu.Game.Screens.Menu
{ {
@ -33,13 +36,15 @@ namespace osu.Game.Screens.Menu
private const float icon_y = -85; private const float icon_y = -85;
private readonly Bindable<User> currentUser = new Bindable<User>();
public Disclaimer() public Disclaimer()
{ {
ValidForResume = false; ValidForResume = false;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours, APIAccess api)
{ {
InternalChildren = new Drawable[] 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.AddParagraph("Things may not work as expected", t => t.Font = t.Font.With(size: 20));
textFlow.NewParagraph(); 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.AddParagraph("Detailed bug reports are welcomed via github issues.", format);
textFlow.NewParagraph(); textFlow.NewParagraph();
@ -96,6 +101,13 @@ namespace osu.Game.Screens.Menu
}).First()); }).First());
iconColour = colours.Yellow; 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() protected override void LoadComplete()

View File

@ -54,7 +54,7 @@ namespace osu.Game.Screens.Multi.Match.Components
hasBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmap.OnlineBeatmapID) != null; 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) if (Beatmap.Value == null)
return; return;

View File

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

View File

@ -298,7 +298,7 @@ namespace osu.Game.Screens.Play
var score = CreateScore(); var score = CreateScore();
if (RulesetContainer.ReplayScore == null) if (RulesetContainer.ReplayScore == null)
scoreManager.Import(score, true); scoreManager.Import(score);
this.Push(CreateResults(score)); this.Push(CreateResults(score));

View File

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

View File

@ -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 onBeatmapSetRemoved(BeatmapSetInfo s) => Carousel.RemoveBeatmapSet(s);
private void onBeatmapRestored(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); 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)); private void onBeatmapHidden(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID));

View File

@ -230,6 +230,8 @@
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTLINE_TYPE_PARAMETER_LIST/@EntryValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTLINE_TYPE_PARAMETER_LIST/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">NEXT_LINE</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">NEXT_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INITIALIZER_BRACES/@EntryValue">NEXT_LINE</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INITIALIZER_BRACES/@EntryValue">NEXT_LINE</s:String>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_CODE/@EntryValue">1</s:Int64>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue">1</s:Int64>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/LINE_FEED_AT_FILE_END/@EntryValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/LINE_FEED_AT_FILE_END/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>