mirror of
https://github.com/ppy/osu.git
synced 2024-12-15 02:42:54 +08:00
Merge branch 'master' into fix-osu-editor-exception
This commit is contained in:
commit
ee4fc049bc
@ -52,7 +52,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.810.0" />
|
<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>
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
<!-- 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 NUnit.Framework;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Rulesets.Osu.Scoring;
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Multiplayer
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
{
|
{
|
||||||
@ -31,7 +33,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
};
|
};
|
||||||
|
|
||||||
foreach (var (userId, _) in clocks)
|
foreach (var (userId, _) in clocks)
|
||||||
|
{
|
||||||
SpectatorClient.StartPlay(userId, 0);
|
SpectatorClient.StartPlay(userId, 0);
|
||||||
|
OnlinePlayDependencies.Client.AddUser(new User { Id = userId });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("create leaderboard", () =>
|
AddStep("create leaderboard", () =>
|
||||||
@ -41,7 +46,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
var scoreProcessor = new OsuScoreProcessor();
|
var scoreProcessor = new OsuScoreProcessor();
|
||||||
scoreProcessor.ApplyBeatmap(playable);
|
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);
|
AddUntilStep("wait for load", () => leaderboard.IsLoaded);
|
||||||
|
@ -8,6 +8,8 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
@ -26,7 +28,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
private MultiSpectatorScreen spectatorScreen;
|
private MultiSpectatorScreen spectatorScreen;
|
||||||
|
|
||||||
private readonly List<int> playingUserIds = new List<int>();
|
private readonly List<MultiplayerRoomUser> playingUsers = new List<MultiplayerRoomUser>();
|
||||||
|
|
||||||
private BeatmapSetInfo importedSet;
|
private BeatmapSetInfo importedSet;
|
||||||
private BeatmapInfo importedBeatmap;
|
private BeatmapInfo importedBeatmap;
|
||||||
@ -41,7 +43,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public new void Setup() => Schedule(() => playingUserIds.Clear());
|
public new void Setup() => Schedule(() => playingUsers.Clear());
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestDelayedStart()
|
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_1_ID }, true);
|
||||||
OnlinePlayDependencies.Client.AddUser(new User { Id = PLAYER_2_ID }, true);
|
OnlinePlayDependencies.Client.AddUser(new User { Id = PLAYER_2_ID }, true);
|
||||||
|
|
||||||
playingUserIds.Add(PLAYER_1_ID);
|
playingUsers.Add(new MultiplayerRoomUser(PLAYER_1_ID));
|
||||||
playingUserIds.Add(PLAYER_2_ID);
|
playingUsers.Add(new MultiplayerRoomUser(PLAYER_2_ID));
|
||||||
});
|
});
|
||||||
|
|
||||||
loadSpectateScreen(false);
|
loadSpectateScreen(false);
|
||||||
@ -78,6 +80,38 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddWaitStep("wait a bit", 20);
|
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]
|
[Test]
|
||||||
public void TestTimeDoesNotProgressWhileAllPlayersPaused()
|
public void TestTimeDoesNotProgressWhileAllPlayersPaused()
|
||||||
{
|
{
|
||||||
@ -254,7 +288,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
Beatmap.Value = beatmapManager.GetWorkingBeatmap(importedBeatmap);
|
Beatmap.Value = beatmapManager.GetWorkingBeatmap(importedBeatmap);
|
||||||
Ruleset.Value = importedBeatmap.Ruleset;
|
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));
|
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);
|
OnlinePlayDependencies.Client.AddUser(new User { Id = id }, true);
|
||||||
|
|
||||||
SpectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId);
|
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.Framework.Utils;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Online.Spectator;
|
using osu.Game.Online.Spectator;
|
||||||
using osu.Game.Replays.Legacy;
|
using osu.Game.Replays.Legacy;
|
||||||
using osu.Game.Rulesets.Osu.Scoring;
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
@ -51,12 +52,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
OsuScoreProcessor scoreProcessor;
|
OsuScoreProcessor scoreProcessor;
|
||||||
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
|
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)
|
foreach (var user in users)
|
||||||
{
|
{
|
||||||
SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
|
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[]
|
Children = new Drawable[]
|
||||||
@ -64,9 +66,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
scoreProcessor = new OsuScoreProcessor(),
|
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,
|
Anchor = Anchor.Centre,
|
||||||
Origin = 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);
|
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]
|
[Test]
|
||||||
public void TestManyUsers()
|
public void TestManyUsers()
|
||||||
{
|
{
|
||||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Tournament.Tests.Components
|
|||||||
|
|
||||||
public TestSceneMatchScoreDisplay()
|
public TestSceneMatchScoreDisplay()
|
||||||
{
|
{
|
||||||
Add(new MatchScoreDisplay
|
Add(new TournamentMatchScoreDisplay
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
@ -16,7 +16,8 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Tournament.Screens.Gameplay.Components
|
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;
|
private const float bar_height = 18;
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
|
|||||||
private readonly Drawable score1Bar;
|
private readonly Drawable score1Bar;
|
||||||
private readonly Drawable score2Bar;
|
private readonly Drawable score2Bar;
|
||||||
|
|
||||||
public MatchScoreDisplay()
|
public TournamentMatchScoreDisplay()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
@ -86,7 +86,7 @@ namespace osu.Game.Tournament.Screens.Gameplay
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scoreDisplay = new MatchScoreDisplay
|
scoreDisplay = new TournamentMatchScoreDisplay
|
||||||
{
|
{
|
||||||
Y = -147,
|
Y = -147,
|
||||||
Anchor = Anchor.BottomCentre,
|
Anchor = Anchor.BottomCentre,
|
||||||
@ -148,7 +148,7 @@ namespace osu.Game.Tournament.Screens.Gameplay
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ScheduledDelegate scheduledOperation;
|
private ScheduledDelegate scheduledOperation;
|
||||||
private MatchScoreDisplay scoreDisplay;
|
private TournamentMatchScoreDisplay scoreDisplay;
|
||||||
|
|
||||||
private TourneyState lastState;
|
private TourneyState lastState;
|
||||||
private MatchHeader header;
|
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 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_RED = new OsuColour().TeamColourRed;
|
||||||
public static readonly Color4 COLOUR_BLUE = Color4Extensions.FromHex("#1462AA");
|
public static readonly Color4 COLOUR_BLUE = new OsuColour().TeamColourBlue;
|
||||||
|
|
||||||
public static readonly Color4 ELEMENT_BACKGROUND_COLOUR = Color4Extensions.FromHex("#fff");
|
public static readonly Color4 ELEMENT_BACKGROUND_COLOUR = Color4Extensions.FromHex("#fff");
|
||||||
public static readonly Color4 ELEMENT_FOREGROUND_COLOUR = Color4Extensions.FromHex("#000");
|
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);
|
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
|
// See https://github.com/ppy/osu-web/blob/master/resources/assets/less/colors.less
|
||||||
public readonly Color4 PurpleLighter = Color4Extensions.FromHex(@"eeeeff");
|
public readonly Color4 PurpleLighter = Color4Extensions.FromHex(@"eeeeff");
|
||||||
public readonly Color4 PurpleLight = Color4Extensions.FromHex(@"aa88ff");
|
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>
|
/// <exception cref="NotJoinedRoomException">If the user is not in a room.</exception>
|
||||||
Task TransferHost(int userId);
|
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>
|
/// <summary>
|
||||||
/// As the host, update the settings of the currently joined room.
|
/// As the host, update the settings of the currently joined room.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -293,6 +293,8 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
|
|
||||||
public abstract Task TransferHost(int userId);
|
public abstract Task TransferHost(int userId);
|
||||||
|
|
||||||
|
public abstract Task KickUser(int userId);
|
||||||
|
|
||||||
public abstract Task ChangeSettings(MultiplayerRoomSettings settings);
|
public abstract Task ChangeSettings(MultiplayerRoomSettings settings);
|
||||||
|
|
||||||
public abstract Task ChangeState(MultiplayerUserState newState);
|
public abstract Task ChangeState(MultiplayerUserState newState);
|
||||||
|
@ -91,6 +91,14 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
return connection.InvokeAsync(nameof(IMultiplayerServer.TransferHost), userId);
|
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)
|
public override Task ChangeSettings(MultiplayerRoomSettings settings)
|
||||||
{
|
{
|
||||||
if (!IsConnected.Value)
|
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()
|
protected override Screen CreateGameplayScreen()
|
||||||
{
|
{
|
||||||
Debug.Assert(client.LocalUser != null);
|
Debug.Assert(client.LocalUser != null);
|
||||||
|
Debug.Assert(client.Room != null);
|
||||||
|
|
||||||
int[] userIds = client.CurrentMatchPlayingUserIds.ToArray();
|
int[] userIds = client.CurrentMatchPlayingUserIds.ToArray();
|
||||||
|
MultiplayerRoomUser[] users = userIds.Select(id => client.Room.Users.First(u => u.UserID == id)).ToArray();
|
||||||
|
|
||||||
switch (client.LocalUser.State)
|
switch (client.LocalUser.State)
|
||||||
{
|
{
|
||||||
case MultiplayerUserState.Spectating:
|
case MultiplayerUserState.Spectating:
|
||||||
return new MultiSpectatorScreen(userIds);
|
return new MultiSpectatorScreen(users.Take(PlayerGrid.MAX_PLAYERS).ToArray());
|
||||||
|
|
||||||
default:
|
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;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
@ -34,16 +37,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
|
|
||||||
private MultiplayerGameplayLeaderboard leaderboard;
|
private MultiplayerGameplayLeaderboard leaderboard;
|
||||||
|
|
||||||
private readonly int[] userIds;
|
private readonly MultiplayerRoomUser[] users;
|
||||||
|
|
||||||
private LoadingLayer loadingDisplay;
|
private LoadingLayer loadingDisplay;
|
||||||
|
private FillFlowContainer leaderboardFlow;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Construct a multiplayer player.
|
/// Construct a multiplayer player.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="playlistItem">The playlist item to be played.</param>
|
/// <param name="playlistItem">The playlist item to be played.</param>
|
||||||
/// <param name="userIds">The users which are participating in this game.</param>
|
/// <param name="users">The users which are participating in this game.</param>
|
||||||
public MultiplayerPlayer(PlaylistItem playlistItem, int[] userIds)
|
public MultiplayerPlayer(PlaylistItem playlistItem, MultiplayerRoomUser[] users)
|
||||||
: base(playlistItem, new PlayerConfiguration
|
: base(playlistItem, new PlayerConfiguration
|
||||||
{
|
{
|
||||||
AllowPause = false,
|
AllowPause = false,
|
||||||
@ -51,14 +55,41 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
AllowSkipping = false,
|
AllowSkipping = false,
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
this.userIds = userIds;
|
this.users = users;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
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.
|
// 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 });
|
HUDOverlay.Add(loadingDisplay = new LoadingLayer(true) { Depth = float.MaxValue });
|
||||||
}
|
}
|
||||||
@ -67,6 +98,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
{
|
{
|
||||||
base.LoadAsyncComplete();
|
base.LoadAsyncComplete();
|
||||||
|
|
||||||
|
if (!LoadedBeatmapSuccessfully)
|
||||||
|
return;
|
||||||
|
|
||||||
if (!ValidForResume)
|
if (!ValidForResume)
|
||||||
return; // token retrieval may have failed.
|
return; // token retrieval may have failed.
|
||||||
|
|
||||||
@ -92,13 +126,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
Debug.Assert(client.Room != null);
|
Debug.Assert(client.Room != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
|
|
||||||
((IBindable<bool>)leaderboard.Expanded).BindTo(HUDOverlay.ShowHud);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void StartGameplay()
|
protected override void StartGameplay()
|
||||||
{
|
{
|
||||||
// block base call, but let the server know we are ready to start.
|
// 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()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
|
if (!LoadedBeatmapSuccessfully)
|
||||||
|
return;
|
||||||
|
|
||||||
adjustLeaderboardPosition();
|
adjustLeaderboardPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +156,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
{
|
{
|
||||||
const float padding = 44; // enough margin to avoid the hit error display.
|
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(() =>
|
private void onMatchStarted() => Scheduler.Add(() =>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
@ -42,6 +43,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
private ModDisplay userModsDisplay;
|
private ModDisplay userModsDisplay;
|
||||||
private StateDisplay userStateDisplay;
|
private StateDisplay userStateDisplay;
|
||||||
|
|
||||||
|
private IconButton kickButton;
|
||||||
|
|
||||||
public ParticipantPanel(MultiplayerRoomUser user)
|
public ParticipantPanel(MultiplayerRoomUser user)
|
||||||
{
|
{
|
||||||
User = user;
|
User = user;
|
||||||
@ -64,7 +67,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
{
|
{
|
||||||
new Dimension(GridSizeMode.Absolute, 18),
|
new Dimension(GridSizeMode.Absolute, 18),
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
new Dimension()
|
new Dimension(),
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
},
|
},
|
||||||
Content = new[]
|
Content = new[]
|
||||||
{
|
{
|
||||||
@ -157,7 +161,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
Margin = new MarginPadding { Right = 10 },
|
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();
|
base.OnRoomUpdated();
|
||||||
|
|
||||||
if (Room == null)
|
if (Room == null || Client.LocalUser == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const double fade_time = 50;
|
const double fade_time = 50;
|
||||||
@ -179,6 +196,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
|
|
||||||
userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability);
|
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)
|
if (Room.Host?.Equals(User) == true)
|
||||||
crown.FadeIn(fade_time);
|
crown.FadeIn(fade_time);
|
||||||
else
|
else
|
||||||
@ -211,13 +233,36 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
new OsuMenuItem("Give host", MenuItemType.Standard, () =>
|
new OsuMenuItem("Give host", MenuItemType.Standard, () =>
|
||||||
{
|
{
|
||||||
// Ensure the local user is still host.
|
// Ensure the local user is still host.
|
||||||
if (Room.Host?.UserID != api.LocalUser.Value.Id)
|
if (!Client.IsHost)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Client.TransferHost(targetUser);
|
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 System;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
|
||||||
@ -11,8 +12,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
{
|
{
|
||||||
public class MultiSpectatorLeaderboard : MultiplayerGameplayLeaderboard
|
public class MultiSpectatorLeaderboard : MultiplayerGameplayLeaderboard
|
||||||
{
|
{
|
||||||
public MultiSpectatorLeaderboard([NotNull] ScoreProcessor scoreProcessor, int[] userIds)
|
public MultiSpectatorLeaderboard([NotNull] ScoreProcessor scoreProcessor, MultiplayerRoomUser[] users)
|
||||||
: base(scoreProcessor, userIds)
|
: base(scoreProcessor, users)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +33,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
((SpectatingTrackedUserData)data).Clock = null;
|
((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()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
@ -47,8 +48,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
public IClock Clock;
|
public IClock Clock;
|
||||||
|
|
||||||
public SpectatingTrackedUserData(int userId, ScoreProcessor scoreProcessor)
|
public SpectatingTrackedUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor)
|
||||||
: base(userId, scoreProcessor)
|
: base(user, scoreProcessor)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Online.Spectator;
|
using osu.Game.Online.Spectator;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Screens.Spectate;
|
using osu.Game.Screens.Spectate;
|
||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||||
@ -45,20 +46,26 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
private PlayerArea currentAudioSource;
|
private PlayerArea currentAudioSource;
|
||||||
private bool canStartMasterClock;
|
private bool canStartMasterClock;
|
||||||
|
|
||||||
|
private readonly MultiplayerRoomUser[] users;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="MultiSpectatorScreen"/>.
|
/// Creates a new <see cref="MultiSpectatorScreen"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="userIds">The players to spectate.</param>
|
/// <param name="users">The players to spectate.</param>
|
||||||
public MultiSpectatorScreen(int[] userIds)
|
public MultiSpectatorScreen(MultiplayerRoomUser[] users)
|
||||||
: base(userIds.Take(PlayerGrid.MAX_PLAYERS).ToArray())
|
: base(users.Select(u => u.UserID).ToArray())
|
||||||
{
|
{
|
||||||
instances = new PlayerArea[UserIds.Count];
|
this.users = users;
|
||||||
|
|
||||||
|
instances = new PlayerArea[Users.Count];
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Container leaderboardContainer;
|
Container leaderboardContainer;
|
||||||
|
Container scoreDisplayContainer;
|
||||||
|
|
||||||
masterClockContainer = new MasterGameplayClockContainer(Beatmap.Value, 0);
|
masterClockContainer = new MasterGameplayClockContainer(Beatmap.Value, 0);
|
||||||
|
|
||||||
InternalChildren = new[]
|
InternalChildren = new[]
|
||||||
@ -67,28 +74,44 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
masterClockContainer.WithChild(new GridContainer
|
masterClockContainer.WithChild(new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
ColumnDimensions = new[]
|
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
||||||
{
|
|
||||||
new Dimension(GridSizeMode.AutoSize)
|
|
||||||
},
|
|
||||||
Content = new[]
|
Content = new[]
|
||||||
{
|
{
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
leaderboardContainer = new Container
|
scoreDisplayContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = 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);
|
syncManager.AddPlayerClock(instances[i].GameplayClock);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,7 +120,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
var scoreProcessor = Ruleset.Value.CreateInstance().CreateScoreProcessor();
|
var scoreProcessor = Ruleset.Value.CreateInstance().CreateScoreProcessor();
|
||||||
scoreProcessor.ApplyBeatmap(playableBeatmap);
|
scoreProcessor.ApplyBeatmap(playableBeatmap);
|
||||||
|
|
||||||
LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, UserIds.ToArray())
|
LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, users)
|
||||||
{
|
{
|
||||||
Expanded = { Value = true },
|
Expanded = { Value = true },
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
@ -108,6 +131,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
|||||||
leaderboard.AddClock(instance.UserId, instance.GameplayClock);
|
leaderboard.AddClock(instance.UserId, instance.GameplayClock);
|
||||||
|
|
||||||
leaderboardContainer.Add(leaderboard);
|
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>
|
/// </param>
|
||||||
public ILeaderboardScore AddPlayer([CanBeNull] User user, bool isTracked)
|
public ILeaderboardScore AddPlayer([CanBeNull] User user, bool isTracked)
|
||||||
{
|
{
|
||||||
var drawable = new GameplayLeaderboardScore(user, isTracked)
|
var drawable = CreateLeaderboardScoreDrawable(user, isTracked);
|
||||||
{
|
|
||||||
Expanded = { BindTarget = Expanded },
|
drawable.Expanded.BindTo(Expanded);
|
||||||
};
|
|
||||||
|
|
||||||
base.Add(drawable);
|
base.Add(drawable);
|
||||||
drawable.TotalScore.BindValueChanged(_ => sorting.Invalidate(), true);
|
drawable.TotalScore.BindValueChanged(_ => sorting.Invalidate(), true);
|
||||||
@ -61,6 +60,9 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
return drawable;
|
return drawable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual GameplayLeaderboardScore CreateLeaderboardScoreDrawable(User user, bool isTracked) =>
|
||||||
|
new GameplayLeaderboardScore(user, isTracked);
|
||||||
|
|
||||||
public sealed override void Add(GameplayLeaderboardScore drawable)
|
public sealed override void Add(GameplayLeaderboardScore drawable)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException($"Use {nameof(AddPlayer)} instead.");
|
throw new NotSupportedException($"Use {nameof(AddPlayer)} instead.");
|
||||||
|
@ -54,6 +54,10 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
public BindableInt Combo { get; } = new BindableInt();
|
public BindableInt Combo { get; } = new BindableInt();
|
||||||
public BindableBool HasQuit { get; } = new BindableBool();
|
public BindableBool HasQuit { get; } = new BindableBool();
|
||||||
|
|
||||||
|
public Color4? BackgroundColour { get; set; }
|
||||||
|
|
||||||
|
public Color4? TextColour { get; set; }
|
||||||
|
|
||||||
private int? scorePosition;
|
private int? scorePosition;
|
||||||
|
|
||||||
public int? ScorePosition
|
public int? ScorePosition
|
||||||
@ -331,19 +335,19 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
if (scorePosition == 1)
|
if (scorePosition == 1)
|
||||||
{
|
{
|
||||||
widthExtension = true;
|
widthExtension = true;
|
||||||
panelColour = Color4Extensions.FromHex("7fcc33");
|
panelColour = BackgroundColour ?? Color4Extensions.FromHex("7fcc33");
|
||||||
textColour = Color4.White;
|
textColour = TextColour ?? Color4.White;
|
||||||
}
|
}
|
||||||
else if (trackedPlayer)
|
else if (trackedPlayer)
|
||||||
{
|
{
|
||||||
widthExtension = true;
|
widthExtension = true;
|
||||||
panelColour = Color4Extensions.FromHex("ffd966");
|
panelColour = BackgroundColour ?? Color4Extensions.FromHex("ffd966");
|
||||||
textColour = Color4Extensions.FromHex("2e576b");
|
textColour = TextColour ?? Color4Extensions.FromHex("2e576b");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
panelColour = Color4Extensions.FromHex("3399cc");
|
panelColour = BackgroundColour ?? Color4Extensions.FromHex("3399cc");
|
||||||
textColour = Color4.White;
|
textColour = TextColour ?? Color4.White;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.TransformTo(nameof(SizeContainerLeftPadding), widthExtension ? -top_player_left_width_extension : 0, panel_transition_duration, Easing.OutElastic);
|
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 System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
||||||
using osu.Game.Online.Spectator;
|
using osu.Game.Online.Spectator;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Users;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play.HUD
|
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>();
|
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]
|
[Resolved]
|
||||||
private SpectatorClient spectatorClient { get; set; }
|
private SpectatorClient spectatorClient { get; set; }
|
||||||
|
|
||||||
@ -31,21 +41,24 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
private UserLookupCache userLookupCache { get; set; }
|
private UserLookupCache userLookupCache { get; set; }
|
||||||
|
|
||||||
private readonly ScoreProcessor scoreProcessor;
|
private readonly ScoreProcessor scoreProcessor;
|
||||||
private readonly IBindableList<int> playingUsers;
|
private readonly MultiplayerRoomUser[] playingUsers;
|
||||||
private Bindable<ScoringMode> scoringMode;
|
private Bindable<ScoringMode> scoringMode;
|
||||||
|
|
||||||
|
private readonly IBindableList<int> playingUserIds = new BindableList<int>();
|
||||||
|
|
||||||
|
private bool hasTeams => TeamScores.Count > 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Construct a new leaderboard.
|
/// Construct a new leaderboard.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="scoreProcessor">A score processor instance to handle score calculation for scores of users in the match.</param>
|
/// <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>
|
/// <param name="users">IDs of all users in this match.</param>
|
||||||
public MultiplayerGameplayLeaderboard(ScoreProcessor scoreProcessor, int[] userIds)
|
public MultiplayerGameplayLeaderboard(ScoreProcessor scoreProcessor, MultiplayerRoomUser[] users)
|
||||||
{
|
{
|
||||||
// todo: this will eventually need to be created per user to support different mod combinations.
|
// todo: this will eventually need to be created per user to support different mod combinations.
|
||||||
this.scoreProcessor = scoreProcessor;
|
this.scoreProcessor = scoreProcessor;
|
||||||
|
|
||||||
// todo: this will likely be passed in as User instances.
|
playingUsers = users;
|
||||||
playingUsers = new BindableList<int>(userIds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -53,14 +66,17 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
{
|
{
|
||||||
scoringMode = config.GetBindable<ScoringMode>(OsuSetting.ScoreDisplayMode);
|
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);
|
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)
|
foreach (var user in users.Result)
|
||||||
{
|
{
|
||||||
@ -83,23 +99,50 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
// BindableList handles binding in a really bad way (Clear then AddRange) so we need to do this manually..
|
// 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))
|
if (!multiplayerClient.CurrentMatchPlayingUserIds.Contains(user.UserID))
|
||||||
usersChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { userId }));
|
usersChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { user.UserID }));
|
||||||
}
|
}
|
||||||
|
|
||||||
// bind here is to support players leaving the match.
|
// bind here is to support players leaving the match.
|
||||||
// new players are not supported.
|
// new players are not supported.
|
||||||
playingUsers.BindTo(multiplayerClient.CurrentMatchPlayingUserIds);
|
playingUserIds.BindTo(multiplayerClient.CurrentMatchPlayingUserIds);
|
||||||
playingUsers.BindCollectionChanged(usersChanged);
|
playingUserIds.BindCollectionChanged(usersChanged);
|
||||||
|
|
||||||
// this leaderboard should be guaranteed to be completely loaded before the gameplay starts (is a prerequisite in MultiplayerPlayer).
|
// this leaderboard should be guaranteed to be completely loaded before the gameplay starts (is a prerequisite in MultiplayerPlayer).
|
||||||
spectatorClient.OnNewFrames += handleIncomingFrames;
|
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)
|
private void usersChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
switch (e.Action)
|
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.Frames.Add(new TimedFrame(bundle.Frames.First().Time, bundle.Header));
|
||||||
trackedData.UpdateScore();
|
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)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
@ -136,7 +196,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
{
|
{
|
||||||
foreach (var user in playingUsers)
|
foreach (var user in playingUsers)
|
||||||
{
|
{
|
||||||
spectatorClient.StopWatchingUser(user);
|
spectatorClient.StopWatchingUser(user.UserID);
|
||||||
}
|
}
|
||||||
|
|
||||||
spectatorClient.OnNewFrames -= handleIncomingFrames;
|
spectatorClient.OnNewFrames -= handleIncomingFrames;
|
||||||
@ -145,7 +205,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
protected class TrackedUserData
|
protected class TrackedUserData
|
||||||
{
|
{
|
||||||
public readonly int UserId;
|
public readonly MultiplayerRoomUser User;
|
||||||
public readonly ScoreProcessor ScoreProcessor;
|
public readonly ScoreProcessor ScoreProcessor;
|
||||||
|
|
||||||
public readonly BindableDouble Score = new BindableDouble();
|
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 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;
|
ScoreProcessor = scoreProcessor;
|
||||||
|
|
||||||
ScoringMode.BindValueChanged(_ => UpdateScore());
|
ScoringMode.BindValueChanged(_ => UpdateScore());
|
||||||
|
@ -24,9 +24,9 @@ namespace osu.Game.Screens.Spectate
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class SpectatorScreen : OsuScreen
|
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]
|
[Resolved]
|
||||||
private BeatmapManager beatmaps { get; set; }
|
private BeatmapManager beatmaps { get; set; }
|
||||||
@ -50,17 +50,17 @@ namespace osu.Game.Screens.Spectate
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="SpectatorScreen"/>.
|
/// Creates a new <see cref="SpectatorScreen"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="userIds">The users to spectate.</param>
|
/// <param name="users">The users to spectate.</param>
|
||||||
protected SpectatorScreen(params int[] userIds)
|
protected SpectatorScreen(params int[] users)
|
||||||
{
|
{
|
||||||
this.userIds.AddRange(userIds);
|
this.users.AddRange(users);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
userLookupCache.GetUsersAsync(userIds.ToArray()).ContinueWith(users => Schedule(() =>
|
userLookupCache.GetUsersAsync(users.ToArray()).ContinueWith(users => Schedule(() =>
|
||||||
{
|
{
|
||||||
foreach (var u in users.Result)
|
foreach (var u in users.Result)
|
||||||
{
|
{
|
||||||
@ -207,7 +207,7 @@ namespace osu.Game.Screens.Spectate
|
|||||||
{
|
{
|
||||||
onUserStateRemoved(userId);
|
onUserStateRemoved(userId);
|
||||||
|
|
||||||
userIds.Remove(userId);
|
users.Remove(userId);
|
||||||
userMap.Remove(userId);
|
userMap.Remove(userId);
|
||||||
|
|
||||||
spectatorClient.StopWatchingUser(userId);
|
spectatorClient.StopWatchingUser(userId);
|
||||||
|
@ -36,24 +36,29 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
if (joinRoom)
|
if (joinRoom)
|
||||||
{
|
{
|
||||||
var room = new Room
|
var room = CreateRoom();
|
||||||
{
|
|
||||||
Name = { Value = "test name" },
|
|
||||||
Playlist =
|
|
||||||
{
|
|
||||||
new PlaylistItem
|
|
||||||
{
|
|
||||||
Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo },
|
|
||||||
Ruleset = { Value = Ruleset.Value }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
RoomManager.CreateRoom(room);
|
RoomManager.CreateRoom(room);
|
||||||
SelectedRoom.Value = 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()
|
public override void SetUpSteps()
|
||||||
{
|
{
|
||||||
base.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 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)
|
public override async Task ChangeSettings(MultiplayerRoomSettings settings)
|
||||||
{
|
{
|
||||||
Debug.Assert(Room != null);
|
Debug.Assert(Room != null);
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="10.3.0" />
|
<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="ppy.osu.Game.Resources" Version="2021.810.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.8.3" />
|
<PackageReference Include="Sentry" Version="3.8.3" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.28.3" />
|
<PackageReference Include="SharpCompress" Version="0.28.3" />
|
||||||
|
@ -70,7 +70,7 @@
|
|||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<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" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.810.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
<!-- 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" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<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="SharpCompress" Version="0.28.3" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||||
|
Loading…
Reference in New Issue
Block a user