From 8aaba324314c4cfc628de35a3b6ab438227103a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Mar 2021 15:55:05 +0900 Subject: [PATCH] Fix storyboard commands occurring before the earliest point of visibility delaying gameplay In osu-stable, storyboard intros start from the first command, but in the case of storyboard drawables which have an initial hidden state, all commands before the time at which they become visible (ie. the first command where `Alpha` increases to a non-zero value) are ignored. This brings lazer in line with that behaviour. It also removes several unnecessary LINQ calls. Note that the alpha check being done in its own pass is important, as it must be the "minimum present alpha across all command groups, including loops". This is what makes the implementation slightly complex. Closes #11981. --- osu.Game/Storyboards/CommandTimelineGroup.cs | 19 +++++++++ osu.Game/Storyboards/StoryboardSprite.cs | 45 +++++++++++++++++--- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs index 6ce3b617e9..617455cf0b 100644 --- a/osu.Game/Storyboards/CommandTimelineGroup.cs +++ b/osu.Game/Storyboards/CommandTimelineGroup.cs @@ -45,11 +45,30 @@ namespace osu.Game.Storyboards }; } + /// + /// Returns the earliest visible time. Will be null unless this group has an command with a start value of zero. + /// + public double? EarliestDisplayedTime + { + get + { + var first = Alpha.Commands.FirstOrDefault(); + + return first?.StartValue == 0 ? first.StartTime : (double?)null; + } + } + [JsonIgnore] public double CommandsStartTime { get { + // if the first alpha command starts at zero it should be given priority over anything else. + // this is due to it creating a state where the target is not present before that time, causing any other events to not be visible. + var earliestDisplay = EarliestDisplayedTime; + if (earliestDisplay != null) + return earliestDisplay.Value; + double min = double.MaxValue; for (int i = 0; i < timelines.Length; i++) diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index f411ad04f3..fdaa59d7d9 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -24,13 +24,46 @@ namespace osu.Game.Storyboards public readonly CommandTimelineGroup TimelineGroup = new CommandTimelineGroup(); - public double StartTime => Math.Min( - TimelineGroup.HasCommands ? TimelineGroup.CommandsStartTime : double.MaxValue, - loops.Any(l => l.HasCommands) ? loops.Where(l => l.HasCommands).Min(l => l.StartTime) : double.MaxValue); + public double StartTime + { + get + { + // check for presence affecting commands as an initial pass. + double earliestStartTime = TimelineGroup.EarliestDisplayedTime ?? double.MaxValue; - public double EndTime => Math.Max( - TimelineGroup.HasCommands ? TimelineGroup.CommandsEndTime : double.MinValue, - loops.Any(l => l.HasCommands) ? loops.Where(l => l.HasCommands).Max(l => l.EndTime) : double.MinValue); + foreach (var l in loops) + { + if (!(l.EarliestDisplayedTime is double lEarliest)) + continue; + + earliestStartTime = Math.Min(earliestStartTime, lEarliest); + } + + if (earliestStartTime < double.MaxValue) + return earliestStartTime; + + // if an alpha-affecting command was not found, use the earliest of any command. + earliestStartTime = TimelineGroup.StartTime; + + foreach (var l in loops) + earliestStartTime = Math.Min(earliestStartTime, l.StartTime); + + return earliestStartTime; + } + } + + public double EndTime + { + get + { + double latestEndTime = TimelineGroup.EndTime; + + foreach (var l in loops) + latestEndTime = Math.Max(latestEndTime, l.EndTime); + + return latestEndTime; + } + } public bool HasCommands => TimelineGroup.HasCommands || loops.Any(l => l.HasCommands);