diff --git a/osu.Android.props b/osu.Android.props
index 591db6e2c2..f8cd4e41d4 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,7 +52,7 @@
-
+
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
index a535f8c3c3..cd3c50cf14 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
@@ -8,6 +8,7 @@ using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Input;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Testing;
@@ -88,6 +89,28 @@ namespace osu.Game.Tests.Visual.Multiplayer
// used to test the flow of multiplayer from visual tests.
}
+ [Test]
+ public void TestCreateRoomViaKeyboard()
+ {
+ // create room dialog
+ AddStep("Press new document", () => InputManager.Keys(PlatformAction.DocumentNew));
+ AddUntilStep("wait for settings", () => InputManager.ChildrenOfType().FirstOrDefault() != null);
+
+ // edit playlist item
+ AddStep("Press select", () => InputManager.Key(Key.Enter));
+ AddUntilStep("wait for song select", () => InputManager.ChildrenOfType().FirstOrDefault() != null);
+
+ // select beatmap
+ AddStep("Press select", () => InputManager.Key(Key.Enter));
+ AddUntilStep("wait for return to screen", () => InputManager.ChildrenOfType().FirstOrDefault() == null);
+
+ // create room
+ AddStep("Press select", () => InputManager.Key(Key.Enter));
+
+ AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true);
+ AddUntilStep("wait for join", () => client.Room != null);
+ }
+
[Test]
public void TestCreateRoomWithoutPassword()
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs
new file mode 100644
index 0000000000..e19665497d
--- /dev/null
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs
@@ -0,0 +1,191 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Platform;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Database;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
+using osu.Game.Online.Rooms;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Screens;
+using osu.Game.Screens.OnlinePlay.Components;
+using osu.Game.Screens.OnlinePlay.Multiplayer;
+using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
+using osu.Game.Screens.OnlinePlay.Multiplayer.Participants;
+using osu.Game.Tests.Resources;
+using osuTK.Input;
+
+namespace osu.Game.Tests.Visual.Multiplayer
+{
+ public class TestSceneTeamVersus : ScreenTestScene
+ {
+ private BeatmapManager beatmaps;
+ private RulesetStore rulesets;
+ private BeatmapSetInfo importedSet;
+
+ private DependenciesScreen dependenciesScreen;
+ private TestMultiplayer multiplayerScreen;
+ private TestMultiplayerClient client;
+
+ [Cached(typeof(UserLookupCache))]
+ private UserLookupCache lookupCache = new TestUserLookupCache();
+
+ [BackgroundDependencyLoader]
+ private void load(GameHost host, AudioManager audio)
+ {
+ Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
+ Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
+ }
+
+ public override void SetUpSteps()
+ {
+ base.SetUpSteps();
+
+ AddStep("import beatmap", () =>
+ {
+ beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
+ importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
+ });
+
+ AddStep("create multiplayer screen", () => multiplayerScreen = new TestMultiplayer());
+
+ AddStep("load dependencies", () =>
+ {
+ client = new TestMultiplayerClient(multiplayerScreen.RoomManager);
+
+ // The screen gets suspended so it stops receiving updates.
+ Child = client;
+
+ LoadScreen(dependenciesScreen = new DependenciesScreen(client));
+ });
+
+ AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded);
+
+ AddStep("load multiplayer", () => LoadScreen(multiplayerScreen));
+ AddUntilStep("wait for multiplayer to load", () => multiplayerScreen.IsLoaded);
+ AddUntilStep("wait for lounge to load", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true);
+ }
+
+ [Test]
+ public void TestCreateWithType()
+ {
+ createRoom(() => new Room
+ {
+ Name = { Value = "Test Room" },
+ Type = { Value = MatchType.TeamVersus },
+ Playlist =
+ {
+ new PlaylistItem
+ {
+ Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
+ Ruleset = { Value = new OsuRuleset().RulesetInfo },
+ }
+ }
+ });
+
+ AddAssert("room type is team vs", () => client.Room?.Settings.MatchType == MatchType.TeamVersus);
+ AddAssert("user state arrived", () => client.Room?.Users.FirstOrDefault()?.MatchState is TeamVersusUserState);
+ }
+
+ [Test]
+ public void TestChangeTeamsViaButton()
+ {
+ createRoom(() => new Room
+ {
+ Name = { Value = "Test Room" },
+ Type = { Value = MatchType.TeamVersus },
+ Playlist =
+ {
+ new PlaylistItem
+ {
+ Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
+ Ruleset = { Value = new OsuRuleset().RulesetInfo },
+ }
+ }
+ });
+
+ AddAssert("user on team 0", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0);
+
+ AddStep("press button", () =>
+ {
+ InputManager.MoveMouseTo(multiplayerScreen.ChildrenOfType().First());
+ InputManager.Click(MouseButton.Left);
+ });
+ AddAssert("user on team 1", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 1);
+
+ AddStep("press button", () => InputManager.Click(MouseButton.Left));
+ AddAssert("user on team 0", () => (client.Room?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0);
+ }
+
+ [Test]
+ public void TestChangeTypeViaMatchSettings()
+ {
+ createRoom(() => new Room
+ {
+ Name = { Value = "Test Room" },
+ Playlist =
+ {
+ new PlaylistItem
+ {
+ Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
+ Ruleset = { Value = new OsuRuleset().RulesetInfo },
+ }
+ }
+ });
+
+ AddAssert("room type is head to head", () => client.Room?.Settings.MatchType == MatchType.HeadToHead);
+
+ AddStep("change to team vs", () => client.ChangeSettings(matchType: MatchType.TeamVersus));
+
+ AddAssert("room type is team vs", () => client.Room?.Settings.MatchType == MatchType.TeamVersus);
+ }
+
+ private void createRoom(Func room)
+ {
+ AddStep("open room", () =>
+ {
+ multiplayerScreen.OpenNewRoom(room());
+ });
+
+ AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true);
+ AddWaitStep("wait for transition", 2);
+
+ AddStep("create room", () =>
+ {
+ InputManager.MoveMouseTo(this.ChildrenOfType().Single());
+ InputManager.Click(MouseButton.Left);
+ });
+
+ AddUntilStep("wait for join", () => client.Room != null);
+ }
+
+ ///
+ /// Used for the sole purpose of adding as a resolvable dependency.
+ ///
+ private class DependenciesScreen : OsuScreen
+ {
+ [Cached(typeof(MultiplayerClient))]
+ public readonly TestMultiplayerClient Client;
+
+ public DependenciesScreen(TestMultiplayerClient client)
+ {
+ Client = client;
+ }
+ }
+
+ private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer
+ {
+ public new TestRequestHandlingMultiplayerRoomManager RoomManager { get; private set; }
+
+ protected override RoomManager CreateRoomManager() => RoomManager = new TestRequestHandlingMultiplayerRoomManager();
+ }
+ }
+}
diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs
index d88fd1e62b..dab5fcbe5f 100644
--- a/osu.Game/Audio/PreviewTrackManager.cs
+++ b/osu.Game/Audio/PreviewTrackManager.cs
@@ -7,6 +7,7 @@ using System.IO;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Audio;
+using osu.Framework.Audio.Mixing;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -30,11 +31,11 @@ namespace osu.Game.Audio
private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(OsuGameBase.GLOBAL_TRACK_VOLUME_ADJUST);
[BackgroundDependencyLoader]
- private void load()
+ private void load(AudioManager audioManager)
{
// this is a temporary solution to get around muting ourselves.
// todo: update this once we have a BackgroundTrackManager or similar.
- trackStore = new PreviewTrackStore(new OnlineStore());
+ trackStore = new PreviewTrackStore(audioManager.Mixer, new OnlineStore());
audio.AddItem(trackStore);
trackStore.AddAdjustment(AdjustableProperty.Volume, globalTrackVolumeAdjust);
@@ -118,10 +119,12 @@ namespace osu.Game.Audio
private class PreviewTrackStore : AudioCollectionManager, ITrackStore
{
+ private readonly AudioMixer defaultMixer;
private readonly IResourceStore store;
- internal PreviewTrackStore(IResourceStore store)
+ internal PreviewTrackStore(AudioMixer defaultMixer, IResourceStore store)
{
+ this.defaultMixer = defaultMixer;
this.store = store;
}
@@ -145,8 +148,12 @@ namespace osu.Game.Audio
if (dataStream == null)
return null;
+ // Todo: This is quite unsafe. TrackBass shouldn't be exposed as public.
Track track = new TrackBass(dataStream);
+
+ defaultMixer.Add(track);
AddItem(track);
+
return track;
}
diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
index 9a3c75dcc6..3210ef0112 100644
--- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
+++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
@@ -93,15 +93,15 @@ namespace osu.Game.Beatmaps.Drawables
new CircularContainer
{
RelativeSizeAxes = Axes.Both,
- Scale = new Vector2(0.84f),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Masking = true,
EdgeEffect = new EdgeEffectParameters
{
- Colour = Color4.Black.Opacity(0.08f),
+ Colour = Color4.Black.Opacity(0.06f),
+
Type = EdgeEffectType.Shadow,
- Radius = 5,
+ Radius = 3,
},
Child = background = new Box
{
diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs b/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs
index 06056f239b..30e38e8938 100644
--- a/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs
+++ b/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs
@@ -89,8 +89,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
{
SelectionArea.CornerRadius = corner_radius;
SelectionArea.Masking = true;
- // purposefully use hard non-AA'd masking to avoid edge artifacts.
- SelectionArea.MaskingSmoothness = 0;
}
protected override Marker CreateMarker() => new OsuMarker();
diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
index 873be7f49c..bffb2d341a 100644
--- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
@@ -142,6 +142,8 @@ namespace osu.Game.Online.Multiplayer
APIRoom = room;
foreach (var user in joinedRoom.Users)
updateUserPlayingState(user.UserID, user.State);
+
+ OnRoomJoined();
}, cancellationSource.Token).ConfigureAwait(false);
// Update room settings.
@@ -149,6 +151,13 @@ namespace osu.Game.Online.Multiplayer
}, cancellationSource.Token).ConfigureAwait(false);
}
+ ///
+ /// Fired when the room join sequence is complete
+ ///
+ protected virtual void OnRoomJoined()
+ {
+ }
+
///
/// Joins the with a given ID.
///
@@ -192,8 +201,9 @@ namespace osu.Game.Online.Multiplayer
///
/// The new room name, if any.
/// The new password, if any.
+ /// The type of the match, if any.
/// The new room playlist item, if any.
- public Task ChangeSettings(Optional name = default, Optional password = default, Optional item = default)
+ public Task ChangeSettings(Optional name = default, Optional password = default, Optional matchType = default, Optional item = default)
{
if (Room == null)
throw new InvalidOperationException("Must be joined to a match to change settings.");
@@ -219,6 +229,7 @@ namespace osu.Game.Online.Multiplayer
BeatmapID = item.GetOr(existingPlaylistItem).BeatmapID,
BeatmapChecksum = item.GetOr(existingPlaylistItem).Beatmap.Value.MD5Hash,
RulesetID = item.GetOr(existingPlaylistItem).RulesetID,
+ MatchType = matchType.GetOr(Room.Settings.MatchType),
RequiredMods = item.HasValue ? item.Value.AsNonNull().RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.RequiredMods,
AllowedMods = item.HasValue ? item.Value.AsNonNull().AllowedMods.Select(m => new APIMod(m)).ToList() : Room.Settings.AllowedMods,
});
diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs
index 706bc750d3..001cf2aa93 100644
--- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs
+++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Online.Multiplayer
public string Password { get; set; } = string.Empty;
[Key(8)]
- public MatchType MatchType { get; set; }
+ public MatchType MatchType { get; set; } = MatchType.HeadToHead;
public bool Equals(MultiplayerRoomSettings other)
=> BeatmapID == other.BeatmapID
diff --git a/osu.Game/Screens/OnlinePlay/Components/DisableableTabControl.cs b/osu.Game/Screens/OnlinePlay/Components/DisableableTabControl.cs
index bbc407e926..2b596da361 100644
--- a/osu.Game/Screens/OnlinePlay/Components/DisableableTabControl.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/DisableableTabControl.cs
@@ -9,7 +9,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
{
public abstract class DisableableTabControl : TabControl
{
- public readonly BindableBool Enabled = new BindableBool();
+ public readonly BindableBool Enabled = new BindableBool(true);
protected override void AddTabItem(TabItem tab, bool addToDropdown = true)
{
diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/CreateRoomButton.cs b/osu.Game/Screens/OnlinePlay/Match/Components/CreateRoomButton.cs
new file mode 100644
index 0000000000..cd4dee5e3a
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/Match/Components/CreateRoomButton.cs
@@ -0,0 +1,39 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Input;
+using osu.Framework.Input.Bindings;
+
+namespace osu.Game.Screens.OnlinePlay.Match.Components
+{
+ public abstract class CreateRoomButton : PurpleTriangleButton, IKeyBindingHandler
+ {
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Triangles.TriangleScale = 1.5f;
+ }
+
+ public bool OnPressed(PlatformAction action)
+ {
+ if (!Enabled.Value)
+ return false;
+
+ switch (action)
+ {
+ case PlatformAction.DocumentNew:
+ // might as well also handle new tab. it's a bit of an undefined flow on this screen.
+ case PlatformAction.TabNew:
+ TriggerClick();
+ return true;
+ }
+
+ return false;
+ }
+
+ public void OnReleased(PlatformAction action)
+ {
+ }
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchSettingsOverlay.cs
index 61bb39d0c5..2676453a7e 100644
--- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchSettingsOverlay.cs
+++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchSettingsOverlay.cs
@@ -4,15 +4,17 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Input.Bindings;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Input.Bindings;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.OnlinePlay.Match.Components
{
- public abstract class MatchSettingsOverlay : FocusedOverlayContainer
+ public abstract class MatchSettingsOverlay : FocusedOverlayContainer, IKeyBindingHandler
{
protected const float TRANSITION_DURATION = 350;
protected const float FIELD_PADDING = 45;
@@ -21,6 +23,10 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
protected override bool BlockScrollInput => false;
+ protected abstract OsuButton SubmitButton { get; }
+
+ protected abstract bool IsLoading { get; }
+
[BackgroundDependencyLoader]
private void load()
{
@@ -29,6 +35,8 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
Add(Settings = CreateSettings());
}
+ protected abstract void SelectBeatmap();
+
protected abstract OnlinePlayComposite CreateSettings();
protected override void PopIn()
@@ -41,6 +49,33 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
Settings.MoveToY(-1, TRANSITION_DURATION, Easing.InSine);
}
+ public bool OnPressed(GlobalAction action)
+ {
+ switch (action)
+ {
+ case GlobalAction.Select:
+ if (IsLoading)
+ return true;
+
+ if (SubmitButton.Enabled.Value)
+ {
+ SubmitButton.TriggerClick();
+ return true;
+ }
+ else
+ {
+ SelectBeatmap();
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public void OnReleased(GlobalAction action)
+ {
+ }
+
protected class SettingsTextBox : OsuTextBox
{
[BackgroundDependencyLoader]
diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchTypePicker.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchTypePicker.cs
index c72fa24b67..c6f9b0f207 100644
--- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchTypePicker.cs
+++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchTypePicker.cs
@@ -30,8 +30,6 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
AddItem(MatchType.HeadToHead);
AddItem(MatchType.TeamVersus);
- // TODO: remove after osu-web is updated to set the correct default type.
- AddItem(MatchType.Playlists);
}
private class GameTypePickerItem : DisableableTabItem
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/CreateMultiplayerMatchButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/CreateMultiplayerMatchButton.cs
index cc51b5b691..e80923ed47 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/CreateMultiplayerMatchButton.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/CreateMultiplayerMatchButton.cs
@@ -8,7 +8,7 @@ using osu.Game.Screens.OnlinePlay.Match.Components;
namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
- public class CreateMultiplayerMatchButton : PurpleTriangleButton
+ public class CreateMultiplayerMatchButton : CreateRoomButton
{
private IBindable isConnected;
private IBindable operationInProgress;
@@ -22,8 +22,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
[BackgroundDependencyLoader]
private void load()
{
- Triangles.TriangleScale = 1.5f;
-
Text = "Create room";
isConnected = multiplayerClient.IsConnected.GetBoundCopy();
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs
index ebe63e26d6..56b87302c2 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs
@@ -47,7 +47,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
RelativeSizeAxes = Axes.X,
Height = 40,
Text = "Select beatmap",
- Action = () => matchSubScreen.Push(new MultiplayerMatchSongSelect()),
+ Action = () =>
+ {
+ if (matchSubScreen.IsCurrentScreen())
+ matchSubScreen.Push(new MultiplayerMatchSongSelect());
+ },
Alpha = 0
}
}
@@ -68,6 +72,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
}, true);
}
+ public void BeginSelection() => selectButton.TriggerClick();
+
private void updateBeatmap()
{
if (SelectedItem.Value == null)
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs
index 425252f3cf..5f3921d742 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs
@@ -28,8 +28,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
public class MultiplayerMatchSettingsOverlay : MatchSettingsOverlay
{
+ private MatchSettings settings;
+
+ protected override OsuButton SubmitButton => settings.ApplyButton;
+
+ [Resolved]
+ private OngoingOperationTracker ongoingOperationTracker { get; set; }
+
+ protected override bool IsLoading => ongoingOperationTracker.InProgress.Value;
+
+ protected override void SelectBeatmap() => settings.SelectBeatmap();
+
protected override OnlinePlayComposite CreateSettings()
- => new MatchSettings
+ => settings = new MatchSettings
{
RelativeSizeAxes = Axes.Both,
RelativePositionAxes = Axes.Y,
@@ -54,6 +65,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
private LoadingLayer loadingLayer;
private BeatmapSelectionControl initialBeatmapControl;
+ public void SelectBeatmap() => initialBeatmapControl.BeginSelection();
+
[Resolved]
private IRoomManager manager { get; set; }
@@ -149,7 +162,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
},
new Section("Game type")
{
- Alpha = disabled_alpha,
Child = new FillFlowContainer
{
AutoSizeAxes = Axes.Y,
@@ -161,7 +173,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
TypePicker = new MatchTypePicker
{
RelativeSizeAxes = Axes.X,
- Enabled = { Value = false }
},
typeLabel = new OsuSpriteText
{
@@ -305,7 +316,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
// Otherwise, update the room directly in preparation for it to be submitted to the API on match creation.
if (client.Room != null)
{
- client.ChangeSettings(name: NameField.Text, password: PasswordTextBox.Text).ContinueWith(t => Schedule(() =>
+ client.ChangeSettings(name: NameField.Text, password: PasswordTextBox.Text, matchType: TypePicker.Current.Value).ContinueWith(t => Schedule(() =>
{
if (t.IsCompletedSuccessfully)
onSuccess(currentRoom.Value);
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs
index dbac826954..d906cc8110 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs
@@ -58,7 +58,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
new Room
{
Name = { Value = $"{API.LocalUser}'s awesome room" },
- Category = { Value = RoomCategory.Realtime }
+ Category = { Value = RoomCategory.Realtime },
+ Type = { Value = MatchType.HeadToHead },
};
protected override string ScreenTitle => "Multiplayer";
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs
index f4a334e9d3..89431445d3 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs
@@ -37,6 +37,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
private RulesetStore rulesets { get; set; }
private SpriteIcon crown;
+
private OsuSpriteText userRankText;
private ModDisplay userModsDisplay;
private StateDisplay userStateDisplay;
@@ -56,99 +57,108 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
var backgroundColour = Color4Extensions.FromHex("#33413C");
- InternalChildren = new Drawable[]
+ InternalChild = new GridContainer
{
- crown = new SpriteIcon
+ RelativeSizeAxes = Axes.Both,
+ ColumnDimensions = new[]
{
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Icon = FontAwesome.Solid.Crown,
- Size = new Vector2(14),
- Colour = Color4Extensions.FromHex("#F7E65D"),
- Alpha = 0
+ new Dimension(GridSizeMode.Absolute, 18),
+ new Dimension(GridSizeMode.AutoSize),
+ new Dimension()
},
- new Container
+ Content = new[]
{
- RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding { Left = 24 },
- Child = new Container
+ new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- CornerRadius = 5,
- Children = new Drawable[]
+ crown = new SpriteIcon
{
- new Box
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Icon = FontAwesome.Solid.Crown,
+ Size = new Vector2(14),
+ Colour = Color4Extensions.FromHex("#F7E65D"),
+ Alpha = 0
+ },
+ new TeamDisplay(user),
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ CornerRadius = 5,
+ Children = new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
- Colour = backgroundColour
- },
- new UserCoverBackground
- {
- Anchor = Anchor.CentreRight,
- Origin = Anchor.CentreRight,
- RelativeSizeAxes = Axes.Both,
- Width = 0.75f,
- User = user,
- Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0), Color4.White.Opacity(0.25f))
- },
- new FillFlowContainer
- {
- RelativeSizeAxes = Axes.Both,
- Spacing = new Vector2(10),
- Direction = FillDirection.Horizontal,
- Children = new Drawable[]
+ new Box
{
- new UpdateableAvatar
+ RelativeSizeAxes = Axes.Both,
+ Colour = backgroundColour
+ },
+ new UserCoverBackground
+ {
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ RelativeSizeAxes = Axes.Both,
+ Width = 0.75f,
+ User = user,
+ Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0), Color4.White.Opacity(0.25f))
+ },
+ new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Spacing = new Vector2(10),
+ Direction = FillDirection.Horizontal,
+ Children = new Drawable[]
{
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- RelativeSizeAxes = Axes.Both,
- FillMode = FillMode.Fit,
- User = user
- },
- new UpdateableFlag
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Size = new Vector2(30, 20),
- Country = user?.Country
- },
- new OsuSpriteText
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 18),
- Text = user?.Username
- },
- userRankText = new OsuSpriteText
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Font = OsuFont.GetFont(size: 14),
+ new UpdateableAvatar
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ RelativeSizeAxes = Axes.Both,
+ FillMode = FillMode.Fit,
+ User = user
+ },
+ new UpdateableFlag
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Size = new Vector2(30, 20),
+ Country = user?.Country
+ },
+ new OsuSpriteText
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 18),
+ Text = user?.Username
+ },
+ userRankText = new OsuSpriteText
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Font = OsuFont.GetFont(size: 14),
+ }
}
- }
- },
- new Container
- {
- Anchor = Anchor.CentreRight,
- Origin = Anchor.CentreRight,
- AutoSizeAxes = Axes.Both,
- Margin = new MarginPadding { Right = 70 },
- Child = userModsDisplay = new ModDisplay
+ },
+ new Container
{
- Scale = new Vector2(0.5f),
- ExpansionMode = ExpansionMode.AlwaysContracted,
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ AutoSizeAxes = Axes.Both,
+ Margin = new MarginPadding { Right = 70 },
+ Child = userModsDisplay = new ModDisplay
+ {
+ Scale = new Vector2(0.5f),
+ ExpansionMode = ExpansionMode.AlwaysContracted,
+ }
+ },
+ userStateDisplay = new StateDisplay
+ {
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ Margin = new MarginPadding { Right = 10 },
}
- },
- userStateDisplay = new StateDisplay
- {
- Anchor = Anchor.CentreRight,
- Origin = Anchor.CentreRight,
- Margin = new MarginPadding { Right = 10 },
}
}
- }
+ },
}
};
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs
new file mode 100644
index 0000000000..5a7073f9de
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs
@@ -0,0 +1,134 @@
+// 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 osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
+using osu.Game.Users;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
+{
+ internal class TeamDisplay : MultiplayerRoomComposite
+ {
+ private readonly User user;
+ private Drawable box;
+
+ [Resolved]
+ private OsuColour colours { get; set; }
+
+ [Resolved]
+ private MultiplayerClient client { get; set; }
+
+ public TeamDisplay(User user)
+ {
+ this.user = user;
+
+ RelativeSizeAxes = Axes.Y;
+ Width = 15;
+
+ Margin = new MarginPadding { Horizontal = 3 };
+
+ Alpha = 0;
+ Scale = new Vector2(0, 1);
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ box = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ CornerRadius = 5,
+ Masking = true,
+ Scale = new Vector2(0, 1),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Child = new Box
+ {
+ Colour = Color4.White,
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }
+ };
+
+ if (user.Id == client.LocalUser?.UserID)
+ {
+ InternalChild = new OsuClickableContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ TooltipText = "Change team",
+ Action = changeTeam,
+ Child = box
+ };
+ }
+ else
+ {
+ InternalChild = box;
+ }
+ }
+
+ private void changeTeam()
+ {
+ client.SendMatchRequest(new ChangeTeamRequest
+ {
+ TeamID = ((client.LocalUser?.MatchState as TeamVersusUserState)?.TeamID + 1) % 2 ?? 0,
+ });
+ }
+
+ private int? displayedTeam;
+
+ protected override void OnRoomUpdated()
+ {
+ base.OnRoomUpdated();
+
+ // we don't have a way of knowing when an individual user's state has updated, so just handle on RoomUpdated for now.
+
+ var userRoomState = Room?.Users.FirstOrDefault(u => u.UserID == user.Id)?.MatchState;
+
+ const double duration = 400;
+
+ int? newTeam = (userRoomState as TeamVersusUserState)?.TeamID;
+
+ if (newTeam == displayedTeam)
+ return;
+
+ displayedTeam = newTeam;
+
+ if (displayedTeam != null)
+ {
+ box.FadeColour(getColourForTeam(displayedTeam.Value), duration, Easing.OutQuint);
+ box.ScaleTo(new Vector2(box.Scale.X < 0 ? 1 : -1, 1), duration, Easing.OutQuint);
+
+ this.ScaleTo(Vector2.One, duration, Easing.OutQuint);
+ this.FadeIn(duration);
+ }
+ else
+ {
+ this.ScaleTo(new Vector2(0, 1), duration, Easing.OutQuint);
+ this.FadeOut(duration);
+ }
+ }
+
+ private ColourInfo getColourForTeam(int id)
+ {
+ switch (id)
+ {
+ default:
+ return colours.Red;
+
+ case 1:
+ return colours.Blue;
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Playlists/CreatePlaylistsRoomButton.cs b/osu.Game/Screens/OnlinePlay/Playlists/CreatePlaylistsRoomButton.cs
index fcb773f8be..a9826a72eb 100644
--- a/osu.Game/Screens/OnlinePlay/Playlists/CreatePlaylistsRoomButton.cs
+++ b/osu.Game/Screens/OnlinePlay/Playlists/CreatePlaylistsRoomButton.cs
@@ -6,13 +6,11 @@ using osu.Game.Screens.OnlinePlay.Match.Components;
namespace osu.Game.Screens.OnlinePlay.Playlists
{
- public class CreatePlaylistsRoomButton : PurpleTriangleButton
+ public class CreatePlaylistsRoomButton : CreateRoomButton
{
[BackgroundDependencyLoader]
private void load()
{
- Triangles.TriangleScale = 1.5f;
-
Text = "Create playlist";
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Playlists/Playlists.cs b/osu.Game/Screens/OnlinePlay/Playlists/Playlists.cs
index 5b132c97fd..4f02651f02 100644
--- a/osu.Game/Screens/OnlinePlay/Playlists/Playlists.cs
+++ b/osu.Game/Screens/OnlinePlay/Playlists/Playlists.cs
@@ -48,7 +48,11 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
protected override Room CreateNewRoom()
{
- return new Room { Name = { Value = $"{API.LocalUser}'s awesome playlist" } };
+ return new Room
+ {
+ Name = { Value = $"{API.LocalUser}'s awesome playlist" },
+ Type = { Value = MatchType.Playlists }
+ };
}
protected override string ScreenTitle => "Playlists";
diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs
index 88ac5ef6e5..2640f99ea5 100644
--- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs
+++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs
@@ -26,8 +26,16 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
{
public Action EditPlaylist;
+ private MatchSettings settings;
+
+ protected override OsuButton SubmitButton => settings.ApplyButton;
+
+ protected override bool IsLoading => settings.IsLoading; // should probably be replaced with an OngoingOperationTracker.
+
+ protected override void SelectBeatmap() => settings.SelectBeatmap();
+
protected override OnlinePlayComposite CreateSettings()
- => new MatchSettings
+ => settings = new MatchSettings
{
RelativeSizeAxes = Axes.Both,
RelativePositionAxes = Axes.Y,
@@ -45,12 +53,16 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
public RoomAvailabilityPicker AvailabilityPicker;
public TriangleButton ApplyButton;
+ public bool IsLoading => loadingLayer.State.Value == Visibility.Visible;
+
public OsuSpriteText ErrorText;
private LoadingLayer loadingLayer;
private DrawableRoomPlaylist playlist;
private OsuSpriteText playlistLength;
+ private PurpleTriangleButton editPlaylistButton;
+
[Resolved(CanBeNull = true)]
private IRoomManager manager { get; set; }
@@ -199,7 +211,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
},
new Drawable[]
{
- new PurpleTriangleButton
+ editPlaylistButton = new PurpleTriangleButton
{
RelativeSizeAxes = Axes.X,
Height = 40,
@@ -292,6 +304,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
ApplyButton.Enabled.Value = hasValidSettings;
}
+ public void SelectBeatmap() => editPlaylistButton.TriggerClick();
+
private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) =>
playlistLength.Text = $"Length: {Playlist.GetTotalDuration()}";
diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs
index 092394446b..45aca24ab2 100644
--- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs
@@ -230,7 +230,11 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
settingsOverlay = new PlaylistsMatchSettingsOverlay
{
RelativeSizeAxes = Axes.Both,
- EditPlaylist = () => this.Push(new PlaylistsSongSelect()),
+ EditPlaylist = () =>
+ {
+ if (this.IsCurrentScreen())
+ this.Push(new PlaylistsSongSelect());
+ },
State = { Value = roomId.Value == null ? Visibility.Visible : Visibility.Hidden }
}
});
diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs
index ce1f223403..0efa66bac0 100644
--- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs
+++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs
@@ -61,8 +61,6 @@ namespace osu.Game.Screens.Play
protected GameplayMenuOverlay()
{
RelativeSizeAxes = Axes.Both;
-
- State.ValueChanged += s => InternalButtons.Deselect();
}
[BackgroundDependencyLoader]
@@ -142,6 +140,8 @@ namespace osu.Game.Screens.Play
},
};
+ State.ValueChanged += s => InternalButtons.Deselect();
+
updateRetryCount();
}
diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
index 7deecdfa28..43aadf5acb 100644
--- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
+++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
@@ -14,6 +14,7 @@ using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.Multiplayer;
+using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Mods;
using osu.Game.Users;
@@ -132,6 +133,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
Settings =
{
Name = apiRoom.Name.Value,
+ MatchType = apiRoom.Type.Value,
BeatmapID = apiRoom.Playlist.Last().BeatmapID,
RulesetID = apiRoom.Playlist.Last().RulesetID,
BeatmapChecksum = apiRoom.Playlist.Last().Beatmap.Value.MD5Hash,
@@ -151,6 +153,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
return Task.FromResult(room);
}
+ protected override void OnRoomJoined()
+ {
+ Debug.Assert(Room != null);
+
+ // emulate the server sending this after the join room. scheduler required to make sure the join room event is fired first (in Join).
+ changeMatchType(Room.Settings.MatchType).Wait();
+ }
+
protected override Task LeaveRoomInternal() => Task.CompletedTask;
public override Task TransferHost(int userId) => ((IMultiplayerClient)this).HostChanged(userId);
@@ -163,6 +173,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
foreach (var user in Room.Users.Where(u => u.State == MultiplayerUserState.Ready))
ChangeUserState(user.UserID, MultiplayerUserState.Idle);
+
+ await changeMatchType(settings.MatchType).ConfigureAwait(false);
}
public override Task ChangeState(MultiplayerUserState newState)
@@ -192,7 +204,30 @@ namespace osu.Game.Tests.Visual.Multiplayer
return Task.CompletedTask;
}
- public override Task SendMatchRequest(MatchUserRequest request) => Task.CompletedTask;
+ public override async Task SendMatchRequest(MatchUserRequest request)
+ {
+ Debug.Assert(Room != null);
+ Debug.Assert(LocalUser != null);
+
+ switch (request)
+ {
+ case ChangeTeamRequest changeTeam:
+
+ TeamVersusRoomState roomState = (TeamVersusRoomState)Room.MatchState!;
+ TeamVersusUserState userState = (TeamVersusUserState)LocalUser.MatchState!;
+
+ var targetTeam = roomState.Teams.FirstOrDefault(t => t.ID == changeTeam.TeamID);
+
+ if (targetTeam != null)
+ {
+ userState.TeamID = targetTeam.ID;
+
+ await ((IMultiplayerClient)this).MatchUserStateChanged(LocalUser.UserID, userState).ConfigureAwait(false);
+ }
+
+ break;
+ }
+ }
public override Task StartMatch()
{
@@ -218,5 +253,27 @@ namespace osu.Game.Tests.Visual.Multiplayer
return Task.FromResult(set);
}
+
+ private async Task changeMatchType(MatchType type)
+ {
+ Debug.Assert(Room != null);
+
+ switch (type)
+ {
+ case MatchType.HeadToHead:
+ await ((IMultiplayerClient)this).MatchRoomStateChanged(null).ConfigureAwait(false);
+
+ foreach (var user in Room.Users)
+ await ((IMultiplayerClient)this).MatchUserStateChanged(user.UserID, null).ConfigureAwait(false);
+ break;
+
+ case MatchType.TeamVersus:
+ await ((IMultiplayerClient)this).MatchRoomStateChanged(TeamVersusRoomState.CreateDefault()).ConfigureAwait(false);
+
+ foreach (var user in Room.Users)
+ await ((IMultiplayerClient)this).MatchUserStateChanged(user.UserID, new TeamVersusUserState()).ConfigureAwait(false);
+ break;
+ }
+ }
}
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 0c26bce8c9..ec59b28ceb 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -36,7 +36,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index b7f252820c..bcc4b5f254 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -93,7 +93,7 @@
-
+