diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs new file mode 100644 index 0000000000..35c3471ce2 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs @@ -0,0 +1,82 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Timing; +using osu.Game.Online.Spectator; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play.HUD; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +{ + public class MultiplayerSpectatorLeaderboard : MultiplayerGameplayLeaderboard + { + private readonly Dictionary trackedData = new Dictionary(); + + public MultiplayerSpectatorLeaderboard(ScoreProcessor scoreProcessor, int[] userIds) + : base(scoreProcessor, userIds) + { + } + + public void AddSource(int userId, IClock source) => trackedData[userId] = new TrackedUserData(source); + + public void RemoveSource(int userId, IClock source) => trackedData.Remove(userId); + + protected override void OnIncomingFrames(int userId, FrameDataBundle bundle) + { + if (!trackedData.TryGetValue(userId, out var data)) + return; + + data.Frames.Add(new TimedFrameHeader(bundle.Frames.First().Time, bundle.Header)); + } + + protected override void Update() + { + base.Update(); + + foreach (var (userId, data) in trackedData) + { + var targetTime = data.Clock.CurrentTime; + + int frameIndex = data.Frames.BinarySearch(new TimedFrameHeader(targetTime)); + if (frameIndex < 0) + frameIndex = ~frameIndex; + frameIndex = Math.Clamp(frameIndex - 1, 0, data.Frames.Count - 1); + + SetCurrentFrame(userId, data.Frames[frameIndex].Header); + } + } + + private class TrackedUserData + { + public readonly IClock Clock; + public readonly List Frames = new List(); + + public TrackedUserData(IClock clock) + { + Clock = clock; + } + } + + private class TimedFrameHeader : IComparable + { + public readonly double Time; + public readonly FrameHeader Header; + + public TimedFrameHeader(double time) + { + Time = time; + } + + public TimedFrameHeader(double time, FrameHeader header) + { + Time = time; + Header = header; + } + + public int CompareTo(TimedFrameHeader other) => Time.CompareTo(other.Time); + } + } +} diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index a3d27c4e71..65ad8be3f0 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -20,7 +20,6 @@ namespace osu.Game.Screens.Play.HUD public class MultiplayerGameplayLeaderboard : GameplayLeaderboard { private readonly ScoreProcessor scoreProcessor; - private readonly Dictionary userScores = new Dictionary(); [Resolved] @@ -116,13 +115,19 @@ namespace osu.Game.Screens.Play.HUD trackedData.UpdateScore(scoreProcessor, mode.NewValue); } - private void handleIncomingFrames(int userId, FrameDataBundle bundle) + private void handleIncomingFrames(int userId, FrameDataBundle bundle) => Schedule(() => { - if (userScores.TryGetValue(userId, out var trackedData)) - { - trackedData.LastHeader = bundle.Header; - trackedData.UpdateScore(scoreProcessor, scoringMode.Value); - } + if (userScores.ContainsKey(userId)) + OnIncomingFrames(userId, bundle); + }); + + protected virtual void OnIncomingFrames(int userId, FrameDataBundle bundle) => SetCurrentFrame(userId, bundle.Header); + + protected void SetCurrentFrame(int userId, FrameHeader header) + { + var trackedScore = userScores[userId]; + trackedScore.LastHeader = header; + trackedScore.UpdateScore(scoreProcessor, scoringMode.Value); } protected override void Dispose(bool isDisposing)