diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 24881d9c47..4f87767fa7 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (strainTime < min_speed_bonus) speedBonus = 1 + 0.75 * Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2); - double distance = Math.Min(single_spacing_threshold, osuCurrObj.TravelDistance + osuCurrObj.JumpDistance); + double distance = Math.Min(single_spacing_threshold, osuCurrObj.TravelDistance + osuCurrObj.MovementDistance); return (speedBonus + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / strainTime; } diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 3ddd6ce62b..7e874495c8 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -163,7 +163,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(joinedRoom != null); // Populate playlist items. - var playlistItems = await Task.WhenAll(joinedRoom.Playlist.Select(createPlaylistItem)).ConfigureAwait(false); + var playlistItems = await Task.WhenAll(joinedRoom.Playlist.Select(item => createPlaylistItem(item, item.ID == joinedRoom.Settings.PlaylistItemId))).ConfigureAwait(false); // Populate users. Debug.Assert(joinedRoom.Users != null); @@ -470,7 +470,32 @@ namespace osu.Game.Online.Multiplayer Task IMultiplayerClient.SettingsChanged(MultiplayerRoomSettings newSettings) { - Scheduler.Add(() => updateLocalRoomSettings(newSettings)); + Debug.Assert(APIRoom != null); + Debug.Assert(Room != null); + + Scheduler.Add(() => + { + // ensure the new selected item is populated immediately. + var playlistItem = APIRoom.Playlist.Single(p => p.ID == newSettings.PlaylistItemId); + + if (playlistItem != null) + { + GetAPIBeatmap(playlistItem.BeatmapID).ContinueWith(b => + { + // Should be called outside of the `Scheduler` logic (and specifically accessing `Exception`) to suppress an exception from firing outwards. + bool success = b.Exception == null; + + Scheduler.Add(() => + { + if (success) + playlistItem.Beatmap.Value = b.Result; + + updateLocalRoomSettings(newSettings); + }); + }); + } + }); + return Task.CompletedTask; } @@ -629,7 +654,7 @@ namespace osu.Game.Online.Multiplayer if (Room == null) return; - var playlistItem = await createPlaylistItem(item).ConfigureAwait(false); + var playlistItem = await createPlaylistItem(item, true).ConfigureAwait(false); Scheduler.Add(() => { @@ -673,7 +698,7 @@ namespace osu.Game.Online.Multiplayer if (Room == null) return; - var playlistItem = await createPlaylistItem(item).ConfigureAwait(false); + var playlistItem = await createPlaylistItem(item, true).ConfigureAwait(false); Scheduler.Add(() => { @@ -728,10 +753,8 @@ namespace osu.Game.Online.Multiplayer CurrentMatchPlayingItem.Value = APIRoom.Playlist.SingleOrDefault(p => p.ID == settings.PlaylistItemId); } - private async Task createPlaylistItem(MultiplayerPlaylistItem item) + private async Task createPlaylistItem(MultiplayerPlaylistItem item, bool populateBeatmapImmediately) { - var apiBeatmap = await GetAPIBeatmap(item.BeatmapID).ConfigureAwait(false); - var ruleset = Rulesets.GetRuleset(item.RulesetID); Debug.Assert(ruleset != null); @@ -741,8 +764,8 @@ namespace osu.Game.Online.Multiplayer var playlistItem = new PlaylistItem { ID = item.ID, + BeatmapID = item.BeatmapID, OwnerID = item.OwnerID, - Beatmap = { Value = apiBeatmap }, Ruleset = { Value = ruleset }, Expired = item.Expired, PlaylistOrder = item.PlaylistOrder, @@ -752,6 +775,9 @@ namespace osu.Game.Online.Multiplayer playlistItem.RequiredMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance))); playlistItem.AllowedMods.AddRange(item.AllowedMods.Select(m => m.ToMod(rulesetInstance))); + if (populateBeatmapImmediately) + playlistItem.Beatmap.Value = await GetAPIBeatmap(item.BeatmapID).ConfigureAwait(false); + return playlistItem; } diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs index fc029543bb..edf9c5d155 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs @@ -80,7 +80,7 @@ namespace osu.Game.Screens.OnlinePlay.Components private void updateRange(object sender, NotifyCollectionChangedEventArgs e) { - var orderedDifficulties = Playlist.Select(p => p.Beatmap.Value).OrderBy(b => b.StarRating).ToArray(); + var orderedDifficulties = Playlist.Where(p => p.Beatmap.Value != null).Select(p => p.Beatmap.Value).OrderBy(b => b.StarRating).ToArray(); StarDifficulty minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarRating : 0, 0); StarDifficulty maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarRating : 0, 0); diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 2dbe2df82c..c291bddeeb 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -16,6 +16,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Database; @@ -50,6 +51,7 @@ namespace osu.Game.Screens.OnlinePlay private LinkFlowContainer authorText; private ExplicitContentBeatmapPill explicitContentPill; private ModDisplay modDisplay; + private FillFlowContainer buttonsFlow; private UpdateableAvatar ownerAvatar; private readonly IBindable valid = new Bindable(); @@ -66,10 +68,19 @@ namespace osu.Game.Screens.OnlinePlay [Resolved] private UserLookupCache userLookupCache { get; set; } + [Resolved] + private BeatmapLookupCache beatmapLookupCache { get; set; } + + private PanelBackground panelBackground; + + private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both }; + private readonly bool allowEdit; private readonly bool allowSelection; private readonly bool showItemOwner; + private FillFlowContainer mainFillFlow; + protected override bool ShouldBeConsideredForInput(Drawable child) => allowEdit || !allowSelection || SelectedItem.Value == Model; public DrawableRoomPlaylistItem(PlaylistItem item, bool allowEdit, bool allowSelection, bool showItemOwner) @@ -130,11 +141,34 @@ namespace osu.Game.Screens.OnlinePlay valid.BindValueChanged(_ => Scheduler.AddOnce(refresh)); requiredMods.CollectionChanged += (_, __) => Scheduler.AddOnce(refresh); + onScreenLoader.DelayedLoadStarted += _ => + { + Task.Run(async () => + { + try + { + if (showItemOwner) + { + var foundUser = await userLookupCache.GetUserAsync(Item.OwnerID).ConfigureAwait(false); + Schedule(() => ownerAvatar.User = foundUser); + } + + if (Item.Beatmap.Value == null) + { + var foundBeatmap = await beatmapLookupCache.GetBeatmapAsync(Item.BeatmapID).ConfigureAwait(false); + Schedule(() => Item.Beatmap.Value = foundBeatmap); + } + } + catch (Exception e) + { + Logger.Log($"Error while populating playlist item {e}"); + } + }); + }; + refresh(); } - private PanelBackground panelBackground; - private void refresh() { if (!valid.Value) @@ -143,22 +177,22 @@ namespace osu.Game.Screens.OnlinePlay maskingContainer.BorderColour = colours.Red; } - if (showItemOwner) - { - ownerAvatar.Show(); - userLookupCache.GetUserAsync(Item.OwnerID) - .ContinueWith(u => Schedule(() => ownerAvatar.User = u.Result), TaskContinuationOptions.OnlyOnRanToCompletion); - } - - difficultyIconContainer.Child = new DifficultyIcon(Item.Beatmap.Value, ruleset.Value, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(ICON_HEIGHT) }; + if (Item.Beatmap.Value != null) + difficultyIconContainer.Child = new DifficultyIcon(Item.Beatmap.Value, ruleset.Value, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(ICON_HEIGHT) }; + else + difficultyIconContainer.Clear(); panelBackground.Beatmap.Value = Item.Beatmap.Value; beatmapText.Clear(); - beatmapText.AddLink(Item.Beatmap.Value.GetDisplayTitleRomanisable(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineID.ToString(), null, text => + + if (Item.Beatmap.Value != null) { - text.Truncate = true; - }); + beatmapText.AddLink(Item.Beatmap.Value.GetDisplayTitleRomanisable(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineID.ToString(), null, text => + { + text.Truncate = true; + }); + } authorText.Clear(); @@ -168,10 +202,16 @@ namespace osu.Game.Screens.OnlinePlay authorText.AddUserLink(Item.Beatmap.Value.Metadata.Author); } - bool hasExplicitContent = (Item.Beatmap.Value.BeatmapSet as IBeatmapSetOnlineInfo)?.HasExplicitContent == true; + bool hasExplicitContent = (Item.Beatmap.Value?.BeatmapSet as IBeatmapSetOnlineInfo)?.HasExplicitContent == true; explicitContentPill.Alpha = hasExplicitContent ? 1 : 0; modDisplay.Current.Value = requiredMods.ToArray(); + + buttonsFlow.Clear(); + buttonsFlow.ChildrenEnumerable = CreateButtons(); + + difficultyIconContainer.FadeInFromZero(500, Easing.OutQuint); + mainFillFlow.FadeInFromZero(500, Easing.OutQuint); } protected override Drawable CreateContent() @@ -192,6 +232,7 @@ namespace osu.Game.Screens.OnlinePlay Alpha = 0, AlwaysPresent = true }, + onScreenLoader, panelBackground = new PanelBackground { RelativeSizeAxes = Axes.Both, @@ -217,7 +258,7 @@ namespace osu.Game.Screens.OnlinePlay AutoSizeAxes = Axes.Both, Margin = new MarginPadding { Left = 8, Right = 8 }, }, - new FillFlowContainer + mainFillFlow = new FillFlowContainer { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -273,7 +314,7 @@ namespace osu.Game.Screens.OnlinePlay } } }, - new FillFlowContainer + buttonsFlow = new FillFlowContainer { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, @@ -305,9 +346,9 @@ namespace osu.Game.Screens.OnlinePlay } protected virtual IEnumerable CreateButtons() => - new Drawable[] + new[] { - new PlaylistDownloadButton(Item), + Item.Beatmap.Value == null ? Empty() : new PlaylistDownloadButton(Item), new PlaylistRemoveButton { Size = new Vector2(30, 30), diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index a4586dea12..520f2c4585 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -4,9 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Scoring; @@ -89,15 +87,6 @@ namespace osu.Game.Tests.Visual.OnlinePlay getRoomRequest.TriggerSuccess(createResponseRoom(ServerSideRooms.Single(r => r.RoomID.Value == getRoomRequest.RoomId), true)); return true; - case GetBeatmapSetRequest getBeatmapSetRequest: - var onlineReq = new GetBeatmapSetRequest(getBeatmapSetRequest.ID, getBeatmapSetRequest.Type); - onlineReq.Success += res => getBeatmapSetRequest.TriggerSuccess(res); - onlineReq.Failure += e => getBeatmapSetRequest.TriggerFailure(e); - - // Get the online API from the game's dependencies. - game.Dependencies.Get().Queue(onlineReq); - return true; - case CreateRoomScoreRequest createRoomScoreRequest: createRoomScoreRequest.TriggerSuccess(new APIScoreToken { ID = 1 }); return true;