1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-13 09:23:06 +08:00

Allow skipping storyboard outro

Reuses SkipOverlay by calculating the endtime of the storyboard and using that as a "start point". Upon skipping the outro the score is instantly shown.
When the end of the storyboard is reached the score screen automatically shows up. If the player holds ESC (pause) during the outro, the score is displayed

The storyboard endtime is calculated by getting the latest endtime of the storyboard's elements, or simply returning 0 if there is no storyboard.

Co-Authored-By: Marlina José <marlina@umich.edu>
This commit is contained in:
Christine Chen 2021-04-14 00:04:03 -04:00
parent 16d34bcc0a
commit 25b8c2f257
6 changed files with 166 additions and 0 deletions

View File

@ -0,0 +1,100 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking;
using osu.Game.Storyboards;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneStoryboardWithOutro : PlayerTestScene
{
protected new OutroPlayer Player => (OutroPlayer)base.Player;
private Storyboard storyboard;
private const double storyboard_duration = 2000;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
config.SetValue(OsuSetting.ShowStoryboard, true);
storyboard = new Storyboard();
var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero);
sprite.TimelineGroup.Alpha.Add(Easing.None, 0, storyboard_duration, 1, 0);
storyboard.GetLayer("Background").Add(sprite);
}
[Test]
public void TestStoryboardSkipOutro()
{
AddUntilStep("storyboard loaded", () => Player.Beatmap.Value.StoryboardLoaded);
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
AddStep("skip outro", () => InputManager.Key(osuTK.Input.Key.Space));
AddAssert("score shown", () => Player.IsScoreShown);
}
[Test]
public void TestStoryboardNoSkipOutro()
{
AddUntilStep("storyboard loaded", () => Player.Beatmap.Value.StoryboardLoaded);
AddUntilStep("storyboard ends", () => Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= storyboard_duration);
AddWaitStep("wait for score", 10);
AddAssert("score shown", () => Player.IsScoreShown);
}
[Test]
public void TestStoryboardExitToSkipOutro()
{
AddUntilStep("storyboard loaded", () => Player.Beatmap.Value.StoryboardLoaded);
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
AddStep("exit via pause", () => Player.ExitViaPause());
AddAssert("score shown", () => Player.IsScoreShown);
}
protected override bool AllowFail => false;
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new OutroPlayer();
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new Beatmap();
beatmap.HitObjects.Add(new HitCircle());
return beatmap;
}
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) =>
new ClockBackedTestWorkingBeatmap(beatmap, storyboard ?? this.storyboard, Clock, Audio);
protected class OutroPlayer : TestPlayer
{
public void ExitViaPause() => PerformExit(true);
public bool IsScoreShown => !this.IsCurrentScreen() && this.GetChildScreen() is ResultsScreen;
public OutroPlayer()
: base(false)
{
}
protected override Task ImportScore(Score score)
{
// avoid database errors from trying to store the score
return Task.CompletedTask;
}
}
}
}

View File

@ -49,6 +49,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
AllowPause = false,
AllowRestart = false,
AllowSkippingIntro = false,
AllowSkippingOutro = false,
})
{
this.userIds = userIds;

View File

@ -228,6 +228,11 @@ namespace osu.Game.Screens.Play
adjustableClock.ChangeSource(track);
}
/// <summary>
/// Gets the endtime of the last element in the storyboard in ms, or start time of the last hitobject if there's no storyboard.
/// </summary>
public double StoryboardEndTime => beatmap.Storyboard.LatestEventTime ?? 0;
protected override void Update()
{
if (!IsPaused.Value)
@ -235,6 +240,8 @@ namespace osu.Game.Screens.Play
userOffsetClock.ProcessFrame();
}
updateHasStoryboardEnded();
base.Update();
}
@ -296,5 +303,23 @@ namespace osu.Game.Screens.Play
{
}
}
# region Storyboard outro logic
public IBindable<bool> HasStoryboardEnded => hasStoryboardEnded;
public bool HasTimeLeftInStoryboard => GameplayClock.CurrentTime <= StoryboardEndTime;
private readonly BindableBool hasStoryboardEnded = new BindableBool(true);
private void updateHasStoryboardEnded()
{
if (StoryboardEndTime == 0)
return;
hasStoryboardEnded.Value = GameplayClock.CurrentTime >= StoryboardEndTime;
}
# endregion
}
}

