1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 13:22:55 +08:00

Fix spectating when starting from a point that isn't at the beginning of the beatmap

This commit is contained in:
Dean Herbert 2020-10-27 18:56:28 +09:00
parent a289b7034f
commit 42b3aa3359
7 changed files with 93 additions and 18 deletions

View File

@ -93,9 +93,15 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestSpectatingDuringGameplay()
{
start();
sendFrames();
// should seek immediately to available frames
loadSpectatingScreen();
AddStep("advance frame count", () => nextFrame = 300);
sendFrames();
waitForPlayer();
AddUntilStep("playing from correct point in time", () => player.ChildrenOfType<DrawableRuleset>().First().FrameStableClock.CurrentTime > 30000);
}
[Test]

View File

@ -37,6 +37,7 @@ namespace osu.Game.Screens.Play
private readonly DecoupleableInterpolatingFramedClock adjustableClock;
private readonly double gameplayStartTime;
private readonly bool startAtGameplayStart;
private readonly double firstHitObjectTime;
@ -62,10 +63,11 @@ namespace osu.Game.Screens.Play
private readonly FramedOffsetClock platformOffsetClock;
public GameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime)
public GameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime, bool startAtGameplayStart = false)
{
this.beatmap = beatmap;
this.gameplayStartTime = gameplayStartTime;
this.startAtGameplayStart = startAtGameplayStart;
track = beatmap.Track;
firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime;
@ -103,16 +105,21 @@ namespace osu.Game.Screens.Play
userAudioOffset.BindValueChanged(offset => userOffsetClock.Offset = offset.NewValue, true);
// sane default provided by ruleset.
double startTime = Math.Min(0, gameplayStartTime);
double startTime = gameplayStartTime;
// if a storyboard is present, it may dictate the appropriate start time by having events in negative time space.
// this is commonly used to display an intro before the audio track start.
startTime = Math.Min(startTime, beatmap.Storyboard.FirstEventTime);
if (!startAtGameplayStart)
{
startTime = Math.Min(0, startTime);
// some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available.
// this is not available as an option in the live editor but can still be applied via .osu editing.
if (beatmap.BeatmapInfo.AudioLeadIn > 0)
startTime = Math.Min(startTime, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn);
// if a storyboard is present, it may dictate the appropriate start time by having events in negative time space.
// this is commonly used to display an intro before the audio track start.
startTime = Math.Min(startTime, beatmap.Storyboard.FirstEventTime);
// some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available.
// this is not available as an option in the live editor but can still be applied via .osu editing.
if (beatmap.BeatmapInfo.AudioLeadIn > 0)
startTime = Math.Min(startTime, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn);
}
Seek(startTime);

View File

@ -199,7 +199,7 @@ namespace osu.Game.Screens.Play
if (!ScoreProcessor.Mode.Disabled)
config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode);
InternalChild = GameplayClockContainer = new GameplayClockContainer(Beatmap.Value, DrawableRuleset.GameplayStartTime);
InternalChild = GameplayClockContainer = CreateGameplayClockContainer(Beatmap.Value, DrawableRuleset.GameplayStartTime);
AddInternal(gameplayBeatmap = new GameplayBeatmap(playableBeatmap));
AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer));
@ -288,6 +288,8 @@ namespace osu.Game.Screens.Play
IsBreakTime.BindValueChanged(onBreakTimeChanged, true);
}
protected virtual GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new GameplayClockContainer(beatmap, gameplayStart);
private Drawable createUnderlayComponents() =>
DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both };

View File

@ -8,7 +8,7 @@ namespace osu.Game.Screens.Play
{
public class ReplayPlayer : Player
{
private readonly Score score;
protected readonly Score Score;
// Disallow replays from failing. (see https://github.com/ppy/osu/issues/6108)
protected override bool CheckModsAllowFailure() => false;
@ -16,12 +16,12 @@ namespace osu.Game.Screens.Play
public ReplayPlayer(Score score, bool allowPause = true, bool showResults = true)
: base(allowPause, showResults)
{
this.score = score;
this.Score = score;
}
protected override void PrepareReplay()
{
DrawableRuleset?.SetReplayScore(score);
DrawableRuleset?.SetReplayScore(Score);
}
protected override ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, false);
@ -31,9 +31,9 @@ namespace osu.Game.Screens.Play
var baseScore = base.CreateScore();
// Since the replay score doesn't contain statistics, we'll pass them through here.
score.ScoreInfo.HitEvents = baseScore.HitEvents;
Score.ScoreInfo.HitEvents = baseScore.HitEvents;
return score.ScoreInfo;
return Score.ScoreInfo;
}
}
}

View File

@ -130,7 +130,7 @@ namespace osu.Game.Screens.Play
ruleset.Value = resolvedRuleset.RulesetInfo;
beatmap.Value = beatmaps.GetWorkingBeatmap(resolvedBeatmap);
this.Push(new ReplayPlayerLoader(new Score
this.Push(new SpectatorPlayerLoader(new Score
{
ScoreInfo = scoreInfo,
Replay = replay,

View File

@ -0,0 +1,28 @@
// 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.Linq;
using osu.Game.Beatmaps;
using osu.Game.Scoring;
namespace osu.Game.Screens.Play
{
public class SpectatorPlayer : ReplayPlayer
{
public SpectatorPlayer(Score score)
: base(score)
{
}
protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart)
{
// if we already have frames, start gameplay at the point in time they exist, should they be too far into the beatmap.
double? firstFrameTime = Score.Replay.Frames.FirstOrDefault()?.Time;
if (firstFrameTime == null || firstFrameTime <= gameplayStart + 5000)
return base.CreateGameplayClockContainer(beatmap, gameplayStart);
return new GameplayClockContainer(beatmap, firstFrameTime.Value, true);
}
}
}

View File

@ -0,0 +1,32 @@
// 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;
using osu.Framework.Screens;
using osu.Game.Scoring;
namespace osu.Game.Screens.Play
{
public class SpectatorPlayerLoader : PlayerLoader
{
public readonly ScoreInfo Score;
public SpectatorPlayerLoader(Score score)
: base(() => new SpectatorPlayer(score))
{
if (score.Replay == null)
throw new ArgumentException($"{nameof(score)} must have a non-null {nameof(score.Replay)}.", nameof(score));
Score = score.ScoreInfo;
}
public override void OnEntering(IScreen last)
{
// these will be reverted thanks to PlayerLoader's lease.
Mods.Value = Score.Mods;
Ruleset.Value = Score.Ruleset;
base.OnEntering(last);
}
}
}