diff --git a/.editorconfig b/.editorconfig
index a5f7795882..0cdf3b92d3 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -194,4 +194,7 @@ dotnet_diagnostic.IDE0068.severity = none
dotnet_diagnostic.IDE0069.severity = none
#Disable operator overloads requiring alternate named methods
-dotnet_diagnostic.CA2225.severity = none
\ No newline at end of file
+dotnet_diagnostic.CA2225.severity = none
+
+# Banned APIs
+dotnet_diagnostic.RS0030.severity = error
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/01-bug-issues.md b/.github/ISSUE_TEMPLATE/01-bug-issues.md
index 0b80ce44dd..e45893b97a 100644
--- a/.github/ISSUE_TEMPLATE/01-bug-issues.md
+++ b/.github/ISSUE_TEMPLATE/01-bug-issues.md
@@ -1,7 +1,18 @@
---
name: Bug Report
-about: Issues regarding encountered bugs.
+about: Report a bug or crash to desktop
---
+
+
+
+
**Describe the bug:**
**Screenshots or videos showing encountered issue:**
@@ -9,8 +20,11 @@ about: Issues regarding encountered bugs.
**osu!lazer version:**
**Logs:**
+
diff --git a/.github/ISSUE_TEMPLATE/02-crash-issues.md b/.github/ISSUE_TEMPLATE/02-crash-issues.md
deleted file mode 100644
index ada8de73c0..0000000000
--- a/.github/ISSUE_TEMPLATE/02-crash-issues.md
+++ /dev/null
@@ -1,18 +0,0 @@
----
-name: Crash Report
-about: Issues regarding crashes or permanent freezes.
----
-**Describe the crash:**
-
-**Screenshots or videos showing encountered issue:**
-
-**osu!lazer version:**
-
-**Logs:**
-
-
-**Computer Specifications:**
diff --git a/.github/ISSUE_TEMPLATE/03-feature-request-issues.md b/.github/ISSUE_TEMPLATE/02-feature-request-issues.md
similarity index 62%
rename from .github/ISSUE_TEMPLATE/03-feature-request-issues.md
rename to .github/ISSUE_TEMPLATE/02-feature-request-issues.md
index 54c4ff94e5..c3357dd780 100644
--- a/.github/ISSUE_TEMPLATE/03-feature-request-issues.md
+++ b/.github/ISSUE_TEMPLATE/02-feature-request-issues.md
@@ -1,6 +1,6 @@
---
name: Feature Request
-about: Features you would like to see in the game!
+about: Propose a feature you would like to see in the game!
---
**Describe the new feature:**
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___legacy_osuTK_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___legacy_osuTK_.xml
deleted file mode 100644
index 9ece926b34..0000000000
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___legacy_osuTK_.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt
index 47839608c9..46c50dbfa2 100644
--- a/CodeAnalysis/BannedSymbols.txt
+++ b/CodeAnalysis/BannedSymbols.txt
@@ -7,3 +7,4 @@ M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText.
M:osu.Framework.Bindables.IBindableList`1.GetBoundCopy();Fails on iOS. Use manual ctor + BindTo instead. (see https://github.com/mono/mono/issues/19900)
T:Microsoft.EntityFrameworkCore.Internal.EnumerableExtensions;Don't use internal extension methods.
T:Microsoft.EntityFrameworkCore.Internal.TypeExtensions;Don't use internal extension methods.
+M:System.Enum.HasFlag(System.Enum);Use osu.Framework.Extensions.EnumExtensions.HasFlagFast() instead.
\ No newline at end of file
diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset
index d497365f87..6a99e230d1 100644
--- a/CodeAnalysis/osu.ruleset
+++ b/CodeAnalysis/osu.ruleset
@@ -30,7 +30,7 @@
-
+
diff --git a/Directory.Build.props b/Directory.Build.props
index 2e1873a9ed..53ad973e47 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -18,7 +18,7 @@
-
+
$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset
diff --git a/osu.Android.props b/osu.Android.props
index bfdc8f6b3c..75ac298626 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs
index ad929bbac3..cffcea22c2 100644
--- a/osu.Android/OsuGameActivity.cs
+++ b/osu.Android/OsuGameActivity.cs
@@ -17,7 +17,7 @@ using osu.Game.Database;
namespace osu.Android
{
- [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance)]
+ [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance, Exported = true)]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")]
[IntentFilter(new[] { Intent.ActionSend, Intent.ActionSendMultiple }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/zip", "application/octet-stream", "application/download", "application/x-zip", "application/x-zip-compressed" })]
@@ -100,15 +100,15 @@ namespace osu.Android
// copy to an arbitrary-access memory stream to be able to proceed with the import.
var copy = new MemoryStream();
using (var stream = ContentResolver.OpenInputStream(uri))
- await stream.CopyToAsync(copy);
+ await stream.CopyToAsync(copy).ConfigureAwait(false);
lock (tasks)
{
tasks.Add(new ImportTask(copy, filename));
}
- }));
+ })).ConfigureAwait(false);
- await game.Import(tasks.ToArray());
+ await game.Import(tasks.ToArray()).ConfigureAwait(false);
}, TaskCreationOptions.LongRunning);
}
}
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index 5909b82c8f..b2487568ce 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -136,24 +136,12 @@ namespace osu.Desktop
var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
- switch (host.Window)
- {
- // Legacy osuTK DesktopGameWindow
- case OsuTKDesktopWindow desktopGameWindow:
- desktopGameWindow.CursorState |= CursorState.Hidden;
- desktopGameWindow.SetIconFromStream(iconStream);
- desktopGameWindow.Title = Name;
- desktopGameWindow.FileDrop += (_, e) => fileDrop(e.FileNames);
- break;
+ var desktopWindow = (SDL2DesktopWindow)host.Window;
- // SDL2 DesktopWindow
- case SDL2DesktopWindow desktopWindow:
- desktopWindow.CursorState |= CursorState.Hidden;
- desktopWindow.SetIconFromStream(iconStream);
- desktopWindow.Title = Name;
- desktopWindow.DragDrop += f => fileDrop(new[] { f });
- break;
- }
+ desktopWindow.CursorState |= CursorState.Hidden;
+ desktopWindow.SetIconFromStream(iconStream);
+ desktopWindow.Title = Name;
+ desktopWindow.DragDrop += f => fileDrop(new[] { f });
}
private void fileDrop(string[] filePaths)
diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs
index 6ca7079654..d06c4b6746 100644
--- a/osu.Desktop/Program.cs
+++ b/osu.Desktop/Program.cs
@@ -22,9 +22,8 @@ namespace osu.Desktop
{
// Back up the cwd before DesktopGameHost changes it
var cwd = Environment.CurrentDirectory;
- bool useOsuTK = args.Contains("--tk");
- using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true, useOsuTK: useOsuTK))
+ using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
{
host.ExceptionThrown += handleException;
diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs
index 71f9fafe57..47cd39dc5a 100644
--- a/osu.Desktop/Updater/SquirrelUpdateManager.cs
+++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs
@@ -42,7 +42,7 @@ namespace osu.Desktop.Updater
Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger));
}
- protected override async Task PerformUpdateCheck() => await checkForUpdateAsync();
+ protected override async Task PerformUpdateCheck() => await checkForUpdateAsync().ConfigureAwait(false);
private async Task checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null)
{
@@ -51,9 +51,9 @@ namespace osu.Desktop.Updater
try
{
- updateManager ??= await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true);
+ updateManager ??= await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true).ConfigureAwait(false);
- var info = await updateManager.CheckForUpdate(!useDeltaPatching);
+ var info = await updateManager.CheckForUpdate(!useDeltaPatching).ConfigureAwait(false);
if (info.ReleasesToApply.Count == 0)
{
@@ -79,12 +79,12 @@ namespace osu.Desktop.Updater
try
{
- await updateManager.DownloadReleases(info.ReleasesToApply, p => notification.Progress = p / 100f);
+ await updateManager.DownloadReleases(info.ReleasesToApply, p => notification.Progress = p / 100f).ConfigureAwait(false);
notification.Progress = 0;
notification.Text = @"Installing update...";
- await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f);
+ await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f).ConfigureAwait(false);
notification.State = ProgressNotificationState.Completed;
updatePending = true;
@@ -97,7 +97,7 @@ namespace osu.Desktop.Updater
// could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959)
// try again without deltas.
- await checkForUpdateAsync(false, notification);
+ await checkForUpdateAsync(false, notification).ConfigureAwait(false);
scheduleRecheck = false;
}
else
@@ -116,7 +116,7 @@ namespace osu.Desktop.Updater
if (scheduleRecheck)
{
// check again in 30 minutes.
- Scheduler.AddDelayed(async () => await checkForUpdateAsync(), 60000 * 30);
+ Scheduler.AddDelayed(async () => await checkForUpdateAsync().ConfigureAwait(false), 60000 * 30);
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs
index f15da29993..1248409b2a 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Tests
[BackgroundDependencyLoader]
private void load()
{
- LocalConfig.Set(OsuSetting.IncreaseFirstObjectVisibility, false);
+ LocalConfig.SetValue(OsuSetting.IncreaseFirstObjectVisibility, false);
}
[Test]
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
index e8bb57cdf3..48efd73222 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
@@ -202,7 +202,7 @@ namespace osu.Game.Rulesets.Catch.Tests
public void TestHitLightingColour()
{
var fruitColour = SkinConfiguration.DefaultComboColours[1];
- AddStep("enable hit lighting", () => config.Set(OsuSetting.HitLighting, true));
+ AddStep("enable hit lighting", () => config.SetValue(OsuSetting.HitLighting, true));
AddStep("catch fruit", () => attemptCatch(new Fruit()));
AddAssert("correct hit lighting colour", () =>
catcher.ChildrenOfType().First()?.ObjectColour == fruitColour);
@@ -211,7 +211,7 @@ namespace osu.Game.Rulesets.Catch.Tests
[Test]
public void TestHitLightingDisabled()
{
- AddStep("disable hit lighting", () => config.Set(OsuSetting.HitLighting, false));
+ AddStep("disable hit lighting", () => config.SetValue(OsuSetting.HitLighting, false));
AddStep("catch fruit", () => attemptCatch(new Fruit()));
AddAssert("no hit lighting", () => !catcher.ChildrenOfType().Any());
}
diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
index bf3aba5859..728af5124e 100644
--- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
+++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index 0a817eca0d..f4ddbd3021 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -21,6 +21,7 @@ using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using System;
+using osu.Framework.Extensions.EnumExtensions;
using osu.Game.Rulesets.Catch.Skinning.Legacy;
using osu.Game.Skinning;
@@ -50,40 +51,40 @@ namespace osu.Game.Rulesets.Catch
public override IEnumerable ConvertFromLegacyMods(LegacyMods mods)
{
- if (mods.HasFlag(LegacyMods.Nightcore))
+ if (mods.HasFlagFast(LegacyMods.Nightcore))
yield return new CatchModNightcore();
- else if (mods.HasFlag(LegacyMods.DoubleTime))
+ else if (mods.HasFlagFast(LegacyMods.DoubleTime))
yield return new CatchModDoubleTime();
- if (mods.HasFlag(LegacyMods.Perfect))
+ if (mods.HasFlagFast(LegacyMods.Perfect))
yield return new CatchModPerfect();
- else if (mods.HasFlag(LegacyMods.SuddenDeath))
+ else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
yield return new CatchModSuddenDeath();
- if (mods.HasFlag(LegacyMods.Cinema))
+ if (mods.HasFlagFast(LegacyMods.Cinema))
yield return new CatchModCinema();
- else if (mods.HasFlag(LegacyMods.Autoplay))
+ else if (mods.HasFlagFast(LegacyMods.Autoplay))
yield return new CatchModAutoplay();
- if (mods.HasFlag(LegacyMods.Easy))
+ if (mods.HasFlagFast(LegacyMods.Easy))
yield return new CatchModEasy();
- if (mods.HasFlag(LegacyMods.Flashlight))
+ if (mods.HasFlagFast(LegacyMods.Flashlight))
yield return new CatchModFlashlight();
- if (mods.HasFlag(LegacyMods.HalfTime))
+ if (mods.HasFlagFast(LegacyMods.HalfTime))
yield return new CatchModHalfTime();
- if (mods.HasFlag(LegacyMods.HardRock))
+ if (mods.HasFlagFast(LegacyMods.HardRock))
yield return new CatchModHardRock();
- if (mods.HasFlag(LegacyMods.Hidden))
+ if (mods.HasFlagFast(LegacyMods.Hidden))
yield return new CatchModHidden();
- if (mods.HasFlag(LegacyMods.NoFail))
+ if (mods.HasFlagFast(LegacyMods.NoFail))
yield return new CatchModNoFail();
- if (mods.HasFlag(LegacyMods.Relax))
+ if (mods.HasFlagFast(LegacyMods.Relax))
yield return new CatchModRelax();
}
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
index a317ef252d..10aae70722 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
@@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
}
}
- protected override Skill[] CreateSkills(IBeatmap beatmap)
+ protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods)
{
halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) * 0.5f;
@@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
return new Skill[]
{
- new Movement(halfCatcherWidth),
+ new Movement(mods, halfCatcherWidth),
};
}
diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
index e679231638..9ad719be1a 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs
@@ -5,6 +5,7 @@ using System;
using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
+using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Difficulty.Skills
{
@@ -25,7 +26,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
private float lastDistanceMoved;
private double lastStrainTime;
- public Movement(float halfCatcherWidth)
+ public Movement(Mod[] mods, float halfCatcherWidth)
+ : base(mods)
{
HalfCatcherWidth = halfCatcherWidth;
}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs
index a5248c7712..399a46aa77 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.Tests
Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
- Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
+ Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed");
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released");
}
@@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Mania.Tests
Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
- Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
+ Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
@@ -148,9 +148,9 @@ namespace osu.Game.Rulesets.Mania.Tests
Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames");
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
- Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 2].Time, "Incorrect first note release time");
+ Assert.AreEqual(3000, generated.Frames[frame_offset + 2].Time, "Incorrect first note release time");
Assert.AreEqual(2000, generated.Frames[frame_offset + 1].Time, "Incorrect second note hit time");
- Assert.AreEqual(4000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 3].Time, "Incorrect second note release time");
+ Assert.AreEqual(4000, generated.Frames[frame_offset + 3].Time, "Incorrect second note release time");
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
Assert.IsTrue(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key1), "Key1 has not been released");
@@ -168,7 +168,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | | |
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
- beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 - ManiaAutoGenerator.RELEASE_DELAY });
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 });
var generated = new ManiaAutoGenerator(beatmap).Generate();
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
index 596430f9e5..7ae69bf7d7 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
@@ -5,11 +5,13 @@ using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Screens;
+using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Replays;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Objects;
@@ -345,6 +347,14 @@ namespace osu.Game.Rulesets.Mania.Tests
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
+
+ AddUntilStep("wait for head", () => currentPlayer.GameplayClockContainer.GameplayClock.CurrentTime >= time_head);
+ AddAssert("head is visible",
+ () => currentPlayer.ChildrenOfType()
+ .Single(note => note.HitObject == beatmap.HitObjects[0])
+ .Head
+ .Alpha == 1);
+
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
}
@@ -352,6 +362,8 @@ namespace osu.Game.Rulesets.Mania.Tests
{
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
+ public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
+
protected override bool PauseOnFocusLost => false;
public ScoreAccessibleReplayPlayer(Score score)
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs
index 6b8f5d5d9d..706268e478 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs
@@ -6,6 +6,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -97,7 +98,7 @@ namespace osu.Game.Rulesets.Mania.Tests
}
private bool verifyAnchors(DrawableHitObject hitObject, Anchor expectedAnchor)
- => hitObject.Anchor.HasFlag(expectedAnchor) && hitObject.Origin.HasFlag(expectedAnchor);
+ => hitObject.Anchor.HasFlagFast(expectedAnchor) && hitObject.Origin.HasFlagFast(expectedAnchor);
private bool verifyAnchors(DrawableHoldNote holdNote, Anchor expectedAnchor)
=> verifyAnchors((DrawableHitObject)holdNote, expectedAnchor) && holdNote.NestedHitObjects.All(n => verifyAnchors(n, expectedAnchor));
diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
index fcc0cafefc..af16f39563 100644
--- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
+++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index 30d33de06e..c81710ed18 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
+using osu.Framework.Extensions.EnumExtensions;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.MathUtils;
@@ -141,7 +142,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (ConversionDifficulty > 6.5)
{
- if (convertType.HasFlag(PatternType.LowProbability))
+ if (convertType.HasFlagFast(PatternType.LowProbability))
return generateNRandomNotes(StartTime, 0.78, 0.3, 0);
return generateNRandomNotes(StartTime, 0.85, 0.36, 0.03);
@@ -149,7 +150,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (ConversionDifficulty > 4)
{
- if (convertType.HasFlag(PatternType.LowProbability))
+ if (convertType.HasFlagFast(PatternType.LowProbability))
return generateNRandomNotes(StartTime, 0.43, 0.08, 0);
return generateNRandomNotes(StartTime, 0.56, 0.18, 0);
@@ -157,13 +158,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (ConversionDifficulty > 2.5)
{
- if (convertType.HasFlag(PatternType.LowProbability))
+ if (convertType.HasFlagFast(PatternType.LowProbability))
return generateNRandomNotes(StartTime, 0.3, 0, 0);
return generateNRandomNotes(StartTime, 0.37, 0.08, 0);
}
- if (convertType.HasFlag(PatternType.LowProbability))
+ if (convertType.HasFlagFast(PatternType.LowProbability))
return generateNRandomNotes(StartTime, 0.17, 0, 0);
return generateNRandomNotes(StartTime, 0.27, 0, 0);
@@ -221,7 +222,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var pattern = new Pattern();
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
- if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
+ if (convertType.HasFlagFast(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
nextColumn = FindAvailableColumn(nextColumn, PreviousPattern);
int lastColumn = nextColumn;
@@ -373,7 +374,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
static bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH;
- bool canGenerateTwoNotes = !convertType.HasFlag(PatternType.LowProbability);
+ bool canGenerateTwoNotes = !convertType.HasFlagFast(PatternType.LowProbability);
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(StartTime).Any(isDoubleSample);
if (canGenerateTwoNotes)
@@ -406,7 +407,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int endTime = startTime + SegmentDuration * SpanCount;
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
- if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
+ if (convertType.HasFlagFast(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
nextColumn = FindAvailableColumn(nextColumn, PreviousPattern);
for (int i = 0; i < columnRepeat; i++)
@@ -435,7 +436,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var pattern = new Pattern();
int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
- if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
+ if (convertType.HasFlagFast(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
holdColumn = FindAvailableColumn(holdColumn, PreviousPattern);
// Create the hold note
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
index bc4ab55767..54c37e9742 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Extensions.EnumExtensions;
using osuTK;
using osu.Game.Audio;
using osu.Game.Beatmaps;
@@ -78,7 +79,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
else
convertType |= PatternType.LowProbability;
- if (!convertType.HasFlag(PatternType.KeepSingle))
+ if (!convertType.HasFlagFast(PatternType.KeepSingle))
{
if (HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH) && TotalColumns != 8)
convertType |= PatternType.Mirror;
@@ -101,7 +102,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0;
- if (convertType.HasFlag(PatternType.Reverse) && PreviousPattern.HitObjects.Any())
+ if (convertType.HasFlagFast(PatternType.Reverse) && PreviousPattern.HitObjects.Any())
{
// Generate a new pattern by copying the last hit objects in reverse-column order
for (int i = RandomStart; i < TotalColumns; i++)
@@ -113,11 +114,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
return pattern;
}
- if (convertType.HasFlag(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1
- // If we convert to 7K + 1, let's not overload the special key
- && (TotalColumns != 8 || lastColumn != 0)
- // Make sure the last column was not the centre column
- && (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2))
+ if (convertType.HasFlagFast(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1
+ // If we convert to 7K + 1, let's not overload the special key
+ && (TotalColumns != 8 || lastColumn != 0)
+ // Make sure the last column was not the centre column
+ && (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2))
{
// Generate a new pattern by cycling backwards (similar to Reverse but for only one hit object)
int column = RandomStart + TotalColumns - lastColumn - 1;
@@ -126,7 +127,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
return pattern;
}
- if (convertType.HasFlag(PatternType.ForceStack) && PreviousPattern.HitObjects.Any())
+ if (convertType.HasFlagFast(PatternType.ForceStack) && PreviousPattern.HitObjects.Any())
{
// Generate a new pattern by placing on the already filled columns
for (int i = RandomStart; i < TotalColumns; i++)
@@ -140,7 +141,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (PreviousPattern.HitObjects.Count() == 1)
{
- if (convertType.HasFlag(PatternType.Stair))
+ if (convertType.HasFlagFast(PatternType.Stair))
{
// Generate a new pattern by placing on the next column, cycling back to the start if there is no "next"
int targetColumn = lastColumn + 1;
@@ -151,7 +152,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
return pattern;
}
- if (convertType.HasFlag(PatternType.ReverseStair))
+ if (convertType.HasFlagFast(PatternType.ReverseStair))
{
// Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous"
int targetColumn = lastColumn - 1;
@@ -163,10 +164,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
}
}
- if (convertType.HasFlag(PatternType.KeepSingle))
+ if (convertType.HasFlagFast(PatternType.KeepSingle))
return generateRandomNotes(1);
- if (convertType.HasFlag(PatternType.Mirror))
+ if (convertType.HasFlagFast(PatternType.Mirror))
{
if (ConversionDifficulty > 6.5)
return generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
@@ -178,7 +179,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (ConversionDifficulty > 6.5)
{
- if (convertType.HasFlag(PatternType.LowProbability))
+ if (convertType.HasFlagFast(PatternType.LowProbability))
return generateRandomPattern(0.78, 0.42, 0, 0);
return generateRandomPattern(1, 0.62, 0, 0);
@@ -186,7 +187,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (ConversionDifficulty > 4)
{
- if (convertType.HasFlag(PatternType.LowProbability))
+ if (convertType.HasFlagFast(PatternType.LowProbability))
return generateRandomPattern(0.35, 0.08, 0, 0);
return generateRandomPattern(0.52, 0.15, 0, 0);
@@ -194,7 +195,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (ConversionDifficulty > 2)
{
- if (convertType.HasFlag(PatternType.LowProbability))
+ if (convertType.HasFlagFast(PatternType.LowProbability))
return generateRandomPattern(0.18, 0, 0, 0);
return generateRandomPattern(0.45, 0, 0, 0);
@@ -207,9 +208,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
foreach (var obj in p.HitObjects)
{
- if (convertType.HasFlag(PatternType.Stair) && obj.Column == TotalColumns - 1)
+ if (convertType.HasFlagFast(PatternType.Stair) && obj.Column == TotalColumns - 1)
StairType = PatternType.ReverseStair;
- if (convertType.HasFlag(PatternType.ReverseStair) && obj.Column == RandomStart)
+ if (convertType.HasFlagFast(PatternType.ReverseStair) && obj.Column == RandomStart)
StairType = PatternType.Stair;
}
@@ -229,7 +230,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
var pattern = new Pattern();
- bool allowStacking = !convertType.HasFlag(PatternType.ForceNotStack);
+ bool allowStacking = !convertType.HasFlagFast(PatternType.ForceNotStack);
if (!allowStacking)
noteCount = Math.Min(noteCount, TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects);
@@ -249,7 +250,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int getNextColumn(int last)
{
- if (convertType.HasFlag(PatternType.Gathered))
+ if (convertType.HasFlagFast(PatternType.Gathered))
{
last++;
if (last == TotalColumns)
@@ -296,7 +297,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// The containing the hit objects.
private Pattern generateRandomPatternWithMirrored(double centreProbability, double p2, double p3)
{
- if (convertType.HasFlag(PatternType.ForceNotStack))
+ if (convertType.HasFlagFast(PatternType.ForceNotStack))
return generateRandomPattern(1 / 2f + p2 / 2, p2, (p2 + p3) / 2, p3);
var pattern = new Pattern();
diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
index 756f2b7b2f..39d0f4bae4 100644
--- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
+++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
@@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Mania.Configuration
{
base.InitialiseDefaults();
- Set(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 5);
- Set(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down);
+ SetDefault(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 5);
+ SetDefault(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down);
}
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
index ade830764d..8c0b9ed8b7 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
@@ -68,9 +68,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty
// Sorting is done in CreateDifficultyHitObjects, since the full list of hitobjects is required.
protected override IEnumerable SortObjects(IEnumerable input) => input;
- protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[]
+ protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[]
{
- new Strain(((ManiaBeatmap)beatmap).TotalColumns)
+ new Strain(mods, ((ManiaBeatmap)beatmap).TotalColumns)
};
protected override Mod[] DifficultyAdjustmentMods
diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs
index 56fb138b1f..830b6004a6 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs
@@ -6,6 +6,7 @@ using osu.Framework.Utils;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Difficulty.Skills
@@ -24,7 +25,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
private double individualStrain;
private double overallStrain;
- public Strain(int totalColumns)
+ public Strain(Mod[] mods, int totalColumns)
+ : base(mods)
{
holdEndTimes = new double[totalColumns];
individualStrains = new double[totalColumns];
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
index 50629f41a9..2689ed4112 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
@@ -45,6 +45,7 @@ namespace osu.Game.Rulesets.Mania.Edit
int minColumn = int.MaxValue;
int maxColumn = int.MinValue;
+ // find min/max in an initial pass before actually performing the movement.
foreach (var obj in EditorBeatmap.SelectedHitObjects.OfType())
{
if (obj.Column < minColumn)
@@ -55,8 +56,11 @@ namespace osu.Game.Rulesets.Mania.Edit
columnDelta = Math.Clamp(columnDelta, -minColumn, maniaPlayfield.TotalColumns - 1 - maxColumn);
- foreach (var obj in EditorBeatmap.SelectedHitObjects.OfType())
- obj.Column += columnDelta;
+ EditorBeatmap.PerformOnSelection(h =>
+ {
+ if (h is ManiaHitObject maniaObj)
+ maniaObj.Column += columnDelta;
+ });
}
}
}
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index 4c729fef83..d624e094ad 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -9,6 +9,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
@@ -59,76 +60,76 @@ namespace osu.Game.Rulesets.Mania
public override IEnumerable ConvertFromLegacyMods(LegacyMods mods)
{
- if (mods.HasFlag(LegacyMods.Nightcore))
+ if (mods.HasFlagFast(LegacyMods.Nightcore))
yield return new ManiaModNightcore();
- else if (mods.HasFlag(LegacyMods.DoubleTime))
+ else if (mods.HasFlagFast(LegacyMods.DoubleTime))
yield return new ManiaModDoubleTime();
- if (mods.HasFlag(LegacyMods.Perfect))
+ if (mods.HasFlagFast(LegacyMods.Perfect))
yield return new ManiaModPerfect();
- else if (mods.HasFlag(LegacyMods.SuddenDeath))
+ else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
yield return new ManiaModSuddenDeath();
- if (mods.HasFlag(LegacyMods.Cinema))
+ if (mods.HasFlagFast(LegacyMods.Cinema))
yield return new ManiaModCinema();
- else if (mods.HasFlag(LegacyMods.Autoplay))
+ else if (mods.HasFlagFast(LegacyMods.Autoplay))
yield return new ManiaModAutoplay();
- if (mods.HasFlag(LegacyMods.Easy))
+ if (mods.HasFlagFast(LegacyMods.Easy))
yield return new ManiaModEasy();
- if (mods.HasFlag(LegacyMods.FadeIn))
+ if (mods.HasFlagFast(LegacyMods.FadeIn))
yield return new ManiaModFadeIn();
- if (mods.HasFlag(LegacyMods.Flashlight))
+ if (mods.HasFlagFast(LegacyMods.Flashlight))
yield return new ManiaModFlashlight();
- if (mods.HasFlag(LegacyMods.HalfTime))
+ if (mods.HasFlagFast(LegacyMods.HalfTime))
yield return new ManiaModHalfTime();
- if (mods.HasFlag(LegacyMods.HardRock))
+ if (mods.HasFlagFast(LegacyMods.HardRock))
yield return new ManiaModHardRock();
- if (mods.HasFlag(LegacyMods.Hidden))
+ if (mods.HasFlagFast(LegacyMods.Hidden))
yield return new ManiaModHidden();
- if (mods.HasFlag(LegacyMods.Key1))
+ if (mods.HasFlagFast(LegacyMods.Key1))
yield return new ManiaModKey1();
- if (mods.HasFlag(LegacyMods.Key2))
+ if (mods.HasFlagFast(LegacyMods.Key2))
yield return new ManiaModKey2();
- if (mods.HasFlag(LegacyMods.Key3))
+ if (mods.HasFlagFast(LegacyMods.Key3))
yield return new ManiaModKey3();
- if (mods.HasFlag(LegacyMods.Key4))
+ if (mods.HasFlagFast(LegacyMods.Key4))
yield return new ManiaModKey4();
- if (mods.HasFlag(LegacyMods.Key5))
+ if (mods.HasFlagFast(LegacyMods.Key5))
yield return new ManiaModKey5();
- if (mods.HasFlag(LegacyMods.Key6))
+ if (mods.HasFlagFast(LegacyMods.Key6))
yield return new ManiaModKey6();
- if (mods.HasFlag(LegacyMods.Key7))
+ if (mods.HasFlagFast(LegacyMods.Key7))
yield return new ManiaModKey7();
- if (mods.HasFlag(LegacyMods.Key8))
+ if (mods.HasFlagFast(LegacyMods.Key8))
yield return new ManiaModKey8();
- if (mods.HasFlag(LegacyMods.Key9))
+ if (mods.HasFlagFast(LegacyMods.Key9))
yield return new ManiaModKey9();
- if (mods.HasFlag(LegacyMods.KeyCoop))
+ if (mods.HasFlagFast(LegacyMods.KeyCoop))
yield return new ManiaModDualStages();
- if (mods.HasFlag(LegacyMods.NoFail))
+ if (mods.HasFlagFast(LegacyMods.NoFail))
yield return new ManiaModNoFail();
- if (mods.HasFlag(LegacyMods.Random))
+ if (mods.HasFlagFast(LegacyMods.Random))
yield return new ManiaModRandom();
- if (mods.HasFlag(LegacyMods.Mirror))
+ if (mods.HasFlagFast(LegacyMods.Mirror))
yield return new ManiaModMirror();
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs
index cbdcd49c5b..f80c9e1f7c 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs
@@ -1,18 +1,20 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Graphics.Sprites;
-using osu.Game.Graphics;
+using System;
+using System.Linq;
using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Mods
{
- public class ManiaModFadeIn : ManiaModHidden
+ public class ManiaModFadeIn : ManiaModPlayfieldCover
{
public override string Name => "Fade In";
public override string Acronym => "FI";
- public override IconUsage? Icon => OsuIcon.ModHidden;
public override string Description => @"Keys appear out of nowhere!";
+ public override double ScoreMultiplier => 1;
+
+ public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModHidden)).ToArray();
protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AlongScroll;
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs
index 4bdb15526f..e3ac624a6e 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs
@@ -3,43 +3,17 @@
using System;
using System.Linq;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.UI;
-using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania.Mods
{
- public class ManiaModHidden : ModHidden, IApplicableToDrawableRuleset
+ public class ManiaModHidden : ManiaModPlayfieldCover
{
public override string Description => @"Keys fade out before you hit them!";
public override double ScoreMultiplier => 1;
- public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) };
- ///
- /// The direction in which the cover should expand.
- ///
- protected virtual CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll;
+ public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModFadeIn)).ToArray();
- public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
- {
- ManiaPlayfield maniaPlayfield = (ManiaPlayfield)drawableRuleset.Playfield;
-
- foreach (Column column in maniaPlayfield.Stages.SelectMany(stage => stage.Columns))
- {
- HitObjectContainer hoc = column.HitObjectArea.HitObjectContainer;
- Container hocParent = (Container)hoc.Parent;
-
- hocParent.Remove(hoc);
- hocParent.Add(new PlayfieldCoveringWrapper(hoc).With(c =>
- {
- c.RelativeSizeAxes = Axes.Both;
- c.Direction = ExpandDirection;
- c.Coverage = 0.5f;
- }));
- }
- }
+ protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll;
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs
new file mode 100644
index 0000000000..87501d07a5
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs
@@ -0,0 +1,43 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Rulesets.Mania.Mods
+{
+ public abstract class ManiaModPlayfieldCover : ModHidden, IApplicableToDrawableRuleset
+ {
+ public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) };
+
+ ///
+ /// The direction in which the cover should expand.
+ ///
+ protected abstract CoverExpandDirection ExpandDirection { get; }
+
+ public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
+ {
+ ManiaPlayfield maniaPlayfield = (ManiaPlayfield)drawableRuleset.Playfield;
+
+ foreach (Column column in maniaPlayfield.Stages.SelectMany(stage => stage.Columns))
+ {
+ HitObjectContainer hoc = column.HitObjectArea.HitObjectContainer;
+ Container hocParent = (Container)hoc.Parent;
+
+ hocParent.Remove(hoc);
+ hocParent.Add(new PlayfieldCoveringWrapper(hoc).With(c =>
+ {
+ c.RelativeSizeAxes = Axes.Both;
+ c.Direction = ExpandDirection;
+ c.Coverage = 0.5f;
+ }));
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs
index 75dcf0e55e..35ba2465fa 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs
@@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Game.Rulesets.Objects.Drawables;
+
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
///
@@ -25,6 +27,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
LifetimeEnd = LifetimeStart + 30000;
}
+ protected override void UpdateHitStateTransforms(ArmedState state)
+ {
+ // suppress the base call explicitly.
+ // the hold note head should never change its visual state on its own due to the "freezing" mechanic
+ // (when hit, it remains visible in place at the judgement line; when dropped, it will scroll past the line).
+ // it will be hidden along with its parenting hold note when required.
+ }
+
public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note
public override void OnReleased(ManiaAction action)
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
index 3ebbe5af8e..7c51d58b74 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq;
using osu.Game.Replays;
using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Replays;
@@ -85,20 +86,28 @@ namespace osu.Game.Rulesets.Mania.Replays
{
var currentObject = Beatmap.HitObjects[i];
var nextObjectInColumn = GetNextObject(i); // Get the next object that requires pressing the same button
-
- double endTime = currentObject.GetEndTime();
-
- bool canDelayKeyUp = nextObjectInColumn == null ||
- nextObjectInColumn.StartTime > endTime + RELEASE_DELAY;
-
- double calculatedDelay = canDelayKeyUp ? RELEASE_DELAY : (nextObjectInColumn.StartTime - endTime) * 0.9;
+ var releaseTime = calculateReleaseTime(currentObject, nextObjectInColumn);
yield return new HitPoint { Time = currentObject.StartTime, Column = currentObject.Column };
- yield return new ReleasePoint { Time = endTime + calculatedDelay, Column = currentObject.Column };
+ yield return new ReleasePoint { Time = releaseTime, Column = currentObject.Column };
}
}
+ private double calculateReleaseTime(HitObject currentObject, HitObject nextObject)
+ {
+ double endTime = currentObject.GetEndTime();
+
+ if (currentObject is HoldNote)
+ // hold note releases must be timed exactly.
+ return endTime;
+
+ bool canDelayKeyUpFully = nextObject == null ||
+ nextObject.StartTime > endTime + RELEASE_DELAY;
+
+ return endTime + (canDelayKeyUpFully ? RELEASE_DELAY : (nextObject.StartTime - endTime) * 0.9);
+ }
+
protected override HitObject GetNextObject(int currentIndex)
{
int desiredColumn = Beatmap.HitObjects[currentIndex].Column;
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs
index 73aece1ed4..e4d466dca5 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs
@@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public class LegacyHitExplosion : LegacyManiaColumnElement, IHitExplosion
{
+ public const double FADE_IN_DURATION = 80;
+
private readonly IBindable direction = new Bindable();
private Drawable explosion;
@@ -72,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
(explosion as IFramedAnimation)?.GotoFrame(0);
- explosion?.FadeInFromZero(80)
+ explosion?.FadeInFromZero(FADE_IN_DURATION)
.Then().FadeOut(120);
}
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs
index 78ccb83a8c..10319a7d4d 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs
@@ -101,8 +101,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
if (action == column.Action.Value)
{
- upSprite.FadeTo(1);
- downSprite.FadeTo(0);
+ upSprite.Delay(LegacyHitExplosion.FADE_IN_DURATION).FadeTo(1);
+ downSprite.Delay(LegacyHitExplosion.FADE_IN_DURATION).FadeTo(0);
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
index cbbbacfe19..24ccae895d 100644
--- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs
@@ -140,7 +140,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
return animation == null ? null : new LegacyManiaJudgementPiece(result, animation);
}
- public override Sample GetSample(ISampleInfo sampleInfo)
+ public override ISample GetSample(ISampleInfo sampleInfo)
{
// layered hit sounds never play in mania
if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample && legacySample.IsLayered)
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
index b6db989231..ab9b7f4847 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
@@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Osu.Tests
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
- [TestCase(6.9311451172608853d, "diffcalc-test")]
- [TestCase(1.0736587013228804d, "zero-length-sliders")]
+ [TestCase(6.9311451172574934d, "diffcalc-test")]
+ [TestCase(1.0736586907780401d, "zero-length-sliders")]
public void Test(double expected, string name)
=> base.Test(expected, name);
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-0.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-0.png
new file mode 100644
index 0000000000..8304617d8c
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-0.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-1.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-1.png
new file mode 100644
index 0000000000..c3b85eb873
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-1.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-2.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-2.png
new file mode 100644
index 0000000000..7f65eb7ca7
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-2.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-3.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-3.png
new file mode 100644
index 0000000000..82bec3babe
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-3.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-4.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-4.png
new file mode 100644
index 0000000000..5e38c75a9d
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-4.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-5.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-5.png
new file mode 100644
index 0000000000..a562d9f2ac
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-5.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-6.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-6.png
new file mode 100644
index 0000000000..b4cf81f26e
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-6.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-7.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-7.png
new file mode 100644
index 0000000000..a23f5379b2
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-7.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-8.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-8.png
new file mode 100644
index 0000000000..430b18509d
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-8.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-9.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-9.png
new file mode 100644
index 0000000000..add1202c31
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-9.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-comma.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-comma.png
new file mode 100644
index 0000000000..f68d32957f
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-comma.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-dot.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-dot.png
new file mode 100644
index 0000000000..80c39b8745
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-dot.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-percent.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-percent.png
new file mode 100644
index 0000000000..fc750abc7e
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-percent.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-x.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-x.png
new file mode 100644
index 0000000000..779773f8bd
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini
index 5369de24e9..89bcd68343 100644
--- a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini
+++ b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini
@@ -1,2 +1,6 @@
[General]
-Version: 1.0
\ No newline at end of file
+Version: 1.0
+
+[Fonts]
+HitCircleOverlap: 3
+ScoreOverlap: 3
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs
index e2d9f144c0..8fd13c7417 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs
@@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Tests
return null;
}
- public Sample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
+ public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
index e4158d8f07..4395ca6281 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Tests
[Test]
public void TestHitLightingDisabled()
{
- AddStep("hit lighting disabled", () => config.Set(OsuSetting.HitLighting, false));
+ AddStep("hit lighting disabled", () => config.SetValue(OsuSetting.HitLighting, false));
showResult(HitResult.Great);
@@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Tests
[Test]
public void TestHitLightingEnabled()
{
- AddStep("hit lighting enabled", () => config.Set(OsuSetting.HitLighting, true));
+ AddStep("hit lighting enabled", () => config.SetValue(OsuSetting.HitLighting, true));
showResult(HitResult.Great);
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
index 461779b185..e3ccf83715 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
@@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Tests
AddSliderStep("circle size", 0f, 10f, 0f, val =>
{
- config.Set(OsuSetting.AutoCursorSize, true);
+ config.SetValue(OsuSetting.AutoCursorSize, true);
gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = val;
Scheduler.AddOnce(recreate);
});
@@ -64,21 +64,21 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestCase(10, 1.5f)]
public void TestSizing(int circleSize, float userScale)
{
- AddStep($"set user scale to {userScale}", () => config.Set(OsuSetting.GameplayCursorSize, userScale));
+ AddStep($"set user scale to {userScale}", () => config.SetValue(OsuSetting.GameplayCursorSize, userScale));
AddStep($"adjust cs to {circleSize}", () => gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = circleSize);
- AddStep("turn on autosizing", () => config.Set(OsuSetting.AutoCursorSize, true));
+ AddStep("turn on autosizing", () => config.SetValue(OsuSetting.AutoCursorSize, true));
AddStep("load content", loadContent);
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize) * userScale);
- AddStep("set user scale to 1", () => config.Set(OsuSetting.GameplayCursorSize, 1f));
+ AddStep("set user scale to 1", () => config.SetValue(OsuSetting.GameplayCursorSize, 1f));
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize));
- AddStep("turn off autosizing", () => config.Set(OsuSetting.AutoCursorSize, false));
+ AddStep("turn off autosizing", () => config.SetValue(OsuSetting.AutoCursorSize, false));
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == 1);
- AddStep($"set user scale to {userScale}", () => config.Set(OsuSetting.GameplayCursorSize, userScale));
+ AddStep($"set user scale to {userScale}", () => config.SetValue(OsuSetting.GameplayCursorSize, userScale));
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == userScale);
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
index 8dbb48c048..6c6f05c5c5 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
@@ -42,10 +42,10 @@ namespace osu.Game.Rulesets.Osu.Tests
{
AddStep("enable user provider", () => testUserSkin.Enabled = true);
- AddStep("enable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, true));
+ AddStep("enable beatmap skin", () => LocalConfig.SetValue(OsuSetting.BeatmapSkins, true));
checkNextHitObject("beatmap");
- AddStep("disable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, false));
+ AddStep("disable beatmap skin", () => LocalConfig.SetValue(OsuSetting.BeatmapSkins, false));
checkNextHitObject("user");
AddStep("disable user provider", () => testUserSkin.Enabled = false);
@@ -57,20 +57,20 @@ namespace osu.Game.Rulesets.Osu.Tests
{
AddStep("enable user provider", () => testUserSkin.Enabled = true);
- AddStep("enable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, true));
- AddStep("enable beatmap colours", () => LocalConfig.Set(OsuSetting.BeatmapColours, true));
+ AddStep("enable beatmap skin", () => LocalConfig.SetValue(OsuSetting.BeatmapSkins, true));
+ AddStep("enable beatmap colours", () => LocalConfig.SetValue(OsuSetting.BeatmapColours, true));
checkNextHitObject("beatmap");
- AddStep("enable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, true));
- AddStep("disable beatmap colours", () => LocalConfig.Set(OsuSetting.BeatmapColours, false));
+ AddStep("enable beatmap skin", () => LocalConfig.SetValue(OsuSetting.BeatmapSkins, true));
+ AddStep("disable beatmap colours", () => LocalConfig.SetValue(OsuSetting.BeatmapColours, false));
checkNextHitObject("beatmap");
- AddStep("disable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, false));
- AddStep("enable beatmap colours", () => LocalConfig.Set(OsuSetting.BeatmapColours, true));
+ AddStep("disable beatmap skin", () => LocalConfig.SetValue(OsuSetting.BeatmapSkins, false));
+ AddStep("enable beatmap colours", () => LocalConfig.SetValue(OsuSetting.BeatmapColours, true));
checkNextHitObject("user");
- AddStep("disable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, false));
- AddStep("disable beatmap colours", () => LocalConfig.Set(OsuSetting.BeatmapColours, false));
+ AddStep("disable beatmap skin", () => LocalConfig.SetValue(OsuSetting.BeatmapSkins, false));
+ AddStep("disable beatmap colours", () => LocalConfig.SetValue(OsuSetting.BeatmapColours, false));
checkNextHitObject("user");
AddStep("disable user provider", () => testUserSkin.Enabled = false);
@@ -162,7 +162,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null;
- public Sample GetSample(ISampleInfo sampleInfo) => null;
+ public ISample GetSample(ISampleInfo sampleInfo) => null;
public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => default;
public IBindable GetConfig(TLookup lookup) => null;
diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
index b4c686ccea..3d2d1f3fec 100644
--- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
+++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs
index e8272057f3..9589fd576f 100644
--- a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs
+++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs
@@ -17,10 +17,10 @@ namespace osu.Game.Rulesets.Osu.Configuration
protected override void InitialiseDefaults()
{
base.InitialiseDefaults();
- Set(OsuRulesetSetting.SnakingInSliders, true);
- Set(OsuRulesetSetting.SnakingOutSliders, true);
- Set(OsuRulesetSetting.ShowCursorTrail, true);
- Set(OsuRulesetSetting.PlayfieldBorderStyle, PlayfieldBorderStyle.None);
+ SetDefault(OsuRulesetSetting.SnakingInSliders, true);
+ SetDefault(OsuRulesetSetting.SnakingOutSliders, true);
+ SetDefault(OsuRulesetSetting.ShowCursorTrail, true);
+ SetDefault(OsuRulesetSetting.PlayfieldBorderStyle, PlayfieldBorderStyle.None);
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 6a7d76151c..75d6786d95 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -79,10 +79,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
}
}
- protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[]
+ protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[]
{
- new Aim(),
- new Speed()
+ new Aim(mods),
+ new Speed(mods)
};
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
index e74f4933b2..90cba13c7c 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
@@ -4,6 +4,7 @@
using System;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
+using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Objects;
@@ -17,6 +18,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
private const double angle_bonus_begin = Math.PI / 3;
private const double timing_threshold = 107;
+ public Aim(Mod[] mods)
+ : base(mods)
+ {
+ }
+
protected override double SkillMultiplier => 26.25;
protected override double StrainDecayBase => 0.15;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
index 01f2fb8dc8..200bc7997d 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
@@ -4,6 +4,7 @@
using System;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
+using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Objects;
@@ -27,6 +28,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
private const double max_speed_bonus = 45; // ~330BPM
private const double speed_balancing_factor = 40;
+ public Speed(Mod[] mods)
+ : base(mods)
+ {
+ }
+
protected override double StrainValueOf(DifficultyHitObject current)
{
if (current.BaseObject is Spinner)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
index e9838de63d..1390675a1a 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
@@ -7,11 +7,13 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
using osuTK;
@@ -23,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
///
/// A visualisation of a single in a .
///
- public class PathControlPointPiece : BlueprintPiece
+ public class PathControlPointPiece : BlueprintPiece, IHasTooltip
{
public Action RequestSelection;
@@ -195,7 +197,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
markerRing.Alpha = IsSelected.Value ? 1 : 0;
- Color4 colour = ControlPoint.Type.Value != null ? colours.Red : colours.Yellow;
+ Color4 colour = getColourFromNodeType();
if (IsHovered || IsSelected.Value)
colour = colour.Lighten(1);
@@ -203,5 +205,28 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
marker.Colour = colour;
marker.Scale = new Vector2(slider.Scale);
}
+
+ private Color4 getColourFromNodeType()
+ {
+ if (!(ControlPoint.Type.Value is PathType pathType))
+ return colours.Yellow;
+
+ switch (pathType)
+ {
+ case PathType.Catmull:
+ return colours.Seafoam;
+
+ case PathType.Bezier:
+ return colours.Pink;
+
+ case PathType.PerfectCurve:
+ return colours.PurpleDark;
+
+ default:
+ return colours.Red;
+ }
+ }
+
+ public string TooltipText => ControlPoint.Type.Value.ToString() ?? string.Empty;
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
index 3d3dff653a..ba9bb3c485 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
+using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -28,6 +29,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
protected SliderBodyPiece BodyPiece { get; private set; }
protected SliderCircleSelectionBlueprint HeadBlueprint { get; private set; }
protected SliderCircleSelectionBlueprint TailBlueprint { get; private set; }
+
+ [CanBeNull]
protected PathControlPointVisualiser ControlPointVisualiser { get; private set; }
private readonly DrawableSlider slider;
@@ -114,6 +117,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
// throw away frame buffers on deselection.
ControlPointVisualiser?.Expire();
+ ControlPointVisualiser = null;
+
BodyPiece.RecyclePath();
}
diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuSpinnerJudgementResult.cs b/osu.Game.Rulesets.Osu/Judgements/OsuSpinnerJudgementResult.cs
index e58aacd86e..9f77175398 100644
--- a/osu.Game.Rulesets.Osu/Judgements/OsuSpinnerJudgementResult.cs
+++ b/osu.Game.Rulesets.Osu/Judgements/OsuSpinnerJudgementResult.cs
@@ -38,6 +38,11 @@ namespace osu.Game.Rulesets.Osu.Judgements
///
public float RateAdjustedRotation;
+ ///
+ /// Time instant at which the spin was started (the first user input which caused an increase in spin).
+ ///
+ public double? TimeStarted;
+
///
/// Time instant at which the spinner has been completed (the user has executed all required spins).
/// Will be null if all required spins haven't been completed.
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
index 77de0cb45b..aac830801b 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override ModType Type => ModType.Automation;
public override string Description => @"Automatic cursor movement - just follow the rhythm.";
public override double ScoreMultiplier => 1;
- public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) };
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay) };
public bool PerformFail() => false;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
index df0a41455f..4b0939db16 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
@@ -11,7 +11,7 @@ using osu.Game.Rulesets.Osu.Skinning.Default;
namespace osu.Game.Rulesets.Osu.Mods
{
- internal class OsuModTraceable : ModWithVisibilityAdjustment
+ public class OsuModTraceable : ModWithVisibilityAdjustment
{
public override string Name => "Traceable";
public override string Acronym => "TC";
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
index 77094f928b..189003875d 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -164,28 +164,29 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
ApproachCircle.Expire(true);
}
+ protected override void UpdateStartTimeStateTransforms()
+ {
+ base.UpdateStartTimeStateTransforms();
+
+ ApproachCircle.FadeOut(50);
+ }
+
protected override void UpdateHitStateTransforms(ArmedState state)
{
Debug.Assert(HitObject.HitWindows != null);
+ // todo: temporary / arbitrary, used for lifetime optimisation.
+ this.Delay(800).FadeOut();
+
switch (state)
{
case ArmedState.Idle:
- this.Delay(HitObject.TimePreempt).FadeOut(500);
HitArea.HitAction = null;
break;
case ArmedState.Miss:
- ApproachCircle.FadeOut(50);
this.FadeOut(100);
break;
-
- case ArmedState.Hit:
- ApproachCircle.FadeOut(50);
-
- // todo: temporary / arbitrary
- this.Delay(800).FadeOut();
- break;
}
Expire();
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index d02376b6c3..39e78a14aa 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -33,12 +33,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public SpinnerSpmCounter SpmCounter { get; private set; }
private Container ticks;
- private SpinnerBonusDisplay bonusDisplay;
private PausableSkinnableSound spinningSample;
private Bindable isSpinning;
private bool spinnerFrequencyModulate;
+ ///
+ /// The amount of bonus score gained from spinning after the required number of spins, for display purposes.
+ ///
+ public IBindable GainedBonus => gainedBonus;
+
+ private readonly Bindable gainedBonus = new Bindable();
+
+ private const double fade_out_duration = 160;
+
public DrawableSpinner()
: this(null)
{
@@ -65,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
RelativeSizeAxes = Axes.Y,
Children = new Drawable[]
{
- new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerBody), _ => new DefaultSpinnerDisc()),
+ new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerBody), _ => new DefaultSpinner()),
RotationTracker = new SpinnerRotationTracker(this)
}
},
@@ -76,12 +84,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Y = 120,
Alpha = 0
},
- bonusDisplay = new SpinnerBonusDisplay
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Y = -120,
- },
spinningSample = new PausableSkinnableSound
{
Volume = { Value = 0 },
@@ -131,12 +133,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (tracking.NewValue)
{
if (!spinningSample.IsPlaying)
- spinningSample?.Play();
- spinningSample?.VolumeTo(1, 300);
+ spinningSample.Play();
+
+ spinningSample.VolumeTo(1, 300);
}
else
{
- spinningSample?.VolumeTo(0, 300).OnComplete(_ => spinningSample.Stop());
+ spinningSample.VolumeTo(0, fade_out_duration);
}
}
@@ -158,11 +161,29 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
}
+ protected override void UpdateStartTimeStateTransforms()
+ {
+ base.UpdateStartTimeStateTransforms();
+
+ if (Result?.TimeStarted is double startTime)
+ {
+ using (BeginAbsoluteSequence(startTime))
+ fadeInCounter();
+ }
+ }
+
protected override void UpdateHitStateTransforms(ArmedState state)
{
base.UpdateHitStateTransforms(state);
- this.FadeOut(160).Expire();
+ this.FadeOut(fade_out_duration).OnComplete(_ =>
+ {
+ // looping sample should be stopped here as it is safer than running in the OnComplete
+ // of the volume transition above.
+ spinningSample.Stop();
+ });
+
+ Expire();
// skin change does a rewind of transforms, which will stop the spinning sound from playing if it's currently in playback.
isSpinning?.TriggerChange();
@@ -262,13 +283,23 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
base.UpdateAfterChildren();
if (!SpmCounter.IsPresent && RotationTracker.Tracking)
- SpmCounter.FadeIn(HitObject.TimeFadeIn);
+ {
+ Result.TimeStarted ??= Time.Current;
+ fadeInCounter();
+ }
- SpmCounter.SetRotation(Result.RateAdjustedRotation);
+ // don't update after end time to avoid the rate display dropping during fade out.
+ // this shouldn't be limited to StartTime as it causes weirdness with the underlying calculation, which is expecting updates during that period.
+ if (Time.Current <= HitObject.EndTime)
+ SpmCounter.SetRotation(Result.RateAdjustedRotation);
updateBonusScore();
}
+ private void fadeInCounter() => SpmCounter.FadeIn(HitObject.TimeFadeIn);
+
+ private static readonly int score_per_tick = new SpinnerBonusTick.OsuSpinnerBonusTickJudgement().MaxNumericResult;
+
private int wholeSpins;
private void updateBonusScore()
@@ -293,8 +324,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (tick != null)
{
tick.TriggerResult(true);
+
if (tick is DrawableSpinnerBonusTick)
- bonusDisplay.SetBonusCount(spins - HitObject.SpinsRequired);
+ gainedBonus.Value = score_per_tick * (spins - HitObject.SpinsRequired);
}
wholeSpins++;
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 18324a18a8..838d707d64 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -29,6 +29,7 @@ using osu.Game.Scoring;
using osu.Game.Skinning;
using System;
using System.Linq;
+using osu.Framework.Extensions.EnumExtensions;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Skinning.Legacy;
using osu.Game.Rulesets.Osu.Statistics;
@@ -58,52 +59,52 @@ namespace osu.Game.Rulesets.Osu
public override IEnumerable ConvertFromLegacyMods(LegacyMods mods)
{
- if (mods.HasFlag(LegacyMods.Nightcore))
+ if (mods.HasFlagFast(LegacyMods.Nightcore))
yield return new OsuModNightcore();
- else if (mods.HasFlag(LegacyMods.DoubleTime))
+ else if (mods.HasFlagFast(LegacyMods.DoubleTime))
yield return new OsuModDoubleTime();
- if (mods.HasFlag(LegacyMods.Perfect))
+ if (mods.HasFlagFast(LegacyMods.Perfect))
yield return new OsuModPerfect();
- else if (mods.HasFlag(LegacyMods.SuddenDeath))
+ else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
yield return new OsuModSuddenDeath();
- if (mods.HasFlag(LegacyMods.Autopilot))
+ if (mods.HasFlagFast(LegacyMods.Autopilot))
yield return new OsuModAutopilot();
- if (mods.HasFlag(LegacyMods.Cinema))
+ if (mods.HasFlagFast(LegacyMods.Cinema))
yield return new OsuModCinema();
- else if (mods.HasFlag(LegacyMods.Autoplay))
+ else if (mods.HasFlagFast(LegacyMods.Autoplay))
yield return new OsuModAutoplay();
- if (mods.HasFlag(LegacyMods.Easy))
+ if (mods.HasFlagFast(LegacyMods.Easy))
yield return new OsuModEasy();
- if (mods.HasFlag(LegacyMods.Flashlight))
+ if (mods.HasFlagFast(LegacyMods.Flashlight))
yield return new OsuModFlashlight();
- if (mods.HasFlag(LegacyMods.HalfTime))
+ if (mods.HasFlagFast(LegacyMods.HalfTime))
yield return new OsuModHalfTime();
- if (mods.HasFlag(LegacyMods.HardRock))
+ if (mods.HasFlagFast(LegacyMods.HardRock))
yield return new OsuModHardRock();
- if (mods.HasFlag(LegacyMods.Hidden))
+ if (mods.HasFlagFast(LegacyMods.Hidden))
yield return new OsuModHidden();
- if (mods.HasFlag(LegacyMods.NoFail))
+ if (mods.HasFlagFast(LegacyMods.NoFail))
yield return new OsuModNoFail();
- if (mods.HasFlag(LegacyMods.Relax))
+ if (mods.HasFlagFast(LegacyMods.Relax))
yield return new OsuModRelax();
- if (mods.HasFlag(LegacyMods.SpunOut))
+ if (mods.HasFlagFast(LegacyMods.SpunOut))
yield return new OsuModSpunOut();
- if (mods.HasFlag(LegacyMods.Target))
+ if (mods.HasFlagFast(LegacyMods.Target))
yield return new OsuModTarget();
- if (mods.HasFlag(LegacyMods.TouchDevice))
+ if (mods.HasFlagFast(LegacyMods.TouchDevice))
yield return new OsuModTouchDevice();
}
diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
index 2883f0c187..fcb544fa5b 100644
--- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
+++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
@@ -18,6 +18,6 @@ namespace osu.Game.Rulesets.Osu
SliderFollowCircle,
SliderBall,
SliderBody,
- SpinnerBody
+ SpinnerBody,
}
}
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve-expected-conversion.json b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve-expected-conversion.json
index 96e4bf1637..1a0bd66246 100644
--- a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve-expected-conversion.json
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve-expected-conversion.json
@@ -1,5 +1,18 @@
{
"Mappings": [{
+ "StartTime": 114993,
+ "Objects": [{
+ "StartTime": 114993,
+ "EndTime": 114993,
+ "X": 493,
+ "Y": 92
+ }, {
+ "StartTime": 115290,
+ "EndTime": 115290,
+ "X": 451.659241,
+ "Y": 267.188
+ }]
+ }, {
"StartTime": 118858.0,
"Objects": [{
"StartTime": 118858.0,
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve.osu b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve.osu
index 8c3edc9571..dd35098502 100644
--- a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve.osu
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve.osu
@@ -9,7 +9,9 @@ SliderMultiplier:1.87
SliderTickRate:1
[TimingPoints]
-49051,230.769230769231,4,2,1,15,1,0
+114000,346.820809248555,4,2,1,71,1,0
+118000,230.769230769231,4,2,1,15,1,0
[HitObjects]
+493,92,114993,2,0,P|472:181|442:308,1,180,12|0,0:0|0:0,0:0:0:0:
219,215,118858,2,0,P|224:170|244:-10,1,187,8|2,0:0|0:0,0:0:0:0:
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs
new file mode 100644
index 0000000000..891821fe2f
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs
@@ -0,0 +1,68 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Globalization;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+
+namespace osu.Game.Rulesets.Osu.Skinning.Default
+{
+ public class DefaultSpinner : CompositeDrawable
+ {
+ private DrawableSpinner drawableSpinner;
+
+ private OsuSpriteText bonusCounter;
+
+ public DefaultSpinner()
+ {
+ RelativeSizeAxes = Axes.Both;
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(DrawableHitObject drawableHitObject)
+ {
+ drawableSpinner = (DrawableSpinner)drawableHitObject;
+
+ AddRangeInternal(new Drawable[]
+ {
+ new DefaultSpinnerDisc
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ bonusCounter = new OsuSpriteText
+ {
+ Alpha = 0,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Font = OsuFont.Numeric.With(size: 24),
+ Y = -120,
+ }
+ });
+ }
+
+ private IBindable gainedBonus;
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ gainedBonus = drawableSpinner.GainedBonus.GetBoundCopy();
+ gainedBonus.BindValueChanged(bonus =>
+ {
+ bonusCounter.Text = bonus.NewValue.ToString(NumberFormatInfo.InvariantInfo);
+ bonusCounter.FadeOutFromOne(1500);
+ bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint);
+ });
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs
index 667fee1495..542f3eff0d 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs
@@ -40,14 +40,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
public DefaultSpinnerDisc()
{
- RelativeSizeAxes = Axes.Both;
-
// we are slightly bigger than our parent, to clip the top and bottom of the circle
// this should probably be revisited when scaled spinners are a thing.
Scale = new Vector2(initial_scale);
-
- Anchor = Anchor.Centre;
- Origin = Anchor.Centre;
}
[BackgroundDependencyLoader]
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs
index fcbe4c1b28..46aeadc59b 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs
@@ -74,10 +74,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
private void updateState(DrawableHitObject drawableObject, ArmedState state)
{
- using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime, true))
- {
+ using (BeginAbsoluteSequence(drawableObject.StateUpdateTime))
glow.FadeOut(400);
+ using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
+ {
switch (state)
{
case ArmedState.Hit:
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/NumberPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/NumberPiece.cs
index bea6186501..43d8d1e27f 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/NumberPiece.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/NumberPiece.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
public string Text
{
- get => number.Text;
+ get => number.Text.ToString();
set => number.Text = value;
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerBonusDisplay.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerBonusDisplay.cs
deleted file mode 100644
index c0db6228ef..0000000000
--- a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerBonusDisplay.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Rulesets.Osu.Objects;
-
-namespace osu.Game.Rulesets.Osu.Skinning.Default
-{
- ///
- /// Shows incremental bonus score achieved for a spinner.
- ///
- public class SpinnerBonusDisplay : CompositeDrawable
- {
- private static readonly int score_per_tick = new SpinnerBonusTick().CreateJudgement().MaxNumericResult;
-
- private readonly OsuSpriteText bonusCounter;
-
- public SpinnerBonusDisplay()
- {
- AutoSizeAxes = Axes.Both;
-
- InternalChild = bonusCounter = new OsuSpriteText
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Font = OsuFont.Numeric.With(size: 24),
- Alpha = 0,
- };
- }
-
- private int displayedCount;
-
- public void SetBonusCount(int count)
- {
- if (displayedCount == count)
- return;
-
- displayedCount = count;
- bonusCounter.Text = $"{score_per_tick * count}";
- bonusCounter.FadeOutFromOne(1500);
- bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint);
- }
- }
-}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs
index efeca53969..22fb3aab86 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs
@@ -37,9 +37,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
AddInternal(scaleContainer = new Container
{
Scale = new Vector2(SPRITE_SCALE),
- Anchor = Anchor.Centre,
+ Anchor = Anchor.TopCentre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
+ Y = SPINNER_Y_CENTRE,
Children = new Drawable[]
{
glow = new Sprite
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs
index 4e07cb60b3..19cb55c16e 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs
@@ -33,47 +33,38 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
spinnerBlink = source.GetConfig(OsuSkinConfiguration.SpinnerNoBlink)?.Value != true;
- AddInternal(new Container
+ AddRangeInternal(new Drawable[]
{
- // the old-style spinner relied heavily on absolute screen-space coordinate values.
- // wrap everything in a container simulating absolute coords to preserve alignment
- // as there are skins that depend on it.
- Width = 640,
- Height = 480,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Children = new Drawable[]
+ new Sprite
{
- new Sprite
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.Centre,
+ Texture = source.GetTexture("spinner-background"),
+ Scale = new Vector2(SPRITE_SCALE),
+ Y = SPINNER_Y_CENTRE,
+ },
+ disc = new Sprite
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.Centre,
+ Texture = source.GetTexture("spinner-circle"),
+ Scale = new Vector2(SPRITE_SCALE),
+ Y = SPINNER_Y_CENTRE,
+ },
+ metre = new Container
+ {
+ AutoSizeAxes = Axes.Both,
+ // this anchor makes no sense, but that's what stable uses.
+ Anchor = Anchor.TopLeft,
+ Origin = Anchor.TopLeft,
+ Margin = new MarginPadding { Top = SPINNER_TOP_OFFSET },
+ Masking = true,
+ Child = metreSprite = new Sprite
{
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Texture = source.GetTexture("spinner-background"),
- Scale = new Vector2(SPRITE_SCALE)
- },
- disc = new Sprite
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Texture = source.GetTexture("spinner-circle"),
- Scale = new Vector2(SPRITE_SCALE)
- },
- metre = new Container
- {
- AutoSizeAxes = Axes.Both,
- // this anchor makes no sense, but that's what stable uses.
+ Texture = source.GetTexture("spinner-metre"),
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
- // adjustment for stable (metre has additional offset)
- Margin = new MarginPadding { Top = 20 },
- Masking = true,
- Child = metreSprite = new Sprite
- {
- Texture = source.GetTexture("spinner-metre"),
- Anchor = Anchor.TopLeft,
- Origin = Anchor.TopLeft,
- Scale = new Vector2(SPRITE_SCALE)
- }
+ Scale = new Vector2(SPRITE_SCALE)
}
}
});
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs
index ec7ecb0d28..dd1c6cad77 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Globalization;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -16,6 +17,15 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
public abstract class LegacySpinner : CompositeDrawable
{
+ ///
+ /// All constants are in osu!stable's gamefield space, which is shifted 16px downwards.
+ /// This offset is negated in both osu!stable and osu!lazer to bring all constants into window-space.
+ /// Note: SPINNER_Y_CENTRE + SPINNER_TOP_OFFSET - Position.Y = 240 (=480/2, or half the window-space in osu!stable)
+ ///
+ protected const float SPINNER_TOP_OFFSET = 45f - 16f;
+
+ protected const float SPINNER_Y_CENTRE = SPINNER_TOP_OFFSET + 219f;
+
protected const float SPRITE_SCALE = 0.625f;
protected DrawableSpinner DrawableSpinner { get; private set; }
@@ -23,43 +33,82 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
private Sprite spin;
private Sprite clear;
+ private LegacySpriteText bonusCounter;
+
[BackgroundDependencyLoader]
private void load(DrawableHitObject drawableHitObject, ISkinSource source)
{
- RelativeSizeAxes = Axes.Both;
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+
+ // osu!stable positions spinner components in window-space (as opposed to gamefield-space). This is a 640x480 area taking up the entire screen.
+ // In lazer, the gamefield-space positional transformation is applied in OsuPlayfieldAdjustmentContainer, which is inverted here to make this area take up the entire window space.
+ Size = new Vector2(640, 480);
+ Position = new Vector2(0, -8f);
DrawableSpinner = (DrawableSpinner)drawableHitObject;
- AddRangeInternal(new[]
+ Container overlayContainer;
+
+ AddInternal(overlayContainer = new Container
{
- spin = new Sprite
+ Depth = float.MinValue,
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
{
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Depth = float.MinValue,
- Texture = source.GetTexture("spinner-spin"),
- Scale = new Vector2(SPRITE_SCALE),
- Y = 120 - 45 // offset temporarily to avoid overlapping default spin counter
- },
- clear = new Sprite
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Depth = float.MinValue,
- Alpha = 0,
- Texture = source.GetTexture("spinner-clear"),
- Scale = new Vector2(SPRITE_SCALE),
- Y = -60
- },
+ spin = new Sprite
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.Centre,
+ Texture = source.GetTexture("spinner-spin"),
+ Scale = new Vector2(SPRITE_SCALE),
+ Y = SPINNER_TOP_OFFSET + 335,
+ },
+ clear = new Sprite
+ {
+ Alpha = 0,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.Centre,
+ Texture = source.GetTexture("spinner-clear"),
+ Scale = new Vector2(SPRITE_SCALE),
+ Y = SPINNER_TOP_OFFSET + 115,
+ },
+ }
});
+
+ bonusCounter = (source.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) as LegacySpriteText)?.With(c =>
+ {
+ c.Alpha = 0f;
+ c.Anchor = Anchor.TopCentre;
+ c.Origin = Anchor.Centre;
+ c.Font = c.Font.With(fixedWidth: false);
+ c.Scale = new Vector2(SPRITE_SCALE);
+ c.Y = SPINNER_TOP_OFFSET + 299;
+ });
+
+ if (bonusCounter != null)
+ overlayContainer.Add(bonusCounter);
}
+ private IBindable gainedBonus;
+
private readonly Bindable completed = new Bindable();
protected override void LoadComplete()
{
base.LoadComplete();
+ if (bonusCounter != null)
+ {
+ gainedBonus = DrawableSpinner.GainedBonus.GetBoundCopy();
+ gainedBonus.BindValueChanged(bonus =>
+ {
+ bonusCounter.Text = bonus.NewValue.ToString(NumberFormatInfo.InvariantInfo);
+ bonusCounter.FadeOutFromOne(800, Easing.Out);
+ bonusCounter.ScaleTo(SPRITE_SCALE * 2f).Then().ScaleTo(SPRITE_SCALE * 1.28f, 800, Easing.Out);
+ });
+ }
+
completed.BindValueChanged(onCompletedChanged, true);
DrawableSpinner.ApplyCustomUpdateState += UpdateStateTransforms;
diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs
index fecb5d4a74..61ea8b664d 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs
@@ -2,8 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
+using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Testing;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.UI;
@@ -13,6 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
[TestFixture]
public class TestSceneHitExplosion : TaikoSkinnableTestScene
{
+ protected override double TimePerAction => 100;
+
[Test]
public void TestNormalHit()
{
@@ -21,11 +27,14 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
AddStep("Miss", () => SetContents(() => getContentFor(createHit(HitResult.Miss))));
}
- [Test]
- public void TestStrongHit([Values(false, true)] bool hitBoth)
+ [TestCase(HitResult.Great)]
+ [TestCase(HitResult.Ok)]
+ public void TestStrongHit(HitResult type)
{
- AddStep("Great", () => SetContents(() => getContentFor(createStrongHit(HitResult.Great, hitBoth))));
- AddStep("Good", () => SetContents(() => getContentFor(createStrongHit(HitResult.Ok, hitBoth))));
+ AddStep("create hit", () => SetContents(() => getContentFor(createStrongHit(type))));
+ AddStep("visualise second hit",
+ () => this.ChildrenOfType()
+ .ForEach(e => e.VisualiseSecondHit(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement()))));
}
private Drawable getContentFor(DrawableTestHit hit)
@@ -38,17 +47,17 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
// the hit needs to be added to hierarchy in order for nested objects to be created correctly.
// setting zero alpha is supposed to prevent the test from looking broken.
hit.With(h => h.Alpha = 0),
- new HitExplosion(hit, hit.Type)
+ new HitExplosion(hit.Type)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- }
+ }.With(explosion => explosion.Apply(hit))
}
};
}
private DrawableTestHit createHit(HitResult type) => new DrawableTestHit(new Hit { StartTime = Time.Current }, type);
- private DrawableTestHit createStrongHit(HitResult type, bool hitBoth) => new DrawableTestStrongHit(Time.Current, type, hitBoth);
+ private DrawableTestHit createStrongHit(HitResult type) => new DrawableTestStrongHit(Time.Current, type);
}
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs
index 7695ca067b..87c936d386 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs
@@ -11,7 +11,6 @@ using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects;
@@ -108,12 +107,12 @@ namespace osu.Game.Rulesets.Taiko.Tests
{
HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Ok : HitResult.Great;
- Hit hit = new Hit();
+ Hit hit = new Hit { StartTime = DrawableRuleset.Playfield.Time.Current };
var h = new DrawableTestHit(hit, kiai: kiai) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) };
DrawableRuleset.Playfield.Add(h);
- ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult });
+ ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(hit, new TaikoJudgement()) { Type = hitResult });
}
private void addStrongHitJudgement(bool kiai)
@@ -122,6 +121,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
Hit hit = new Hit
{
+ StartTime = DrawableRuleset.Playfield.Time.Current,
IsStrong = true,
Samples = createSamples(strong: true)
};
@@ -129,8 +129,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
DrawableRuleset.Playfield.Add(h);
- ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult });
- ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h.NestedHitObjects.Single(), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great });
+ ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(hit, new TaikoJudgement()) { Type = hitResult });
+ ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h.NestedHitObjects.Single(), new JudgementResult(hit.NestedHitObjects.Single(), new TaikoStrongJudgement()) { Type = HitResult.Great });
}
private void addMissJudgement()
diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
index 2b084f3bee..fa00922706 100644
--- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
+++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs
index 32421ee00a..cc0738e252 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs
@@ -5,6 +5,7 @@ using System;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Difficulty.Utils;
+using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
using osu.Game.Rulesets.Taiko.Objects;
@@ -39,6 +40,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
///
private int currentMonoLength;
+ public Colour(Mod[] mods)
+ : base(mods)
+ {
+ }
+
protected override double StrainValueOf(DifficultyHitObject current)
{
// changing from/to a drum roll or a swell does not constitute a colour change.
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs
index 5569b27ad5..f2b8309ac5 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs
@@ -5,6 +5,7 @@ using System;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Difficulty.Utils;
+using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
using osu.Game.Rulesets.Taiko.Objects;
@@ -47,6 +48,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
///
private int notesSinceRhythmChange;
+ public Rhythm(Mod[] mods)
+ : base(mods)
+ {
+ }
+
protected override double StrainValueOf(DifficultyHitObject current)
{
// drum rolls and swells are exempt.
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs
index 0b61eb9930..c34cce0cd6 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs
@@ -5,6 +5,7 @@ using System.Linq;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Difficulty.Utils;
+using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
using osu.Game.Rulesets.Taiko.Objects;
@@ -48,8 +49,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
///
/// Creates a skill.
///
+ /// Mods for use in skill calculations.
/// Whether this instance is performing calculations for the right hand.
- public Stamina(bool rightHand)
+ public Stamina(Mod[] mods, bool rightHand)
+ : base(mods)
{
hand = rightHand ? 1 : 0;
}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
index e5485db4df..fc198d2493 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
@@ -29,12 +29,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
{
}
- protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[]
+ protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[]
{
- new Colour(),
- new Rhythm(),
- new Stamina(true),
- new Stamina(false),
+ new Colour(mods),
+ new Rhythm(mods),
+ new Stamina(mods, true),
+ new Stamina(mods, false),
};
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs
index 3fbcee44af..ac2dd4bdb6 100644
--- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs
+++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs
@@ -52,32 +52,24 @@ namespace osu.Game.Rulesets.Taiko.Edit
public void SetStrongState(bool state)
{
- var hits = EditorBeatmap.SelectedHitObjects.OfType();
-
- EditorBeatmap.BeginChange();
-
- foreach (var h in hits)
+ EditorBeatmap.PerformOnSelection(h =>
{
- if (h.IsStrong != state)
- {
- h.IsStrong = state;
- EditorBeatmap.Update(h);
- }
- }
+ if (!(h is Hit taikoHit)) return;
- EditorBeatmap.EndChange();
+ if (taikoHit.IsStrong != state)
+ {
+ taikoHit.IsStrong = state;
+ EditorBeatmap.Update(taikoHit);
+ }
+ });
}
public void SetRimState(bool state)
{
- var hits = EditorBeatmap.SelectedHitObjects.OfType();
-
- EditorBeatmap.BeginChange();
-
- foreach (var h in hits)
- h.Type = state ? HitType.Rim : HitType.Centre;
-
- EditorBeatmap.EndChange();
+ EditorBeatmap.PerformOnSelection(h =>
+ {
+ if (h is Hit taikoHit) taikoHit.Type = state ? HitType.Rim : HitType.Centre;
+ });
}
protected override IEnumerable
+
+ $(NoWarn);CA2007
+
{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}
@@ -45,7 +48,7 @@
-
+
-
\ No newline at end of file
+
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index c32e359de6..0c35e9471d 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -852,6 +852,21 @@ namespace osu.Game.Tests.Beatmaps.IO
}
}
+ public static async Task LoadQuickOszIntoOsu(OsuGameBase osu)
+ {
+ var temp = TestResources.GetQuickTestBeatmapForImport();
+
+ var manager = osu.Dependencies.Get();
+
+ var importedSet = await manager.Import(new ImportTask(temp));
+
+ ensureLoaded(osu);
+
+ waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
+
+ return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID);
+ }
+
public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false)
{
var temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack);
diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
index 3ded3009bd..883791c35c 100644
--- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
@@ -121,7 +121,7 @@ namespace osu.Game.Tests.Gameplay
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException();
- public Sample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
+ public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public IBindable GetConfig(TLookup lookup)
{
diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
index 7a0dd5b719..cae5f20332 100644
--- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
@@ -13,6 +13,7 @@ using osu.Framework.Graphics.Audio;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Framework.Testing;
+using osu.Framework.Utils;
using osu.Game.Audio;
using osu.Game.IO;
using osu.Game.Rulesets;
@@ -35,7 +36,7 @@ namespace osu.Game.Tests.Gameplay
public void TestRetrieveTopLevelSample()
{
ISkin skin = null;
- Sample channel = null;
+ ISample channel = null;
AddStep("create skin", () => skin = new TestSkin("test-sample", this));
AddStep("retrieve sample", () => channel = skin.GetSample(new SampleInfo("test-sample")));
@@ -47,7 +48,7 @@ namespace osu.Game.Tests.Gameplay
public void TestRetrieveSampleInSubFolder()
{
ISkin skin = null;
- Sample channel = null;
+ ISample channel = null;
AddStep("create skin", () => skin = new TestSkin("folder/test-sample", this));
AddStep("retrieve sample", () => channel = skin.GetSample(new SampleInfo("folder/test-sample")));
@@ -90,6 +91,7 @@ namespace osu.Game.Tests.Gameplay
public void TestSamplePlaybackWithRateMods(Type expectedMod, double expectedRate)
{
GameplayClockContainer gameplayContainer = null;
+ StoryboardSampleInfo sampleInfo = null;
TestDrawableStoryboardSample sample = null;
Mod testedMod = Activator.CreateInstance(expectedMod) as Mod;
@@ -101,7 +103,7 @@ namespace osu.Game.Tests.Gameplay
break;
case ModTimeRamp m:
- m.InitialRate.Value = m.FinalRate.Value = expectedRate;
+ m.FinalRate.Value = m.InitialRate.Value = expectedRate;
break;
}
@@ -117,7 +119,7 @@ namespace osu.Game.Tests.Gameplay
Child = beatmapSkinSourceContainer
});
- beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1))
+ beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(sampleInfo = new StoryboardSampleInfo("test-sample", 1, 1))
{
Clock = gameplayContainer.GameplayClock
});
@@ -125,7 +127,10 @@ namespace osu.Game.Tests.Gameplay
AddStep("start", () => gameplayContainer.Start());
- AddAssert("sample playback rate matches mod rates", () => sample.ChildrenOfType().First().AggregateFrequency.Value == expectedRate);
+ AddAssert("sample playback rate matches mod rates", () =>
+ testedMod != null && Precision.AlmostEquals(
+ sample.ChildrenOfType().First().AggregateFrequency.Value,
+ ((IApplicableToRate)testedMod).ApplyToRate(sampleInfo.StartTime)));
}
private class TestSkin : LegacySkin
diff --git a/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs b/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs
index b90382488f..27cece42e8 100644
--- a/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs
+++ b/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs
@@ -88,7 +88,7 @@ namespace osu.Game.Tests.Input
=> AddStep($"make window {mode}", () => frameworkConfigManager.GetBindable(FrameworkSetting.WindowMode).Value = mode);
private void setGameSideModeTo(OsuConfineMouseMode mode)
- => AddStep($"set {mode} game-side", () => Game.LocalConfig.Set(OsuSetting.ConfineMouseMode, mode));
+ => AddStep($"set {mode} game-side", () => Game.LocalConfig.SetValue(OsuSetting.ConfineMouseMode, mode));
private void setLocalUserPlayingTo(bool playing)
=> AddStep($"local user {(playing ? "playing" : "not playing")}", () => Game.LocalUserPlaying.Value = playing);
diff --git a/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs b/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs
new file mode 100644
index 0000000000..883c9d1ac2
--- /dev/null
+++ b/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs
@@ -0,0 +1,44 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Bindables;
+using osu.Game.Configuration;
+
+namespace osu.Game.Tests.Mods
+{
+ [TestFixture]
+ public class SettingsSourceAttributeTest
+ {
+ [Test]
+ public void TestOrdering()
+ {
+ var objectWithSettings = new ClassWithSettings();
+
+ var orderedSettings = objectWithSettings.GetOrderedSettingsSourceProperties().ToArray();
+
+ Assert.That(orderedSettings, Has.Length.EqualTo(4));
+
+ Assert.That(orderedSettings[0].Item2.Name, Is.EqualTo(nameof(ClassWithSettings.FirstSetting)));
+ Assert.That(orderedSettings[1].Item2.Name, Is.EqualTo(nameof(ClassWithSettings.SecondSetting)));
+ Assert.That(orderedSettings[2].Item2.Name, Is.EqualTo(nameof(ClassWithSettings.ThirdSetting)));
+ Assert.That(orderedSettings[3].Item2.Name, Is.EqualTo(nameof(ClassWithSettings.UnorderedSetting)));
+ }
+
+ private class ClassWithSettings
+ {
+ [SettingSource("Unordered setting", "Should be last")]
+ public BindableFloat UnorderedSetting { get; set; } = new BindableFloat();
+
+ [SettingSource("Second setting", "Another description", 2)]
+ public BindableBool SecondSetting { get; set; } = new BindableBool();
+
+ [SettingSource("First setting", "A description", 1)]
+ public BindableDouble FirstSetting { get; set; } = new BindableDouble();
+
+ [SettingSource("Third setting", "Yet another description", 3)]
+ public BindableInt ThirdSetting { get; set; } = new BindableInt();
+ }
+ }
+}
diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs
index 045246e5ed..a763544c37 100644
--- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs
+++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs
@@ -47,7 +47,7 @@ namespace osu.Game.Tests.NonVisual
using (var host = new CustomTestHeadlessGameHost())
{
using (var storageConfig = new StorageConfigManager(host.InitialStorage))
- storageConfig.Set(StorageConfig.FullPath, customPath);
+ storageConfig.SetValue(StorageConfig.FullPath, customPath);
try
{
@@ -73,7 +73,7 @@ namespace osu.Game.Tests.NonVisual
using (var host = new CustomTestHeadlessGameHost())
{
using (var storageConfig = new StorageConfigManager(host.InitialStorage))
- storageConfig.Set(StorageConfig.FullPath, customPath);
+ storageConfig.SetValue(StorageConfig.FullPath, customPath);
try
{
diff --git a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs
index 5c7adb3f49..1c0bfd56dd 100644
--- a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs
+++ b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs
@@ -212,7 +212,7 @@ namespace osu.Game.Tests.NonVisual
throw new NotImplementedException();
}
- protected override Skill[] CreateSkills(IBeatmap beatmap)
+ protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods)
{
throw new NotImplementedException();
}
diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
index 24a0a662ba..8ff2743b6a 100644
--- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
+++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
@@ -4,8 +4,10 @@
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Filter;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Carousel;
+using osu.Game.Screens.Select.Filter;
namespace osu.Game.Tests.NonVisual.Filtering
{
@@ -214,5 +216,31 @@ namespace osu.Game.Tests.NonVisual.Filtering
Assert.AreEqual(filtered, carouselItem.Filtered.Value);
}
+
+ [Test]
+ public void TestCustomRulesetCriteria([Values(null, true, false)] bool? matchCustomCriteria)
+ {
+ var beatmap = getExampleBeatmap();
+
+ var customCriteria = matchCustomCriteria is bool match ? new CustomCriteria(match) : null;
+ var criteria = new FilterCriteria { RulesetCriteria = customCriteria };
+ var carouselItem = new CarouselBeatmap(beatmap);
+ carouselItem.Filter(criteria);
+
+ Assert.AreEqual(matchCustomCriteria == false, carouselItem.Filtered.Value);
+ }
+
+ private class CustomCriteria : IRulesetFilterCriteria
+ {
+ private readonly bool match;
+
+ public CustomCriteria(bool shouldMatch)
+ {
+ match = shouldMatch;
+ }
+
+ public bool Matches(BeatmapInfo beatmap) => match;
+ public bool TryParseCustomKeywordCriteria(string key, Operator op, string value) => false;
+ }
}
}
diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
index d15682b1eb..49389e67aa 100644
--- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
+++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
@@ -4,7 +4,9 @@
using System;
using NUnit.Framework;
using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Filter;
using osu.Game.Screens.Select;
+using osu.Game.Screens.Select.Filter;
namespace osu.Game.Tests.NonVisual.Filtering
{
@@ -194,5 +196,63 @@ namespace osu.Game.Tests.NonVisual.Filtering
Assert.AreEqual(1, filterCriteria.SearchTerms.Length);
Assert.AreEqual("double\"quote", filterCriteria.Artist.SearchTerm);
}
+
+ [Test]
+ public void TestOperatorParsing()
+ {
+ const string query = "artist=>=bad")]
+ [TestCase("divisor true;
+
+ public bool TryParseCustomKeywordCriteria(string key, Operator op, string value)
+ {
+ if (key == "custom" && op == Operator.Equal)
+ {
+ CustomValue = value;
+ return true;
+ }
+
+ return false;
+ }
+ }
}
}
diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs
index da004b9088..b08a228de3 100644
--- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs
+++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs
@@ -59,7 +59,7 @@ namespace osu.Game.Tests.NonVisual.Skinning
}
public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotSupportedException();
- public Sample GetSample(ISampleInfo sampleInfo) => throw new NotSupportedException();
+ public ISample GetSample(ISampleInfo sampleInfo) => throw new NotSupportedException();
public IBindable GetConfig(TLookup lookup) => throw new NotSupportedException();
}
diff --git a/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs b/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs
index 42948c3731..aa29d76843 100644
--- a/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs
+++ b/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs
@@ -23,8 +23,10 @@ namespace osu.Game.Tests.Online
{
case CommentVoteRequest cRequest:
cRequest.TriggerSuccess(new CommentBundle());
- break;
+ return true;
}
+
+ return false;
});
CommentVoteRequest request = null;
@@ -108,8 +110,10 @@ namespace osu.Game.Tests.Online
{
case LeaveChannelRequest cRequest:
cRequest.TriggerSuccess();
- break;
+ return true;
}
+
+ return false;
});
}
}
diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
index 3ffb512b7f..8c30802ce3 100644
--- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
+++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
@@ -52,7 +52,7 @@ namespace osu.Game.Tests.Online
{
beatmaps.AllowImport = new TaskCompletionSource();
- testBeatmapFile = TestResources.GetTestBeatmapForImport();
+ testBeatmapFile = TestResources.GetQuickTestBeatmapForImport();
testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile);
testBeatmapSet = testBeatmapInfo.BeatmapSet;
diff --git a/osu.Game.Tests/OnlinePlay/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/OnlinePlay/StatefulMultiplayerClientTest.cs
new file mode 100644
index 0000000000..82ce588c6f
--- /dev/null
+++ b/osu.Game.Tests/OnlinePlay/StatefulMultiplayerClientTest.cs
@@ -0,0 +1,35 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Testing;
+using osu.Game.Tests.Visual.Multiplayer;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.OnlinePlay
+{
+ [HeadlessTest]
+ public class StatefulMultiplayerClientTest : MultiplayerTestScene
+ {
+ [Test]
+ public void TestUserAddedOnJoin()
+ {
+ var user = new User { Id = 33 };
+
+ AddRepeatStep("add user multiple times", () => Client.AddUser(user), 3);
+ AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2);
+ }
+
+ [Test]
+ public void TestUserRemovedOnLeave()
+ {
+ var user = new User { Id = 44 };
+
+ AddStep("add user", () => Client.AddUser(user));
+ AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2);
+
+ AddRepeatStep("remove user multiple times", () => Client.RemoveUser(user), 3);
+ AddAssert("room has 1 user", () => Client.Room?.Users.Count == 1);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus_virtual_quick.osz b/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus_virtual_quick.osz
new file mode 100644
index 0000000000..e9f5fb0328
Binary files /dev/null and b/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus_virtual_quick.osz differ
diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs
index e882229570..cef0532f9d 100644
--- a/osu.Game.Tests/Resources/TestResources.cs
+++ b/osu.Game.Tests/Resources/TestResources.cs
@@ -15,6 +15,28 @@ namespace osu.Game.Tests.Resources
public static Stream GetTestBeatmapStream(bool virtualTrack = false) => OpenResource($"Archives/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}.osz");
+ ///
+ /// Retrieve a path to a copy of a shortened (~10 second) beatmap archive with a virtual track.
+ ///
+ ///
+ /// This is intended for use in tests which need to run to completion as soon as possible and don't need to test a full length beatmap.
+ /// A path to a copy of a beatmap archive (osz). Should be deleted after use.
+ public static string GetQuickTestBeatmapForImport()
+ {
+ var tempPath = Path.GetTempFileName() + ".osz";
+ using (var stream = OpenResource("Archives/241526 Soleily - Renatus_virtual_quick.osz"))
+ using (var newFile = File.Create(tempPath))
+ stream.CopyTo(newFile);
+
+ Assert.IsTrue(File.Exists(tempPath));
+ return tempPath;
+ }
+
+ ///
+ /// Retrieve a path to a copy of a full-fledged beatmap archive.
+ ///
+ /// Whether the audio track should be virtual.
+ /// A path to a copy of a beatmap archive (osz). Should be deleted after use.
public static string GetTestBeatmapForImport(bool virtualTrack = false)
{
var tempPath = Path.GetTempFileName() + ".osz";
diff --git a/osu.Game.Tests/Resources/skin-with-space.ini b/osu.Game.Tests/Resources/skin-with-space.ini
new file mode 100644
index 0000000000..3e64257a3e
--- /dev/null
+++ b/osu.Game.Tests/Resources/skin-with-space.ini
@@ -0,0 +1,2 @@
+[General]
+Version: 2
\ No newline at end of file
diff --git a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs
index 4aebed0d31..f421a30283 100644
--- a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs
+++ b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs
@@ -118,6 +118,10 @@ namespace osu.Game.Tests.Rulesets
public BindableNumber Frequency => throw new NotImplementedException();
public BindableNumber Tempo => throw new NotImplementedException();
+ public void BindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException();
+
+ public void UnbindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException();
+
public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => throw new NotImplementedException();
public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => throw new NotImplementedException();
diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs
index a5b4b04ef5..8124bd4199 100644
--- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs
+++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs
@@ -113,6 +113,31 @@ namespace osu.Game.Tests.Skins.IO
}
}
+ [Test]
+ public async Task TestImportUpperCasedOskArchive()
+ {
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest)))
+ {
+ try
+ {
+ var osu = LoadOsuIntoHost(host);
+
+ var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("name 1", "author 1"), "skin1.OsK"));
+
+ Assert.That(imported.Name, Is.EqualTo("name 1"));
+ Assert.That(imported.Creator, Is.EqualTo("author 1"));
+
+ var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("name 1", "author 1"), "skin1.oSK"));
+
+ Assert.That(imported2.Hash, Is.EqualTo(imported.Hash));
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
private MemoryStream createOsk(string name, string author)
{
var zipStream = new MemoryStream();
diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
index aedf26ee75..dcb866c99f 100644
--- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
+++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
@@ -91,6 +91,15 @@ namespace osu.Game.Tests.Skins
Assert.AreEqual(2.0m, decoder.Decode(stream).LegacyVersion);
}
+ [Test]
+ public void TestStripWhitespace()
+ {
+ var decoder = new LegacySkinDecoder();
+ using (var resStream = TestResources.OpenResource("skin-with-space.ini"))
+ using (var stream = new LineBufferedReader(resStream))
+ Assert.AreEqual(2.0m, decoder.Decode(stream).LegacyVersion);
+ }
+
[Test]
public void TestDecodeLatestVersion()
{
diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
index 414f7d3f88..732a3f3f42 100644
--- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
+++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
@@ -219,7 +219,7 @@ namespace osu.Game.Tests.Skins
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => skin.GetTexture(componentName, wrapModeS, wrapModeT);
- public Sample GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo);
+ public ISample GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo);
public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup);
}
diff --git a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs
index fba0d92d4b..dc5a4f4a3e 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs
@@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Background
public void SetUp() => Schedule(() =>
{
// reset API response in statics to avoid test crosstalk.
- statics.Set(Static.SeasonalBackgrounds, null);
+ statics.SetValue(Static.SeasonalBackgrounds, null);
textureStore.PerformedLookups.Clear();
dummyAPI.SetState(APIState.Online);
@@ -135,18 +135,20 @@ namespace osu.Game.Tests.Visual.Background
dummyAPI.HandleRequest = request =>
{
if (dummyAPI.State.Value != APIState.Online || !(request is GetSeasonalBackgroundsRequest backgroundsRequest))
- return;
+ return false;
backgroundsRequest.TriggerSuccess(new APISeasonalBackgrounds
{
Backgrounds = seasonal_background_urls.Select(url => new APISeasonalBackground { Url = url }).ToList(),
EndDate = endDate
});
+
+ return true;
};
});
private void setSeasonalBackgroundMode(SeasonalBackgroundMode mode)
- => AddStep($"set seasonal mode to {mode}", () => config.Set(OsuSetting.SeasonalBackgroundMode, mode));
+ => AddStep($"set seasonal mode to {mode}", () => config.SetValue(OsuSetting.SeasonalBackgroundMode, mode));
private void createLoader()
=> AddStep("create loader", () =>
diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
index 7ade7725d9..ba4d12b19f 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
@@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Background
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
Dependencies.Cache(new OsuConfigManager(LocalStorage));
- manager.Import(TestResources.GetTestBeatmapForImport()).Wait();
+ manager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
Beatmap.SetDefault();
}
diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs
index fef1605f0c..eca857f9e5 100644
--- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs
+++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs
@@ -38,13 +38,13 @@ namespace osu.Game.Tests.Visual.Collections
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default));
- beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait();
+ beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
base.Content.AddRange(new Drawable[]
{
manager = new CollectionManager(LocalStorage),
Content,
- dialogOverlay = new DialogOverlay()
+ dialogOverlay = new DialogOverlay(),
});
Dependencies.Cache(manager);
@@ -134,6 +134,27 @@ namespace osu.Game.Tests.Visual.Collections
assertCollectionName(0, "2");
}
+ [Test]
+ public void TestCollectionNameCollisions()
+ {
+ AddStep("add dropdown", () =>
+ {
+ Add(new CollectionFilterDropdown
+ {
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ RelativeSizeAxes = Axes.X,
+ Width = 0.4f,
+ }
+ );
+ });
+ AddStep("add two collections with same name", () => manager.Collections.AddRange(new[]
+ {
+ new BeatmapCollection { Name = { Value = "1" } },
+ new BeatmapCollection { Name = { Value = "1" }, Beatmaps = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0] } },
+ }));
+ }
+
[Test]
public void TestRemoveCollectionViaButton()
{
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs
new file mode 100644
index 0000000000..fd9c09fd5f
--- /dev/null
+++ b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs
@@ -0,0 +1,70 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Screens.Edit.Compose.Components;
+using osu.Game.Tests.Beatmaps;
+using osuTK;
+using osuTK.Input;
+
+namespace osu.Game.Tests.Visual.Editing
+{
+ public class TestSceneBlueprintSelection : EditorTestScene
+ {
+ protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
+
+ private BlueprintContainer blueprintContainer
+ => Editor.ChildrenOfType().First();
+
+ [Test]
+ public void TestSelectedObjectHasPriorityWhenOverlapping()
+ {
+ var firstSlider = new Slider
+ {
+ Path = new SliderPath(new[]
+ {
+ new PathControlPoint(new Vector2()),
+ new PathControlPoint(new Vector2(150, -50)),
+ new PathControlPoint(new Vector2(300, 0))
+ }),
+ Position = new Vector2(0, 100)
+ };
+ var secondSlider = new Slider
+ {
+ Path = new SliderPath(new[]
+ {
+ new PathControlPoint(new Vector2()),
+ new PathControlPoint(new Vector2(-50, 50)),
+ new PathControlPoint(new Vector2(-100, 100))
+ }),
+ Position = new Vector2(200, 0)
+ };
+
+ AddStep("add overlapping sliders", () =>
+ {
+ EditorBeatmap.Add(firstSlider);
+ EditorBeatmap.Add(secondSlider);
+ });
+ AddStep("select first slider", () => EditorBeatmap.SelectedHitObjects.Add(firstSlider));
+
+ AddStep("move mouse to common point", () =>
+ {
+ var pos = blueprintContainer.ChildrenOfType().ElementAt(1).ScreenSpaceDrawQuad.Centre;
+ InputManager.MoveMouseTo(pos);
+ });
+ AddStep("right click", () => InputManager.Click(MouseButton.Right));
+
+ AddAssert("selection is unchanged", () => EditorBeatmap.SelectedHitObjects.Single() == firstSlider);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs
new file mode 100644
index 0000000000..390198be04
--- /dev/null
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs
@@ -0,0 +1,81 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Screens.Edit.Components;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.Editing
+{
+ [TestFixture]
+ public class TestSceneEditorClock : EditorClockTestScene
+ {
+ public TestSceneEditorClock()
+ {
+ Add(new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new TimeInfoContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(200, 100)
+ },
+ new PlaybackControl
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(200, 100)
+ }
+ }
+ });
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
+ // ensure that music controller does not change this beatmap due to it
+ // completing naturally as part of the test.
+ Beatmap.Disabled = true;
+ }
+
+ [Test]
+ public void TestStopAtTrackEnd()
+ {
+ AddStep("reset clock", () => Clock.Seek(0));
+
+ AddStep("start clock", Clock.Start);
+ AddAssert("clock running", () => Clock.IsRunning);
+
+ AddStep("seek near end", () => Clock.Seek(Clock.TrackLength - 250));
+ AddUntilStep("clock stops", () => !Clock.IsRunning);
+
+ AddAssert("clock stopped at end", () => Clock.CurrentTime == Clock.TrackLength);
+
+ AddStep("start clock again", Clock.Start);
+ AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500);
+ }
+
+ [Test]
+ public void TestWrapWhenStoppedAtTrackEnd()
+ {
+ AddStep("reset clock", () => Clock.Seek(0));
+
+ AddStep("stop clock", Clock.Stop);
+ AddAssert("clock stopped", () => !Clock.IsRunning);
+
+ AddStep("seek exactly to end", () => Clock.Seek(Clock.TrackLength));
+ AddAssert("clock stopped at end", () => Clock.CurrentTime == Clock.TrackLength);
+
+ AddStep("start clock again", Clock.Start);
+ AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs
index d6db171cf0..1da6433707 100644
--- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs
+++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs
@@ -110,8 +110,6 @@ namespace osu.Game.Tests.Visual.Editing
[Resolved]
private EditorClock editorClock { get; set; }
- private bool started;
-
public StartStopButton()
{
BackgroundColour = Color4.SlateGray;
@@ -123,18 +121,17 @@ namespace osu.Game.Tests.Visual.Editing
private void onClick()
{
- if (started)
- {
+ if (editorClock.IsRunning)
editorClock.Stop();
- Text = "Start";
- }
else
- {
editorClock.Start();
- Text = "Stop";
- }
+ }
- started = !started;
+ protected override void Update()
+ {
+ base.Update();
+
+ Text = editorClock.IsRunning ? "Stop" : "Start";
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs
index 1c55595c97..5a1a9d3d87 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs
@@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay
});
AddStep("show health", () => showHealth.Value = true);
- AddStep("enable layer", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true));
+ AddStep("enable layer", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, true));
AddUntilStep("layer is visible", () => layer.IsPresent);
}
@@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestLayerDisabledViaConfig()
{
- AddStep("disable layer", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false));
+ AddStep("disable layer", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false));
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
AddUntilStep("layer is not visible", () => !layer.IsPresent);
}
@@ -81,19 +81,19 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("set health to 0.10", () => layer.Current.Value = 0.1);
AddStep("don't show health", () => showHealth.Value = false);
- AddStep("disable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false));
+ AddStep("disable FadePlayfieldWhenHealthLow", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false));
AddUntilStep("layer fade is invisible", () => !layer.IsPresent);
AddStep("don't show health", () => showHealth.Value = false);
- AddStep("enable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true));
+ AddStep("enable FadePlayfieldWhenHealthLow", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, true));
AddUntilStep("layer fade is invisible", () => !layer.IsPresent);
AddStep("show health", () => showHealth.Value = true);
- AddStep("disable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false));
+ AddStep("disable FadePlayfieldWhenHealthLow", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false));
AddUntilStep("layer fade is invisible", () => !layer.IsPresent);
AddStep("show health", () => showHealth.Value = true);
- AddStep("enable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true));
+ AddStep("enable FadePlayfieldWhenHealthLow", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, true));
AddUntilStep("layer fade is visible", () => layer.IsPresent);
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
index f9914e0193..3cefb8623f 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
@@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.HUDVisibilityMode));
- AddStep("set hud to never show", () => config.Set(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
+ AddStep("set hud to never show", () => config.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
@@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft));
AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
- AddStep("set original config value", () => config.Set(OsuSetting.HUDVisibilityMode, originalConfigValue));
+ AddStep("set original config value", () => config.SetValue(OsuSetting.HUDVisibilityMode, originalConfigValue));
}
[Test]
@@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("set keycounter visible false", () =>
{
- config.Set(OsuSetting.KeyOverlay, false);
+ config.SetValue(OsuSetting.KeyOverlay, false);
hudOverlay.KeyCounter.AlwaysVisible.Value = false;
});
@@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent);
AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent);
- AddStep("return value", () => config.Set(OsuSetting.KeyOverlay, keyCounterVisibleValue));
+ AddStep("return value", () => config.SetValue(OsuSetting.KeyOverlay, keyCounterVisibleValue));
}
private void createNew(Action action = null)
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs
index 563d6be0da..dccde366c2 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs
@@ -46,11 +46,12 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestCase(0, 0)]
[TestCase(-1000, -1000)]
[TestCase(-10000, -10000)]
- public void TestStoryboardProducesCorrectStartTime(double firstStoryboardEvent, double expectedStartTime)
+ public void TestStoryboardProducesCorrectStartTimeSimpleAlpha(double firstStoryboardEvent, double expectedStartTime)
{
var storyboard = new Storyboard();
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
+
sprite.TimelineGroup.Alpha.Add(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1);
storyboard.GetLayer("Background").Add(sprite);
@@ -64,6 +65,43 @@ namespace osu.Game.Tests.Visual.Gameplay
});
}
+ [TestCase(1000, 0, false)]
+ [TestCase(0, 0, false)]
+ [TestCase(-1000, -1000, false)]
+ [TestCase(-10000, -10000, false)]
+ [TestCase(1000, 0, true)]
+ [TestCase(0, 0, true)]
+ [TestCase(-1000, -1000, true)]
+ [TestCase(-10000, -10000, true)]
+ public void TestStoryboardProducesCorrectStartTimeFadeInAfterOtherEvents(double firstStoryboardEvent, double expectedStartTime, bool addEventToLoop)
+ {
+ var storyboard = new Storyboard();
+
+ var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
+
+ // these should be ignored as we have an alpha visibility blocker proceeding this command.
+ sprite.TimelineGroup.Scale.Add(Easing.None, -20000, -18000, 0, 1);
+ var loopGroup = sprite.AddLoop(-20000, 50);
+ loopGroup.Scale.Add(Easing.None, -20000, -18000, 0, 1);
+
+ var target = addEventToLoop ? loopGroup : sprite.TimelineGroup;
+ target.Alpha.Add(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1);
+
+ // these should be ignored due to being in the future.
+ sprite.TimelineGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1);
+ loopGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1);
+
+ storyboard.GetLayer("Background").Add(sprite);
+
+ loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo), storyboard);
+
+ AddAssert($"first frame is {expectedStartTime}", () =>
+ {
+ Debug.Assert(player.FirstFrameClockTime != null);
+ return Precision.AlmostEquals(player.FirstFrameClockTime.Value, expectedStartTime, lenience_ms);
+ });
+ }
+
private void loadPlayerWithBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
{
AddStep("create player", () =>
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs
index e43e5ba3ce..49c1163c6c 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -8,21 +9,17 @@ using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Storyboards;
+using osu.Game.Tests.Beatmaps;
+using osuTK;
namespace osu.Game.Tests.Visual.Gameplay
{
[HeadlessTest] // we alter unsafe properties on the game host to test inactive window state.
public class TestScenePauseWhenInactive : OsuPlayerTestScene
{
- protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
- {
- var beatmap = (Beatmap)base.CreateBeatmap(ruleset);
-
- beatmap.HitObjects.RemoveAll(h => h.StartTime < 30000);
-
- return beatmap;
- }
-
[Resolved]
private GameHost host { get; set; }
@@ -33,10 +30,57 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("resume player", () => Player.GameplayClockContainer.Start());
AddAssert("ensure not paused", () => !Player.GameplayClockContainer.IsPaused.Value);
+
+ AddStep("progress time to gameplay", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.GameplayStartTime));
+ AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value);
+ }
+
+ ///
+ /// Tests that if a pause from focus lose is performed while in pause cooldown,
+ /// the player will still pause after the cooldown is finished.
+ ///
+ [Test]
+ public void TestPauseWhileInCooldown()
+ {
+ AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10)));
+
+ AddStep("resume player", () => Player.GameplayClockContainer.Start());
+ AddStep("skip to gameplay", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.GameplayStartTime));
+
+ AddStep("set inactive", () => ((Bindable)host.IsActive).Value = false);
+ AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value);
+
+ AddStep("set active", () => ((Bindable)host.IsActive).Value = true);
+
+ AddStep("resume player", () => Player.Resume());
+ AddAssert("unpaused", () => !Player.GameplayClockContainer.IsPaused.Value);
+
+ bool pauseCooldownActive = false;
+
+ AddStep("set inactive again", () =>
+ {
+ pauseCooldownActive = Player.PauseCooldownActive;
+ ((Bindable)host.IsActive).Value = false;
+ });
+ AddAssert("pause cooldown active", () => pauseCooldownActive);
AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value);
- AddAssert("time of pause is after gameplay start time", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= Player.DrawableRuleset.GameplayStartTime);
}
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, true, true);
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
+ {
+ return new Beatmap
+ {
+ HitObjects = new List
+ {
+ new HitCircle { StartTime = 30000 },
+ new HitCircle { StartTime = 35000 },
+ },
+ };
+ }
+
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
+ => new TestWorkingBeatmap(beatmap, storyboard, Audio);
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
index 44142b69d7..7a6e2f54c2 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
@@ -298,7 +298,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException();
- public Sample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
+ public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
}
@@ -309,7 +309,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException();
- public Sample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
+ public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
}
@@ -321,7 +321,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException();
- public Sample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
+ public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs
index d688e9cb21..d792405eeb 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs
@@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public Drawable GetDrawableComponent(ISkinComponent component) => source?.GetDrawableComponent(component);
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => source?.GetTexture(componentName, wrapModeS, wrapModeT);
- public Sample GetSample(ISampleInfo sampleInfo) => source?.GetSample(sampleInfo);
+ public ISample GetSample(ISampleInfo sampleInfo) => source?.GetSample(sampleInfo);
public IBindable GetConfig(TLookup lookup) => source?.GetConfig(lookup);
public void TriggerSourceChanged()
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs
index 1544f8fd35..a718a98aa6 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
- config.Set(OsuSetting.ShowStoryboard, true);
+ config.SetValue(OsuSetting.ShowStoryboard, true);
storyboard = new Storyboard();
var backgroundLayer = storyboard.GetLayer("Background");
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
index 2e39471dc0..78bc51e47b 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
@@ -1,46 +1,41 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Game.Online.Multiplayer;
using osu.Game.Screens.OnlinePlay.Components;
-using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
- public class TestSceneMultiplayer : MultiplayerTestScene
+ public class TestSceneMultiplayer : ScreenTestScene
{
+ private TestMultiplayer multiplayerScreen;
+
public TestSceneMultiplayer()
{
- var multi = new TestMultiplayer();
+ AddStep("show", () =>
+ {
+ multiplayerScreen = new TestMultiplayer();
- AddStep("show", () => LoadScreen(multi));
- AddUntilStep("wait for loaded", () => multi.IsLoaded);
- }
+ // Needs to be added at a higher level since the multiplayer screen becomes non-current.
+ Child = multiplayerScreen.Client;
- [Test]
- public void TestOneUserJoinedMultipleTimes()
- {
- var user = new User { Id = 33 };
+ LoadScreen(multiplayerScreen);
+ });
- AddRepeatStep("add user multiple times", () => Client.AddUser(user), 3);
-
- AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2);
- }
-
- [Test]
- public void TestOneUserLeftMultipleTimes()
- {
- var user = new User { Id = 44 };
-
- AddStep("add user", () => Client.AddUser(user));
- AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2);
-
- AddRepeatStep("remove user multiple times", () => Client.RemoveUser(user), 3);
- AddAssert("room has 1 user", () => Client.Room?.Users.Count == 1);
+ AddUntilStep("wait for loaded", () => multiplayerScreen.IsLoaded);
}
private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer
{
+ [Cached(typeof(StatefulMultiplayerClient))]
+ public readonly TestMultiplayerClient Client;
+
+ public TestMultiplayer()
+ {
+ Client = new TestMultiplayerClient((TestMultiplayerRoomManager)RoomManager);
+ }
+
protected override RoomManager CreateRoomManager() => new TestMultiplayerRoomManager();
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
index 95c333e9f4..faa5d9e6fc 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
@@ -7,17 +7,23 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
+using osu.Framework.Bindables;
using osu.Framework.Extensions;
+using osu.Framework.Extensions.TypeExtensions;
using osu.Framework.Platform;
using osu.Framework.Screens;
+using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
+using osu.Game.Overlays.Mods;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
+using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Taiko;
using osu.Game.Rulesets.Taiko.Mods;
+using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.Select;
@@ -137,8 +143,30 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("mods not changed", () => SelectedMods.Value.Single() is TaikoModDoubleTime);
}
+ [TestCase(typeof(OsuModHidden), typeof(OsuModHidden))] // Same mod.
+ [TestCase(typeof(OsuModHidden), typeof(OsuModTraceable))] // Incompatible.
+ public void TestAllowedModDeselectedWhenRequired(Type allowedMod, Type requiredMod)
+ {
+ AddStep($"select {allowedMod.ReadableName()} as allowed", () => songSelect.FreeMods.Value = new[] { (Mod)Activator.CreateInstance(allowedMod) });
+ AddStep($"select {requiredMod.ReadableName()} as required", () => songSelect.Mods.Value = new[] { (Mod)Activator.CreateInstance(requiredMod) });
+
+ AddAssert("freemods empty", () => songSelect.FreeMods.Value.Count == 0);
+ assertHasFreeModButton(allowedMod, false);
+ assertHasFreeModButton(requiredMod, false);
+ }
+
+ private void assertHasFreeModButton(Type type, bool hasButton = true)
+ {
+ AddAssert($"{type.ReadableName()} {(hasButton ? "displayed" : "not displayed")} in freemod overlay",
+ () => songSelect.ChildrenOfType().Single().ChildrenOfType().All(b => b.Mod.GetType() != type));
+ }
+
private class TestMultiplayerMatchSongSelect : MultiplayerMatchSongSelect
{
+ public new Bindable> Mods => base.Mods;
+
+ public new Bindable> FreeMods => base.FreeMods;
+
public new BeatmapCarousel Carousel => base.Carousel;
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
index 2344ebea0e..8869718fd1 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
@@ -3,12 +3,10 @@
using System.Linq;
using NUnit.Framework;
-using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Osu;
-using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
using osu.Game.Tests.Beatmaps;
@@ -20,9 +18,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
private MultiplayerMatchSubScreen screen;
- [Cached]
- private OngoingOperationTracker ongoingOperationTracker = new OngoingOperationTracker();
-
public TestSceneMultiplayerMatchSubScreen()
: base(false)
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs
index 3b3b1bee86..b44e5b1e5b 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
- beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).Wait();
+ beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
Add(beatmapTracker = new OnlinePlayBeatmapAvailablilityTracker
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs
index 6de5704410..91c15de69f 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs
@@ -1,10 +1,13 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Online.Rooms;
+using osu.Game.Screens.OnlinePlay.Components;
+using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual.Multiplayer
{
@@ -21,15 +24,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoomManager().With(d => d.OnLoadComplete += _ =>
{
- roomManager.CreateRoom(new Room { Name = { Value = "1" } });
+ roomManager.CreateRoom(createRoom(r => r.Name.Value = "1"));
roomManager.PartRoom();
- roomManager.CreateRoom(new Room { Name = { Value = "2" } });
+ roomManager.CreateRoom(createRoom(r => r.Name.Value = "2"));
roomManager.PartRoom();
roomManager.ClearRooms();
});
});
- AddAssert("manager polled for rooms", () => roomManager.Rooms.Count == 2);
+ AddAssert("manager polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 2);
AddAssert("initial rooms received", () => roomManager.InitialRoomsReceived.Value);
}
@@ -40,16 +43,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoomManager().With(d => d.OnLoadComplete += _ =>
{
- roomManager.CreateRoom(new Room());
+ roomManager.CreateRoom(createRoom());
roomManager.PartRoom();
- roomManager.CreateRoom(new Room());
+ roomManager.CreateRoom(createRoom());
roomManager.PartRoom();
});
});
AddStep("disconnect", () => roomContainer.Client.Disconnect());
- AddAssert("rooms cleared", () => roomManager.Rooms.Count == 0);
+ AddAssert("rooms cleared", () => ((RoomManager)roomManager).Rooms.Count == 0);
AddAssert("initial rooms not received", () => !roomManager.InitialRoomsReceived.Value);
}
@@ -60,9 +63,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoomManager().With(d => d.OnLoadComplete += _ =>
{
- roomManager.CreateRoom(new Room());
+ roomManager.CreateRoom(createRoom());
roomManager.PartRoom();
- roomManager.CreateRoom(new Room());
+ roomManager.CreateRoom(createRoom());
roomManager.PartRoom();
});
});
@@ -70,7 +73,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("disconnect", () => roomContainer.Client.Disconnect());
AddStep("connect", () => roomContainer.Client.Connect());
- AddAssert("manager polled for rooms", () => roomManager.Rooms.Count == 2);
+ AddAssert("manager polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 2);
AddAssert("initial rooms received", () => roomManager.InitialRoomsReceived.Value);
}
@@ -81,12 +84,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoomManager().With(d => d.OnLoadComplete += _ =>
{
- roomManager.CreateRoom(new Room());
+ roomManager.CreateRoom(createRoom());
roomManager.ClearRooms();
});
});
- AddAssert("manager not polled for rooms", () => roomManager.Rooms.Count == 0);
+ AddAssert("manager not polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 0);
AddAssert("initial rooms not received", () => !roomManager.InitialRoomsReceived.Value);
}
@@ -97,7 +100,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoomManager().With(d => d.OnLoadComplete += _ =>
{
- roomManager.CreateRoom(new Room());
+ roomManager.CreateRoom(createRoom());
});
});
@@ -111,7 +114,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoomManager().With(d => d.OnLoadComplete += _ =>
{
- roomManager.CreateRoom(new Room());
+ roomManager.CreateRoom(createRoom());
roomManager.PartRoom();
});
});
@@ -126,7 +129,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoomManager().With(d => d.OnLoadComplete += _ =>
{
- var r = new Room();
+ var r = createRoom();
roomManager.CreateRoom(r);
roomManager.PartRoom();
roomManager.JoinRoom(r);
@@ -136,6 +139,21 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("multiplayer room joined", () => roomContainer.Client.Room != null);
}
+ private Room createRoom(Action initFunc = null)
+ {
+ var room = new Room();
+
+ room.Name.Value = "test room";
+ room.Playlist.Add(new PlaylistItem
+ {
+ Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo },
+ Ruleset = { Value = Ruleset.Value }
+ });
+
+ initFunc?.Invoke(room);
+ return room;
+ }
+
private TestMultiplayerRoomManager createRoomManager()
{
Child = roomContainer = new TestMultiplayerRoomContainer
diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs
index 96393cc4c3..bf5338d81a 100644
--- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs
+++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs
@@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
-using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Platform;
@@ -62,10 +61,6 @@ namespace osu.Game.Tests.Visual.Navigation
RecycleLocalStorage();
- // see MouseSettings
- var frameworkConfig = host.Dependencies.Get();
- frameworkConfig.GetBindable(FrameworkSetting.CursorSensitivity).Disabled = false;
-
CreateGame();
});
@@ -82,7 +77,7 @@ namespace osu.Game.Tests.Visual.Navigation
// todo: this can be removed once we can run audio tracks without a device present
// see https://github.com/ppy/osu/issues/1302
- Game.LocalConfig.Set(OsuSetting.IntroSequence, IntroSequence.Circles);
+ Game.LocalConfig.SetValue(OsuSetting.IntroSequence, IntroSequence.Circles);
Add(Game);
}
@@ -136,7 +131,7 @@ namespace osu.Game.Tests.Visual.Navigation
base.LoadComplete();
API.Login("Rhythm Champion", "osu!");
- Dependencies.Get().Set(Static.MutedAudioNotificationShownOnce, true);
+ Dependencies.Get().SetValue(Static.MutedAudioNotificationShownOnce, true);
}
}
diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs
index 21d3bdaae3..2791952b66 100644
--- a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs
@@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Navigation
public void TestPerformAtSongSelectFromPlayerLoader()
{
PushAndConfirm(() => new PlaySongSelect());
- PushAndConfirm(() => new PlayerLoader(() => new Player()));
+ PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer()));
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) }));
AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
@@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Navigation
public void TestPerformAtMenuFromPlayerLoader()
{
PushAndConfirm(() => new PlaySongSelect());
- PushAndConfirm(() => new PlayerLoader(() => new Player()));
+ PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer()));
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is MainMenu);
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
index d8380b2dd3..f2bb518b2e 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
@@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Navigation
PushAndConfirm(() => new TestSongSelect());
- AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait());
+ AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
@@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Navigation
AddStep("press enter", () => InputManager.Key(Key.Enter));
AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null);
- AddStep("seek to end", () => beatmap().Track.Seek(beatmap().Track.Length));
+ AddStep("seek to end", () => player.ChildrenOfType().First().Seek(beatmap().Track.Length));
AddUntilStep("wait for pass", () => (results = Game.ScreenStack.CurrentScreen as ResultsScreen) != null && results.IsLoaded);
AddStep("attempt to retry", () => results.ChildrenOfType().First().Action());
AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != player && Game.ScreenStack.CurrentScreen is Player);
@@ -214,6 +214,50 @@ namespace osu.Game.Tests.Visual.Navigation
AddAssert("Options overlay still visible", () => songSelect.BeatmapOptionsOverlay.State.Value == Visibility.Visible);
}
+ [Test]
+ public void TestSettingsViaHotkeyFromMainMenu()
+ {
+ AddAssert("toolbar not displayed", () => Game.Toolbar.State.Value == Visibility.Hidden);
+
+ AddStep("press settings hotkey", () =>
+ {
+ InputManager.PressKey(Key.ControlLeft);
+ InputManager.Key(Key.O);
+ InputManager.ReleaseKey(Key.ControlLeft);
+ });
+
+ AddUntilStep("settings displayed", () => Game.Settings.State.Value == Visibility.Visible);
+ }
+
+ [Test]
+ public void TestToolbarHiddenByUser()
+ {
+ AddStep("Enter menu", () => InputManager.Key(Key.Enter));
+
+ AddUntilStep("Wait for toolbar to load", () => Game.Toolbar.IsLoaded);
+
+ AddStep("Hide toolbar", () =>
+ {
+ InputManager.PressKey(Key.ControlLeft);
+ InputManager.Key(Key.T);
+ InputManager.ReleaseKey(Key.ControlLeft);
+ });
+
+ pushEscape();
+
+ AddStep("Enter menu", () => InputManager.Key(Key.Enter));
+
+ AddAssert("Toolbar is hidden", () => Game.Toolbar.State.Value == Visibility.Hidden);
+
+ AddStep("Enter song select", () =>
+ {
+ InputManager.Key(Key.Enter);
+ InputManager.Key(Key.Enter);
+ });
+
+ AddAssert("Toolbar is hidden", () => Game.Toolbar.State.Value == Visibility.Hidden);
+ }
+
private void pushEscape() =>
AddStep("Press escape", () => InputManager.Key(Key.Escape));
diff --git a/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs b/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs
index c0b77b580e..c1c968e862 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs
@@ -15,8 +15,8 @@ namespace osu.Game.Tests.Visual.Navigation
using (var config = new OsuConfigManager(LocalStorage))
{
- config.Set(OsuSetting.Version, "2020.101.0");
- config.Set(OsuSetting.DisplayStarsMaximum, 10.0);
+ config.SetValue(OsuSetting.Version, "2020.101.0");
+ config.SetValue(OsuSetting.DisplayStarsMaximum, 10.0);
}
}
@@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Navigation
{
AddAssert("config has migrated value", () => Precision.AlmostEquals(Game.LocalConfig.Get(OsuSetting.DisplayStarsMaximum), 10.1));
- AddStep("set value again", () => Game.LocalConfig.Set(OsuSetting.DisplayStarsMaximum, 10));
+ AddStep("set value again", () => Game.LocalConfig.SetValue(OsuSetting.DisplayStarsMaximum, 10.0));
AddStep("force save config", () => Game.LocalConfig.Save());
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs
index 1349264bf9..156d6b744e 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs
@@ -30,13 +30,14 @@ namespace osu.Game.Tests.Visual.Online
((DummyAPIAccess)API).HandleRequest = req =>
{
- if (req is SearchBeatmapSetsRequest searchBeatmapSetsRequest)
+ if (!(req is SearchBeatmapSetsRequest searchBeatmapSetsRequest)) return false;
+
+ searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse
{
- searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse
- {
- BeatmapSets = setsForResponse,
- });
- }
+ BeatmapSets = setsForResponse,
+ });
+
+ return true;
};
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
index cd2c4e9346..8818ac75b1 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
@@ -63,13 +63,15 @@ namespace osu.Game.Tests.Visual.Online
Builds = builds.Values.ToList()
};
changelogRequest.TriggerSuccess(changelogResponse);
- break;
+ return true;
case GetChangelogBuildRequest buildRequest:
if (requestedBuild != null)
buildRequest.TriggerSuccess(requestedBuild);
- break;
+ return true;
}
+
+ return false;
};
Child = changelog = new TestChangelogOverlay();
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
index fca642ad6c..b13dd34ebc 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
@@ -11,6 +11,8 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
using osu.Game.Online.Chat;
using osu.Game.Overlays;
using osu.Game.Overlays.Chat.Selection;
@@ -64,6 +66,24 @@ namespace osu.Game.Tests.Visual.Online
});
}
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("register request handling", () =>
+ {
+ ((DummyAPIAccess)API).HandleRequest = req =>
+ {
+ switch (req)
+ {
+ case JoinChannelRequest _:
+ return true;
+ }
+
+ return false;
+ };
+ });
+ }
+
[Test]
public void TestHideOverlay()
{
diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs
index c2a18330c9..cd22bb2513 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs
@@ -85,9 +85,10 @@ namespace osu.Game.Tests.Visual.Online
dummyAPI.HandleRequest = request =>
{
if (!(request is GetCommentsRequest getCommentsRequest))
- return;
+ return false;
getCommentsRequest.TriggerSuccess(commentBundle);
+ return true;
};
});
diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs
index 63bda08c88..0c199bfb62 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online
ensureSoleilyRemoved();
createButtonWithBeatmap(createSoleily());
AddAssert("button state not downloaded", () => downloadButton.DownloadState == DownloadState.NotDownloaded);
- AddStep("import soleily", () => beatmaps.Import(TestResources.GetTestBeatmapForImport()));
+ AddStep("import soleily", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()));
AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineBeatmapSetID == 241526));
createButtonWithBeatmap(createSoleily());
AddAssert("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable);
diff --git a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs
index 9bece39ca0..e8d9ff72af 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs
@@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Online
Username = "flyte",
Id = 3103765,
IsOnline = true,
- CurrentModeRank = 1111,
+ Statistics = new UserStatistics { GlobalRank = 1111 },
Country = new Country { FlagName = "JP" },
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c6.jpg"
},
@@ -60,7 +60,7 @@ namespace osu.Game.Tests.Visual.Online
Username = "peppy",
Id = 2,
IsOnline = false,
- CurrentModeRank = 2222,
+ Statistics = new UserStatistics { GlobalRank = 2222 },
Country = new Country { FlagName = "AU" },
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
IsSupporter = true,
diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs
index 37d51c16d2..6ebe8fcc07 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs
@@ -33,9 +33,10 @@ namespace osu.Game.Tests.Visual.Online
dummyAPI.HandleRequest = request =>
{
if (!(request is GetNewsRequest getNewsRequest))
- return;
+ return false;
getNewsRequest.TriggerSuccess(r);
+ return true;
};
});
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs
index 8dd81e02e2..255f147ec9 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Playlists
Room.RecentParticipants.Add(new User
{
Username = "peppy",
- CurrentModeRank = 1234,
+ Statistics = new UserStatistics { GlobalRank = 1234 },
Id = 2
});
}
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs
index cdcded8f61..61d49e4018 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs
@@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
-using System.Threading.Tasks;
using JetBrains.Annotations;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
@@ -76,7 +75,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("bind user score info handler", () =>
{
userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineScoreID = currentScoreId++ };
- bindHandler(3000, userScore);
+ bindHandler(true, userScore);
});
createResults(() => userScore);
@@ -89,7 +88,7 @@ namespace osu.Game.Tests.Visual.Playlists
[Test]
public void TestShowNullUserScoreWithDelay()
{
- AddStep("bind delayed handler", () => bindHandler(3000));
+ AddStep("bind delayed handler", () => bindHandler(true));
createResults();
waitForDisplay();
@@ -103,7 +102,7 @@ namespace osu.Game.Tests.Visual.Playlists
createResults();
waitForDisplay();
- AddStep("bind delayed handler", () => bindHandler(3000));
+ AddStep("bind delayed handler", () => bindHandler(true));
for (int i = 0; i < 2; i++)
{
@@ -134,7 +133,7 @@ namespace osu.Game.Tests.Visual.Playlists
createResults(() => userScore);
waitForDisplay();
- AddStep("bind delayed handler", () => bindHandler(3000));
+ AddStep("bind delayed handler", () => bindHandler(true));
for (int i = 0; i < 2; i++)
{
@@ -169,70 +168,60 @@ namespace osu.Game.Tests.Visual.Playlists
AddWaitStep("wait for display", 5);
}
- private void bindHandler(double delay = 0, ScoreInfo userScore = null, bool failRequests = false) => ((DummyAPIAccess)API).HandleRequest = request =>
+ private void bindHandler(bool delayed = false, ScoreInfo userScore = null, bool failRequests = false) => ((DummyAPIAccess)API).HandleRequest = request =>
{
- requestComplete = false;
-
- if (failRequests)
- {
- triggerFail(request, delay);
- return;
- }
-
+ // pre-check for requests we should be handling (as they are scheduled below).
switch (request)
{
- case ShowPlaylistUserScoreRequest s:
- if (userScore == null)
- triggerFail(s, delay);
- else
- triggerSuccess(s, createUserResponse(userScore), delay);
+ case ShowPlaylistUserScoreRequest _:
+ case IndexPlaylistScoresRequest _:
break;
- case IndexPlaylistScoresRequest i:
- triggerSuccess(i, createIndexResponse(i), delay);
- break;
+ default:
+ return false;
}
+
+ requestComplete = false;
+
+ double delay = delayed ? 3000 : 0;
+
+ Scheduler.AddDelayed(() =>
+ {
+ if (failRequests)
+ {
+ triggerFail(request);
+ return;
+ }
+
+ switch (request)
+ {
+ case ShowPlaylistUserScoreRequest s:
+ if (userScore == null)
+ triggerFail(s);
+ else
+ triggerSuccess(s, createUserResponse(userScore));
+ break;
+
+ case IndexPlaylistScoresRequest i:
+ triggerSuccess(i, createIndexResponse(i));
+ break;
+ }
+ }, delay);
+
+ return true;
};
- private void triggerSuccess(APIRequest req, T result, double delay)
+ private void triggerSuccess(APIRequest req, T result)
where T : class
{
- if (delay == 0)
- success();
- else
- {
- Task.Run(async () =>
- {
- await Task.Delay(TimeSpan.FromMilliseconds(delay));
- Schedule(success);
- });
- }
-
- void success()
- {
- requestComplete = true;
- req.TriggerSuccess(result);
- }
+ requestComplete = true;
+ req.TriggerSuccess(result);
}
- private void triggerFail(APIRequest req, double delay)
+ private void triggerFail(APIRequest req)
{
- if (delay == 0)
- fail();
- else
- {
- Task.Run(async () =>
- {
- await Task.Delay(TimeSpan.FromMilliseconds(delay));
- Schedule(fail);
- });
- }
-
- void fail()
- {
- requestComplete = true;
- req.TriggerFailure(new WebException("Failed."));
- }
+ requestComplete = true;
+ req.TriggerFailure(new WebException("Failed."));
}
private MultiplayerScore createUserResponse([NotNull] ScoreInfo userScore)
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs
index 7be44a62de..591095252f 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Ranking
Beatmap = createTestBeatmap(author)
}));
- AddAssert("mapper name present", () => this.ChildrenOfType().Any(spriteText => spriteText.Text == "mapper_name"));
+ AddAssert("mapper name present", () => this.ChildrenOfType().Any(spriteText => spriteText.Current.Value == "mapper_name"));
}
[Test]
@@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Ranking
}));
AddAssert("mapped by text not present", () =>
- this.ChildrenOfType().All(spriteText => !containsAny(spriteText.Text, "mapped", "by")));
+ this.ChildrenOfType().All(spriteText => !containsAny(spriteText.Text.ToString(), "mapped", "by")));
}
private void showPanel(ScoreInfo score) => Child = new ExpandedPanelMiddleContentContainer(score);
diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
index 8330b9b360..f495e0fb23 100644
--- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
+++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
@@ -78,7 +78,7 @@ namespace osu.Game.Tests.Visual.Settings
clickClearButton();
- AddAssert("first binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType().First().Text.Text));
+ AddAssert("first binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType().First().Text.Text.ToString()));
AddStep("click second binding", () =>
{
@@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual.Settings
clickClearButton();
- AddAssert("second binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType().ElementAt(1).Text.Text));
+ AddAssert("second binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType().ElementAt(1).Text.Text.ToString()));
void clickClearButton()
{
diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs
new file mode 100644
index 0000000000..8f1c17ed29
--- /dev/null
+++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs
@@ -0,0 +1,43 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Testing;
+using osu.Game.Overlays.Settings;
+
+namespace osu.Game.Tests.Visual.Settings
+{
+ [TestFixture]
+ public class TestSceneSettingsItem : OsuTestScene
+ {
+ [Test]
+ public void TestRestoreDefaultValueButtonVisibility()
+ {
+ TestSettingsTextBox textBox = null;
+
+ AddStep("create settings item", () => Child = textBox = new TestSettingsTextBox
+ {
+ Current = new Bindable
+ {
+ Default = "test",
+ Value = "test"
+ }
+ });
+ AddAssert("restore button hidden", () => textBox.RestoreDefaultValueButton.Alpha == 0);
+
+ AddStep("change value from default", () => textBox.Current.Value = "non-default");
+ AddUntilStep("restore button shown", () => textBox.RestoreDefaultValueButton.Alpha > 0);
+
+ AddStep("restore default", () => textBox.Current.SetDefault());
+ AddUntilStep("restore button hidden", () => textBox.RestoreDefaultValueButton.Alpha == 0);
+ }
+
+ private class TestSettingsTextBox : SettingsTextBox
+ {
+ public new Drawable RestoreDefaultValueButton => this.ChildrenOfType().Single();
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs
new file mode 100644
index 0000000000..a7f6c8c0d3
--- /dev/null
+++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs
@@ -0,0 +1,73 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Input.Handlers.Tablet;
+using osu.Framework.Platform;
+using osu.Framework.Utils;
+using osu.Game.Overlays;
+using osu.Game.Overlays.Settings.Sections.Input;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.Settings
+{
+ [TestFixture]
+ public class TestSceneTabletSettings : OsuTestScene
+ {
+ [BackgroundDependencyLoader]
+ private void load(GameHost host)
+ {
+ var tabletHandler = new TestTabletHandler();
+
+ AddRange(new Drawable[]
+ {
+ new TabletSettings(tabletHandler)
+ {
+ RelativeSizeAxes = Axes.None,
+ Width = SettingsPanel.WIDTH,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ }
+ });
+
+ AddStep("Test with wide tablet", () => tabletHandler.SetTabletSize(new Vector2(160, 100)));
+ AddStep("Test with square tablet", () => tabletHandler.SetTabletSize(new Vector2(300, 300)));
+ AddStep("Test with tall tablet", () => tabletHandler.SetTabletSize(new Vector2(100, 300)));
+ AddStep("Test with very tall tablet", () => tabletHandler.SetTabletSize(new Vector2(100, 700)));
+ AddStep("Test no tablet present", () => tabletHandler.SetTabletSize(Vector2.Zero));
+ }
+
+ public class TestTabletHandler : ITabletHandler
+ {
+ public Bindable AreaOffset { get; } = new Bindable();
+ public Bindable AreaSize { get; } = new Bindable();
+
+ public IBindable Tablet => tablet;
+
+ private readonly Bindable tablet = new Bindable();
+
+ public BindableBool Enabled { get; } = new BindableBool(true);
+
+ public void SetTabletSize(Vector2 size)
+ {
+ tablet.Value = size != Vector2.Zero ? new TabletInfo($"test tablet T-{RNG.Next(999):000}", size) : null;
+
+ AreaSize.Default = new Vector2(size.X, size.Y);
+
+ // if it's clear the user has not configured the area, take the full area from the tablet that was just found.
+ if (AreaSize.Value == Vector2.Zero)
+ AreaSize.SetDefault();
+
+ AreaOffset.Default = new Vector2(size.X / 2, size.Y / 2);
+
+ // likewise with the position, use the centre point if it has not been configured.
+ // it's safe to assume no user would set their centre point to 0,0 for now.
+ if (AreaOffset.Value == Vector2.Zero)
+ AreaOffset.SetDefault();
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs
index 0b2c0ce63b..7ea6373763 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs
@@ -7,6 +7,7 @@ using JetBrains.Annotations;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets;
@@ -103,27 +104,27 @@ namespace osu.Game.Tests.Visual.SongSelect
private void testBeatmapLabels(Ruleset ruleset)
{
- AddAssert("check version", () => infoWedge.Info.VersionLabel.Text == $"{ruleset.ShortName}Version");
- AddAssert("check title", () => infoWedge.Info.TitleLabel.Text == $"{ruleset.ShortName}Source — {ruleset.ShortName}Title");
- AddAssert("check artist", () => infoWedge.Info.ArtistLabel.Text == $"{ruleset.ShortName}Artist");
- AddAssert("check author", () => infoWedge.Info.MapperContainer.Children.OfType().Any(s => s.Text == $"{ruleset.ShortName}Author"));
+ 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 artist", () => infoWedge.Info.ArtistLabel.Current.Value == $"{ruleset.ShortName}Artist");
+ AddAssert("check author", () => infoWedge.Info.MapperContainer.Children.OfType().Any(s => s.Current.Value == $"{ruleset.ShortName}Author"));
}
private void testInfoLabels(int expectedCount)
{
- AddAssert("check info labels exists", () => infoWedge.Info.InfoLabelContainer.Children.Any());
- AddAssert("check info labels count", () => infoWedge.Info.InfoLabelContainer.Children.Count == expectedCount);
+ AddAssert("check info labels exists", () => infoWedge.Info.ChildrenOfType().Any());
+ AddAssert("check info labels count", () => infoWedge.Info.ChildrenOfType().Count() == expectedCount);
}
[Test]
public void TestNullBeatmap()
{
selectBeatmap(null);
- AddAssert("check empty version", () => string.IsNullOrEmpty(infoWedge.Info.VersionLabel.Text));
- AddAssert("check default title", () => infoWedge.Info.TitleLabel.Text == Beatmap.Default.BeatmapInfo.Metadata.Title);
- AddAssert("check default artist", () => infoWedge.Info.ArtistLabel.Text == Beatmap.Default.BeatmapInfo.Metadata.Artist);
+ AddAssert("check empty version", () => string.IsNullOrEmpty(infoWedge.Info.VersionLabel.Current.Value));
+ AddAssert("check default title", () => infoWedge.Info.TitleLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Title);
+ AddAssert("check default artist", () => infoWedge.Info.ArtistLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Artist);
AddAssert("check empty author", () => !infoWedge.Info.MapperContainer.Children.Any());
- AddAssert("check no info labels", () => !infoWedge.Info.InfoLabelContainer.Children.Any());
+ AddAssert("check no info labels", () => !infoWedge.Info.ChildrenOfType().Any());
}
[Test]
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs
index 53a956c77c..5e2d5eba5d 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs
@@ -32,8 +32,10 @@ namespace osu.Game.Tests.Visual.SongSelect
{
case GetUserRequest userRequest:
userRequest.TriggerSuccess(getUser(userRequest.Ruleset.ID));
- break;
+ return true;
}
+
+ return false;
};
});
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs
index 5d0fb248df..c13bdf0955 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default));
- beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait();
+ beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
base.Content.AddRange(new Drawable[]
{
diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
index 35c6d62cb7..5731b1ac2c 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
@@ -207,14 +207,14 @@ namespace osu.Game.Tests.Visual.SongSelect
addRulesetImportStep(0);
addRulesetImportStep(0);
- AddStep("change convert setting", () => config.Set(OsuSetting.ShowConvertedBeatmaps, false));
+ AddStep("change convert setting", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, false));
createSongSelect();
AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child")));
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
- AddStep("change convert setting", () => config.Set(OsuSetting.ShowConvertedBeatmaps, true));
+ AddStep("change convert setting", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true));
AddStep("return", () => songSelect.MakeCurrent());
AddUntilStep("wait for current", () => songSelect.IsCurrentScreen());
@@ -297,13 +297,13 @@ namespace osu.Game.Tests.Visual.SongSelect
AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap);
- AddStep(@"Sort by Artist", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.Artist));
- AddStep(@"Sort by Title", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.Title));
- AddStep(@"Sort by Author", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.Author));
- AddStep(@"Sort by DateAdded", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.DateAdded));
- AddStep(@"Sort by BPM", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.BPM));
- AddStep(@"Sort by Length", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.Length));
- AddStep(@"Sort by Difficulty", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.Difficulty));
+ AddStep(@"Sort by Artist", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Artist));
+ AddStep(@"Sort by Title", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Title));
+ AddStep(@"Sort by Author", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Author));
+ AddStep(@"Sort by DateAdded", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.DateAdded));
+ AddStep(@"Sort by BPM", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.BPM));
+ AddStep(@"Sort by Length", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Length));
+ AddStep(@"Sort by Difficulty", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Difficulty));
}
[Test]
@@ -470,7 +470,7 @@ namespace osu.Game.Tests.Visual.SongSelect
changeRuleset(0);
// used for filter check below
- AddStep("allow convert display", () => config.Set(OsuSetting.ShowConvertedBeatmaps, true));
+ AddStep("allow convert display", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true));
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap != null);
@@ -648,7 +648,7 @@ namespace osu.Game.Tests.Visual.SongSelect
{
int changeCount = 0;
- AddStep("change convert setting", () => config.Set(OsuSetting.ShowConvertedBeatmaps, false));
+ AddStep("change convert setting", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, false));
AddStep("bind beatmap changed", () =>
{
Beatmap.ValueChanged += onChange;
@@ -686,7 +686,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddAssert("selection changed only fired twice", () => changeCount == 2);
AddStep("unbind beatmap changed", () => Beatmap.ValueChanged -= onChange);
- AddStep("change convert setting", () => config.Set(OsuSetting.ShowConvertedBeatmaps, true));
+ AddStep("change convert setting", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true));
// ReSharper disable once AccessToModifiedClosure
void onChange(ValueChangedEvent valueChangedEvent) => changeCount++;
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs
index a9747e73f9..abd1baf0ac 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs
@@ -34,6 +34,7 @@ namespace osu.Game.Tests.Visual.UserInterface
public void SetUp() => Schedule(() =>
{
OsuSpriteText query;
+ OsuSpriteText general;
OsuSpriteText ruleset;
OsuSpriteText category;
OsuSpriteText genre;
@@ -58,6 +59,7 @@ namespace osu.Game.Tests.Visual.UserInterface
Children = new Drawable[]
{
query = new OsuSpriteText(),
+ general = new OsuSpriteText(),
ruleset = new OsuSpriteText(),
category = new OsuSpriteText(),
genre = new OsuSpriteText(),
@@ -71,6 +73,7 @@ namespace osu.Game.Tests.Visual.UserInterface
};
control.Query.BindValueChanged(q => query.Text = $"Query: {q.NewValue}", true);
+ control.General.BindCollectionChanged((u, v) => general.Text = $"General: {(control.General.Any() ? string.Join('.', control.General.Select(i => i.ToString().ToLowerInvariant())) : "")}", true);
control.Ruleset.BindValueChanged(r => ruleset.Text = $"Ruleset: {r.NewValue}", true);
control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true);
control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true);
@@ -92,10 +95,10 @@ namespace osu.Game.Tests.Visual.UserInterface
[Test]
public void TestExplicitConfig()
{
- AddStep("configure explicit content to allowed", () => localConfig.Set(OsuSetting.ShowOnlineExplicitContent, true));
+ AddStep("configure explicit content to allowed", () => localConfig.SetValue(OsuSetting.ShowOnlineExplicitContent, true));
AddAssert("explicit control set to show", () => control.ExplicitContent.Value == SearchExplicit.Show);
- AddStep("configure explicit content to disallowed", () => localConfig.Set(OsuSetting.ShowOnlineExplicitContent, false));
+ AddStep("configure explicit content to disallowed", () => localConfig.SetValue(OsuSetting.ShowOnlineExplicitContent, false));
AddAssert("explicit control set to hide", () => control.ExplicitContent.Value == SearchExplicit.Hide);
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs
index 81862448a8..97f3b2954d 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs
@@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.UserInterface
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), dependencies.Get(), Beatmap.Default));
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory));
- beatmap = beatmapManager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).Result.Beatmaps[0];
+ beatmap = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Beatmaps[0];
for (int i = 0; i < 50; i++)
{
@@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("click delete option", () =>
{
- InputManager.MoveMouseTo(contextMenuContainer.ChildrenOfType().First(i => i.Item.Text.Value.ToLowerInvariant() == "delete"));
+ InputManager.MoveMouseTo(contextMenuContainer.ChildrenOfType().First(i => i.Item.Text.Value.ToString().ToLowerInvariant() == "delete"));
InputManager.Click(MouseButton.Left);
});
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs
index 1e3b1c2ffd..546e905ded 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs
@@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.UserInterface
var multiplier = mods.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier);
var expectedValue = multiplier.Equals(1.0) ? string.Empty : $"{multiplier:N2}x";
- return expectedValue == footerButtonMods.MultiplierText.Text;
+ return expectedValue == footerButtonMods.MultiplierText.Current.Value;
}
private class TestFooterButtonMods : FooterButtonMods
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs
index 45720548c8..493e2f54e5 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs
@@ -44,22 +44,22 @@ namespace osu.Game.Tests.Visual.UserInterface
protected override void InitialiseDefaults()
{
- Set(TestConfigSetting.ToggleSettingNoKeybind, false);
- Set(TestConfigSetting.EnumSettingNoKeybind, EnumSetting.Setting1);
- Set(TestConfigSetting.ToggleSettingWithKeybind, false);
- Set(TestConfigSetting.EnumSettingWithKeybind, EnumSetting.Setting1);
+ SetDefault(TestConfigSetting.ToggleSettingNoKeybind, false);
+ SetDefault(TestConfigSetting.EnumSettingNoKeybind, EnumSetting.Setting1);
+ SetDefault(TestConfigSetting.ToggleSettingWithKeybind, false);
+ SetDefault(TestConfigSetting.EnumSettingWithKeybind, EnumSetting.Setting1);
base.InitialiseDefaults();
}
- public void ToggleSetting(TestConfigSetting setting) => Set(setting, !Get(setting));
+ public void ToggleSetting(TestConfigSetting setting) => SetValue(setting, !Get(setting));
public void IncrementEnumSetting(TestConfigSetting setting)
{
var nextValue = Get(setting) + 1;
if (nextValue > EnumSetting.Setting4)
nextValue = EnumSetting.Setting1;
- Set(setting, nextValue);
+ SetValue(setting, nextValue);
}
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj
index 7e3868bd3b..e36b3cdc74 100644
--- a/osu.Game.Tests/osu.Game.Tests.csproj
+++ b/osu.Game.Tests/osu.Game.Tests.csproj
@@ -3,16 +3,19 @@
-
+
-
+
WinExe
net5.0
+
+ tests.ruleset
+
diff --git a/osu.Game.Tests/tests.ruleset b/osu.Game.Tests/tests.ruleset
new file mode 100644
index 0000000000..a0abb781d3
--- /dev/null
+++ b/osu.Game.Tests/tests.ruleset
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs
index 567d9f0d62..46c3b8bc3b 100644
--- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs
+++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs
@@ -50,7 +50,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
storage.DeleteDirectory(string.Empty);
using (var storageConfig = new TournamentStorageManager(storage))
- storageConfig.Set(StorageConfig.CurrentTournament, custom_tournament);
+ storageConfig.SetValue(StorageConfig.CurrentTournament, custom_tournament);
try
{
diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneDrawingsScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneDrawingsScreen.cs
new file mode 100644
index 0000000000..e2954c8f10
--- /dev/null
+++ b/osu.Game.Tournament.Tests/Screens/TestSceneDrawingsScreen.cs
@@ -0,0 +1,35 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.IO;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Platform;
+using osu.Game.Graphics.Cursor;
+using osu.Game.Tournament.Screens.Drawings;
+
+namespace osu.Game.Tournament.Tests.Screens
+{
+ public class TestSceneDrawingsScreen : TournamentTestScene
+ {
+ [BackgroundDependencyLoader]
+ private void load(Storage storage)
+ {
+ using (var stream = storage.GetStream("drawings.txt", FileAccess.Write))
+ using (var writer = new StreamWriter(stream))
+ {
+ writer.WriteLine("KR : South Korea : KOR");
+ writer.WriteLine("US : United States : USA");
+ writer.WriteLine("PH : Philippines : PHL");
+ writer.WriteLine("BR : Brazil : BRA");
+ writer.WriteLine("JP : Japan : JPN");
+ }
+
+ Add(new OsuContextMenuContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new DrawingsScreen()
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
index 77ae06d89c..b20583dd7e 100644
--- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
+++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
@@ -5,7 +5,7 @@
-
+
diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs
index d1197b1a61..e6d73c6e83 100644
--- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs
+++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs
@@ -74,9 +74,9 @@ namespace osu.Game.Tournament.Components
{
new TournamentSpriteText
{
- Text = new LocalisedString((
+ Text = new RomanisableString(
$"{Beatmap.Metadata.ArtistUnicode ?? Beatmap.Metadata.Artist} - {Beatmap.Metadata.TitleUnicode ?? Beatmap.Metadata.Title}",
- $"{Beatmap.Metadata.Artist} - {Beatmap.Metadata.Title}")),
+ $"{Beatmap.Metadata.Artist} - {Beatmap.Metadata.Title}"),
Font = OsuFont.Torus.With(weight: FontWeight.Bold),
},
new FillFlowContainer
diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs
index 2ba1b6be8f..5d9fed6288 100644
--- a/osu.Game.Tournament/IO/TournamentStorage.cs
+++ b/osu.Game.Tournament/IO/TournamentStorage.cs
@@ -70,7 +70,7 @@ namespace osu.Game.Tournament.IO
moveFileIfExists("drawings.ini", destination);
ChangeTargetStorage(newStorage);
- storageConfig.Set(StorageConfig.CurrentTournament, default_tournament);
+ storageConfig.SetValue(StorageConfig.CurrentTournament, default_tournament);
storageConfig.Save();
}
diff --git a/osu.Game.Tournament/Screens/Drawings/Components/DrawingsConfigManager.cs b/osu.Game.Tournament/Screens/Drawings/Components/DrawingsConfigManager.cs
index d197c0f5d9..1a2f5a1ff4 100644
--- a/osu.Game.Tournament/Screens/Drawings/Components/DrawingsConfigManager.cs
+++ b/osu.Game.Tournament/Screens/Drawings/Components/DrawingsConfigManager.cs
@@ -12,8 +12,8 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
protected override void InitialiseDefaults()
{
- Set(DrawingsConfig.Groups, 8, 1, 8);
- Set(DrawingsConfig.TeamsPerGroup, 8, 1, 8);
+ SetDefault(DrawingsConfig.Groups, 8, 1, 8);
+ SetDefault(DrawingsConfig.TeamsPerGroup, 8, 1, 8);
}
public DrawingsConfigManager(Storage storage)
diff --git a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs
index 3ff4718b75..c7060bd538 100644
--- a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs
+++ b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs
@@ -345,7 +345,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
Flag.Anchor = Anchor.Centre;
Flag.Origin = Anchor.Centre;
- Flag.Scale = new Vector2(0.9f);
+ Flag.Scale = new Vector2(0.7f);
InternalChildren = new Drawable[]
{
diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs
index fadb821bef..87e23e3404 100644
--- a/osu.Game.Tournament/TournamentGame.cs
+++ b/osu.Game.Tournament/TournamentGame.cs
@@ -2,18 +2,21 @@
// See the LICENCE file in the repository root for full licence text.
using System.Drawing;
-using osu.Framework.Extensions.Color4Extensions;
+using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Configuration;
+using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Colour;
-using osu.Game.Graphics.Cursor;
-using osu.Game.Tournament.Models;
+using osu.Framework.Input.Handlers.Mouse;
+using osu.Framework.Platform;
using osu.Game.Graphics;
+using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Tournament.Models;
using osuTK;
using osuTK.Graphics;
@@ -36,7 +39,7 @@ namespace osu.Game.Tournament
private LoadingSpinner loadingSpinner;
[BackgroundDependencyLoader]
- private void load(FrameworkConfigManager frameworkConfig)
+ private void load(FrameworkConfigManager frameworkConfig, GameHost host)
{
windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize);
windowMode = frameworkConfig.GetBindable(FrameworkSetting.WindowMode);
@@ -48,6 +51,13 @@ namespace osu.Game.Tournament
Margin = new MarginPadding(40),
});
+ // in order to have the OS mouse cursor visible, relative mode needs to be disabled.
+ // can potentially be removed when https://github.com/ppy/osu-framework/issues/4309 is resolved.
+ var mouseHandler = host.AvailableInputHandlers.OfType().FirstOrDefault();
+
+ if (mouseHandler != null)
+ mouseHandler.UseRelativeMode.Value = false;
+
loadingSpinner.Show();
BracketLoadTask.ContinueWith(_ => LoadComponentsAsync(new[]
diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs
index d506724017..2ee52c35aa 100644
--- a/osu.Game.Tournament/TournamentGameBase.cs
+++ b/osu.Game.Tournament/TournamentGameBase.cs
@@ -150,7 +150,9 @@ namespace osu.Game.Tournament
{
foreach (var p in t.Players)
{
- if (string.IsNullOrEmpty(p.Username) || p.Statistics?.GlobalRank == null)
+ if (string.IsNullOrEmpty(p.Username)
+ || p.Statistics?.GlobalRank == null
+ || p.Statistics?.CountryRank == null)
{
PopulateUser(p, immediate: true);
addedInfo = true;
diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs
index cb0b3a8d09..b291edd19d 100644
--- a/osu.Game/Beatmaps/BeatmapConverter.cs
+++ b/osu.Game/Beatmaps/BeatmapConverter.cs
@@ -17,12 +17,12 @@ namespace osu.Game.Beatmaps
public abstract class BeatmapConverter : IBeatmapConverter
where T : HitObject
{
- private event Action> ObjectConverted;
+ private event Action> objectConverted;
event Action> IBeatmapConverter.ObjectConverted
{
- add => ObjectConverted += value;
- remove => ObjectConverted -= value;
+ add => objectConverted += value;
+ remove => objectConverted -= value;
}
public IBeatmap Beatmap { get; }
@@ -92,10 +92,10 @@ namespace osu.Game.Beatmaps
var converted = ConvertHitObject(obj, beatmap, cancellationToken);
- if (ObjectConverted != null)
+ if (objectConverted != null)
{
converted = converted.ToList();
- ObjectConverted.Invoke(obj, converted);
+ objectConverted.Invoke(obj, converted);
}
foreach (var c in converted)
diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs
index 37d262abe5..53d82c385d 100644
--- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs
+++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs
@@ -69,8 +69,8 @@ namespace osu.Game.Beatmaps
///
/// The to get the difficulty of.
/// An optional which stops updating the star difficulty for the given .
- /// A bindable that is updated to contain the star difficulty when it becomes available.
- public IBindable GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, CancellationToken cancellationToken = default)
+ /// A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state (but not during updates to ruleset and mods if a stale value is already propagated).
+ public IBindable GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, CancellationToken cancellationToken = default)
{
var bindable = createBindable(beatmapInfo, currentRuleset.Value, currentMods.Value, cancellationToken);
@@ -90,9 +90,9 @@ namespace osu.Game.Beatmaps
/// The to get the difficulty with. If null, the 's ruleset is used.
/// The s to get the difficulty with. If null, no mods will be assumed.
/// An optional which stops updating the star difficulty for the given .
- /// A bindable that is updated to contain the star difficulty when it becomes available.
- public IBindable GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods,
- CancellationToken cancellationToken = default)
+ /// A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state.
+ public IBindable GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods,
+ CancellationToken cancellationToken = default)
=> createBindable(beatmapInfo, rulesetInfo, mods, cancellationToken);
///
@@ -313,7 +313,7 @@ namespace osu.Game.Beatmaps
}
}
- private class BindableStarDifficulty : Bindable
+ private class BindableStarDifficulty : Bindable
{
public readonly BeatmapInfo Beatmap;
public readonly CancellationToken CancellationToken;
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 3c6a6ba302..b4ea898b7d 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -20,6 +20,7 @@ using osu.Framework.IO.Stores;
using osu.Framework.Lists;
using osu.Framework.Logging;
using osu.Framework.Platform;
+using osu.Framework.Statistics;
using osu.Framework.Testing;
using osu.Game.Beatmaps.Formats;
using osu.Game.Database;
@@ -112,8 +113,6 @@ namespace osu.Game.Beatmaps
{
var metadata = new BeatmapMetadata
{
- Artist = "artist",
- Title = "title",
Author = user,
};
@@ -127,7 +126,6 @@ namespace osu.Game.Beatmaps
BaseDifficulty = new BeatmapDifficulty(),
Ruleset = ruleset,
Metadata = metadata,
- Version = "difficulty"
}
}
};
@@ -155,7 +153,7 @@ namespace osu.Game.Beatmaps
bool hadOnlineBeatmapIDs = beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0);
if (onlineLookupQueue != null)
- await onlineLookupQueue.UpdateAsync(beatmapSet, cancellationToken);
+ await onlineLookupQueue.UpdateAsync(beatmapSet, cancellationToken).ConfigureAwait(false);
// ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID.
if (hadOnlineBeatmapIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0))
@@ -311,6 +309,9 @@ namespace osu.Game.Beatmaps
workingCache.Add(working = new BeatmapManagerWorkingBeatmap(beatmapInfo, this));
+ // best effort; may be higher than expected.
+ GlobalStatistics.Get(nameof(Beatmaps), $"Cached {nameof(WorkingBeatmap)}s").Value = workingCache.Count();
+
return working;
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
index 0bc5605051..73337ab6f5 100644
--- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Beatmaps.ControlPoints
///
public readonly BindableDouble SpeedMultiplierBindable = new BindableDouble(1)
{
- Precision = 0.1,
+ Precision = 0.01,
Default = 1,
MinValue = 0.1,
MaxValue = 10
diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
index 96e18f120a..c62b803d1a 100644
--- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
+++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
@@ -151,7 +151,7 @@ namespace osu.Game.Beatmaps.Drawables
this.mods = mods;
}
- private IBindable localStarDifficulty;
+ private IBindable localStarDifficulty;
[BackgroundDependencyLoader]
private void load()
@@ -160,7 +160,11 @@ namespace osu.Game.Beatmaps.Drawables
localStarDifficulty = ruleset != null
? difficultyCache.GetBindableDifficulty(beatmap, ruleset, mods, difficultyCancellation.Token)
: difficultyCache.GetBindableDifficulty(beatmap, difficultyCancellation.Token);
- localStarDifficulty.BindValueChanged(difficulty => StarDifficulty.Value = difficulty.NewValue);
+ localStarDifficulty.BindValueChanged(d =>
+ {
+ if (d.NewValue is StarDifficulty diff)
+ StarDifficulty.Value = diff;
+ });
}
protected override void Dispose(bool isDisposing)
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index 37ab489da5..40bc75e847 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using osu.Framework.Extensions;
+using osu.Framework.Extensions.EnumExtensions;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Beatmaps.Timing;
@@ -66,16 +67,14 @@ namespace osu.Game.Beatmaps.Formats
protected override void ParseLine(Beatmap beatmap, Section section, string line)
{
- var strippedLine = StripComments(line);
-
switch (section)
{
case Section.General:
- handleGeneral(strippedLine);
+ handleGeneral(line);
return;
case Section.Editor:
- handleEditor(strippedLine);
+ handleEditor(line);
return;
case Section.Metadata:
@@ -83,19 +82,19 @@ namespace osu.Game.Beatmaps.Formats
return;
case Section.Difficulty:
- handleDifficulty(strippedLine);
+ handleDifficulty(line);
return;
case Section.Events:
- handleEvent(strippedLine);
+ handleEvent(line);
return;
case Section.TimingPoints:
- handleTimingPoint(strippedLine);
+ handleTimingPoint(line);
return;
case Section.HitObjects:
- handleHitObject(strippedLine);
+ handleHitObject(line);
return;
}
@@ -348,8 +347,8 @@ namespace osu.Game.Beatmaps.Formats
if (split.Length >= 8)
{
LegacyEffectFlags effectFlags = (LegacyEffectFlags)Parsing.ParseInt(split[7]);
- kiaiMode = effectFlags.HasFlag(LegacyEffectFlags.Kiai);
- omitFirstBarSignature = effectFlags.HasFlag(LegacyEffectFlags.OmitFirstBarLine);
+ kiaiMode = effectFlags.HasFlagFast(LegacyEffectFlags.Kiai);
+ omitFirstBarSignature = effectFlags.HasFlagFast(LegacyEffectFlags.OmitFirstBarLine);
}
string stringSampleSet = sampleSet.ToString().ToLowerInvariant();
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
index df940e8c8e..d06478b9de 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
@@ -471,9 +471,6 @@ namespace osu.Game.Beatmaps.Formats
private string toLegacyCustomSampleBank(HitSampleInfo hitSampleInfo)
{
- if (hitSampleInfo == null)
- return "0";
-
if (hitSampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy)
return legacy.CustomSampleBank.ToString(CultureInfo.InvariantCulture);
diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
index 2fb24c24e0..b39890084f 100644
--- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
@@ -36,6 +36,14 @@ namespace osu.Game.Beatmaps.Formats
if (ShouldSkipLine(line))
continue;
+ if (section != Section.Metadata)
+ {
+ // comments should not be stripped from metadata lines, as the song metadata may contain "//" as valid data.
+ line = StripComments(line);
+ }
+
+ line = line.TrimEnd();
+
if (line.StartsWith('[') && line.EndsWith(']'))
{
if (!Enum.TryParse(line[1..^1], out section))
@@ -71,8 +79,6 @@ namespace osu.Game.Beatmaps.Formats
protected virtual void ParseLine(T output, Section section, string line)
{
- line = StripComments(line);
-
switch (section)
{
case Section.Colours:
diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
index b9bf6823b5..6301c42deb 100644
--- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
@@ -45,8 +45,6 @@ namespace osu.Game.Beatmaps.Formats
protected override void ParseLine(Storyboard storyboard, Section section, string line)
{
- line = StripComments(line);
-
switch (section)
{
case Section.General:
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index aab8ff6bd6..f7f276230f 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -12,7 +12,6 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Framework.Logging;
-using osu.Framework.Statistics;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
@@ -34,8 +33,6 @@ namespace osu.Game.Beatmaps
protected AudioManager AudioManager { get; }
- private static readonly GlobalStatistic total_count = GlobalStatistics.Get(nameof(Beatmaps), $"Total {nameof(WorkingBeatmap)}s");
-
protected WorkingBeatmap(BeatmapInfo beatmapInfo, AudioManager audioManager)
{
AudioManager = audioManager;
@@ -47,8 +44,6 @@ namespace osu.Game.Beatmaps
waveform = new RecyclableLazy(GetWaveform);
storyboard = new RecyclableLazy(GetStoryboard);
skin = new RecyclableLazy(GetSkin);
-
- total_count.Value++;
}
protected virtual Track GetVirtualTrack(double emptyLength = 0)
@@ -331,11 +326,6 @@ namespace osu.Game.Beatmaps
protected virtual ISkin GetSkin() => new DefaultSkin();
private readonly RecyclableLazy skin;
- ~WorkingBeatmap()
- {
- total_count.Value--;
- }
-
public class RecyclableLazy
{
private Lazy lazy;
diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs
index bb743d4ccc..1eceb56e33 100644
--- a/osu.Game/Collections/CollectionFilterDropdown.cs
+++ b/osu.Game/Collections/CollectionFilterDropdown.cs
@@ -11,6 +11,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
+using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
@@ -121,7 +122,7 @@ namespace osu.Game.Collections
Current.TriggerChange();
}
- protected override string GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName.Value;
+ protected override LocalisableString GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName.Value;
protected sealed override DropdownHeader CreateHeader() => CreateCollectionHeader().With(d =>
{
@@ -139,7 +140,7 @@ namespace osu.Game.Collections
public readonly Bindable SelectedItem = new Bindable();
private readonly Bindable collectionName = new Bindable();
- protected override string Label
+ protected override LocalisableString Label
{
get => base.Label;
set { } // See updateText().
diff --git a/osu.Game/Collections/CollectionFilterMenuItem.cs b/osu.Game/Collections/CollectionFilterMenuItem.cs
index fe79358223..0617996872 100644
--- a/osu.Game/Collections/CollectionFilterMenuItem.cs
+++ b/osu.Game/Collections/CollectionFilterMenuItem.cs
@@ -36,7 +36,19 @@ namespace osu.Game.Collections
}
public bool Equals(CollectionFilterMenuItem other)
- => other != null && CollectionName.Value == other.CollectionName.Value;
+ {
+ if (other == null)
+ return false;
+
+ // collections may have the same name, so compare first on reference equality.
+ // this relies on the assumption that only one instance of the BeatmapCollection exists game-wide, managed by CollectionManager.
+ if (Collection != null)
+ return Collection == other.Collection;
+
+ // fallback to name-based comparison.
+ // this is required for special dropdown items which don't have a collection (all beatmaps / manage collections items below).
+ return CollectionName.Value == other.CollectionName.Value;
+ }
public override int GetHashCode() => CollectionName.Value.GetHashCode();
}
diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs
index a65d9a415d..9723409c79 100644
--- a/osu.Game/Collections/CollectionManager.cs
+++ b/osu.Game/Collections/CollectionManager.cs
@@ -124,7 +124,7 @@ namespace osu.Game.Collections
return Task.Run(async () =>
{
using (var stream = stable.GetStream(database_name))
- await Import(stream);
+ await Import(stream).ConfigureAwait(false);
});
}
@@ -138,10 +138,10 @@ namespace osu.Game.Collections
PostNotification?.Invoke(notification);
- var collection = readCollections(stream, notification);
- await importCollections(collection);
+ var collections = readCollections(stream, notification);
+ await importCollections(collections).ConfigureAwait(false);
- notification.CompletionText = $"Imported {collection.Count} collections";
+ notification.CompletionText = $"Imported {collections.Count} collections";
notification.State = ProgressNotificationState.Completed;
}
@@ -155,7 +155,7 @@ namespace osu.Game.Collections
{
foreach (var newCol in newCollections)
{
- var existing = Collections.FirstOrDefault(c => c.Name == newCol.Name);
+ var existing = Collections.FirstOrDefault(c => c.Name.Value == newCol.Name.Value);
if (existing == null)
Collections.Add(existing = new BeatmapCollection { Name = { Value = newCol.Name.Value } });
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index d0fa45bb7a..387cfbb193 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -24,126 +24,126 @@ namespace osu.Game.Configuration
protected override void InitialiseDefaults()
{
// UI/selection defaults
- Set(OsuSetting.Ruleset, 0, 0, int.MaxValue);
- Set(OsuSetting.Skin, 0, -1, int.MaxValue);
+ SetDefault(OsuSetting.Ruleset, 0, 0, int.MaxValue);
+ SetDefault(OsuSetting.Skin, 0, -1, int.MaxValue);
- Set(OsuSetting.BeatmapDetailTab, PlayBeatmapDetailArea.TabType.Details);
- Set(OsuSetting.BeatmapDetailModsFilter, false);
+ SetDefault(OsuSetting.BeatmapDetailTab, PlayBeatmapDetailArea.TabType.Details);
+ SetDefault(OsuSetting.BeatmapDetailModsFilter, false);
- Set(OsuSetting.ShowConvertedBeatmaps, true);
- Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1);
- Set(OsuSetting.DisplayStarsMaximum, 10.1, 0, 10.1, 0.1);
+ SetDefault(OsuSetting.ShowConvertedBeatmaps, true);
+ SetDefault(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1);
+ SetDefault(OsuSetting.DisplayStarsMaximum, 10.1, 0, 10.1, 0.1);
- Set(OsuSetting.SongSelectGroupingMode, GroupMode.All);
- Set(OsuSetting.SongSelectSortingMode, SortMode.Title);
+ SetDefault(OsuSetting.SongSelectGroupingMode, GroupMode.All);
+ SetDefault(OsuSetting.SongSelectSortingMode, SortMode.Title);
- Set(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation);
+ SetDefault(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation);
- Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f);
+ SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f);
// Online settings
- Set(OsuSetting.Username, string.Empty);
- Set(OsuSetting.Token, string.Empty);
+ SetDefault(OsuSetting.Username, string.Empty);
+ SetDefault(OsuSetting.Token, string.Empty);
- Set(OsuSetting.AutomaticallyDownloadWhenSpectating, false);
+ SetDefault(OsuSetting.AutomaticallyDownloadWhenSpectating, false);
- Set(OsuSetting.SavePassword, false).ValueChanged += enabled =>
+ SetDefault(OsuSetting.SavePassword, false).ValueChanged += enabled =>
{
- if (enabled.NewValue) Set(OsuSetting.SaveUsername, true);
+ if (enabled.NewValue) SetValue(OsuSetting.SaveUsername, true);
};
- Set(OsuSetting.SaveUsername, true).ValueChanged += enabled =>
+ SetDefault(OsuSetting.SaveUsername, true).ValueChanged += enabled =>
{
- if (!enabled.NewValue) Set(OsuSetting.SavePassword, false);
+ if (!enabled.NewValue) SetValue(OsuSetting.SavePassword, false);
};
- Set(OsuSetting.ExternalLinkWarning, true);
- Set(OsuSetting.PreferNoVideo, false);
+ SetDefault(OsuSetting.ExternalLinkWarning, true);
+ SetDefault(OsuSetting.PreferNoVideo, false);
- Set(OsuSetting.ShowOnlineExplicitContent, false);
+ SetDefault(OsuSetting.ShowOnlineExplicitContent, false);
// Audio
- Set(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01);
+ SetDefault(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01);
- Set(OsuSetting.MenuVoice, true);
- Set(OsuSetting.MenuMusic, true);
+ SetDefault(OsuSetting.MenuVoice, true);
+ SetDefault(OsuSetting.MenuMusic, true);
- Set(OsuSetting.AudioOffset, 0, -500.0, 500.0, 1);
+ SetDefault(OsuSetting.AudioOffset, 0, -500.0, 500.0, 1);
// Input
- Set(OsuSetting.MenuCursorSize, 1.0f, 0.5f, 2f, 0.01f);
- Set(OsuSetting.GameplayCursorSize, 1.0f, 0.1f, 2f, 0.01f);
- Set(OsuSetting.AutoCursorSize, false);
+ SetDefault(OsuSetting.MenuCursorSize, 1.0f, 0.5f, 2f, 0.01f);
+ SetDefault(OsuSetting.GameplayCursorSize, 1.0f, 0.1f, 2f, 0.01f);
+ SetDefault(OsuSetting.AutoCursorSize, false);
- Set(OsuSetting.MouseDisableButtons, false);
- Set(OsuSetting.MouseDisableWheel, false);
- Set(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.DuringGameplay);
+ SetDefault(OsuSetting.MouseDisableButtons, false);
+ SetDefault(OsuSetting.MouseDisableWheel, false);
+ SetDefault(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.DuringGameplay);
// Graphics
- Set(OsuSetting.ShowFpsDisplay, false);
+ SetDefault(OsuSetting.ShowFpsDisplay, false);
- Set(OsuSetting.ShowStoryboard, true);
- Set(OsuSetting.BeatmapSkins, true);
- Set(OsuSetting.BeatmapColours, true);
- Set(OsuSetting.BeatmapHitsounds, true);
+ SetDefault(OsuSetting.ShowStoryboard, true);
+ SetDefault(OsuSetting.BeatmapSkins, true);
+ SetDefault(OsuSetting.BeatmapColours, true);
+ SetDefault(OsuSetting.BeatmapHitsounds, true);
- Set(OsuSetting.CursorRotation, true);
+ SetDefault(OsuSetting.CursorRotation, true);
- Set(OsuSetting.MenuParallax, true);
+ SetDefault(OsuSetting.MenuParallax, true);
// Gameplay
- Set(OsuSetting.DimLevel, 0.8, 0, 1, 0.01);
- Set(OsuSetting.BlurLevel, 0, 0, 1, 0.01);
- Set(OsuSetting.LightenDuringBreaks, true);
+ SetDefault(OsuSetting.DimLevel, 0.8, 0, 1, 0.01);
+ SetDefault(OsuSetting.BlurLevel, 0, 0, 1, 0.01);
+ SetDefault(OsuSetting.LightenDuringBreaks, true);
- Set(OsuSetting.HitLighting, true);
+ SetDefault(OsuSetting.HitLighting, true);
- Set(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always);
- Set(OsuSetting.ShowProgressGraph, true);
- Set(OsuSetting.ShowHealthDisplayWhenCantFail, true);
- Set(OsuSetting.FadePlayfieldWhenHealthLow, true);
- Set(OsuSetting.KeyOverlay, false);
- Set(OsuSetting.PositionalHitSounds, true);
- Set(OsuSetting.AlwaysPlayFirstComboBreak, true);
- Set(OsuSetting.ScoreMeter, ScoreMeterType.HitErrorBoth);
+ SetDefault(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always);
+ SetDefault(OsuSetting.ShowProgressGraph, true);
+ SetDefault(OsuSetting.ShowHealthDisplayWhenCantFail, true);
+ SetDefault(OsuSetting.FadePlayfieldWhenHealthLow, true);
+ SetDefault(OsuSetting.KeyOverlay, false);
+ SetDefault(OsuSetting.PositionalHitSounds, true);
+ SetDefault(OsuSetting.AlwaysPlayFirstComboBreak, true);
+ SetDefault(OsuSetting.ScoreMeter, ScoreMeterType.HitErrorBoth);
- Set(OsuSetting.FloatingComments, false);
+ SetDefault(OsuSetting.FloatingComments, false);
- Set(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised);
+ SetDefault(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised);
- Set(OsuSetting.IncreaseFirstObjectVisibility, true);
- Set(OsuSetting.GameplayDisableWinKey, true);
+ SetDefault(OsuSetting.IncreaseFirstObjectVisibility, true);
+ SetDefault(OsuSetting.GameplayDisableWinKey, true);
// Update
- Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer);
+ SetDefault(OsuSetting.ReleaseStream, ReleaseStream.Lazer);
- Set(OsuSetting.Version, string.Empty);
+ SetDefault(OsuSetting.Version, string.Empty);
- Set(OsuSetting.ScreenshotFormat, ScreenshotFormat.Jpg);
- Set(OsuSetting.ScreenshotCaptureMenuCursor, false);
+ SetDefault(OsuSetting.ScreenshotFormat, ScreenshotFormat.Jpg);
+ SetDefault(OsuSetting.ScreenshotCaptureMenuCursor, false);
- Set(OsuSetting.SongSelectRightMouseScroll, false);
+ SetDefault(OsuSetting.SongSelectRightMouseScroll, false);
- Set(OsuSetting.Scaling, ScalingMode.Off);
+ SetDefault(OsuSetting.Scaling, ScalingMode.Off);
- Set(OsuSetting.ScalingSizeX, 0.8f, 0.2f, 1f);
- Set(OsuSetting.ScalingSizeY, 0.8f, 0.2f, 1f);
+ SetDefault(OsuSetting.ScalingSizeX, 0.8f, 0.2f, 1f);
+ SetDefault(OsuSetting.ScalingSizeY, 0.8f, 0.2f, 1f);
- Set(OsuSetting.ScalingPositionX, 0.5f, 0f, 1f);
- Set(OsuSetting.ScalingPositionY, 0.5f, 0f, 1f);
+ SetDefault(OsuSetting.ScalingPositionX, 0.5f, 0f, 1f);
+ SetDefault(OsuSetting.ScalingPositionY, 0.5f, 0f, 1f);
- Set(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f);
+ SetDefault(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f);
- Set(OsuSetting.UIHoldActivationDelay, 200f, 0f, 500f, 50f);
+ SetDefault(OsuSetting.UIHoldActivationDelay, 200f, 0f, 500f, 50f);
- Set(OsuSetting.IntroSequence, IntroSequence.Triangles);
+ SetDefault(OsuSetting.IntroSequence, IntroSequence.Triangles);
- Set(OsuSetting.MenuBackgroundSource, BackgroundSource.Skin);
- Set(OsuSetting.SeasonalBackgroundMode, SeasonalBackgroundMode.Sometimes);
+ SetDefault(OsuSetting.MenuBackgroundSource, BackgroundSource.Skin);
+ SetDefault(OsuSetting.SeasonalBackgroundMode, SeasonalBackgroundMode.Sometimes);
- Set(OsuSetting.DiscordRichPresence, DiscordRichPresenceMode.Full);
+ SetDefault(OsuSetting.DiscordRichPresence, DiscordRichPresenceMode.Full);
- Set(OsuSetting.EditorWaveformOpacity, 1f);
+ SetDefault(OsuSetting.EditorWaveformOpacity, 1f);
}
public OsuConfigManager(Storage storage)
diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs
index fd401119ff..36eb6964dd 100644
--- a/osu.Game/Configuration/SessionStatics.cs
+++ b/osu.Game/Configuration/SessionStatics.cs
@@ -14,10 +14,10 @@ namespace osu.Game.Configuration
{
protected override void InitialiseDefaults()
{
- Set(Static.LoginOverlayDisplayed, false);
- Set(Static.MutedAudioNotificationShownOnce, false);
- Set(Static.LastHoverSoundPlaybackTime, (double?)null);
- Set(Static.SeasonalBackgrounds, null);
+ SetDefault(Static.LoginOverlayDisplayed, false);
+ SetDefault(Static.MutedAudioNotificationShownOnce, false);
+ SetDefault(Static.LastHoverSoundPlaybackTime, (double?)null);
+ SetDefault(Static.SeasonalBackgrounds, null);
}
}
diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs
index 70d67aaaa0..cfce615130 100644
--- a/osu.Game/Configuration/SettingSourceAttribute.cs
+++ b/osu.Game/Configuration/SettingSourceAttribute.cs
@@ -8,6 +8,7 @@ using System.Reflection;
using JetBrains.Annotations;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Localisation;
using osu.Game.Overlays.Settings;
namespace osu.Game.Configuration
@@ -22,9 +23,9 @@ namespace osu.Game.Configuration
///
[MeansImplicitUse]
[AttributeUsage(AttributeTargets.Property)]
- public class SettingSourceAttribute : Attribute
+ public class SettingSourceAttribute : Attribute, IComparable
{
- public string Label { get; }
+ public LocalisableString Label { get; }
public string Description { get; }
@@ -41,6 +42,21 @@ namespace osu.Game.Configuration
{
OrderPosition = orderPosition;
}
+
+ public int CompareTo(SettingSourceAttribute other)
+ {
+ if (OrderPosition == other.OrderPosition)
+ return 0;
+
+ // unordered items come last (are greater than any ordered items).
+ if (OrderPosition == null)
+ return 1;
+ if (other.OrderPosition == null)
+ return -1;
+
+ // ordered items are sorted by the order value.
+ return OrderPosition.Value.CompareTo(other.OrderPosition);
+ }
}
public static class SettingSourceExtensions
@@ -136,14 +152,9 @@ namespace osu.Game.Configuration
}
}
- public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetOrderedSettingsSourceProperties(this object obj)
- {
- var original = obj.GetSettingsSourceProperties();
-
- var orderedRelative = original.Where(attr => attr.Item1.OrderPosition != null).OrderBy(attr => attr.Item1.OrderPosition);
- var unordered = original.Except(orderedRelative);
-
- return orderedRelative.Concat(unordered);
- }
+ public static ICollection<(SettingSourceAttribute, PropertyInfo)> GetOrderedSettingsSourceProperties(this object obj)
+ => obj.GetSettingsSourceProperties()
+ .OrderBy(attr => attr.Item1)
+ .ToArray();
}
}
diff --git a/osu.Game/Configuration/StorageConfigManager.cs b/osu.Game/Configuration/StorageConfigManager.cs
index 929f8f22ad..90ea42b638 100644
--- a/osu.Game/Configuration/StorageConfigManager.cs
+++ b/osu.Game/Configuration/StorageConfigManager.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Configuration
{
base.InitialiseDefaults();
- Set(StorageConfig.FullPath, string.Empty);
+ SetDefault(StorageConfig.FullPath, string.Empty);
}
}
diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs
index 03b8db2cb8..d809dbcb01 100644
--- a/osu.Game/Database/ArchiveModelManager.cs
+++ b/osu.Game/Database/ArchiveModelManager.cs
@@ -22,7 +22,6 @@ using osu.Game.IO.Archives;
using osu.Game.IPC;
using osu.Game.Overlays.Notifications;
using SharpCompress.Archives.Zip;
-using FileInfo = osu.Game.IO.FileInfo;
namespace osu.Game.Database
{
@@ -141,6 +140,13 @@ namespace osu.Game.Database
protected async Task> Import(ProgressNotification notification, params ImportTask[] tasks)
{
+ if (tasks.Length == 0)
+ {
+ notification.CompletionText = $"No {HumanisedModelName}s were found to import!";
+ notification.State = ProgressNotificationState.Completed;
+ return Enumerable.Empty();
+ }
+
notification.Progress = 0;
notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is initialising...";
@@ -156,7 +162,7 @@ namespace osu.Game.Database
try
{
- var model = await Import(task, isLowPriorityImport, notification.CancellationToken);
+ var model = await Import(task, isLowPriorityImport, notification.CancellationToken).ConfigureAwait(false);
lock (imported)
{
@@ -176,7 +182,7 @@ namespace osu.Game.Database
{
Logger.Error(e, $@"Could not import ({task})", LoggingTarget.Database);
}
- }));
+ })).ConfigureAwait(false);
if (imported.Count == 0)
{
@@ -219,7 +225,7 @@ namespace osu.Game.Database
TModel import;
using (ArchiveReader reader = task.GetReader())
- import = await Import(reader, lowPriority, cancellationToken);
+ import = await Import(reader, lowPriority, cancellationToken).ConfigureAwait(false);
// We may or may not want to delete the file depending on where it is stored.
// e.g. reconstructing/repairing database with items from default storage.
@@ -351,7 +357,7 @@ namespace osu.Game.Database
item.Files = archive != null ? createFileInfos(archive, Files) : new List();
item.Hash = ComputeHash(item, archive);
- await Populate(item, archive, cancellationToken);
+ await Populate(item, archive, cancellationToken).ConfigureAwait(false);
using (var write = ContextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes.
{
@@ -403,7 +409,7 @@ namespace osu.Game.Database
flushEvents(true);
return item;
- }, cancellationToken, TaskCreationOptions.HideScheduler, lowPriority ? import_scheduler_low_priority : import_scheduler).Unwrap();
+ }, cancellationToken, TaskCreationOptions.HideScheduler, lowPriority ? import_scheduler_low_priority : import_scheduler).Unwrap().ConfigureAwait(false);
///
/// Exports an item to a legacy (.zip based) package.
@@ -614,7 +620,7 @@ namespace osu.Game.Database
}
///
- /// Create all required s for the provided archive, adding them to the global file store.
+ /// Create all required s for the provided archive, adding them to the global file store.
///
private List createFileInfos(ArchiveReader reader, FileStore files)
{
@@ -692,7 +698,7 @@ namespace osu.Game.Database
return Task.CompletedTask;
}
- return Task.Run(async () => await Import(GetStableImportPaths(storage).ToArray()));
+ return Task.Run(async () => await Import(GetStableImportPaths(storage).ToArray()).ConfigureAwait(false));
}
///
diff --git a/osu.Game/Database/DatabaseWriteUsage.cs b/osu.Game/Database/DatabaseWriteUsage.cs
index ddafd77066..84c39e3532 100644
--- a/osu.Game/Database/DatabaseWriteUsage.cs
+++ b/osu.Game/Database/DatabaseWriteUsage.cs
@@ -54,10 +54,5 @@ namespace osu.Game.Database
Dispose(true);
GC.SuppressFinalize(this);
}
-
- ~DatabaseWriteUsage()
- {
- Dispose(false);
- }
}
}
diff --git a/osu.Game/Database/DownloadableArchiveModelManager.cs b/osu.Game/Database/DownloadableArchiveModelManager.cs
index 50b022f9ff..da3144e8d0 100644
--- a/osu.Game/Database/DownloadableArchiveModelManager.cs
+++ b/osu.Game/Database/DownloadableArchiveModelManager.cs
@@ -82,7 +82,7 @@ namespace osu.Game.Database
Task.Factory.StartNew(async () =>
{
// This gets scheduled back to the update thread, but we want the import to run in the background.
- var imported = await Import(notification, new ImportTask(filename));
+ var imported = await Import(notification, new ImportTask(filename)).ConfigureAwait(false);
// for now a failed import will be marked as a failed download for simplicity.
if (!imported.Any())
diff --git a/osu.Game/Database/MemoryCachingComponent.cs b/osu.Game/Database/MemoryCachingComponent.cs
index d913e66428..a1a1279d71 100644
--- a/osu.Game/Database/MemoryCachingComponent.cs
+++ b/osu.Game/Database/MemoryCachingComponent.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Database
if (CheckExists(lookup, out TValue performance))
return performance;
- var computed = await ComputeValueAsync(lookup, token);
+ var computed = await ComputeValueAsync(lookup, token).ConfigureAwait(false);
if (computed != null || CacheNullValues)
cache[lookup] = computed;
diff --git a/osu.Game/Database/UserLookupCache.cs b/osu.Game/Database/UserLookupCache.cs
index 568726199c..19cc211709 100644
--- a/osu.Game/Database/UserLookupCache.cs
+++ b/osu.Game/Database/UserLookupCache.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Database
public Task GetUserAsync(int userId, CancellationToken token = default) => GetAsync(userId, token);
protected override async Task ComputeValueAsync(int lookup, CancellationToken token = default)
- => await queryUser(lookup);
+ => await queryUser(lookup).ConfigureAwait(false);
private readonly Queue<(int id, TaskCompletionSource)> pendingUserTasks = new Queue<(int, TaskCompletionSource)>();
private Task pendingRequestTask;
diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs
index f7914cbbca..fb7fe4947b 100644
--- a/osu.Game/Graphics/ScreenshotManager.cs
+++ b/osu.Game/Graphics/ScreenshotManager.cs
@@ -103,7 +103,7 @@ namespace osu.Game.Graphics
}
}
- using (var image = await host.TakeScreenshotAsync())
+ using (var image = await host.TakeScreenshotAsync().ConfigureAwait(false))
{
if (Interlocked.Decrement(ref screenShotTasks) == 0 && cursorVisibility.Value == false)
cursorVisibility.Value = true;
@@ -116,13 +116,13 @@ namespace osu.Game.Graphics
switch (screenshotFormat.Value)
{
case ScreenshotFormat.Png:
- await image.SaveAsPngAsync(stream);
+ await image.SaveAsPngAsync(stream).ConfigureAwait(false);
break;
case ScreenshotFormat.Jpg:
const int jpeg_quality = 92;
- await image.SaveAsJpegAsync(stream, new JpegEncoder { Quality = jpeg_quality });
+ await image.SaveAsJpegAsync(stream, new JpegEncoder { Quality = jpeg_quality }).ConfigureAwait(false);
break;
default:
diff --git a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs
index 85df2d167f..fb273d7293 100644
--- a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs
+++ b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs
@@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
using osuTK;
namespace osu.Game.Graphics.Sprites
@@ -14,7 +15,7 @@ namespace osu.Game.Graphics.Sprites
{
private readonly OsuSpriteText spriteText, blurredText;
- public string Text
+ public LocalisableString Text
{
get => spriteText.Text;
set => blurredText.Text = spriteText.Text = value;
diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs
index 953f3985f9..407bf6a923 100644
--- a/osu.Game/Graphics/UserInterface/BarGraph.cs
+++ b/osu.Game/Graphics/UserInterface/BarGraph.cs
@@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Extensions.EnumExtensions;
namespace osu.Game.Graphics.UserInterface
{
@@ -24,11 +25,11 @@ namespace osu.Game.Graphics.UserInterface
set
{
direction = value;
- base.Direction = direction.HasFlag(BarDirection.Horizontal) ? FillDirection.Vertical : FillDirection.Horizontal;
+ base.Direction = direction.HasFlagFast(BarDirection.Horizontal) ? FillDirection.Vertical : FillDirection.Horizontal;
foreach (var bar in Children)
{
- bar.Size = direction.HasFlag(BarDirection.Horizontal) ? new Vector2(1, 1.0f / Children.Count) : new Vector2(1.0f / Children.Count, 1);
+ bar.Size = direction.HasFlagFast(BarDirection.Horizontal) ? new Vector2(1, 1.0f / Children.Count) : new Vector2(1.0f / Children.Count, 1);
bar.Direction = direction;
}
}
@@ -56,14 +57,14 @@ namespace osu.Game.Graphics.UserInterface
if (bar.Bar != null)
{
bar.Bar.Length = length;
- bar.Bar.Size = direction.HasFlag(BarDirection.Horizontal) ? new Vector2(1, size) : new Vector2(size, 1);
+ bar.Bar.Size = direction.HasFlagFast(BarDirection.Horizontal) ? new Vector2(1, size) : new Vector2(size, 1);
}
else
{
Add(new Bar
{
RelativeSizeAxes = Axes.Both,
- Size = direction.HasFlag(BarDirection.Horizontal) ? new Vector2(1, size) : new Vector2(size, 1),
+ Size = direction.HasFlagFast(BarDirection.Horizontal) ? new Vector2(1, size) : new Vector2(size, 1),
Length = length,
Direction = Direction,
});
diff --git a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs
index b499b26f38..8df2c1c2fd 100644
--- a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs
+++ b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs
@@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
+using osu.Framework.Localisation;
using osu.Game.Graphics.Sprites;
using osuTK.Graphics;
@@ -105,7 +106,7 @@ namespace osu.Game.Graphics.UserInterface
protected class TextContainer : Container, IHasText
{
- public string Text
+ public LocalisableString Text
{
get => NormalText.Text;
set
diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs
index 9cf8f02024..a22c837080 100644
--- a/osu.Game/Graphics/UserInterface/OsuButton.cs
+++ b/osu.Game/Graphics/UserInterface/OsuButton.cs
@@ -11,6 +11,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
+using osu.Framework.Localisation;
using osu.Game.Graphics.Sprites;
using osuTK.Graphics;
@@ -21,9 +22,9 @@ namespace osu.Game.Graphics.UserInterface
///
public class OsuButton : Button
{
- public string Text
+ public LocalisableString Text
{
- get => SpriteText?.Text;
+ get => SpriteText?.Text ?? default;
set
{
if (SpriteText != null)
diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs
index cc76c12975..15fb00ccb0 100644
--- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs
+++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Localisation;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osuTK;
@@ -168,7 +169,7 @@ namespace osu.Game.Graphics.UserInterface
protected new class Content : FillFlowContainer, IHasText
{
- public string Text
+ public LocalisableString Text
{
get => Label.Text;
set => Label.Text = value;
@@ -215,7 +216,7 @@ namespace osu.Game.Graphics.UserInterface
{
protected readonly SpriteText Text;
- protected override string Label
+ protected override LocalisableString Label
{
get => Text.Text;
set => Text.Text = value;
diff --git a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs
index bdc95ee048..b66a4a58ce 100644
--- a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs
+++ b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs
@@ -11,6 +11,7 @@ using osu.Game.Graphics.Sprites;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
+using osu.Framework.Localisation;
namespace osu.Game.Graphics.UserInterface
{
@@ -35,7 +36,7 @@ namespace osu.Game.Graphics.UserInterface
}
}
- public string Text
+ public LocalisableString Text
{
get => text.Text;
set => text.Text = value;
diff --git a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs
index 924c7913f3..615895074c 100644
--- a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs
+++ b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs
@@ -11,6 +11,7 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osuTK;
using System.Collections.Generic;
+using osu.Framework.Localisation;
namespace osu.Game.Graphics.UserInterface
{
@@ -18,7 +19,7 @@ namespace osu.Game.Graphics.UserInterface
{
private const int duration = 200;
- public string Text
+ public LocalisableString Text
{
get => text.Text;
set => text.Text = value;
diff --git a/osu.Game/Graphics/UserInterface/TriangleButton.cs b/osu.Game/Graphics/UserInterface/TriangleButton.cs
index 5baf794227..003a81f562 100644
--- a/osu.Game/Graphics/UserInterface/TriangleButton.cs
+++ b/osu.Game/Graphics/UserInterface/TriangleButton.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Graphics.UserInterface
});
}
- public virtual IEnumerable FilterTerms => new[] { Text };
+ public virtual IEnumerable FilterTerms => new[] { Text.ToString() };
public bool MatchingFilter
{
diff --git a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs
index 120149d8c1..8f03c7073c 100644
--- a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs
+++ b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs
@@ -12,6 +12,7 @@ using osu.Game.Graphics.Containers;
using osu.Game.Beatmaps.ControlPoints;
using osu.Framework.Audio.Track;
using System;
+using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
@@ -56,15 +57,15 @@ namespace osu.Game.Graphics.UserInterface
set
{
base.Origin = value;
- c1.Origin = c1.Anchor = value.HasFlag(Anchor.x2) ? Anchor.TopLeft : Anchor.TopRight;
- c2.Origin = c2.Anchor = value.HasFlag(Anchor.x2) ? Anchor.TopRight : Anchor.TopLeft;
+ c1.Origin = c1.Anchor = value.HasFlagFast(Anchor.x2) ? Anchor.TopLeft : Anchor.TopRight;
+ c2.Origin = c2.Anchor = value.HasFlagFast(Anchor.x2) ? Anchor.TopRight : Anchor.TopLeft;
- X = value.HasFlag(Anchor.x2) ? SIZE_RETRACTED.X * shear.X * 0.5f : 0;
+ X = value.HasFlagFast(Anchor.x2) ? SIZE_RETRACTED.X * shear.X * 0.5f : 0;
Remove(c1);
Remove(c2);
- c1.Depth = value.HasFlag(Anchor.x2) ? 0 : 1;
- c2.Depth = value.HasFlag(Anchor.x2) ? 1 : 0;
+ c1.Depth = value.HasFlagFast(Anchor.x2) ? 0 : 1;
+ c2.Depth = value.HasFlagFast(Anchor.x2) ? 1 : 0;
Add(c1);
Add(c2);
}
diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs
index 4aeda74be8..266eb11319 100644
--- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs
+++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs
@@ -5,6 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Graphics.UserInterfaceV2
@@ -53,6 +54,14 @@ namespace osu.Game.Graphics.UserInterfaceV2
CornerRadius = CORNER_RADIUS,
};
+ public override bool AcceptsFocus => true;
+
+ protected override void OnFocus(FocusEvent e)
+ {
+ base.OnFocus(e);
+ GetContainingInputManager().ChangeFocus(Component);
+ }
+
protected override OsuTextBox CreateComponent() => CreateTextBox().With(t =>
{
t.OnCommit += (sender, newText) => OnCommit?.Invoke(sender, newText);
diff --git a/osu.Game/IO/Archives/ArchiveReader.cs b/osu.Game/IO/Archives/ArchiveReader.cs
index f74574e60c..679ab40402 100644
--- a/osu.Game/IO/Archives/ArchiveReader.cs
+++ b/osu.Game/IO/Archives/ArchiveReader.cs
@@ -41,7 +41,7 @@ namespace osu.Game.IO.Archives
return null;
byte[] buffer = new byte[input.Length];
- await input.ReadAsync(buffer);
+ await input.ReadAsync(buffer).ConfigureAwait(false);
return buffer;
}
}
diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs
index 8097f61ea4..7df5d820ee 100644
--- a/osu.Game/IO/OsuStorage.cs
+++ b/osu.Game/IO/OsuStorage.cs
@@ -58,7 +58,7 @@ namespace osu.Game.IO
///
public void ResetCustomStoragePath()
{
- storageConfig.Set(StorageConfig.FullPath, string.Empty);
+ storageConfig.SetValue(StorageConfig.FullPath, string.Empty);
storageConfig.Save();
ChangeTargetStorage(defaultStorage);
@@ -103,7 +103,7 @@ namespace osu.Game.IO
public override void Migrate(Storage newStorage)
{
base.Migrate(newStorage);
- storageConfig.Set(StorageConfig.FullPath, newStorage.GetFullPath("."));
+ storageConfig.SetValue(StorageConfig.FullPath, newStorage.GetFullPath("."));
storageConfig.Save();
}
}
diff --git a/osu.Game/IO/Serialization/Converters/Vector2Converter.cs b/osu.Game/IO/Serialization/Converters/Vector2Converter.cs
deleted file mode 100644
index 46447b607b..0000000000
--- a/osu.Game/IO/Serialization/Converters/Vector2Converter.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using osuTK;
-
-namespace osu.Game.IO.Serialization.Converters
-{
- ///
- /// A type of that serializes only the X and Y coordinates of a .
- ///
- public class Vector2Converter : JsonConverter
- {
- public override Vector2 ReadJson(JsonReader reader, Type objectType, Vector2 existingValue, bool hasExistingValue, JsonSerializer serializer)
- {
- var obj = JObject.Load(reader);
- return new Vector2((float)obj["x"], (float)obj["y"]);
- }
-
- public override void WriteJson(JsonWriter writer, Vector2 value, JsonSerializer serializer)
- {
- writer.WriteStartObject();
-
- writer.WritePropertyName("x");
- writer.WriteValue(value.X);
- writer.WritePropertyName("y");
- writer.WriteValue(value.Y);
-
- writer.WriteEndObject();
- }
- }
-}
diff --git a/osu.Game/IO/Serialization/IJsonSerializable.cs b/osu.Game/IO/Serialization/IJsonSerializable.cs
index ac95d47c4b..ba188963ea 100644
--- a/osu.Game/IO/Serialization/IJsonSerializable.cs
+++ b/osu.Game/IO/Serialization/IJsonSerializable.cs
@@ -1,8 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using Newtonsoft.Json;
-using osu.Game.IO.Serialization.Converters;
+using osu.Framework.IO.Serialization;
namespace osu.Game.IO.Serialization
{
@@ -28,7 +29,7 @@ namespace osu.Game.IO.Serialization
Formatting = Formatting.Indented,
ObjectCreationHandling = ObjectCreationHandling.Replace,
DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate,
- Converters = new JsonConverter[] { new Vector2Converter() },
+ Converters = new List { new Vector2Converter() },
ContractResolver = new KeyContractResolver()
};
}
diff --git a/osu.Game/IPC/ArchiveImportIPCChannel.cs b/osu.Game/IPC/ArchiveImportIPCChannel.cs
index 029908ec9d..d9d0e4c0ea 100644
--- a/osu.Game/IPC/ArchiveImportIPCChannel.cs
+++ b/osu.Game/IPC/ArchiveImportIPCChannel.cs
@@ -33,12 +33,12 @@ namespace osu.Game.IPC
if (importer == null)
{
// we want to contact a remote osu! to handle the import.
- await SendMessageAsync(new ArchiveImportMessage { Path = path });
+ await SendMessageAsync(new ArchiveImportMessage { Path = path }).ConfigureAwait(false);
return;
}
if (importer.HandledExtensions.Contains(Path.GetExtension(path)?.ToLowerInvariant()))
- await importer.Import(path);
+ await importer.Import(path).ConfigureAwait(false);
}
}
diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs
index 8ffa0221c8..944525c119 100644
--- a/osu.Game/Online/API/APIAccess.cs
+++ b/osu.Game/Online/API/APIAccess.cs
@@ -89,7 +89,7 @@ namespace osu.Game.Online.API
thread.Start();
}
- private void onTokenChanged(ValueChangedEvent e) => config.Set(OsuSetting.Token, config.Get(OsuSetting.SavePassword) ? authentication.TokenString : string.Empty);
+ private void onTokenChanged(ValueChangedEvent e) => config.SetValue(OsuSetting.Token, config.Get(OsuSetting.SavePassword) ? authentication.TokenString : string.Empty);
internal new void Schedule(Action action) => base.Schedule(action);
@@ -134,7 +134,7 @@ namespace osu.Game.Online.API
state.Value = APIState.Connecting;
// save the username at this point, if the user requested for it to be.
- config.Set(OsuSetting.Username, config.Get(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty);
+ config.SetValue(OsuSetting.Username, config.Get(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty);
if (!authentication.HasValidAccessToken && !authentication.AuthenticateWithLogin(ProvidedUsername, password))
{
@@ -246,7 +246,8 @@ namespace osu.Game.Online.API
this.password = password;
}
- public IHubClientConnector GetHubConnector(string clientName, string endpoint) => new HubClientConnector(clientName, endpoint, this, versionHash);
+ public IHubClientConnector GetHubConnector(string clientName, string endpoint) =>
+ new HubClientConnector(clientName, endpoint, this, versionHash);
public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password)
{
@@ -373,7 +374,13 @@ namespace osu.Game.Online.API
public void Queue(APIRequest request)
{
- lock (queue) queue.Enqueue(request);
+ lock (queue)
+ {
+ if (state.Value == APIState.Offline)
+ return;
+
+ queue.Enqueue(request);
+ }
}
private void flushQueue(bool failOldRequests = true)
@@ -394,8 +401,6 @@ namespace osu.Game.Online.API
public void Logout()
{
- flushQueue();
-
password = null;
authentication.Clear();
@@ -407,6 +412,7 @@ namespace osu.Game.Online.API
});
state.Value = APIState.Offline;
+ flushQueue();
}
private static User createGuestUser() => new GuestUser();
diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs
index a7174324d8..1a6868cfa4 100644
--- a/osu.Game/Online/API/APIRequest.cs
+++ b/osu.Game/Online/API/APIRequest.cs
@@ -131,8 +131,11 @@ namespace osu.Game.Online.API
{
}
+ private bool succeeded;
+
internal virtual void TriggerSuccess()
{
+ succeeded = true;
Success?.Invoke();
}
@@ -145,10 +148,7 @@ namespace osu.Game.Online.API
public void Fail(Exception e)
{
- if (WebRequest?.Completed == true)
- return;
-
- if (cancelled)
+ if (succeeded || cancelled)
return;
cancelled = true;
@@ -181,9 +181,13 @@ namespace osu.Game.Online.API
/// Whether we are in a failed or cancelled state.
private bool checkAndScheduleFailure()
{
- if (API == null || pendingFailure == null) return cancelled;
+ if (pendingFailure == null) return cancelled;
+
+ if (API == null)
+ pendingFailure();
+ else
+ API.Schedule(pendingFailure);
- API.Schedule(pendingFailure);
pendingFailure = null;
return true;
}
diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs
index 943b52db88..52f2365165 100644
--- a/osu.Game/Online/API/DummyAPIAccess.cs
+++ b/osu.Game/Online/API/DummyAPIAccess.cs
@@ -34,8 +34,9 @@ namespace osu.Game.Online.API
///
/// Provide handling logic for an arbitrary API request.
+ /// Should return true is a request was handled. If null or false return, the request will be failed with a .
///
- public Action HandleRequest;
+ public Func HandleRequest;
private readonly Bindable state = new Bindable(APIState.Online);
@@ -55,7 +56,12 @@ namespace osu.Game.Online.API
public virtual void Queue(APIRequest request)
{
- HandleRequest?.Invoke(request);
+ if (HandleRequest?.Invoke(request) != true)
+ {
+ // this will fail due to not receiving an APIAccess, and trigger a failure on the request.
+ // this is intended - any request in testing that needs non-failures should use HandleRequest.
+ request.Perform(this);
+ }
}
public void Perform(APIRequest request) => HandleRequest?.Invoke(request);
diff --git a/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs b/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs
index 707c59436d..e8871bef05 100644
--- a/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs
+++ b/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.IO.Network;
using osu.Game.Beatmaps;
namespace osu.Game.Online.API.Requests
@@ -15,6 +16,13 @@ namespace osu.Game.Online.API.Requests
this.noVideo = noVideo;
}
+ protected override WebRequest CreateWebRequest()
+ {
+ var req = base.CreateWebRequest();
+ req.Timeout = 60000;
+ return req;
+ }
+
protected override string Target => $@"beatmapsets/{Model.OnlineBeatmapSetID}/download{(noVideo ? "?noVideo=1" : "")}";
}
}
diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs
index 5360d36f3d..f1cb02fb10 100644
--- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs
+++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs
@@ -15,6 +15,9 @@ namespace osu.Game.Online.API.Requests
{
public class SearchBeatmapSetsRequest : APIRequest
{
+ [CanBeNull]
+ public IReadOnlyCollection General { get; }
+
public SearchCategory SearchCategory { get; }
public SortCriteria SortCriteria { get; }
@@ -45,6 +48,7 @@ namespace osu.Game.Online.API.Requests
string query,
RulesetInfo ruleset,
Cursor cursor = null,
+ IReadOnlyCollection general = null,
SearchCategory searchCategory = SearchCategory.Any,
SortCriteria sortCriteria = SortCriteria.Ranked,
SortDirection sortDirection = SortDirection.Descending,
@@ -59,6 +63,7 @@ namespace osu.Game.Online.API.Requests
this.ruleset = ruleset;
this.cursor = cursor;
+ General = general;
SearchCategory = searchCategory;
SortCriteria = sortCriteria;
SortDirection = sortDirection;
@@ -75,6 +80,9 @@ namespace osu.Game.Online.API.Requests
var req = base.CreateWebRequest();
req.AddParameter("q", query);
+ if (General != null && General.Any())
+ req.AddParameter("c", string.Join('.', General.Select(e => e.ToString().ToLowerInvariant())));
+
if (ruleset.ID.HasValue)
req.AddParameter("m", ruleset.ID.Value.ToString());
diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs
index 036ec4d0f3..a980f4c54b 100644
--- a/osu.Game/Online/Chat/ChannelManager.cs
+++ b/osu.Game/Online/Chat/ChannelManager.cs
@@ -152,7 +152,7 @@ namespace osu.Game.Online.Chat
createNewPrivateMessageRequest.Failure += exception =>
{
- Logger.Error(exception, "Posting message failed.");
+ handlePostException(exception);
target.ReplaceMessage(message, null);
dequeueAndRun();
};
@@ -171,7 +171,7 @@ namespace osu.Game.Online.Chat
req.Failure += exception =>
{
- Logger.Error(exception, "Posting message failed.");
+ handlePostException(exception);
target.ReplaceMessage(message, null);
dequeueAndRun();
};
@@ -184,6 +184,14 @@ namespace osu.Game.Online.Chat
dequeueAndRun();
}
+ private static void handlePostException(Exception exception)
+ {
+ if (exception is APIException apiException)
+ Logger.Log(apiException.Message, level: LogLevel.Important);
+ else
+ Logger.Error(exception, "Posting message failed.");
+ }
+
///
/// Posts a command locally. Commands like /help will result in a help message written in the current channel.
///
diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs
index fdb21c5000..3839762e46 100644
--- a/osu.Game/Online/HubClientConnector.cs
+++ b/osu.Game/Online/HubClientConnector.cs
@@ -79,7 +79,7 @@ namespace osu.Game.Online
{
cancelExistingConnect();
- if (!await connectionLock.WaitAsync(10000))
+ if (!await connectionLock.WaitAsync(10000).ConfigureAwait(false))
throw new TimeoutException("Could not obtain a lock to connect. A previous attempt is likely stuck.");
try
@@ -88,7 +88,7 @@ namespace osu.Game.Online
{
// ensure any previous connection was disposed.
// this will also create a new cancellation token source.
- await disconnect(false);
+ await disconnect(false).ConfigureAwait(false);
// this token will be valid for the scope of this connection.
// if cancelled, we can be sure that a disconnect or reconnect is handled elsewhere.
@@ -103,7 +103,7 @@ namespace osu.Game.Online
// importantly, rebuild the connection each attempt to get an updated access token.
CurrentConnection = buildConnection(cancellationToken);
- await CurrentConnection.StartAsync(cancellationToken);
+ await CurrentConnection.StartAsync(cancellationToken).ConfigureAwait(false);
Logger.Log($"{clientName} connected!", LoggingTarget.Network);
isConnected.Value = true;
@@ -119,7 +119,7 @@ namespace osu.Game.Online
Logger.Log($"{clientName} connection error: {e}", LoggingTarget.Network);
// retry on any failure.
- await Task.Delay(5000, cancellationToken);
+ await Task.Delay(5000, cancellationToken).ConfigureAwait(false);
}
}
}
@@ -174,14 +174,14 @@ namespace osu.Game.Online
if (takeLock)
{
- if (!await connectionLock.WaitAsync(10000))
+ if (!await connectionLock.WaitAsync(10000).ConfigureAwait(false))
throw new TimeoutException("Could not obtain a lock to disconnect. A previous attempt is likely stuck.");
}
try
{
if (CurrentConnection != null)
- await CurrentConnection.DisposeAsync();
+ await CurrentConnection.DisposeAsync().ConfigureAwait(false);
}
finally
{
diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
index 95d76f384f..4529dfd0a7 100644
--- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
@@ -9,7 +9,9 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR.Client;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Game.Beatmaps;
using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
using osu.Game.Online.Rooms;
namespace osu.Game.Online.Multiplayer
@@ -121,6 +123,29 @@ namespace osu.Game.Online.Multiplayer
return connection.InvokeAsync(nameof(IMultiplayerServer.StartMatch));
}
+ protected override Task GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default)
+ {
+ var tcs = new TaskCompletionSource();
+ var req = new GetBeatmapSetRequest(beatmapId, BeatmapSetLookupType.BeatmapId);
+
+ req.Success += res =>
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ tcs.SetCanceled();
+ return;
+ }
+
+ tcs.SetResult(res.ToBeatmapSet(Rulesets));
+ };
+
+ req.Failure += e => tcs.SetException(e);
+
+ API.Queue(req);
+
+ return tcs.Task;
+ }
+
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs
index bfd505fb19..0f7050596f 100644
--- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs
@@ -17,8 +17,6 @@ using osu.Framework.Logging;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Online.API;
-using osu.Game.Online.API.Requests;
-using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
using osu.Game.Online.Rooms.RoomStatuses;
using osu.Game.Rulesets;
@@ -71,7 +69,7 @@ namespace osu.Game.Online.Multiplayer
///
/// The corresponding to the local player, if available.
///
- public MultiplayerRoomUser? LocalUser => Room?.Users.SingleOrDefault(u => u.User?.Id == api.LocalUser.Value.Id);
+ public MultiplayerRoomUser? LocalUser => Room?.Users.SingleOrDefault(u => u.User?.Id == API.LocalUser.Value.Id);
///
/// Whether the is the host in .
@@ -85,15 +83,15 @@ namespace osu.Game.Online.Multiplayer
}
}
+ [Resolved]
+ protected IAPIProvider API { get; private set; } = null!;
+
+ [Resolved]
+ protected RulesetStore Rulesets { get; private set; } = null!;
+
[Resolved]
private UserLookupCache userLookupCache { get; set; } = null!;
- [Resolved]
- private IAPIProvider api { get; set; } = null!;
-
- [Resolved]
- private RulesetStore rulesets { get; set; } = null!;
-
// Only exists for compatibility with old osu-server-spectator build.
// Todo: Can be removed on 2021/02/26.
private long defaultPlaylistItemId;
@@ -133,12 +131,12 @@ namespace osu.Game.Online.Multiplayer
Debug.Assert(room.RoomID.Value != null);
// Join the server-side room.
- var joinedRoom = await JoinRoom(room.RoomID.Value.Value);
+ var joinedRoom = await JoinRoom(room.RoomID.Value.Value).ConfigureAwait(false);
Debug.Assert(joinedRoom != null);
// Populate users.
Debug.Assert(joinedRoom.Users != null);
- await Task.WhenAll(joinedRoom.Users.Select(PopulateUser));
+ await Task.WhenAll(joinedRoom.Users.Select(PopulateUser)).ConfigureAwait(false);
// Update the stored room (must be done on update thread for thread-safety).
await scheduleAsync(() =>
@@ -146,11 +144,11 @@ namespace osu.Game.Online.Multiplayer
Room = joinedRoom;
apiRoom = room;
defaultPlaylistItemId = apiRoom.Playlist.FirstOrDefault()?.ID ?? 0;
- }, cancellationSource.Token);
+ }, cancellationSource.Token).ConfigureAwait(false);
// Update room settings.
- await updateLocalRoomSettings(joinedRoom.Settings, cancellationSource.Token);
- }, cancellationSource.Token);
+ await updateLocalRoomSettings(joinedRoom.Settings, cancellationSource.Token).ConfigureAwait(false);
+ }, cancellationSource.Token).ConfigureAwait(false);
}
///
@@ -180,8 +178,8 @@ namespace osu.Game.Online.Multiplayer
return joinOrLeaveTaskChain.Add(async () =>
{
- await scheduledReset;
- await LeaveRoomInternal();
+ await scheduledReset.ConfigureAwait(false);
+ await LeaveRoomInternal().ConfigureAwait(false);
});
}
@@ -239,11 +237,11 @@ namespace osu.Game.Online.Multiplayer
switch (localUser.State)
{
case MultiplayerUserState.Idle:
- await ChangeState(MultiplayerUserState.Ready);
+ await ChangeState(MultiplayerUserState.Ready).ConfigureAwait(false);
return;
case MultiplayerUserState.Ready:
- await ChangeState(MultiplayerUserState.Idle);
+ await ChangeState(MultiplayerUserState.Idle).ConfigureAwait(false);
return;
default:
@@ -309,7 +307,7 @@ namespace osu.Game.Online.Multiplayer
if (Room == null)
return;
- await PopulateUser(user);
+ await PopulateUser(user).ConfigureAwait(false);
Scheduler.Add(() =>
{
@@ -488,7 +486,7 @@ namespace osu.Game.Online.Multiplayer
/// Populates the for a given .
///
/// The to populate.
- protected async Task PopulateUser(MultiplayerRoomUser multiplayerUser) => multiplayerUser.User ??= await userLookupCache.GetUserAsync(multiplayerUser.UserID);
+ protected async Task PopulateUser(MultiplayerRoomUser multiplayerUser) => multiplayerUser.User ??= await userLookupCache.GetUserAsync(multiplayerUser.UserID).ConfigureAwait(false);
///
/// Updates the local room settings with the given .
@@ -515,30 +513,26 @@ namespace osu.Game.Online.Multiplayer
RoomUpdated?.Invoke();
- var req = new GetBeatmapSetRequest(settings.BeatmapID, BeatmapSetLookupType.BeatmapId);
- req.Success += res =>
+ GetOnlineBeatmapSet(settings.BeatmapID, cancellationToken).ContinueWith(set => Schedule(() =>
{
if (cancellationToken.IsCancellationRequested)
return;
- updatePlaylist(settings, res);
- };
-
- api.Queue(req);
+ updatePlaylist(settings, set.Result);
+ }), TaskContinuationOptions.OnlyOnRanToCompletion);
}, cancellationToken);
- private void updatePlaylist(MultiplayerRoomSettings settings, APIBeatmapSet onlineSet)
+ private void updatePlaylist(MultiplayerRoomSettings settings, BeatmapSetInfo beatmapSet)
{
if (Room == null || !Room.Settings.Equals(settings))
return;
Debug.Assert(apiRoom != null);
- var beatmapSet = onlineSet.ToBeatmapSet(rulesets);
var beatmap = beatmapSet.Beatmaps.Single(b => b.OnlineBeatmapID == settings.BeatmapID);
beatmap.MD5Hash = settings.BeatmapChecksum;
- var ruleset = rulesets.GetRuleset(settings.RulesetID).CreateInstance();
+ var ruleset = Rulesets.GetRuleset(settings.RulesetID).CreateInstance();
var mods = settings.RequiredMods.Select(m => m.ToMod(ruleset));
var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset));
@@ -568,6 +562,14 @@ namespace osu.Game.Online.Multiplayer
}
}
+ ///
+ /// Retrieves a from an online source.
+ ///
+ /// The beatmap set ID.
+ /// A token to cancel the request.
+ /// The retrieval task.
+ protected abstract Task GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default);
+
///
/// For the provided user ID, update whether the user is included in .
///
diff --git a/osu.Game/Online/Rooms/APIScoreToken.cs b/osu.Game/Online/Rooms/APIScoreToken.cs
index f652c1720d..6b559876de 100644
--- a/osu.Game/Online/Rooms/APIScoreToken.cs
+++ b/osu.Game/Online/Rooms/APIScoreToken.cs
@@ -8,6 +8,6 @@ namespace osu.Game.Online.Rooms
public class APIScoreToken
{
[JsonProperty("id")]
- public int ID { get; set; }
+ public long ID { get; set; }
}
}
diff --git a/osu.Game/Online/Solo/CreateSoloScoreRequest.cs b/osu.Game/Online/Solo/CreateSoloScoreRequest.cs
new file mode 100644
index 0000000000..ae5ac5e26c
--- /dev/null
+++ b/osu.Game/Online/Solo/CreateSoloScoreRequest.cs
@@ -0,0 +1,32 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Net.Http;
+using osu.Framework.IO.Network;
+using osu.Game.Online.API;
+using osu.Game.Online.Rooms;
+
+namespace osu.Game.Online.Solo
+{
+ public class CreateSoloScoreRequest : APIRequest
+ {
+ private readonly int beatmapId;
+ private readonly string versionHash;
+
+ public CreateSoloScoreRequest(int beatmapId, string versionHash)
+ {
+ this.beatmapId = beatmapId;
+ this.versionHash = versionHash;
+ }
+
+ protected override WebRequest CreateWebRequest()
+ {
+ var req = base.CreateWebRequest();
+ req.Method = HttpMethod.Post;
+ req.AddParameter("version_hash", versionHash);
+ return req;
+ }
+
+ protected override string Target => $@"solo/{beatmapId}/scores";
+ }
+}
diff --git a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs
new file mode 100644
index 0000000000..98ba4fa052
--- /dev/null
+++ b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs
@@ -0,0 +1,45 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Net.Http;
+using Newtonsoft.Json;
+using osu.Framework.IO.Network;
+using osu.Game.Online.API;
+using osu.Game.Online.Rooms;
+using osu.Game.Scoring;
+
+namespace osu.Game.Online.Solo
+{
+ public class SubmitSoloScoreRequest : APIRequest
+ {
+ private readonly long scoreId;
+
+ private readonly int beatmapId;
+
+ private readonly ScoreInfo scoreInfo;
+
+ public SubmitSoloScoreRequest(int beatmapId, long scoreId, ScoreInfo scoreInfo)
+ {
+ this.beatmapId = beatmapId;
+ this.scoreId = scoreId;
+ this.scoreInfo = scoreInfo;
+ }
+
+ protected override WebRequest CreateWebRequest()
+ {
+ var req = base.CreateWebRequest();
+
+ req.ContentType = "application/json";
+ req.Method = HttpMethod.Put;
+
+ req.AddRaw(JsonConvert.SerializeObject(scoreInfo, new JsonSerializerSettings
+ {
+ ReferenceLoopHandling = ReferenceLoopHandling.Ignore
+ }));
+
+ return req;
+ }
+
+ protected override string Target => $@"solo/{beatmapId}/scores/{scoreId}";
+ }
+}
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 771bcd2310..dd1fa32ad9 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -361,14 +361,6 @@ namespace osu.Game
PerformFromScreen(screen =>
{
- // we might already be at song select, so a check is required before performing the load to solo.
- if (screen is MainMenu)
- menuScreen.LoadToSolo();
-
- // we might even already be at the song
- if (Beatmap.Value.BeatmapSetInfo.Hash == databasedSet.Hash && (difficultyCriteria?.Invoke(Beatmap.Value.BeatmapInfo) ?? true))
- return;
-
// Find beatmaps that match our predicate.
var beatmaps = databasedSet.Beatmaps.Where(b => difficultyCriteria?.Invoke(b) ?? true).ToList();
@@ -381,9 +373,16 @@ namespace osu.Game
?? beatmaps.FirstOrDefault(b => b.Ruleset.Equals(Ruleset.Value))
?? beatmaps.First();
- Ruleset.Value = selection.Ruleset;
- Beatmap.Value = BeatmapManager.GetWorkingBeatmap(selection);
- }, validScreens: new[] { typeof(SongSelect) });
+ if (screen is IHandlePresentBeatmap presentableScreen)
+ {
+ presentableScreen.PresentBeatmap(BeatmapManager.GetWorkingBeatmap(selection), selection.Ruleset);
+ }
+ else
+ {
+ Ruleset.Value = selection.Ruleset;
+ Beatmap.Value = BeatmapManager.GetWorkingBeatmap(selection);
+ }
+ }, validScreens: new[] { typeof(SongSelect), typeof(IHandlePresentBeatmap) });
}
///
@@ -441,7 +440,7 @@ namespace osu.Game
public override Task Import(params ImportTask[] imports)
{
// encapsulate task as we don't want to begin the import process until in a ready state.
- var importTask = new Task(async () => await base.Import(imports));
+ var importTask = new Task(async () => await base.Import(imports).ConfigureAwait(false));
waitForReady(() => this, _ => importTask.Start());
@@ -532,6 +531,13 @@ namespace osu.Game
SentryLogger.Dispose();
}
+ protected override IDictionary GetFrameworkConfigDefaults()
+ => new Dictionary
+ {
+ // General expectation that osu! starts in fullscreen by default (also gives the most predictable performance)
+ { FrameworkSetting.WindowMode, WindowMode.Fullscreen }
+ };
+
protected override void LoadComplete()
{
base.LoadComplete();
@@ -759,9 +765,15 @@ namespace osu.Game
{
otherOverlays.Where(o => o != overlay).ForEach(o => o.Hide());
- // show above others if not visible at all, else leave at current depth.
- if (!overlay.IsPresent)
+ // Partially visible so leave it at the current depth.
+ if (overlay.IsPresent)
+ return;
+
+ // Show above all other overlays.
+ if (overlay.IsLoaded)
overlayContent.ChangeChildDepth(overlay, (float)-Clock.CurrentTime);
+ else
+ overlay.Depth = (float)-Clock.CurrentTime;
}
private void forwardLoggedErrorsToNotifications()
@@ -832,7 +844,7 @@ namespace osu.Game
asyncLoadStream = Task.Run(async () =>
{
if (previousLoadStream != null)
- await previousLoadStream;
+ await previousLoadStream.ConfigureAwait(false);
try
{
@@ -846,7 +858,7 @@ namespace osu.Game
// The delegate won't complete if OsuGame has been disposed in the meantime
while (!IsDisposed && !del.Completed)
- await Task.Delay(10);
+ await Task.Delay(10).ConfigureAwait(false);
// Either we're disposed or the load process has started successfully
if (IsDisposed)
@@ -854,7 +866,7 @@ namespace osu.Game
Debug.Assert(task != null);
- await task;
+ await task.ConfigureAwait(false);
Logger.Log($"Loaded {component}!", level: LogLevel.Debug);
}
@@ -881,22 +893,13 @@ namespace osu.Game
switch (action)
{
case GlobalAction.ResetInputSettings:
- var sensitivity = frameworkConfig.GetBindable(FrameworkSetting.CursorSensitivity);
-
- sensitivity.Disabled = false;
- sensitivity.Value = 1;
- sensitivity.Disabled = true;
-
- frameworkConfig.Set(FrameworkSetting.IgnoredInputHandlers, string.Empty);
+ Host.ResetInputHandlers();
frameworkConfig.GetBindable(FrameworkSetting.ConfineMouseMode).SetDefault();
return true;
- case GlobalAction.ToggleToolbar:
- Toolbar.ToggleVisibility();
- return true;
-
case GlobalAction.ToggleGameplayMouseButtons:
- LocalConfig.Set(OsuSetting.MouseDisableButtons, !LocalConfig.Get(OsuSetting.MouseDisableButtons));
+ var mouseDisableButtons = LocalConfig.GetBindable(OsuSetting.MouseDisableButtons);
+ mouseDisableButtons.Value = !mouseDisableButtons.Value;
return true;
case GlobalAction.RandomSkin:
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 3d24f245f9..e1c7b67a8c 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -434,7 +434,7 @@ namespace osu.Game
foreach (var importer in fileImporters)
{
if (importer.HandledExtensions.Contains(extension))
- await importer.Import(paths);
+ await importer.Import(paths).ConfigureAwait(false);
}
}
@@ -445,7 +445,7 @@ namespace osu.Game
{
var importer = fileImporters.FirstOrDefault(i => i.HandledExtensions.Contains(taskGroup.Key));
return importer?.Import(taskGroup.ToArray()) ?? Task.CompletedTask;
- }));
+ })).ConfigureAwait(false);
}
public IEnumerable HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions);
diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs
index bcc5a91677..1935a250b7 100644
--- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs
+++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs
@@ -134,6 +134,7 @@ namespace osu.Game.Overlays.BeatmapListing
queueUpdateSearch(true);
});
+ searchControl.General.CollectionChanged += (_, __) => queueUpdateSearch();
searchControl.Ruleset.BindValueChanged(_ => queueUpdateSearch());
searchControl.Category.BindValueChanged(_ => queueUpdateSearch());
searchControl.Genre.BindValueChanged(_ => queueUpdateSearch());
@@ -187,6 +188,7 @@ namespace osu.Game.Overlays.BeatmapListing
searchControl.Query.Value,
searchControl.Ruleset.Value,
lastResponse?.Cursor,
+ searchControl.General,
searchControl.Category.Value,
sortControl.Current.Value,
sortControl.SortDirection.Value,
diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs
index b138a5ac52..1576431d40 100644
--- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs
+++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs
@@ -29,6 +29,8 @@ namespace osu.Game.Overlays.BeatmapListing
public Bindable Query => textBox.Current;
+ public BindableList General => generalFilter.Current;
+
public Bindable Ruleset => modeFilter.Current;
public Bindable Category => categoryFilter.Current;
@@ -61,6 +63,7 @@ namespace osu.Game.Overlays.BeatmapListing
}
private readonly BeatmapSearchTextBox textBox;
+ private readonly BeatmapSearchMultipleSelectionFilterRow generalFilter;
private readonly BeatmapSearchRulesetFilterRow modeFilter;
private readonly BeatmapSearchFilterRow categoryFilter;
private readonly BeatmapSearchFilterRow genreFilter;
@@ -123,6 +126,7 @@ namespace osu.Game.Overlays.BeatmapListing
Padding = new MarginPadding { Horizontal = 10 },
Children = new Drawable[]
{
+ generalFilter = new BeatmapSearchMultipleSelectionFilterRow(@"General"),
modeFilter = new BeatmapSearchRulesetFilterRow(),
categoryFilter = new BeatmapSearchFilterRow(@"Categories"),
genreFilter = new BeatmapSearchFilterRow(@"Genre"),
diff --git a/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs
index c1d366bb82..4d5c387c4a 100644
--- a/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs
+++ b/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs
@@ -84,14 +84,14 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
{
new OsuSpriteText
{
- Text = new LocalisedString((SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title)),
+ Text = new RomanisableString(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title),
Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold, italics: true)
},
}
},
new OsuSpriteText
{
- Text = new LocalisedString((SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist)),
+ Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true)
},
},
diff --git a/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs
index 76a30d1c11..00ffd168c1 100644
--- a/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs
+++ b/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs
@@ -107,14 +107,14 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
{
new OsuSpriteText
{
- Text = new LocalisedString((SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title)),
+ Text = new RomanisableString(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title),
Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold, italics: true)
},
}
},
new OsuSpriteText
{
- Text = new LocalisedString((SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist)),
+ Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true)
},
}
diff --git a/osu.Game/Overlays/BeatmapListing/SearchGeneral.cs b/osu.Game/Overlays/BeatmapListing/SearchGeneral.cs
new file mode 100644
index 0000000000..175942c626
--- /dev/null
+++ b/osu.Game/Overlays/BeatmapListing/SearchGeneral.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.ComponentModel;
+
+namespace osu.Game.Overlays.BeatmapListing
+{
+ public enum SearchGeneral
+ {
+ [Description("Recommended difficulty")]
+ Recommended,
+
+ [Description("Include converted beatmaps")]
+ Converts,
+
+ [Description("Subscribed mappers")]
+ Follows
+ }
+}
diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs
index a2464bef09..cf74c0d4d3 100644
--- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs
+++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
@@ -96,7 +97,7 @@ namespace osu.Game.Overlays.BeatmapSet
public string TooltipText { get; }
- public string Value
+ public LocalisableString Value
{
get => value.Text;
set => this.value.Text = value;
diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs
index 93744dd6a3..5cb834b510 100644
--- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs
+++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs
@@ -9,7 +9,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
-using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
@@ -204,7 +203,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
this.text = text;
}
- public LocalisedString Text
+ public string Text
{
set => text.Text = value;
}
diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs
index 537dd00727..e7d68853ad 100644
--- a/osu.Game/Overlays/ChangelogOverlay.cs
+++ b/osu.Game/Overlays/ChangelogOverlay.cs
@@ -21,6 +21,8 @@ namespace osu.Game.Overlays
{
public class ChangelogOverlay : OnlineOverlay
{
+ public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
+
public readonly Bindable Current = new Bindable();
private Sample sampleBack;
@@ -126,8 +128,11 @@ namespace osu.Game.Overlays
private Task initialFetchTask;
- private void performAfterFetch(Action action) => fetchListing()?.ContinueWith(_ =>
- Schedule(action), TaskContinuationOptions.OnlyOnRanToCompletion);
+ private void performAfterFetch(Action action) => Schedule(() =>
+ {
+ fetchListing()?.ContinueWith(_ =>
+ Schedule(action), TaskContinuationOptions.OnlyOnRanToCompletion);
+ });
private Task fetchListing()
{
@@ -160,10 +165,10 @@ namespace osu.Game.Overlays
tcs.SetException(e);
};
- await API.PerformAsync(req);
+ await API.PerformAsync(req).ConfigureAwait(false);
- await tcs.Task;
- });
+ return tcs.Task;
+ }).Unwrap();
}
private CancellationTokenSource loadContentCancellation;
diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSection.cs b/osu.Game/Overlays/Chat/Selection/ChannelSection.cs
index eac48ca5cb..537ac975ac 100644
--- a/osu.Game/Overlays/Chat/Selection/ChannelSection.cs
+++ b/osu.Game/Overlays/Chat/Selection/ChannelSection.cs
@@ -4,19 +4,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using osuTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Chat;
+using osuTK;
namespace osu.Game.Overlays.Chat.Selection
{
public class ChannelSection : Container, IHasFilterableChildren
{
- private readonly OsuSpriteText header;
-
public readonly FillFlowContainer ChannelFlow;
public IEnumerable FilterableChildren => ChannelFlow.Children;
@@ -29,12 +27,6 @@ namespace osu.Game.Overlays.Chat.Selection
public bool FilteringActive { get; set; }
- public string Header
- {
- get => header.Text;
- set => header.Text = value.ToUpperInvariant();
- }
-
public IEnumerable Channels
{
set => ChannelFlow.ChildrenEnumerable = value.Select(c => new ChannelListItem(c));
@@ -47,9 +39,10 @@ namespace osu.Game.Overlays.Chat.Selection
Children = new Drawable[]
{
- header = new OsuSpriteText
+ new OsuSpriteText
{
Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold),
+ Text = "All Channels".ToUpperInvariant()
},
ChannelFlow = new FillFlowContainer
{
diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs
index be9ecc6746..231d7ca63c 100644
--- a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs
+++ b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs
@@ -131,11 +131,7 @@ namespace osu.Game.Overlays.Chat.Selection
{
sectionsFlow.ChildrenEnumerable = new[]
{
- new ChannelSection
- {
- Header = "All Channels",
- Channels = channels,
- },
+ new ChannelSection { Channels = channels, },
};
foreach (ChannelSection s in sectionsFlow.Children)
diff --git a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs
index 57bf2af4d2..2f7f16dd6f 100644
--- a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs
+++ b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs
@@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
+using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
@@ -16,7 +17,7 @@ namespace osu.Game.Overlays.Comments.Buttons
{
public abstract class CommentRepliesButton : CompositeDrawable
{
- protected string Text
+ protected LocalisableString Text
{
get => text.Text;
set => text.Text = value;
diff --git a/osu.Game/Overlays/Comments/DeletedCommentsCounter.cs b/osu.Game/Overlays/Comments/DeletedCommentsCounter.cs
index 56588ef0a8..8c40d79f7a 100644
--- a/osu.Game/Overlays/Comments/DeletedCommentsCounter.cs
+++ b/osu.Game/Overlays/Comments/DeletedCommentsCounter.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Comments
{
new SpriteIcon
{
- Icon = FontAwesome.Solid.Trash,
+ Icon = FontAwesome.Regular.TrashAlt,
Size = new Vector2(14),
},
countText = new OsuSpriteText
diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs
index e6fe6ac749..0922ce5ecc 100644
--- a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs
+++ b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs
@@ -244,7 +244,7 @@ namespace osu.Game.Overlays.Dashboard.Friends
return unsorted.OrderByDescending(u => u.LastVisit).ToList();
case UserSortCriteria.Rank:
- return unsorted.OrderByDescending(u => u.CurrentModeRank.HasValue).ThenBy(u => u.CurrentModeRank ?? 0).ToList();
+ return unsorted.OrderByDescending(u => u.Statistics.GlobalRank.HasValue).ThenBy(u => u.Statistics.GlobalRank ?? 0).ToList();
case UserSortCriteria.Username:
return unsorted.OrderBy(u => u.Username).ToList();
diff --git a/osu.Game/Overlays/Dialog/ConfirmDialog.cs b/osu.Game/Overlays/Dialog/ConfirmDialog.cs
new file mode 100644
index 0000000000..a87c06ffdf
--- /dev/null
+++ b/osu.Game/Overlays/Dialog/ConfirmDialog.cs
@@ -0,0 +1,42 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Graphics.Sprites;
+
+namespace osu.Game.Overlays.Dialog
+{
+ ///
+ /// A dialog which confirms a user action.
+ ///
+ public class ConfirmDialog : PopupDialog
+ {
+ ///
+ /// Construct a new confirmation dialog.
+ ///
+ /// The description of the action to be displayed to the user.
+ /// An action to perform on confirmation.
+ /// An optional action to perform on cancel.
+ public ConfirmDialog(string message, Action onConfirm, Action onCancel = null)
+ {
+ HeaderText = message;
+ BodyText = "Last chance to turn back";
+
+ Icon = FontAwesome.Solid.ExclamationTriangle;
+
+ Buttons = new PopupDialogButton[]
+ {
+ new PopupDialogOkButton
+ {
+ Text = @"Yes",
+ Action = onConfirm
+ },
+ new PopupDialogCancelButton
+ {
+ Text = @"Cancel",
+ Action = onCancel
+ },
+ };
+ }
+ }
+}
diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
index b808d49fa2..300fce962a 100644
--- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
+++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
@@ -51,7 +51,7 @@ namespace osu.Game.Overlays.KeyBinding
private FillFlowContainer cancelAndClearButtons;
private FillFlowContainer buttons;
- public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend((string)text.Text);
+ public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(text.Text.ToString());
public KeyBindingRow(object action, IEnumerable bindings)
{
diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs
index c3e56abd05..aa8a5efd39 100644
--- a/osu.Game/Overlays/Mods/ModSection.cs
+++ b/osu.Game/Overlays/Mods/ModSection.cs
@@ -23,13 +23,15 @@ namespace osu.Game.Overlays.Mods
public FillFlowContainer ButtonsContainer { get; }
+ protected IReadOnlyList Buttons { get; private set; } = Array.Empty();
+
public Action Action;
public Key[] ToggleKeys;
public readonly ModType ModType;
- public IEnumerable SelectedMods => buttons.Select(b => b.SelectedMod).Where(m => m != null);
+ public IEnumerable SelectedMods => Buttons.Select(b => b.SelectedMod).Where(m => m != null);
private CancellationTokenSource modsLoadCts;
@@ -77,7 +79,7 @@ namespace osu.Game.Overlays.Mods
ButtonsContainer.ChildrenEnumerable = c;
}, (modsLoadCts = new CancellationTokenSource()).Token);
- buttons = modContainers.OfType().ToArray();
+ Buttons = modContainers.OfType().ToArray();
header.FadeIn(200);
this.FadeIn(200);
@@ -88,8 +90,6 @@ namespace osu.Game.Overlays.Mods
{
}
- private ModButton[] buttons = Array.Empty();
-
protected override bool OnKeyDown(KeyDownEvent e)
{
if (e.ControlPressed) return false;
@@ -97,8 +97,8 @@ namespace osu.Game.Overlays.Mods
if (ToggleKeys != null)
{
var index = Array.IndexOf(ToggleKeys, e.Key);
- if (index > -1 && index < buttons.Length)
- buttons[index].SelectNext(e.ShiftPressed ? -1 : 1);
+ if (index > -1 && index < Buttons.Count)
+ Buttons[index].SelectNext(e.ShiftPressed ? -1 : 1);
}
return base.OnKeyDown(e);
@@ -141,7 +141,7 @@ namespace osu.Game.Overlays.Mods
{
pendingSelectionOperations.Clear();
- foreach (var button in buttons.Where(b => !b.Selected))
+ foreach (var button in Buttons.Where(b => !b.Selected))
pendingSelectionOperations.Enqueue(() => button.SelectAt(0));
}
@@ -151,7 +151,7 @@ namespace osu.Game.Overlays.Mods
public void DeselectAll()
{
pendingSelectionOperations.Clear();
- DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null));
+ DeselectTypes(Buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null));
}
///
@@ -161,7 +161,7 @@ namespace osu.Game.Overlays.Mods
/// Whether the deselection should happen immediately. Should only be used when required to ensure correct selection flow.
public void DeselectTypes(IEnumerable modTypes, bool immediate = false)
{
- foreach (var button in buttons)
+ foreach (var button in Buttons)
{
if (button.SelectedMod == null) continue;
@@ -184,7 +184,7 @@ namespace osu.Game.Overlays.Mods
/// The new list of selected mods to select.
public void UpdateSelectedButtons(IReadOnlyList newSelectedMods)
{
- foreach (var button in buttons)
+ foreach (var button in Buttons)
updateButtonSelection(button, newSelectedMods);
}
diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs
index eef91deb4c..26b8632d7f 100644
--- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs
+++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs
@@ -456,6 +456,7 @@ namespace osu.Game.Overlays.Mods
}
updateSelectedButtons();
+ OnAvailableModsChanged();
}
///
@@ -533,6 +534,13 @@ namespace osu.Game.Overlays.Mods
private void playSelectedSound() => sampleOn?.Play();
private void playDeselectedSound() => sampleOff?.Play();
+ ///
+ /// Invoked after has changed.
+ ///
+ protected virtual void OnAvailableModsChanged()
+ {
+ }
+
///
/// Invoked when a new has been selected.
///
diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs
index 96dff39fae..571b14428e 100644
--- a/osu.Game/Overlays/Music/PlaylistItem.cs
+++ b/osu.Game/Overlays/Music/PlaylistItem.cs
@@ -48,8 +48,8 @@ namespace osu.Game.Overlays.Music
artistColour = colours.Gray9;
HandleColour = colours.Gray5;
- title = localisation.GetLocalisedString(new LocalisedString((Model.Metadata.TitleUnicode, Model.Metadata.Title)));
- artist = localisation.GetLocalisedString(new LocalisedString((Model.Metadata.ArtistUnicode, Model.Metadata.Artist)));
+ title = localisation.GetLocalisedString(new RomanisableString(Model.Metadata.TitleUnicode, Model.Metadata.Title));
+ artist = localisation.GetLocalisedString(new RomanisableString(Model.Metadata.ArtistUnicode, Model.Metadata.Artist));
}
protected override void LoadComplete()
diff --git a/osu.Game/Overlays/Notifications/NotificationSection.cs b/osu.Game/Overlays/Notifications/NotificationSection.cs
index 38ba712254..2316199049 100644
--- a/osu.Game/Overlays/Notifications/NotificationSection.cs
+++ b/osu.Game/Overlays/Notifications/NotificationSection.cs
@@ -8,10 +8,11 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Localisation;
using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osuTK;
-using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.Notifications
{
@@ -37,7 +38,7 @@ namespace osu.Game.Overlays.Notifications
public NotificationSection(string title, string clearButtonText)
{
- this.clearButtonText = clearButtonText;
+ this.clearButtonText = clearButtonText.ToUpperInvariant();
titleText = title;
}
@@ -121,7 +122,20 @@ namespace osu.Game.Overlays.Notifications
{
base.Update();
- countDrawable.Text = notifications.Children.Count(c => c.Alpha > 0.99f).ToString();
+ countDrawable.Text = getVisibleCount().ToString();
+ }
+
+ private int getVisibleCount()
+ {
+ int count = 0;
+
+ foreach (var c in notifications)
+ {
+ if (c.Alpha > 0.99f)
+ count++;
+ }
+
+ return count;
}
private class ClearAllButton : OsuClickableContainer
@@ -138,10 +152,10 @@ namespace osu.Game.Overlays.Notifications
};
}
- public string Text
+ public LocalisableString Text
{
get => text.Text;
- set => text.Text = value.ToUpperInvariant();
+ set => text.Text = value;
}
}
diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs
index 2866d2ad6d..81bf71cdec 100644
--- a/osu.Game/Overlays/NowPlayingOverlay.cs
+++ b/osu.Game/Overlays/NowPlayingOverlay.cs
@@ -293,8 +293,8 @@ namespace osu.Game.Overlays
else
{
BeatmapMetadata metadata = beatmap.Metadata;
- title.Text = new LocalisedString((metadata.TitleUnicode, metadata.Title));
- artist.Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist));
+ title.Text = new RomanisableString(metadata.TitleUnicode, metadata.Title);
+ artist.Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist);
}
});
diff --git a/osu.Game/Overlays/OverlaySortTabControl.cs b/osu.Game/Overlays/OverlaySortTabControl.cs
index b2212336ef..0ebabd424f 100644
--- a/osu.Game/Overlays/OverlaySortTabControl.cs
+++ b/osu.Game/Overlays/OverlaySortTabControl.cs
@@ -17,6 +17,7 @@ using osu.Game.Overlays.Comments;
using JetBrains.Annotations;
using System;
using osu.Framework.Extensions;
+using osu.Framework.Localisation;
namespace osu.Game.Overlays
{
@@ -30,7 +31,7 @@ namespace osu.Game.Overlays
set => current.Current = value;
}
- public string Title
+ public LocalisableString Title
{
get => text.Text;
set => text.Text = value;
diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs
index 2925107766..fe61e532e1 100644
--- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs
+++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs
@@ -3,6 +3,7 @@
using System;
using System.Linq;
+using Humanizer;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
@@ -113,7 +114,12 @@ namespace osu.Game.Overlays.Profile.Header
}
topLinkContainer.AddText("Contributed ");
- topLinkContainer.AddLink($@"{user.PostCount:#,##0} forum posts", $"{api.WebsiteRootUrl}/users/{user.Id}/posts", creationParameters: embolden);
+ topLinkContainer.AddLink("forum post".ToQuantity(user.PostCount, "#,##0"), $"{api.WebsiteRootUrl}/users/{user.Id}/posts", creationParameters: embolden);
+
+ addSpacer(topLinkContainer);
+
+ topLinkContainer.AddText("Posted ");
+ topLinkContainer.AddLink("comment".ToQuantity(user.CommentsCount, "#,##0"), $"{api.WebsiteRootUrl}/comments?user_id={user.Id}", creationParameters: embolden);
string websiteWithoutProtocol = user.Website;
@@ -138,7 +144,6 @@ namespace osu.Game.Overlays.Profile.Header
if (!string.IsNullOrEmpty(user.Twitter))
anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}");
anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Discord, user.Discord);
- anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Skype, user.Skype, @"skype:" + user.Skype + @"?chat");
anyInfoAdded |= tryAddInfo(FontAwesome.Solid.Link, websiteWithoutProtocol, user.Website);
// If no information was added to the bottomLinkContainer, hide it to avoid unwanted padding
diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs
index 5b7c5efbe2..20e40569e8 100644
--- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs
+++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs
@@ -5,7 +5,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
-using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
@@ -13,6 +12,7 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osuTK;
using osu.Framework.Graphics.Cursor;
+using osu.Framework.Localisation;
namespace osu.Game.Overlays.Profile.Sections.Historical
{
@@ -129,14 +129,14 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
{
new OsuSpriteText
{
- Text = new LocalisedString((
+ Text = new RomanisableString(
$"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} [{beatmap.Version}] ",
- $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] ")),
+ $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] "),
Font = OsuFont.GetFont(weight: FontWeight.Bold)
},
new OsuSpriteText
{
- Text = "by " + new LocalisedString((beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist)),
+ Text = "by " + new RomanisableString(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist),
Font = OsuFont.GetFont(weight: FontWeight.Regular)
},
};
diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs
index d4d0976724..cdb24b784c 100644
--- a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs
+++ b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs
@@ -23,51 +23,24 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu
{
this.user.BindTo(user);
CountSection total;
- CountSection avaliable;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Masking = true;
CornerRadius = 3;
- Children = new Drawable[]
- {
- new FillFlowContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(5, 0),
- Children = new[]
- {
- total = new CountTotal(),
- avaliable = new CountAvailable()
- }
- }
- };
- this.user.ValueChanged += u =>
- {
- total.Count = u.NewValue?.Kudosu.Total ?? 0;
- avaliable.Count = u.NewValue?.Kudosu.Available ?? 0;
- };
+ Child = total = new CountTotal();
+
+ this.user.ValueChanged += u => total.Count = u.NewValue?.Kudosu.Total ?? 0;
}
protected override bool OnClick(ClickEvent e) => true;
- private class CountAvailable : CountSection
- {
- public CountAvailable()
- : base("Kudosu Avaliable")
- {
- DescriptionText.Text = "Kudosu can be traded for kudosu stars, which will help your beatmap get more attention. This is the number of kudosu you haven't traded in yet.";
- }
- }
-
private class CountTotal : CountSection
{
public CountTotal()
: base("Total Kudosu Earned")
{
DescriptionText.AddText("Based on how much of a contribution the user has made to beatmap moderation. See ");
- DescriptionText.AddLink("this link", "https://osu.ppy.sh/wiki/Kudosu");
+ DescriptionText.AddLink("this page", "https://osu.ppy.sh/wiki/Kudosu");
DescriptionText.AddText(" for more information.");
}
}
@@ -80,13 +53,12 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu
public new int Count
{
- set => valueText.Text = value.ToString();
+ set => valueText.Text = value.ToString("N0");
}
public CountSection(string header)
{
RelativeSizeAxes = Axes.X;
- Width = 0.5f;
AutoSizeAxes = Axes.Y;
Padding = new MarginPadding { Top = 10, Bottom = 20 };
Child = new FillFlowContainer
@@ -131,7 +103,6 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu
private void load(OverlayColourProvider colourProvider)
{
lineBackground.Colour = colourProvider.Highlight1;
- DescriptionText.Colour = colourProvider.Foreground1;
}
}
}
diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs
index 2c20dcc0ef..713303285a 100644
--- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs
+++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs
@@ -256,16 +256,16 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
- Text = new LocalisedString((
+ Text = new RomanisableString(
$"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} ",
- $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} ")),
+ $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} "),
Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold, italics: true)
},
new OsuSpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
- Text = "by " + new LocalisedString((beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist)),
+ Text = "by " + new RomanisableString(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist),
Font = OsuFont.GetFont(size: 12, italics: true)
},
};
diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs
index bed74542c9..b31e7dc45b 100644
--- a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs
@@ -6,6 +6,7 @@ using osu.Framework.Audio;
using osu.Framework.Graphics;
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Settings.Sections.Audio
@@ -76,7 +77,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
private class AudioDeviceDropdownControl : DropdownControl
{
- protected override string GenerateItemText(string item)
+ protected override LocalisableString GenerateItemText(string item)
=> string.IsNullOrEmpty(item) ? "Default" : base.GenerateItemText(item);
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index 7acbf038d8..4d5c2e06eb 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -11,6 +11,7 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
@@ -234,7 +235,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
private class ResolutionDropdownControl : DropdownControl
{
- protected override string GenerateItemText(Size item)
+ protected override LocalisableString GenerateItemText(Size item)
{
if (item == new Size(9999, 9999))
return "Default";
diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs
similarity index 70%
rename from osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs
rename to osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs
index db6f24a954..79c73863cf 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs
@@ -5,17 +5,17 @@ using osu.Framework.Graphics;
namespace osu.Game.Overlays.Settings.Sections.Input
{
- public class KeyboardSettings : SettingsSubsection
+ public class BindingSettings : SettingsSubsection
{
- protected override string Header => "Keyboard";
+ protected override string Header => "Shortcut and gameplay bindings";
- public KeyboardSettings(KeyBindingPanel keyConfig)
+ public BindingSettings(KeyBindingPanel keyConfig)
{
Children = new Drawable[]
{
new SettingsButton
{
- Text = "Key configuration",
+ Text = "Configure",
TooltipText = "change global shortcut keys and gameplay bindings",
Action = keyConfig.ToggleVisibility
},
diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs
index 455e13711d..fb908a7669 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs
@@ -1,11 +1,11 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
+using osu.Framework.Input.Handlers.Mouse;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input;
@@ -14,41 +14,44 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{
public class MouseSettings : SettingsSubsection
{
+ private readonly MouseHandler mouseHandler;
+
protected override string Header => "Mouse";
- private readonly BindableBool rawInputToggle = new BindableBool();
- private Bindable sensitivityBindable = new BindableDouble();
- private Bindable ignoredInputHandlers;
+ private Bindable handlerSensitivity;
+
+ private Bindable localSensitivity;
private Bindable windowMode;
private SettingsEnumDropdown confineMouseModeSetting;
+ private Bindable relativeMode;
+
+ public MouseSettings(MouseHandler mouseHandler)
+ {
+ this.mouseHandler = mouseHandler;
+ }
[BackgroundDependencyLoader]
private void load(OsuConfigManager osuConfig, FrameworkConfigManager config)
{
- var configSensitivity = config.GetBindable(FrameworkSetting.CursorSensitivity);
-
// use local bindable to avoid changing enabled state of game host's bindable.
- sensitivityBindable = configSensitivity.GetUnboundCopy();
- configSensitivity.BindValueChanged(val => sensitivityBindable.Value = val.NewValue);
- sensitivityBindable.BindValueChanged(val => configSensitivity.Value = val.NewValue);
+ handlerSensitivity = mouseHandler.Sensitivity.GetBoundCopy();
+ localSensitivity = handlerSensitivity.GetUnboundCopy();
+
+ relativeMode = mouseHandler.UseRelativeMode.GetBoundCopy();
+ windowMode = config.GetBindable(FrameworkSetting.WindowMode);
Children = new Drawable[]
{
new SettingsCheckbox
{
- LabelText = "Raw input",
- Current = rawInputToggle
+ LabelText = "High precision mouse",
+ Current = relativeMode
},
new SensitivitySetting
{
LabelText = "Cursor sensitivity",
- Current = sensitivityBindable
- },
- new SettingsCheckbox
- {
- LabelText = "Map absolute input to window",
- Current = config.GetBindable(FrameworkSetting.MapAbsoluteInputToWindow)
+ Current = localSensitivity
},
confineMouseModeSetting = new SettingsEnumDropdown
{
@@ -66,36 +69,40 @@ namespace osu.Game.Overlays.Settings.Sections.Input
Current = osuConfig.GetBindable(OsuSetting.MouseDisableButtons)
},
};
+ }
- windowMode = config.GetBindable(FrameworkSetting.WindowMode);
- windowMode.BindValueChanged(mode => confineMouseModeSetting.Alpha = mode.NewValue == WindowMode.Fullscreen ? 0 : 1, true);
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
- if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows)
+ relativeMode.BindValueChanged(relative => localSensitivity.Disabled = !relative.NewValue, true);
+
+ handlerSensitivity.BindValueChanged(val =>
{
- rawInputToggle.Disabled = true;
- sensitivityBindable.Disabled = true;
- }
- else
+ var disabled = localSensitivity.Disabled;
+
+ localSensitivity.Disabled = false;
+ localSensitivity.Value = val.NewValue;
+ localSensitivity.Disabled = disabled;
+ }, true);
+
+ localSensitivity.BindValueChanged(val => handlerSensitivity.Value = val.NewValue);
+
+ windowMode.BindValueChanged(mode =>
{
- rawInputToggle.ValueChanged += enabled =>
+ var isFullscreen = mode.NewValue == WindowMode.Fullscreen;
+
+ if (isFullscreen)
{
- // this is temporary until we support per-handler settings.
- const string raw_mouse_handler = @"OsuTKRawMouseHandler";
- const string standard_mouse_handlers = @"OsuTKMouseHandler MouseHandler";
-
- ignoredInputHandlers.Value = enabled.NewValue ? standard_mouse_handlers : raw_mouse_handler;
- };
-
- ignoredInputHandlers = config.GetBindable(FrameworkSetting.IgnoredInputHandlers);
- ignoredInputHandlers.ValueChanged += handler =>
+ confineMouseModeSetting.Current.Disabled = true;
+ confineMouseModeSetting.TooltipText = "Not applicable in full screen mode";
+ }
+ else
{
- bool raw = !handler.NewValue.Contains("Raw");
- rawInputToggle.Value = raw;
- sensitivityBindable.Disabled = !raw;
- };
-
- ignoredInputHandlers.TriggerChange();
- }
+ confineMouseModeSetting.Current.Disabled = false;
+ confineMouseModeSetting.TooltipText = string.Empty;
+ }
+ }, true);
}
private class SensitivitySetting : SettingsSlider
@@ -109,7 +116,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
private class SensitivitySlider : OsuSliderBar
{
- public override string TooltipText => Current.Disabled ? "enable raw input to adjust sensitivity" : $"{base.TooltipText}x";
+ public override string TooltipText => Current.Disabled ? "enable high precision mouse to adjust sensitivity" : $"{base.TooltipText}x";
}
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs
new file mode 100644
index 0000000000..ecb8acce54
--- /dev/null
+++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs
@@ -0,0 +1,185 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input.Handlers.Tablet;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Overlays.Settings.Sections.Input
+{
+ public class TabletAreaSelection : CompositeDrawable
+ {
+ private readonly ITabletHandler handler;
+
+ private Container tabletContainer;
+ private Container usableAreaContainer;
+
+ private readonly Bindable areaOffset = new Bindable();
+ private readonly Bindable areaSize = new Bindable();
+
+ private readonly IBindable tablet = new Bindable();
+
+ private OsuSpriteText tabletName;
+
+ private Box usableFill;
+ private OsuSpriteText usableAreaText;
+
+ public TabletAreaSelection(ITabletHandler handler)
+ {
+ this.handler = handler;
+
+ Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ InternalChild = tabletContainer = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Masking = true,
+ CornerRadius = 5,
+ BorderThickness = 2,
+ BorderColour = colour.Gray3,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = colour.Gray1,
+ },
+ usableAreaContainer = new Container
+ {
+ Origin = Anchor.Centre,
+ Children = new Drawable[]
+ {
+ usableFill = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0.6f,
+ },
+ new Box
+ {
+ Colour = Color4.White,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Height = 5,
+ },
+ new Box
+ {
+ Colour = Color4.White,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Width = 5,
+ },
+ usableAreaText = new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Colour = Color4.White,
+ Font = OsuFont.Default.With(size: 12),
+ Y = 10
+ }
+ }
+ },
+ tabletName = new OsuSpriteText
+ {
+ Padding = new MarginPadding(3),
+ Font = OsuFont.Default.With(size: 8)
+ },
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ areaOffset.BindTo(handler.AreaOffset);
+ areaOffset.BindValueChanged(val =>
+ {
+ usableAreaContainer.MoveTo(val.NewValue, 100, Easing.OutQuint)
+ .OnComplete(_ => checkBounds()); // required as we are using SSDQ.
+ }, true);
+
+ areaSize.BindTo(handler.AreaSize);
+ areaSize.BindValueChanged(val =>
+ {
+ usableAreaContainer.ResizeTo(val.NewValue, 100, Easing.OutQuint)
+ .OnComplete(_ => checkBounds()); // required as we are using SSDQ.
+
+ int x = (int)val.NewValue.X;
+ int y = (int)val.NewValue.Y;
+ int commonDivider = greatestCommonDivider(x, y);
+
+ usableAreaText.Text = $"{(float)x / commonDivider}:{(float)y / commonDivider}";
+ }, true);
+
+ tablet.BindTo(handler.Tablet);
+ tablet.BindValueChanged(_ => Scheduler.AddOnce(updateTabletDetails));
+
+ updateTabletDetails();
+ // initial animation should be instant.
+ FinishTransforms(true);
+ }
+
+ private void updateTabletDetails()
+ {
+ tabletContainer.Size = tablet.Value?.Size ?? Vector2.Zero;
+ tabletName.Text = tablet.Value?.Name ?? string.Empty;
+ checkBounds();
+ }
+
+ private static int greatestCommonDivider(int a, int b)
+ {
+ while (b != 0)
+ {
+ int remainder = a % b;
+ a = b;
+ b = remainder;
+ }
+
+ return a;
+ }
+
+ [Resolved]
+ private OsuColour colour { get; set; }
+
+ private void checkBounds()
+ {
+ if (tablet.Value == null)
+ return;
+
+ var usableSsdq = usableAreaContainer.ScreenSpaceDrawQuad;
+
+ bool isWithinBounds = tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.TopLeft + new Vector2(1)) &&
+ tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.BottomRight - new Vector2(1));
+
+ usableFill.FadeColour(isWithinBounds ? colour.Blue : colour.RedLight, 100);
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (!(tablet.Value?.Size is Vector2 size))
+ return;
+
+ float fitX = size.X / (DrawWidth - Padding.Left - Padding.Right);
+ float fitY = size.Y / DrawHeight;
+
+ float adjust = MathF.Max(fitX, fitY);
+
+ tabletContainer.Scale = new Vector2(1 / adjust);
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs
new file mode 100644
index 0000000000..bd0f7ddc4c
--- /dev/null
+++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs
@@ -0,0 +1,285 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Input.Handlers.Tablet;
+using osu.Framework.Platform;
+using osu.Framework.Threading;
+using osu.Game.Graphics.Sprites;
+using osuTK;
+
+namespace osu.Game.Overlays.Settings.Sections.Input
+{
+ public class TabletSettings : SettingsSubsection
+ {
+ private readonly ITabletHandler tabletHandler;
+
+ private readonly Bindable areaOffset = new Bindable();
+ private readonly Bindable areaSize = new Bindable();
+ private readonly IBindable tablet = new Bindable();
+
+ private readonly BindableNumber offsetX = new BindableNumber { MinValue = 0 };
+ private readonly BindableNumber offsetY = new BindableNumber { MinValue = 0 };
+
+ private readonly BindableNumber sizeX = new BindableNumber { MinValue = 10 };
+ private readonly BindableNumber sizeY = new BindableNumber { MinValue = 10 };
+
+ [Resolved]
+ private GameHost host { get; set; }
+
+ ///
+ /// Based on ultrawide monitor configurations.
+ ///
+ private const float largest_feasible_aspect_ratio = 21f / 9;
+
+ private readonly BindableNumber aspectRatio = new BindableFloat(1)
+ {
+ MinValue = 1 / largest_feasible_aspect_ratio,
+ MaxValue = largest_feasible_aspect_ratio,
+ Precision = 0.01f,
+ };
+
+ private readonly BindableBool aspectLock = new BindableBool();
+
+ private ScheduledDelegate aspectRatioApplication;
+
+ private FillFlowContainer mainSettings;
+
+ private OsuSpriteText noTabletMessage;
+
+ protected override string Header => "Tablet";
+
+ public TabletSettings(ITabletHandler tabletHandler)
+ {
+ this.tabletHandler = tabletHandler;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Children = new Drawable[]
+ {
+ new SettingsCheckbox
+ {
+ LabelText = "Enabled",
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Current = tabletHandler.Enabled
+ },
+ noTabletMessage = new OsuSpriteText
+ {
+ Text = "No tablet detected!",
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS }
+ },
+ mainSettings = new FillFlowContainer
+ {
+ Alpha = 0,
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Spacing = new Vector2(0, 8),
+ Direction = FillDirection.Vertical,
+ Children = new Drawable[]
+ {
+ new TabletAreaSelection(tabletHandler)
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = 300,
+ },
+ new DangerousSettingsButton
+ {
+ Text = "Reset to full area",
+ Action = () =>
+ {
+ aspectLock.Value = false;
+
+ areaOffset.SetDefault();
+ areaSize.SetDefault();
+ },
+ },
+ new SettingsButton
+ {
+ Text = "Conform to current game aspect ratio",
+ Action = () =>
+ {
+ forceAspectRatio((float)host.Window.ClientSize.Width / host.Window.ClientSize.Height);
+ }
+ },
+ new SettingsSlider
+ {
+ TransferValueOnCommit = true,
+ LabelText = "Aspect Ratio",
+ Current = aspectRatio
+ },
+ new SettingsSlider
+ {
+ TransferValueOnCommit = true,
+ LabelText = "X Offset",
+ Current = offsetX
+ },
+ new SettingsSlider
+ {
+ TransferValueOnCommit = true,
+ LabelText = "Y Offset",
+ Current = offsetY
+ },
+ new SettingsCheckbox
+ {
+ LabelText = "Lock aspect ratio",
+ Current = aspectLock
+ },
+ new SettingsSlider
+ {
+ TransferValueOnCommit = true,
+ LabelText = "Width",
+ Current = sizeX
+ },
+ new SettingsSlider
+ {
+ TransferValueOnCommit = true,
+ LabelText = "Height",
+ Current = sizeY
+ },
+ }
+ },
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ areaOffset.BindTo(tabletHandler.AreaOffset);
+ areaOffset.BindValueChanged(val =>
+ {
+ offsetX.Value = val.NewValue.X;
+ offsetY.Value = val.NewValue.Y;
+ }, true);
+
+ offsetX.BindValueChanged(val => areaOffset.Value = new Vector2(val.NewValue, areaOffset.Value.Y));
+ offsetY.BindValueChanged(val => areaOffset.Value = new Vector2(areaOffset.Value.X, val.NewValue));
+
+ areaSize.BindTo(tabletHandler.AreaSize);
+ areaSize.BindValueChanged(val =>
+ {
+ sizeX.Value = val.NewValue.X;
+ sizeY.Value = val.NewValue.Y;
+ }, true);
+
+ sizeX.BindValueChanged(val =>
+ {
+ areaSize.Value = new Vector2(val.NewValue, areaSize.Value.Y);
+
+ aspectRatioApplication?.Cancel();
+ aspectRatioApplication = Schedule(() => applyAspectRatio(sizeX));
+ });
+
+ sizeY.BindValueChanged(val =>
+ {
+ areaSize.Value = new Vector2(areaSize.Value.X, val.NewValue);
+
+ aspectRatioApplication?.Cancel();
+ aspectRatioApplication = Schedule(() => applyAspectRatio(sizeY));
+ });
+
+ updateAspectRatio();
+ aspectRatio.BindValueChanged(aspect =>
+ {
+ aspectRatioApplication?.Cancel();
+ aspectRatioApplication = Schedule(() => forceAspectRatio(aspect.NewValue));
+ });
+
+ tablet.BindTo(tabletHandler.Tablet);
+ tablet.BindValueChanged(val =>
+ {
+ Scheduler.AddOnce(toggleVisibility);
+
+ var tab = val.NewValue;
+
+ bool tabletFound = tab != null;
+ if (!tabletFound)
+ return;
+
+ offsetX.MaxValue = tab.Size.X;
+ offsetX.Default = tab.Size.X / 2;
+ sizeX.Default = sizeX.MaxValue = tab.Size.X;
+
+ offsetY.MaxValue = tab.Size.Y;
+ offsetY.Default = tab.Size.Y / 2;
+ sizeY.Default = sizeY.MaxValue = tab.Size.Y;
+
+ areaSize.Default = new Vector2(sizeX.Default, sizeY.Default);
+ }, true);
+ }
+
+ private void toggleVisibility()
+ {
+ bool tabletFound = tablet.Value != null;
+
+ if (!tabletFound)
+ {
+ mainSettings.Hide();
+ noTabletMessage.Show();
+ return;
+ }
+
+ mainSettings.Show();
+ noTabletMessage.Hide();
+ }
+
+ private void applyAspectRatio(BindableNumber sizeChanged)
+ {
+ try
+ {
+ if (!aspectLock.Value)
+ {
+ float proposedAspectRatio = currentAspectRatio;
+
+ if (proposedAspectRatio >= aspectRatio.MinValue && proposedAspectRatio <= aspectRatio.MaxValue)
+ {
+ // aspect ratio was in a valid range.
+ updateAspectRatio();
+ return;
+ }
+ }
+
+ // if lock is applied (or the specified values were out of range) aim to adjust the axis the user was not adjusting to conform.
+ if (sizeChanged == sizeX)
+ sizeY.Value = (int)(areaSize.Value.X / aspectRatio.Value);
+ else
+ sizeX.Value = (int)(areaSize.Value.Y * aspectRatio.Value);
+ }
+ finally
+ {
+ // cancel any event which may have fired while updating variables as a result of aspect ratio limitations.
+ // this avoids a potential feedback loop.
+ aspectRatioApplication?.Cancel();
+ }
+ }
+
+ private void forceAspectRatio(float aspectRatio)
+ {
+ aspectLock.Value = false;
+
+ int proposedHeight = (int)(sizeX.Value / aspectRatio);
+
+ if (proposedHeight < sizeY.MaxValue)
+ sizeY.Value = proposedHeight;
+ else
+ sizeX.Value = (int)(sizeY.Value * aspectRatio);
+
+ updateAspectRatio();
+
+ aspectRatioApplication?.Cancel();
+ aspectLock.Value = true;
+ }
+
+ private void updateAspectRatio() => aspectRatio.Value = currentAspectRatio;
+
+ private float currentAspectRatio => sizeX.Value / sizeY.Value;
+ }
+}
diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs
index b43453f53d..6e99891794 100644
--- a/osu.Game/Overlays/Settings/Sections/InputSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs
@@ -1,28 +1,106 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input.Handlers;
+using osu.Framework.Input.Handlers.Joystick;
+using osu.Framework.Input.Handlers.Midi;
+using osu.Framework.Input.Handlers.Mouse;
+using osu.Framework.Input.Handlers.Tablet;
+using osu.Framework.Platform;
using osu.Game.Overlays.Settings.Sections.Input;
namespace osu.Game.Overlays.Settings.Sections
{
public class InputSection : SettingsSection
{
+ private readonly KeyBindingPanel keyConfig;
+
public override string Header => "Input";
+ [Resolved]
+ private GameHost host { get; set; }
+
public override Drawable CreateIcon() => new SpriteIcon
{
Icon = FontAwesome.Solid.Keyboard
};
public InputSection(KeyBindingPanel keyConfig)
+ {
+ this.keyConfig = keyConfig;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
{
Children = new Drawable[]
{
- new MouseSettings(),
- new KeyboardSettings(keyConfig),
+ new BindingSettings(keyConfig),
};
+
+ foreach (var handler in host.AvailableInputHandlers)
+ {
+ var handlerSection = createSectionFor(handler);
+
+ if (handlerSection != null)
+ Add(handlerSection);
+ }
+ }
+
+ private SettingsSubsection createSectionFor(InputHandler handler)
+ {
+ SettingsSubsection section;
+
+ switch (handler)
+ {
+ // ReSharper disable once SuspiciousTypeConversion.Global (net standard fuckery)
+ case ITabletHandler th:
+ section = new TabletSettings(th);
+ break;
+
+ case MouseHandler mh:
+ section = new MouseSettings(mh);
+ break;
+
+ // whitelist the handlers which should be displayed to avoid any weird cases of users touching settings they shouldn't.
+ case JoystickHandler _:
+ case MidiHandler _:
+ section = new HandlerSection(handler);
+ break;
+
+ default:
+ return null;
+ }
+
+ return section;
+ }
+
+ private class HandlerSection : SettingsSubsection
+ {
+ private readonly InputHandler handler;
+
+ public HandlerSection(InputHandler handler)
+ {
+ this.handler = handler;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Children = new Drawable[]
+ {
+ new SettingsCheckbox
+ {
+ LabelText = "Enabled",
+ Current = handler.Enabled
+ },
+ };
+ }
+
+ protected override string Header => handler.Description;
}
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
index 7c8309fd56..316837d27d 100644
--- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
@@ -8,6 +8,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
@@ -178,7 +179,7 @@ namespace osu.Game.Overlays.Settings.Sections
private class SkinDropdownControl : DropdownControl
{
- protected override string GenerateItemText(SkinInfo item) => item.ToString();
+ protected override LocalisableString GenerateItemText(SkinInfo item) => item.ToString();
}
}
diff --git a/osu.Game/Overlays/Settings/SettingsCheckbox.cs b/osu.Game/Overlays/Settings/SettingsCheckbox.cs
index 437b2e45b3..8b7ac80a5b 100644
--- a/osu.Game/Overlays/Settings/SettingsCheckbox.cs
+++ b/osu.Game/Overlays/Settings/SettingsCheckbox.cs
@@ -2,20 +2,22 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
+using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Settings
{
public class SettingsCheckbox : SettingsItem
{
- private string labelText;
+ private LocalisableString labelText;
protected override Drawable CreateControl() => new OsuCheckbox();
- public override string LabelText
+ public override LocalisableString LabelText
{
get => labelText;
- set => ((OsuCheckbox)Control).LabelText = labelText = value;
+ // checkbox doesn't properly support localisation yet.
+ set => ((OsuCheckbox)Control).LabelText = (labelText = value).ToString();
}
}
}
diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs
index af225889da..85765bf991 100644
--- a/osu.Game/Overlays/Settings/SettingsItem.cs
+++ b/osu.Game/Overlays/Settings/SettingsItem.cs
@@ -15,6 +15,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
+using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK;
@@ -39,7 +40,7 @@ namespace osu.Game.Overlays.Settings
public string TooltipText { get; set; }
- public virtual string LabelText
+ public virtual LocalisableString LabelText
{
get => labelText?.Text ?? string.Empty;
set
@@ -69,7 +70,7 @@ namespace osu.Game.Overlays.Settings
set => controlWithCurrent.Current = value;
}
- public virtual IEnumerable FilterTerms => Keywords == null ? new[] { LabelText } : new List(Keywords) { LabelText }.ToArray();
+ public virtual IEnumerable FilterTerms => Keywords == null ? new[] { LabelText.ToString() } : new List(Keywords) { LabelText.ToString() }.ToArray();
public IEnumerable Keywords { get; set; }
@@ -120,8 +121,10 @@ namespace osu.Game.Overlays.Settings
labelText.Alpha = controlWithCurrent.Current.Disabled ? 0.3f : 1;
}
- private class RestoreDefaultValueButton : Container, IHasTooltip
+ protected internal class RestoreDefaultValueButton : Container, IHasTooltip
{
+ public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
+
private Bindable bindable;
public Bindable Bindable
@@ -206,7 +209,9 @@ namespace osu.Game.Overlays.Settings
UpdateState();
}
- public void UpdateState()
+ public void UpdateState() => Scheduler.AddOnce(updateState);
+
+ private void updateState()
{
if (bindable == null)
return;
diff --git a/osu.Game/Overlays/Settings/SettingsSubsection.cs b/osu.Game/Overlays/Settings/SettingsSubsection.cs
index 1b82d973e9..6abf6283b9 100644
--- a/osu.Game/Overlays/Settings/SettingsSubsection.cs
+++ b/osu.Game/Overlays/Settings/SettingsSubsection.cs
@@ -8,10 +8,12 @@ using osu.Game.Graphics.Sprites;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
+using osu.Framework.Testing;
using osu.Game.Graphics;
namespace osu.Game.Overlays.Settings
{
+ [ExcludeFromDynamicCompile]
public abstract class SettingsSubsection : FillFlowContainer, IHasFilterableChildren
{
protected override Container Content => FlowContent;
diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs
index f1270f750e..8f3274b2b5 100644
--- a/osu.Game/Overlays/SettingsPanel.cs
+++ b/osu.Game/Overlays/SettingsPanel.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Overlays
private const float sidebar_width = Sidebar.DEFAULT_WIDTH;
- protected const float WIDTH = 400;
+ public const float WIDTH = 400;
protected Container ContentContainer;
diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs
index 393e349bd0..d049c2d3ec 100644
--- a/osu.Game/Overlays/Toolbar/Toolbar.cs
+++ b/osu.Game/Overlays/Toolbar/Toolbar.cs
@@ -13,14 +13,22 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Input.Events;
using osu.Game.Rulesets;
+using osu.Framework.Input.Bindings;
+using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.Toolbar
{
- public class Toolbar : VisibilityContainer
+ public class Toolbar : VisibilityContainer, IKeyBindingHandler
{
public const float HEIGHT = 40;
public const float TOOLTIP_HEIGHT = 30;
+ ///
+ /// Whether the user hid this with .
+ /// In this state, automatic toggles should not occur, respecting the user's preference to have no toolbar.
+ ///
+ private bool hiddenByUser;
+
public Action OnHome;
private ToolbarUserButton userButton;
@@ -30,13 +38,22 @@ namespace osu.Game.Overlays.Toolbar
protected readonly IBindable OverlayActivationMode = new Bindable(OverlayActivation.All);
- // Toolbar components like RulesetSelector should receive keyboard input events even when the toolbar is hidden.
+ // Toolbar and its components need keyboard input even when hidden.
public override bool PropagateNonPositionalInputSubTree => true;
public Toolbar()
{
RelativeSizeAxes = Axes.X;
Size = new Vector2(1, HEIGHT);
+ AlwaysPresent = true;
+ }
+
+ protected override void UpdateAfterChildren()
+ {
+ base.UpdateAfterChildren();
+
+ // this only needed to be set for the initial LoadComplete/Update, so layout completes and gets buttons in a state they can correctly handle keyboard input for hotkeys.
+ AlwaysPresent = false;
}
[BackgroundDependencyLoader(true)]
@@ -133,7 +150,9 @@ namespace osu.Game.Overlays.Toolbar
protected override void UpdateState(ValueChangedEvent state)
{
- if (state.NewValue == Visibility.Visible && OverlayActivationMode.Value == OverlayActivation.Disabled)
+ bool blockShow = hiddenByUser || OverlayActivationMode.Value == OverlayActivation.Disabled;
+
+ if (state.NewValue == Visibility.Visible && blockShow)
{
State.Value = Visibility.Hidden;
return;
@@ -155,5 +174,25 @@ namespace osu.Game.Overlays.Toolbar
this.MoveToY(-DrawSize.Y, transition_time, Easing.OutQuint);
this.FadeOut(transition_time, Easing.InQuint);
}
+
+ public bool OnPressed(GlobalAction action)
+ {
+ if (OverlayActivationMode.Value == OverlayActivation.Disabled)
+ return false;
+
+ switch (action)
+ {
+ case GlobalAction.ToggleToolbar:
+ hiddenByUser = State.Value == Visibility.Visible; // set before toggling to allow the operation to always succeed.
+ ToggleVisibility();
+ return true;
+ }
+
+ return false;
+ }
+
+ public void OnReleased(GlobalAction action)
+ {
+ }
}
}
diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs
index 83f2bdf6cb..1933422dd9 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs
@@ -4,6 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Caching;
using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
@@ -12,6 +13,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
+using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Containers;
@@ -43,19 +45,19 @@ namespace osu.Game.Overlays.Toolbar
Texture = textures.Get(texture),
});
- public string Text
+ public LocalisableString Text
{
get => DrawableText.Text;
set => DrawableText.Text = value;
}
- public string TooltipMain
+ public LocalisableString TooltipMain
{
get => tooltip1.Text;
set => tooltip1.Text = value;
}
- public string TooltipSub
+ public LocalisableString TooltipSub
{
get => tooltip2.Text;
set => tooltip2.Text = value;
@@ -127,9 +129,9 @@ namespace osu.Game.Overlays.Toolbar
{
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.Both, // stops us being considered in parent's autosize
- Anchor = TooltipAnchor.HasFlag(Anchor.x0) ? Anchor.BottomLeft : Anchor.BottomRight,
+ Anchor = TooltipAnchor.HasFlagFast(Anchor.x0) ? Anchor.BottomLeft : Anchor.BottomRight,
Origin = TooltipAnchor,
- Position = new Vector2(TooltipAnchor.HasFlag(Anchor.x0) ? 5 : -5, 5),
+ Position = new Vector2(TooltipAnchor.HasFlagFast(Anchor.x0) ? 5 : -5, 5),
Alpha = 0,
Children = new Drawable[]
{
diff --git a/osu.Game/PerformFromMenuRunner.cs b/osu.Game/PerformFromMenuRunner.cs
index a4179c94da..6f979b8dc8 100644
--- a/osu.Game/PerformFromMenuRunner.cs
+++ b/osu.Game/PerformFromMenuRunner.cs
@@ -5,14 +5,13 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
-using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Framework.Threading;
-using osu.Game.Beatmaps;
using osu.Game.Overlays;
using osu.Game.Overlays.Dialog;
using osu.Game.Overlays.Notifications;
+using osu.Game.Screens;
using osu.Game.Screens.Menu;
namespace osu.Game
@@ -29,9 +28,6 @@ namespace osu.Game
[Resolved]
private DialogOverlay dialogOverlay { get; set; }
- [Resolved]
- private IBindable beatmap { get; set; }
-
[Resolved(canBeNull: true)]
private OsuGame game { get; set; }
@@ -81,27 +77,45 @@ namespace osu.Game
game?.CloseAllOverlays(false);
- // we may already be at the target screen type.
+ findValidTarget(current);
+ }
+
+ private bool findValidTarget(IScreen current)
+ {
var type = current.GetType();
- if (validScreens.Any(t => t.IsAssignableFrom(type)) && !beatmap.Disabled)
+ // check if we are already at a valid target screen.
+ if (validScreens.Any(t => t.IsAssignableFrom(type)))
{
finalAction(current);
Cancel();
- return;
+ return true;
}
while (current != null)
{
+ // if this has a sub stack, recursively check the screens within it.
+ if (current is IHasSubScreenStack currentSubScreen)
+ {
+ if (findValidTarget(currentSubScreen.SubScreenStack.CurrentScreen))
+ {
+ // should be correct in theory, but currently untested/unused in existing implementations.
+ current.MakeCurrent();
+ return true;
+ }
+ }
+
if (validScreens.Any(t => t.IsAssignableFrom(type)))
{
current.MakeCurrent();
- break;
+ return true;
}
current = current.GetParentScreen();
type = current?.GetType();
}
+
+ return false;
}
///
diff --git a/osu.Game/Replays/Legacy/LegacyReplayFrame.cs b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs
index ab9ccda9b9..f6abf259e8 100644
--- a/osu.Game/Replays/Legacy/LegacyReplayFrame.cs
+++ b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs
@@ -3,6 +3,7 @@
using MessagePack;
using Newtonsoft.Json;
+using osu.Framework.Extensions.EnumExtensions;
using osu.Game.Rulesets.Replays;
using osuTK;
@@ -31,19 +32,19 @@ namespace osu.Game.Replays.Legacy
[JsonIgnore]
[IgnoreMember]
- public bool MouseLeft1 => ButtonState.HasFlag(ReplayButtonState.Left1);
+ public bool MouseLeft1 => ButtonState.HasFlagFast(ReplayButtonState.Left1);
[JsonIgnore]
[IgnoreMember]
- public bool MouseRight1 => ButtonState.HasFlag(ReplayButtonState.Right1);
+ public bool MouseRight1 => ButtonState.HasFlagFast(ReplayButtonState.Right1);
[JsonIgnore]
[IgnoreMember]
- public bool MouseLeft2 => ButtonState.HasFlag(ReplayButtonState.Left2);
+ public bool MouseLeft2 => ButtonState.HasFlagFast(ReplayButtonState.Left2);
[JsonIgnore]
[IgnoreMember]
- public bool MouseRight2 => ButtonState.HasFlag(ReplayButtonState.Right2);
+ public bool MouseRight2 => ButtonState.HasFlagFast(ReplayButtonState.Right2);
[Key(3)]
public ReplayButtonState ButtonState;
diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs
index 8c2292dcaa..9d06f960b7 100644
--- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs
+++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs
@@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Difficulty
private DifficultyAttributes calculate(IBeatmap beatmap, Mod[] mods, double clockRate)
{
- var skills = CreateSkills(beatmap);
+ var skills = CreateSkills(beatmap, mods);
if (!beatmap.HitObjects.Any())
return CreateDifficultyAttributes(beatmap, mods, skills, clockRate);
@@ -202,7 +202,8 @@ namespace osu.Game.Rulesets.Difficulty
/// Creates the s to calculate the difficulty of an .
///
/// The whose difficulty will be calculated.
+ /// Mods to calculate difficulty with.
/// The s.
- protected abstract Skill[] CreateSkills(IBeatmap beatmap);
+ protected abstract Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods);
}
}
diff --git a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs
index 44ce78c8e3..126e30ed73 100644
--- a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs
+++ b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Utils;
+using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Difficulty.Skills
{
@@ -46,10 +47,22 @@ namespace osu.Game.Rulesets.Difficulty.Skills
///
protected double CurrentStrain { get; private set; } = 1;
+ ///
+ /// Mods for use in skill calculations.
+ ///
+ protected IReadOnlyList Mods => mods;
+
private double currentSectionPeak = 1; // We also keep track of the peak strain level in the current section.
private readonly List strainPeaks = new List();
+ private readonly Mod[] mods;
+
+ protected Skill(Mod[] mods)
+ {
+ this.mods = mods;
+ }
+
///
/// Process a and update current strain values accordingly.
///
diff --git a/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs b/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs
new file mode 100644
index 0000000000..13cc41f8e0
--- /dev/null
+++ b/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs
@@ -0,0 +1,55 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Beatmaps;
+using osu.Game.Screens.Select;
+using osu.Game.Screens.Select.Filter;
+
+namespace osu.Game.Rulesets.Filter
+{
+ ///
+ /// Allows for extending the beatmap filtering capabilities of song select (as implemented in )
+ /// with ruleset-specific criteria.
+ ///
+ public interface IRulesetFilterCriteria
+ {
+ ///
+ /// Checks whether the supplied satisfies ruleset-specific custom criteria,
+ /// in addition to the ones mandated by song select.
+ ///
+ /// The beatmap to test the criteria against.
+ ///
+ /// true if the beatmap matches the ruleset-specific custom filtering criteria,
+ /// false otherwise.
+ ///
+ bool Matches(BeatmapInfo beatmap);
+
+ ///
+ /// Attempts to parse a single custom keyword criterion, given by the user via the song select search box.
+ /// The format of the criterion is:
+ ///
+ /// {key}{op}{value}
+ ///
+ ///
+ ///
+ ///
+ /// For adding optional string criteria, can be used for matching,
+ /// along with for parsing.
+ ///
+ ///
+ /// For adding numerical-type range criteria, can be used for matching,
+ /// along with
+ /// and - and -typed overloads for parsing.
+ ///
+ ///
+ /// The key (name) of the criterion.
+ /// The operator in the criterion.
+ /// The value of the criterion.
+ ///
+ /// true if the keyword criterion is valid, false if it has been ignored.
+ /// Valid criteria are stripped from ,
+ /// while ignored criteria are included in .
+ ///
+ bool TryParseCustomKeywordCriteria(string key, Operator op, string value);
+ }
+}
diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs
index da9bb8a09d..feeafb7151 100644
--- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs
+++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs
@@ -150,17 +150,13 @@ namespace osu.Game.Rulesets.Judgements
}
if (JudgementBody.Drawable is IAnimatableJudgement animatable)
- {
- var drawableAnimation = (Drawable)animatable;
-
animatable.PlayAnimation();
- // a derived version of DrawableJudgement may be proposing a lifetime.
- // if not adjusted (or the skinned portion requires greater bounds than calculated) use the skinned source's lifetime.
- double lastTransformTime = drawableAnimation.LatestTransformEndTime;
- if (LifetimeEnd == double.MaxValue || lastTransformTime > LifetimeEnd)
- LifetimeEnd = lastTransformTime;
- }
+ // a derived version of DrawableJudgement may be proposing a lifetime.
+ // if not adjusted (or the skinned portion requires greater bounds than calculated) use the skinned source's lifetime.
+ double lastTransformTime = JudgementBody.Drawable.LatestTransformEndTime;
+ if (LifetimeEnd == double.MaxValue || lastTransformTime > LifetimeEnd)
+ LifetimeEnd = lastTransformTime;
}
}
diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs
index d1d23def67..d6e1d46b06 100644
--- a/osu.Game/Rulesets/Mods/ModAutoplay.cs
+++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs
@@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mods
public bool RestartOnFail => false;
- public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) };
+ public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail) };
public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0;
diff --git a/osu.Game/Rulesets/Mods/ModFailCondition.cs b/osu.Game/Rulesets/Mods/ModFailCondition.cs
new file mode 100644
index 0000000000..c0d7bae2b2
--- /dev/null
+++ b/osu.Game/Rulesets/Mods/ModFailCondition.cs
@@ -0,0 +1,25 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Mods
+{
+ public abstract class ModFailCondition : Mod, IApplicableToHealthProcessor, IApplicableFailOverride
+ {
+ public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) };
+
+ public virtual bool PerformFail() => true;
+
+ public virtual bool RestartOnFail => true;
+
+ public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
+ {
+ healthProcessor.FailConditions += FailCondition;
+ }
+
+ protected abstract bool FailCondition(HealthProcessor healthProcessor, JudgementResult result);
+ }
+}
diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs
index b95ec7490e..c0f24e116a 100644
--- a/osu.Game/Rulesets/Mods/ModNoFail.cs
+++ b/osu.Game/Rulesets/Mods/ModNoFail.cs
@@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Mods
public override string Description => "You can't fail, no matter what.";
public override double ScoreMultiplier => 0.5;
public override bool Ranked => true;
- public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModAutoplay) };
+ public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModFailCondition), typeof(ModAutoplay) };
}
}
diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs
index df0fc9c4b6..d0b09b50f2 100644
--- a/osu.Game/Rulesets/Mods/ModPerfect.cs
+++ b/osu.Game/Rulesets/Mods/ModPerfect.cs
@@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using System.Linq;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements;
@@ -8,13 +10,18 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mods
{
- public abstract class ModPerfect : ModSuddenDeath
+ public abstract class ModPerfect : ModFailCondition
{
public override string Name => "Perfect";
public override string Acronym => "PF";
public override IconUsage? Icon => OsuIcon.ModPerfect;
+ public override ModType Type => ModType.DifficultyIncrease;
+ public override bool Ranked => true;
+ public override double ScoreMultiplier => 1;
public override string Description => "SS or quit.";
+ public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModSuddenDeath)).ToArray();
+
protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
=> result.Type.AffectsAccuracy()
&& result.Type != result.Judgement.MaxResult;
diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs
index b016a6d43b..e66650f7b4 100644
--- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs
+++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
@@ -24,6 +25,8 @@ namespace osu.Game.Rulesets.Mods
public double ApplyToRate(double time, double rate) => rate * SpeedChange.Value;
+ public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp) };
+
public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N2}x";
}
}
diff --git a/osu.Game/Rulesets/Mods/ModRelax.cs b/osu.Game/Rulesets/Mods/ModRelax.cs
index b6fec42f43..e5995ff180 100644
--- a/osu.Game/Rulesets/Mods/ModRelax.cs
+++ b/osu.Game/Rulesets/Mods/ModRelax.cs
@@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Mods
public override IconUsage? Icon => OsuIcon.ModRelax;
public override ModType Type => ModType.Automation;
public override double ScoreMultiplier => 1;
- public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModSuddenDeath) };
+ public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModFailCondition) };
}
}
diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs
index ae71041a64..617ae38feb 100644
--- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs
+++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Linq;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements;
@@ -9,7 +10,7 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mods
{
- public abstract class ModSuddenDeath : Mod, IApplicableToHealthProcessor, IApplicableFailOverride
+ public abstract class ModSuddenDeath : ModFailCondition
{
public override string Name => "Sudden Death";
public override string Acronym => "SD";
@@ -18,18 +19,10 @@ namespace osu.Game.Rulesets.Mods
public override string Description => "Miss and fail.";
public override double ScoreMultiplier => 1;
public override bool Ranked => true;
- public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) };
- public bool PerformFail() => true;
+ public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModPerfect)).ToArray();
- public bool RestartOnFail => true;
-
- public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
- {
- healthProcessor.FailConditions += FailCondition;
- }
-
- protected virtual bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
+ protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
=> result.Type.AffectsCombo()
&& !result.IsHit;
}
diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs
index 330945d3d3..b5cd64dafa 100644
--- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs
+++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs
@@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Mods
[SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
public abstract BindableBool AdjustPitch { get; }
+ public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust) };
+
public override string SettingDescription => $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x";
private double finalRateTime;
diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs
index 679b50057b..08bd44f7bd 100644
--- a/osu.Game/Rulesets/Mods/ModWindDown.cs
+++ b/osu.Game/Rulesets/Mods/ModWindDown.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mods
[SettingSource("Initial rate", "The starting speed of the track")]
public override BindableNumber InitialRate { get; } = new BindableDouble
{
- MinValue = 1,
+ MinValue = 0.51,
MaxValue = 2,
Default = 1,
Value = 1,
@@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mods
public override BindableNumber FinalRate { get; } = new BindableDouble
{
MinValue = 0.5,
- MaxValue = 0.99,
+ MaxValue = 1.99,
Default = 0.75,
Value = 0.75,
Precision = 0.01,
@@ -45,5 +45,20 @@ namespace osu.Game.Rulesets.Mods
};
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindUp)).ToArray();
+
+ public ModWindDown()
+ {
+ InitialRate.BindValueChanged(val =>
+ {
+ if (val.NewValue <= FinalRate.Value)
+ FinalRate.Value = val.NewValue - FinalRate.Precision;
+ });
+
+ FinalRate.BindValueChanged(val =>
+ {
+ if (val.NewValue >= InitialRate.Value)
+ InitialRate.Value = val.NewValue + InitialRate.Precision;
+ });
+ }
}
}
diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs
index b733bf423e..df8f781148 100644
--- a/osu.Game/Rulesets/Mods/ModWindUp.cs
+++ b/osu.Game/Rulesets/Mods/ModWindUp.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mods
public override BindableNumber InitialRate { get; } = new BindableDouble
{
MinValue = 0.5,
- MaxValue = 1,
+ MaxValue = 1.99,
Default = 1,
Value = 1,
Precision = 0.01,
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods
[SettingSource("Final rate", "The speed increase to ramp towards")]
public override BindableNumber FinalRate { get; } = new BindableDouble
{
- MinValue = 1.01,
+ MinValue = 0.51,
MaxValue = 2,
Default = 1.5,
Value = 1.5,
@@ -45,5 +45,20 @@ namespace osu.Game.Rulesets.Mods
};
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindDown)).ToArray();
+
+ public ModWindUp()
+ {
+ InitialRate.BindValueChanged(val =>
+ {
+ if (val.NewValue >= FinalRate.Value)
+ FinalRate.Value = val.NewValue + FinalRate.Precision;
+ });
+
+ FinalRate.BindValueChanged(val =>
+ {
+ if (val.NewValue <= InitialRate.Value)
+ InitialRate.Value = val.NewValue - InitialRate.Precision;
+ });
+ }
}
}
diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
index 72025de131..8419dd66de 100644
--- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
@@ -10,6 +10,7 @@ using osu.Game.Beatmaps.Formats;
using osu.Game.Audio;
using System.Linq;
using JetBrains.Annotations;
+using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Utils;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Skinning;
@@ -54,7 +55,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
int comboOffset = (int)(type & LegacyHitObjectType.ComboOffset) >> 4;
type &= ~LegacyHitObjectType.ComboOffset;
- bool combo = type.HasFlag(LegacyHitObjectType.NewCombo);
+ bool combo = type.HasFlagFast(LegacyHitObjectType.NewCombo);
type &= ~LegacyHitObjectType.NewCombo;
var soundType = (LegacyHitSoundType)Parsing.ParseInt(split[4]);
@@ -62,14 +63,14 @@ namespace osu.Game.Rulesets.Objects.Legacy
HitObject result = null;
- if (type.HasFlag(LegacyHitObjectType.Circle))
+ if (type.HasFlagFast(LegacyHitObjectType.Circle))
{
result = CreateHit(pos, combo, comboOffset);
if (split.Length > 5)
readCustomSampleBanks(split[5], bankInfo);
}
- else if (type.HasFlag(LegacyHitObjectType.Slider))
+ else if (type.HasFlagFast(LegacyHitObjectType.Slider))
{
double? length = null;
@@ -141,7 +142,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
result = CreateSlider(pos, combo, comboOffset, convertPathString(split[5], pos), length, repeatCount, nodeSamples);
}
- else if (type.HasFlag(LegacyHitObjectType.Spinner))
+ else if (type.HasFlagFast(LegacyHitObjectType.Spinner))
{
double duration = Math.Max(0, Parsing.ParseDouble(split[5]) + Offset - startTime);
@@ -150,7 +151,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
if (split.Length > 6)
readCustomSampleBanks(split[6], bankInfo);
}
- else if (type.HasFlag(LegacyHitObjectType.Hold))
+ else if (type.HasFlagFast(LegacyHitObjectType.Hold))
{
// Note: Hold is generated by BMS converts
@@ -436,16 +437,16 @@ namespace osu.Game.Rulesets.Objects.Legacy
new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.Normal, bankInfo.Volume, bankInfo.CustomSampleBank,
// if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample.
// None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds
- type != LegacyHitSoundType.None && !type.HasFlag(LegacyHitSoundType.Normal))
+ type != LegacyHitSoundType.None && !type.HasFlagFast(LegacyHitSoundType.Normal))
};
- if (type.HasFlag(LegacyHitSoundType.Finish))
+ if (type.HasFlagFast(LegacyHitSoundType.Finish))
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_FINISH, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank));
- if (type.HasFlag(LegacyHitSoundType.Whistle))
+ if (type.HasFlagFast(LegacyHitSoundType.Whistle))
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_WHISTLE, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank));
- if (type.HasFlag(LegacyHitSoundType.Clap))
+ if (type.HasFlagFast(LegacyHitSoundType.Clap))
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_CLAP, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank));
return soundTypes;
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index dbc2bd4d01..38d30a2e31 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -26,6 +26,7 @@ using JetBrains.Annotations;
using osu.Framework.Extensions;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Testing;
+using osu.Game.Rulesets.Filter;
using osu.Game.Screens.Ranking.Statistics;
namespace osu.Game.Rulesets
@@ -306,5 +307,11 @@ namespace osu.Game.Rulesets
/// The result type to get the name for.
/// The display name.
public virtual string GetDisplayNameForHitResult(HitResult result) => result.GetDescription();
+
+ ///
+ /// Creates ruleset-specific beatmap filter criteria to be used on the song select screen.
+ ///
+ [CanBeNull]
+ public virtual IRulesetFilterCriteria CreateRulesetFilterCriteria() => null;
}
}
diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs
index bbaca7c80f..e66a8c016c 100644
--- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs
+++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs
@@ -63,6 +63,7 @@ namespace osu.Game.Rulesets.UI
~DrawableRulesetDependencies()
{
+ // required to potentially clean up sample store from audio hierarchy.
Dispose(false);
}
@@ -116,6 +117,10 @@ namespace osu.Game.Rulesets.UI
public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotSupportedException();
+ public void BindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException();
+
+ public void UnbindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException();
+
public BindableNumber Volume => throw new NotSupportedException();
public BindableNumber Balance => throw new NotSupportedException();
@@ -124,8 +129,6 @@ namespace osu.Game.Rulesets.UI
public BindableNumber Tempo => throw new NotSupportedException();
- public IBindable GetAggregate(AdjustableProperty type) => throw new NotSupportedException();
-
public IBindable AggregateVolume => throw new NotSupportedException();
public IBindable AggregateBalance => throw new NotSupportedException();
diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs
index 07de2bf601..d6f002ea2c 100644
--- a/osu.Game/Rulesets/UI/RulesetInputManager.cs
+++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs
@@ -16,7 +16,6 @@ using osu.Game.Configuration;
using osu.Game.Input.Bindings;
using osu.Game.Input.Handlers;
using osu.Game.Screens.Play;
-using osuTK.Input;
using static osu.Game.Input.Handlers.ReplayInputHandler;
namespace osu.Game.Rulesets.UI
@@ -109,9 +108,9 @@ namespace osu.Game.Rulesets.UI
{
switch (e)
{
- case MouseDownEvent mouseDown when mouseDown.Button == MouseButton.Left || mouseDown.Button == MouseButton.Right:
+ case MouseDownEvent _:
if (mouseDisabled.Value)
- return false;
+ return true; // importantly, block upwards propagation so global bindings also don't fire.
break;
diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs
index a6beb19876..c7ee26c248 100644
--- a/osu.Game/Scoring/ScoreManager.cs
+++ b/osu.Game/Scoring/ScoreManager.cs
@@ -20,6 +20,7 @@ using osu.Game.IO.Archives;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring.Legacy;
@@ -137,7 +138,7 @@ namespace osu.Game.Scoring
ScoringMode.BindValueChanged(onScoringModeChanged, true);
}
- private IBindable difficultyBindable;
+ private IBindable difficultyBindable;
private CancellationTokenSource difficultyCancellationSource;
private void onScoringModeChanged(ValueChangedEvent mode)
@@ -152,9 +153,21 @@ namespace osu.Game.Scoring
}
int beatmapMaxCombo;
+ double accuracy = score.Accuracy;
if (score.IsLegacyScore)
{
+ if (score.RulesetID == 3)
+ {
+ // In osu!stable, a full-GREAT score has 100% accuracy in mania. Along with a full combo, the score becomes indistinguishable from a full-PERFECT score.
+ // To get around this, recalculate accuracy based on the hit statistics.
+ // Note: This cannot be applied universally to all legacy scores, as some rulesets (e.g. catch) group multiple judgements together.
+ double maxBaseScore = score.Statistics.Select(kvp => kvp.Value).Sum() * Judgement.ToNumericResult(HitResult.Perfect);
+ double baseScore = score.Statistics.Select(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value).Sum();
+ if (maxBaseScore > 0)
+ accuracy = baseScore / maxBaseScore;
+ }
+
// This score is guaranteed to be an osu!stable score.
// The combo must be determined through either the beatmap's max combo value or the difficulty calculator, as lazer's scoring has changed and the score statistics cannot be used.
if (score.Beatmap.MaxCombo == null)
@@ -168,7 +181,11 @@ namespace osu.Game.Scoring
// We can compute the max combo locally after the async beatmap difficulty computation.
difficultyBindable = difficulties().GetBindableDifficulty(score.Beatmap, score.Ruleset, score.Mods, (difficultyCancellationSource = new CancellationTokenSource()).Token);
- difficultyBindable.BindValueChanged(d => updateScore(d.NewValue.MaxCombo), true);
+ difficultyBindable.BindValueChanged(d =>
+ {
+ if (d.NewValue is StarDifficulty diff)
+ updateScore(diff.MaxCombo, accuracy);
+ }, true);
return;
}
@@ -182,10 +199,10 @@ namespace osu.Game.Scoring
beatmapMaxCombo = Enum.GetValues(typeof(HitResult)).OfType().Where(r => r.AffectsCombo()).Select(r => score.Statistics.GetOrDefault(r)).Sum();
}
- updateScore(beatmapMaxCombo);
+ updateScore(beatmapMaxCombo, accuracy);
}
- private void updateScore(int beatmapMaxCombo)
+ private void updateScore(int beatmapMaxCombo, double accuracy)
{
if (beatmapMaxCombo == 0)
{
@@ -198,7 +215,7 @@ namespace osu.Game.Scoring
scoreProcessor.Mods.Value = score.Mods;
- Value = (long)Math.Round(scoreProcessor.GetScore(ScoringMode.Value, beatmapMaxCombo, score.Accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics));
+ Value = (long)Math.Round(scoreProcessor.GetScore(ScoringMode.Value, beatmapMaxCombo, accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics));
}
}
diff --git a/osu.Game/Scoring/ScorePerformanceCache.cs b/osu.Game/Scoring/ScorePerformanceCache.cs
index 5f66c13d2f..bb15983de3 100644
--- a/osu.Game/Scoring/ScorePerformanceCache.cs
+++ b/osu.Game/Scoring/ScorePerformanceCache.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Scoring
{
var score = lookup.ScoreInfo;
- var attributes = await difficultyCache.GetDifficultyAsync(score.Beatmap, score.Ruleset, score.Mods, token);
+ var attributes = await difficultyCache.GetDifficultyAsync(score.Beatmap, score.Ruleset, score.Mods, token).ConfigureAwait(false);
// Performance calculation requires the beatmap and ruleset to be locally available. If not, return a default value.
if (attributes.Attributes == null)
diff --git a/osu.Game/Screens/BackgroundScreen.cs b/osu.Game/Screens/BackgroundScreen.cs
index 48c5523883..a6fb94b151 100644
--- a/osu.Game/Screens/BackgroundScreen.cs
+++ b/osu.Game/Screens/BackgroundScreen.cs
@@ -13,6 +13,8 @@ namespace osu.Game.Screens
{
private readonly bool animateOnEnter;
+ public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
+
protected BackgroundScreen(bool animateOnEnter = true)
{
this.animateOnEnter = animateOnEnter;
diff --git a/osu.Game/Screens/Edit/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs
index d9477dd4bc..ff33f0c70d 100644
--- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs
+++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs
@@ -4,7 +4,6 @@
using System;
using System.Linq;
using osu.Framework.Bindables;
-using osu.Framework.Graphics.Colour;
using osu.Game.Graphics;
using osuTK.Graphics;
@@ -48,7 +47,7 @@ namespace osu.Game.Screens.Edit
/// The beat divisor.
/// The set of colours.
/// The applicable colour from for .
- public static ColourInfo GetColourFor(int beatDivisor, OsuColour colours)
+ public static Color4 GetColourFor(int beatDivisor, OsuColour colours)
{
switch (beatDivisor)
{
diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs
index 9739f2876a..bdc6e238c8 100644
--- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs
+++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Screens.Edit.Components
[Resolved]
private EditorClock editorClock { get; set; }
- private readonly BindableNumber tempo = new BindableDouble(1);
+ private readonly BindableNumber freqAdjust = new BindableDouble(1);
[BackgroundDependencyLoader]
private void load()
@@ -58,16 +58,16 @@ namespace osu.Game.Screens.Edit.Components
RelativeSizeAxes = Axes.Both,
Height = 0.5f,
Padding = new MarginPadding { Left = 45 },
- Child = new PlaybackTabControl { Current = tempo },
+ Child = new PlaybackTabControl { Current = freqAdjust },
}
};
- Track.BindValueChanged(tr => tr.NewValue?.AddAdjustment(AdjustableProperty.Tempo, tempo), true);
+ Track.BindValueChanged(tr => tr.NewValue?.AddAdjustment(AdjustableProperty.Frequency, freqAdjust), true);
}
protected override void Dispose(bool isDisposing)
{
- Track.Value?.RemoveAdjustment(AdjustableProperty.Tempo, tempo);
+ Track.Value?.RemoveAdjustment(AdjustableProperty.Frequency, freqAdjust);
base.Dispose(isDisposing);
}
@@ -101,7 +101,7 @@ namespace osu.Game.Screens.Edit.Components
private class PlaybackTabControl : OsuTabControl
{
- private static readonly double[] tempo_values = { 0.5, 0.75, 1 };
+ private static readonly double[] tempo_values = { 0.25, 0.5, 0.75, 1 };
protected override TabItem CreateTabItem(double value) => new PlaybackTabItem(value);
diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
index 5371beac60..5699be4560 100644
--- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
@@ -268,6 +268,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void addBlueprintFor(HitObject hitObject)
{
+ if (hitObject is IBarLine)
+ return;
+
if (blueprintMap.ContainsKey(hitObject))
return;
@@ -338,7 +341,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
private bool beginClickSelection(MouseButtonEvent e)
{
// Iterate from the top of the input stack (blueprints closest to the front of the screen first).
- foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Reverse())
+ // Priority is given to already-selected blueprints.
+ foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Reverse().OrderByDescending(b => b.IsSelected))
{
if (!blueprint.IsHovered) continue;
@@ -495,8 +499,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
// Apply the start time at the newly snapped-to position
double offset = result.Time.Value - movementBlueprints.First().HitObject.StartTime;
- foreach (HitObject obj in Beatmap.SelectedHitObjects)
- obj.StartTime += offset;
+ Beatmap.PerformOnSelection(obj => obj.StartTime += offset);
}
return true;
diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs
index 8a92a2011d..59f88ac641 100644
--- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs
@@ -132,7 +132,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
var colour = BindableBeatDivisor.GetColourFor(BindableBeatDivisor.GetDivisorForBeatIndex(beatIndex + placementIndex + 1, beatDivisor.Value), Colours);
int repeatIndex = placementIndex / beatDivisor.Value;
- return colour.MultiplyAlpha(0.5f / (repeatIndex + 1));
+ return ColourInfo.SingleColour(colour).MultiplyAlpha(0.5f / (repeatIndex + 1));
}
}
}
diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs
index 2f4721f63e..9d6b44e207 100644
--- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs
@@ -113,16 +113,25 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (e.Repeat || !e.ControlPressed)
return false;
+ bool runOperationFromHotkey(Func operation)
+ {
+ operationStarted();
+ bool result = operation?.Invoke() ?? false;
+ operationEnded();
+
+ return result;
+ }
+
switch (e.Key)
{
case Key.G:
- return CanReverse && OnReverse?.Invoke() == true;
+ return CanReverse && runOperationFromHotkey(OnReverse);
case Key.H:
- return CanScaleX && OnFlip?.Invoke(Direction.Horizontal) == true;
+ return CanScaleX && runOperationFromHotkey(() => OnFlip?.Invoke(Direction.Horizontal) ?? false);
case Key.J:
- return CanScaleY && OnFlip?.Invoke(Direction.Vertical) == true;
+ return CanScaleY && runOperationFromHotkey(() => OnFlip?.Invoke(Direction.Vertical) ?? false);
}
return base.OnKeyDown(e);
diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs
index 788b485449..018d4d081c 100644
--- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs
@@ -320,18 +320,14 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// The name of the hit sample.
public void AddHitSample(string sampleName)
{
- EditorBeatmap.BeginChange();
-
- foreach (var h in EditorBeatmap.SelectedHitObjects)
+ EditorBeatmap.PerformOnSelection(h =>
{
// Make sure there isn't already an existing sample
if (h.Samples.Any(s => s.Name == sampleName))
- continue;
+ return;
h.Samples.Add(new HitSampleInfo(sampleName));
- }
-
- EditorBeatmap.EndChange();
+ });
}
///
@@ -341,19 +337,15 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// Throws if any selected object doesn't implement
public void SetNewCombo(bool state)
{
- EditorBeatmap.BeginChange();
-
- foreach (var h in EditorBeatmap.SelectedHitObjects)
+ EditorBeatmap.PerformOnSelection(h =>
{
var comboInfo = h as IHasComboInformation;
- if (comboInfo == null || comboInfo.NewCombo == state) continue;
+ if (comboInfo == null || comboInfo.NewCombo == state) return;
comboInfo.NewCombo = state;
EditorBeatmap.Update(h);
- }
-
- EditorBeatmap.EndChange();
+ });
}
///
@@ -362,12 +354,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// The name of the hit sample.
public void RemoveHitSample(string sampleName)
{
- EditorBeatmap.BeginChange();
-
- foreach (var h in EditorBeatmap.SelectedHitObjects)
- h.SamplesBindable.RemoveAll(s => s.Name == sampleName);
-
- EditorBeatmap.EndChange();
+ EditorBeatmap.PerformOnSelection(h => h.SamplesBindable.RemoveAll(s => s.Name == sampleName));
}
#endregion
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs
index 666026e05e..0697dbb392 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs
@@ -177,7 +177,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
if (!track.IsLoaded)
return;
- editorClock.Seek(Current / Content.DrawWidth * track.Length);
+ double target = Current / Content.DrawWidth * track.Length;
+ editorClock.Seek(Math.Min(track.Length, target));
}
private void scrollToTrackTime()
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs
index fb11b859a7..c070c833f8 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs
@@ -6,7 +6,9 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Caching;
+using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts;
@@ -124,25 +126,28 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
if (beat == 0 && i == 0)
nextMinTick = float.MinValue;
- var indexInBar = beat % ((int)point.TimeSignature * beatDivisor.Value);
+ int indexInBar = beat % ((int)point.TimeSignature * beatDivisor.Value);
var divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value);
var colour = BindableBeatDivisor.GetColourFor(divisor, colours);
+ bool isMainBeat = indexInBar == 0;
+
// even though "bar lines" take up the full vertical space, we render them in two pieces because it allows for less anchor/origin churn.
- var height = indexInBar == 0 ? 0.5f : 0.1f - (float)divisor / highestDivisor * 0.08f;
+ float height = isMainBeat ? 0.5f : 0.4f - (float)divisor / highestDivisor * 0.2f;
+ float gradientOpacity = isMainBeat ? 1 : 0;
var topPoint = getNextUsablePoint();
topPoint.X = xPos;
- topPoint.Colour = colour;
topPoint.Height = height;
+ topPoint.Colour = ColourInfo.GradientVertical(colour, colour.Opacity(gradientOpacity));
topPoint.Anchor = Anchor.TopLeft;
topPoint.Origin = Anchor.TopCentre;
var bottomPoint = getNextUsablePoint();
bottomPoint.X = xPos;
- bottomPoint.Colour = colour;
bottomPoint.Anchor = Anchor.BottomLeft;
+ bottomPoint.Colour = ColourInfo.GradientVertical(colour.Opacity(gradientOpacity), colour);
bottomPoint.Origin = Anchor.BottomCentre;
bottomPoint.Height = height;
}
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index 0ba202b082..0c24eb6a4d 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -74,7 +74,6 @@ namespace osu.Game.Screens.Edit
private string lastSavedHash;
- private Box bottomBackground;
private Container screenContainer;
private EditorScreen currentScreen;
@@ -106,26 +105,29 @@ namespace osu.Game.Screens.Edit
[BackgroundDependencyLoader]
private void load(OsuColour colours, GameHost host, OsuConfigManager config)
{
- if (Beatmap.Value is DummyWorkingBeatmap)
+ var loadableBeatmap = Beatmap.Value;
+
+ if (loadableBeatmap is DummyWorkingBeatmap)
{
isNewBeatmap = true;
- var newBeatmap = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value);
+ loadableBeatmap = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value);
+
+ // required so we can get the track length in EditorClock.
+ // this is safe as nothing has yet got a reference to this new beatmap.
+ loadableBeatmap.LoadTrack();
// this is a bit haphazard, but guards against setting the lease Beatmap bindable if
// the editor has already been exited.
if (!ValidForPush)
return;
-
- // this probably shouldn't be set in the asynchronous load method, but everything following relies on it.
- Beatmap.Value = newBeatmap;
}
- beatDivisor.Value = Beatmap.Value.BeatmapInfo.BeatDivisor;
- beatDivisor.BindValueChanged(divisor => Beatmap.Value.BeatmapInfo.BeatDivisor = divisor.NewValue);
+ beatDivisor.Value = loadableBeatmap.BeatmapInfo.BeatDivisor;
+ beatDivisor.BindValueChanged(divisor => loadableBeatmap.BeatmapInfo.BeatDivisor = divisor.NewValue);
// Todo: should probably be done at a DrawableRuleset level to share logic with Player.
- clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false };
+ clock = new EditorClock(loadableBeatmap, beatDivisor) { IsCoupled = false };
UpdateClockSource();
@@ -139,7 +141,7 @@ namespace osu.Game.Screens.Edit
try
{
- playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset);
+ playableBeatmap = loadableBeatmap.GetPlayableBeatmap(loadableBeatmap.BeatmapInfo.Ruleset);
// clone these locally for now to avoid incurring overhead on GetPlayableBeatmap usages.
// eventually we will want to improve how/where this is done as there are issues with *not* cloning it in all cases.
@@ -153,13 +155,21 @@ namespace osu.Game.Screens.Edit
return;
}
- AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, Beatmap.Value.Skin));
+ AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, loadableBeatmap.Skin));
dependencies.CacheAs(editorBeatmap);
changeHandler = new EditorChangeHandler(editorBeatmap);
dependencies.CacheAs(changeHandler);
updateLastSavedHash();
+ Schedule(() =>
+ {
+ // we need to avoid changing the beatmap from an asynchronous load thread. it can potentially cause weirdness including crashes.
+ // this assumes that nothing during the rest of this load() method is accessing Beatmap.Value (loadableBeatmap should be preferred).
+ // generally this is quite safe, as the actual load of editor content comes after menuBar.Mode.ValueChanged is fired in its own LoadComplete.
+ Beatmap.Value = loadableBeatmap;
+ });
+
OsuMenuItem undoMenuItem;
OsuMenuItem redoMenuItem;
@@ -167,17 +177,6 @@ namespace osu.Game.Screens.Edit
EditorMenuItem copyMenuItem;
EditorMenuItem pasteMenuItem;
- var fileMenuItems = new List