diff --git a/osu.Android.props b/osu.Android.props
index 8de516240f..454bb46059 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,7 +52,7 @@
-
+
diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs
index 0e61c02e2d..d4f1602a46 100644
--- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs
+++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs
@@ -41,6 +41,11 @@ namespace osu.Game.Rulesets.Osu.Edit
protected override GameplayCursorContainer CreateCursor() => null;
+ public OsuEditorPlayfield()
+ {
+ HitPolicy = new AnyOrderHitPolicy();
+ }
+
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
diff --git a/osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs
new file mode 100644
index 0000000000..b4de91562b
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs
@@ -0,0 +1,22 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Rulesets.Osu.UI
+{
+ ///
+ /// An which allows hitobjects to be hit in any order.
+ ///
+ public class AnyOrderHitPolicy : IHitPolicy
+ {
+ public IHitObjectContainer HitObjectContainer { get; set; }
+
+ public bool IsHittable(DrawableHitObject hitObject, double time) => true;
+
+ public void HandleHit(DrawableHitObject hitObject)
+ {
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Components/TestScenePollingComponent.cs b/osu.Game.Tests/Visual/Components/TestScenePollingComponent.cs
index 2236f85b92..cc8503589d 100644
--- a/osu.Game.Tests/Visual/Components/TestScenePollingComponent.cs
+++ b/osu.Game.Tests/Visual/Components/TestScenePollingComponent.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Logging;
+using osu.Framework.Testing;
using osu.Game.Graphics.Sprites;
using osu.Game.Online;
using osuTK;
@@ -15,6 +16,7 @@ using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Components
{
+ [HeadlessTest]
public class TestScenePollingComponent : OsuTestScene
{
private Container pollBox;
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs
index e6a1bbeb92..299bbacf08 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs
@@ -13,6 +13,7 @@ using osu.Framework.Utils;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Rooms;
using osu.Game.Online.Rooms.RoomStatuses;
+using osu.Game.Overlays;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Tests.Beatmaps;
@@ -26,6 +27,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Cached]
private readonly Bindable selectedRoom = new Bindable();
+ [Cached]
+ protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
+
[Test]
public void TestMultipleStatuses()
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs
index e14df62af1..ade24b8740 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs
@@ -6,9 +6,11 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Framework.Timing;
+using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
using osu.Game.Screens.Play.HUD;
+using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
@@ -31,7 +33,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
};
foreach (var (userId, _) in clocks)
+ {
SpectatorClient.StartPlay(userId, 0);
+ OnlinePlayDependencies.Client.AddUser(new User { Id = userId });
+ }
});
AddStep("create leaderboard", () =>
@@ -41,7 +46,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
var scoreProcessor = new OsuScoreProcessor();
scoreProcessor.ApplyBeatmap(playable);
- LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.ToArray()) { Expanded = { Value = true } }, Add);
+ LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray()) { Expanded = { Value = true } }, Add);
});
AddUntilStep("wait for load", () => leaderboard.IsLoaded);
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
index e9fae32335..65b1d6d53a 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
@@ -8,6 +8,8 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
using osu.Game.Screens.Play;
@@ -26,7 +28,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
private MultiSpectatorScreen spectatorScreen;
- private readonly List playingUserIds = new List();
+ private readonly List playingUsers = new List();
private BeatmapSetInfo importedSet;
private BeatmapInfo importedBeatmap;
@@ -41,7 +43,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[SetUp]
- public new void Setup() => Schedule(() => playingUserIds.Clear());
+ public new void Setup() => Schedule(() => playingUsers.Clear());
[Test]
public void TestDelayedStart()
@@ -51,8 +53,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
OnlinePlayDependencies.Client.AddUser(new User { Id = PLAYER_1_ID }, true);
OnlinePlayDependencies.Client.AddUser(new User { Id = PLAYER_2_ID }, true);
- playingUserIds.Add(PLAYER_1_ID);
- playingUserIds.Add(PLAYER_2_ID);
+ playingUsers.Add(new MultiplayerRoomUser(PLAYER_1_ID));
+ playingUsers.Add(new MultiplayerRoomUser(PLAYER_2_ID));
});
loadSpectateScreen(false);
@@ -78,6 +80,38 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddWaitStep("wait a bit", 20);
}
+ [Test]
+ public void TestTeamDisplay()
+ {
+ AddStep("start players", () =>
+ {
+ var player1 = OnlinePlayDependencies.Client.AddUser(new User { Id = PLAYER_1_ID }, true);
+ player1.MatchState = new TeamVersusUserState
+ {
+ TeamID = 0,
+ };
+
+ var player2 = OnlinePlayDependencies.Client.AddUser(new User { Id = PLAYER_2_ID }, true);
+ player2.MatchState = new TeamVersusUserState
+ {
+ TeamID = 1,
+ };
+
+ SpectatorClient.StartPlay(player1.UserID, importedBeatmapId);
+ SpectatorClient.StartPlay(player2.UserID, importedBeatmapId);
+
+ playingUsers.Add(player1);
+ playingUsers.Add(player2);
+ });
+
+ loadSpectateScreen();
+
+ sendFrames(PLAYER_1_ID, 1000);
+ sendFrames(PLAYER_2_ID, 1000);
+
+ AddWaitStep("wait a bit", 20);
+ }
+
[Test]
public void TestTimeDoesNotProgressWhileAllPlayersPaused()
{
@@ -254,7 +288,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
Beatmap.Value = beatmapManager.GetWorkingBeatmap(importedBeatmap);
Ruleset.Value = importedBeatmap.Ruleset;
- LoadScreen(spectatorScreen = new MultiSpectatorScreen(playingUserIds.ToArray()));
+ LoadScreen(spectatorScreen = new MultiSpectatorScreen(playingUsers.ToArray()));
});
AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded));
@@ -269,7 +303,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
OnlinePlayDependencies.Client.AddUser(new User { Id = id }, true);
SpectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId);
- playingUserIds.Add(id);
+ playingUsers.Add(new MultiplayerRoomUser(id));
}
});
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs
index 8121492a0b..3317ddc767 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs
@@ -12,6 +12,7 @@ using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Online.API;
+using osu.Game.Online.Multiplayer;
using osu.Game.Online.Spectator;
using osu.Game.Replays.Legacy;
using osu.Game.Rulesets.Osu.Scoring;
@@ -51,12 +52,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
OsuScoreProcessor scoreProcessor;
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
- var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
+ var playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
+ var multiplayerUsers = new List();
foreach (var user in users)
{
SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
- OnlinePlayDependencies.Client.AddUser(new User { Id = user }, true);
+ multiplayerUsers.Add(OnlinePlayDependencies.Client.AddUser(new User { Id = user }, true));
}
Children = new Drawable[]
@@ -64,9 +66,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
scoreProcessor = new OsuScoreProcessor(),
};
- scoreProcessor.ApplyBeatmap(playable);
+ scoreProcessor.ApplyBeatmap(playableBeatmap);
- LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, users.ToArray())
+ LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, multiplayerUsers.ToArray())
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs
new file mode 100644
index 0000000000..dfaf2f1dc3
--- /dev/null
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs
@@ -0,0 +1,121 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Testing;
+using osu.Framework.Utils;
+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.Osu.Scoring;
+using osu.Game.Screens.OnlinePlay.Multiplayer;
+using osu.Game.Screens.Play.HUD;
+using osu.Game.Tests.Visual.OnlinePlay;
+using osu.Game.Tests.Visual.Spectator;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual.Multiplayer
+{
+ public class TestSceneMultiplayerGameplayLeaderboardTeams : MultiplayerTestScene
+ {
+ private static IEnumerable users => Enumerable.Range(0, 16);
+
+ public new TestSceneMultiplayerGameplayLeaderboard.TestMultiplayerSpectatorClient SpectatorClient =>
+ (TestSceneMultiplayerGameplayLeaderboard.TestMultiplayerSpectatorClient)OnlinePlayDependencies?.SpectatorClient;
+
+ protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies();
+
+ protected class TestDependencies : MultiplayerTestSceneDependencies
+ {
+ protected override TestSpectatorClient CreateSpectatorClient() => new TestSceneMultiplayerGameplayLeaderboard.TestMultiplayerSpectatorClient();
+ }
+
+ private MultiplayerGameplayLeaderboard leaderboard;
+ private GameplayMatchScoreDisplay gameplayScoreDisplay;
+
+ protected override Room CreateRoom()
+ {
+ var room = base.CreateRoom();
+ room.Type.Value = MatchType.TeamVersus;
+ return room;
+ }
+
+ [SetUpSteps]
+ public override void SetUpSteps()
+ {
+ AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).Result);
+
+ AddStep("create leaderboard", () =>
+ {
+ leaderboard?.Expire();
+
+ OsuScoreProcessor scoreProcessor;
+ Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
+
+ var playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
+ var multiplayerUsers = new List();
+
+ foreach (var user in users)
+ {
+ SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
+ var roomUser = OnlinePlayDependencies.Client.AddUser(new User { Id = user }, true);
+
+ roomUser.MatchState = new TeamVersusUserState
+ {
+ TeamID = RNG.Next(0, 2)
+ };
+
+ multiplayerUsers.Add(roomUser);
+ }
+
+ Children = new Drawable[]
+ {
+ scoreProcessor = new OsuScoreProcessor(),
+ };
+
+ scoreProcessor.ApplyBeatmap(playableBeatmap);
+
+ LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, multiplayerUsers.ToArray())
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }, gameplayLeaderboard =>
+ {
+ LoadComponentAsync(new MatchScoreDisplay
+ {
+ Team1Score = { BindTarget = leaderboard.TeamScores[0] },
+ Team2Score = { BindTarget = leaderboard.TeamScores[1] }
+ }, Add);
+
+ LoadComponentAsync(gameplayScoreDisplay = new GameplayMatchScoreDisplay
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre,
+ Team1Score = { BindTarget = leaderboard.TeamScores[0] },
+ Team2Score = { BindTarget = leaderboard.TeamScores[1] }
+ }, Add);
+
+ Add(gameplayLeaderboard);
+ });
+ });
+
+ AddUntilStep("wait for load", () => leaderboard.IsLoaded);
+ AddUntilStep("wait for user population", () => Client.CurrentMatchPlayingUserIds.Count > 0);
+ }
+
+ [Test]
+ public void TestScoreUpdates()
+ {
+ AddRepeatStep("update state", () => SpectatorClient.RandomlyUpdateState(), 100);
+ AddToggleStep("switch compact mode", expanded =>
+ {
+ leaderboard.Expanded.Value = expanded;
+ gameplayScoreDisplay.Expanded.Value = expanded;
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs
index 6526f7eea7..a3e6c8de3b 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs
@@ -155,6 +155,42 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("second user crown visible", () => this.ChildrenOfType().ElementAt(1).ChildrenOfType().First().Alpha == 1);
}
+ [Test]
+ public void TestKickButtonOnlyPresentWhenHost()
+ {
+ AddStep("add user", () => Client.AddUser(new User
+ {
+ Id = 3,
+ Username = "Second",
+ CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
+ }));
+
+ AddUntilStep("kick buttons visible", () => this.ChildrenOfType().Count(d => d.IsPresent) == 1);
+
+ AddStep("make second user host", () => Client.TransferHost(3));
+
+ AddUntilStep("kick buttons not visible", () => this.ChildrenOfType().Count(d => d.IsPresent) == 0);
+
+ AddStep("make local user host again", () => Client.TransferHost(API.LocalUser.Value.Id));
+
+ AddUntilStep("kick buttons visible", () => this.ChildrenOfType().Count(d => d.IsPresent) == 1);
+ }
+
+ [Test]
+ public void TestKickButtonKicks()
+ {
+ AddStep("add user", () => Client.AddUser(new User
+ {
+ Id = 3,
+ Username = "Second",
+ CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
+ }));
+
+ AddStep("kick second user", () => this.ChildrenOfType().Single(d => d.IsPresent).TriggerClick());
+
+ AddAssert("second user kicked", () => Client.Room?.Users.Single().UserID == API.LocalUser.Value.Id);
+ }
+
[Test]
public void TestManyUsers()
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneRecentParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneRecentParticipantsList.cs
index f05c092dfb..50ec2bf3ac 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneRecentParticipantsList.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneRecentParticipantsList.cs
@@ -26,12 +26,58 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- NumberOfAvatars = 3
+ NumberOfCircles = 4
};
});
[Test]
- public void TestAvatarCount()
+ public void TestCircleCountNearLimit()
+ {
+ AddStep("add 8 users", () =>
+ {
+ for (int i = 0; i < 8; i++)
+ addUser(i);
+ });
+
+ AddStep("set 8 circles", () => list.NumberOfCircles = 8);
+ AddAssert("0 hidden users", () => list.ChildrenOfType().Single().Count == 0);
+
+ AddStep("add one more user", () => addUser(9));
+ AddAssert("2 hidden users", () => list.ChildrenOfType().Single().Count == 2);
+
+ AddStep("remove first user", () => removeUserAt(0));
+ AddAssert("0 hidden users", () => list.ChildrenOfType().Single().Count == 0);
+
+ AddStep("add one more user", () => addUser(9));
+ AddAssert("2 hidden users", () => list.ChildrenOfType().Single().Count == 2);
+
+ AddStep("remove last user", () => removeUserAt(8));
+ AddAssert("0 hidden users", () => list.ChildrenOfType().Single().Count == 0);
+ }
+
+ [Test]
+ public void TestHiddenUsersBecomeDisplayed()
+ {
+ AddStep("add 8 users", () =>
+ {
+ for (int i = 0; i < 8; i++)
+ addUser(i);
+ });
+
+ AddStep("set 3 circles", () => list.NumberOfCircles = 3);
+
+ for (int i = 0; i < 8; i++)
+ {
+ AddStep("remove user", () => removeUserAt(0));
+ int remainingUsers = 7 - i;
+
+ int displayedUsers = remainingUsers > 3 ? 2 : remainingUsers;
+ AddAssert($"{displayedUsers} avatars displayed", () => list.ChildrenOfType().Count() == displayedUsers);
+ }
+ }
+
+ [Test]
+ public void TestCircleCount()
{
AddStep("add 50 users", () =>
{
@@ -39,13 +85,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
addUser(i);
});
- AddStep("set 3 avatars", () => list.NumberOfAvatars = 3);
- AddAssert("3 avatars displayed", () => list.ChildrenOfType().Count() == 3);
- AddAssert("47 hidden users", () => list.ChildrenOfType().Single().Count == 47);
+ AddStep("set 3 circles", () => list.NumberOfCircles = 3);
+ AddAssert("2 users displayed", () => list.ChildrenOfType().Count() == 2);
+ AddAssert("48 hidden users", () => list.ChildrenOfType().Single().Count == 48);
- AddStep("set 10 avatars", () => list.NumberOfAvatars = 10);
- AddAssert("10 avatars displayed", () => list.ChildrenOfType().Count() == 10);
- AddAssert("40 hidden users", () => list.ChildrenOfType().Single().Count == 40);
+ AddStep("set 10 circles", () => list.NumberOfCircles = 10);
+ AddAssert("9 users displayed", () => list.ChildrenOfType().Count() == 9);
+ AddAssert("41 hidden users", () => list.ChildrenOfType().Single().Count == 41);
}
[Test]
@@ -58,40 +104,40 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
AddStep("remove from start", () => removeUserAt(0));
- AddAssert("3 avatars displayed", () => list.ChildrenOfType().Count() == 3);
+ AddAssert("3 circles displayed", () => list.ChildrenOfType().Count() == 3);
AddAssert("46 hidden users", () => list.ChildrenOfType().Single().Count == 46);
AddStep("remove from end", () => removeUserAt(SelectedRoom.Value.RecentParticipants.Count - 1));
- AddAssert("3 avatars displayed", () => list.ChildrenOfType().Count() == 3);
+ AddAssert("3 circles displayed", () => list.ChildrenOfType().Count() == 3);
AddAssert("45 hidden users", () => list.ChildrenOfType().Single().Count == 45);
AddRepeatStep("remove 45 users", () => removeUserAt(0), 45);
- AddAssert("3 avatars displayed", () => list.ChildrenOfType().Count() == 3);
+ AddAssert("3 circles displayed", () => list.ChildrenOfType().Count() == 3);
AddAssert("0 hidden users", () => list.ChildrenOfType().Single().Count == 0);
AddAssert("hidden users bubble hidden", () => list.ChildrenOfType().Single().Alpha < 0.5f);
AddStep("remove another user", () => removeUserAt(0));
- AddAssert("2 avatars displayed", () => list.ChildrenOfType().Count() == 2);
+ AddAssert("2 circles displayed", () => list.ChildrenOfType().Count() == 2);
AddAssert("0 hidden users", () => list.ChildrenOfType().Single().Count == 0);
AddRepeatStep("remove the remaining two users", () => removeUserAt(0), 2);
- AddAssert("0 avatars displayed", () => !list.ChildrenOfType().Any());
+ AddAssert("0 circles displayed", () => !list.ChildrenOfType().Any());
}
private void addUser(int id)
{
- SelectedRoom.Value.ParticipantCount.Value++;
SelectedRoom.Value.RecentParticipants.Add(new User
{
Id = id,
Username = $"User {id}"
});
+ SelectedRoom.Value.ParticipantCount.Value++;
}
private void removeUserAt(int index)
{
- SelectedRoom.Value.ParticipantCount.Value--;
SelectedRoom.Value.RecentParticipants.RemoveAt(index);
+ SelectedRoom.Value.ParticipantCount.Value--;
}
}
}
diff --git a/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs
index acd5d53310..11b5cc7556 100644
--- a/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs
+++ b/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Tournament.Tests.Components
public TestSceneMatchScoreDisplay()
{
- Add(new MatchScoreDisplay
+ Add(new TournamentMatchScoreDisplay
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TournamentMatchScoreDisplay.cs
similarity index 97%
rename from osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs
rename to osu.Game.Tournament/Screens/Gameplay/Components/TournamentMatchScoreDisplay.cs
index 695c6d6f3e..994dee4da0 100644
--- a/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs
+++ b/osu.Game.Tournament/Screens/Gameplay/Components/TournamentMatchScoreDisplay.cs
@@ -16,7 +16,8 @@ using osuTK;
namespace osu.Game.Tournament.Screens.Gameplay.Components
{
- public class MatchScoreDisplay : CompositeDrawable
+ // TODO: Update to derive from osu-side class?
+ public class TournamentMatchScoreDisplay : CompositeDrawable
{
private const float bar_height = 18;
@@ -29,7 +30,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
private readonly Drawable score1Bar;
private readonly Drawable score2Bar;
- public MatchScoreDisplay()
+ public TournamentMatchScoreDisplay()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs
index f61506d7f2..540b45eb56 100644
--- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs
+++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs
@@ -86,7 +86,7 @@ namespace osu.Game.Tournament.Screens.Gameplay
},
}
},
- scoreDisplay = new MatchScoreDisplay
+ scoreDisplay = new TournamentMatchScoreDisplay
{
Y = -147,
Anchor = Anchor.BottomCentre,
@@ -148,7 +148,7 @@ namespace osu.Game.Tournament.Screens.Gameplay
}
private ScheduledDelegate scheduledOperation;
- private MatchScoreDisplay scoreDisplay;
+ private TournamentMatchScoreDisplay scoreDisplay;
private TourneyState lastState;
private MatchHeader header;
diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs
index cd0e601a2f..7a43fee013 100644
--- a/osu.Game.Tournament/TournamentGame.cs
+++ b/osu.Game.Tournament/TournamentGame.cs
@@ -26,8 +26,8 @@ namespace osu.Game.Tournament
{
public static ColourInfo GetTeamColour(TeamColour teamColour) => teamColour == TeamColour.Red ? COLOUR_RED : COLOUR_BLUE;
- public static readonly Color4 COLOUR_RED = Color4Extensions.FromHex("#AA1414");
- public static readonly Color4 COLOUR_BLUE = Color4Extensions.FromHex("#1462AA");
+ public static readonly Color4 COLOUR_RED = new OsuColour().TeamColourRed;
+ public static readonly Color4 COLOUR_BLUE = new OsuColour().TeamColourBlue;
public static readonly Color4 ELEMENT_BACKGROUND_COLOUR = Color4Extensions.FromHex("#fff");
public static readonly Color4 ELEMENT_FOREGROUND_COLOUR = Color4Extensions.FromHex("#000");
diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs
index 1f87c06dd2..d7cfc4094c 100644
--- a/osu.Game/Graphics/OsuColour.cs
+++ b/osu.Game/Graphics/OsuColour.cs
@@ -130,6 +130,9 @@ namespace osu.Game.Graphics
return Gray(brightness > 0.5f ? 0.2f : 0.9f);
}
+ public readonly Color4 TeamColourRed = Color4Extensions.FromHex("#AA1414");
+ public readonly Color4 TeamColourBlue = Color4Extensions.FromHex("#1462AA");
+
// See https://github.com/ppy/osu-web/blob/master/resources/assets/less/colors.less
public readonly Color4 PurpleLighter = Color4Extensions.FromHex(@"eeeeff");
public readonly Color4 PurpleLight = Color4Extensions.FromHex(@"aa88ff");
diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs
index b26c4d8201..da637c229f 100644
--- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs
+++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs
@@ -27,6 +27,14 @@ namespace osu.Game.Online.Multiplayer
/// If the user is not in a room.
Task TransferHost(int userId);
+ ///
+ /// As the host, kick another user from the room.
+ ///
+ /// The user to kick..
+ /// A user other than the current host is attempting to kick a user.
+ /// If the user is not in a room.
+ Task KickUser(int userId);
+
///
/// As the host, update the settings of the currently joined room.
///
diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
index dafc737ba2..4607211cdf 100644
--- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
@@ -293,6 +293,8 @@ namespace osu.Game.Online.Multiplayer
public abstract Task TransferHost(int userId);
+ public abstract Task KickUser(int userId);
+
public abstract Task ChangeSettings(MultiplayerRoomSettings settings);
public abstract Task ChangeState(MultiplayerUserState newState);
diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs
index 8b8d10ce4f..55477a9fc7 100644
--- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs
@@ -91,6 +91,14 @@ namespace osu.Game.Online.Multiplayer
return connection.InvokeAsync(nameof(IMultiplayerServer.TransferHost), userId);
}
+ public override Task KickUser(int userId)
+ {
+ if (!IsConnected.Value)
+ return Task.CompletedTask;
+
+ return connection.InvokeAsync(nameof(IMultiplayerServer.KickUser), userId);
+ }
+
public override Task ChangeSettings(MultiplayerRoomSettings settings)
{
if (!IsConnected.Value)
diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs
index 008e7696e1..e7b3e6d873 100644
--- a/osu.Game/Overlays/OverlayColourProvider.cs
+++ b/osu.Game/Overlays/OverlayColourProvider.cs
@@ -18,6 +18,7 @@ namespace osu.Game.Overlays
public static OverlayColourProvider Green { get; } = new OverlayColourProvider(OverlayColourScheme.Green);
public static OverlayColourProvider Purple { get; } = new OverlayColourProvider(OverlayColourScheme.Purple);
public static OverlayColourProvider Blue { get; } = new OverlayColourProvider(OverlayColourScheme.Blue);
+ public static OverlayColourProvider Plum { get; } = new OverlayColourProvider(OverlayColourScheme.Plum);
public OverlayColourProvider(OverlayColourScheme colourScheme)
{
@@ -80,6 +81,9 @@ namespace osu.Game.Overlays
case OverlayColourScheme.Blue:
return 200 / 360f;
+
+ case OverlayColourScheme.Plum:
+ return 320 / 360f;
}
}
}
@@ -92,6 +96,7 @@ namespace osu.Game.Overlays
Lime,
Green,
Purple,
- Blue
+ Blue,
+ Plum,
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs
index 3a56be64e0..193fb0cf57 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs
@@ -28,6 +28,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Input.Bindings;
using osu.Game.Online.Rooms;
+using osu.Game.Overlays;
using osu.Game.Screens.OnlinePlay.Components;
using osuTK;
using osuTK.Graphics;
@@ -41,8 +42,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
private const float transition_duration = 60;
private const float height = 100;
- private static readonly Color4 background_colour = Color4Extensions.FromHex(@"#27302E");
-
public event Action StateChanged;
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
@@ -121,7 +120,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
numberOfAvatars = value;
if (recentParticipantsList != null)
- recentParticipantsList.NumberOfAvatars = value;
+ recentParticipantsList.NumberOfCircles = value;
}
}
@@ -154,7 +153,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours, AudioManager audio)
+ private void load(OverlayColourProvider colours, AudioManager audio)
{
Children = new Drawable[]
{
@@ -167,7 +166,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
new Box
{
RelativeSizeAxes = Axes.Both,
- Colour = background_colour,
+ Colour = colours.Background5,
},
new OnlinePlayBackgroundSprite
{
@@ -208,12 +207,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
new Box
{
RelativeSizeAxes = Axes.Both,
- Colour = background_colour,
+ Colour = colours.Background5,
},
new Box
{
RelativeSizeAxes = Axes.Both,
- Colour = ColourInfo.GradientHorizontal(background_colour, background_colour.Opacity(0.3f))
+ Colour = ColourInfo.GradientHorizontal(colours.Background5, colours.Background5.Opacity(0.3f))
},
}
}
@@ -316,7 +315,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
- NumberOfAvatars = NumberOfAvatars
+ NumberOfCircles = NumberOfAvatars
}
}
},
@@ -531,7 +530,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
private OsuPasswordTextBox passwordTextbox;
[BackgroundDependencyLoader]
- private void load(OsuColour colours)
+ private void load()
{
Child = new FillFlowContainer
{
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RecentParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RecentParticipantsList.cs
index 7f3fdf01d0..bc658f45e4 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RecentParticipantsList.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RecentParticipantsList.cs
@@ -4,16 +4,16 @@
using System.Collections.Specialized;
using System.Linq;
using osu.Framework.Allocation;
-using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
+using osu.Game.Overlays;
using osu.Game.Users;
using osu.Game.Users.Drawables;
using osuTK;
-using osuTK.Graphics;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
@@ -22,7 +22,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
private const float avatar_size = 36;
private FillFlowContainer avatarFlow;
+
private HiddenUserCount hiddenUsers;
+ private OsuSpriteText totalCount;
public RecentParticipantsList()
{
@@ -31,7 +33,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
}
[BackgroundDependencyLoader]
- private void load()
+ private void load(OverlayColourProvider colours)
{
InternalChildren = new Drawable[]
{
@@ -44,7 +46,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
Child = new Box
{
RelativeSizeAxes = Axes.Both,
- Colour = Color4Extensions.FromHex(@"#2E3835")
+ Colour = colours.Background4,
}
},
new FillFlowContainer
@@ -54,7 +56,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(4),
- Padding = new MarginPadding { Left = 8, Right = 16 },
+ Padding = new MarginPadding { Right = 16 },
Children = new Drawable[]
{
new SpriteIcon
@@ -62,15 +64,23 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Size = new Vector2(16),
+ Margin = new MarginPadding { Left = 8 },
Icon = FontAwesome.Solid.User,
},
+ totalCount = new OsuSpriteText
+ {
+ Font = OsuFont.Default.With(weight: FontWeight.Bold),
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ },
avatarFlow = new FillFlowContainer
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
- Spacing = new Vector2(4)
+ Spacing = new Vector2(4),
+ Margin = new MarginPadding { Left = 4 },
},
hiddenUsers = new HiddenUserCount
{
@@ -87,17 +97,24 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
base.LoadComplete();
RecentParticipants.BindCollectionChanged(onParticipantsChanged, true);
- ParticipantCount.BindValueChanged(_ => updateHiddenUserCount(), true);
+ ParticipantCount.BindValueChanged(_ =>
+ {
+ updateHiddenUsers();
+ totalCount.Text = ParticipantCount.Value.ToString();
+ }, true);
}
- private int numberOfAvatars = 3;
+ private int numberOfCircles = 4;
- public int NumberOfAvatars
+ ///
+ /// The maximum number of circles visible (including the "hidden count" circle in the overflow case).
+ ///
+ public int NumberOfCircles
{
- get => numberOfAvatars;
+ get => numberOfCircles;
set
{
- numberOfAvatars = value;
+ numberOfCircles = value;
if (LoadState < LoadState.Loaded)
return;
@@ -106,6 +123,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
clearUsers();
foreach (var u in RecentParticipants)
addUser(u);
+
+ updateHiddenUsers();
}
}
@@ -135,34 +154,45 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
addUser(u);
break;
}
+
+ updateHiddenUsers();
}
+ private int displayedCircles => avatarFlow.Count + (hiddenUsers.Count > 0 ? 1 : 0);
+
private void addUser(User user)
{
- if (avatarFlow.Count < NumberOfAvatars)
+ if (displayedCircles < NumberOfCircles)
avatarFlow.Add(new CircularAvatar { User = user });
-
- updateHiddenUserCount();
}
private void removeUser(User user)
{
- if (avatarFlow.RemoveAll(a => a.User == user) > 0)
- {
- if (RecentParticipants.Count >= NumberOfAvatars)
- avatarFlow.Add(new CircularAvatar { User = RecentParticipants.First(u => avatarFlow.All(a => a.User != u)) });
- }
-
- updateHiddenUserCount();
+ avatarFlow.RemoveAll(a => a.User == user);
}
private void clearUsers()
{
avatarFlow.Clear();
- updateHiddenUserCount();
+ updateHiddenUsers();
}
- private void updateHiddenUserCount() => hiddenUsers.Count = ParticipantCount.Value - avatarFlow.Count;
+ private void updateHiddenUsers()
+ {
+ int hiddenCount = 0;
+ if (RecentParticipants.Count > NumberOfCircles)
+ hiddenCount = ParticipantCount.Value - NumberOfCircles + 1;
+
+ hiddenUsers.Count = hiddenCount;
+
+ if (displayedCircles > NumberOfCircles)
+ avatarFlow.Remove(avatarFlow.Last());
+ else if (displayedCircles < NumberOfCircles)
+ {
+ var nextUser = RecentParticipants.FirstOrDefault(u => avatarFlow.All(a => a.User != u));
+ if (nextUser != null) addUser(nextUser);
+ }
+ }
private class CircularAvatar : CompositeDrawable
{
@@ -172,9 +202,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
set => avatar.User = value;
}
- private readonly UpdateableAvatar avatar;
+ private readonly UpdateableAvatar avatar = new UpdateableAvatar(showUsernameTooltip: true) { RelativeSizeAxes = Axes.Both };
- public CircularAvatar()
+ [BackgroundDependencyLoader]
+ private void load(OverlayColourProvider colours)
{
Size = new Vector2(avatar_size);
@@ -182,7 +213,15 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
RelativeSizeAxes = Axes.Both,
Masking = true,
- Child = avatar = new UpdateableAvatar(showUsernameTooltip: true) { RelativeSizeAxes = Axes.Both }
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = colours.Background5,
+ RelativeSizeAxes = Axes.Both,
+ },
+ avatar
+ }
};
}
}
@@ -206,9 +245,15 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
private int count;
- private readonly SpriteText countText;
+ private readonly SpriteText countText = new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Font = OsuFont.Default.With(weight: FontWeight.Bold),
+ };
- public HiddenUserCount()
+ [BackgroundDependencyLoader]
+ private void load(OverlayColourProvider colours)
{
Size = new Vector2(avatar_size);
Alpha = 0;
@@ -222,13 +267,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
new Box
{
RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black
+ Colour = colours.Background5,
},
- countText = new OsuSpriteText
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- }
+ countText
}
};
}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
index b9b034272d..5e5863c7c4 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
@@ -50,6 +50,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
+ // account for the fact we are in a scroll container and want a bit of spacing from the scroll bar.
+ Padding = new MarginPadding { Right = 5 };
+
InternalChild = new OsuContextMenuContainer
{
RelativeSizeAxes = Axes.X,
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayMatchScoreDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayMatchScoreDisplay.cs
new file mode 100644
index 0000000000..20a88545c5
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayMatchScoreDisplay.cs
@@ -0,0 +1,40 @@
+// 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.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Screens.Play.HUD;
+using osuTK;
+
+namespace osu.Game.Screens.OnlinePlay.Multiplayer
+{
+ public class GameplayMatchScoreDisplay : MatchScoreDisplay
+ {
+ public Bindable Expanded = new Bindable();
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Scale = new Vector2(0.5f);
+
+ Expanded.BindValueChanged(expandedChanged, true);
+ }
+
+ private void expandedChanged(ValueChangedEvent expanded)
+ {
+ if (expanded.NewValue)
+ {
+ Score1Text.FadeIn(500, Easing.OutQuint);
+ Score2Text.FadeIn(500, Easing.OutQuint);
+ this.ResizeWidthTo(2, 500, Easing.OutQuint);
+ }
+ else
+ {
+ Score1Text.FadeOut(500, Easing.OutQuint);
+ Score2Text.FadeOut(500, Easing.OutQuint);
+ this.ResizeWidthTo(1, 500, Easing.OutQuint);
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
index 561fa220c8..9fa19aaf21 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
@@ -475,16 +475,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
protected override Screen CreateGameplayScreen()
{
Debug.Assert(client.LocalUser != null);
+ Debug.Assert(client.Room != null);
int[] userIds = client.CurrentMatchPlayingUserIds.ToArray();
+ MultiplayerRoomUser[] users = userIds.Select(id => client.Room.Users.First(u => u.UserID == id)).ToArray();
switch (client.LocalUser.State)
{
case MultiplayerUserState.Spectating:
- return new MultiSpectatorScreen(userIds);
+ return new MultiSpectatorScreen(users.Take(PlayerGrid.MAX_PLAYERS).ToArray());
default:
- return new PlayerLoader(() => new MultiplayerPlayer(SelectedItem.Value, userIds));
+ return new PlayerLoader(() => new MultiplayerPlayer(SelectedItem.Value, users));
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs
index b54a4a7726..3ba7b8b982 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs
@@ -3,9 +3,12 @@
using System;
using System.Diagnostics;
+using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Logging;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
@@ -34,16 +37,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
private MultiplayerGameplayLeaderboard leaderboard;
- private readonly int[] userIds;
+ private readonly MultiplayerRoomUser[] users;
private LoadingLayer loadingDisplay;
+ private FillFlowContainer leaderboardFlow;
///
/// Construct a multiplayer player.
///
/// The playlist item to be played.
- /// The users which are participating in this game.
- public MultiplayerPlayer(PlaylistItem playlistItem, int[] userIds)
+ /// The users which are participating in this game.
+ public MultiplayerPlayer(PlaylistItem playlistItem, MultiplayerRoomUser[] users)
: base(playlistItem, new PlayerConfiguration
{
AllowPause = false,
@@ -51,14 +55,41 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
AllowSkipping = false,
})
{
- this.userIds = userIds;
+ this.users = users;
}
[BackgroundDependencyLoader]
private void load()
{
+ if (!LoadedBeatmapSuccessfully)
+ return;
+
+ HUDOverlay.Add(leaderboardFlow = new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Vertical,
+ });
+
// todo: this should be implemented via a custom HUD implementation, and correctly masked to the main content area.
- LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(ScoreProcessor, userIds), HUDOverlay.Add);
+ LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(ScoreProcessor, users), l =>
+ {
+ if (!LoadedBeatmapSuccessfully)
+ return;
+
+ ((IBindable)leaderboard.Expanded).BindTo(HUDOverlay.ShowHud);
+
+ leaderboardFlow.Add(l);
+
+ if (leaderboard.TeamScores.Count >= 2)
+ {
+ LoadComponentAsync(new GameplayMatchScoreDisplay
+ {
+ Team1Score = { BindTarget = leaderboard.TeamScores.First().Value },
+ Team2Score = { BindTarget = leaderboard.TeamScores.Last().Value },
+ Expanded = { BindTarget = HUDOverlay.ShowHud },
+ }, leaderboardFlow.Add);
+ }
+ });
HUDOverlay.Add(loadingDisplay = new LoadingLayer(true) { Depth = float.MaxValue });
}
@@ -67,6 +98,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
base.LoadAsyncComplete();
+ if (!LoadedBeatmapSuccessfully)
+ return;
+
if (!ValidForResume)
return; // token retrieval may have failed.
@@ -92,13 +126,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
Debug.Assert(client.Room != null);
}
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- ((IBindable)leaderboard.Expanded).BindTo(HUDOverlay.ShowHud);
- }
-
protected override void StartGameplay()
{
// block base call, but let the server know we are ready to start.
@@ -118,6 +145,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
protected override void Update()
{
base.Update();
+
+ if (!LoadedBeatmapSuccessfully)
+ return;
+
adjustLeaderboardPosition();
}
@@ -125,7 +156,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
const float padding = 44; // enough margin to avoid the hit error display.
- leaderboard.Position = new Vector2(padding, padding + HUDOverlay.TopScoringElementsHeight);
+ leaderboardFlow.Position = new Vector2(padding, padding + HUDOverlay.TopScoringElementsHeight);
}
private void onMatchStarted() => Scheduler.Add(() =>
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs
index 89431445d3..1787480e1f 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
@@ -42,6 +43,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
private ModDisplay userModsDisplay;
private StateDisplay userStateDisplay;
+ private IconButton kickButton;
+
public ParticipantPanel(MultiplayerRoomUser user)
{
User = user;
@@ -64,7 +67,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
{
new Dimension(GridSizeMode.Absolute, 18),
new Dimension(GridSizeMode.AutoSize),
- new Dimension()
+ new Dimension(),
+ new Dimension(GridSizeMode.AutoSize),
},
Content = new[]
{
@@ -157,7 +161,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
Margin = new MarginPadding { Right = 10 },
}
}
- }
+ },
+ kickButton = new KickButton
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Alpha = 0,
+ Margin = new MarginPadding(4),
+ Action = () =>
+ {
+ Debug.Assert(user != null);
+
+ Client.KickUser(user.Id);
+ }
+ },
},
}
};
@@ -167,7 +184,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
{
base.OnRoomUpdated();
- if (Room == null)
+ if (Room == null || Client.LocalUser == null)
return;
const double fade_time = 50;
@@ -179,6 +196,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability);
+ if (Client.IsHost && !User.Equals(Client.LocalUser))
+ kickButton.FadeIn(fade_time);
+ else
+ kickButton.FadeOut(fade_time);
+
if (Room.Host?.Equals(User) == true)
crown.FadeIn(fade_time);
else
@@ -211,13 +233,36 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
new OsuMenuItem("Give host", MenuItemType.Standard, () =>
{
// Ensure the local user is still host.
- if (Room.Host?.UserID != api.LocalUser.Value.Id)
+ if (!Client.IsHost)
return;
Client.TransferHost(targetUser);
+ }),
+ new OsuMenuItem("Kick", MenuItemType.Destructive, () =>
+ {
+ // Ensure the local user is still host.
+ if (!Client.IsHost)
+ return;
+
+ Client.KickUser(targetUser);
})
};
}
}
+
+ public class KickButton : IconButton
+ {
+ public KickButton()
+ {
+ Icon = FontAwesome.Solid.UserTimes;
+ TooltipText = "Kick";
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ IconHoverColour = colours.Red;
+ }
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs
index 55c4270c70..1614828a78 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs
@@ -4,6 +4,7 @@
using System;
using JetBrains.Annotations;
using osu.Framework.Timing;
+using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play.HUD;
@@ -11,8 +12,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
{
public class MultiSpectatorLeaderboard : MultiplayerGameplayLeaderboard
{
- public MultiSpectatorLeaderboard([NotNull] ScoreProcessor scoreProcessor, int[] userIds)
- : base(scoreProcessor, userIds)
+ public MultiSpectatorLeaderboard([NotNull] ScoreProcessor scoreProcessor, MultiplayerRoomUser[] users)
+ : base(scoreProcessor, users)
{
}
@@ -32,7 +33,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
((SpectatingTrackedUserData)data).Clock = null;
}
- protected override TrackedUserData CreateUserData(int userId, ScoreProcessor scoreProcessor) => new SpectatingTrackedUserData(userId, scoreProcessor);
+ protected override TrackedUserData CreateUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor) => new SpectatingTrackedUserData(user, scoreProcessor);
protected override void Update()
{
@@ -47,8 +48,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
[CanBeNull]
public IClock Clock;
- public SpectatingTrackedUserData(int userId, ScoreProcessor scoreProcessor)
- : base(userId, scoreProcessor)
+ public SpectatingTrackedUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor)
+ : base(user, scoreProcessor)
{
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs
index 56ed7a9564..d10917259d 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs
@@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Spectator;
using osu.Game.Screens.Play;
+using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Spectate;
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
@@ -45,20 +46,26 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
private PlayerArea currentAudioSource;
private bool canStartMasterClock;
+ private readonly MultiplayerRoomUser[] users;
+
///
/// Creates a new .
///
- /// The players to spectate.
- public MultiSpectatorScreen(int[] userIds)
- : base(userIds.Take(PlayerGrid.MAX_PLAYERS).ToArray())
+ /// The players to spectate.
+ public MultiSpectatorScreen(MultiplayerRoomUser[] users)
+ : base(users.Select(u => u.UserID).ToArray())
{
- instances = new PlayerArea[UserIds.Count];
+ this.users = users;
+
+ instances = new PlayerArea[Users.Count];
}
[BackgroundDependencyLoader]
private void load()
{
Container leaderboardContainer;
+ Container scoreDisplayContainer;
+
masterClockContainer = new MasterGameplayClockContainer(Beatmap.Value, 0);
InternalChildren = new[]
@@ -67,28 +74,44 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
masterClockContainer.WithChild(new GridContainer
{
RelativeSizeAxes = Axes.Both,
- ColumnDimensions = new[]
- {
- new Dimension(GridSizeMode.AutoSize)
- },
+ RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
Content = new[]
{
new Drawable[]
{
- leaderboardContainer = new Container
+ scoreDisplayContainer = new Container
{
- RelativeSizeAxes = Axes.Y,
- AutoSizeAxes = Axes.X
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y
},
- grid = new PlayerGrid { RelativeSizeAxes = Axes.Both }
+ },
+ new Drawable[]
+ {
+ new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ leaderboardContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Y,
+ AutoSizeAxes = Axes.X
+ },
+ grid = new PlayerGrid { RelativeSizeAxes = Axes.Both }
+ }
+ }
+ }
}
}
})
};
- for (int i = 0; i < UserIds.Count; i++)
+ for (int i = 0; i < Users.Count; i++)
{
- grid.Add(instances[i] = new PlayerArea(UserIds[i], masterClockContainer.GameplayClock));
+ grid.Add(instances[i] = new PlayerArea(Users[i], masterClockContainer.GameplayClock));
syncManager.AddPlayerClock(instances[i].GameplayClock);
}
@@ -97,7 +120,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
var scoreProcessor = Ruleset.Value.CreateInstance().CreateScoreProcessor();
scoreProcessor.ApplyBeatmap(playableBeatmap);
- LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, UserIds.ToArray())
+ LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, users)
{
Expanded = { Value = true },
Anchor = Anchor.CentreLeft,
@@ -108,6 +131,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
leaderboard.AddClock(instance.UserId, instance.GameplayClock);
leaderboardContainer.Add(leaderboard);
+
+ if (leaderboard.TeamScores.Count == 2)
+ {
+ LoadComponentAsync(new MatchScoreDisplay
+ {
+ Team1Score = { BindTarget = leaderboard.TeamScores.First().Value },
+ Team2Score = { BindTarget = leaderboard.TeamScores.Last().Value },
+ }, scoreDisplayContainer.Add);
+ }
});
}
diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
index d1c701974e..978b35e4b1 100644
--- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
@@ -30,6 +30,9 @@ namespace osu.Game.Screens.OnlinePlay
[Cached]
public abstract class OnlinePlayScreen : OsuScreen, IHasSubScreenStack
{
+ [Cached]
+ protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
+
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/Screens/Play/HUD/GameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs
index 34efeab54c..63cb4f89f5 100644
--- a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs
+++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs
@@ -48,10 +48,9 @@ namespace osu.Game.Screens.Play.HUD
///
public ILeaderboardScore AddPlayer([CanBeNull] User user, bool isTracked)
{
- var drawable = new GameplayLeaderboardScore(user, isTracked)
- {
- Expanded = { BindTarget = Expanded },
- };
+ var drawable = CreateLeaderboardScoreDrawable(user, isTracked);
+
+ drawable.Expanded.BindTo(Expanded);
base.Add(drawable);
drawable.TotalScore.BindValueChanged(_ => sorting.Invalidate(), true);
@@ -61,6 +60,9 @@ namespace osu.Game.Screens.Play.HUD
return drawable;
}
+ protected virtual GameplayLeaderboardScore CreateLeaderboardScoreDrawable(User user, bool isTracked) =>
+ new GameplayLeaderboardScore(user, isTracked);
+
public sealed override void Add(GameplayLeaderboardScore drawable)
{
throw new NotSupportedException($"Use {nameof(AddPlayer)} instead.");
diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs
index 10476e5565..433bf78e9b 100644
--- a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs
+++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs
@@ -54,6 +54,10 @@ namespace osu.Game.Screens.Play.HUD
public BindableInt Combo { get; } = new BindableInt();
public BindableBool HasQuit { get; } = new BindableBool();
+ public Color4? BackgroundColour { get; set; }
+
+ public Color4? TextColour { get; set; }
+
private int? scorePosition;
public int? ScorePosition
@@ -331,19 +335,19 @@ namespace osu.Game.Screens.Play.HUD
if (scorePosition == 1)
{
widthExtension = true;
- panelColour = Color4Extensions.FromHex("7fcc33");
- textColour = Color4.White;
+ panelColour = BackgroundColour ?? Color4Extensions.FromHex("7fcc33");
+ textColour = TextColour ?? Color4.White;
}
else if (trackedPlayer)
{
widthExtension = true;
- panelColour = Color4Extensions.FromHex("ffd966");
- textColour = Color4Extensions.FromHex("2e576b");
+ panelColour = BackgroundColour ?? Color4Extensions.FromHex("ffd966");
+ textColour = TextColour ?? Color4Extensions.FromHex("2e576b");
}
else
{
- panelColour = Color4Extensions.FromHex("3399cc");
- textColour = Color4.White;
+ panelColour = BackgroundColour ?? Color4Extensions.FromHex("3399cc");
+ textColour = TextColour ?? Color4.White;
}
this.TransformTo(nameof(SizeContainerLeftPadding), widthExtension ? -top_player_left_width_extension : 0, panel_transition_duration, Easing.OutElastic);
diff --git a/osu.Game/Screens/Play/HUD/MatchScoreDisplay.cs b/osu.Game/Screens/Play/HUD/MatchScoreDisplay.cs
new file mode 100644
index 0000000000..c77b872786
--- /dev/null
+++ b/osu.Game/Screens/Play/HUD/MatchScoreDisplay.cs
@@ -0,0 +1,176 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+using osuTK;
+
+namespace osu.Game.Screens.Play.HUD
+{
+ public class MatchScoreDisplay : CompositeDrawable
+ {
+ private const float bar_height = 18;
+ private const float font_size = 50;
+
+ public BindableInt Team1Score = new BindableInt();
+ public BindableInt Team2Score = new BindableInt();
+
+ protected MatchScoreCounter Score1Text;
+ protected MatchScoreCounter Score2Text;
+
+ private Drawable score1Bar;
+ private Drawable score2Bar;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+
+ InternalChildren = new[]
+ {
+ new Box
+ {
+ Name = "top bar red (static)",
+ RelativeSizeAxes = Axes.X,
+ Height = bar_height / 4,
+ Width = 0.5f,
+ Colour = colours.TeamColourRed,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopRight
+ },
+ new Box
+ {
+ Name = "top bar blue (static)",
+ RelativeSizeAxes = Axes.X,
+ Height = bar_height / 4,
+ Width = 0.5f,
+ Colour = colours.TeamColourBlue,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopLeft
+ },
+ score1Bar = new Box
+ {
+ Name = "top bar red",
+ RelativeSizeAxes = Axes.X,
+ Height = bar_height,
+ Width = 0,
+ Colour = colours.TeamColourRed,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopRight
+ },
+ score2Bar = new Box
+ {
+ Name = "top bar blue",
+ RelativeSizeAxes = Axes.X,
+ Height = bar_height,
+ Width = 0,
+ Colour = colours.TeamColourBlue,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopLeft
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = font_size + bar_height,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Children = new Drawable[]
+ {
+ Score1Text = new MatchScoreCounter
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre
+ },
+ Score2Text = new MatchScoreCounter
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre
+ },
+ }
+ },
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Team1Score.BindValueChanged(_ => updateScores());
+ Team2Score.BindValueChanged(_ => updateScores());
+ }
+
+ private void updateScores()
+ {
+ Score1Text.Current.Value = Team1Score.Value;
+ Score2Text.Current.Value = Team2Score.Value;
+
+ int comparison = Team1Score.Value.CompareTo(Team2Score.Value);
+
+ if (comparison > 0)
+ {
+ Score1Text.Winning = true;
+ Score2Text.Winning = false;
+ }
+ else if (comparison < 0)
+ {
+ Score1Text.Winning = false;
+ Score2Text.Winning = true;
+ }
+ else
+ {
+ Score1Text.Winning = false;
+ Score2Text.Winning = false;
+ }
+
+ var winningBar = Team1Score.Value > Team2Score.Value ? score1Bar : score2Bar;
+ var losingBar = Team1Score.Value <= Team2Score.Value ? score1Bar : score2Bar;
+
+ var diff = Math.Max(Team1Score.Value, Team2Score.Value) - Math.Min(Team1Score.Value, Team2Score.Value);
+
+ losingBar.ResizeWidthTo(0, 400, Easing.OutQuint);
+ winningBar.ResizeWidthTo(Math.Min(0.4f, MathF.Pow(diff / 1500000f, 0.5f) / 2), 400, Easing.OutQuint);
+ }
+
+ protected override void UpdateAfterChildren()
+ {
+ base.UpdateAfterChildren();
+ Score1Text.X = -Math.Max(5 + Score1Text.DrawWidth / 2, score1Bar.DrawWidth);
+ Score2Text.X = Math.Max(5 + Score2Text.DrawWidth / 2, score2Bar.DrawWidth);
+ }
+
+ protected class MatchScoreCounter : ScoreCounter
+ {
+ private OsuSpriteText displayedSpriteText;
+
+ public MatchScoreCounter()
+ {
+ Margin = new MarginPadding { Top = bar_height, Horizontal = 10 };
+ }
+
+ public bool Winning
+ {
+ set => updateFont(value);
+ }
+
+ protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s =>
+ {
+ displayedSpriteText = s;
+ displayedSpriteText.Spacing = new Vector2(-6);
+ updateFont(false);
+ });
+
+ private void updateFont(bool winning)
+ => displayedSpriteText.Font = winning
+ ? OsuFont.Torus.With(weight: FontWeight.Bold, size: font_size, fixedWidth: true)
+ : OsuFont.Torus.With(weight: FontWeight.Regular, size: font_size * 0.8f, fixedWidth: true);
+ }
+ }
+}
diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs
index 7ee77759b0..3f9258930e 100644
--- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs
+++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs
@@ -7,12 +7,17 @@ using System.Collections.Specialized;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Extensions.Color4Extensions;
using osu.Game.Configuration;
using osu.Game.Database;
+using osu.Game.Graphics;
using osu.Game.Online.API;
using osu.Game.Online.Multiplayer;
+using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
using osu.Game.Online.Spectator;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Users;
+using osuTK.Graphics;
namespace osu.Game.Screens.Play.HUD
{
@@ -21,6 +26,11 @@ namespace osu.Game.Screens.Play.HUD
{
protected readonly Dictionary UserScores = new Dictionary();
+ public readonly SortedDictionary TeamScores = new SortedDictionary();
+
+ [Resolved]
+ private OsuColour colours { get; set; }
+
[Resolved]
private SpectatorClient spectatorClient { get; set; }
@@ -31,21 +41,24 @@ namespace osu.Game.Screens.Play.HUD
private UserLookupCache userLookupCache { get; set; }
private readonly ScoreProcessor scoreProcessor;
- private readonly IBindableList playingUsers;
+ private readonly MultiplayerRoomUser[] playingUsers;
private Bindable scoringMode;
+ private readonly IBindableList playingUserIds = new BindableList();
+
+ private bool hasTeams => TeamScores.Count > 0;
+
///
/// Construct a new leaderboard.
///
/// A score processor instance to handle score calculation for scores of users in the match.
- /// IDs of all users in this match.
- public MultiplayerGameplayLeaderboard(ScoreProcessor scoreProcessor, int[] userIds)
+ /// IDs of all users in this match.
+ public MultiplayerGameplayLeaderboard(ScoreProcessor scoreProcessor, MultiplayerRoomUser[] users)
{
// todo: this will eventually need to be created per user to support different mod combinations.
this.scoreProcessor = scoreProcessor;
- // todo: this will likely be passed in as User instances.
- playingUsers = new BindableList(userIds);
+ playingUsers = users;
}
[BackgroundDependencyLoader]
@@ -53,14 +66,17 @@ namespace osu.Game.Screens.Play.HUD
{
scoringMode = config.GetBindable(OsuSetting.ScoreDisplayMode);
- foreach (var userId in playingUsers)
+ foreach (var user in playingUsers)
{
- var trackedUser = CreateUserData(userId, scoreProcessor);
+ var trackedUser = CreateUserData(user, scoreProcessor);
trackedUser.ScoringMode.BindTo(scoringMode);
- UserScores[userId] = trackedUser;
+ UserScores[user.UserID] = trackedUser;
+
+ if (trackedUser.Team is int team && !TeamScores.ContainsKey(team))
+ TeamScores.Add(team, new BindableInt());
}
- userLookupCache.GetUsersAsync(playingUsers.ToArray()).ContinueWith(users => Schedule(() =>
+ userLookupCache.GetUsersAsync(playingUsers.Select(u => u.UserID).ToArray()).ContinueWith(users => Schedule(() =>
{
foreach (var user in users.Result)
{
@@ -83,23 +99,50 @@ namespace osu.Game.Screens.Play.HUD
base.LoadComplete();
// BindableList handles binding in a really bad way (Clear then AddRange) so we need to do this manually..
- foreach (int userId in playingUsers)
+ foreach (var user in playingUsers)
{
- spectatorClient.WatchUser(userId);
+ spectatorClient.WatchUser(user.UserID);
- if (!multiplayerClient.CurrentMatchPlayingUserIds.Contains(userId))
- usersChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { userId }));
+ if (!multiplayerClient.CurrentMatchPlayingUserIds.Contains(user.UserID))
+ usersChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { user.UserID }));
}
// bind here is to support players leaving the match.
// new players are not supported.
- playingUsers.BindTo(multiplayerClient.CurrentMatchPlayingUserIds);
- playingUsers.BindCollectionChanged(usersChanged);
+ playingUserIds.BindTo(multiplayerClient.CurrentMatchPlayingUserIds);
+ playingUserIds.BindCollectionChanged(usersChanged);
// this leaderboard should be guaranteed to be completely loaded before the gameplay starts (is a prerequisite in MultiplayerPlayer).
spectatorClient.OnNewFrames += handleIncomingFrames;
}
+ protected virtual TrackedUserData CreateUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor) => new TrackedUserData(user, scoreProcessor);
+
+ protected override GameplayLeaderboardScore CreateLeaderboardScoreDrawable(User user, bool isTracked)
+ {
+ var leaderboardScore = base.CreateLeaderboardScoreDrawable(user, isTracked);
+
+ if (UserScores[user.Id].Team is int team)
+ {
+ leaderboardScore.BackgroundColour = getTeamColour(team).Lighten(1.2f);
+ leaderboardScore.TextColour = Color4.White;
+ }
+
+ return leaderboardScore;
+ }
+
+ private Color4 getTeamColour(int team)
+ {
+ switch (team)
+ {
+ case 0:
+ return colours.TeamColourRed;
+
+ default:
+ return colours.TeamColourBlue;
+ }
+ }
+
private void usersChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
@@ -124,9 +167,26 @@ namespace osu.Game.Screens.Play.HUD
trackedData.Frames.Add(new TimedFrame(bundle.Frames.First().Time, bundle.Header));
trackedData.UpdateScore();
+
+ updateTotals();
});
- protected virtual TrackedUserData CreateUserData(int userId, ScoreProcessor scoreProcessor) => new TrackedUserData(userId, scoreProcessor);
+ private void updateTotals()
+ {
+ if (!hasTeams)
+ return;
+
+ foreach (var scores in TeamScores.Values) scores.Value = 0;
+
+ foreach (var u in UserScores.Values)
+ {
+ if (u.Team == null)
+ continue;
+
+ if (TeamScores.TryGetValue(u.Team.Value, out var team))
+ team.Value += (int)u.Score.Value;
+ }
+ }
protected override void Dispose(bool isDisposing)
{
@@ -136,7 +196,7 @@ namespace osu.Game.Screens.Play.HUD
{
foreach (var user in playingUsers)
{
- spectatorClient.StopWatchingUser(user);
+ spectatorClient.StopWatchingUser(user.UserID);
}
spectatorClient.OnNewFrames -= handleIncomingFrames;
@@ -145,7 +205,7 @@ namespace osu.Game.Screens.Play.HUD
protected class TrackedUserData
{
- public readonly int UserId;
+ public readonly MultiplayerRoomUser User;
public readonly ScoreProcessor ScoreProcessor;
public readonly BindableDouble Score = new BindableDouble();
@@ -157,9 +217,11 @@ namespace osu.Game.Screens.Play.HUD
public readonly List Frames = new List();
- public TrackedUserData(int userId, ScoreProcessor scoreProcessor)
+ public int? Team => (User.MatchState as TeamVersusUserState)?.TeamID;
+
+ public TrackedUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor)
{
- UserId = userId;
+ User = user;
ScoreProcessor = scoreProcessor;
ScoringMode.BindValueChanged(_ => UpdateScore());
diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs
index b6eafe496f..f0a68ea078 100644
--- a/osu.Game/Screens/Spectate/SpectatorScreen.cs
+++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs
@@ -24,9 +24,9 @@ namespace osu.Game.Screens.Spectate
///
public abstract class SpectatorScreen : OsuScreen
{
- protected IReadOnlyList UserIds => userIds;
+ protected IReadOnlyList Users => users;
- private readonly List userIds = new List();
+ private readonly List users = new List();
[Resolved]
private BeatmapManager beatmaps { get; set; }
@@ -50,17 +50,17 @@ namespace osu.Game.Screens.Spectate
///
/// Creates a new .
///
- /// The users to spectate.
- protected SpectatorScreen(params int[] userIds)
+ /// The users to spectate.
+ protected SpectatorScreen(params int[] users)
{
- this.userIds.AddRange(userIds);
+ this.users.AddRange(users);
}
protected override void LoadComplete()
{
base.LoadComplete();
- userLookupCache.GetUsersAsync(userIds.ToArray()).ContinueWith(users => Schedule(() =>
+ userLookupCache.GetUsersAsync(users.ToArray()).ContinueWith(users => Schedule(() =>
{
foreach (var u in users.Result)
{
@@ -207,7 +207,7 @@ namespace osu.Game.Screens.Spectate
{
onUserStateRemoved(userId);
- userIds.Remove(userId);
+ users.Remove(userId);
userMap.Remove(userId);
spectatorClient.StopWatchingUser(userId);
diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs
index 42345b7266..f259784170 100644
--- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs
+++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs
@@ -36,24 +36,29 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
if (joinRoom)
{
- var room = new Room
- {
- Name = { Value = "test name" },
- Playlist =
- {
- new PlaylistItem
- {
- Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo },
- Ruleset = { Value = Ruleset.Value }
- }
- }
- };
+ var room = CreateRoom();
RoomManager.CreateRoom(room);
SelectedRoom.Value = room;
}
});
+ protected virtual Room CreateRoom()
+ {
+ return new Room
+ {
+ Name = { Value = "test name" },
+ Playlist =
+ {
+ new PlaylistItem
+ {
+ Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo },
+ Ruleset = { Value = Ruleset.Value }
+ }
+ }
+ };
+ }
+
public override void SetUpSteps()
{
base.SetUpSteps();
diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
index cffaea5c94..a28b4140ca 100644
--- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
+++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
@@ -174,6 +174,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
public override Task TransferHost(int userId) => ((IMultiplayerClient)this).HostChanged(userId);
+ public override Task KickUser(int userId)
+ {
+ Debug.Assert(Room != null);
+
+ return ((IMultiplayerClient)this).UserLeft(Room.Users.Single(u => u.UserID == userId));
+ }
+
public override async Task ChangeSettings(MultiplayerRoomSettings settings)
{
Debug.Assert(Room != null);
diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs
index ddbbfe501b..05ba509a73 100644
--- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs
+++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs
@@ -7,6 +7,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Online.Rooms;
+using osu.Game.Overlays;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
@@ -46,6 +47,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay
CacheAs(Filter);
CacheAs(OngoingOperationTracker);
CacheAs(AvailabilityTracker);
+ CacheAs(new OverlayColourProvider(OverlayColourScheme.Plum));
}
public object Get(Type type)
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 59283084db..e6219fcb85 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 c8d3d150db..9904946363 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -93,7 +93,7 @@
-
+