diff --git a/Gemfile.lock b/Gemfile.lock
index e3954c2681..bf971d2c22 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -5,6 +5,22 @@ GEM
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
atomos (0.1.3)
+ aws-eventstream (1.1.0)
+ aws-partitions (1.329.0)
+ aws-sdk-core (3.99.2)
+ aws-eventstream (~> 1, >= 1.0.2)
+ aws-partitions (~> 1, >= 1.239.0)
+ aws-sigv4 (~> 1.1)
+ jmespath (~> 1.0)
+ aws-sdk-kms (1.34.1)
+ aws-sdk-core (~> 3, >= 3.99.0)
+ aws-sigv4 (~> 1.1)
+ aws-sdk-s3 (1.68.1)
+ aws-sdk-core (~> 3, >= 3.99.0)
+ aws-sdk-kms (~> 1)
+ aws-sigv4 (~> 1.1)
+ aws-sigv4 (1.1.4)
+ aws-eventstream (~> 1.0, >= 1.0.2)
babosa (1.0.3)
claide (1.0.3)
colored (1.2)
@@ -13,23 +29,24 @@ GEM
highline (~> 1.7.2)
declarative (0.0.10)
declarative-option (0.1.0)
- digest-crc (0.4.1)
+ digest-crc (0.5.1)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.5)
emoji_regex (1.0.1)
- excon (0.71.1)
- faraday (0.17.3)
+ excon (0.74.0)
+ faraday (1.0.1)
multipart-post (>= 1.2, < 3)
faraday-cookie_jar (0.0.6)
faraday (>= 0.7.4)
http-cookie (~> 1.0.0)
- faraday_middleware (0.13.1)
- faraday (>= 0.7.4, < 1.0)
+ faraday_middleware (1.0.0)
+ faraday (~> 1.0)
fastimage (2.1.7)
- fastlane (2.140.0)
+ fastlane (2.149.1)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
+ aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.2, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored
@@ -37,12 +54,12 @@ GEM
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 2.0)
excon (>= 0.71.0, < 1.0.0)
- faraday (~> 0.17)
+ faraday (>= 0.17, < 2.0)
faraday-cookie_jar (~> 0.0.6)
- faraday_middleware (~> 0.13.1)
+ faraday_middleware (>= 0.13.1, < 2.0)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
- google-api-client (>= 0.29.2, < 0.37.0)
+ google-api-client (>= 0.37.0, < 0.39.0)
google-cloud-storage (>= 1.15.0, < 2.0.0)
highline (>= 1.7.2, < 2.0.0)
json (< 3.0.0)
@@ -69,7 +86,7 @@ GEM
souyuz (= 0.9.1)
fastlane-plugin-xamarin (0.6.3)
gh_inspector (1.1.3)
- google-api-client (0.36.4)
+ google-api-client (0.38.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.9)
httpclient (>= 2.8.1, < 3.0)
@@ -80,27 +97,28 @@ GEM
google-cloud-core (1.5.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
- google-cloud-env (1.3.0)
- faraday (~> 0.11)
- google-cloud-errors (1.0.0)
- google-cloud-storage (1.25.1)
+ google-cloud-env (1.3.2)
+ faraday (>= 0.17.3, < 2.0)
+ google-cloud-errors (1.0.1)
+ google-cloud-storage (1.26.2)
addressable (~> 2.5)
digest-crc (~> 0.4)
google-api-client (~> 0.33)
google-cloud-core (~> 1.2)
googleauth (~> 0.9)
mini_mime (~> 1.0)
- googleauth (0.10.0)
- faraday (~> 0.12)
+ googleauth (0.12.0)
+ faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
- signet (~> 0.12)
+ signet (~> 0.14)
highline (1.7.10)
http-cookie (1.0.3)
domain_name (~> 0.5)
httpclient (2.8.3)
+ jmespath (1.4.0)
json (2.3.0)
jwt (2.1.0)
memoist (0.16.2)
@@ -114,7 +132,7 @@ GEM
naturally (2.2.0)
nokogiri (1.10.7)
mini_portile2 (~> 2.4.0)
- os (1.0.1)
+ os (1.1.0)
plist (3.5.0)
public_suffix (2.0.5)
representable (3.0.4)
@@ -125,12 +143,12 @@ GEM
rouge (2.0.7)
rubyzip (1.3.0)
security (0.1.3)
- signet (0.12.0)
+ signet (0.14.0)
addressable (~> 2.3)
- faraday (~> 0.9)
+ faraday (>= 0.17.3, < 2.0)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
- simctl (1.6.7)
+ simctl (1.6.8)
CFPropertyList
naturally
slack-notifier (2.3.2)
@@ -141,17 +159,17 @@ GEM
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
- tty-cursor (0.7.0)
- tty-screen (0.7.0)
- tty-spinner (0.9.2)
+ tty-cursor (0.7.1)
+ tty-screen (0.8.0)
+ tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)
unf (0.1.4)
unf_ext
- unf_ext (0.0.7.6)
- unicode-display_width (1.6.1)
+ unf_ext (0.0.7.7)
+ unicode-display_width (1.7.0)
word_wrap (1.0.0)
- xcodeproj (1.14.0)
+ xcodeproj (1.16.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index 5f74883803..cd31df316a 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -10,7 +10,6 @@ using Microsoft.Win32;
using osu.Desktop.Overlays;
using osu.Framework.Platform;
using osu.Game;
-using osuTK.Input;
using osu.Desktop.Updater;
using osu.Framework;
using osu.Framework.Logging;
@@ -122,21 +121,27 @@ namespace osu.Desktop
{
base.SetHost(host);
- if (host.Window is DesktopGameWindow desktopWindow)
+ switch (host.Window)
{
- desktopWindow.CursorState |= CursorState.Hidden;
+ // Legacy osuTK DesktopGameWindow
+ case DesktopGameWindow desktopGameWindow:
+ desktopGameWindow.CursorState |= CursorState.Hidden;
+ desktopGameWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico"));
+ desktopGameWindow.Title = Name;
+ desktopGameWindow.FileDrop += (_, e) => fileDrop(e.FileNames);
+ break;
- desktopWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico"));
- desktopWindow.Title = Name;
-
- desktopWindow.FileDrop += fileDrop;
+ // SDL2 DesktopWindow
+ case DesktopWindow desktopWindow:
+ desktopWindow.CursorState.Value |= CursorState.Hidden;
+ desktopWindow.Title = Name;
+ desktopWindow.DragDrop += f => fileDrop(new[] { f });
+ break;
}
}
- private void fileDrop(object sender, FileDropEventArgs e)
+ private void fileDrop(string[] filePaths)
{
- var filePaths = e.FileNames;
-
var firstExtension = Path.GetExtension(filePaths.First());
if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return;
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index c34e1e1221..7a99c70999 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -30,6 +30,10 @@
+
+
+
+
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit0@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit0@2x.png
new file mode 100644
index 0000000000..2e7b9bc34f
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit0@2x.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit100@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit100@2x.png
new file mode 100644
index 0000000000..27ca7f8b42
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit100@2x.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit200@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit200@2x.png
new file mode 100644
index 0000000000..24ad926375
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit200@2x.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300@2x.png
new file mode 100644
index 0000000000..098561f980
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300@2x.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300g-0@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300g-0@2x.png
new file mode 100644
index 0000000000..7e6501d1be
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300g-0@2x.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300g-1@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300g-1@2x.png
new file mode 100644
index 0000000000..f17b2b1e73
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300g-1@2x.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit50@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit50@2x.png
new file mode 100644
index 0000000000..1afec2f4a9
Binary files /dev/null and b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit50@2x.png differ
diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini
index 56564776b3..941abac1da 100644
--- a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini
+++ b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini
@@ -1,6 +1,12 @@
[General]
-Version: 2.4
+Version: 2.5
[Mania]
Keys: 4
-ColumnLineWidth: 3,1,3,1,1
\ No newline at end of file
+ColumnLineWidth: 3,1,3,1,1
+Hit0: mania/hit0
+Hit50: mania/hit50
+Hit100: mania/hit100
+Hit200: mania/hit200
+Hit300: mania/hit300
+Hit300g: mania/hit300g
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs
index 497b80950a..a4d4ec50f8 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs
@@ -6,6 +6,7 @@ using System.Linq;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
@@ -16,14 +17,19 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{
public TestSceneDrawableJudgement()
{
+ var hitWindows = new ManiaHitWindows();
+
foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1))
{
- AddStep("Show " + result.GetDescription(), () => SetContents(() =>
- new DrawableManiaJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- }));
+ if (hitWindows.IsHitResultAllowed(result))
+ {
+ AddStep("Show " + result.GetDescription(), () => SetContents(() =>
+ new DrawableManiaJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }));
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs
index e64178083a..74a983fac8 100644
--- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs
@@ -11,6 +11,7 @@ using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Skinning;
+using System.Collections.Generic;
namespace osu.Game.Rulesets.Mania.Skinning
{
@@ -19,6 +20,36 @@ namespace osu.Game.Rulesets.Mania.Skinning
private readonly ISkin source;
private readonly ManiaBeatmap beatmap;
+ ///
+ /// Mapping of to their corresponding
+ /// value.
+ ///
+ private static readonly IReadOnlyDictionary hitresult_mapping
+ = new Dictionary
+ {
+ { HitResult.Perfect, LegacyManiaSkinConfigurationLookups.Hit300g },
+ { HitResult.Great, LegacyManiaSkinConfigurationLookups.Hit300 },
+ { HitResult.Good, LegacyManiaSkinConfigurationLookups.Hit200 },
+ { HitResult.Ok, LegacyManiaSkinConfigurationLookups.Hit100 },
+ { HitResult.Meh, LegacyManiaSkinConfigurationLookups.Hit50 },
+ { HitResult.Miss, LegacyManiaSkinConfigurationLookups.Hit0 }
+ };
+
+ ///
+ /// Mapping of to their corresponding
+ /// default filenames.
+ ///
+ private static readonly IReadOnlyDictionary default_hitresult_skin_filenames
+ = new Dictionary
+ {
+ { HitResult.Perfect, "mania-hit300g" },
+ { HitResult.Great, "mania-hit300" },
+ { HitResult.Good, "mania-hit200" },
+ { HitResult.Ok, "mania-hit100" },
+ { HitResult.Meh, "mania-hit50" },
+ { HitResult.Miss, "mania-hit0" }
+ };
+
private Lazy isLegacySkin;
///
@@ -50,7 +81,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
switch (component)
{
case GameplaySkinComponent resultComponent:
- return getResult(resultComponent);
+ return getResult(resultComponent.Component);
case ManiaSkinComponent maniaComponent:
if (!isLegacySkin.Value || !hasKeyTexture.Value)
@@ -95,30 +126,13 @@ namespace osu.Game.Rulesets.Mania.Skinning
return null;
}
- private Drawable getResult(GameplaySkinComponent resultComponent)
+ private Drawable getResult(HitResult result)
{
- switch (resultComponent.Component)
- {
- case HitResult.Miss:
- return this.GetAnimation("mania-hit0", true, true);
+ string filename = GetConfig(
+ new ManiaSkinConfigurationLookup(hitresult_mapping[result])
+ )?.Value ?? default_hitresult_skin_filenames[result];
- case HitResult.Meh:
- return this.GetAnimation("mania-hit50", true, true);
-
- case HitResult.Ok:
- return this.GetAnimation("mania-hit100", true, true);
-
- case HitResult.Good:
- return this.GetAnimation("mania-hit200", true, true);
-
- case HitResult.Great:
- return this.GetAnimation("mania-hit300", true, true);
-
- case HitResult.Perfect:
- return this.GetAnimation("mania-hit300g", true, true);
- }
-
- return null;
+ return this.GetAnimation(filename, true, true);
}
public Texture GetTexture(string componentName) => source.GetTexture(componentName);
diff --git a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs
new file mode 100644
index 0000000000..a97566ba7b
--- /dev/null
+++ b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.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 NUnit.Framework;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Screens.Play;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Tests.Gameplay
+{
+ public class TestSceneGameplayClockContainer : OsuTestScene
+ {
+ [Test]
+ public void TestStartThenElapsedTime()
+ {
+ GameplayClockContainer gcc = null;
+
+ AddStep("create container", () => Add(gcc = new GameplayClockContainer(CreateWorkingBeatmap(new OsuRuleset().RulesetInfo), Array.Empty(), 0)));
+ AddStep("start track", () => gcc.Start());
+ AddUntilStep("elapsed greater than zero", () => gcc.GameplayClock.ElapsedFrameTime > 0);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
index 84506739ab..2c85c4809b 100644
--- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.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 System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
@@ -10,7 +11,12 @@ using osu.Framework.Audio.Sample;
using osu.Framework.IO.Stores;
using osu.Framework.Testing;
using osu.Game.Audio;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Screens.Play;
using osu.Game.Skinning;
+using osu.Game.Storyboards;
+using osu.Game.Storyboards.Drawables;
using osu.Game.Tests.Resources;
using osu.Game.Tests.Visual;
@@ -43,6 +49,27 @@ namespace osu.Game.Tests.Gameplay
AddAssert("sample is non-null", () => channel != null);
}
+ [Test]
+ public void TestSamplePlaybackAtZero()
+ {
+ GameplayClockContainer gameplayContainer = null;
+ DrawableStoryboardSample sample = null;
+
+ AddStep("create container", () =>
+ {
+ Add(gameplayContainer = new GameplayClockContainer(CreateWorkingBeatmap(new OsuRuleset().RulesetInfo), Array.Empty(), 0));
+
+ gameplayContainer.Add(sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1))
+ {
+ Clock = gameplayContainer.GameplayClock
+ });
+ });
+
+ AddStep("start time", () => gameplayContainer.Start());
+
+ AddUntilStep("sample playback succeeded", () => sample.LifetimeEnd < double.MaxValue);
+ }
+
private class TestSkin : LegacySkin
{
public TestSkin(string resourceName, AudioManager audioManager)
diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
index d601f40afe..19294d12fc 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
@@ -19,6 +19,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Scoring;
using osu.Game.Screens;
@@ -27,6 +28,7 @@ using osu.Game.Screens.Play;
using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Select;
+using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Resources;
using osu.Game.Users;
using osuTK;
@@ -186,9 +188,15 @@ namespace osu.Game.Tests.Visual.Background
public void TestTransition()
{
performFullSetup();
+
FadeAccessibleResults results = null;
- AddStep("Transition to Results", () => player.Push(results =
- new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } })));
+
+ AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(new ScoreInfo
+ {
+ User = new User { Username = "osu!" },
+ Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo
+ })));
+
AddUntilStep("Wait for results is current", () => results.IsCurrentScreen());
AddUntilStep("Screen is undimmed, original background retained", () =>
songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect());
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs
new file mode 100644
index 0000000000..9fc7c336cb
--- /dev/null
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs
@@ -0,0 +1,124 @@
+// 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.Collections.Generic;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
+using osu.Game.Screens.Multi.Ranking;
+using osu.Game.Tests.Beatmaps;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual.Multiplayer
+{
+ public class TestSceneTimeshiftResultsScreen : ScreenTestScene
+ {
+ private bool roomsReceived;
+
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ roomsReceived = false;
+ bindHandler();
+ });
+
+ [Test]
+ public void TestShowResultsWithScore()
+ {
+ createResults(new TestScoreInfo(new OsuRuleset().RulesetInfo));
+ AddWaitStep("wait for display", 5);
+ }
+
+ [Test]
+ public void TestShowResultsNullScore()
+ {
+ createResults(null);
+ AddWaitStep("wait for display", 5);
+ }
+
+ [Test]
+ public void TestShowResultsNullScoreWithDelay()
+ {
+ AddStep("bind delayed handler", () => bindHandler(3000));
+ createResults(null);
+ AddUntilStep("wait for rooms to be received", () => roomsReceived);
+ AddWaitStep("wait for display", 5);
+ }
+
+ private void createResults(ScoreInfo score)
+ {
+ AddStep("load results", () =>
+ {
+ LoadScreen(new TimeshiftResultsScreen(score, 1, new PlaylistItem
+ {
+ Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
+ Ruleset = { Value = new OsuRuleset().RulesetInfo }
+ }));
+ });
+ }
+
+ private void bindHandler(double delay = 0)
+ {
+ var roomScores = new List();
+
+ for (int i = 0; i < 10; i++)
+ {
+ roomScores.Add(new RoomScore
+ {
+ ID = i,
+ Accuracy = 0.9 - 0.01 * i,
+ EndedAt = DateTimeOffset.Now.Subtract(TimeSpan.FromHours(i)),
+ Passed = true,
+ Rank = ScoreRank.B,
+ MaxCombo = 999,
+ TotalScore = 999999 - i * 1000,
+ User = new User
+ {
+ Id = 2,
+ Username = $"peppy{i}",
+ CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
+ },
+ Statistics =
+ {
+ { HitResult.Miss, 1 },
+ { HitResult.Meh, 50 },
+ { HitResult.Good, 100 },
+ { HitResult.Great, 300 },
+ }
+ });
+ }
+
+ ((DummyAPIAccess)API).HandleRequest = request =>
+ {
+ switch (request)
+ {
+ case GetRoomPlaylistScoresRequest r:
+ if (delay == 0)
+ success();
+ else
+ {
+ Task.Run(async () =>
+ {
+ await Task.Delay(TimeSpan.FromMilliseconds(delay));
+ Schedule(success);
+ });
+ }
+
+ void success()
+ {
+ r.TriggerSuccess(new RoomPlaylistScores { Scores = roomScores });
+ roomsReceived = true;
+ }
+
+ break;
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs
index 69511b85c0..7be44a62de 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs
@@ -4,7 +4,6 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
-using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -33,7 +32,10 @@ namespace osu.Game.Tests.Visual.Ranking
{
var author = new User { Username = "mapper_name" };
- AddStep("show example score", () => showPanel(createTestBeatmap(author), new TestScoreInfo(new OsuRuleset().RulesetInfo)));
+ AddStep("show example score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo)
+ {
+ Beatmap = createTestBeatmap(author)
+ }));
AddAssert("mapper name present", () => this.ChildrenOfType().Any(spriteText => spriteText.Text == "mapper_name"));
}
@@ -41,38 +43,34 @@ namespace osu.Game.Tests.Visual.Ranking
[Test]
public void TestMapWithUnknownMapper()
{
- AddStep("show example score", () => showPanel(createTestBeatmap(null), new TestScoreInfo(new OsuRuleset().RulesetInfo)));
+ AddStep("show example score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo)
+ {
+ Beatmap = createTestBeatmap(null)
+ }));
AddAssert("mapped by text not present", () =>
this.ChildrenOfType().All(spriteText => !containsAny(spriteText.Text, "mapped", "by")));
}
- private void showPanel(WorkingBeatmap workingBeatmap, ScoreInfo score)
- {
- Child = new ExpandedPanelMiddleContentContainer(workingBeatmap, score);
- }
+ private void showPanel(ScoreInfo score) => Child = new ExpandedPanelMiddleContentContainer(score);
- private WorkingBeatmap createTestBeatmap(User author)
+ private BeatmapInfo createTestBeatmap(User author)
{
- var beatmap = new TestBeatmap(rulesetStore.GetRuleset(0));
+ var beatmap = new TestBeatmap(rulesetStore.GetRuleset(0)).BeatmapInfo;
+
beatmap.Metadata.Author = author;
beatmap.Metadata.Title = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap title";
beatmap.Metadata.Artist = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap artist";
- return new TestWorkingBeatmap(beatmap);
+ return beatmap;
}
private bool containsAny(string text, params string[] stringsToMatch) => stringsToMatch.Any(text.Contains);
private class ExpandedPanelMiddleContentContainer : Container
{
- [Cached]
- private Bindable workingBeatmap { get; set; }
-
- public ExpandedPanelMiddleContentContainer(WorkingBeatmap beatmap, ScoreInfo score)
+ public ExpandedPanelMiddleContentContainer(ScoreInfo score)
{
- workingBeatmap = new Bindable(beatmap);
-
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
Size = new Vector2(ScorePanel.EXPANDED_WIDTH, 700);
diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
index a7e2dbeccb..f7d66ca5cf 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
@@ -38,13 +38,9 @@ namespace osu.Game.Tests.Visual.SongSelect
public class TestScenePlaySongSelect : ScreenTestScene
{
private BeatmapManager manager;
-
private RulesetStore rulesets;
-
private MusicController music;
-
private WorkingBeatmap defaultBeatmap;
-
private TestSongSelect songSelect;
[BackgroundDependencyLoader]
@@ -308,15 +304,13 @@ namespace osu.Game.Tests.Visual.SongSelect
AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap);
- var sortMode = config.GetBindable(OsuSetting.SongSelectSortingMode);
-
- AddStep(@"Sort by Artist", delegate { sortMode.Value = SortMode.Artist; });
- AddStep(@"Sort by Title", delegate { sortMode.Value = SortMode.Title; });
- AddStep(@"Sort by Author", delegate { sortMode.Value = SortMode.Author; });
- AddStep(@"Sort by DateAdded", delegate { sortMode.Value = SortMode.DateAdded; });
- AddStep(@"Sort by BPM", delegate { sortMode.Value = SortMode.BPM; });
- AddStep(@"Sort by Length", delegate { sortMode.Value = SortMode.Length; });
- AddStep(@"Sort by Difficulty", delegate { sortMode.Value = SortMode.Difficulty; });
+ 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));
}
[Test]
diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs
index 78bb66d553..7b1a174c1e 100644
--- a/osu.Game.Tournament/TournamentGame.cs
+++ b/osu.Game.Tournament/TournamentGame.cs
@@ -1,11 +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.Drawing;
using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Configuration;
using osu.Framework.Graphics;
+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.Game.Graphics;
+using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tournament
@@ -21,19 +29,87 @@ namespace osu.Game.Tournament
public static readonly Color4 ELEMENT_FOREGROUND_COLOUR = Color4Extensions.FromHex("#000");
public static readonly Color4 TEXT_COLOUR = Color4Extensions.FromHex("#fff");
+ private Drawable heightWarning;
+ private Bindable windowSize;
- protected override void LoadComplete()
+ [BackgroundDependencyLoader]
+ private void load(FrameworkConfigManager frameworkConfig)
{
- base.LoadComplete();
-
- Add(new OsuContextMenuContainer
+ windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize);
+ windowSize.BindValueChanged(size => ScheduleAfterChildren(() =>
{
- RelativeSizeAxes = Axes.Both,
- Child = new TournamentSceneManager()
- });
+ var minWidth = (int)(size.NewValue.Height / 768f * TournamentSceneManager.REQUIRED_WIDTH) - 1;
- // we don't want to show the menu cursor as it would appear on stream output.
- MenuCursorContainer.Cursor.Alpha = 0;
+ heightWarning.Alpha = size.NewValue.Width < minWidth ? 1 : 0;
+ }), true);
+
+ AddRange(new[]
+ {
+ new Container
+ {
+ CornerRadius = 10,
+ Depth = float.MinValue,
+ Position = new Vector2(5),
+ Masking = true,
+ AutoSizeAxes = Axes.Both,
+ Anchor = Anchor.BottomRight,
+ Origin = Anchor.BottomRight,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = OsuColour.Gray(0.2f),
+ RelativeSizeAxes = Axes.Both,
+ },
+ new TourneyButton
+ {
+ Text = "Save Changes",
+ Width = 140,
+ Height = 50,
+ Padding = new MarginPadding
+ {
+ Top = 10,
+ Left = 10,
+ },
+ Margin = new MarginPadding
+ {
+ Right = 10,
+ Bottom = 10,
+ },
+ Action = SaveChanges,
+ },
+ }
+ },
+ heightWarning = new Container
+ {
+ Masking = true,
+ CornerRadius = 5,
+ Depth = float.MinValue,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ AutoSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.Red,
+ RelativeSizeAxes = Axes.Both,
+ },
+ new TournamentSpriteText
+ {
+ Text = "Please make the window wider",
+ Font = OsuFont.Torus.With(weight: FontWeight.Bold),
+ Colour = Color4.White,
+ Padding = new MarginPadding(20)
+ }
+ }
+ },
+ new OsuContextMenuContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new TournamentSceneManager()
+ }
+ });
}
}
}
diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs
index 928c6deb3c..718c8ee644 100644
--- a/osu.Game.Tournament/TournamentGameBase.cs
+++ b/osu.Game.Tournament/TournamentGameBase.cs
@@ -2,34 +2,25 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Drawing;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using osu.Framework.Allocation;
-using osu.Framework.Bindables;
-using osu.Framework.Configuration;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
using osu.Framework.IO.Stores;
using osu.Framework.Platform;
using osu.Game.Beatmaps;
-using osu.Game.Graphics;
using osu.Game.Online.API.Requests;
using osu.Game.Tournament.IPC;
using osu.Game.Tournament.Models;
using osu.Game.Users;
-using osuTK;
-using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Tournament
{
[Cached(typeof(TournamentGameBase))]
- public abstract class TournamentGameBase : OsuGameBase
+ public class TournamentGameBase : OsuGameBase
{
private const string bracket_filename = "bracket.json";
@@ -40,19 +31,15 @@ namespace osu.Game.Tournament
private TournamentStorage tournamentStorage;
private DependencyContainer dependencies;
-
- private Bindable windowSize;
private FileBasedIPC ipc;
- private Drawable heightWarning;
-
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
return dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
}
[BackgroundDependencyLoader]
- private void load(Storage storage, FrameworkConfigManager frameworkConfig)
+ private void load(Storage storage)
{
Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly));
@@ -62,83 +49,12 @@ namespace osu.Game.Tournament
this.storage = storage;
- windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize);
- windowSize.BindValueChanged(size => ScheduleAfterChildren(() =>
- {
- var minWidth = (int)(size.NewValue.Height / 768f * TournamentSceneManager.REQUIRED_WIDTH) - 1;
-
- heightWarning.Alpha = size.NewValue.Width < minWidth ? 1 : 0;
- }), true);
-
readBracket();
ladder.CurrentMatch.Value = ladder.Matches.FirstOrDefault(p => p.Current.Value);
dependencies.CacheAs(ipc = new FileBasedIPC());
Add(ipc);
-
- AddRange(new[]
- {
- new Container
- {
- CornerRadius = 10,
- Depth = float.MinValue,
- Position = new Vector2(5),
- Masking = true,
- AutoSizeAxes = Axes.Both,
- Anchor = Anchor.BottomRight,
- Origin = Anchor.BottomRight,
- Children = new Drawable[]
- {
- new Box
- {
- Colour = OsuColour.Gray(0.2f),
- RelativeSizeAxes = Axes.Both,
- },
- new TourneyButton
- {
- Text = "Save Changes",
- Width = 140,
- Height = 50,
- Padding = new MarginPadding
- {
- Top = 10,
- Left = 10,
- },
- Margin = new MarginPadding
- {
- Right = 10,
- Bottom = 10,
- },
- Action = SaveChanges,
- },
- }
- },
- heightWarning = new Container
- {
- Masking = true,
- CornerRadius = 5,
- Depth = float.MinValue,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- AutoSizeAxes = Axes.Both,
- Children = new Drawable[]
- {
- new Box
- {
- Colour = Color4.Red,
- RelativeSizeAxes = Axes.Both,
- },
- new TournamentSpriteText
- {
- Text = "Please make the window wider",
- Font = OsuFont.Torus.With(weight: FontWeight.Bold),
- Colour = Color4.White,
- Padding = new MarginPadding(20)
- }
- }
- },
- });
}
private void readBracket()
@@ -313,6 +229,8 @@ namespace osu.Game.Tournament
protected override void LoadComplete()
{
MenuCursorContainer.Cursor.AlwaysPresent = true; // required for tooltip display
+
+ // we don't want to show the menu cursor as it would appear on stream output.
MenuCursorContainer.Cursor.Alpha = 0;
base.LoadComplete();
diff --git a/osu.Game/Graphics/UserInterface/LoadingSpinner.cs b/osu.Game/Graphics/UserInterface/LoadingSpinner.cs
index 4f4607c114..8174c4d5fe 100644
--- a/osu.Game/Graphics/UserInterface/LoadingSpinner.cs
+++ b/osu.Game/Graphics/UserInterface/LoadingSpinner.cs
@@ -17,6 +17,8 @@ namespace osu.Game.Graphics.UserInterface
{
private readonly SpriteIcon spinner;
+ protected override bool StartHidden => true;
+
protected Container MainContents;
public const float TRANSITION_DURATION = 500;
diff --git a/osu.Game/Online/API/Requests/GetRoomPlaylistScoresRequest.cs b/osu.Game/Online/API/Requests/GetRoomPlaylistScoresRequest.cs
new file mode 100644
index 0000000000..38f852870b
--- /dev/null
+++ b/osu.Game/Online/API/Requests/GetRoomPlaylistScoresRequest.cs
@@ -0,0 +1,28 @@
+// 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;
+
+namespace osu.Game.Online.API.Requests
+{
+ public class GetRoomPlaylistScoresRequest : APIRequest
+ {
+ private readonly int roomId;
+ private readonly int playlistItemId;
+
+ public GetRoomPlaylistScoresRequest(int roomId, int playlistItemId)
+ {
+ this.roomId = roomId;
+ this.playlistItemId = playlistItemId;
+ }
+
+ protected override string Target => $@"rooms/{roomId}/playlist/{playlistItemId}/scores";
+ }
+
+ public class RoomPlaylistScores
+ {
+ [JsonProperty("scores")]
+ public List Scores { get; set; }
+ }
+}
diff --git a/osu.Game/Online/API/Requests/SubmitRoomScoreRequest.cs b/osu.Game/Online/API/Requests/SubmitRoomScoreRequest.cs
index 50b62cd6ed..8eb2952159 100644
--- a/osu.Game/Online/API/Requests/SubmitRoomScoreRequest.cs
+++ b/osu.Game/Online/API/Requests/SubmitRoomScoreRequest.cs
@@ -8,7 +8,7 @@ using osu.Game.Scoring;
namespace osu.Game.Online.API.Requests
{
- public class SubmitRoomScoreRequest : APIRequest
+ public class SubmitRoomScoreRequest : APIRequest
{
private readonly int scoreId;
private readonly int roomId;
diff --git a/osu.Game/Online/API/RoomScore.cs b/osu.Game/Online/API/RoomScore.cs
new file mode 100644
index 0000000000..3c7f8c9833
--- /dev/null
+++ b/osu.Game/Online/API/RoomScore.cs
@@ -0,0 +1,75 @@
+// 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.Collections.Generic;
+using System.Linq;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
+using osu.Game.Users;
+
+namespace osu.Game.Online.API
+{
+ public class RoomScore
+ {
+ [JsonProperty("id")]
+ public int ID { get; set; }
+
+ [JsonProperty("user")]
+ public User User { get; set; }
+
+ [JsonProperty("rank")]
+ [JsonConverter(typeof(StringEnumConverter))]
+ public ScoreRank Rank { get; set; }
+
+ [JsonProperty("total_score")]
+ public long TotalScore { get; set; }
+
+ [JsonProperty("accuracy")]
+ public double Accuracy { get; set; }
+
+ [JsonProperty("max_combo")]
+ public int MaxCombo { get; set; }
+
+ [JsonProperty("mods")]
+ public APIMod[] Mods { get; set; }
+
+ [JsonProperty("statistics")]
+ public Dictionary Statistics = new Dictionary();
+
+ [JsonProperty("passed")]
+ public bool Passed { get; set; }
+
+ [JsonProperty("ended_at")]
+ public DateTimeOffset EndedAt { get; set; }
+
+ public ScoreInfo CreateScoreInfo(PlaylistItem playlistItem)
+ {
+ var rulesetInstance = playlistItem.Ruleset.Value.CreateInstance();
+
+ var scoreInfo = new ScoreInfo
+ {
+ OnlineScoreID = ID,
+ TotalScore = TotalScore,
+ MaxCombo = MaxCombo,
+ Beatmap = playlistItem.Beatmap.Value,
+ BeatmapInfoID = playlistItem.BeatmapID,
+ Ruleset = playlistItem.Ruleset.Value,
+ RulesetID = playlistItem.RulesetID,
+ Statistics = Statistics,
+ User = User,
+ Accuracy = Accuracy,
+ Date = EndedAt,
+ Hash = string.Empty, // todo: temporary?
+ Rank = Rank,
+ Mods = Mods?.Select(m => m.ToMod(rulesetInstance)).ToArray() ?? Array.Empty()
+ };
+
+ return scoreInfo;
+ }
+ }
+}
diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs
index c1f3e357a1..df059eef7d 100644
--- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs
+++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs
@@ -26,6 +26,9 @@ namespace osu.Game.Rulesets.Mods
[SettingSource("Final rate", "The final speed to ramp to")]
public abstract BindableNumber FinalRate { get; }
+ [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
+ public abstract BindableBool AdjustPitch { get; }
+
public override string SettingDescription => $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x";
private double finalRateTime;
@@ -43,15 +46,16 @@ namespace osu.Game.Rulesets.Mods
protected ModTimeRamp()
{
// for preview purpose at song select. eventually we'll want to be able to update every frame.
- FinalRate.BindValueChanged(val => applyAdjustment(1), true);
+ FinalRate.BindValueChanged(val => applyRateAdjustment(1), true);
+ AdjustPitch.BindValueChanged(applyPitchAdjustment);
}
public void ApplyToTrack(Track track)
{
this.track = track;
- track.AddAdjustment(AdjustableProperty.Frequency, SpeedChange);
FinalRate.TriggerChange();
+ AdjustPitch.TriggerChange();
}
public virtual void ApplyToBeatmap(IBeatmap beatmap)
@@ -66,14 +70,25 @@ namespace osu.Game.Rulesets.Mods
public virtual void Update(Playfield playfield)
{
- applyAdjustment((track.CurrentTime - beginRampTime) / finalRateTime);
+ applyRateAdjustment((track.CurrentTime - beginRampTime) / finalRateTime);
}
///
/// Adjust the rate along the specified ramp
///
/// The amount of adjustment to apply (from 0..1).
- private void applyAdjustment(double amount) =>
+ private void applyRateAdjustment(double amount) =>
SpeedChange.Value = InitialRate.Value + (FinalRate.Value - InitialRate.Value) * Math.Clamp(amount, 0, 1);
+
+ private void applyPitchAdjustment(ValueChangedEvent adjustPitchSetting)
+ {
+ // remove existing old adjustment
+ track.RemoveAdjustment(adjustmentForPitchSetting(adjustPitchSetting.OldValue), SpeedChange);
+
+ track.AddAdjustment(adjustmentForPitchSetting(adjustPitchSetting.NewValue), SpeedChange);
+ }
+
+ private AdjustableProperty adjustmentForPitchSetting(bool adjustPitchSettingValue)
+ => adjustPitchSettingValue ? AdjustableProperty.Frequency : AdjustableProperty.Tempo;
}
}
diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs
index 5e634ac434..679b50057b 100644
--- a/osu.Game/Rulesets/Mods/ModWindDown.cs
+++ b/osu.Game/Rulesets/Mods/ModWindDown.cs
@@ -37,6 +37,13 @@ namespace osu.Game.Rulesets.Mods
Precision = 0.01,
};
+ [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
+ public override BindableBool AdjustPitch { get; } = new BindableBool
+ {
+ Default = true,
+ Value = true
+ };
+
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindUp)).ToArray();
}
}
diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs
index 74c6fc22d3..b733bf423e 100644
--- a/osu.Game/Rulesets/Mods/ModWindUp.cs
+++ b/osu.Game/Rulesets/Mods/ModWindUp.cs
@@ -37,6 +37,13 @@ namespace osu.Game.Rulesets.Mods
Precision = 0.01,
};
+ [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
+ public override BindableBool AdjustPitch { get; } = new BindableBool
+ {
+ Default = true,
+ Value = true
+ };
+
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindDown)).ToArray();
}
}
diff --git a/osu.Game/Scoring/ScoreStore.cs b/osu.Game/Scoring/ScoreStore.cs
index 9627481f4d..f5c5cd5dad 100644
--- a/osu.Game/Scoring/ScoreStore.cs
+++ b/osu.Game/Scoring/ScoreStore.cs
@@ -18,6 +18,8 @@ namespace osu.Game.Scoring
protected override IQueryable AddIncludesForConsumption(IQueryable query)
=> base.AddIncludesForConsumption(query)
.Include(s => s.Beatmap)
+ .Include(s => s.Beatmap).ThenInclude(b => b.Metadata)
+ .Include(s => s.Beatmap).ThenInclude(b => b.BeatmapSet).ThenInclude(s => s.Metadata)
.Include(s => s.Ruleset);
}
}
diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
index d07cffff0c..cc417bbb10 100644
--- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
@@ -44,8 +44,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
private readonly BindableList selectedHitObjects = new BindableList();
- public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
-
[Resolved(canBeNull: true)]
private IPositionSnapProvider snapProvider { get; set; }
diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
index 0b5d8262fd..e1f311f1b8 100644
--- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
@@ -11,6 +11,7 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
+using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components
{
@@ -26,6 +27,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
private readonly Container placementBlueprintContainer;
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
+
private InputManager inputManager;
private readonly IEnumerable drawableHitObjects;
diff --git a/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs b/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs
index 71cabd8b50..8d8d4cc404 100644
--- a/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs
+++ b/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs
@@ -80,7 +80,7 @@ namespace osu.Game.Screens.Multi.Components
},
new Drawable[]
{
- Content = new Container { Margin = new MarginPadding { Top = 5 } }
+ Content = new Container { Padding = new MarginPadding { Top = 5 } }
}
}
};
diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs
index bbfbaf81af..f837a407a5 100644
--- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs
+++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -10,12 +11,16 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Screens;
using osu.Game.Audio;
using osu.Game.Beatmaps;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.API;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.GameTypes;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Multi.Components;
using osu.Game.Screens.Multi.Match.Components;
using osu.Game.Screens.Multi.Play;
+using osu.Game.Screens.Multi.Ranking;
+using osu.Game.Screens.Play;
using osu.Game.Screens.Select;
using Footer = osu.Game.Screens.Multi.Match.Components.Footer;
@@ -112,10 +117,36 @@ namespace osu.Game.Screens.Multi.Match
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = 5 },
- Child = new OverlinedPlaylist(true) // Temporarily always allow selection
+ Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
- SelectedItem = { BindTarget = SelectedItem }
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ new OverlinedPlaylist(true) // Temporarily always allow selection
+ {
+ RelativeSizeAxes = Axes.Both,
+ SelectedItem = { BindTarget = SelectedItem }
+ }
+ },
+ null,
+ new Drawable[]
+ {
+ new TriangleButton
+ {
+ RelativeSizeAxes = Axes.X,
+ Text = "Show beatmap results",
+ Action = showBeatmapResults
+ }
+ }
+ },
+ RowDimensions = new[]
+ {
+ new Dimension(),
+ new Dimension(GridSizeMode.Absolute, 5),
+ new Dimension(GridSizeMode.AutoSize)
+ }
}
},
new Container
@@ -162,6 +193,9 @@ namespace osu.Game.Screens.Multi.Match
};
}
+ [Resolved]
+ private IAPIProvider api { get; set; }
+
protected override void LoadComplete()
{
base.LoadComplete();
@@ -225,12 +259,18 @@ namespace osu.Game.Screens.Multi.Match
{
default:
case GameTypeTimeshift _:
- multiplayer?.Start(() => new TimeshiftPlayer(SelectedItem.Value)
+ multiplayer?.Push(new PlayerLoader(() => new TimeshiftPlayer(SelectedItem.Value)
{
Exited = () => leaderboardChatDisplay.RefreshScores()
- });
+ }));
break;
}
}
+
+ private void showBeatmapResults()
+ {
+ Debug.Assert(roomId.Value != null);
+ multiplayer?.Push(new TimeshiftResultsScreen(null, roomId.Value.Value, SelectedItem.Value, false));
+ }
}
}
diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs
index 863a28609b..e724152e08 100644
--- a/osu.Game/Screens/Multi/Multiplayer.cs
+++ b/osu.Game/Screens/Multi/Multiplayer.cs
@@ -1,7 +1,6 @@
// 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.Extensions.Color4Extensions;
@@ -24,7 +23,6 @@ using osu.Game.Screens.Multi.Lounge;
using osu.Game.Screens.Multi.Lounge.Components;
using osu.Game.Screens.Multi.Match;
using osu.Game.Screens.Multi.Match.Components;
-using osu.Game.Screens.Play;
using osuTK;
namespace osu.Game.Screens.Multi
@@ -197,18 +195,6 @@ namespace osu.Game.Screens.Multi
Logger.Log($"Polling adjusted (listing: {roomManager.TimeBetweenListingPolls}, selection: {roomManager.TimeBetweenSelectionPolls})");
}
- ///
- /// Push a to the main screen stack to begin gameplay.
- /// Generally called from a via DI resolution.
- ///
- public void Start(Func player)
- {
- if (!this.IsCurrentScreen())
- return;
-
- this.Push(new PlayerLoader(player));
- }
-
public void APIStateChanged(IAPIProvider api, APIState state)
{
if (state != APIState.Online)
diff --git a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs
index 7f58de29fb..cf0197d26b 100644
--- a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs
+++ b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs
@@ -14,7 +14,9 @@ using osu.Game.Online.API.Requests;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets;
using osu.Game.Scoring;
+using osu.Game.Screens.Multi.Ranking;
using osu.Game.Screens.Play;
+using osu.Game.Screens.Ranking;
namespace osu.Game.Screens.Multi.Play
{
@@ -88,23 +90,25 @@ namespace osu.Game.Screens.Multi.Play
return false;
}
- protected override ScoreInfo CreateScore()
+ protected override ResultsScreen CreateResults(ScoreInfo score)
{
- submitScore();
- return base.CreateScore();
+ Debug.Assert(roomId.Value != null);
+ return new TimeshiftResultsScreen(score, roomId.Value.Value, playlistItem);
}
- private void submitScore()
+ protected override ScoreInfo CreateScore()
{
var score = base.CreateScore();
-
score.TotalScore = (int)Math.Round(ScoreProcessor.GetStandardisedScore());
Debug.Assert(token != null);
var request = new SubmitRoomScoreRequest(token.Value, roomId.Value ?? 0, playlistItem.ID, score);
+ request.Success += s => score.OnlineScoreID = s.ID;
request.Failure += e => Logger.Error(e, "Failed to submit score");
api.Queue(request);
+
+ return score;
}
protected override void Dispose(bool isDisposing)
diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs
new file mode 100644
index 0000000000..5cafc974f1
--- /dev/null
+++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs
@@ -0,0 +1,61 @@
+// 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.Collections.Generic;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Scoring;
+using osu.Game.Screens.Ranking;
+
+namespace osu.Game.Screens.Multi.Ranking
+{
+ public class TimeshiftResultsScreen : ResultsScreen
+ {
+ private readonly int roomId;
+ private readonly PlaylistItem playlistItem;
+
+ private LoadingSpinner loadingLayer;
+
+ public TimeshiftResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem, bool allowRetry = true)
+ : base(score, allowRetry)
+ {
+ this.roomId = roomId;
+ this.playlistItem = playlistItem;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ AddInternal(loadingLayer = new LoadingLayer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ X = -10,
+ State = { Value = Score == null ? Visibility.Visible : Visibility.Hidden },
+ Padding = new MarginPadding { Bottom = TwoLayerButton.SIZE_EXTENDED.Y }
+ });
+ }
+
+ protected override APIRequest FetchScores(Action> scoresCallback)
+ {
+ var req = new GetRoomPlaylistScoresRequest(roomId, playlistItem.ID);
+
+ req.Success += r =>
+ {
+ scoresCallback?.Invoke(r.Scores.Where(s => s.ID != Score?.OnlineScoreID).Select(s => s.CreateScoreInfo(playlistItem)));
+ loadingLayer.Hide();
+ };
+
+ req.Failure += _ => loadingLayer.Hide();
+
+ return req;
+ }
+ }
+}
diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs
index 2f85d6ad1e..0653373c91 100644
--- a/osu.Game/Screens/Play/GameplayClockContainer.cs
+++ b/osu.Game/Screens/Play/GameplayClockContainer.cs
@@ -251,8 +251,9 @@ namespace osu.Game.Screens.Play
private class HardwareCorrectionOffsetClock : FramedOffsetClock
{
- // we always want to apply the same real-time offset, so it should be adjusted by the playback rate to achieve this.
- public override double CurrentTime => SourceTime + Offset * Rate;
+ // we always want to apply the same real-time offset, so it should be adjusted by the difference in playback rate (from realtime) to achieve this.
+ // base implementation already adds offset at 1.0 rate, so we only add the difference from that here.
+ public override double CurrentTime => base.CurrentTime + Offset * (Rate - 1);
public HardwareCorrectionOffsetClock(IClock source, bool processSource = true)
: base(source, processSource)
diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
index 81d5d113ae..01502c0913 100644
--- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
+++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
@@ -4,11 +4,9 @@
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
-using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
-using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
@@ -52,10 +50,10 @@ namespace osu.Game.Screens.Ranking.Expanded
}
[BackgroundDependencyLoader]
- private void load(Bindable working)
+ private void load()
{
- var beatmap = working.Value.BeatmapInfo;
- var metadata = beatmap.Metadata;
+ var beatmap = score.Beatmap;
+ var metadata = beatmap.BeatmapSet?.Metadata ?? beatmap.Metadata;
var creator = metadata.Author?.Username;
var topStatistics = new List
diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs
index c76d5c8784..4990ca8e60 100644
--- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs
+++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs
@@ -43,6 +43,12 @@ namespace osu.Game.Skinning
MinimumColumnWidth,
LeftStageImage,
RightStageImage,
- BottomStageImage
+ BottomStageImage,
+ Hit300g,
+ Hit300,
+ Hit200,
+ Hit100,
+ Hit50,
+ Hit0,
}
}
diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
index a988bd589f..0806676fde 100644
--- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
+++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
@@ -111,11 +111,10 @@ namespace osu.Game.Skinning
HandleColours(currentConfig, line);
break;
+ // Custom sprite paths
case string _ when pair.Key.StartsWith("NoteImage"):
- currentConfig.ImageLookups[pair.Key] = pair.Value;
- break;
-
case string _ when pair.Key.StartsWith("KeyImage"):
+ case string _ when pair.Key.StartsWith("Hit"):
currentConfig.ImageLookups[pair.Key] = pair.Value;
break;
}
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index 003fa24d5b..0b2b723440 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -257,6 +257,14 @@ namespace osu.Game.Skinning
case LegacyManiaSkinConfigurationLookups.RightLineWidth:
Debug.Assert(maniaLookup.TargetColumn != null);
return SkinUtils.As(new Bindable(existing.ColumnLineWidth[maniaLookup.TargetColumn.Value + 1]));
+
+ case LegacyManiaSkinConfigurationLookups.Hit0:
+ case LegacyManiaSkinConfigurationLookups.Hit50:
+ case LegacyManiaSkinConfigurationLookups.Hit100:
+ case LegacyManiaSkinConfigurationLookups.Hit200:
+ case LegacyManiaSkinConfigurationLookups.Hit300:
+ case LegacyManiaSkinConfigurationLookups.Hit300g:
+ return SkinUtils.As(getManiaImage(existing, maniaLookup.Lookup.ToString()));
}
return null;
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs
index f3f8308964..8292b02068 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs
@@ -51,7 +51,7 @@ namespace osu.Game.Storyboards.Drawables
LifetimeStart = sampleInfo.StartTime;
LifetimeEnd = double.MaxValue;
}
- else if (Time.Current - Time.Elapsed < sampleInfo.StartTime)
+ else if (Time.Current - Time.Elapsed <= sampleInfo.StartTime)
{
// We've passed the start time of the sample. We only play the sample if we're within an allowable range
// from the sample's start, to reduce layering if we've been fast-forwarded far into the future