diff --git a/osu.Android.props b/osu.Android.props
index 7378450c38..d4331a5e65 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,7 +51,7 @@
-
+
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs
index e14ad92842..449a6ff23d 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneTimingBasedNoteColouring.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.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Allocation;
@@ -13,6 +14,10 @@ using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Framework.Bindables;
+using osu.Framework.Testing;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Tests
{
@@ -22,14 +27,65 @@ namespace osu.Game.Rulesets.Mania.Tests
[Resolved]
private RulesetConfigCache configCache { get; set; }
- private readonly Bindable configTimingBasedNoteColouring = new Bindable();
+ private Bindable configTimingBasedNoteColouring;
- protected override void LoadComplete()
+ private ManualClock clock;
+ private DrawableManiaRuleset drawableRuleset;
+
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("setup hierarchy", () => Child = new Container
+ {
+ Clock = new FramedClock(clock = new ManualClock()),
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Children = new[]
+ {
+ drawableRuleset = (DrawableManiaRuleset)Ruleset.Value.CreateInstance().CreateDrawableRulesetWith(createTestBeatmap())
+ }
+ });
+ AddStep("retrieve config bindable", () =>
+ {
+ var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
+ configTimingBasedNoteColouring = config.GetBindable(ManiaRulesetSetting.TimingBasedNoteColouring);
+ });
+ }
+
+ [Test]
+ public void TestSimple()
+ {
+ AddStep("enable", () => configTimingBasedNoteColouring.Value = true);
+ AddStep("disable", () => configTimingBasedNoteColouring.Value = false);
+ }
+
+ [Test]
+ public void TestToggleOffScreen()
+ {
+ AddStep("enable", () => configTimingBasedNoteColouring.Value = true);
+
+ seekTo(10000);
+ AddStep("disable", () => configTimingBasedNoteColouring.Value = false);
+ seekTo(0);
+ AddAssert("all notes not coloured", () => this.ChildrenOfType().All(note => note.Colour == Colour4.White));
+
+ seekTo(10000);
+ AddStep("enable again", () => configTimingBasedNoteColouring.Value = true);
+ seekTo(0);
+ AddAssert("some notes coloured", () => this.ChildrenOfType().Any(note => note.Colour != Colour4.White));
+ }
+
+ private void seekTo(double time)
+ {
+ AddStep($"seek to {time}", () => clock.CurrentTime = time);
+ AddUntilStep("wait for seek", () => Precision.AlmostEquals(drawableRuleset.FrameStableClock.CurrentTime, time, 1));
+ }
+
+ private ManiaBeatmap createTestBeatmap()
{
const double beat_length = 500;
- var ruleset = new ManiaRuleset();
-
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 })
{
HitObjects =
@@ -45,7 +101,7 @@ namespace osu.Game.Rulesets.Mania.Tests
new Note { StartTime = beat_length }
},
ControlPointInfo = new ControlPointInfo(),
- BeatmapInfo = { Ruleset = ruleset.RulesetInfo },
+ BeatmapInfo = { Ruleset = Ruleset.Value },
};
foreach (var note in beatmap.HitObjects)
@@ -57,24 +113,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{
BeatLength = beat_length
});
-
- Child = new Container
- {
- Clock = new FramedClock(new ManualClock()),
- RelativeSizeAxes = Axes.Both,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Children = new[]
- {
- ruleset.CreateDrawableRulesetWith(beatmap)
- }
- };
-
- var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
- config.BindWith(ManiaRulesetSetting.TimingBasedNoteColouring, configTimingBasedNoteColouring);
-
- AddStep("Enable", () => configTimingBasedNoteColouring.Value = true);
- AddStep("Disable", () => configTimingBasedNoteColouring.Value = false);
+ return beatmap;
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
index 33d872dfb6..d53c28868d 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
@@ -66,6 +66,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
StartTimeBindable.BindValueChanged(_ => updateSnapColour(), true);
}
+ protected override void OnApply()
+ {
+ base.OnApply();
+ updateSnapColour();
+ }
+
protected override void OnDirectionChanged(ValueChangedEvent e)
{
base.OnDirectionChanged(e);
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index 8560a36fb4..a4bf8c92e3 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -64,6 +64,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsFalse(beatmapInfo.LetterboxInBreaks);
Assert.IsFalse(beatmapInfo.SpecialStyle);
Assert.IsFalse(beatmapInfo.WidescreenStoryboard);
+ Assert.IsFalse(beatmapInfo.SamplesMatchPlaybackRate);
Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown);
Assert.AreEqual(0, beatmapInfo.CountdownOffset);
}
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs
index 0f3d413a7d..a439555fde 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs
@@ -7,28 +7,19 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
-using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Dialog;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit;
-using osu.Game.Screens.Edit.Components.Menus;
-using osu.Game.Screens.Menu;
using osu.Game.Tests.Beatmaps.IO;
-using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing
{
- public class TestSceneDifficultySwitching : ScreenTestScene
+ public class TestSceneDifficultySwitching : EditorTestScene
{
- private BeatmapSetInfo importedBeatmapSet;
- private Editor editor;
+ protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
- // required for screen transitions to work properly
- // (see comment in EditorLoader.LogoArriving).
- [Cached]
- private OsuLogo logo = new OsuLogo
- {
- Alpha = 0
- };
+ protected override bool IsolateSavingFromDatabase => false;
[Resolved]
private OsuGameBase game { get; set; }
@@ -36,20 +27,18 @@ namespace osu.Game.Tests.Visual.Editing
[Resolved]
private BeatmapManager beatmaps { get; set; }
- [BackgroundDependencyLoader]
- private void load() => Add(logo);
+ private BeatmapSetInfo importedBeatmapSet;
- [SetUpSteps]
- public void SetUp()
+ public override void SetUpSteps()
{
AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result);
+ base.SetUpSteps();
+ }
- AddStep("set current beatmap", () => Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First()));
- AddStep("push loader", () => Stack.Push(new EditorLoader()));
-
- AddUntilStep("wait for editor push", () => Stack.CurrentScreen is Editor);
- AddStep("store editor", () => editor = (Editor)Stack.CurrentScreen);
- AddUntilStep("wait for editor to load", () => editor.IsLoaded);
+ protected override void LoadEditor()
+ {
+ Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First());
+ base.LoadEditor();
}
[Test]
@@ -72,11 +61,7 @@ namespace osu.Game.Tests.Visual.Editing
BeatmapInfo targetDifficulty = null;
PromptForSaveDialog saveDialog = null;
- AddStep("remove first hitobject", () =>
- {
- var editorBeatmap = editor.ChildrenOfType().Single();
- editorBeatmap.RemoveAt(0);
- });
+ AddStep("remove first hitobject", () => EditorBeatmap.RemoveAt(0));
AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo)));
switchToDifficulty(() => targetDifficulty);
@@ -105,11 +90,7 @@ namespace osu.Game.Tests.Visual.Editing
BeatmapInfo targetDifficulty = null;
PromptForSaveDialog saveDialog = null;
- AddStep("remove first hitobject", () =>
- {
- var editorBeatmap = editor.ChildrenOfType().Single();
- editorBeatmap.RemoveAt(0);
- });
+ AddStep("remove first hitobject", () => EditorBeatmap.RemoveAt(0));
AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo)));
switchToDifficulty(() => targetDifficulty);
@@ -132,34 +113,7 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("stack empty", () => Stack.CurrentScreen == null);
}
- private void switchToDifficulty(Func difficulty)
- {
- AddUntilStep("wait for menubar to load", () => editor.ChildrenOfType().Any());
- AddStep("open file menu", () =>
- {
- var menuBar = editor.ChildrenOfType().Single();
- var fileMenu = menuBar.ChildrenOfType().First();
- InputManager.MoveMouseTo(fileMenu);
- InputManager.Click(MouseButton.Left);
- });
-
- AddStep("open difficulty menu", () =>
- {
- var difficultySelector =
- editor.ChildrenOfType().Single(item => item.Item.Text.Value.ToString().Contains("Change difficulty"));
- InputManager.MoveMouseTo(difficultySelector);
- });
- AddWaitStep("wait for open", 3);
-
- AddStep("switch to target difficulty", () =>
- {
- var difficultyMenuItem =
- editor.ChildrenOfType()
- .Last(item => item.Item is DifficultyMenuItem difficultyItem && difficultyItem.Beatmap.Equals(difficulty.Invoke()));
- InputManager.MoveMouseTo(difficultyMenuItem);
- InputManager.Click(MouseButton.Left);
- });
- }
+ private void switchToDifficulty(Func difficulty) => AddStep("switch to difficulty", () => Editor.SwitchToDifficulty(difficulty.Invoke()));
private void confirmEditingBeatmap(Func targetDifficulty)
{
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs
index b6ae91844a..440d66ff9f 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs
@@ -11,6 +11,7 @@ using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
+using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Setup;
using osu.Game.Tests.Resources;
using SharpCompress.Archives;
@@ -55,6 +56,9 @@ namespace osu.Game.Tests.Visual.Editing
[Test]
public void TestExitWithoutSave()
{
+ EditorBeatmap editorBeatmap = null;
+
+ AddStep("store editor beatmap", () => editorBeatmap = EditorBeatmap);
AddStep("exit without save", () =>
{
Editor.Exit();
@@ -62,7 +66,7 @@ namespace osu.Game.Tests.Visual.Editing
});
AddUntilStep("wait for exit", () => !Editor.IsCurrentScreen());
- AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == true);
+ AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == editorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == true);
}
[Test]
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs
index 99f6ab1ae1..61565c88f4 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerLoungeSubScreen.cs
@@ -51,6 +51,24 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("room join password correct", () => lastJoinedPassword == null);
}
+ [Test]
+ public void TestPopoverHidesOnBackButton()
+ {
+ AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true));
+ AddStep("select room", () => InputManager.Key(Key.Down));
+ AddStep("attempt join room", () => InputManager.Key(Key.Enter));
+
+ AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType().Any());
+
+ AddAssert("textbox has focus", () => InputManager.FocusedDrawable is OsuPasswordTextBox);
+
+ AddStep("hit escape", () => InputManager.Key(Key.Escape));
+ AddAssert("textbox lost focus", () => InputManager.FocusedDrawable is SearchTextBox);
+
+ AddStep("hit escape", () => InputManager.Key(Key.Escape));
+ AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType().Any());
+ }
+
[Test]
public void TestPopoverHidesOnLeavingScreen()
{
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
index b536233ff0..cc64d37116 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
@@ -15,6 +15,7 @@ using osu.Game.Overlays.Mods;
using osu.Game.Overlays.Toolbar;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Screens.Menu;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.Play;
@@ -388,6 +389,19 @@ namespace osu.Game.Tests.Visual.Navigation
AddAssert("now playing is hidden", () => nowPlayingOverlay.State.Value == Visibility.Hidden);
}
+ [Test]
+ public void TestExitGameFromSongSelect()
+ {
+ PushAndConfirm(() => new TestPlaySongSelect());
+ exitViaEscapeAndConfirm();
+
+ pushEscape(); // returns to osu! logo
+
+ AddStep("Hold escape", () => InputManager.PressKey(Key.Escape));
+ AddUntilStep("Wait for intro", () => Game.ScreenStack.CurrentScreen is IntroTriangles);
+ AddUntilStep("Wait for game exit", () => Game.ScreenStack.CurrentScreen == null);
+ }
+
private void pushEscape() =>
AddStep("Press escape", () => InputManager.Key(Key.Escape));
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
index 7cfca31167..609e637914 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.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.Linq;
using NUnit.Framework;
@@ -85,6 +86,22 @@ namespace osu.Game.Tests.Visual.Online
case JoinChannelRequest joinChannel:
joinChannel.TriggerSuccess();
return true;
+
+ case GetUserRequest getUser:
+ if (getUser.Lookup.Equals("some body", StringComparison.OrdinalIgnoreCase))
+ {
+ getUser.TriggerSuccess(new User
+ {
+ Username = "some body",
+ Id = 1,
+ });
+ }
+ else
+ {
+ getUser.TriggerFailure(new Exception());
+ }
+
+ return true;
}
return false;
@@ -322,6 +339,27 @@ namespace osu.Game.Tests.Visual.Online
AddAssert("Current channel is channel 1", () => currentChannel == channel1);
}
+ [Test]
+ public void TestChatCommand()
+ {
+ AddStep("Join channel 1", () => channelManager.JoinChannel(channel1));
+ AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1]));
+
+ AddStep("Open chat with user", () => channelManager.PostCommand("chat some body"));
+ AddAssert("PM channel is selected", () =>
+ channelManager.CurrentChannel.Value.Type == ChannelType.PM && channelManager.CurrentChannel.Value.Users.Single().Username == "some body");
+
+ AddStep("Open chat with non-existent user", () => channelManager.PostCommand("chat nobody"));
+ AddAssert("Last message is error", () => channelManager.CurrentChannel.Value.Messages.Last() is ErrorMessage);
+
+ // Make sure no unnecessary requests are made when the PM channel is already open.
+ AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1]));
+ AddStep("Unregister request handling", () => ((DummyAPIAccess)API).HandleRequest = null);
+ AddStep("Open chat with user", () => channelManager.PostCommand("chat some body"));
+ AddAssert("PM channel is selected", () =>
+ channelManager.CurrentChannel.Value.Type == ChannelType.PM && channelManager.CurrentChannel.Value.Users.Single().Username == "some body");
+ }
+
private void pressChannelHotkey(int number)
{
var channelKey = Key.Number0 + number;
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs
index e7fa7d9235..513eb2fafc 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs
@@ -1,7 +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.Linq;
using NUnit.Framework;
+using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.UI;
@@ -17,5 +19,16 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("create mod icon", () => Child = icon = new ModIcon(new OsuModDoubleTime()));
AddStep("change mod", () => icon.Mod = new OsuModEasy());
}
+
+ [Test]
+ public void TestInterfaceModType()
+ {
+ ModIcon icon = null;
+
+ var ruleset = new OsuRuleset();
+
+ AddStep("create mod icon", () => Child = icon = new ModIcon(ruleset.AllMods.First(m => m.Acronym == "DT")));
+ AddStep("change mod", () => icon.Mod = ruleset.AllMods.First(m => m.Acronym == "EZ"));
+ }
}
}
diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index 3eb766a667..8cb5da8083 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -93,6 +93,12 @@ namespace osu.Game.Beatmaps
public bool WidescreenStoryboard { get; set; }
public bool EpilepsyWarning { get; set; }
+ ///
+ /// Whether or not sound samples should change rate when playing with speed-changing mods.
+ /// TODO: only read/write supported for now, requires implementation in gameplay.
+ ///
+ public bool SamplesMatchPlaybackRate { get; set; }
+
public CountdownType Countdown { get; set; } = CountdownType.Normal;
///
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 27aa874dc9..bd85017d58 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -129,6 +129,7 @@ namespace osu.Game.Beatmaps
Ruleset = ruleset,
Metadata = metadata,
WidescreenStoryboard = true,
+ SamplesMatchPlaybackRate = true,
}
}
};
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index accefb2583..4b5eaafa4a 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -180,6 +180,10 @@ namespace osu.Game.Beatmaps.Formats
beatmap.BeatmapInfo.EpilepsyWarning = Parsing.ParseInt(pair.Value) == 1;
break;
+ case @"SamplesMatchPlaybackRate":
+ beatmap.BeatmapInfo.SamplesMatchPlaybackRate = Parsing.ParseInt(pair.Value) == 1;
+ break;
+
case @"Countdown":
beatmap.BeatmapInfo.Countdown = (CountdownType)Enum.Parse(typeof(CountdownType), pair.Value);
break;
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
index 75d9a56f3e..aef13b8872 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
@@ -105,8 +105,8 @@ namespace osu.Game.Beatmaps.Formats
if (beatmap.BeatmapInfo.RulesetID == 3)
writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.BeatmapInfo.SpecialStyle ? '1' : '0')}"));
writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.BeatmapInfo.WidescreenStoryboard ? '1' : '0')}"));
- // if (b.SamplesMatchPlaybackRate)
- // writer.WriteLine(@"SamplesMatchPlaybackRate: 1");
+ if (beatmap.BeatmapInfo.SamplesMatchPlaybackRate)
+ writer.WriteLine(@"SamplesMatchPlaybackRate: 1");
}
private void handleEditor(TextWriter writer)
diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs
index c07a5de1e4..2cb696be0a 100644
--- a/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs
+++ b/osu.Game/Graphics/UserInterfaceV2/OsuPopover.cs
@@ -4,14 +4,17 @@
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Input.Bindings;
+using osu.Game.Input.Bindings;
using osu.Game.Overlays;
using osuTK;
namespace osu.Game.Graphics.UserInterfaceV2
{
- public class OsuPopover : Popover
+ public class OsuPopover : Popover, IKeyBindingHandler
{
private const float fade_duration = 250;
private const double scale_duration = 500;
@@ -51,5 +54,23 @@ namespace osu.Game.Graphics.UserInterfaceV2
this.ScaleTo(0.7f, scale_duration, Easing.OutQuint);
this.FadeOut(fade_duration, Easing.OutQuint);
}
+
+ public bool OnPressed(GlobalAction action)
+ {
+ if (State.Value == Visibility.Hidden)
+ return false;
+
+ if (action == GlobalAction.Back)
+ {
+ Hide();
+ return true;
+ }
+
+ return false;
+ }
+
+ public void OnReleased(GlobalAction action)
+ {
+ }
}
}
diff --git a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs
new file mode 100644
index 0000000000..6e53d7fae0
--- /dev/null
+++ b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.Designer.cs
@@ -0,0 +1,515 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using osu.Game.Database;
+
+namespace osu.Game.Migrations
+{
+ [DbContext(typeof(OsuDbContext))]
+ [Migration("20210912144011_AddSamplesMatchPlaybackRate")]
+ partial class AddSamplesMatchPlaybackRate
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "2.2.6-servicing-10079");
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("ApproachRate");
+
+ b.Property("CircleSize");
+
+ b.Property("DrainRate");
+
+ b.Property("OverallDifficulty");
+
+ b.Property("SliderMultiplier");
+
+ b.Property("SliderTickRate");
+
+ b.HasKey("ID");
+
+ b.ToTable("BeatmapDifficulty");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("AudioLeadIn");
+
+ b.Property("BPM");
+
+ b.Property("BaseDifficultyID");
+
+ b.Property("BeatDivisor");
+
+ b.Property("BeatmapSetInfoID");
+
+ b.Property("Countdown");
+
+ b.Property("CountdownOffset");
+
+ b.Property("DistanceSpacing");
+
+ b.Property("EpilepsyWarning");
+
+ b.Property("GridSize");
+
+ b.Property("Hash");
+
+ b.Property("Hidden");
+
+ b.Property("Length");
+
+ b.Property("LetterboxInBreaks");
+
+ b.Property("MD5Hash");
+
+ b.Property("MetadataID");
+
+ b.Property("OnlineBeatmapID");
+
+ b.Property("Path");
+
+ b.Property("RulesetID");
+
+ b.Property("SamplesMatchPlaybackRate");
+
+ b.Property("SpecialStyle");
+
+ b.Property("StackLeniency");
+
+ b.Property("StarDifficulty");
+
+ b.Property("Status");
+
+ b.Property("StoredBookmarks");
+
+ b.Property("TimelineZoom");
+
+ b.Property("Version");
+
+ b.Property("WidescreenStoryboard");
+
+ b.HasKey("ID");
+
+ b.HasIndex("BaseDifficultyID");
+
+ b.HasIndex("BeatmapSetInfoID");
+
+ b.HasIndex("Hash");
+
+ b.HasIndex("MD5Hash");
+
+ b.HasIndex("MetadataID");
+
+ b.HasIndex("OnlineBeatmapID")
+ .IsUnique();
+
+ b.HasIndex("RulesetID");
+
+ b.ToTable("BeatmapInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Artist");
+
+ b.Property("ArtistUnicode");
+
+ b.Property("AudioFile");
+
+ b.Property("AuthorID")
+ .HasColumnName("AuthorID");
+
+ b.Property("AuthorString")
+ .HasColumnName("Author");
+
+ b.Property("BackgroundFile");
+
+ b.Property("PreviewTime");
+
+ b.Property("Source");
+
+ b.Property("Tags");
+
+ b.Property("Title");
+
+ b.Property("TitleUnicode");
+
+ b.HasKey("ID");
+
+ b.ToTable("BeatmapMetadata");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("BeatmapSetInfoID");
+
+ b.Property("FileInfoID");
+
+ b.Property("Filename")
+ .IsRequired();
+
+ b.HasKey("ID");
+
+ b.HasIndex("BeatmapSetInfoID");
+
+ b.HasIndex("FileInfoID");
+
+ b.ToTable("BeatmapSetFileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("DateAdded");
+
+ b.Property("DeletePending");
+
+ b.Property("Hash");
+
+ b.Property("MetadataID");
+
+ b.Property("OnlineBeatmapSetID");
+
+ b.Property("Protected");
+
+ b.Property("Status");
+
+ b.HasKey("ID");
+
+ b.HasIndex("DeletePending");
+
+ b.HasIndex("Hash")
+ .IsUnique();
+
+ b.HasIndex("MetadataID");
+
+ b.HasIndex("OnlineBeatmapSetID")
+ .IsUnique();
+
+ b.ToTable("BeatmapSetInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Key")
+ .HasColumnName("Key");
+
+ b.Property("RulesetID");
+
+ b.Property("SkinInfoID");
+
+ b.Property("StringValue")
+ .HasColumnName("Value");
+
+ b.Property("Variant");
+
+ b.HasKey("ID");
+
+ b.HasIndex("SkinInfoID");
+
+ b.HasIndex("RulesetID", "Variant");
+
+ b.ToTable("Settings");
+ });
+
+ modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Hash");
+
+ b.Property("ReferenceCount");
+
+ b.HasKey("ID");
+
+ b.HasIndex("Hash")
+ .IsUnique();
+
+ b.HasIndex("ReferenceCount");
+
+ b.ToTable("FileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("IntAction")
+ .HasColumnName("Action");
+
+ b.Property("KeysString")
+ .HasColumnName("Keys");
+
+ b.Property("RulesetID");
+
+ b.Property("Variant");
+
+ b.HasKey("ID");
+
+ b.HasIndex("IntAction");
+
+ b.HasIndex("RulesetID", "Variant");
+
+ b.ToTable("KeyBinding");
+ });
+
+ modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Available");
+
+ b.Property("InstantiationInfo");
+
+ b.Property("Name");
+
+ b.Property("ShortName");
+
+ b.HasKey("ID");
+
+ b.HasIndex("Available");
+
+ b.HasIndex("ShortName")
+ .IsUnique();
+
+ b.ToTable("RulesetInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("FileInfoID");
+
+ b.Property("Filename")
+ .IsRequired();
+
+ b.Property("ScoreInfoID");
+
+ b.HasKey("ID");
+
+ b.HasIndex("FileInfoID");
+
+ b.HasIndex("ScoreInfoID");
+
+ b.ToTable("ScoreFileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Accuracy")
+ .HasColumnType("DECIMAL(1,4)");
+
+ b.Property("BeatmapInfoID");
+
+ b.Property("Combo");
+
+ b.Property("Date");
+
+ b.Property("DeletePending");
+
+ b.Property("Hash");
+
+ b.Property("MaxCombo");
+
+ b.Property("ModsJson")
+ .HasColumnName("Mods");
+
+ b.Property("OnlineScoreID");
+
+ b.Property("PP");
+
+ b.Property("Rank");
+
+ b.Property("RulesetID");
+
+ b.Property("StatisticsJson")
+ .HasColumnName("Statistics");
+
+ b.Property("TotalScore");
+
+ b.Property("UserID")
+ .HasColumnName("UserID");
+
+ b.Property("UserString")
+ .HasColumnName("User");
+
+ b.HasKey("ID");
+
+ b.HasIndex("BeatmapInfoID");
+
+ b.HasIndex("OnlineScoreID")
+ .IsUnique();
+
+ b.HasIndex("RulesetID");
+
+ b.ToTable("ScoreInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("FileInfoID");
+
+ b.Property("Filename")
+ .IsRequired();
+
+ b.Property("SkinInfoID");
+
+ b.HasKey("ID");
+
+ b.HasIndex("FileInfoID");
+
+ b.HasIndex("SkinInfoID");
+
+ b.ToTable("SkinFileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Creator");
+
+ b.Property("DeletePending");
+
+ b.Property("Hash");
+
+ b.Property("InstantiationInfo");
+
+ b.Property("Name");
+
+ b.HasKey("ID");
+
+ b.HasIndex("DeletePending");
+
+ b.HasIndex("Hash")
+ .IsUnique();
+
+ b.ToTable("SkinInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty")
+ .WithMany()
+ .HasForeignKey("BaseDifficultyID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet")
+ .WithMany("Beatmaps")
+ .HasForeignKey("BeatmapSetInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
+ .WithMany("Beatmaps")
+ .HasForeignKey("MetadataID");
+
+ b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
+ .WithMany()
+ .HasForeignKey("RulesetID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo")
+ .WithMany("Files")
+ .HasForeignKey("BeatmapSetInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+ .WithMany()
+ .HasForeignKey("FileInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
+ .WithMany("BeatmapSets")
+ .HasForeignKey("MetadataID");
+ });
+
+ modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
+ {
+ b.HasOne("osu.Game.Skinning.SkinInfo")
+ .WithMany("Settings")
+ .HasForeignKey("SkinInfoID");
+ });
+
+ modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
+ {
+ b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+ .WithMany()
+ .HasForeignKey("FileInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Scoring.ScoreInfo")
+ .WithMany("Files")
+ .HasForeignKey("ScoreInfoID");
+ });
+
+ modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap")
+ .WithMany("Scores")
+ .HasForeignKey("BeatmapInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
+ .WithMany()
+ .HasForeignKey("RulesetID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
+ {
+ b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+ .WithMany()
+ .HasForeignKey("FileInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Skinning.SkinInfo")
+ .WithMany("Files")
+ .HasForeignKey("SkinInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs
new file mode 100644
index 0000000000..bf3f855d5f
--- /dev/null
+++ b/osu.Game/Migrations/20210912144011_AddSamplesMatchPlaybackRate.cs
@@ -0,0 +1,23 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace osu.Game.Migrations
+{
+ public partial class AddSamplesMatchPlaybackRate : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "SamplesMatchPlaybackRate",
+ table: "BeatmapInfo",
+ nullable: false,
+ defaultValue: false);
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "SamplesMatchPlaybackRate",
+ table: "BeatmapInfo");
+ }
+ }
+}
diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
index 470907ada6..036c26cb0a 100644
--- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
+++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
@@ -81,6 +81,8 @@ namespace osu.Game.Migrations
b.Property("RulesetID");
+ b.Property("SamplesMatchPlaybackRate");
+
b.Property("SpecialStyle");
b.Property("StackLeniency");
diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs
index bf3441d2a0..b4e0e44b2c 100644
--- a/osu.Game/Online/API/Requests/GetScoresRequest.cs
+++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs
@@ -18,9 +18,9 @@ namespace osu.Game.Online.API.Requests
private readonly BeatmapInfo beatmap;
private readonly BeatmapLeaderboardScope scope;
private readonly RulesetInfo ruleset;
- private readonly IEnumerable mods;
+ private readonly IEnumerable mods;
- public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable mods = null)
+ public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable mods = null)
{
if (!beatmap.OnlineBeatmapID.HasValue)
throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}.");
@@ -31,7 +31,7 @@ namespace osu.Game.Online.API.Requests
this.beatmap = beatmap;
this.scope = scope;
this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset));
- this.mods = mods ?? Array.Empty();
+ this.mods = mods ?? Array.Empty();
Success += onSuccess;
}
diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs
index e49c4ab298..730e4e02ed 100644
--- a/osu.Game/Online/API/Requests/GetUserRequest.cs
+++ b/osu.Game/Online/API/Requests/GetUserRequest.cs
@@ -8,7 +8,7 @@ namespace osu.Game.Online.API.Requests
{
public class GetUserRequest : APIRequest
{
- private readonly string lookup;
+ public readonly string Lookup;
public readonly RulesetInfo Ruleset;
private readonly LookupType lookupType;
@@ -26,7 +26,7 @@ namespace osu.Game.Online.API.Requests
/// The ruleset to get the user's info for.
public GetUserRequest(long? userId = null, RulesetInfo ruleset = null)
{
- lookup = userId.ToString();
+ Lookup = userId.ToString();
lookupType = LookupType.Id;
Ruleset = ruleset;
}
@@ -38,12 +38,12 @@ namespace osu.Game.Online.API.Requests
/// The ruleset to get the user's info for.
public GetUserRequest(string username = null, RulesetInfo ruleset = null)
{
- lookup = username;
+ Lookup = username;
lookupType = LookupType.Username;
Ruleset = ruleset;
}
- protected override string Target => lookup != null ? $@"users/{lookup}/{Ruleset?.ShortName}?k={lookupType.ToString().ToLower()}" : $@"me/{Ruleset?.ShortName}";
+ protected override string Target => Lookup != null ? $@"users/{Lookup}/{Ruleset?.ShortName}?key={lookupType.ToString().ToLower()}" : $@"me/{Ruleset?.ShortName}";
private enum LookupType
{
diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs
index 1937019ef6..47d5955fb0 100644
--- a/osu.Game/Online/Chat/ChannelManager.cs
+++ b/osu.Game/Online/Chat/ChannelManager.cs
@@ -256,8 +256,36 @@ namespace osu.Game.Online.Chat
JoinChannel(channel);
break;
+ case "chat":
+ case "msg":
+ case "query":
+ if (string.IsNullOrWhiteSpace(content))
+ {
+ target.AddNewMessages(new ErrorMessage($"Usage: /{command} [user]"));
+ break;
+ }
+
+ // Check if the user has joined the requested channel already.
+ // This uses the channel name for comparison as the PM user's username is unavailable after a restart.
+ var privateChannel = JoinedChannels.FirstOrDefault(
+ c => c.Type == ChannelType.PM && c.Users.Count == 1 && c.Name.Equals(content, StringComparison.OrdinalIgnoreCase));
+
+ if (privateChannel != null)
+ {
+ CurrentChannel.Value = privateChannel;
+ break;
+ }
+
+ var request = new GetUserRequest(content);
+ request.Success += OpenPrivateChannel;
+ request.Failure += e => target.AddNewMessages(
+ new ErrorMessage(e.InnerException?.Message == @"NotFound" ? $"User '{content}' was not found." : $"Could not fetch user '{content}'."));
+
+ api.Queue(request);
+ break;
+
case "help":
- target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel], /np"));
+ target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel], /chat [user], /np"));
break;
default:
diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs
index 6861d17f26..935a89b99b 100644
--- a/osu.Game/Overlays/BeatmapListingOverlay.cs
+++ b/osu.Game/Overlays/BeatmapListingOverlay.cs
@@ -75,6 +75,7 @@ namespace osu.Game.Overlays
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
+ Masking = true,
Padding = new MarginPadding { Horizontal = 20 },
Children = new Drawable[]
{
@@ -186,21 +187,16 @@ namespace osu.Game.Overlays
if (lastContent != null)
{
- var transform = lastContent.FadeOut(100, Easing.OutQuint);
+ lastContent.FadeOut(100, Easing.OutQuint);
- if (lastContent == notFoundContent || lastContent == supporterRequiredContent)
- {
- // the placeholders may be used multiple times, so don't expire/dispose them.
- transform.Schedule(() => panelTarget.Remove(lastContent));
- }
- else
- {
- // Consider the case when the new content is smaller than the last content.
- // If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird.
- // At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0.
- // To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so.
- lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y).Then().Schedule(() => lastContent.Expire());
- }
+ // Consider the case when the new content is smaller than the last content.
+ // If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird.
+ // At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0.
+ // To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so.
+ var sequence = lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y);
+
+ if (lastContent != notFoundContent && lastContent != supporterRequiredContent)
+ sequence.Then().Schedule(() => lastContent.Expire());
}
if (!content.IsAlive)
@@ -208,6 +204,9 @@ namespace osu.Game.Overlays
content.FadeInFromZero(200, Easing.OutQuint);
currentContent = content;
+ // currentContent may be one of the placeholders, and still have BypassAutoSizeAxes set to Y from the last fade-out.
+ // restore to the initial state.
+ currentContent.BypassAutoSizeAxes = Axes.None;
}
protected override void Dispose(bool isDisposing)
diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs
index 2683d7bc6d..6349f115cb 100644
--- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs
+++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Overlays.BeatmapSet
{
public class LeaderboardModSelector : CompositeDrawable
{
- public readonly BindableList SelectedMods = new BindableList();
+ public readonly BindableList SelectedMods = new BindableList();
public readonly Bindable Ruleset = new Bindable();
private readonly FillFlowContainer modsContainer;
@@ -54,7 +54,7 @@ namespace osu.Game.Overlays.BeatmapSet
return;
modsContainer.Add(new ModButton(new ModNoMod()));
- modsContainer.AddRange(ruleset.NewValue.CreateInstance().AllMods.Where(m => m.UserPlayable).Select(m => new ModButton(m.CreateInstance())));
+ modsContainer.AddRange(ruleset.NewValue.CreateInstance().AllMods.Where(m => m.UserPlayable).Select(m => new ModButton(m)));
modsContainer.ForEach(button =>
{
@@ -76,7 +76,7 @@ namespace osu.Game.Overlays.BeatmapSet
updateHighlighted();
}
- private void selectionChanged(Mod mod, bool selected)
+ private void selectionChanged(IMod mod, bool selected)
{
if (selected)
SelectedMods.Add(mod);
@@ -101,9 +101,9 @@ namespace osu.Game.Overlays.BeatmapSet
private const int duration = 200;
public readonly BindableBool Highlighted = new BindableBool();
- public Action OnSelectionChanged;
+ public Action OnSelectionChanged;
- public ModButton(Mod mod)
+ public ModButton(IMod mod)
: base(mod)
{
Scale = new Vector2(0.4f);
diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs
index cf930e985c..f5720cffb0 100644
--- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs
+++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs
@@ -61,7 +61,7 @@ namespace osu.Game.Overlays.Profile.Header
Origin = Anchor.CentreLeft,
Children = new Drawable[]
{
- avatar = new UpdateableAvatar(openOnClick: false, showGuestOnNull: false)
+ avatar = new UpdateableAvatar(isInteractive: false, showGuestOnNull: false)
{
Size = new Vector2(avatar_size),
Masking = true,
diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs
index e509cac2f1..1d67968ab1 100644
--- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs
+++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs
@@ -24,6 +24,8 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
private OsuDirectorySelector directorySelector;
+ public override bool AllowTrackAdjustments => false;
+
///
/// Text to display in the header to inform the user of what they are selecting.
///
diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs
index 165c095514..5d4430caa2 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Toolbar
Add(new OpaqueBackground { Depth = 1 });
- Flow.Add(avatar = new UpdateableAvatar(openOnClick: false)
+ Flow.Add(avatar = new UpdateableAvatar(isInteractive: false)
{
Masking = true,
Size = new Vector2(32),
diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs
index ca5053aaca..d5d1de91de 100644
--- a/osu.Game/Rulesets/Mods/IMod.cs
+++ b/osu.Game/Rulesets/Mods/IMod.cs
@@ -13,6 +13,21 @@ namespace osu.Game.Rulesets.Mods
///
string Acronym { get; }
+ ///
+ /// The name of this mod.
+ ///
+ string Name { get; }
+
+ ///
+ /// The user readable description of this mod.
+ ///
+ string Description { get; }
+
+ ///
+ /// The type of this mod.
+ ///
+ ModType Type { get; }
+
///
/// The icon of this mod.
///
diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs
index fedee857c3..7136795461 100644
--- a/osu.Game/Rulesets/Mods/Mod.cs
+++ b/osu.Game/Rulesets/Mods/Mod.cs
@@ -22,29 +22,17 @@ namespace osu.Game.Rulesets.Mods
[ExcludeFromDynamicCompile]
public abstract class Mod : IMod, IEquatable, IDeepCloneable
{
- ///
- /// The name of this mod.
- ///
[JsonIgnore]
public abstract string Name { get; }
- ///
- /// The shortened name of this mod.
- ///
public abstract string Acronym { get; }
[JsonIgnore]
public virtual IconUsage? Icon => null;
- ///
- /// The type of this mod.
- ///
[JsonIgnore]
public virtual ModType Type => ModType.Fun;
- ///
- /// The user readable description of this mod.
- ///
[JsonIgnore]
public abstract string Description { get; }
diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs
index 725cfa9c26..79bada0490 100644
--- a/osu.Game/Rulesets/UI/ModIcon.cs
+++ b/osu.Game/Rulesets/UI/ModIcon.cs
@@ -30,12 +30,12 @@ namespace osu.Game.Rulesets.UI
private const float size = 80;
- public virtual LocalisableString TooltipText => showTooltip ? mod.IconTooltip : null;
+ public virtual LocalisableString TooltipText => showTooltip ? ((mod as Mod)?.IconTooltip ?? mod.Name) : null;
- private Mod mod;
+ private IMod mod;
private readonly bool showTooltip;
- public Mod Mod
+ public IMod Mod
{
get => mod;
set
@@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.UI
///
/// The mod to be displayed
/// Whether a tooltip describing the mod should display on hover.
- public ModIcon(Mod mod, bool showTooltip = true)
+ public ModIcon(IMod mod, bool showTooltip = true)
{
this.mod = mod ?? throw new ArgumentNullException(nameof(mod));
this.showTooltip = showTooltip;
@@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.UI
updateMod(mod);
}
- private void updateMod(Mod value)
+ private void updateMod(IMod value)
{
modAcronym.Text = value.Acronym;
modIcon.Icon = value.Icon ?? FontAwesome.Solid.Question;
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index 1b9a94da58..28ae7e620e 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -737,10 +737,10 @@ namespace osu.Game.Screens.Edit
private DifficultyMenuItem createDifficultyMenuItem(BeatmapInfo beatmapInfo)
{
bool isCurrentDifficulty = playableBeatmap.BeatmapInfo.Equals(beatmapInfo);
- return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, switchToDifficulty);
+ return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, SwitchToDifficulty);
}
- private void switchToDifficulty(BeatmapInfo beatmapInfo) => loader?.ScheduleDifficultySwitch(beatmapInfo);
+ protected void SwitchToDifficulty(BeatmapInfo beatmapInfo) => loader?.ScheduleDifficultySwitch(beatmapInfo);
private void cancelExit() => loader?.CancelPendingDifficultySwitch();
diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs
index aec7d32939..6bbfa92c3b 100644
--- a/osu.Game/Screens/Edit/EditorLoader.cs
+++ b/osu.Game/Screens/Edit/EditorLoader.cs
@@ -34,6 +34,20 @@ namespace osu.Game.Screens.Edit
[CanBeNull]
private ScheduledDelegate scheduledDifficultySwitch;
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ AddRangeInternal(new Drawable[]
+ {
+ new LoadingSpinner(true)
+ {
+ State = { Value = Visibility.Visible },
+ }
+ });
+ }
+
+ protected virtual Editor CreateEditor() => new Editor(this);
+
protected override void LogoArriving(OsuLogo logo, bool resuming)
{
base.LogoArriving(logo, resuming);
@@ -47,18 +61,6 @@ namespace osu.Game.Screens.Edit
}
}
- [BackgroundDependencyLoader]
- private void load()
- {
- AddRangeInternal(new Drawable[]
- {
- new LoadingSpinner(true)
- {
- State = { Value = Visibility.Visible },
- }
- });
- }
-
public void ScheduleDifficultySwitch(BeatmapInfo beatmapInfo)
{
scheduledDifficultySwitch?.Cancel();
@@ -81,7 +83,7 @@ namespace osu.Game.Screens.Edit
private void pushEditor()
{
- this.Push(new Editor(this));
+ this.Push(CreateEditor());
ValidForResume = false;
}
diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs
index 90f95a668e..d5d93db050 100644
--- a/osu.Game/Screens/Edit/Setup/DesignSection.cs
+++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs
@@ -25,6 +25,7 @@ namespace osu.Game.Screens.Edit.Setup
private LabelledSwitchButton widescreenSupport;
private LabelledSwitchButton epilepsyWarning;
private LabelledSwitchButton letterboxDuringBreaks;
+ private LabelledSwitchButton samplesMatchPlaybackRate;
public override LocalisableString Title => "Design";
@@ -79,6 +80,12 @@ namespace osu.Game.Screens.Edit.Setup
Label = "Letterbox during breaks",
Description = "Adds horizontal letterboxing to give a cinematic look during breaks.",
Current = { Value = Beatmap.BeatmapInfo.LetterboxInBreaks }
+ },
+ samplesMatchPlaybackRate = new LabelledSwitchButton
+ {
+ Label = "Samples match playback rate",
+ Description = "When enabled, all samples will speed up or slow down when rate-changing mods are enabled.",
+ Current = { Value = Beatmap.BeatmapInfo.SamplesMatchPlaybackRate }
}
};
}
@@ -96,6 +103,7 @@ namespace osu.Game.Screens.Edit.Setup
widescreenSupport.Current.BindValueChanged(_ => updateBeatmap());
epilepsyWarning.Current.BindValueChanged(_ => updateBeatmap());
letterboxDuringBreaks.Current.BindValueChanged(_ => updateBeatmap());
+ samplesMatchPlaybackRate.Current.BindValueChanged(_ => updateBeatmap());
}
private void updateCountdownSettingsVisibility() => CountdownSettings.FadeTo(EnableCountdown.Current.Value ? 1 : 0);
@@ -115,6 +123,7 @@ namespace osu.Game.Screens.Edit.Setup
Beatmap.BeatmapInfo.WidescreenStoryboard = widescreenSupport.Current.Value;
Beatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning.Current.Value;
Beatmap.BeatmapInfo.LetterboxInBreaks = letterboxDuringBreaks.Current.Value;
+ Beatmap.BeatmapInfo.SamplesMatchPlaybackRate = samplesMatchPlaybackRate.Current.Value;
}
}
}
diff --git a/osu.Game/Screens/Import/FileImportScreen.cs b/osu.Game/Screens/Import/FileImportScreen.cs
index 7e1d55b3e2..606174193d 100644
--- a/osu.Game/Screens/Import/FileImportScreen.cs
+++ b/osu.Game/Screens/Import/FileImportScreen.cs
@@ -23,6 +23,8 @@ namespace osu.Game.Screens.Import
{
public override bool HideOverlaysOnEnter => true;
+ public override bool AllowTrackAdjustments => false;
+
private OsuFileSelector fileSelector;
private Container contentContainer;
private TextFlowContainer currentFileText;
diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs
index 36296487a8..a8ca17cec1 100644
--- a/osu.Game/Screens/Menu/IntroTriangles.cs
+++ b/osu.Game/Screens/Menu/IntroTriangles.cs
@@ -42,6 +42,7 @@ namespace osu.Game.Screens.Menu
private Sample welcome;
private DecoupleableInterpolatingFramedClock decoupledClock;
+ private TrianglesIntroSequence intro;
[BackgroundDependencyLoader]
private void load()
@@ -66,7 +67,7 @@ namespace osu.Game.Screens.Menu
if (UsingThemedIntro)
decoupledClock.ChangeSource(Track);
- LoadComponentAsync(new TrianglesIntroSequence(logo, background)
+ LoadComponentAsync(intro = new TrianglesIntroSequence(logo, background)
{
RelativeSizeAxes = Axes.Both,
Clock = decoupledClock,
@@ -82,6 +83,14 @@ namespace osu.Game.Screens.Menu
}
}
+ public override void OnSuspending(IScreen next)
+ {
+ base.OnSuspending(next);
+
+ // important as there is a clock attached to a track which will likely be disposed before returning to this screen.
+ intro.Expire();
+ }
+
public override void OnResuming(IScreen last)
{
base.OnResuming(last);
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs
index 351b9b3673..833fbd6605 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs
@@ -3,6 +3,8 @@
using System.Linq;
using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
@@ -22,6 +24,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
private Drawable box;
+ private Sample sampleTeamSwap;
+
[Resolved]
private OsuColour colours { get; set; }
@@ -39,7 +43,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
}
[BackgroundDependencyLoader]
- private void load()
+ private void load(AudioManager audio)
{
box = new Container
{
@@ -72,6 +76,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
{
InternalChild = box;
}
+
+ sampleTeamSwap = audio.Samples.Get(@"Multiplayer/team-swap");
}
private void changeTeam()
@@ -99,6 +105,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
if (newTeam == displayedTeam)
return;
+ // only play the sample if an already valid team changes to another valid team.
+ // this avoids playing a sound for each user if the match type is changed to/from a team mode.
+ if (newTeam != null && displayedTeam != null)
+ sampleTeamSwap?.Play();
+
displayedTeam = newTeam;
if (displayedTeam != null)
diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
index fc20b21b60..62bfd2cfed 100644
--- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
@@ -24,6 +24,8 @@ namespace osu.Game.Screens.OnlinePlay
[Cached]
protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
+ public override bool AllowTrackAdjustments => false;
+
public override bool CursorVisible => (screenStack?.CurrentScreen as IOnlinePlaySubScreen)?.CursorVisible ?? true;
// this is required due to PlayerLoader eventually being pushed to the main stack
diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs
index a393802309..1e26036116 100644
--- a/osu.Game/Tests/Visual/EditorTestScene.cs
+++ b/osu.Game/Tests/Visual/EditorTestScene.cs
@@ -16,26 +16,38 @@ using osu.Game.Rulesets;
using osu.Game.Rulesets.Edit;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
+using osu.Game.Screens.Menu;
using osu.Game.Skinning;
namespace osu.Game.Tests.Visual
{
public abstract class EditorTestScene : ScreenTestScene
{
- protected EditorBeatmap EditorBeatmap;
+ private TestEditorLoader editorLoader;
- protected TestEditor Editor { get; private set; }
+ protected TestEditor Editor => editorLoader.Editor;
- protected EditorClock EditorClock { get; private set; }
+ protected EditorBeatmap EditorBeatmap => Editor.ChildrenOfType().Single();
+ protected EditorClock EditorClock => Editor.ChildrenOfType().Single();
///
/// Whether any saves performed by the editor should be isolate (and not persist) to the underlying .
///
protected virtual bool IsolateSavingFromDatabase => true;
+ // required for screen transitions to work properly
+ // (see comment in EditorLoader.LogoArriving).
+ [Cached]
+ private OsuLogo logo = new OsuLogo
+ {
+ Alpha = 0
+ };
+
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio, RulesetStore rulesets)
{
+ Add(logo);
+
var working = CreateWorkingBeatmap(Ruleset.Value);
Beatmap.Value = working;
@@ -53,13 +65,11 @@ namespace osu.Game.Tests.Visual
AddStep("load editor", LoadEditor);
AddUntilStep("wait for editor to load", () => EditorComponentsReady);
- AddStep("get beatmap", () => EditorBeatmap = Editor.ChildrenOfType().Single());
- AddStep("get clock", () => EditorClock = Editor.ChildrenOfType().Single());
}
protected virtual void LoadEditor()
{
- LoadScreen(Editor = CreateEditor());
+ LoadScreen(editorLoader = new TestEditorLoader());
}
///
@@ -70,7 +80,14 @@ namespace osu.Game.Tests.Visual
protected sealed override Ruleset CreateRuleset() => CreateEditorRuleset();
- protected virtual TestEditor CreateEditor() => new TestEditor();
+ protected class TestEditorLoader : EditorLoader
+ {
+ public TestEditor Editor { get; private set; }
+
+ protected sealed override Editor CreateEditor() => Editor = CreateTestEditor(this);
+
+ protected virtual TestEditor CreateTestEditor(EditorLoader loader) => new TestEditor(loader);
+ }
protected class TestEditor : Editor
{
@@ -86,7 +103,14 @@ namespace osu.Game.Tests.Visual
public new void Paste() => base.Paste();
+ public new void SwitchToDifficulty(BeatmapInfo beatmapInfo) => base.SwitchToDifficulty(beatmapInfo);
+
public new bool HasUnsavedChanges => base.HasUnsavedChanges;
+
+ public TestEditor(EditorLoader loader = null)
+ : base(loader)
+ {
+ }
}
private class TestBeatmapManager : BeatmapManager
diff --git a/osu.Game/Users/Drawables/UpdateableAvatar.cs b/osu.Game/Users/Drawables/UpdateableAvatar.cs
index df724404e9..6d48104131 100644
--- a/osu.Game/Users/Drawables/UpdateableAvatar.cs
+++ b/osu.Game/Users/Drawables/UpdateableAvatar.cs
@@ -44,7 +44,7 @@ namespace osu.Game.Users.Drawables
protected override double LoadDelay => 200;
- private readonly bool openOnClick;
+ private readonly bool isInteractive;
private readonly bool showUsernameTooltip;
private readonly bool showGuestOnNull;
@@ -52,12 +52,12 @@ namespace osu.Game.Users.Drawables
/// Construct a new UpdateableAvatar.
///
/// The initial user to display.
- /// Whether to open the user's profile when clicked.
- /// Whether to show the username rather than "view profile" on the tooltip.
+ /// If set to true, hover/click sounds will play and clicking the avatar will open the user's profile.
+ /// Whether to show the username rather than "view profile" on the tooltip. (note: this only applies if is also true)
/// Whether to show a default guest representation on null user (as opposed to nothing).
- public UpdateableAvatar(User user = null, bool openOnClick = true, bool showUsernameTooltip = false, bool showGuestOnNull = true)
+ public UpdateableAvatar(User user = null, bool isInteractive = true, bool showUsernameTooltip = false, bool showGuestOnNull = true)
{
- this.openOnClick = openOnClick;
+ this.isInteractive = isInteractive;
this.showUsernameTooltip = showUsernameTooltip;
this.showGuestOnNull = showGuestOnNull;
@@ -69,14 +69,22 @@ namespace osu.Game.Users.Drawables
if (user == null && !showGuestOnNull)
return null;
- var avatar = new ClickableAvatar(user)
+ if (isInteractive)
{
- OpenOnClick = openOnClick,
- ShowUsernameTooltip = showUsernameTooltip,
- RelativeSizeAxes = Axes.Both,
- };
-
- return avatar;
+ return new ClickableAvatar(user)
+ {
+ OpenOnClick = true,
+ ShowUsernameTooltip = showUsernameTooltip,
+ RelativeSizeAxes = Axes.Both,
+ };
+ }
+ else
+ {
+ return new DrawableAvatar(user)
+ {
+ RelativeSizeAxes = Axes.Both,
+ };
+ }
}
}
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index d80dd075ee..941656bb70 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -37,7 +37,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 8ce757974e..73e0030114 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -71,7 +71,7 @@
-
+