mirror of
https://github.com/ppy/osu.git
synced 2024-11-12 01:07:25 +08:00
Merge branch 'master' into fix-osu-editor-exception
This commit is contained in:
commit
ee4fc049bc
@ -52,7 +52,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.810.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.810.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.811.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Transitive Dependencies">
|
||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||
|
@ -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);
|
||||
|
@ -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<int> playingUserIds = new List<int>();
|
||||
private readonly List<MultiplayerRoomUser> playingUsers = new List<MultiplayerRoomUser>();
|
||||
|
||||
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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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<MultiplayerRoomUser>();
|
||||
|
||||
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,
|
||||
|
@ -0,0 +1,121 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<int> 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<MultiplayerRoomUser>();
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -155,6 +155,42 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("second user crown visible", () => this.ChildrenOfType<ParticipantPanel>().ElementAt(1).ChildrenOfType<SpriteIcon>().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<ParticipantPanel.KickButton>().Count(d => d.IsPresent) == 1);
|
||||
|
||||
AddStep("make second user host", () => Client.TransferHost(3));
|
||||
|
||||
AddUntilStep("kick buttons not visible", () => this.ChildrenOfType<ParticipantPanel.KickButton>().Count(d => d.IsPresent) == 0);
|
||||
|
||||
AddStep("make local user host again", () => Client.TransferHost(API.LocalUser.Value.Id));
|
||||
|
||||
AddUntilStep("kick buttons visible", () => this.ChildrenOfType<ParticipantPanel.KickButton>().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<ParticipantPanel.KickButton>().Single(d => d.IsPresent).TriggerClick());
|
||||
|
||||
AddAssert("second user kicked", () => Client.Room?.Users.Single().UserID == API.LocalUser.Value.Id);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestManyUsers()
|
||||
{
|
||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Tournament.Tests.Components
|
||||
|
||||
public TestSceneMatchScoreDisplay()
|
||||
{
|
||||
Add(new MatchScoreDisplay
|
||||
Add(new TournamentMatchScoreDisplay
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -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;
|
@ -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;
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
|
@ -27,6 +27,14 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// <exception cref="NotJoinedRoomException">If the user is not in a room.</exception>
|
||||
Task TransferHost(int userId);
|
||||
|
||||
/// <summary>
|
||||
/// As the host, kick another user from the room.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user to kick..</param>
|
||||
/// <exception cref="NotHostException">A user other than the current host is attempting to kick a user.</exception>
|
||||
/// <exception cref="NotJoinedRoomException">If the user is not in a room.</exception>
|
||||
Task KickUser(int userId);
|
||||
|
||||
/// <summary>
|
||||
/// As the host, update the settings of the currently joined room.
|
||||
/// </summary>
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -0,0 +1,40 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<bool> Expanded = new Bindable<bool>();
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Scale = new Vector2(0.5f);
|
||||
|
||||
Expanded.BindValueChanged(expandedChanged, true);
|
||||
}
|
||||
|
||||
private void expandedChanged(ValueChangedEvent<bool> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a multiplayer player.
|
||||
/// </summary>
|
||||
/// <param name="playlistItem">The playlist item to be played.</param>
|
||||
/// <param name="userIds">The users which are participating in this game.</param>
|
||||
public MultiplayerPlayer(PlaylistItem playlistItem, int[] userIds)
|
||||
/// <param name="users">The users which are participating in this game.</param>
|
||||
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<bool>)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<bool>)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(() =>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="MultiSpectatorScreen"/>.
|
||||
/// </summary>
|
||||
/// <param name="userIds">The players to spectate.</param>
|
||||
public MultiSpectatorScreen(int[] userIds)
|
||||
: base(userIds.Take(PlayerGrid.MAX_PLAYERS).ToArray())
|
||||
/// <param name="users">The players to spectate.</param>
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -48,10 +48,9 @@ namespace osu.Game.Screens.Play.HUD
|
||||
/// </param>
|
||||
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.");
|
||||
|
@ -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);
|
||||
|
176
osu.Game/Screens/Play/HUD/MatchScoreDisplay.cs
Normal file
176
osu.Game/Screens/Play/HUD/MatchScoreDisplay.cs
Normal file
@ -0,0 +1,176 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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<int, TrackedUserData> UserScores = new Dictionary<int, TrackedUserData>();
|
||||
|
||||
public readonly SortedDictionary<int, BindableInt> TeamScores = new SortedDictionary<int, BindableInt>();
|
||||
|
||||
[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<int> playingUsers;
|
||||
private readonly MultiplayerRoomUser[] playingUsers;
|
||||
private Bindable<ScoringMode> scoringMode;
|
||||
|
||||
private readonly IBindableList<int> playingUserIds = new BindableList<int>();
|
||||
|
||||
private bool hasTeams => TeamScores.Count > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new leaderboard.
|
||||
/// </summary>
|
||||
/// <param name="scoreProcessor">A score processor instance to handle score calculation for scores of users in the match.</param>
|
||||
/// <param name="userIds">IDs of all users in this match.</param>
|
||||
public MultiplayerGameplayLeaderboard(ScoreProcessor scoreProcessor, int[] userIds)
|
||||
/// <param name="users">IDs of all users in this match.</param>
|
||||
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<int>(userIds);
|
||||
playingUsers = users;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -53,14 +66,17 @@ namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
scoringMode = config.GetBindable<ScoringMode>(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<TimedFrame> Frames = new List<TimedFrame>();
|
||||
|
||||
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());
|
||||
|
@ -24,9 +24,9 @@ namespace osu.Game.Screens.Spectate
|
||||
/// </summary>
|
||||
public abstract class SpectatorScreen : OsuScreen
|
||||
{
|
||||
protected IReadOnlyList<int> UserIds => userIds;
|
||||
protected IReadOnlyList<int> Users => users;
|
||||
|
||||
private readonly List<int> userIds = new List<int>();
|
||||
private readonly List<int> users = new List<int>();
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; }
|
||||
@ -50,17 +50,17 @@ namespace osu.Game.Screens.Spectate
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="SpectatorScreen"/>.
|
||||
/// </summary>
|
||||
/// <param name="userIds">The users to spectate.</param>
|
||||
protected SpectatorScreen(params int[] userIds)
|
||||
/// <param name="users">The users to spectate.</param>
|
||||
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);
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -36,7 +36,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="10.3.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2021.810.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2021.811.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.810.0" />
|
||||
<PackageReference Include="Sentry" Version="3.8.3" />
|
||||
<PackageReference Include="SharpCompress" Version="0.28.3" />
|
||||
|
@ -70,7 +70,7 @@
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.810.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.811.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.810.0" />
|
||||
</ItemGroup>
|
||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
||||
@ -93,7 +93,7 @@
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2021.810.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2021.811.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.28.3" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||
|
Loading…
Reference in New Issue
Block a user