View File

@ -74,6 +74,8 @@ namespace osu.Game.Screens.Play
private Bindable<bool> mouseWheelDisabled;
private Bindable<bool> storyboardEnabled;
private readonly Bindable<bool> storyboardReplacesBackground = new Bindable<bool>();
protected readonly Bindable<bool> LocalUserPlaying = new Bindable<bool>();
@ -106,6 +108,8 @@ namespace osu.Game.Screens.Play
private SkipOverlay skipOverlay;
private SkipOverlay skipOutroOverlay;
protected ScoreProcessor ScoreProcessor { get; private set; }
protected HealthProcessor HealthProcessor { get; private set; }
@ -190,6 +194,8 @@ namespace osu.Game.Screens.Play
mouseWheelDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableWheel);
storyboardEnabled = config.GetBindable<bool>(OsuSetting.ShowStoryboard);
if (game != null)
gameActive.BindTo(game.IsActive);
@ -285,6 +291,9 @@ namespace osu.Game.Screens.Play
ScoreProcessor.HasCompleted.ValueChanged += updateCompletionState;
HealthProcessor.Failed += onFail;
// Keep track of whether the storyboard ended after the playable portion
GameplayClockContainer.HasStoryboardEnded.ValueChanged += updateCompletionState;
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
mod.ApplyToScoreProcessor(ScoreProcessor);
@ -360,6 +369,10 @@ namespace osu.Game.Screens.Play
{
RequestSkip = performUserRequestedSkip
},
skipOutroOverlay = new SkipOverlay(GameplayClockContainer.StoryboardEndTime)
{
RequestSkip = scheduleCompletion
},
FailOverlay = new FailOverlay
{
OnRetry = Restart,
@ -389,6 +402,9 @@ namespace osu.Game.Screens.Play
if (!Configuration.AllowSkippingIntro)
skipOverlay.Expire();
if (!Configuration.AllowSkippingOutro)
skipOutroOverlay.Expire();
if (Configuration.AllowRestart)
{
container.Add(new HotkeyRetryOverlay
@ -403,6 +419,8 @@ namespace osu.Game.Screens.Play
});
}
skipOutroOverlay.Hide();
return container;
}
@ -523,6 +541,14 @@ namespace osu.Game.Screens.Play
Pause();
return;
}
// show the score if in storyboard outro (score has been set)
bool scoreReady = prepareScoreForDisplayTask != null && prepareScoreForDisplayTask.IsCompleted;
if (scoreReady)
{
scheduleCompletion();
}
}
this.Exit();
@ -611,6 +637,14 @@ namespace osu.Game.Screens.Play
return score.ScoreInfo;
});
// show skip overlay if storyboard is enabled and has an outro
if (storyboardEnabled.Value && GameplayClockContainer.HasTimeLeftInStoryboard)
{
skipOutroOverlay.Show();
completionProgressDelegate = null;
return;
}
using (BeginDelayedSequence(RESULTS_DISPLAY_DELAY))
scheduleCompletion();
}

View File

@ -24,5 +24,10 @@ namespace osu.Game.Screens.Play
/// Whether the player should be allowed to skip the intro, advancing to the start of gameplay.
/// </summary>
public bool AllowSkippingIntro { get; set; } = true;
/// <summary>
/// Whether the player should be allowed to skip the outro, advancing to the end of a storyboard.
/// </summary>
public bool AllowSkippingOutro { get; set; } = true;
}
}

View File

@ -45,6 +45,7 @@ namespace osu.Game.Storyboards
/// Videos and samples return StartTime as their EndTIme.
/// </remarks>
public double? LatestEventTime => Layers.SelectMany(l => l.Elements).OrderByDescending(e => e.EndTime).FirstOrDefault()?.EndTime;
/// <summary>
/// Depth of the currently front-most storyboard layer, excluding the overlay layer.
/// </summary>