diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs index 33d4e16662..c18d180783 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Linq; using osu.Framework.Graphics.Cursor; using osu.Framework.Input; using OpenTK; @@ -46,6 +47,15 @@ namespace osu.Game.Rulesets.Osu.UI protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuReplayInputHandler(replay); + public override double GameplayStartTime + { + get + { + var first = (OsuHitObject)Objects.First(); + return first.StartTime - first.TimePreempt; + } + } + protected override Vector2 GetAspectAdjustedSize() { var aspectSize = DrawSize.X * 0.75f < DrawSize.Y ? new Vector2(DrawSize.X, DrawSize.X * 0.75f) : new Vector2(DrawSize.Y * 4f / 3f, DrawSize.Y); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 49780199fd..78042349d1 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -50,6 +50,11 @@ namespace osu.Game.Beatmaps /// public event Action BeatmapDownloadFailed; + /// + /// Fired when a beatmap load is requested (into the interactive game UI). + /// + public Action PresentBeatmap; + /// /// A default representation of a WorkingBeatmap to use when no beatmap is available. /// @@ -168,12 +173,20 @@ namespace osu.Game.Beatmaps Task.Factory.StartNew(() => { + BeatmapSetInfo importedBeatmap; + // This gets scheduled back to the update thread, but we want the import to run in the background. using (var stream = new MemoryStream(data)) using (var archive = new ZipArchiveReader(stream, beatmapSetInfo.ToString())) - Import(archive); + importedBeatmap = Import(archive); + downloadNotification.CompletionClickAction = () => + { + PresentBeatmap?.Invoke(importedBeatmap); + return true; + }; downloadNotification.State = ProgressNotificationState.Completed; + currentDownloads.Remove(request); }, TaskCreationOptions.LongRunning); }; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 557b6e4469..18a1d018d0 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -24,6 +24,7 @@ using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Platform; using osu.Framework.Threading; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Scoring; using osu.Game.Overlays.Notifications; @@ -34,6 +35,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Skinning; using OpenTK.Graphics; using osu.Game.Overlays.Volume; +using osu.Game.Screens.Select; namespace osu.Game { @@ -179,6 +181,41 @@ namespace osu.Game /// The set to display. public void ShowBeatmapSet(int setId) => beatmapSetOverlay.FetchAndShowBeatmapSet(setId); + /// + /// Present a beatmap at song select. + /// + /// The beatmap to select. + public void PresentBeatmap(BeatmapSetInfo beatmap) + { + CloseAllOverlays(false); + + void setBeatmap() + { + if (Beatmap.Disabled) + { + Schedule(setBeatmap); + return; + } + + Beatmap.Value = BeatmapManager.GetWorkingBeatmap(beatmap.Beatmaps.First()); + } + + switch (currentScreen) + { + case SongSelect _: + break; + default: + // navigate to song select if we are not already there. + var menu = (MainMenu)intro.ChildScreen; + + menu.MakeCurrent(); + menu.LoadToSolo(); + break; + } + + setBeatmap(); + } + /// /// Show a user's profile as an overlay. /// @@ -245,6 +282,7 @@ namespace osu.Game BeatmapManager.PostNotification = n => notifications?.Post(n); BeatmapManager.GetStableStorage = GetStorageForStableInstall; + BeatmapManager.PresentBeatmap = PresentBeatmap; AddRange(new Drawable[] { diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index c584a32a82..98cf111ba0 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -44,6 +44,8 @@ namespace osu.Game.Overlays.Mods private void rulesetChanged(RulesetInfo newRuleset) { + if (newRuleset == null) return; + var instance = newRuleset.CreateInstance(); foreach (ModSection section in ModSectionsContainer.Children) @@ -173,7 +175,10 @@ namespace osu.Game.Overlays.Mods refreshSelectedMods(); } - private void refreshSelectedMods() => SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); + private void refreshSelectedMods() + { + SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); + } public ModSelectOverlay() { diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 7058d1bed6..8f73ff4c2d 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mods { - public class ModAutoplay : ModAutoplay, IApplicableToRulesetContainer + public abstract class ModAutoplay : ModAutoplay, IApplicableToRulesetContainer where T : HitObject { protected virtual Score CreateReplayScore(Beatmap beatmap) => new Score { Replay = new Replay() }; diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index 0bfde148e7..d68afdfedc 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -56,6 +56,12 @@ namespace osu.Game.Rulesets.UI public abstract IEnumerable Objects { get; } + /// + /// The point in time at which gameplay starts, including any required lead-in for display purposes. + /// Defaults to two seconds before the first . Override as necessary. + /// + public virtual double GameplayStartTime => Objects.First().StartTime - 2000; + private readonly Lazy playfield; /// diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index d922f49ce3..4790996833 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Menu OnChart = delegate { Push(new ChartListing()); }, OnDirect = delegate { Push(new OnlineListing()); }, OnEdit = delegate { Push(new Editor()); }, - OnSolo = delegate { Push(consumeSongSelect()); }, + OnSolo = onSolo, OnMulti = delegate { Push(new Multiplayer()); }, OnExit = Exit, } @@ -85,6 +85,10 @@ namespace osu.Game.Screens.Menu LoadComponentAsync(songSelect = new PlaySongSelect()); } + public void LoadToSolo() => Schedule(onSolo); + + private void onSolo() => Push(consumeSongSelect()); + private Screen consumeSongSelect() { var s = songSelect; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 49a180902b..fc439a48c5 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -138,10 +138,9 @@ namespace osu.Game.Screens.Play sourceClock = (IAdjustableClock)working.Track ?? new StopwatchClock(); adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - var firstObjectTime = RulesetContainer.Objects.First().StartTime; adjustableClock.Seek(AllowLeadIn - ? Math.Min(0, firstObjectTime - Math.Max(beatmap.ControlPointInfo.TimingPointAt(firstObjectTime).BeatLength * 4, beatmap.BeatmapInfo.AudioLeadIn)) - : firstObjectTime); + ? Math.Min(RulesetContainer.GameplayStartTime, beatmap.HitObjects.First().StartTime - beatmap.BeatmapInfo.AudioLeadIn) + : RulesetContainer.GameplayStartTime); adjustableClock.ProcessFrame(); @@ -199,7 +198,7 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, Origin = Anchor.Centre }, - new SkipOverlay(firstObjectTime) + new SkipOverlay(RulesetContainer.GameplayStartTime) { Clock = Clock, // skip button doesn't want to use the audio clock directly ProcessCustomClock = false, diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 8ce40fcfa0..a346911ca2 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -84,10 +84,10 @@ namespace osu.Game.Screens.Select protected override void UpdateBeatmap(WorkingBeatmap beatmap) { - base.UpdateBeatmap(beatmap); - beatmap.Mods.BindTo(SelectedMods); + base.UpdateBeatmap(beatmap); + BeatmapDetails.Beatmap = beatmap; if (beatmap.Track != null) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index e6926c118b..234508a195 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Linq; using OpenTK; using OpenTK.Input; using osu.Framework.Allocation; @@ -20,6 +21,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using osu.Game.Overlays; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; using osu.Game.Screens.Menu; @@ -70,7 +72,14 @@ namespace osu.Game.Screens.Select private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + { + dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs(this); + dependencies.CacheAs(Ruleset); + dependencies.CacheAs>(Ruleset); + + return dependencies; + } protected SongSelect() { @@ -189,9 +198,9 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader(true)] private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours) { - dependencies.CacheAs(this); - dependencies.CacheAs(Ruleset); - dependencies.CacheAs>(Ruleset); + // manual binding to parent ruleset to allow for delayed load in the incoming direction. + base.Ruleset.ValueChanged += r => updateSelectedBeatmap(beatmapNoDebounce); + Ruleset.ValueChanged += r => base.Ruleset.Value = r; if (Footer != null) { @@ -277,7 +286,7 @@ namespace osu.Game.Screens.Select // If selecting new beatmap without bypassing filters failed, there's possibly a ruleset mismatch if (beatmap?.BeatmapInfo?.Ruleset != null && beatmap.BeatmapInfo.Ruleset != Ruleset.Value) { - Ruleset.Value = beatmap.BeatmapInfo.Ruleset; + base.Ruleset.Value = beatmap.BeatmapInfo.Ruleset; Carousel.SelectBeatmap(beatmap.BeatmapInfo); } } @@ -295,18 +304,25 @@ namespace osu.Game.Screens.Select void performLoad() { + WorkingBeatmap working = Beatmap.Value; + bool preview = false; + // We may be arriving here due to another component changing the bindable Beatmap. // In these cases, the other component has already loaded the beatmap, so we don't need to do so again. if (beatmap?.Equals(Beatmap.Value.BeatmapInfo) != true) { - bool preview = beatmap?.BeatmapSetInfoID != Beatmap.Value?.BeatmapInfo.BeatmapSetInfoID; - - Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap.Value); - ensurePlayingSelected(preview); + preview = beatmap?.BeatmapSetInfoID != Beatmap.Value?.BeatmapInfo.BeatmapSetInfoID; + working = beatmaps.GetWorkingBeatmap(beatmap, Beatmap.Value); } + + working.Mods.Value = Enumerable.Empty(); + + Beatmap.Value = working; Ruleset.Value = ruleset; + ensurePlayingSelected(preview); + UpdateBeatmap(Beatmap.Value); }