mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 16:52:54 +08:00
Merge branch 'master' into realtime-ready-button
This commit is contained in:
commit
bcd140b8df
@ -61,12 +61,12 @@ namespace osu.Game.Tests.Visual.Components
|
|||||||
{
|
{
|
||||||
createPoller(true);
|
createPoller(true);
|
||||||
|
|
||||||
AddStep("set poll interval to 1", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust);
|
AddStep("set poll interval to 1", () => poller.TimeBetweenPolls.Value = TimePerAction * safety_adjust);
|
||||||
checkCount(1);
|
checkCount(1);
|
||||||
checkCount(2);
|
checkCount(2);
|
||||||
checkCount(3);
|
checkCount(3);
|
||||||
|
|
||||||
AddStep("set poll interval to 5", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust * 5);
|
AddStep("set poll interval to 5", () => poller.TimeBetweenPolls.Value = TimePerAction * safety_adjust * 5);
|
||||||
checkCount(4);
|
checkCount(4);
|
||||||
checkCount(4);
|
checkCount(4);
|
||||||
checkCount(4);
|
checkCount(4);
|
||||||
@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Components
|
|||||||
checkCount(5);
|
checkCount(5);
|
||||||
checkCount(5);
|
checkCount(5);
|
||||||
|
|
||||||
AddStep("set poll interval to 1", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust);
|
AddStep("set poll interval to 1", () => poller.TimeBetweenPolls.Value = TimePerAction * safety_adjust);
|
||||||
checkCount(6);
|
checkCount(6);
|
||||||
checkCount(7);
|
checkCount(7);
|
||||||
}
|
}
|
||||||
@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Components
|
|||||||
{
|
{
|
||||||
createPoller(false);
|
createPoller(false);
|
||||||
|
|
||||||
AddStep("set poll interval to 1", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust * 5);
|
AddStep("set poll interval to 1", () => poller.TimeBetweenPolls.Value = TimePerAction * safety_adjust * 5);
|
||||||
checkCount(0);
|
checkCount(0);
|
||||||
skip();
|
skip();
|
||||||
checkCount(0);
|
checkCount(0);
|
||||||
@ -141,7 +141,7 @@ namespace osu.Game.Tests.Visual.Components
|
|||||||
|
|
||||||
public class TestSlowPoller : TestPoller
|
public class TestSlowPoller : TestPoller
|
||||||
{
|
{
|
||||||
protected override Task Poll() => Task.Delay((int)(TimeBetweenPolls / 2f / Clock.Rate)).ContinueWith(_ => base.Poll());
|
protected override Task Poll() => Task.Delay((int)(TimeBetweenPolls.Value / 2f / Clock.Rate)).ContinueWith(_ => base.Poll());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,8 @@ using osu.Framework.Timing;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.Ranking;
|
||||||
using osu.Game.Storyboards;
|
using osu.Game.Storyboards;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -50,7 +52,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
cancel();
|
cancel();
|
||||||
complete();
|
complete();
|
||||||
|
|
||||||
AddUntilStep("attempted to push ranking", () => ((FakeRankingPushPlayer)Player).GotoRankingInvoked);
|
AddUntilStep("attempted to push ranking", () => ((FakeRankingPushPlayer)Player).ResultsCreated);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -84,7 +86,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
// wait to ensure there was no attempt of pushing the results screen.
|
// wait to ensure there was no attempt of pushing the results screen.
|
||||||
AddWaitStep("wait", resultsDisplayWaitCount);
|
AddWaitStep("wait", resultsDisplayWaitCount);
|
||||||
AddAssert("no attempt to push ranking", () => !((FakeRankingPushPlayer)Player).GotoRankingInvoked);
|
AddAssert("no attempt to push ranking", () => !((FakeRankingPushPlayer)Player).ResultsCreated);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||||
@ -110,16 +112,18 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
public class FakeRankingPushPlayer : TestPlayer
|
public class FakeRankingPushPlayer : TestPlayer
|
||||||
{
|
{
|
||||||
public bool GotoRankingInvoked;
|
public bool ResultsCreated { get; private set; }
|
||||||
|
|
||||||
public FakeRankingPushPlayer()
|
public FakeRankingPushPlayer()
|
||||||
: base(true, true)
|
: base(true, true)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void GotoRanking()
|
protected override ResultsScreen CreateResults(ScoreInfo score)
|
||||||
{
|
{
|
||||||
GotoRankingInvoked = true;
|
var results = base.CreateResults(score);
|
||||||
|
ResultsCreated = true;
|
||||||
|
return results;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -26,7 +27,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Scale = new Vector2(2),
|
Scale = new Vector2(2),
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
playerScore.Value = 1222333;
|
playerScore.Value = 1222333;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("add player user", () => leaderboard.AddPlayer(playerScore, new User { Username = "You" }));
|
AddStep("add local player", () => createLeaderboardScore(playerScore, new User { Username = "You", Id = 3 }, true));
|
||||||
AddSliderStep("set player score", 50, 5000000, 1222333, v => playerScore.Value = v);
|
AddSliderStep("set player score", 50, 5000000, 1222333, v => playerScore.Value = v);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,8 +49,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
var player2Score = new BindableDouble(1234567);
|
var player2Score = new BindableDouble(1234567);
|
||||||
var player3Score = new BindableDouble(1111111);
|
var player3Score = new BindableDouble(1111111);
|
||||||
|
|
||||||
AddStep("add player 2", () => leaderboard.AddPlayer(player2Score, new User { Username = "Player 2" }));
|
AddStep("add player 2", () => createLeaderboardScore(player2Score, new User { Username = "Player 2" }));
|
||||||
AddStep("add player 3", () => leaderboard.AddPlayer(player3Score, new User { Username = "Player 3" }));
|
AddStep("add player 3", () => createLeaderboardScore(player3Score, new User { Username = "Player 3" }));
|
||||||
|
|
||||||
AddAssert("is player 2 position #1", () => leaderboard.CheckPositionByUsername("Player 2", 1));
|
AddAssert("is player 2 position #1", () => leaderboard.CheckPositionByUsername("Player 2", 1));
|
||||||
AddAssert("is player position #2", () => leaderboard.CheckPositionByUsername("You", 2));
|
AddAssert("is player position #2", () => leaderboard.CheckPositionByUsername("You", 2));
|
||||||
@ -67,6 +67,30 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("is player 2 position #3", () => leaderboard.CheckPositionByUsername("Player 2", 3));
|
AddAssert("is player 2 position #3", () => leaderboard.CheckPositionByUsername("Player 2", 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRandomScores()
|
||||||
|
{
|
||||||
|
int playerNumber = 1;
|
||||||
|
AddRepeatStep("add player with random score", () => createRandomScore(new User { Username = $"Player {playerNumber++}" }), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestExistingUsers()
|
||||||
|
{
|
||||||
|
AddStep("add peppy", () => createRandomScore(new User { Username = "peppy", Id = 2 }));
|
||||||
|
AddStep("add smoogipoo", () => createRandomScore(new User { Username = "smoogipoo", Id = 1040328 }));
|
||||||
|
AddStep("add flyte", () => createRandomScore(new User { Username = "flyte", Id = 3103765 }));
|
||||||
|
AddStep("add frenzibyte", () => createRandomScore(new User { Username = "frenzibyte", Id = 14210502 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createRandomScore(User user) => createLeaderboardScore(new BindableDouble(RNG.Next(0, 5_000_000)), user);
|
||||||
|
|
||||||
|
private void createLeaderboardScore(BindableDouble score, User user, bool isTracked = false)
|
||||||
|
{
|
||||||
|
var leaderboardScore = leaderboard.AddPlayer(user, isTracked);
|
||||||
|
leaderboardScore.TotalScore.BindTo(score);
|
||||||
|
}
|
||||||
|
|
||||||
private class TestGameplayLeaderboard : GameplayLeaderboard
|
private class TestGameplayLeaderboard : GameplayLeaderboard
|
||||||
{
|
{
|
||||||
public bool CheckPositionByUsername(string username, int? expectedPosition)
|
public bool CheckPositionByUsername(string username, int? expectedPosition)
|
||||||
|
@ -0,0 +1,155 @@
|
|||||||
|
// 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 System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Online.Spectator;
|
||||||
|
using osu.Game.Replays.Legacy;
|
||||||
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osu.Game.Tests.Visual.Online;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
|
{
|
||||||
|
public class TestSceneMultiplayerGameplayLeaderboard : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached(typeof(SpectatorStreamingClient))]
|
||||||
|
private TestMultiplayerStreaming streamingClient = new TestMultiplayerStreaming(16);
|
||||||
|
|
||||||
|
[Cached(typeof(UserLookupCache))]
|
||||||
|
private UserLookupCache lookupCache = new TestSceneCurrentlyPlayingDisplay.TestUserLookupCache();
|
||||||
|
|
||||||
|
private MultiplayerGameplayLeaderboard leaderboard;
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content { get; } = new Container { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
|
public TestSceneMultiplayerGameplayLeaderboard()
|
||||||
|
{
|
||||||
|
base.Content.Children = new Drawable[]
|
||||||
|
{
|
||||||
|
streamingClient,
|
||||||
|
lookupCache,
|
||||||
|
Content
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("create leaderboard", () =>
|
||||||
|
{
|
||||||
|
OsuScoreProcessor scoreProcessor;
|
||||||
|
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
|
||||||
|
|
||||||
|
var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
|
||||||
|
|
||||||
|
streamingClient.Start(Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
scoreProcessor = new OsuScoreProcessor(),
|
||||||
|
};
|
||||||
|
|
||||||
|
scoreProcessor.ApplyBeatmap(playable);
|
||||||
|
|
||||||
|
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, streamingClient.PlayingUsers.ToArray())
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
}, Add);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for load", () => leaderboard.IsLoaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScoreUpdates()
|
||||||
|
{
|
||||||
|
AddRepeatStep("update state", () => streamingClient.RandomlyUpdateState(), 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestMultiplayerStreaming : SpectatorStreamingClient
|
||||||
|
{
|
||||||
|
public new BindableList<int> PlayingUsers => (BindableList<int>)base.PlayingUsers;
|
||||||
|
|
||||||
|
private readonly int totalUsers;
|
||||||
|
|
||||||
|
public TestMultiplayerStreaming(int totalUsers)
|
||||||
|
{
|
||||||
|
this.totalUsers = totalUsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start(int beatmapId)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < totalUsers; i++)
|
||||||
|
{
|
||||||
|
((ISpectatorClient)this).UserBeganPlaying(i, new SpectatorState
|
||||||
|
{
|
||||||
|
BeatmapID = beatmapId,
|
||||||
|
RulesetID = 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Dictionary<int, FrameHeader> lastHeaders = new Dictionary<int, FrameHeader>();
|
||||||
|
|
||||||
|
public void RandomlyUpdateState()
|
||||||
|
{
|
||||||
|
foreach (var userId in PlayingUsers)
|
||||||
|
{
|
||||||
|
if (RNG.NextBool())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!lastHeaders.TryGetValue(userId, out var header))
|
||||||
|
{
|
||||||
|
lastHeaders[userId] = header = new FrameHeader(new ScoreInfo
|
||||||
|
{
|
||||||
|
Statistics = new Dictionary<HitResult, int>
|
||||||
|
{
|
||||||
|
[HitResult.Miss] = 0,
|
||||||
|
[HitResult.Meh] = 0,
|
||||||
|
[HitResult.Great] = 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (RNG.Next(0, 3))
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
header.Combo = 0;
|
||||||
|
header.Statistics[HitResult.Miss]++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
header.Combo++;
|
||||||
|
header.MaxCombo = Math.Max(header.MaxCombo, header.Combo);
|
||||||
|
header.Statistics[HitResult.Meh]++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
header.Combo++;
|
||||||
|
header.MaxCombo = Math.Max(header.MaxCombo, header.Combo);
|
||||||
|
header.Statistics[HitResult.Great]++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
((ISpectatorClient)this).UserSentFrames(userId, new FrameDataBundle(header, Array.Empty<LegacyReplayFrame>()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task Connect() => Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -232,7 +232,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
public class TestSpectatorStreamingClient : SpectatorStreamingClient
|
public class TestSpectatorStreamingClient : SpectatorStreamingClient
|
||||||
{
|
{
|
||||||
public readonly User StreamingUser = new User { Id = 1234, Username = "Test user" };
|
public readonly User StreamingUser = new User { Id = 55, Username = "Test user" };
|
||||||
|
|
||||||
public new BindableList<int> PlayingUsers => (BindableList<int>)base.PlayingUsers;
|
public new BindableList<int> PlayingUsers => (BindableList<int>)base.PlayingUsers;
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
public readonly BindableList<Room> Rooms = new BindableList<Room>();
|
public readonly BindableList<Room> Rooms = new BindableList<Room>();
|
||||||
|
|
||||||
public Bindable<bool> InitialRoomsReceived { get; } = new Bindable<bool>(true);
|
public IBindable<bool> InitialRoomsReceived { get; } = new Bindable<bool>(true);
|
||||||
|
|
||||||
IBindableList<Room> IRoomManager.Rooms => Rooms;
|
IBindableList<Room> IRoomManager.Rooms => Rooms;
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
remove { }
|
remove { }
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bindable<bool> InitialRoomsReceived { get; } = new Bindable<bool>(true);
|
public IBindable<bool> InitialRoomsReceived { get; } = new Bindable<bool>(true);
|
||||||
|
|
||||||
public IBindableList<Room> Rooms => null;
|
public IBindableList<Room> Rooms => null;
|
||||||
|
|
||||||
|
@ -151,7 +151,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
remove => throw new NotImplementedException();
|
remove => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bindable<bool> InitialRoomsReceived { get; } = new Bindable<bool>(true);
|
public IBindable<bool> InitialRoomsReceived { get; } = new Bindable<bool>(true);
|
||||||
|
|
||||||
public IBindableList<Room> Rooms { get; } = new BindableList<Room>();
|
public IBindableList<Room> Rooms { get; } = new BindableList<Room>();
|
||||||
|
|
||||||
|
@ -69,8 +69,32 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
|
|
||||||
internal class TestUserLookupCache : UserLookupCache
|
internal class TestUserLookupCache : UserLookupCache
|
||||||
{
|
{
|
||||||
|
private static readonly string[] usernames =
|
||||||
|
{
|
||||||
|
"fieryrage",
|
||||||
|
"Kerensa",
|
||||||
|
"MillhioreF",
|
||||||
|
"Player01",
|
||||||
|
"smoogipoo",
|
||||||
|
"Ephemeral",
|
||||||
|
"BTMC",
|
||||||
|
"Cilvery",
|
||||||
|
"m980",
|
||||||
|
"HappyStick",
|
||||||
|
"LittleEndu",
|
||||||
|
"frenzibyte",
|
||||||
|
"Zallius",
|
||||||
|
"BanchoBot",
|
||||||
|
"rocketminer210",
|
||||||
|
"pishifat"
|
||||||
|
};
|
||||||
|
|
||||||
protected override Task<User> ComputeValueAsync(int lookup, CancellationToken token = default)
|
protected override Task<User> ComputeValueAsync(int lookup, CancellationToken token = default)
|
||||||
=> Task.FromResult(new User { Username = "peppy", Id = 2 });
|
=> Task.FromResult(new User
|
||||||
|
{
|
||||||
|
Id = lookup,
|
||||||
|
Username = usernames[lookup % usernames.Length],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.RealtimeMultiplayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestMultiplayerRoomPartedWhenAPIRoomJoined()
|
public void TestMultiplayerRoomJoinedWhenAPIRoomJoined()
|
||||||
{
|
{
|
||||||
AddStep("create room manager with a room", () =>
|
AddStep("create room manager with a room", () =>
|
||||||
{
|
{
|
||||||
@ -133,7 +133,7 @@ namespace osu.Game.Tests.Visual.RealtimeMultiplayer
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("multiplayer room parted", () => roomContainer.Client.Room != null);
|
AddAssert("multiplayer room joined", () => roomContainer.Client.Room != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TestRealtimeRoomManager createRoomManager()
|
private TestRealtimeRoomManager createRoomManager()
|
||||||
|
@ -57,7 +57,7 @@ namespace osu.Game.Online.Chat
|
|||||||
{
|
{
|
||||||
CurrentChannel.ValueChanged += currentChannelChanged;
|
CurrentChannel.ValueChanged += currentChannelChanged;
|
||||||
|
|
||||||
HighPollRate.BindValueChanged(enabled => TimeBetweenPolls = enabled.NewValue ? 1000 : 6000, true);
|
HighPollRate.BindValueChanged(enabled => TimeBetweenPolls.Value = enabled.NewValue ? 1000 : 6000, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -24,8 +24,8 @@ using osu.Game.Scoring;
|
|||||||
using osu.Game.Users.Drawables;
|
using osu.Game.Users.Drawables;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using Humanizer;
|
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Online.Leaderboards
|
namespace osu.Game.Online.Leaderboards
|
||||||
{
|
{
|
||||||
@ -358,7 +358,7 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Font = OsuFont.GetFont(size: 20, italics: true),
|
Font = OsuFont.GetFont(size: 20, italics: true),
|
||||||
Text = rank == null ? "-" : rank.Value.ToMetric(decimals: rank < 100000 ? 1 : 0),
|
Text = rank == null ? "-" : rank.Value.FormatRank()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
|
|
||||||
@ -19,22 +20,11 @@ namespace osu.Game.Online
|
|||||||
|
|
||||||
private bool pollingActive;
|
private bool pollingActive;
|
||||||
|
|
||||||
private double timeBetweenPolls;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The time in milliseconds to wait between polls.
|
/// The time in milliseconds to wait between polls.
|
||||||
/// Setting to zero stops all polling.
|
/// Setting to zero stops all polling.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double TimeBetweenPolls
|
public readonly Bindable<double> TimeBetweenPolls = new Bindable<double>();
|
||||||
{
|
|
||||||
get => timeBetweenPolls;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
timeBetweenPolls = value;
|
|
||||||
scheduledPoll?.Cancel();
|
|
||||||
pollIfNecessary();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
@ -42,7 +32,13 @@ namespace osu.Game.Online
|
|||||||
/// <param name="timeBetweenPolls">The initial time in milliseconds to wait between polls. Setting to zero stops all polling.</param>
|
/// <param name="timeBetweenPolls">The initial time in milliseconds to wait between polls. Setting to zero stops all polling.</param>
|
||||||
protected PollingComponent(double timeBetweenPolls = 0)
|
protected PollingComponent(double timeBetweenPolls = 0)
|
||||||
{
|
{
|
||||||
TimeBetweenPolls = timeBetweenPolls;
|
TimeBetweenPolls.BindValueChanged(_ =>
|
||||||
|
{
|
||||||
|
scheduledPoll?.Cancel();
|
||||||
|
pollIfNecessary();
|
||||||
|
});
|
||||||
|
|
||||||
|
TimeBetweenPolls.Value = timeBetweenPolls;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -60,7 +56,7 @@ namespace osu.Game.Online
|
|||||||
if (pollingActive) return false;
|
if (pollingActive) return false;
|
||||||
|
|
||||||
// don't try polling if the time between polls hasn't been set.
|
// don't try polling if the time between polls hasn't been set.
|
||||||
if (timeBetweenPolls == 0) return false;
|
if (TimeBetweenPolls.Value == 0) return false;
|
||||||
|
|
||||||
if (!lastTimePolled.HasValue)
|
if (!lastTimePolled.HasValue)
|
||||||
{
|
{
|
||||||
@ -68,7 +64,7 @@ namespace osu.Game.Online
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Time.Current - lastTimePolled.Value > timeBetweenPolls)
|
if (Time.Current - lastTimePolled.Value > TimeBetweenPolls.Value)
|
||||||
{
|
{
|
||||||
doPoll();
|
doPoll();
|
||||||
return true;
|
return true;
|
||||||
@ -99,7 +95,7 @@ namespace osu.Game.Online
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void PollImmediately()
|
public void PollImmediately()
|
||||||
{
|
{
|
||||||
lastTimePolled = Time.Current - timeBetweenPolls;
|
lastTimePolled = Time.Current - TimeBetweenPolls.Value;
|
||||||
scheduleNextPoll();
|
scheduleNextPoll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +117,7 @@ namespace osu.Game.Online
|
|||||||
|
|
||||||
double lastPollDuration = lastTimePolled.HasValue ? Time.Current - lastTimePolled.Value : 0;
|
double lastPollDuration = lastTimePolled.HasValue ? Time.Current - lastTimePolled.Value : 0;
|
||||||
|
|
||||||
scheduledPoll = Scheduler.AddDelayed(doPoll, Math.Max(0, timeBetweenPolls - lastPollDuration));
|
scheduledPoll = Scheduler.AddDelayed(doPoll, Math.Max(0, TimeBetweenPolls.Value - lastPollDuration));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,11 +9,13 @@ 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.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Online.Multiplayer.RoomStatuses;
|
using osu.Game.Online.Multiplayer.RoomStatuses;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
@ -69,7 +71,9 @@ namespace osu.Game.Online.RealtimeMultiplayer
|
|||||||
private RulesetStore rulesets { get; set; } = null!;
|
private RulesetStore rulesets { get; set; } = null!;
|
||||||
|
|
||||||
private Room? apiRoom;
|
private Room? apiRoom;
|
||||||
private int playlistItemId; // Todo: THIS IS SUPER TEMPORARY!!
|
|
||||||
|
// Todo: This is temporary, until the multiplayer server returns the item id on match start or otherwise.
|
||||||
|
private int playlistItemId;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Joins the <see cref="MultiplayerRoom"/> for a given API <see cref="Room"/>.
|
/// Joins the <see cref="MultiplayerRoom"/> for a given API <see cref="Room"/>.
|
||||||
@ -142,20 +146,14 @@ namespace osu.Game.Online.RealtimeMultiplayer
|
|||||||
RulesetID = Room.Settings.RulesetID
|
RulesetID = Room.Settings.RulesetID
|
||||||
};
|
};
|
||||||
|
|
||||||
var newSettings = new MultiplayerRoomSettings
|
ChangeSettings(new MultiplayerRoomSettings
|
||||||
{
|
{
|
||||||
Name = name.GetOr(Room.Settings.Name),
|
Name = name.GetOr(Room.Settings.Name),
|
||||||
BeatmapID = item.GetOr(existingPlaylistItem).BeatmapID,
|
BeatmapID = item.GetOr(existingPlaylistItem).BeatmapID,
|
||||||
BeatmapChecksum = item.GetOr(existingPlaylistItem).Beatmap.Value.MD5Hash,
|
BeatmapChecksum = item.GetOr(existingPlaylistItem).Beatmap.Value.MD5Hash,
|
||||||
RulesetID = item.GetOr(existingPlaylistItem).RulesetID,
|
RulesetID = item.GetOr(existingPlaylistItem).RulesetID,
|
||||||
Mods = item.HasValue ? item.Value!.RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.Mods
|
Mods = item.HasValue ? item.Value.AsNonNull().RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.Mods
|
||||||
};
|
});
|
||||||
|
|
||||||
// Make sure there would be a meaningful change in settings.
|
|
||||||
if (newSettings.Equals(Room.Settings))
|
|
||||||
return;
|
|
||||||
|
|
||||||
ChangeSettings(newSettings);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Task TransferHost(int userId);
|
public abstract Task TransferHost(int userId);
|
||||||
@ -356,7 +354,7 @@ namespace osu.Game.Online.RealtimeMultiplayer
|
|||||||
if (Room == null)
|
if (Room == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Update a few instantaneously properties of the room.
|
// Update a few properties of the room instantaneously.
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
if (Room == null)
|
if (Room == null)
|
||||||
@ -375,38 +373,36 @@ namespace osu.Game.Online.RealtimeMultiplayer
|
|||||||
});
|
});
|
||||||
|
|
||||||
var req = new GetBeatmapSetRequest(settings.BeatmapID, BeatmapSetLookupType.BeatmapId);
|
var req = new GetBeatmapSetRequest(settings.BeatmapID, BeatmapSetLookupType.BeatmapId);
|
||||||
req.Success += res =>
|
req.Success += res => updatePlaylist(settings, res);
|
||||||
{
|
|
||||||
var beatmapSet = res.ToBeatmapSet(rulesets);
|
|
||||||
|
|
||||||
var beatmap = beatmapSet.Beatmaps.Single(b => b.OnlineBeatmapID == settings.BeatmapID);
|
|
||||||
beatmap.MD5Hash = settings.BeatmapChecksum;
|
|
||||||
|
|
||||||
var ruleset = rulesets.GetRuleset(settings.RulesetID);
|
|
||||||
var mods = settings.Mods.Select(m => m.ToMod(ruleset.CreateInstance()));
|
|
||||||
|
|
||||||
PlaylistItem playlistItem = new PlaylistItem
|
|
||||||
{
|
|
||||||
ID = playlistItemId,
|
|
||||||
Beatmap = { Value = beatmap },
|
|
||||||
Ruleset = { Value = ruleset },
|
|
||||||
};
|
|
||||||
|
|
||||||
playlistItem.RequiredMods.AddRange(mods);
|
|
||||||
|
|
||||||
Schedule(() =>
|
|
||||||
{
|
|
||||||
if (Room == null || !Room.Settings.Equals(settings))
|
|
||||||
return;
|
|
||||||
|
|
||||||
Debug.Assert(apiRoom != null);
|
|
||||||
|
|
||||||
apiRoom.Playlist.Clear(); // Clearing should be unnecessary, but here for sanity.
|
|
||||||
apiRoom.Playlist.Add(playlistItem);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
api.Queue(req);
|
api.Queue(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updatePlaylist(MultiplayerRoomSettings settings, APIBeatmapSet onlineSet)
|
||||||
|
{
|
||||||
|
if (Room == null || !Room.Settings.Equals(settings))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Debug.Assert(apiRoom != null);
|
||||||
|
|
||||||
|
var beatmapSet = onlineSet.ToBeatmapSet(rulesets);
|
||||||
|
var beatmap = beatmapSet.Beatmaps.Single(b => b.OnlineBeatmapID == settings.BeatmapID);
|
||||||
|
beatmap.MD5Hash = settings.BeatmapChecksum;
|
||||||
|
|
||||||
|
var ruleset = rulesets.GetRuleset(settings.RulesetID).CreateInstance();
|
||||||
|
var mods = settings.Mods.Select(m => m.ToMod(ruleset));
|
||||||
|
|
||||||
|
PlaylistItem playlistItem = new PlaylistItem
|
||||||
|
{
|
||||||
|
ID = playlistItemId,
|
||||||
|
Beatmap = { Value = beatmap },
|
||||||
|
Ruleset = { Value = ruleset.RulesetInfo },
|
||||||
|
};
|
||||||
|
|
||||||
|
playlistItem.RequiredMods.AddRange(mods);
|
||||||
|
|
||||||
|
apiRoom.Playlist.Clear(); // Clearing should be unnecessary, but here for sanity.
|
||||||
|
apiRoom.Playlist.Add(playlistItem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,12 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
private readonly double comboPortion;
|
private readonly double comboPortion;
|
||||||
|
|
||||||
private int maxAchievableCombo;
|
private int maxAchievableCombo;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum achievable base score.
|
||||||
|
/// </summary>
|
||||||
private double maxBaseScore;
|
private double maxBaseScore;
|
||||||
|
|
||||||
private double rollingMaxBaseScore;
|
private double rollingMaxBaseScore;
|
||||||
private double baseScore;
|
private double baseScore;
|
||||||
|
|
||||||
@ -196,8 +201,8 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
private double getScore(ScoringMode mode)
|
private double getScore(ScoringMode mode)
|
||||||
{
|
{
|
||||||
return GetScore(mode, maxAchievableCombo,
|
return GetScore(mode, maxAchievableCombo,
|
||||||
maxBaseScore > 0 ? baseScore / maxBaseScore : 0,
|
calculateAccuracyRatio(baseScore),
|
||||||
maxAchievableCombo > 0 ? (double)HighestCombo.Value / maxAchievableCombo : 1,
|
calculateComboRatio(HighestCombo.Value),
|
||||||
scoreResultCounts);
|
scoreResultCounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,6 +232,37 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Given a minimal set of inputs, return the computed score and accuracy for the tracked beatmap / mods combination.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mode">The <see cref="ScoringMode"/> to compute the total score in.</param>
|
||||||
|
/// <param name="maxCombo">The maximum combo achievable in the beatmap.</param>
|
||||||
|
/// <param name="statistics">Statistics to be used for calculating accuracy, bonus score, etc.</param>
|
||||||
|
/// <returns>The computed score and accuracy for provided inputs.</returns>
|
||||||
|
public (double score, double accuracy) GetScoreAndAccuracy(ScoringMode mode, int maxCombo, Dictionary<HitResult, int> statistics)
|
||||||
|
{
|
||||||
|
// calculate base score from statistics pairs
|
||||||
|
int computedBaseScore = 0;
|
||||||
|
|
||||||
|
foreach (var pair in statistics)
|
||||||
|
{
|
||||||
|
if (!pair.Key.AffectsAccuracy())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
computedBaseScore += Judgement.ToNumericResult(pair.Key) * pair.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
double accuracy = calculateAccuracyRatio(computedBaseScore);
|
||||||
|
double comboRatio = calculateComboRatio(maxCombo);
|
||||||
|
|
||||||
|
double score = GetScore(mode, maxAchievableCombo, accuracy, comboRatio, scoreResultCounts);
|
||||||
|
|
||||||
|
return (score, accuracy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double calculateAccuracyRatio(double baseScore) => maxBaseScore > 0 ? baseScore / maxBaseScore : 0;
|
||||||
|
private double calculateComboRatio(int maxCombo) => maxAchievableCombo > 0 ? (double)maxCombo / maxAchievableCombo : 1;
|
||||||
|
|
||||||
private double getBonusScore(Dictionary<HitResult, int> statistics)
|
private double getBonusScore(Dictionary<HitResult, int> statistics)
|
||||||
=> statistics.GetOrDefault(HitResult.SmallBonus) * Judgement.SMALL_BONUS_SCORE
|
=> statistics.GetOrDefault(HitResult.SmallBonus) * Judgement.SMALL_BONUS_SCORE
|
||||||
+ statistics.GetOrDefault(HitResult.LargeBonus) * Judgement.LARGE_BONUS_SCORE;
|
+ statistics.GetOrDefault(HitResult.LargeBonus) * Judgement.LARGE_BONUS_SCORE;
|
||||||
|
@ -92,6 +92,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Container dragHandles;
|
||||||
private FillFlowContainer buttons;
|
private FillFlowContainer buttons;
|
||||||
|
|
||||||
public const float BORDER_RADIUS = 3;
|
public const float BORDER_RADIUS = 3;
|
||||||
@ -151,6 +152,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
dragHandles = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
// ensures that the centres of all drag handles line up with the middle of the selection box border.
|
||||||
|
Padding = new MarginPadding(BORDER_RADIUS / 2)
|
||||||
|
},
|
||||||
buttons = new FillFlowContainer
|
buttons = new FillFlowContainer
|
||||||
{
|
{
|
||||||
Y = 20,
|
Y = 20,
|
||||||
@ -232,7 +239,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addDragHandle(Anchor anchor) => AddInternal(new SelectionBoxDragHandle
|
private void addDragHandle(Anchor anchor) => dragHandles.Add(new SelectionBoxDragHandle
|
||||||
{
|
{
|
||||||
Anchor = anchor,
|
Anchor = anchor,
|
||||||
HandleDrag = e => OnScale?.Invoke(e.Delta, anchor),
|
HandleDrag = e => OnScale?.Invoke(e.Delta, anchor),
|
||||||
|
@ -25,6 +25,7 @@ namespace osu.Game.Screens.Multi.Components
|
|||||||
{
|
{
|
||||||
currentFilter.BindValueChanged(_ =>
|
currentFilter.BindValueChanged(_ =>
|
||||||
{
|
{
|
||||||
|
NotifyRoomsReceived(null);
|
||||||
if (IsLoaded)
|
if (IsLoaded)
|
||||||
PollImmediately();
|
PollImmediately();
|
||||||
});
|
});
|
||||||
|
@ -23,11 +23,13 @@ namespace osu.Game.Screens.Multi.Components
|
|||||||
|
|
||||||
private readonly BindableList<Room> rooms = new BindableList<Room>();
|
private readonly BindableList<Room> rooms = new BindableList<Room>();
|
||||||
|
|
||||||
public Bindable<bool> InitialRoomsReceived { get; } = new Bindable<bool>();
|
public IBindable<bool> InitialRoomsReceived => initialRoomsReceived;
|
||||||
|
private readonly Bindable<bool> initialRoomsReceived = new Bindable<bool>();
|
||||||
|
|
||||||
public IBindableList<Room> Rooms => rooms;
|
public IBindableList<Room> Rooms => rooms;
|
||||||
|
|
||||||
protected Room JoinedRoom { get; private set; }
|
protected IBindable<Room> JoinedRoom => joinedRoom;
|
||||||
|
private readonly Bindable<Room> joinedRoom = new Bindable<Room>();
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private RulesetStore rulesets { get; set; }
|
private RulesetStore rulesets { get; set; }
|
||||||
@ -44,7 +46,6 @@ namespace osu.Game.Screens.Multi.Components
|
|||||||
|
|
||||||
InternalChildren = CreatePollingComponents().Select(p =>
|
InternalChildren = CreatePollingComponents().Select(p =>
|
||||||
{
|
{
|
||||||
p.InitialRoomsReceived.BindTo(InitialRoomsReceived);
|
|
||||||
p.RoomsReceived = onRoomsReceived;
|
p.RoomsReceived = onRoomsReceived;
|
||||||
return p;
|
return p;
|
||||||
}).ToList();
|
}).ToList();
|
||||||
@ -64,7 +65,7 @@ namespace osu.Game.Screens.Multi.Components
|
|||||||
|
|
||||||
req.Success += result =>
|
req.Success += result =>
|
||||||
{
|
{
|
||||||
JoinedRoom = room;
|
joinedRoom.Value = room;
|
||||||
|
|
||||||
update(room, result);
|
update(room, result);
|
||||||
addRoom(room);
|
addRoom(room);
|
||||||
@ -93,7 +94,7 @@ namespace osu.Game.Screens.Multi.Components
|
|||||||
|
|
||||||
currentJoinRoomRequest.Success += () =>
|
currentJoinRoomRequest.Success += () =>
|
||||||
{
|
{
|
||||||
JoinedRoom = room;
|
joinedRoom.Value = room;
|
||||||
onSuccess?.Invoke(room);
|
onSuccess?.Invoke(room);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -114,14 +115,20 @@ namespace osu.Game.Screens.Multi.Components
|
|||||||
if (JoinedRoom == null)
|
if (JoinedRoom == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
api.Queue(new PartRoomRequest(JoinedRoom));
|
api.Queue(new PartRoomRequest(joinedRoom.Value));
|
||||||
JoinedRoom = null;
|
joinedRoom.Value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly HashSet<int> ignoredRooms = new HashSet<int>();
|
private readonly HashSet<int> ignoredRooms = new HashSet<int>();
|
||||||
|
|
||||||
private void onRoomsReceived(List<Room> received)
|
private void onRoomsReceived(List<Room> received)
|
||||||
{
|
{
|
||||||
|
if (received == null)
|
||||||
|
{
|
||||||
|
ClearRooms();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Remove past matches
|
// Remove past matches
|
||||||
foreach (var r in rooms.ToList())
|
foreach (var r in rooms.ToList())
|
||||||
{
|
{
|
||||||
@ -155,6 +162,7 @@ namespace osu.Game.Screens.Multi.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
RoomsUpdated?.Invoke();
|
RoomsUpdated?.Invoke();
|
||||||
|
initialRoomsReceived.Value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void RemoveRoom(Room room) => rooms.Remove(room);
|
protected void RemoveRoom(Room room) => rooms.Remove(room);
|
||||||
@ -162,7 +170,7 @@ namespace osu.Game.Screens.Multi.Components
|
|||||||
protected void ClearRooms()
|
protected void ClearRooms()
|
||||||
{
|
{
|
||||||
rooms.Clear();
|
rooms.Clear();
|
||||||
InitialRoomsReceived.Value = false;
|
initialRoomsReceived.Value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -191,6 +199,6 @@ namespace osu.Game.Screens.Multi.Components
|
|||||||
existing.CopyFrom(room);
|
existing.CopyFrom(room);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract RoomPollingComponent[] CreatePollingComponents();
|
protected abstract IEnumerable<RoomPollingComponent> CreatePollingComponents();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Game.Online;
|
using osu.Game.Online;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
@ -13,29 +12,18 @@ namespace osu.Game.Screens.Multi.Components
|
|||||||
{
|
{
|
||||||
public abstract class RoomPollingComponent : PollingComponent
|
public abstract class RoomPollingComponent : PollingComponent
|
||||||
{
|
{
|
||||||
public Action<List<Room>> RoomsReceived;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The time in milliseconds to wait between polls.
|
/// Invoked when any <see cref="Room"/>s have been received from the API.
|
||||||
/// Setting to zero stops all polling.
|
/// <para>
|
||||||
|
/// Any <see cref="Room"/>s present locally but not returned by this event are to be removed from display.
|
||||||
|
/// If null, the display of local rooms is reset to an initial state.
|
||||||
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public new readonly Bindable<double> TimeBetweenPolls = new Bindable<double>();
|
public Action<List<Room>> RoomsReceived;
|
||||||
|
|
||||||
public IBindable<bool> InitialRoomsReceived => initialRoomsReceived;
|
|
||||||
private readonly Bindable<bool> initialRoomsReceived = new Bindable<bool>();
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
protected IAPIProvider API { get; private set; }
|
protected IAPIProvider API { get; private set; }
|
||||||
|
|
||||||
protected RoomPollingComponent()
|
protected void NotifyRoomsReceived(List<Room> rooms) => RoomsReceived?.Invoke(rooms);
|
||||||
{
|
|
||||||
TimeBetweenPolls.BindValueChanged(time => base.TimeBetweenPolls = time.NewValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void NotifyRoomsReceived(List<Room> rooms)
|
|
||||||
{
|
|
||||||
initialRoomsReceived.Value = true;
|
|
||||||
RoomsReceived?.Invoke(rooms);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Screens.Multi
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether an initial listing of rooms has been received.
|
/// Whether an initial listing of rooms has been received.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Bindable<bool> InitialRoomsReceived { get; }
|
IBindable<bool> InitialRoomsReceived { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All the active <see cref="Room"/>s.
|
/// All the active <see cref="Room"/>s.
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Screens.Multi.Lounge
|
|||||||
|
|
||||||
protected override UserActivity InitialActivity => new UserActivity.SearchingForLobby();
|
protected override UserActivity InitialActivity => new UserActivity.SearchingForLobby();
|
||||||
|
|
||||||
private readonly Bindable<bool> initialRoomsReceived = new Bindable<bool>();
|
private readonly IBindable<bool> initialRoomsReceived = new Bindable<bool>();
|
||||||
|
|
||||||
private Container content;
|
private Container content;
|
||||||
private LoadingLayer loadingLayer;
|
private LoadingLayer loadingLayer;
|
||||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Screens.Multi
|
|||||||
private readonly IBindable<bool> isIdle = new BindableBool();
|
private readonly IBindable<bool> isIdle = new BindableBool();
|
||||||
|
|
||||||
[Cached(Type = typeof(IRoomManager))]
|
[Cached(Type = typeof(IRoomManager))]
|
||||||
protected IRoomManager RoomManager { get; private set; }
|
protected RoomManager RoomManager { get; private set; }
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private readonly Bindable<Room> selectedRoom = new Bindable<Room>();
|
private readonly Bindable<Room> selectedRoom = new Bindable<Room>();
|
||||||
@ -81,7 +81,7 @@ namespace osu.Game.Screens.Multi
|
|||||||
InternalChild = waves = new MultiplayerWaveContainer
|
InternalChild = waves = new MultiplayerWaveContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Box
|
new Box
|
||||||
{
|
{
|
||||||
@ -136,7 +136,7 @@ namespace osu.Game.Screens.Multi
|
|||||||
Origin = Anchor.TopRight,
|
Origin = Anchor.TopRight,
|
||||||
Action = () => CreateRoom()
|
Action = () => CreateRoom()
|
||||||
},
|
},
|
||||||
(Drawable)(RoomManager = CreateRoomManager())
|
RoomManager = CreateRoomManager()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -353,7 +353,7 @@ namespace osu.Game.Screens.Multi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract IRoomManager CreateRoomManager();
|
protected abstract RoomManager CreateRoomManager();
|
||||||
|
|
||||||
private class MultiplayerWaveContainer : WaveContainer
|
private class MultiplayerWaveContainer : WaveContainer
|
||||||
{
|
{
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
@ -95,19 +96,36 @@ namespace osu.Game.Screens.Multi.Play
|
|||||||
return new TimeshiftResultsScreen(score, roomId.Value.Value, playlistItem, true);
|
return new TimeshiftResultsScreen(score, roomId.Value.Value, playlistItem, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ScoreInfo CreateScore()
|
protected override Score CreateScore()
|
||||||
{
|
{
|
||||||
var score = base.CreateScore();
|
var score = base.CreateScore();
|
||||||
score.TotalScore = (int)Math.Round(ScoreProcessor.GetStandardisedScore());
|
score.ScoreInfo.TotalScore = (int)Math.Round(ScoreProcessor.GetStandardisedScore());
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task SubmitScore(Score score)
|
||||||
|
{
|
||||||
|
await base.SubmitScore(score);
|
||||||
|
|
||||||
Debug.Assert(token != null);
|
Debug.Assert(token != null);
|
||||||
|
|
||||||
var request = new SubmitRoomScoreRequest(token.Value, roomId.Value ?? 0, playlistItem.ID, score);
|
var tcs = new TaskCompletionSource<bool>();
|
||||||
request.Success += s => score.OnlineScoreID = s.ID;
|
var request = new SubmitRoomScoreRequest(token.Value, roomId.Value ?? 0, playlistItem.ID, score.ScoreInfo);
|
||||||
request.Failure += e => Logger.Error(e, "Failed to submit score");
|
|
||||||
api.Queue(request);
|
|
||||||
|
|
||||||
return score;
|
request.Success += s =>
|
||||||
|
{
|
||||||
|
score.ScoreInfo.OnlineScoreID = s.ID;
|
||||||
|
tcs.SetResult(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
request.Failure += e =>
|
||||||
|
{
|
||||||
|
Logger.Error(e, "Failed to submit score");
|
||||||
|
tcs.SetResult(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
api.Queue(request);
|
||||||
|
await tcs.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
|
@ -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;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -30,7 +31,8 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
isConnected.BindTo(multiplayerClient.IsConnected);
|
isConnected.BindTo(multiplayerClient.IsConnected);
|
||||||
isConnected.BindValueChanged(_ => Schedule(updatePolling), true);
|
isConnected.BindValueChanged(_ => Schedule(updatePolling));
|
||||||
|
JoinedRoom.BindValueChanged(_ => updatePolling());
|
||||||
|
|
||||||
updatePolling();
|
updatePolling();
|
||||||
}
|
}
|
||||||
@ -46,15 +48,18 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer
|
|||||||
if (JoinedRoom == null)
|
if (JoinedRoom == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var joinedRoom = JoinedRoom;
|
var joinedRoom = JoinedRoom.Value;
|
||||||
|
|
||||||
base.PartRoom();
|
base.PartRoom();
|
||||||
multiplayerClient.LeaveRoom();
|
multiplayerClient.LeaveRoom();
|
||||||
|
|
||||||
// Todo: This is not the way to do this. Basically when we're the only participant and the room closes, there's no way to know if this is actually the case.
|
// Todo: This is not the way to do this. Basically when we're the only participant and the room closes, there's no way to know if this is actually the case.
|
||||||
RemoveRoom(joinedRoom);
|
|
||||||
// This is delayed one frame because upon exiting the match subscreen, multiplayer updates the polling rate and messes with polling.
|
// This is delayed one frame because upon exiting the match subscreen, multiplayer updates the polling rate and messes with polling.
|
||||||
Schedule(() => listingPollingComponent.PollImmediately());
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
RemoveRoom(joinedRoom);
|
||||||
|
listingPollingComponent.PollImmediately();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void joinMultiplayerRoom(Room room, Action<Room> onSuccess = null)
|
private void joinMultiplayerRoom(Room room, Action<Room> onSuccess = null)
|
||||||
@ -62,7 +67,7 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer
|
|||||||
Debug.Assert(room.RoomID.Value != null);
|
Debug.Assert(room.RoomID.Value != null);
|
||||||
|
|
||||||
var joinTask = multiplayerClient.JoinRoom(room);
|
var joinTask = multiplayerClient.JoinRoom(room);
|
||||||
joinTask.ContinueWith(_ => onSuccess?.Invoke(room));
|
joinTask.ContinueWith(_ => onSuccess?.Invoke(room), TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||||
joinTask.ContinueWith(t =>
|
joinTask.ContinueWith(t =>
|
||||||
{
|
{
|
||||||
PartRoom();
|
PartRoom();
|
||||||
@ -77,10 +82,10 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer
|
|||||||
ClearRooms();
|
ClearRooms();
|
||||||
|
|
||||||
// Don't poll when not connected or when a room has been joined.
|
// Don't poll when not connected or when a room has been joined.
|
||||||
allowPolling.Value = isConnected.Value && JoinedRoom == null;
|
allowPolling.Value = isConnected.Value && JoinedRoom.Value == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override RoomPollingComponent[] CreatePollingComponents() => new RoomPollingComponent[]
|
protected override IEnumerable<RoomPollingComponent> CreatePollingComponents() => new RoomPollingComponent[]
|
||||||
{
|
{
|
||||||
listingPollingComponent = new RealtimeListingPollingComponent
|
listingPollingComponent = new RealtimeListingPollingComponent
|
||||||
{
|
{
|
||||||
@ -102,8 +107,11 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
AllowPolling.BindValueChanged(_ =>
|
AllowPolling.BindValueChanged(allowPolling =>
|
||||||
{
|
{
|
||||||
|
if (!allowPolling.NewValue)
|
||||||
|
return;
|
||||||
|
|
||||||
if (IsLoaded)
|
if (IsLoaded)
|
||||||
PollImmediately();
|
PollImmediately();
|
||||||
});
|
});
|
||||||
@ -120,8 +128,11 @@ namespace osu.Game.Screens.Multi.RealtimeMultiplayer
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
AllowPolling.BindValueChanged(_ =>
|
AllowPolling.BindValueChanged(allowPolling =>
|
||||||
{
|
{
|
||||||
|
if (!allowPolling.NewValue)
|
||||||
|
return;
|
||||||
|
|
||||||
if (IsLoaded)
|
if (IsLoaded)
|
||||||
PollImmediately();
|
PollImmediately();
|
||||||
});
|
});
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
|
using osu.Game.Screens.Multi.Components;
|
||||||
using osu.Game.Screens.Multi.Lounge;
|
using osu.Game.Screens.Multi.Lounge;
|
||||||
using osu.Game.Screens.Multi.Match;
|
using osu.Game.Screens.Multi.Match;
|
||||||
|
|
||||||
@ -43,6 +44,6 @@ namespace osu.Game.Screens.Multi.Timeshift
|
|||||||
Logger.Log($"Polling adjusted (listing: {timeshiftManager.TimeBetweenListingPolls.Value}, selection: {timeshiftManager.TimeBetweenSelectionPolls.Value})");
|
Logger.Log($"Polling adjusted (listing: {timeshiftManager.TimeBetweenListingPolls.Value}, selection: {timeshiftManager.TimeBetweenSelectionPolls.Value})");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IRoomManager CreateRoomManager() => new TimeshiftRoomManager();
|
protected override RoomManager CreateRoomManager() => new TimeshiftRoomManager();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Screens.Multi.Components;
|
using osu.Game.Screens.Multi.Components;
|
||||||
|
|
||||||
@ -11,7 +12,7 @@ namespace osu.Game.Screens.Multi.Timeshift
|
|||||||
public readonly Bindable<double> TimeBetweenListingPolls = new Bindable<double>();
|
public readonly Bindable<double> TimeBetweenListingPolls = new Bindable<double>();
|
||||||
public readonly Bindable<double> TimeBetweenSelectionPolls = new Bindable<double>();
|
public readonly Bindable<double> TimeBetweenSelectionPolls = new Bindable<double>();
|
||||||
|
|
||||||
protected override RoomPollingComponent[] CreatePollingComponents() => new RoomPollingComponent[]
|
protected override IEnumerable<RoomPollingComponent> CreatePollingComponents() => new RoomPollingComponent[]
|
||||||
{
|
{
|
||||||
new ListingPollingComponent { TimeBetweenPolls = { BindTarget = TimeBetweenListingPolls } },
|
new ListingPollingComponent { TimeBetweenPolls = { BindTarget = TimeBetweenListingPolls } },
|
||||||
new SelectionPollingComponent { TimeBetweenPolls = { BindTarget = TimeBetweenSelectionPolls } }
|
new SelectionPollingComponent { TimeBetweenPolls = { BindTarget = TimeBetweenSelectionPolls } }
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
@ -15,8 +14,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
{
|
{
|
||||||
public GameplayLeaderboard()
|
public GameplayLeaderboard()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
Width = GameplayLeaderboardScore.EXTENDED_WIDTH + GameplayLeaderboardScore.SHEAR_WIDTH;
|
||||||
AutoSizeAxes = Axes.Y;
|
|
||||||
|
|
||||||
Direction = FillDirection.Vertical;
|
Direction = FillDirection.Vertical;
|
||||||
|
|
||||||
@ -29,32 +27,35 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a player to the leaderboard.
|
/// Adds a player to the leaderboard.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="currentScore">The bindable current score of the player.</param>
|
|
||||||
/// <param name="user">The player.</param>
|
/// <param name="user">The player.</param>
|
||||||
public void AddPlayer([NotNull] BindableDouble currentScore, [NotNull] User user)
|
/// <param name="isTracked">
|
||||||
|
/// Whether the player should be tracked on the leaderboard.
|
||||||
|
/// Set to <c>true</c> for the local player or a player whose replay is currently being played.
|
||||||
|
/// </param>
|
||||||
|
public ILeaderboardScore AddPlayer(User user, bool isTracked)
|
||||||
{
|
{
|
||||||
var scoreItem = addScore(currentScore.Value, user);
|
var drawable = new GameplayLeaderboardScore(user, isTracked)
|
||||||
currentScore.ValueChanged += s => scoreItem.TotalScore = s.NewValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private GameplayLeaderboardScore addScore(double totalScore, User user)
|
|
||||||
{
|
|
||||||
var scoreItem = new GameplayLeaderboardScore
|
|
||||||
{
|
{
|
||||||
User = user,
|
Anchor = Anchor.TopRight,
|
||||||
TotalScore = totalScore,
|
Origin = Anchor.TopRight,
|
||||||
OnScoreChange = updateScores,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Add(scoreItem);
|
base.Add(drawable);
|
||||||
updateScores();
|
drawable.TotalScore.BindValueChanged(_ => Scheduler.AddOnce(sort), true);
|
||||||
|
|
||||||
return scoreItem;
|
Height = Count * (GameplayLeaderboardScore.PANEL_HEIGHT + Spacing.Y);
|
||||||
|
|
||||||
|
return drawable;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateScores()
|
public sealed override void Add(GameplayLeaderboardScore drawable)
|
||||||
{
|
{
|
||||||
var orderedByScore = this.OrderByDescending(i => i.TotalScore).ToList();
|
throw new NotSupportedException($"Use {nameof(AddPlayer)} instead.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sort()
|
||||||
|
{
|
||||||
|
var orderedByScore = this.OrderByDescending(i => i.TotalScore.Value).ToList();
|
||||||
|
|
||||||
for (int i = 0; i < Count; i++)
|
for (int i = 0; i < Count; i++)
|
||||||
{
|
{
|
||||||
|
@ -1,25 +1,39 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using Humanizer;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
|
using osu.Game.Users.Drawables;
|
||||||
|
using osu.Game.Utils;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play.HUD
|
namespace osu.Game.Screens.Play.HUD
|
||||||
{
|
{
|
||||||
public class GameplayLeaderboardScore : CompositeDrawable
|
public class GameplayLeaderboardScore : CompositeDrawable, ILeaderboardScore
|
||||||
{
|
{
|
||||||
private readonly OsuSpriteText positionText, positionSymbol, userString;
|
public const float EXTENDED_WIDTH = 255f;
|
||||||
private readonly GlowingSpriteText scoreText;
|
|
||||||
|
|
||||||
public Action OnScoreChange;
|
private const float regular_width = 235f;
|
||||||
|
|
||||||
|
public const float PANEL_HEIGHT = 35f;
|
||||||
|
|
||||||
|
public const float SHEAR_WIDTH = PANEL_HEIGHT * panel_shear;
|
||||||
|
|
||||||
|
private const float panel_shear = 0.15f;
|
||||||
|
|
||||||
|
private OsuSpriteText positionText, scoreText, accuracyText, comboText, usernameText;
|
||||||
|
|
||||||
|
public BindableDouble TotalScore { get; } = new BindableDouble();
|
||||||
|
public BindableDouble Accuracy { get; } = new BindableDouble(1);
|
||||||
|
public BindableInt Combo { get; } = new BindableInt();
|
||||||
|
|
||||||
private int? scorePosition;
|
private int? scorePosition;
|
||||||
|
|
||||||
@ -28,109 +42,249 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
get => scorePosition;
|
get => scorePosition;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
|
if (value == scorePosition)
|
||||||
|
return;
|
||||||
|
|
||||||
scorePosition = value;
|
scorePosition = value;
|
||||||
|
|
||||||
if (scorePosition.HasValue)
|
if (scorePosition.HasValue)
|
||||||
positionText.Text = $"#{scorePosition.Value.ToMetric(decimals: scorePosition < 100000 ? 1 : 0)}";
|
positionText.Text = $"#{scorePosition.Value.FormatRank()}";
|
||||||
|
|
||||||
positionText.FadeTo(scorePosition.HasValue ? 1 : 0);
|
positionText.FadeTo(scorePosition.HasValue ? 1 : 0);
|
||||||
positionSymbol.FadeTo(scorePosition.HasValue ? 1 : 0);
|
updateColour();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private double totalScore;
|
public User User { get; }
|
||||||
|
|
||||||
public double TotalScore
|
private readonly bool trackedPlayer;
|
||||||
|
|
||||||
|
private Container mainFillContainer;
|
||||||
|
private Box centralFill;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="GameplayLeaderboardScore"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">The score's player.</param>
|
||||||
|
/// <param name="trackedPlayer">Whether the player is the local user or a replay player.</param>
|
||||||
|
public GameplayLeaderboardScore(User user, bool trackedPlayer)
|
||||||
{
|
{
|
||||||
get => totalScore;
|
User = user;
|
||||||
set
|
this.trackedPlayer = trackedPlayer;
|
||||||
{
|
|
||||||
totalScore = value;
|
|
||||||
scoreText.Text = totalScore.ToString("N0");
|
|
||||||
|
|
||||||
OnScoreChange?.Invoke();
|
Size = new Vector2(EXTENDED_WIDTH, PANEL_HEIGHT);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private User user;
|
|
||||||
|
|
||||||
public User User
|
|
||||||
{
|
|
||||||
get => user;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
user = value;
|
|
||||||
userString.Text = user?.Username;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameplayLeaderboardScore()
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X;
|
|
||||||
AutoSizeAxes = Axes.Y;
|
|
||||||
|
|
||||||
InternalChild = new Container
|
|
||||||
{
|
|
||||||
Masking = true,
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new FillFlowContainer
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopRight,
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Direction = FillDirection.Horizontal,
|
|
||||||
Margin = new MarginPadding { Right = 2.5f },
|
|
||||||
Spacing = new Vector2(2.5f),
|
|
||||||
Children = new[]
|
|
||||||
{
|
|
||||||
positionText = new OsuSpriteText
|
|
||||||
{
|
|
||||||
Alpha = 0,
|
|
||||||
Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold),
|
|
||||||
},
|
|
||||||
positionSymbol = new OsuSpriteText
|
|
||||||
{
|
|
||||||
Alpha = 0,
|
|
||||||
Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold),
|
|
||||||
Text = ">",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new FillFlowContainer
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopLeft,
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Direction = FillDirection.Horizontal,
|
|
||||||
Margin = new MarginPadding { Left = 2.5f },
|
|
||||||
Spacing = new Vector2(2.5f),
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
userString = new OsuSpriteText
|
|
||||||
{
|
|
||||||
Size = new Vector2(80, 16),
|
|
||||||
Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true),
|
|
||||||
},
|
|
||||||
scoreText = new GlowingSpriteText
|
|
||||||
{
|
|
||||||
GlowColour = Color4Extensions.FromHex(@"83ccfa"),
|
|
||||||
Font = OsuFont.Numeric.With(size: 14),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
positionText.Colour = colours.YellowLight;
|
InternalChildren = new Drawable[]
|
||||||
positionSymbol.Colour = colours.Yellow;
|
{
|
||||||
|
mainFillContainer = new Container
|
||||||
|
{
|
||||||
|
Width = regular_width,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 5f,
|
||||||
|
Shear = new Vector2(panel_shear, 0f),
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
Alpha = 0.5f,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new GridContainer
|
||||||
|
{
|
||||||
|
Width = regular_width,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
ColumnDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.Absolute, 35f),
|
||||||
|
new Dimension(),
|
||||||
|
new Dimension(GridSizeMode.Absolute, 85f),
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
positionText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Padding = new MarginPadding { Right = SHEAR_WIDTH / 2 },
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Colour = Color4.White,
|
||||||
|
Font = OsuFont.Torus.With(size: 14, weight: FontWeight.Bold),
|
||||||
|
Shadow = false,
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Padding = new MarginPadding { Horizontal = SHEAR_WIDTH / 3 },
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 5f,
|
||||||
|
Shear = new Vector2(panel_shear, 0f),
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
centralFill = new Box
|
||||||
|
{
|
||||||
|
Alpha = 0.5f,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4Extensions.FromHex("3399cc"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
Padding = new MarginPadding { Left = SHEAR_WIDTH },
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Spacing = new Vector2(4f, 0f),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new CircularContainer
|
||||||
|
{
|
||||||
|
Masking = true,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Size = new Vector2(25f),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Name = "Placeholder while avatar loads",
|
||||||
|
Alpha = 0.3f,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colours.Gray4,
|
||||||
|
},
|
||||||
|
new UpdateableAvatar(User)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
usernameText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 0.6f,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Colour = Color4.White,
|
||||||
|
Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold),
|
||||||
|
Text = User.Username,
|
||||||
|
Truncate = true,
|
||||||
|
Shadow = false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Padding = new MarginPadding { Top = 2f, Right = 17.5f, Bottom = 5f },
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Colour = Color4.White,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
scoreText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Spacing = new Vector2(-1f, 0f),
|
||||||
|
Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold, fixedWidth: true),
|
||||||
|
Shadow = false,
|
||||||
|
},
|
||||||
|
accuracyText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold, fixedWidth: true),
|
||||||
|
Spacing = new Vector2(-1f, 0f),
|
||||||
|
Shadow = false,
|
||||||
|
},
|
||||||
|
comboText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
Spacing = new Vector2(-1f, 0f),
|
||||||
|
Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold, fixedWidth: true),
|
||||||
|
Shadow = false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TotalScore.BindValueChanged(v => scoreText.Text = v.NewValue.ToString("N0"), true);
|
||||||
|
Accuracy.BindValueChanged(v => accuracyText.Text = v.NewValue.FormatAccuracy(), true);
|
||||||
|
Combo.BindValueChanged(v => comboText.Text = $"{v.NewValue}x", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
updateColour();
|
||||||
|
FinishTransforms(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private const double panel_transition_duration = 500;
|
||||||
|
|
||||||
|
private void updateColour()
|
||||||
|
{
|
||||||
|
if (scorePosition == 1)
|
||||||
|
{
|
||||||
|
mainFillContainer.ResizeWidthTo(EXTENDED_WIDTH, panel_transition_duration, Easing.OutElastic);
|
||||||
|
panelColour = Color4Extensions.FromHex("7fcc33");
|
||||||
|
textColour = Color4.White;
|
||||||
|
}
|
||||||
|
else if (trackedPlayer)
|
||||||
|
{
|
||||||
|
mainFillContainer.ResizeWidthTo(EXTENDED_WIDTH, panel_transition_duration, Easing.OutElastic);
|
||||||
|
panelColour = Color4Extensions.FromHex("ffd966");
|
||||||
|
textColour = Color4Extensions.FromHex("2e576b");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mainFillContainer.ResizeWidthTo(regular_width, panel_transition_duration, Easing.OutElastic);
|
||||||
|
panelColour = Color4Extensions.FromHex("3399cc");
|
||||||
|
textColour = Color4.White;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color4 panelColour
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
mainFillContainer.FadeColour(value, panel_transition_duration, Easing.OutQuint);
|
||||||
|
centralFill.FadeColour(value, panel_transition_duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const double text_transition_duration = 200;
|
||||||
|
|
||||||
|
private Color4 textColour
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
scoreText.FadeColour(value, text_transition_duration, Easing.OutQuint);
|
||||||
|
accuracyText.FadeColour(value, text_transition_duration, Easing.OutQuint);
|
||||||
|
comboText.FadeColour(value, text_transition_duration, Easing.OutQuint);
|
||||||
|
usernameText.FadeColour(value, text_transition_duration, Easing.OutQuint);
|
||||||
|
positionText.FadeColour(value, text_transition_duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
14
osu.Game/Screens/Play/HUD/ILeaderboardScore.cs
Normal file
14
osu.Game/Screens/Play/HUD/ILeaderboardScore.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Play.HUD
|
||||||
|
{
|
||||||
|
public interface ILeaderboardScore
|
||||||
|
{
|
||||||
|
BindableDouble TotalScore { get; }
|
||||||
|
BindableDouble Accuracy { get; }
|
||||||
|
BindableInt Combo { get; }
|
||||||
|
}
|
||||||
|
}
|
131
osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs
Normal file
131
osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
// 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 JetBrains.Annotations;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.Spectator;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Play.HUD
|
||||||
|
{
|
||||||
|
[LongRunningLoad]
|
||||||
|
public class MultiplayerGameplayLeaderboard : GameplayLeaderboard
|
||||||
|
{
|
||||||
|
private readonly ScoreProcessor scoreProcessor;
|
||||||
|
|
||||||
|
private readonly int[] userIds;
|
||||||
|
|
||||||
|
private readonly Dictionary<int, TrackedUserData> userScores = new Dictionary<int, TrackedUserData>();
|
||||||
|
|
||||||
|
/// <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)
|
||||||
|
{
|
||||||
|
// 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.
|
||||||
|
this.userIds = userIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private SpectatorStreamingClient streamingClient { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private UserLookupCache userLookupCache { get; set; }
|
||||||
|
|
||||||
|
private Bindable<ScoringMode> scoringMode;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config, IAPIProvider api)
|
||||||
|
{
|
||||||
|
streamingClient.OnNewFrames += handleIncomingFrames;
|
||||||
|
|
||||||
|
foreach (var user in userIds)
|
||||||
|
{
|
||||||
|
streamingClient.WatchUser(user);
|
||||||
|
|
||||||
|
// probably won't be required in the final implementation.
|
||||||
|
var resolvedUser = userLookupCache.GetUserAsync(user).Result;
|
||||||
|
|
||||||
|
var trackedUser = new TrackedUserData();
|
||||||
|
|
||||||
|
userScores[user] = trackedUser;
|
||||||
|
var leaderboardScore = AddPlayer(resolvedUser, resolvedUser.Id == api.LocalUser.Value.Id);
|
||||||
|
|
||||||
|
((IBindable<double>)leaderboardScore.Accuracy).BindTo(trackedUser.Accuracy);
|
||||||
|
((IBindable<double>)leaderboardScore.TotalScore).BindTo(trackedUser.Score);
|
||||||
|
((IBindable<int>)leaderboardScore.Combo).BindTo(trackedUser.CurrentCombo);
|
||||||
|
}
|
||||||
|
|
||||||
|
scoringMode = config.GetBindable<ScoringMode>(OsuSetting.ScoreDisplayMode);
|
||||||
|
scoringMode.BindValueChanged(updateAllScores, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAllScores(ValueChangedEvent<ScoringMode> mode)
|
||||||
|
{
|
||||||
|
foreach (var trackedData in userScores.Values)
|
||||||
|
trackedData.UpdateScore(scoreProcessor, mode.NewValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleIncomingFrames(int userId, FrameDataBundle bundle)
|
||||||
|
{
|
||||||
|
if (userScores.TryGetValue(userId, out var trackedData))
|
||||||
|
{
|
||||||
|
trackedData.LastHeader = bundle.Header;
|
||||||
|
trackedData.UpdateScore(scoreProcessor, scoringMode.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (streamingClient != null)
|
||||||
|
{
|
||||||
|
foreach (var user in userIds)
|
||||||
|
{
|
||||||
|
streamingClient.StopWatchingUser(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
streamingClient.OnNewFrames -= handleIncomingFrames;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TrackedUserData
|
||||||
|
{
|
||||||
|
public IBindableNumber<double> Score => score;
|
||||||
|
|
||||||
|
private readonly BindableDouble score = new BindableDouble();
|
||||||
|
|
||||||
|
public IBindableNumber<double> Accuracy => accuracy;
|
||||||
|
|
||||||
|
private readonly BindableDouble accuracy = new BindableDouble(1);
|
||||||
|
|
||||||
|
public IBindableNumber<int> CurrentCombo => currentCombo;
|
||||||
|
|
||||||
|
private readonly BindableInt currentCombo = new BindableInt();
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
public FrameHeader LastHeader;
|
||||||
|
|
||||||
|
public void UpdateScore(ScoreProcessor processor, ScoringMode mode)
|
||||||
|
{
|
||||||
|
if (LastHeader == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
(score.Value, accuracy.Value) = processor.GetScoreAndAccuracy(mode, LastHeader.MaxCombo, LastHeader.Statistics);
|
||||||
|
|
||||||
|
currentCombo.Value = LastHeader.Combo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
@ -22,8 +23,10 @@ using osu.Game.Graphics.Containers;
|
|||||||
using osu.Game.IO.Archives;
|
using osu.Game.IO.Archives;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Replays;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
@ -501,6 +504,7 @@ namespace osu.Game.Screens.Play
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ScheduledDelegate completionProgressDelegate;
|
private ScheduledDelegate completionProgressDelegate;
|
||||||
|
private Task<ScoreInfo> scoreSubmissionTask;
|
||||||
|
|
||||||
private void updateCompletionState(ValueChangedEvent<bool> completionState)
|
private void updateCompletionState(ValueChangedEvent<bool> completionState)
|
||||||
{
|
{
|
||||||
@ -527,33 +531,50 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
if (!showResults) return;
|
if (!showResults) return;
|
||||||
|
|
||||||
using (BeginDelayedSequence(RESULTS_DISPLAY_DELAY))
|
scoreSubmissionTask ??= Task.Run(async () =>
|
||||||
completionProgressDelegate = Schedule(GotoRanking);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual ScoreInfo CreateScore()
|
|
||||||
{
|
|
||||||
var score = new ScoreInfo
|
|
||||||
{
|
{
|
||||||
Beatmap = Beatmap.Value.BeatmapInfo,
|
var score = CreateScore();
|
||||||
Ruleset = rulesetInfo,
|
|
||||||
Mods = Mods.Value.ToArray(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (DrawableRuleset.ReplayScore != null)
|
try
|
||||||
score.User = DrawableRuleset.ReplayScore.ScoreInfo?.User ?? new GuestUser();
|
{
|
||||||
else
|
await SubmitScore(score);
|
||||||
score.User = api.LocalUser.Value;
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Error(ex, "Score submission failed!");
|
||||||
|
}
|
||||||
|
|
||||||
ScoreProcessor.PopulateScore(score);
|
try
|
||||||
|
{
|
||||||
|
await ImportScore(score);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Error(ex, "Score import failed!");
|
||||||
|
}
|
||||||
|
|
||||||
return score;
|
return score.ScoreInfo;
|
||||||
|
});
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(RESULTS_DISPLAY_DELAY))
|
||||||
|
scheduleCompletion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void scheduleCompletion() => completionProgressDelegate = Schedule(() =>
|
||||||
|
{
|
||||||
|
if (!scoreSubmissionTask.IsCompleted)
|
||||||
|
{
|
||||||
|
scheduleCompletion();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// screen may be in the exiting transition phase.
|
||||||
|
if (this.IsCurrentScreen())
|
||||||
|
this.Push(CreateResults(scoreSubmissionTask.Result));
|
||||||
|
});
|
||||||
|
|
||||||
protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !GameplayClockContainer.IsPaused.Value;
|
protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !GameplayClockContainer.IsPaused.Value;
|
||||||
|
|
||||||
protected virtual ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, true);
|
|
||||||
|
|
||||||
#region Fail Logic
|
#region Fail Logic
|
||||||
|
|
||||||
protected FailOverlay FailOverlay { get; private set; }
|
protected FailOverlay FailOverlay { get; private set; }
|
||||||
@ -748,39 +769,74 @@ namespace osu.Game.Screens.Play
|
|||||||
return base.OnExiting(next);
|
return base.OnExiting(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void GotoRanking()
|
/// <summary>
|
||||||
|
/// Creates the player's <see cref="Score"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The <see cref="Score"/>.</returns>
|
||||||
|
protected virtual Score CreateScore()
|
||||||
{
|
{
|
||||||
|
var score = new Score
|
||||||
|
{
|
||||||
|
ScoreInfo = new ScoreInfo
|
||||||
|
{
|
||||||
|
Beatmap = Beatmap.Value.BeatmapInfo,
|
||||||
|
Ruleset = rulesetInfo,
|
||||||
|
Mods = Mods.Value.ToArray(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (DrawableRuleset.ReplayScore != null)
|
if (DrawableRuleset.ReplayScore != null)
|
||||||
{
|
{
|
||||||
// if a replay is present, we likely don't want to import into the local database.
|
score.ScoreInfo.User = DrawableRuleset.ReplayScore.ScoreInfo?.User ?? new GuestUser();
|
||||||
this.Push(CreateResults(CreateScore()));
|
score.Replay = DrawableRuleset.ReplayScore.Replay;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
LegacyByteArrayReader replayReader = null;
|
|
||||||
|
|
||||||
var score = new Score { ScoreInfo = CreateScore() };
|
|
||||||
|
|
||||||
if (recordingScore?.Replay.Frames.Count > 0)
|
|
||||||
{
|
{
|
||||||
score.Replay = recordingScore.Replay;
|
score.ScoreInfo.User = api.LocalUser.Value;
|
||||||
|
score.Replay = new Replay { Frames = recordingScore?.Replay.Frames.ToList() ?? new List<ReplayFrame>() };
|
||||||
using (var stream = new MemoryStream())
|
|
||||||
{
|
|
||||||
new LegacyScoreEncoder(score, gameplayBeatmap.PlayableBeatmap).Encode(stream);
|
|
||||||
replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scoreManager.Import(score.ScoreInfo, replayReader)
|
ScoreProcessor.PopulateScore(score.ScoreInfo);
|
||||||
.ContinueWith(imported => Schedule(() =>
|
|
||||||
{
|
return score;
|
||||||
// screen may be in the exiting transition phase.
|
|
||||||
if (this.IsCurrentScreen())
|
|
||||||
this.Push(CreateResults(imported.Result));
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Imports the player's <see cref="Score"/> to the local database.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="score">The <see cref="Score"/> to import.</param>
|
||||||
|
/// <returns>The imported score.</returns>
|
||||||
|
protected virtual Task ImportScore(Score score)
|
||||||
|
{
|
||||||
|
// Replays are already populated and present in the game's database, so should not be re-imported.
|
||||||
|
if (DrawableRuleset.ReplayScore != null)
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
LegacyByteArrayReader replayReader;
|
||||||
|
|
||||||
|
using (var stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
new LegacyScoreEncoder(score, gameplayBeatmap.PlayableBeatmap).Encode(stream);
|
||||||
|
replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr");
|
||||||
|
}
|
||||||
|
|
||||||
|
return scoreManager.Import(score.ScoreInfo, replayReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Submits the player's <see cref="Score"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="score">The <see cref="Score"/> to submit.</param>
|
||||||
|
/// <returns>The submitted score.</returns>
|
||||||
|
protected virtual Task SubmitScore(Score score) => Task.CompletedTask;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates the <see cref="ResultsScreen"/> for a <see cref="ScoreInfo"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="score">The <see cref="ScoreInfo"/> to be displayed in the results screen.</param>
|
||||||
|
/// <returns>The <see cref="ResultsScreen"/>.</returns>
|
||||||
|
protected virtual ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, true);
|
||||||
|
|
||||||
private void fadeOut(bool instant = false)
|
private void fadeOut(bool instant = false)
|
||||||
{
|
{
|
||||||
float fadeOutDuration = instant ? 0 : 250;
|
float fadeOutDuration = instant ? 0 : 250;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
@ -26,18 +27,21 @@ namespace osu.Game.Screens.Play
|
|||||||
DrawableRuleset?.SetReplayScore(Score);
|
DrawableRuleset?.SetReplayScore(Score);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, false);
|
protected override Score CreateScore()
|
||||||
|
|
||||||
protected override ScoreInfo CreateScore()
|
|
||||||
{
|
{
|
||||||
var baseScore = base.CreateScore();
|
var baseScore = base.CreateScore();
|
||||||
|
|
||||||
// Since the replay score doesn't contain statistics, we'll pass them through here.
|
// Since the replay score doesn't contain statistics, we'll pass them through here.
|
||||||
Score.ScoreInfo.HitEvents = baseScore.HitEvents;
|
Score.ScoreInfo.HitEvents = baseScore.ScoreInfo.HitEvents;
|
||||||
|
|
||||||
return Score.ScoreInfo;
|
return Score;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't re-import replay scores as they're already present in the database.
|
||||||
|
protected override Task ImportScore(Score score) => Task.CompletedTask;
|
||||||
|
|
||||||
|
protected override ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, false);
|
||||||
|
|
||||||
public bool OnPressed(GlobalAction action)
|
public bool OnPressed(GlobalAction action)
|
||||||
{
|
{
|
||||||
switch (action)
|
switch (action)
|
||||||
|
@ -106,14 +106,14 @@ namespace osu.Game.Tests.Visual.RealtimeMultiplayer
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task StartMatch()
|
public override Task StartMatch()
|
||||||
{
|
{
|
||||||
Debug.Assert(Room != null);
|
Debug.Assert(Room != null);
|
||||||
|
|
||||||
foreach (var user in Room.Users.Where(u => u.State == MultiplayerUserState.Ready))
|
foreach (var user in Room.Users.Where(u => u.State == MultiplayerUserState.Ready))
|
||||||
ChangeUserState(user.UserID, MultiplayerUserState.WaitingForLoad);
|
ChangeUserState(user.UserID, MultiplayerUserState.WaitingForLoad);
|
||||||
|
|
||||||
await ((IMultiplayerClient)this).LoadRequested();
|
return ((IMultiplayerClient)this).LoadRequested();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using Humanizer;
|
||||||
|
|
||||||
namespace osu.Game.Utils
|
namespace osu.Game.Utils
|
||||||
{
|
{
|
||||||
public static class FormatUtils
|
public static class FormatUtils
|
||||||
@ -18,5 +20,11 @@ namespace osu.Game.Utils
|
|||||||
/// <param name="accuracy">The accuracy to be formatted</param>
|
/// <param name="accuracy">The accuracy to be formatted</param>
|
||||||
/// <returns>formatted accuracy in percentage</returns>
|
/// <returns>formatted accuracy in percentage</returns>
|
||||||
public static string FormatAccuracy(this decimal accuracy) => $"{accuracy:0.00}%";
|
public static string FormatAccuracy(this decimal accuracy) => $"{accuracy:0.00}%";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Formats the supplied rank/leaderboard position in a consistent, simplified way.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rank">The rank/position to be formatted.</param>
|
||||||
|
public static string FormatRank(this int rank) => rank.ToMetric(decimals: rank < 100_000 ? 1 : 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user