diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index d508ec2636..2af1de7355 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -7,6 +7,7 @@ using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; using osu.Game.Rulesets.Objects; using System.Linq; +using osu.Framework.Caching; using osu.Framework.Configuration; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -26,8 +27,11 @@ namespace osu.Game.Rulesets.Osu.Objects public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; public double Duration => EndTime - StartTime; + private Cached endPositionCache; + + public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1); + public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t); - public override Vector2 EndPosition => Position + this.CurvePositionAt(1); public override int ComboIndex { @@ -56,7 +60,11 @@ namespace osu.Game.Rulesets.Osu.Objects public SliderPath Path { get => PathBindable.Value; - set => PathBindable.Value = value; + set + { + PathBindable.Value = value; + endPositionCache.Invalidate(); + } } public double Distance => Path.Distance; @@ -73,6 +81,8 @@ namespace osu.Game.Rulesets.Osu.Objects if (TailCircle != null) TailCircle.Position = EndPosition; + + endPositionCache.Invalidate(); } } @@ -92,7 +102,17 @@ namespace osu.Game.Rulesets.Osu.Objects public List> NodeSamples { get; set; } = new List>(); - public int RepeatCount { get; set; } + private int repeatCount; + + public int RepeatCount + { + get => repeatCount; + set + { + repeatCount = value; + endPositionCache.Invalidate(); + } + } /// /// The length of one span of this . @@ -169,7 +189,11 @@ namespace osu.Game.Rulesets.Osu.Objects private void createTicks() { - var length = Path.Distance; + // A very lenient maximum length of a slider for ticks to be generated. + // This exists for edge cases such as /b/1573664 where the beatmap has been edited by the user, and should never be reached in normal usage. + const double max_length = 100000; + + var length = Math.Min(max_length, Path.Distance); var tickDistance = MathHelper.Clamp(TickDistance, 0, length); if (tickDistance == 0) return; diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 7cd78c8be7..a24efe4a1e 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; @@ -72,5 +73,7 @@ namespace osu.Game.Rulesets.Osu.Scoring } protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(judgement); + + protected override HitWindows CreateHitWindows() => new OsuHitWindows(); } } diff --git a/osu.Game.Tests/Visual/TestCaseLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/TestCaseLoungeRoomsContainer.cs index e9dfb0f041..3e9f2fb3a4 100644 --- a/osu.Game.Tests/Visual/TestCaseLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseLoungeRoomsContainer.cs @@ -71,6 +71,8 @@ namespace osu.Game.Tests.Visual private class TestRoomManager : IRoomManager { + public event Action RoomsUpdated; + public readonly BindableCollection Rooms = new BindableCollection(); IBindableCollection IRoomManager.Rooms => Rooms; diff --git a/osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs index afce8999b4..7fb9d4dded 100644 --- a/osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs @@ -136,6 +136,8 @@ namespace osu.Game.Tests.Visual public Func CreateRequested; + public event Action RoomsUpdated; + public IBindableCollection Rooms { get; } = null; public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) diff --git a/osu.Game.Tests/Visual/TestCaseReplay.cs b/osu.Game.Tests/Visual/TestCaseReplay.cs index e0ea613534..0fc4616f56 100644 --- a/osu.Game.Tests/Visual/TestCaseReplay.cs +++ b/osu.Game.Tests/Visual/TestCaseReplay.cs @@ -5,7 +5,6 @@ using System.ComponentModel; using System.Linq; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Scoring; using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual @@ -23,7 +22,7 @@ namespace osu.Game.Tests.Visual // Reset the mods Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Where(m => !(m is ModAutoplay)); - return new ReplayPlayer(new Score { Replay = dummyRulesetContainer.Replay }); + return new ReplayPlayer(dummyRulesetContainer.ReplayScore); } } } diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs index 93783f757b..724c6d656a 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs @@ -25,23 +25,26 @@ namespace osu.Game.Beatmaps.Drawables protected override Drawable CreateDrawable(BeatmapInfo model) { - Drawable drawable; + return new DelayedLoadUnloadWrapper(() => { + Drawable drawable; - var localBeatmap = beatmaps.GetWorkingBeatmap(model); + var localBeatmap = beatmaps.GetWorkingBeatmap(model); - if (localBeatmap.BeatmapInfo.ID == 0 && model?.BeatmapSet?.OnlineInfo != null) - drawable = new BeatmapSetCover(model.BeatmapSet); - else - drawable = new BeatmapBackgroundSprite(localBeatmap); + if (localBeatmap.BeatmapInfo.ID == 0 && model?.BeatmapSet?.OnlineInfo != null) + drawable = new BeatmapSetCover(model.BeatmapSet); + else + drawable = new BeatmapBackgroundSprite(localBeatmap); - drawable.RelativeSizeAxes = Axes.Both; - drawable.Anchor = Anchor.Centre; - drawable.Origin = Anchor.Centre; - drawable.FillMode = FillMode.Fill; + drawable.RelativeSizeAxes = Axes.Both; + drawable.Anchor = Anchor.Centre; + drawable.Origin = Anchor.Centre; + drawable.FillMode = FillMode.Fill; + drawable.OnLoadComplete = d => d.FadeInFromZero(400); - return drawable; + return drawable; + }, 500, 10000); } - protected override double FadeDuration => 400; + protected override double FadeDuration => 0; } } diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs index 0d6a8ae3a8..448f5ced91 100644 --- a/osu.Game/Online/Multiplayer/Room.cs +++ b/osu.Game/Online/Multiplayer/Room.cs @@ -79,6 +79,12 @@ namespace osu.Game.Online.Multiplayer set => MaxAttempts.Value = value; } + /// + /// The position of this in the list. This is not read from or written to the API. + /// + [JsonIgnore] + public int Position = -1; + public void CopyFrom(Room other) { RoomID.Value = other.RoomID; @@ -103,6 +109,8 @@ namespace osu.Game.Online.Multiplayer Playlist.AddRange(other.Playlist); else if (other.Playlist.Count > 0) Playlist.First().ID = other.Playlist.First().ID; + + Position = other.Position; } public bool ShouldSerializeRoomID() => false; diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 23739d8ad1..90f4e5851d 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -396,11 +396,11 @@ namespace osu.Game.Overlays.Profile infoTextLeft.NewLine(); infoTextLeft.AddText("Last seen ", lightText); infoTextLeft.AddText(new DrawableDate(user.LastVisit.Value), boldItalic); - infoTextLeft.NewParagraph(); } if (user.PlayStyle?.Length > 0) { + infoTextLeft.NewParagraph(); infoTextLeft.AddText("Plays with ", lightText); infoTextLeft.AddText(string.Join(", ", user.PlayStyle), boldItalic); } diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 932439618d..72ae88d67a 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mods public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0; - public virtual void ApplyToRulesetContainer(RulesetContainer rulesetContainer) => rulesetContainer.SetReplay(CreateReplayScore(rulesetContainer.Beatmap)?.Replay); + public virtual void ApplyToRulesetContainer(RulesetContainer rulesetContainer) => rulesetContainer.SetReplayScore(CreateReplayScore(rulesetContainer.Beatmap)); } public abstract class ModAutoplay : Mod, IApplicableFailOverride diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index ae0f0dda50..4b3012192d 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -163,8 +163,6 @@ namespace osu.Game.Rulesets.Scoring AllJudged?.Invoke(); } - private readonly Dictionary scoreResultCounts = new Dictionary(); - /// /// Retrieve a score populated with data for the current play this processor is responsible for. /// @@ -180,9 +178,11 @@ namespace osu.Game.Rulesets.Scoring var hitWindows = CreateHitWindows(); foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) - score.Statistics[result] = scoreResultCounts.GetOrDefault(result); + score.Statistics[result] = GetStatistic(result); } + protected abstract int GetStatistic(HitResult result); + public abstract double GetStandardisedScore(); } @@ -378,6 +378,8 @@ namespace osu.Game.Rulesets.Scoring } } + protected override int GetStatistic(HitResult result) => scoreResultCounts.GetOrDefault(result); + public override double GetStandardisedScore() => getScore(ScoringMode.Standardised); protected override void Reset(bool storeResults) diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index 56222ff282..c8c7f3154b 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -22,6 +22,7 @@ using osu.Game.Overlays; using osu.Game.Replays; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.UI { @@ -130,7 +131,7 @@ namespace osu.Game.Rulesets.UI protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null; - public Replay Replay { get; private set; } + public Score ReplayScore { get; private set; } /// /// Whether the game is paused. Used to block user input. @@ -140,14 +141,14 @@ namespace osu.Game.Rulesets.UI /// /// Sets a replay to be used, overriding local input. /// - /// The replay, null for local input. - public virtual void SetReplay(Replay replay) + /// The replay, null for local input. + public virtual void SetReplayScore(Score replayScore) { if (ReplayInputManager == null) throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports replay loading is not available"); - Replay = replay; - ReplayInputManager.ReplayInputHandler = replay != null ? CreateReplayInputHandler(replay) : null; + ReplayScore = replayScore; + ReplayInputManager.ReplayInputHandler = replayScore != null ? CreateReplayInputHandler(replayScore.Replay) : null; HasReplayLoaded.Value = ReplayInputManager.ReplayInputHandler != null; } @@ -302,9 +303,9 @@ namespace osu.Game.Rulesets.UI mod.ReadFromConfig(config); } - public override void SetReplay(Replay replay) + public override void SetReplayScore(Score replayScore) { - base.SetReplay(replay); + base.SetReplayScore(replayScore); if (ReplayInputManager?.ReplayInputHandler != null) ReplayInputManager.ReplayInputHandler.GamefieldToScreenSpace = Playfield.GamefieldToScreenSpace; diff --git a/osu.Game/Screens/Multi/IRoomManager.cs b/osu.Game/Screens/Multi/IRoomManager.cs index a929e1a0f7..f0dbcb0e71 100644 --- a/osu.Game/Screens/Multi/IRoomManager.cs +++ b/osu.Game/Screens/Multi/IRoomManager.cs @@ -10,6 +10,11 @@ namespace osu.Game.Screens.Multi { public interface IRoomManager { + /// + /// Invoked when the s have been updated. + /// + event Action RoomsUpdated; + /// /// All the active s. /// diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index 33b71d1203..5133e96a52 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -52,6 +52,8 @@ namespace osu.Game.Screens.Multi.Lounge.Components rooms.ItemsAdded += addRooms; rooms.ItemsRemoved += removeRooms; + roomManager.RoomsUpdated += updateSorting; + addRooms(rooms); } @@ -104,6 +106,12 @@ namespace osu.Game.Screens.Multi.Lounge.Components } } + private void updateSorting() + { + foreach (var room in roomFlow) + roomFlow.SetLayoutPosition(room, room.Room.Position); + } + private void selectRoom(Room room) { var drawable = roomFlow.FirstOrDefault(r => r.Room == room); @@ -115,5 +123,13 @@ namespace osu.Game.Screens.Multi.Lounge.Components selectedRoom.Value = room; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (roomManager != null) + roomManager.RoomsUpdated -= updateSorting; + } } } diff --git a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs index 68c97d16eb..69ca4b1deb 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs @@ -165,8 +165,8 @@ namespace osu.Game.Screens.Multi.Match.Components TimeSpan.FromHours(12), //TimeSpan.FromHours(16), TimeSpan.FromHours(24), - //TimeSpan.FromDays(3), - //TimeSpan.FromDays(7) + TimeSpan.FromDays(3), + TimeSpan.FromDays(7) } } }, diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index 6fe2307d8b..fab19c3fd7 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -19,6 +19,8 @@ namespace osu.Game.Screens.Multi { public class RoomManager : PollingComponent, IRoomManager { + public event Action RoomsUpdated; + private readonly BindableCollection rooms = new BindableCollection(); public IBindableCollection Rooms => rooms; @@ -52,6 +54,8 @@ namespace osu.Game.Screens.Multi update(room, result); addRoom(room); + RoomsUpdated?.Invoke(); + onSuccess?.Invoke(room); }; @@ -125,13 +129,17 @@ namespace osu.Game.Screens.Multi rooms.Remove(r); } - // Add new matches, or update existing - foreach (var r in result) + for (int i = 0; i < result.Count; i++) { + var r = result[i]; + r.Position = i; + update(r, r); addRoom(r); } + RoomsUpdated?.Invoke(); + tcs.SetResult(true); }; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c102fb0223..14dc644100 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -285,7 +285,7 @@ namespace osu.Game.Screens.Play if (!IsCurrentScreen) return; var score = CreateScore(); - if (RulesetContainer.Replay == null) + if (RulesetContainer.ReplayScore == null) scoreManager.Import(score, true); Push(CreateResults(score)); @@ -297,7 +297,7 @@ namespace osu.Game.Screens.Play protected virtual ScoreInfo CreateScore() { - var score = new ScoreInfo + var score = RulesetContainer.ReplayScore?.ScoreInfo ?? new ScoreInfo { Beatmap = Beatmap.Value.BeatmapInfo, Ruleset = ruleset, diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index fe77fd57f2..04cf922d74 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Play protected override void LoadComplete() { base.LoadComplete(); - RulesetContainer.SetReplay(score.Replay); + RulesetContainer.SetReplayScore(score); } protected override ScoreInfo CreateScore() => score.ScoreInfo;