1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-13 19:53:23 +08:00

Merge branch 'master' into fix-countdown-refresh

This commit is contained in:
Salman Ahmed 2022-03-30 16:26:12 +03:00 committed by GitHub
commit 0f144f6801
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 268 additions and 93 deletions

View File

@ -27,7 +27,7 @@ namespace osu.Game.Benchmarks
storage = new TemporaryNativeStorage("realm-benchmark"); storage = new TemporaryNativeStorage("realm-benchmark");
storage.DeleteDirectory(string.Empty); storage.DeleteDirectory(string.Empty);
realm = new RealmAccess(storage, "client"); realm = new RealmAccess(storage, OsuGameBase.CLIENT_DATABASE_FILENAME);
realm.Run(r => realm.Run(r =>
{ {

View File

@ -39,7 +39,7 @@ namespace osu.Game.Tests.Database
// ReSharper disable once AccessToDisposedClosure // ReSharper disable once AccessToDisposedClosure
var testStorage = new OsuStorage(host, storage.GetStorageForDirectory(caller)); var testStorage = new OsuStorage(host, storage.GetStorageForDirectory(caller));
using (var realm = new RealmAccess(testStorage, "client")) using (var realm = new RealmAccess(testStorage, OsuGameBase.CLIENT_DATABASE_FILENAME))
{ {
Logger.Log($"Running test using realm file {testStorage.GetFullPath(realm.Filename)}"); Logger.Log($"Running test using realm file {testStorage.GetFullPath(realm.Filename)}");
testAction(realm, testStorage); testAction(realm, testStorage);
@ -62,7 +62,7 @@ namespace osu.Game.Tests.Database
{ {
var testStorage = storage.GetStorageForDirectory(caller); var testStorage = storage.GetStorageForDirectory(caller);
using (var realm = new RealmAccess(testStorage, "client")) using (var realm = new RealmAccess(testStorage, OsuGameBase.CLIENT_DATABASE_FILENAME))
{ {
Logger.Log($"Running test using realm file {testStorage.GetFullPath(realm.Filename)}"); Logger.Log($"Running test using realm file {testStorage.GetFullPath(realm.Filename)}");
await testAction(realm, testStorage); await testAction(realm, testStorage);

View File

@ -143,14 +143,14 @@ namespace osu.Game.Tests.NonVisual
Assert.That(osuStorage, Is.Not.Null); Assert.That(osuStorage, Is.Not.Null);
// In the following tests, realm files are ignored as // In the following tests, realm files are ignored as
// - in the case of checking the source, interacting with the pipe files (client.realm.note) may // - in the case of checking the source, interacting with the pipe files (.realm.note) may
// lead to unexpected behaviour. // lead to unexpected behaviour.
// - in the case of checking the destination, the files may have already been recreated by the game // - in the case of checking the destination, the files may have already been recreated by the game
// as part of the standard migration flow. // as part of the standard migration flow.
foreach (string file in osuStorage.IgnoreFiles) foreach (string file in osuStorage.IgnoreFiles)
{ {
if (!file.Contains("realm", StringComparison.Ordinal)) if (!file.Contains(".realm", StringComparison.Ordinal))
{ {
Assert.That(File.Exists(Path.Combine(originalDirectory, file))); Assert.That(File.Exists(Path.Combine(originalDirectory, file)));
Assert.That(storage.Exists(file), Is.False, () => $"{file} exists in destination when it was expected to be ignored"); Assert.That(storage.Exists(file), Is.False, () => $"{file} exists in destination when it was expected to be ignored");
@ -159,7 +159,7 @@ namespace osu.Game.Tests.NonVisual
foreach (string dir in osuStorage.IgnoreDirectories) foreach (string dir in osuStorage.IgnoreDirectories)
{ {
if (!dir.Contains("realm", StringComparison.Ordinal)) if (!dir.Contains(".realm", StringComparison.Ordinal))
{ {
Assert.That(Directory.Exists(Path.Combine(originalDirectory, dir))); Assert.That(Directory.Exists(Path.Combine(originalDirectory, dir)));
Assert.That(storage.Exists(dir), Is.False, () => $"{dir} exists in destination when it was expected to be ignored"); Assert.That(storage.Exists(dir), Is.False, () => $"{dir} exists in destination when it was expected to be ignored");
@ -188,19 +188,17 @@ namespace osu.Game.Tests.NonVisual
{ {
var osu = LoadOsuIntoHost(host); var osu = LoadOsuIntoHost(host);
const string database_filename = "client.realm";
Assert.DoesNotThrow(() => osu.Migrate(customPath)); Assert.DoesNotThrow(() => osu.Migrate(customPath));
Assert.That(File.Exists(Path.Combine(customPath, database_filename))); Assert.That(File.Exists(Path.Combine(customPath, OsuGameBase.CLIENT_DATABASE_FILENAME)));
Assert.DoesNotThrow(() => osu.Migrate(customPath2)); Assert.DoesNotThrow(() => osu.Migrate(customPath2));
Assert.That(File.Exists(Path.Combine(customPath2, database_filename))); Assert.That(File.Exists(Path.Combine(customPath2, OsuGameBase.CLIENT_DATABASE_FILENAME)));
// some files may have been left behind for whatever reason, but that's not what we're testing here. // some files may have been left behind for whatever reason, but that's not what we're testing here.
cleanupPath(customPath); cleanupPath(customPath);
Assert.DoesNotThrow(() => osu.Migrate(customPath)); Assert.DoesNotThrow(() => osu.Migrate(customPath));
Assert.That(File.Exists(Path.Combine(customPath, database_filename))); Assert.That(File.Exists(Path.Combine(customPath, OsuGameBase.CLIENT_DATABASE_FILENAME)));
} }
finally finally
{ {
@ -233,6 +231,46 @@ namespace osu.Game.Tests.NonVisual
} }
} }
[Test]
public void TestMigrationFailsOnExistingData()
{
string customPath = prepareCustomPath();
string customPath2 = prepareCustomPath();
using (var host = new CustomTestHeadlessGameHost())
{
try
{
var osu = LoadOsuIntoHost(host);
var storage = osu.Dependencies.Get<Storage>();
var osuStorage = storage as OsuStorage;
string originalDirectory = storage.GetFullPath(".");
Assert.DoesNotThrow(() => osu.Migrate(customPath));
Assert.That(File.Exists(Path.Combine(customPath, OsuGameBase.CLIENT_DATABASE_FILENAME)));
Directory.CreateDirectory(customPath2);
File.Copy(Path.Combine(customPath, OsuGameBase.CLIENT_DATABASE_FILENAME), Path.Combine(customPath2, OsuGameBase.CLIENT_DATABASE_FILENAME));
// Fails because file already exists.
Assert.Throws<ArgumentException>(() => osu.Migrate(customPath2));
osuStorage?.ChangeDataPath(customPath2);
Assert.That(osuStorage?.CustomStoragePath, Is.EqualTo(customPath2));
Assert.That(new StreamReader(Path.Combine(originalDirectory, "storage.ini")).ReadToEnd().Contains($"FullPath = {customPath2}"));
}
finally
{
host.Exit();
cleanupPath(customPath);
cleanupPath(customPath2);
}
}
}
[Test] [Test]
public void TestMigrationToNestedTargetFails() public void TestMigrationToNestedTargetFails()
{ {

View File

@ -3,7 +3,9 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Database; using osu.Game.Database;
@ -28,6 +30,23 @@ namespace osu.Game.Tests.Visual.Navigation
stream.CopyTo(outStream); stream.CopyTo(outStream);
} }
[SetUp]
public void SetUp()
{
if (RuntimeInfo.OS == RuntimeInfo.Platform.macOS && RuntimeInformation.OSArchitecture == Architecture.Arm64)
Assert.Ignore("EF-to-realm migrations are not supported on M1 ARM architectures.");
}
public override void SetUpSteps()
{
// base SetUpSteps are executed before the above SetUp, therefore early-return to allow ignoring test properly.
// attempting to ignore here would yield a TargetInvocationException instead.
if (RuntimeInfo.OS == RuntimeInfo.Platform.macOS && RuntimeInformation.OSArchitecture == Architecture.Arm64)
return;
base.SetUpSteps();
}
[Test] [Test]
public void TestMigration() public void TestMigration()
{ {

View File

@ -116,7 +116,7 @@ namespace osu.Game.Tests.Visual.SongSelect
private void testBeatmapLabels(Ruleset ruleset) private void testBeatmapLabels(Ruleset ruleset)
{ {
AddAssert("check version", () => infoWedge.Info.VersionLabel.Current.Value == $"{ruleset.ShortName}Version"); AddAssert("check version", () => infoWedge.Info.VersionLabel.Current.Value == $"{ruleset.ShortName}Version");
AddAssert("check title", () => infoWedge.Info.TitleLabel.Current.Value == $"{ruleset.ShortName}Source — {ruleset.ShortName}Title"); AddAssert("check title", () => infoWedge.Info.TitleLabel.Current.Value == $"{ruleset.ShortName}Title");
AddAssert("check artist", () => infoWedge.Info.ArtistLabel.Current.Value == $"{ruleset.ShortName}Artist"); AddAssert("check artist", () => infoWedge.Info.ArtistLabel.Current.Value == $"{ruleset.ShortName}Artist");
AddAssert("check author", () => infoWedge.Info.MapperContainer.ChildrenOfType<OsuSpriteText>().Any(s => s.Current.Value == $"{ruleset.ShortName}Author")); AddAssert("check author", () => infoWedge.Info.MapperContainer.ChildrenOfType<OsuSpriteText>().Any(s => s.Current.Value == $"{ruleset.ShortName}Author"));
} }

View File

@ -68,7 +68,9 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("reset defaults", () => AddStep("reset defaults", () =>
{ {
Ruleset.Value = new OsuRuleset().RulesetInfo; Ruleset.Value = new OsuRuleset().RulesetInfo;
Beatmap.SetDefault(); Beatmap.SetDefault();
SelectedMods.SetDefault();
songSelect = null; songSelect = null;
}); });
@ -563,7 +565,7 @@ namespace osu.Game.Tests.Visual.SongSelect
} }
[Test] [Test]
public void TestAutoplayViaCtrlEnter() public void TestAutoplayShortcut()
{ {
addRulesetImportStep(0); addRulesetImportStep(0);
@ -580,11 +582,65 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader); AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader);
AddAssert("autoplay enabled", () => songSelect.Mods.Value.FirstOrDefault() is ModAutoplay); AddAssert("autoplay selected", () => songSelect.Mods.Value.Single() is ModAutoplay);
AddUntilStep("wait for return to ss", () => songSelect.IsCurrentScreen()); AddUntilStep("wait for return to ss", () => songSelect.IsCurrentScreen());
AddAssert("mod disabled", () => songSelect.Mods.Value.Count == 0); AddAssert("no mods selected", () => songSelect.Mods.Value.Count == 0);
}
[Test]
public void TestAutoplayShortcutKeepsAutoplayIfSelectedAlready()
{
addRulesetImportStep(0);
createSongSelect();
AddUntilStep("wait for selection", () => !Beatmap.IsDefault);
changeMods(new OsuModAutoplay());
AddStep("press ctrl+enter", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.Key(Key.Enter);
InputManager.ReleaseKey(Key.ControlLeft);
});
AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader);
AddAssert("autoplay selected", () => songSelect.Mods.Value.Single() is ModAutoplay);
AddUntilStep("wait for return to ss", () => songSelect.IsCurrentScreen());
AddAssert("autoplay still selected", () => songSelect.Mods.Value.Single() is ModAutoplay);
}
[Test]
public void TestAutoplayShortcutReturnsInitialModsOnExit()
{
addRulesetImportStep(0);
createSongSelect();
AddUntilStep("wait for selection", () => !Beatmap.IsDefault);
changeMods(new OsuModRelax());
AddStep("press ctrl+enter", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.Key(Key.Enter);
InputManager.ReleaseKey(Key.ControlLeft);
});
AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader);
AddAssert("only autoplay selected", () => songSelect.Mods.Value.Single() is ModAutoplay);
AddUntilStep("wait for return to ss", () => songSelect.IsCurrentScreen());
AddAssert("relax returned", () => songSelect.Mods.Value.Single() is ModRelax);
} }
[Test] [Test]

View File

@ -212,7 +212,7 @@ namespace osu.Game.Database
if (realm.All<ScoreInfo>().Any()) if (realm.All<ScoreInfo>().Any())
{ {
Logger.Log(@"Recovery aborted as the existing database has scores set already.", LoggingTarget.Database); Logger.Log(@"Recovery aborted as the existing database has scores set already.", LoggingTarget.Database);
Logger.Log(@"To perform recovery, delete client.realm while osu! is not running.", LoggingTarget.Database); Logger.Log($@"To perform recovery, delete {OsuGameBase.CLIENT_DATABASE_FILENAME} while osu! is not running.", LoggingTarget.Database);
return; return;
} }
} }

View File

@ -36,15 +36,15 @@ namespace osu.Game.IO
public override string[] IgnoreDirectories => new[] public override string[] IgnoreDirectories => new[]
{ {
"cache", "cache",
"client.realm.management" $"{OsuGameBase.CLIENT_DATABASE_FILENAME}.management",
}; };
public override string[] IgnoreFiles => new[] public override string[] IgnoreFiles => new[]
{ {
"framework.ini", "framework.ini",
"storage.ini", "storage.ini",
"client.realm.note", $"{OsuGameBase.CLIENT_DATABASE_FILENAME}.note",
"client.realm.lock", $"{OsuGameBase.CLIENT_DATABASE_FILENAME}.lock",
}; };
public OsuStorage(GameHost host, Storage defaultStorage) public OsuStorage(GameHost host, Storage defaultStorage)
@ -64,12 +64,22 @@ namespace osu.Game.IO
/// </summary> /// </summary>
public void ResetCustomStoragePath() public void ResetCustomStoragePath()
{ {
storageConfig.SetValue(StorageConfig.FullPath, string.Empty); ChangeDataPath(string.Empty);
storageConfig.Save();
ChangeTargetStorage(defaultStorage); ChangeTargetStorage(defaultStorage);
} }
/// <summary>
/// Updates the target data path without immediately switching.
/// This does NOT migrate any data.
/// The game should immediately be restarted after calling this.
/// </summary>
public void ChangeDataPath(string newPath)
{
storageConfig.SetValue(StorageConfig.FullPath, newPath);
storageConfig.Save();
}
/// <summary> /// <summary>
/// Attempts to change to the user's custom storage path. /// Attempts to change to the user's custom storage path.
/// </summary> /// </summary>
@ -117,8 +127,7 @@ namespace osu.Game.IO
{ {
bool cleanupSucceeded = base.Migrate(newStorage); bool cleanupSucceeded = base.Migrate(newStorage);
storageConfig.SetValue(StorageConfig.FullPath, newStorage.GetFullPath(".")); ChangeDataPath(newStorage.GetFullPath("."));
storageConfig.Save();
return cleanupSucceeded; return cleanupSucceeded;
} }

View File

@ -57,6 +57,11 @@ namespace osu.Game
public const string CLIENT_STREAM_NAME = @"lazer"; public const string CLIENT_STREAM_NAME = @"lazer";
/// <summary>
/// The filename of the main client database.
/// </summary>
public const string CLIENT_DATABASE_FILENAME = @"client.realm";
public const int SAMPLE_CONCURRENCY = 6; public const int SAMPLE_CONCURRENCY = 6;
/// <summary> /// <summary>
@ -200,7 +205,7 @@ namespace osu.Game
if (Storage.Exists(DatabaseContextFactory.DATABASE_NAME)) if (Storage.Exists(DatabaseContextFactory.DATABASE_NAME))
dependencies.Cache(EFContextFactory = new DatabaseContextFactory(Storage)); dependencies.Cache(EFContextFactory = new DatabaseContextFactory(Storage));
dependencies.Cache(realm = new RealmAccess(Storage, "client", Host.UpdateThread, EFContextFactory)); dependencies.Cache(realm = new RealmAccess(Storage, CLIENT_DATABASE_FILENAME, Host.UpdateThread, EFContextFactory));
dependencies.CacheAs<RulesetStore>(RulesetStore = new RealmRulesetStore(realm, Storage)); dependencies.CacheAs<RulesetStore>(RulesetStore = new RealmRulesetStore(realm, Storage));
dependencies.CacheAs<IRulesetStore>(RulesetStore); dependencies.CacheAs<IRulesetStore>(RulesetStore);

View File

@ -3,11 +3,14 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.IO;
using osu.Game.Overlays.Dialog;
namespace osu.Game.Overlays.Settings.Sections.Maintenance namespace osu.Game.Overlays.Settings.Sections.Maintenance
{ {
@ -16,6 +19,12 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
[Resolved] [Resolved]
private Storage storage { get; set; } private Storage storage { get; set; }
[Resolved]
private OsuGameBase game { get; set; }
[Resolved(canBeNull: true)]
private DialogOverlay dialogOverlay { get; set; }
protected override DirectoryInfo InitialPath => new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent; protected override DirectoryInfo InitialPath => new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent;
public override bool AllowExternalScreenChange => false; public override bool AllowExternalScreenChange => false;
@ -32,9 +41,30 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
try try
{ {
if (target.GetDirectories().Length > 0 || target.GetFiles().Length > 0) var directoryInfos = target.GetDirectories();
var fileInfos = target.GetFiles();
if (directoryInfos.Length > 0 || fileInfos.Length > 0)
{
// Quick test for whether there's already an osu! install at the target path.
if (fileInfos.Any(f => f.Name == OsuGameBase.CLIENT_DATABASE_FILENAME))
{
dialogOverlay.Push(new ConfirmDialog("The target directory already seems to have an osu! install. Use that data instead?", () =>
{
dialogOverlay.Push(new ConfirmDialog("To complete this operation, osu! will close. Please open it again to use the new data location.", () =>
{
(storage as OsuStorage)?.ChangeDataPath(target.FullName);
game.GracefullyExit();
}, () => { }));
},
() => { }));
return;
}
target = target.CreateSubdirectory("osu-lazer"); target = target.CreateSubdirectory("osu-lazer");
} }
}
catch (Exception e) catch (Exception e)
{ {
Logger.Log($"Error during migration: {e.Message}", level: LogLevel.Error); Logger.Log($"Error during migration: {e.Message}", level: LogLevel.Error);

View File

@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Mods
public override bool UserPlayable => false; public override bool UserPlayable => false;
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail) }; public override Type[] IncompatibleMods => new[] { typeof(ModCinema), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail) };
public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0; public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0;

View File

@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mods
public override string Description => "The whole playfield is on a wheel!"; public override string Description => "The whole playfield is on a wheel!";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override string SettingDescription => $"{SpinSpeed.Value} rpm {Direction.Value.GetDescription().ToLowerInvariant()}"; public override string SettingDescription => $"{SpinSpeed.Value:N2} rpm {Direction.Value.GetDescription().ToLowerInvariant()}";
public void Update(Playfield playfield) public void Update(Playfield playfield)
{ {

View File

@ -1,6 +1,8 @@
// 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 System.Linq;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
@ -27,6 +29,8 @@ namespace osu.Game.Rulesets.Mods
public override IconUsage? Icon => OsuIcon.ModCinema; public override IconUsage? Icon => OsuIcon.ModCinema;
public override string Description => "Watch the video without visual distractions."; public override string Description => "Watch the video without visual distractions.";
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModAutoplay)).ToArray();
public void ApplyToHUD(HUDOverlay overlay) public void ApplyToHUD(HUDOverlay overlay)
{ {
overlay.ShowHud.Value = false; overlay.ShowHud.Value = false;

View File

@ -90,12 +90,7 @@ namespace osu.Game.Scoring
/// </remarks> /// </remarks>
/// <param name="score">The <see cref="ScoreInfo"/> to retrieve the bindable for.</param> /// <param name="score">The <see cref="ScoreInfo"/> to retrieve the bindable for.</param>
/// <returns>The bindable containing the total score.</returns> /// <returns>The bindable containing the total score.</returns>
public Bindable<long> GetBindableTotalScore([NotNull] ScoreInfo score) public Bindable<long> GetBindableTotalScore([NotNull] ScoreInfo score) => new TotalScoreBindable(score, this, configManager);
{
var bindable = new TotalScoreBindable(score, this);
configManager?.BindWith(OsuSetting.ScoreDisplayMode, bindable.ScoringMode);
return bindable;
}
/// <summary> /// <summary>
/// Retrieves a bindable that represents the formatted total score string of a <see cref="ScoreInfo"/>. /// Retrieves a bindable that represents the formatted total score string of a <see cref="ScoreInfo"/>.
@ -118,7 +113,11 @@ namespace osu.Game.Scoring
public void GetTotalScore([NotNull] ScoreInfo score, [NotNull] Action<long> callback, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default) public void GetTotalScore([NotNull] ScoreInfo score, [NotNull] Action<long> callback, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default)
{ {
GetTotalScoreAsync(score, mode, cancellationToken) GetTotalScoreAsync(score, mode, cancellationToken)
.ContinueWith(task => scheduler.Add(() => callback(task.GetResultSafely())), TaskContinuationOptions.OnlyOnRanToCompletion); .ContinueWith(task => scheduler.Add(() =>
{
if (!cancellationToken.IsCancellationRequested)
callback(task.GetResultSafely());
}), TaskContinuationOptions.OnlyOnRanToCompletion);
} }
/// <summary> /// <summary>
@ -183,8 +182,7 @@ namespace osu.Game.Scoring
/// </summary> /// </summary>
private class TotalScoreBindable : Bindable<long> private class TotalScoreBindable : Bindable<long>
{ {
public readonly Bindable<ScoringMode> ScoringMode = new Bindable<ScoringMode>(); private readonly Bindable<ScoringMode> scoringMode = new Bindable<ScoringMode>();
private readonly ScoreInfo score; private readonly ScoreInfo score;
private readonly ScoreManager scoreManager; private readonly ScoreManager scoreManager;
@ -195,12 +193,14 @@ namespace osu.Game.Scoring
/// </summary> /// </summary>
/// <param name="score">The <see cref="ScoreInfo"/> to provide the total score of.</param> /// <param name="score">The <see cref="ScoreInfo"/> to provide the total score of.</param>
/// <param name="scoreManager">The <see cref="ScoreManager"/>.</param> /// <param name="scoreManager">The <see cref="ScoreManager"/>.</param>
public TotalScoreBindable(ScoreInfo score, ScoreManager scoreManager) /// <param name="configManager">The config.</param>
public TotalScoreBindable(ScoreInfo score, ScoreManager scoreManager, OsuConfigManager configManager)
{ {
this.score = score; this.score = score;
this.scoreManager = scoreManager; this.scoreManager = scoreManager;
ScoringMode.BindValueChanged(onScoringModeChanged, true); configManager?.BindWith(OsuSetting.ScoreDisplayMode, scoringMode);
scoringMode.BindValueChanged(onScoringModeChanged, true);
} }
private void onScoringModeChanged(ValueChangedEvent<ScoringMode> mode) private void onScoringModeChanged(ValueChangedEvent<ScoringMode> mode)

View File

@ -80,6 +80,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{ {
Schedule(() => Schedule(() =>
{ {
// If an error or server side trigger occurred this screen may have already exited by external means.
if (!this.IsCurrentScreen())
return;
loadingLayer.Hide(); loadingLayer.Hide();
if (t.IsFaulted) if (t.IsFaulted)

View File

@ -21,7 +21,9 @@ namespace osu.Game.Screens.Play.HUD
private uint scheduledPopOutCurrentId; private uint scheduledPopOutCurrentId;
private const double pop_out_duration = 150; private const double big_pop_out_duration = 300;
private const double small_pop_out_duration = 100;
private const double fade_out_duration = 100; private const double fade_out_duration = 100;
@ -65,32 +67,28 @@ namespace osu.Game.Screens.Play.HUD
Margin = new MarginPadding(10); Margin = new MarginPadding(10);
Scale = new Vector2(1.2f); Scale = new Vector2(1.28f);
InternalChildren = new[] InternalChildren = new[]
{ {
counterContainer = new Container counterContainer = new Container
{ {
AutoSizeAxes = Axes.Both,
AlwaysPresent = true, AlwaysPresent = true,
Children = new[] Children = new[]
{ {
popOutCount = new LegacySpriteText(LegacyFont.Combo) popOutCount = new LegacySpriteText(LegacyFont.Combo)
{ {
Alpha = 0, Alpha = 0,
Margin = new MarginPadding(0.05f),
Blending = BlendingParameters.Additive, Blending = BlendingParameters.Additive,
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
BypassAutoSizeAxes = Axes.Both, BypassAutoSizeAxes = Axes.Both,
}, },
displayedCountSpriteText = new LegacySpriteText(LegacyFont.Combo) displayedCountSpriteText = new LegacySpriteText(LegacyFont.Combo)
{ {
// Initial text and AlwaysPresent allow the counter to have a size before it first displays a combo.
// This is useful for display in the skin editor.
Text = formatCount(0),
AlwaysPresent = true,
Alpha = 0, Alpha = 0,
AlwaysPresent = true,
Anchor = Anchor.BottomLeft,
BypassAutoSizeAxes = Axes.Both,
}, },
} }
} }
@ -130,8 +128,25 @@ namespace osu.Game.Screens.Play.HUD
base.LoadComplete(); base.LoadComplete();
((IHasText)displayedCountSpriteText).Text = formatCount(Current.Value); ((IHasText)displayedCountSpriteText).Text = formatCount(Current.Value);
((IHasText)popOutCount).Text = formatCount(Current.Value);
Current.BindValueChanged(combo => updateCount(combo.NewValue == 0), true); Current.BindValueChanged(combo => updateCount(combo.NewValue == 0), true);
updateLayout();
}
private void updateLayout()
{
const float font_height_ratio = 0.625f;
const float vertical_offset = 9;
displayedCountSpriteText.OriginPosition = new Vector2(0, font_height_ratio * displayedCountSpriteText.Height + vertical_offset);
displayedCountSpriteText.Position = new Vector2(0, -(1 - font_height_ratio) * displayedCountSpriteText.Height + vertical_offset);
popOutCount.OriginPosition = new Vector2(3, font_height_ratio * popOutCount.Height + vertical_offset); // In stable, the bigger pop out scales a bit to the left
popOutCount.Position = new Vector2(0, -(1 - font_height_ratio) * popOutCount.Height + vertical_offset);
counterContainer.Size = displayedCountSpriteText.Size;
} }
private void updateCount(bool rolling) private void updateCount(bool rolling)
@ -164,27 +179,31 @@ namespace osu.Game.Screens.Play.HUD
{ {
((IHasText)popOutCount).Text = formatCount(newValue); ((IHasText)popOutCount).Text = formatCount(newValue);
popOutCount.ScaleTo(1.6f); popOutCount.ScaleTo(1.56f)
popOutCount.FadeTo(0.75f); .ScaleTo(1, big_pop_out_duration);
popOutCount.MoveTo(Vector2.Zero);
popOutCount.ScaleTo(1, pop_out_duration); popOutCount.FadeTo(0.6f)
popOutCount.FadeOut(pop_out_duration); .FadeOut(big_pop_out_duration);
popOutCount.MoveTo(displayedCountSpriteText.Position, pop_out_duration);
} }
private void transformNoPopOut(int newValue) private void transformNoPopOut(int newValue)
{ {
((IHasText)displayedCountSpriteText).Text = formatCount(newValue); ((IHasText)displayedCountSpriteText).Text = formatCount(newValue);
counterContainer.Size = displayedCountSpriteText.Size;
displayedCountSpriteText.ScaleTo(1); displayedCountSpriteText.ScaleTo(1);
} }
private void transformPopOutSmall(int newValue) private void transformPopOutSmall(int newValue)
{ {
((IHasText)displayedCountSpriteText).Text = formatCount(newValue); ((IHasText)displayedCountSpriteText).Text = formatCount(newValue);
displayedCountSpriteText.ScaleTo(1.1f);
displayedCountSpriteText.ScaleTo(1, pop_out_duration); counterContainer.Size = displayedCountSpriteText.Size;
displayedCountSpriteText.ScaleTo(1).Then()
.ScaleTo(1.1f, small_pop_out_duration / 2, Easing.In).Then()
.ScaleTo(1, small_pop_out_duration / 2, Easing.Out);
} }
private void scheduledPopOutSmall(uint id) private void scheduledPopOutSmall(uint id)
@ -212,7 +231,7 @@ namespace osu.Game.Screens.Play.HUD
Scheduler.AddDelayed(delegate Scheduler.AddDelayed(delegate
{ {
scheduledPopOutSmall(newTaskId); scheduledPopOutSmall(newTaskId);
}, pop_out_duration); }, big_pop_out_duration - 140);
} }
private void onCountRolling(int currentValue, int newValue) private void onCountRolling(int currentValue, int newValue)

View File

@ -287,12 +287,14 @@ namespace osu.Game.Screens.Select
{ {
TitleLabel = new OsuSpriteText TitleLabel = new OsuSpriteText
{ {
Current = { BindTarget = titleBinding },
Font = OsuFont.GetFont(size: 28, italics: true), Font = OsuFont.GetFont(size: 28, italics: true),
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Truncate = true, Truncate = true,
}, },
ArtistLabel = new OsuSpriteText ArtistLabel = new OsuSpriteText
{ {
Current = { BindTarget = artistBinding },
Font = OsuFont.GetFont(size: 17, italics: true), Font = OsuFont.GetFont(size: 17, italics: true),
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Truncate = true, Truncate = true,
@ -314,9 +316,6 @@ namespace osu.Game.Screens.Select
} }
}; };
titleBinding.BindValueChanged(_ => setMetadata(metadata.Source));
artistBinding.BindValueChanged(_ => setMetadata(metadata.Source), true);
addInfoLabels(); addInfoLabels();
} }
@ -352,12 +351,6 @@ namespace osu.Game.Screens.Select
}, true); }, true);
} }
private void setMetadata(string source)
{
ArtistLabel.Text = artistBinding.Value;
TitleLabel.Text = string.IsNullOrEmpty(source) ? titleBinding.Value : source + " — " + titleBinding.Value;
}
private void addInfoLabels() private void addInfoLabels()
{ {
if (working.Beatmap?.HitObjects?.Any() != true) if (working.Beatmap?.HitObjects?.Any() != true)

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
@ -14,13 +15,13 @@ using osu.Game.Scoring;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking;
using osu.Game.Users; using osu.Game.Users;
using osu.Game.Utils;
using osuTK.Input; using osuTK.Input;
namespace osu.Game.Screens.Select namespace osu.Game.Screens.Select
{ {
public class PlaySongSelect : SongSelect public class PlaySongSelect : SongSelect
{ {
private bool removeAutoModOnResume;
private OsuScreen playerLoader; private OsuScreen playerLoader;
[Resolved(CanBeNull = true)] [Resolved(CanBeNull = true)]
@ -43,25 +44,6 @@ namespace osu.Game.Screens.Select
protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea();
private ModAutoplay getAutoplayMod() => Ruleset.Value.CreateInstance().GetAutoplayMod();
public override void OnResuming(IScreen last)
{
base.OnResuming(last);
playerLoader = null;
if (removeAutoModOnResume)
{
var autoType = getAutoplayMod()?.GetType();
if (autoType != null)
Mods.Value = Mods.Value.Where(m => m.GetType() != autoType).ToArray();
removeAutoModOnResume = false;
}
}
protected override bool OnKeyDown(KeyDownEvent e) protected override bool OnKeyDown(KeyDownEvent e)
{ {
switch (e.Key) switch (e.Key)
@ -77,10 +59,16 @@ namespace osu.Game.Screens.Select
return base.OnKeyDown(e); return base.OnKeyDown(e);
} }
private IReadOnlyList<Mod> modsAtGameplayStart;
private ModAutoplay getAutoplayMod() => Ruleset.Value.CreateInstance().GetAutoplayMod();
protected override bool OnStart() protected override bool OnStart()
{ {
if (playerLoader != null) return false; if (playerLoader != null) return false;
modsAtGameplayStart = Mods.Value;
// Ctrl+Enter should start map with autoplay enabled. // Ctrl+Enter should start map with autoplay enabled.
if (GetContainingInputManager().CurrentState?.Keyboard.ControlPressed == true) if (GetContainingInputManager().CurrentState?.Keyboard.ControlPressed == true)
{ {
@ -95,13 +83,12 @@ namespace osu.Game.Screens.Select
return false; return false;
} }
var mods = Mods.Value; var mods = Mods.Value.Append(autoInstance).ToArray();
if (mods.All(m => m.GetType() != autoInstance.GetType())) if (!ModUtils.CheckCompatibleSet(mods, out var invalid))
{ mods = mods.Except(invalid).Append(autoInstance).ToArray();
Mods.Value = mods.Append(autoInstance).ToArray();
removeAutoModOnResume = true; Mods.Value = mods;
}
} }
SampleConfirm?.Play(); SampleConfirm?.Play();
@ -118,5 +105,16 @@ namespace osu.Game.Screens.Select
return new SoloPlayer(); return new SoloPlayer();
} }
} }
public override void OnResuming(IScreen last)
{
base.OnResuming(last);
if (playerLoader != null)
{
Mods.Value = modsAtGameplayStart;
playerLoader = null;
}
}
} }
} }

View File

@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual
[TearDownSteps] [TearDownSteps]
public void TearDownSteps() public void TearDownSteps()
{ {
if (DebugUtils.IsNUnitRunning) if (DebugUtils.IsNUnitRunning && Game != null)
{ {
AddStep("exit game", () => Game.Exit()); AddStep("exit game", () => Game.Exit());
AddUntilStep("wait for game exit", () => Game.Parent == null); AddUntilStep("wait for game exit", () => Game.Parent == null);

View File

@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual
Resources = parent.Get<OsuGameBase>().Resources; Resources = parent.Get<OsuGameBase>().Resources;
realm = new Lazy<RealmAccess>(() => new RealmAccess(LocalStorage, "client", host.UpdateThread)); realm = new Lazy<RealmAccess>(() => new RealmAccess(LocalStorage, OsuGameBase.CLIENT_DATABASE_FILENAME, host.UpdateThread));
RecycleLocalStorage(false); RecycleLocalStorage(false);