// 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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate.Sync; using osu.Game.Screens.Play; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { /// /// Provides an area for and manages the hierarchy of a spectated player within a . /// public class PlayerArea : CompositeDrawable, IAdjustableAudioComponent { /// /// Whether a is loaded in the area. /// public bool PlayerLoaded => stack?.CurrentScreen is Player; /// /// The user id this corresponds to. /// public readonly int UserId; /// /// The used to control the gameplay running state of a loaded . /// [NotNull] public readonly ISpectatorPlayerClock GameplayClock = new CatchUpSpectatorPlayerClock(); /// /// The currently-loaded score. /// [CanBeNull] public Score Score { get; private set; } [Resolved] private BeatmapManager beatmapManager { get; set; } private readonly Container gameplayContent; private readonly LoadingLayer loadingLayer; private readonly AudioContainer audioContainer; private OsuScreenStack stack; public PlayerArea(int userId, IFrameBasedClock masterClock) { UserId = userId; RelativeSizeAxes = Axes.Both; Masking = true; InternalChildren = new Drawable[] { audioContainer = new AudioContainer { RelativeSizeAxes = Axes.Both, Child = gameplayContent = new DrawSizePreservingFillContainer { RelativeSizeAxes = Axes.Both }, }, loadingLayer = new LoadingLayer(true) { State = { Value = Visibility.Visible } } }; GameplayClock.Source = masterClock; } public void LoadScore([NotNull] Score score) { if (Score != null) throw new InvalidOperationException($"Cannot load a new score on a {nameof(PlayerArea)} that has an existing score."); Score = score; gameplayContent.Child = new PlayerIsolationContainer(beatmapManager.GetWorkingBeatmap(Score.ScoreInfo.Beatmap), Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods) { RelativeSizeAxes = Axes.Both, Child = stack = new OsuScreenStack() }; stack.Push(new MultiSpectatorPlayerLoader(Score, () => new MultiSpectatorPlayer(Score, GameplayClock))); loadingLayer.Hide(); } // Player interferes with global input, so disable input for now. public override bool PropagatePositionalInputSubTree => false; public override bool PropagateNonPositionalInputSubTree => false; #region IAdjustableAudioComponent public IBindable AggregateVolume => audioContainer.AggregateVolume; public IBindable AggregateBalance => audioContainer.AggregateBalance; public IBindable AggregateFrequency => audioContainer.AggregateFrequency; public IBindable AggregateTempo => audioContainer.AggregateTempo; public void BindAdjustments(IAggregateAudioAdjustment component) { audioContainer.BindAdjustments(component); } public void UnbindAdjustments(IAggregateAudioAdjustment component) { audioContainer.UnbindAdjustments(component); } public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) { audioContainer.AddAdjustment(type, adjustBindable); } public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) { audioContainer.RemoveAdjustment(type, adjustBindable); } public void RemoveAllAdjustments(AdjustableProperty type) { audioContainer.RemoveAllAdjustments(type); } public BindableNumber Volume => audioContainer.Volume; public BindableNumber Balance => audioContainer.Balance; public BindableNumber Frequency => audioContainer.Frequency; public BindableNumber Tempo => audioContainer.Tempo; #endregion private class PlayerIsolationContainer : Container { [Cached] private readonly Bindable ruleset = new Bindable(); [Cached] private readonly Bindable beatmap = new Bindable(); [Cached] private readonly Bindable> mods = new Bindable>(); public PlayerIsolationContainer(WorkingBeatmap beatmap, RulesetInfo ruleset, IReadOnlyList mods) { this.beatmap.Value = beatmap; this.ruleset.Value = ruleset; this.mods.Value = mods; } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); dependencies.CacheAs(ruleset.BeginLease(false)); dependencies.CacheAs(beatmap.BeginLease(false)); dependencies.CacheAs(mods.BeginLease(false)); return dependencies; } } } }