// 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.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.GameTypes; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Multi.Components; using osu.Game.Screens.Multi.Match.Components; using osu.Game.Screens.Multi.Play; using osu.Game.Screens.Select; using Footer = osu.Game.Screens.Multi.Match.Components.Footer; namespace osu.Game.Screens.Multi.Match { [Cached(typeof(IPreviewTrackOwner))] public class MatchSubScreen : MultiplayerSubScreen, IPreviewTrackOwner { public override bool DisallowExternalBeatmapRulesetChanges => true; public override string Title { get; } public override string ShortTitle => "room"; [Resolved(typeof(Room), nameof(Room.RoomID))] private Bindable roomId { get; set; } [Resolved(typeof(Room), nameof(Room.Type))] private Bindable type { get; set; } [Resolved(typeof(Room), nameof(Room.Playlist))] private BindableList playlist { get; set; } [Resolved] private BeatmapManager beatmapManager { get; set; } [Resolved(canBeNull: true)] private Multiplayer multiplayer { get; set; } protected readonly Bindable SelectedItem = new Bindable(); private LeaderboardChatDisplay leaderboardChatDisplay; private MatchSettingsOverlay settingsOverlay; public MatchSubScreen(Room room) { Title = room.RoomID.Value == null ? "New room" : room.Name.Value; } [BackgroundDependencyLoader] private void load() { InternalChildren = new Drawable[] { new GridContainer { RelativeSizeAxes = Axes.Both, Content = new[] { new Drawable[] { new Container { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Horizontal = 105, Vertical = 20 }, Child = new GridContainer { RelativeSizeAxes = Axes.Both, Content = new[] { new Drawable[] { new Components.Header() }, new Drawable[] { new Container { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = 65 }, Child = new GridContainer { ColumnDimensions = new[] { new Dimension(minSize: 160), new Dimension(minSize: 360), new Dimension(minSize: 400), }, RelativeSizeAxes = Axes.Both, Content = new[] { new Drawable[] { new Container { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Right = 5 }, Child = new OverlinedParticipants(Direction.Vertical) { RelativeSizeAxes = Axes.Both } }, new Container { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Horizontal = 5 }, Child = new OverlinedPlaylist(true) // Temporarily always allow selection { RelativeSizeAxes = Axes.Both, SelectedItem = { BindTarget = SelectedItem } } }, new Container { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Left = 5 }, Child = leaderboardChatDisplay = new LeaderboardChatDisplay() } }, } } } } }, RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), new Dimension(), } } } }, new Drawable[] { new Footer { OnStart = onStart, SelectedItem = { BindTarget = SelectedItem } } } }, RowDimensions = new[] { new Dimension(), new Dimension(GridSizeMode.AutoSize), } }, settingsOverlay = new MatchSettingsOverlay { RelativeSizeAxes = Axes.Both, EditPlaylist = () => this.Push(new MatchSongSelect()), State = { Value = roomId.Value == null ? Visibility.Visible : Visibility.Hidden } } }; } protected override void LoadComplete() { base.LoadComplete(); roomId.BindValueChanged(id => { if (id.NewValue == null) settingsOverlay.Show(); else { settingsOverlay.Hide(); // Set the first playlist item. // This is scheduled since updating the room and playlist may happen in an arbitrary order (via Room.CopyFrom()). Schedule(() => SelectedItem.Value = playlist.FirstOrDefault()); } }, true); SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged)); SelectedItem.Value = playlist.FirstOrDefault(); beatmapManager.ItemAdded += beatmapAdded; } public override bool OnExiting(IScreen next) { RoomManager?.PartRoom(); Mods.Value = Array.Empty(); return base.OnExiting(next); } private void selectedItemChanged() { updateWorkingBeatmap(); var item = SelectedItem.Value; Mods.Value = item?.RequiredMods?.ToArray() ?? Array.Empty(); if (item?.Ruleset != null) Ruleset.Value = item.Ruleset.Value; } private void updateWorkingBeatmap() { var beatmap = SelectedItem.Value?.Beatmap.Value; // Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info var localBeatmap = beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == beatmap.OnlineBeatmapID); Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); } private void beatmapAdded(BeatmapSetInfo model) => Schedule(() => { if (Beatmap.Value != beatmapManager.DefaultBeatmap) return; updateWorkingBeatmap(); }); private void onStart() { switch (type.Value) { default: case GameTypeTimeshift _: multiplayer?.Start(() => new TimeshiftPlayer(SelectedItem.Value) { Exited = () => leaderboardChatDisplay.RefreshScores() }); break; } } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); if (beatmapManager != null) beatmapManager.ItemAdded -= beatmapAdded; } } }