1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-16 10:23:04 +08:00

Merge branch 'master' into fix-cursor-in-scale-container

This commit is contained in:
Dean Herbert 2019-03-05 22:22:56 +09:00 committed by GitHub
commit 6b042eae94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 340 additions and 151 deletions

View File

@ -333,6 +333,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual("hit_2.wav", getTestableSampleInfo(hitObjects[1]).LookupNames.First()); Assert.AreEqual("hit_2.wav", getTestableSampleInfo(hitObjects[1]).LookupNames.First());
Assert.AreEqual("normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First()); Assert.AreEqual("normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
Assert.AreEqual("hit_1.wav", getTestableSampleInfo(hitObjects[3]).LookupNames.First()); Assert.AreEqual("hit_1.wav", getTestableSampleInfo(hitObjects[3]).LookupNames.First());
Assert.AreEqual(70, getTestableSampleInfo(hitObjects[3]).Volume);
} }
SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);

View File

@ -13,4 +13,4 @@ SampleSet: Normal
255,193,2170,1,0,0:0:0:0:hit_1.wav 255,193,2170,1,0,0:0:0:0:hit_1.wav
256,191,2638,5,0,0:0:0:0:hit_2.wav 256,191,2638,5,0,0:0:0:0:hit_2.wav
255,193,3107,1,0,0:0:0:0: 255,193,3107,1,0,0:0:0:0:
256,191,3576,1,0,0:0:0:0:hit_1.wav 256,191,3576,1,0,0:0:0:70:hit_1.wav

View File

@ -188,10 +188,10 @@ namespace osu.Game.Tests.Visual
public void PauseTest() public void PauseTest()
{ {
performFullSetup(true); performFullSetup(true);
AddStep("Pause", () => player.CurrentPauseContainer.Pause()); AddStep("Pause", () => player.CurrentPausableGameplayContainer.Pause());
waitForDim(); waitForDim();
AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed()); AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed());
AddStep("Unpause", () => player.CurrentPauseContainer.Resume()); AddStep("Unpause", () => player.CurrentPausableGameplayContainer.Resume());
waitForDim(); waitForDim();
AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed()); AddAssert("Screen is dimmed", () => songSelect.IsBackgroundDimmed());
} }
@ -328,7 +328,7 @@ namespace osu.Game.Tests.Visual
}; };
} }
public PauseContainer CurrentPauseContainer => PauseContainer; public PausableGameplayContainer CurrentPausableGameplayContainer => PausableGameplayContainer;
public UserDimContainer CurrentStoryboardContainer => StoryboardContainer; public UserDimContainer CurrentStoryboardContainer => StoryboardContainer;

View File

@ -0,0 +1,47 @@
// 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 System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays.Direct;
using osu.Game.Rulesets.Osu;
using osu.Game.Tests.Beatmaps;
using osuTK;
namespace osu.Game.Tests.Visual
{
public class TestCaseDirectPanel : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(DirectGridPanel),
typeof(DirectListPanel),
typeof(IconPill)
};
[BackgroundDependencyLoader]
private void load()
{
var beatmap = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo, null);
beatmap.BeatmapSetInfo.OnlineInfo.HasVideo = true;
beatmap.BeatmapSetInfo.OnlineInfo.HasStoryboard = true;
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Padding = new MarginPadding(20),
Spacing = new Vector2(0, 20),
Children = new Drawable[]
{
new DirectGridPanel(beatmap.BeatmapSetInfo),
new DirectListPanel(beatmap.BeatmapSetInfo)
}
};
}
}
}

View File

@ -17,15 +17,15 @@ namespace osu.Game.Tests.Visual
[Description("player pause/fail screens")] [Description("player pause/fail screens")]
public class TestCaseGameplayMenuOverlay : ManualInputManagerTestCase public class TestCaseGameplayMenuOverlay : ManualInputManagerTestCase
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseContainer) }; public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(FailOverlay), typeof(PausableGameplayContainer) };
private FailOverlay failOverlay; private FailOverlay failOverlay;
private PauseContainer.PauseOverlay pauseOverlay; private PausableGameplayContainer.PauseOverlay pauseOverlay;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Add(pauseOverlay = new PauseContainer.PauseOverlay Add(pauseOverlay = new PausableGameplayContainer.PauseOverlay
{ {
OnResume = () => Logger.Log(@"Resume"), OnResume = () => Logger.Log(@"Resume"),
OnRetry = () => Logger.Log(@"Retry"), OnRetry = () => Logger.Log(@"Retry"),

View File

@ -3,6 +3,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Framework.Timing; using osu.Framework.Timing;
@ -19,14 +20,20 @@ namespace osu.Game.Tests.Visual
private readonly StopwatchClock clock; private readonly StopwatchClock clock;
[Cached]
private readonly GameplayClock gameplayClock;
private readonly FramedClock framedClock;
public TestCaseSongProgress() public TestCaseSongProgress()
{ {
clock = new StopwatchClock(true); clock = new StopwatchClock(true);
gameplayClock = new GameplayClock(framedClock = new FramedClock(clock));
Add(progress = new SongProgress Add(progress = new SongProgress
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AudioClock = new StopwatchClock(true),
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
}); });
@ -68,8 +75,13 @@ namespace osu.Game.Tests.Visual
progress.Objects = objects; progress.Objects = objects;
graph.Objects = objects; graph.Objects = objects;
progress.AudioClock = clock; progress.RequestSeek = pos => clock.Seek(pos);
progress.OnSeek = pos => clock.Seek(pos); }
protected override void Update()
{
base.Update();
framedClock.ProcessFrame();
} }
private class TestSongProgressGraph : SongProgressGraph private class TestSongProgressGraph : SongProgressGraph

View File

@ -213,12 +213,6 @@ namespace osu.Game.Online.Leaderboards
pendingUpdateScores?.Cancel(); pendingUpdateScores?.Cancel();
pendingUpdateScores = Schedule(() => pendingUpdateScores = Schedule(() =>
{ {
if (api?.IsLoggedIn != true)
{
PlaceholderState = PlaceholderState.NotLoggedIn;
return;
}
PlaceholderState = PlaceholderState.Retrieving; PlaceholderState = PlaceholderState.Retrieving;
loading.Show(); loading.Show();
@ -231,6 +225,12 @@ namespace osu.Game.Online.Leaderboards
if (getScoresRequest == null) if (getScoresRequest == null)
return; return;
if (api?.IsLoggedIn != true)
{
PlaceholderState = PlaceholderState.NotLoggedIn;
return;
}
getScoresRequest.Failure += e => Schedule(() => getScoresRequest.Failure += e => Schedule(() =>
{ {
if (e is OperationCanceledException) if (e is OperationCanceledException)
@ -243,6 +243,11 @@ namespace osu.Game.Online.Leaderboards
}); });
} }
/// <summary>
/// Performs a fetch/refresh of scores to be displayed.
/// </summary>
/// <param name="scoresCallback">A callback which should be called when fetching is completed. Scheduling is not required.</param>
/// <returns>An <see cref="APIRequest"/> responsible for the fetch operation. This will be queued and performed automatically.</returns>
protected abstract APIRequest FetchScores(Action<IEnumerable<ScoreInfo>> scoresCallback); protected abstract APIRequest FetchScores(Action<IEnumerable<ScoreInfo>> scoresCallback);
private Placeholder currentPlaceholder; private Placeholder currentPlaceholder;

View File

@ -13,6 +13,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
namespace osu.Game.Overlays.Direct namespace osu.Game.Overlays.Direct
{ {
@ -23,6 +24,7 @@ namespace osu.Game.Overlays.Direct
private const float vertical_padding = 5; private const float vertical_padding = 5;
private const float height = 70; private const float height = 70;
private FillFlowContainer statusContainer;
private PlayButton playButton; private PlayButton playButton;
private Box progressBar; private Box progressBar;
@ -107,6 +109,18 @@ namespace osu.Game.Overlays.Direct
} }
}, },
new FillFlowContainer new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
statusContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Vertical = vertical_padding, Horizontal = 5 },
Spacing = new Vector2(5),
},
new FillFlowContainer
{ {
AutoSizeAxes = Axes.X, AutoSizeAxes = Axes.X,
Height = 20, Height = 20,
@ -115,6 +129,8 @@ namespace osu.Game.Overlays.Direct
}, },
}, },
}, },
},
},
} }
}, },
new FillFlowContainer new FillFlowContainer
@ -194,6 +210,23 @@ namespace osu.Game.Overlays.Direct
Colour = colours.Yellow, Colour = colours.Yellow,
}, },
}); });
if (SetInfo.OnlineInfo?.HasVideo ?? false)
{
statusContainer.Add(new IconPill(FontAwesome.fa_film) { IconSize = new Vector2(20) });
}
if (SetInfo.OnlineInfo?.HasStoryboard ?? false)
{
statusContainer.Add(new IconPill(FontAwesome.fa_image) { IconSize = new Vector2(20) });
}
statusContainer.Add(new BeatmapSetOnlineStatusPill
{
TextSize = 12,
TextPadding = new MarginPadding { Horizontal = 10, Vertical = 4 },
Status = SetInfo.OnlineInfo?.Status ?? BeatmapSetOnlineStatus.None,
});
} }
} }
} }

View File

@ -12,6 +12,14 @@ namespace osu.Game.Overlays.Direct
{ {
public class IconPill : CircularContainer public class IconPill : CircularContainer
{ {
public Vector2 IconSize
{
get => iconContainer.Size;
set => iconContainer.Size = value;
}
private readonly Container iconContainer;
public IconPill(FontAwesome icon) public IconPill(FontAwesome icon)
{ {
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
@ -25,16 +33,16 @@ namespace osu.Game.Overlays.Direct
Colour = Color4.Black, Colour = Color4.Black,
Alpha = 0.5f, Alpha = 0.5f,
}, },
new Container iconContainer = new Container
{ {
AutoSizeAxes = Axes.Both, Size = new Vector2(22),
Margin = new MarginPadding(5), Padding = new MarginPadding(5),
Child = new SpriteIcon Child = new SpriteIcon
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Icon = icon, Icon = icon,
Size = new Vector2(12),
}, },
}, },
}; };

View File

@ -301,7 +301,16 @@ namespace osu.Game.Rulesets.Objects.Legacy
{ {
// Todo: This should return the normal SampleInfos if the specified sample file isn't found, but that's a pretty edge-case scenario // Todo: This should return the normal SampleInfos if the specified sample file isn't found, but that's a pretty edge-case scenario
if (!string.IsNullOrEmpty(bankInfo.Filename)) if (!string.IsNullOrEmpty(bankInfo.Filename))
return new List<SampleInfo> { new FileSampleInfo { Filename = bankInfo.Filename } }; {
return new List<SampleInfo>
{
new FileSampleInfo
{
Filename = bankInfo.Filename,
Volume = bankInfo.Volume
}
};
}
var soundTypes = new List<SampleInfo> var soundTypes = new List<SampleInfo>
{ {

View File

@ -41,6 +41,7 @@ namespace osu.Game.Rulesets.UI
protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
{ {
InternalChild = KeyBindingContainer = CreateKeyBindingContainer(ruleset, variant, unique); InternalChild = KeyBindingContainer = CreateKeyBindingContainer(ruleset, variant, unique);
gameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock()));
} }
#region Action mapping (for replays) #region Action mapping (for replays)
@ -86,22 +87,28 @@ namespace osu.Game.Rulesets.UI
#region Clock control #region Clock control
private ManualClock clock; private readonly ManualClock manualClock;
private IFrameBasedClock parentClock;
private readonly FramedClock framedClock;
[Cached]
private GameplayClock gameplayClock;
private IFrameBasedClock parentGameplayClock;
[BackgroundDependencyLoader(true)]
private void load(OsuConfigManager config, GameplayClock clock)
{
mouseDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableButtons);
if (clock != null)
parentGameplayClock = clock;
}
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
setClock();
//our clock will now be our parent's clock, but we want to replace this to allow manual control.
parentClock = Clock;
ProcessCustomClock = false;
Clock = new FramedClock(clock = new ManualClock
{
CurrentTime = parentClock.CurrentTime,
Rate = parentClock.Rate,
});
} }
/// <summary> /// <summary>
@ -147,25 +154,28 @@ namespace osu.Game.Rulesets.UI
private void updateClock() private void updateClock()
{ {
if (parentClock == null) return; if (parentGameplayClock == null)
setClock(); // LoadComplete may not be run yet, but we still want the clock.
clock.Rate = parentClock.Rate; validState = true;
clock.IsRunning = parentClock.IsRunning;
var newProposedTime = parentClock.CurrentTime; manualClock.Rate = parentGameplayClock.Rate;
manualClock.IsRunning = parentGameplayClock.IsRunning;
var newProposedTime = parentGameplayClock.CurrentTime;
try try
{ {
if (Math.Abs(clock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f)
{ {
newProposedTime = clock.Rate > 0 newProposedTime = manualClock.Rate > 0
? Math.Min(newProposedTime, clock.CurrentTime + sixty_frame_time) ? Math.Min(newProposedTime, manualClock.CurrentTime + sixty_frame_time)
: Math.Max(newProposedTime, clock.CurrentTime - sixty_frame_time); : Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time);
} }
if (!isAttached) if (!isAttached)
{ {
clock.CurrentTime = newProposedTime; manualClock.CurrentTime = newProposedTime;
} }
else else
{ {
@ -177,35 +187,39 @@ namespace osu.Game.Rulesets.UI
validState = false; validState = false;
requireMoreUpdateLoops = true; requireMoreUpdateLoops = true;
clock.CurrentTime = newProposedTime; manualClock.CurrentTime = newProposedTime;
return; return;
} }
clock.CurrentTime = newTime.Value; manualClock.CurrentTime = newTime.Value;
} }
requireMoreUpdateLoops = clock.CurrentTime != parentClock.CurrentTime; requireMoreUpdateLoops = manualClock.CurrentTime != parentGameplayClock.CurrentTime;
} }
finally finally
{ {
// The manual clock time has changed in the above code. The framed clock now needs to be updated // The manual clock time has changed in the above code. The framed clock now needs to be updated
// to ensure that the its time is valid for our children before input is processed // to ensure that the its time is valid for our children before input is processed
Clock.ProcessFrame(); framedClock.ProcessFrame();
} }
} }
private void setClock()
{
// in case a parent gameplay clock isn't available, just use the parent clock.
if (parentGameplayClock == null)
parentGameplayClock = Clock;
Clock = gameplayClock;
ProcessCustomClock = false;
}
#endregion #endregion
#region Setting application (disables etc.) #region Setting application (disables etc.)
private Bindable<bool> mouseDisabled; private Bindable<bool> mouseDisabled;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
mouseDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableButtons);
}
protected override bool Handle(UIEvent e) protected override bool Handle(UIEvent e)
{ {
switch (e) switch (e)

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
@ -40,13 +41,7 @@ namespace osu.Game.Screens.Play
private readonly BreakInfo info; private readonly BreakInfo info;
private readonly BreakArrows breakArrows; private readonly BreakArrows breakArrows;
public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor) public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor = null)
: this(letterboxing)
{
bindProcessor(scoreProcessor);
}
public BreakOverlay(bool letterboxing)
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
Child = fadeContainer = new Container Child = fadeContainer = new Container
@ -98,6 +93,14 @@ namespace osu.Game.Screens.Play
} }
} }
}; };
if (scoreProcessor != null) bindProcessor(scoreProcessor);
}
[BackgroundDependencyLoader(true)]
private void load(GameplayClock clock)
{
if (clock != null) Clock = clock;
} }
protected override void LoadComplete() protected override void LoadComplete()

View File

@ -0,0 +1,43 @@
// 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 osu.Framework.Timing;
namespace osu.Game.Screens.Play
{
/// <summary>
/// A clock which is used for gameplay elements that need to follow audio time 1:1.
/// Exposed via DI by <see cref="PausableGameplayContainer"/>.
/// <remarks>
/// The main purpose of this clock is to stop components using it from accidentally processing the main
/// <see cref="IFrameBasedClock"/>, as this should only be done once to ensure accuracy.
/// </remarks>
/// </summary>
public class GameplayClock : IFrameBasedClock
{
private readonly IFrameBasedClock underlyingClock;
public GameplayClock(IFrameBasedClock underlyingClock)
{
this.underlyingClock = underlyingClock;
}
public double CurrentTime => underlyingClock.CurrentTime;
public double Rate => underlyingClock.Rate;
public bool IsRunning => underlyingClock.IsRunning;
public void ProcessFrame()
{
// we do not want to process the underlying clock.
// this is handled by PauseContainer.
}
public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime;
public double FramesPerSecond => underlyingClock.FramesPerSecond;
public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo;
}
}

View File

@ -40,7 +40,7 @@ namespace osu.Game.Screens.Play
private static bool hasShownNotificationOnce; private static bool hasShownNotificationOnce;
public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, WorkingBeatmap working, IClock offsetClock, IAdjustableClock adjustableClock) public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, WorkingBeatmap working, IAdjustableClock adjustableClock)
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
@ -81,7 +81,7 @@ namespace osu.Game.Screens.Play
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Children = new Drawable[] Children = new Drawable[]
{ {
KeyCounter = CreateKeyCounter(adjustableClock as IFrameBasedClock), KeyCounter = CreateKeyCounter(),
HoldToQuit = CreateHoldForMenuButton(), HoldToQuit = CreateHoldForMenuButton(),
} }
} }
@ -91,9 +91,8 @@ namespace osu.Game.Screens.Play
BindRulesetContainer(rulesetContainer); BindRulesetContainer(rulesetContainer);
Progress.Objects = rulesetContainer.Objects; Progress.Objects = rulesetContainer.Objects;
Progress.AudioClock = offsetClock;
Progress.AllowSeeking = rulesetContainer.HasReplayLoaded.Value; Progress.AllowSeeking = rulesetContainer.HasReplayLoaded.Value;
Progress.OnSeek = pos => adjustableClock.Seek(pos); Progress.RequestSeek = pos => adjustableClock.Seek(pos);
ModDisplay.Current.BindTo(working.Mods); ModDisplay.Current.BindTo(working.Mods);
@ -202,13 +201,12 @@ namespace osu.Game.Screens.Play
Margin = new MarginPadding { Top = 20 } Margin = new MarginPadding { Top = 20 }
}; };
protected virtual KeyCounterCollection CreateKeyCounter(IFrameBasedClock offsetClock) => new KeyCounterCollection protected virtual KeyCounterCollection CreateKeyCounter() => new KeyCounterCollection
{ {
FadeTime = 50, FadeTime = 50,
Anchor = Anchor.BottomRight, Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight, Origin = Anchor.BottomRight,
Margin = new MarginPadding(10), Margin = new MarginPadding(10),
AudioClock = offsetClock
}; };
protected virtual SongProgress CreateProgress() => new SongProgress protected virtual SongProgress CreateProgress() => new SongProgress

View File

@ -71,9 +71,12 @@ namespace osu.Game.Screens.Play
Name = name; Name = name;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader(true)]
private void load(TextureStore textures) private void load(TextureStore textures, GameplayClock clock)
{ {
if (clock != null)
Clock = clock;
Children = new Drawable[] Children = new Drawable[]
{ {
buttonSprite = new Sprite buttonSprite = new Sprite

View File

@ -8,7 +8,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Timing;
using osu.Game.Configuration; using osu.Game.Configuration;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -37,9 +36,6 @@ namespace osu.Game.Screens.Play
key.FadeTime = FadeTime; key.FadeTime = FadeTime;
key.KeyDownTextColor = KeyDownTextColor; key.KeyDownTextColor = KeyDownTextColor;
key.KeyUpTextColor = KeyUpTextColor; key.KeyUpTextColor = KeyUpTextColor;
// Use the same clock object as SongProgress for saving KeyCounter state
if (AudioClock != null)
key.Clock = AudioClock;
} }
public void ResetCount() public void ResetCount()
@ -125,8 +121,6 @@ namespace osu.Game.Screens.Play
public override bool HandleNonPositionalInput => receptor == null; public override bool HandleNonPositionalInput => receptor == null;
public override bool HandlePositionalInput => receptor == null; public override bool HandlePositionalInput => receptor == null;
public IFrameBasedClock AudioClock { get; set; }
private Receptor receptor; private Receptor receptor;
public Receptor GetReceptor() public Receptor GetReceptor()

View File

@ -14,10 +14,11 @@ using osuTK.Graphics;
namespace osu.Game.Screens.Play namespace osu.Game.Screens.Play
{ {
/// <summary> /// <summary>
/// A container which handles pausing children, displaying a pause overlay with choices etc. /// A container which handles pausing children, displaying a pause overlay with choices and processing the clock.
/// Exposes a <see cref="GameplayClock"/> to children via DI.
/// This alleviates a lot of the intricate pause logic from being in <see cref="Player"/> /// This alleviates a lot of the intricate pause logic from being in <see cref="Player"/>
/// </summary> /// </summary>
public class PauseContainer : Container public class PausableGameplayContainer : Container
{ {
public readonly BindableBool IsPaused = new BindableBool(); public readonly BindableBool IsPaused = new BindableBool();
@ -43,24 +44,32 @@ namespace osu.Game.Screens.Play
public Action OnRetry; public Action OnRetry;
public Action OnQuit; public Action OnQuit;
private readonly FramedClock framedClock; private readonly FramedClock offsetClock;
private readonly DecoupleableInterpolatingFramedClock decoupledClock; private readonly DecoupleableInterpolatingFramedClock adjustableClock;
/// <summary> /// <summary>
/// Creates a new <see cref="PauseContainer"/>. /// The final clock which is exposed to underlying components.
/// </summary> /// </summary>
/// <param name="framedClock">The gameplay clock. This is the clock that will process frames.</param> [Cached]
/// <param name="decoupledClock">The seekable clock. This is the clock that will be paused and resumed.</param> private readonly GameplayClock gameplayClock;
public PauseContainer(FramedClock framedClock, DecoupleableInterpolatingFramedClock decoupledClock)
/// <summary>
/// Creates a new <see cref="PausableGameplayContainer"/>.
/// </summary>
/// <param name="offsetClock">The gameplay clock. This is the clock that will process frames. Includes user/system offsets.</param>
/// <param name="adjustableClock">The seekable clock. This is the clock that will be paused and resumed. Should not be processed (it is processed automatically by <see cref="offsetClock"/>).</param>
public PausableGameplayContainer(FramedClock offsetClock, DecoupleableInterpolatingFramedClock adjustableClock)
{ {
this.framedClock = framedClock; this.offsetClock = offsetClock;
this.decoupledClock = decoupledClock; this.adjustableClock = adjustableClock;
gameplayClock = new GameplayClock(offsetClock);
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
AddInternal(content = new Container AddInternal(content = new Container
{ {
Clock = this.framedClock, Clock = this.offsetClock,
ProcessCustomClock = false, ProcessCustomClock = false,
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both
}); });
@ -84,7 +93,7 @@ namespace osu.Game.Screens.Play
if (IsPaused.Value) return; if (IsPaused.Value) return;
// stop the seekable clock (stops the audio eventually) // stop the seekable clock (stops the audio eventually)
decoupledClock.Stop(); adjustableClock.Stop();
IsPaused.Value = true; IsPaused.Value = true;
pauseOverlay.Show(); pauseOverlay.Show();
@ -102,8 +111,8 @@ namespace osu.Game.Screens.Play
// Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time // Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time
// This accounts for the audio clock source potentially taking time to enter a completely stopped state // This accounts for the audio clock source potentially taking time to enter a completely stopped state
decoupledClock.Seek(decoupledClock.CurrentTime); adjustableClock.Seek(adjustableClock.CurrentTime);
decoupledClock.Start(); adjustableClock.Start();
pauseOverlay.Hide(); pauseOverlay.Hide();
} }
@ -123,7 +132,7 @@ namespace osu.Game.Screens.Play
Pause(); Pause();
if (!IsPaused.Value) if (!IsPaused.Value)
framedClock.ProcessFrame(); offsetClock.ProcessFrame();
base.Update(); base.Update();
} }

View File

@ -67,7 +67,7 @@ namespace osu.Game.Screens.Play
[Resolved] [Resolved]
private ScoreManager scoreManager { get; set; } private ScoreManager scoreManager { get; set; }
protected PauseContainer PauseContainer { get; private set; } protected PausableGameplayContainer PausableGameplayContainer { get; private set; }
private RulesetInfo ruleset; private RulesetInfo ruleset;
@ -170,10 +170,10 @@ namespace osu.Game.Screens.Play
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
PauseContainer = new PauseContainer(offsetClock, adjustableClock) PausableGameplayContainer = new PausableGameplayContainer(offsetClock, adjustableClock)
{ {
Retries = RestartCount, Retries = RestartCount,
OnRetry = Restart, OnRetry = restart,
OnQuit = performUserRequestedExit, OnQuit = performUserRequestedExit,
CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded.Value, CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded.Value,
Children = new Container[] Children = new Container[]
@ -191,32 +191,26 @@ namespace osu.Game.Screens.Play
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
ProcessCustomClock = false,
Breaks = beatmap.Breaks Breaks = beatmap.Breaks
}, },
new ScalingContainer(ScalingMode.Gameplay) new ScalingContainer(ScalingMode.Gameplay)
{ {
Child = RulesetContainer.Cursor?.CreateProxy() ?? new Container(), Child = RulesetContainer.Cursor?.CreateProxy() ?? new Container(),
}, },
HUDOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working, offsetClock, adjustableClock) HUDOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working, adjustableClock)
{ {
Clock = Clock, // hud overlay doesn't want to use the audio clock directly
ProcessCustomClock = false,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre Origin = Anchor.Centre
}, },
new SkipOverlay(RulesetContainer.GameplayStartTime) new SkipOverlay(RulesetContainer.GameplayStartTime)
{ {
Clock = Clock, // skip button doesn't want to use the audio clock directly RequestSeek = time => adjustableClock.Seek(time)
ProcessCustomClock = false,
AdjustableClock = adjustableClock,
FramedClock = offsetClock,
}, },
} }
}, },
failOverlay = new FailOverlay failOverlay = new FailOverlay
{ {
OnRetry = Restart, OnRetry = restart,
OnQuit = performUserRequestedExit, OnQuit = performUserRequestedExit,
}, },
new HotkeyRetryOverlay new HotkeyRetryOverlay
@ -226,7 +220,7 @@ namespace osu.Game.Screens.Play
if (!this.IsCurrentScreen()) return; if (!this.IsCurrentScreen()) return;
fadeOut(true); fadeOut(true);
Restart(); restart();
}, },
} }
}; };
@ -234,7 +228,7 @@ namespace osu.Game.Screens.Play
HUDOverlay.HoldToQuit.Action = performUserRequestedExit; HUDOverlay.HoldToQuit.Action = performUserRequestedExit;
HUDOverlay.KeyCounter.Visible.BindTo(RulesetContainer.HasReplayLoaded); HUDOverlay.KeyCounter.Visible.BindTo(RulesetContainer.HasReplayLoaded);
RulesetContainer.IsPaused.BindTo(PauseContainer.IsPaused); RulesetContainer.IsPaused.BindTo(PausableGameplayContainer.IsPaused);
if (ShowStoryboard.Value) if (ShowStoryboard.Value)
initializeStoryboard(false); initializeStoryboard(false);
@ -263,7 +257,7 @@ namespace osu.Game.Screens.Play
this.Exit(); this.Exit();
} }
public void Restart() private void restart()
{ {
if (!this.IsCurrentScreen()) return; if (!this.IsCurrentScreen()) return;
@ -367,7 +361,7 @@ namespace osu.Game.Screens.Play
this.Delay(750).Schedule(() => this.Delay(750).Schedule(() =>
{ {
if (!PauseContainer.IsPaused.Value) if (!PausableGameplayContainer.IsPaused.Value)
{ {
adjustableClock.Start(); adjustableClock.Start();
} }
@ -375,8 +369,8 @@ namespace osu.Game.Screens.Play
}); });
}); });
PauseContainer.Alpha = 0; PausableGameplayContainer.Alpha = 0;
PauseContainer.FadeIn(750, Easing.OutQuint); PausableGameplayContainer.FadeIn(750, Easing.OutQuint);
} }
public override void OnSuspending(IScreen next) public override void OnSuspending(IScreen next)
@ -394,7 +388,7 @@ namespace osu.Game.Screens.Play
return true; return true;
} }
if ((!AllowPause || HasFailed || !ValidForResume || PauseContainer?.IsPaused.Value != false || RulesetContainer?.HasReplayLoaded.Value != false) && (!PauseContainer?.IsResuming ?? true)) if ((!AllowPause || HasFailed || !ValidForResume || PausableGameplayContainer?.IsPaused.Value != false || RulesetContainer?.HasReplayLoaded.Value != false) && (!PausableGameplayContainer?.IsResuming ?? true))
{ {
// In the case of replays, we may have changed the playback rate. // In the case of replays, we may have changed the playback rate.
applyRateFromMods(); applyRateFromMods();
@ -403,7 +397,7 @@ namespace osu.Game.Screens.Play
} }
if (LoadedBeatmapSuccessfully) if (LoadedBeatmapSuccessfully)
PauseContainer?.Pause(); PausableGameplayContainer?.Pause();
return true; return true;
} }
@ -417,7 +411,7 @@ namespace osu.Game.Screens.Play
storyboardReplacesBackground.Value = false; storyboardReplacesBackground.Value = false;
} }
protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !PauseContainer.IsPaused.Value; protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !PausableGameplayContainer.IsPaused.Value;
private void initializeStoryboard(bool asyncLoad) private void initializeStoryboard(bool asyncLoad)
{ {

View File

@ -17,6 +17,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Play.PlayerSettings;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -296,6 +297,7 @@ namespace osu.Game.Screens.Play
private readonly WorkingBeatmap beatmap; private readonly WorkingBeatmap beatmap;
private LoadingAnimation loading; private LoadingAnimation loading;
private Sprite backgroundSprite; private Sprite backgroundSprite;
private ModDisplay modDisplay;
public bool Loading public bool Loading
{ {
@ -322,7 +324,7 @@ namespace osu.Game.Screens.Play
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
var metadata = beatmap?.BeatmapInfo?.Metadata ?? new BeatmapMetadata(); var metadata = beatmap.BeatmapInfo?.Metadata ?? new BeatmapMetadata();
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
Children = new Drawable[] Children = new Drawable[]
@ -391,6 +393,14 @@ namespace osu.Game.Screens.Play
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
}, },
new ModDisplay
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = 20 },
Current = beatmap.Mods
}
}, },
} }
}; };

View File

@ -9,7 +9,6 @@ using osu.Framework.Audio.Sample;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Framework.Timing;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking;
@ -27,8 +26,7 @@ namespace osu.Game.Screens.Play
{ {
private readonly double startTime; private readonly double startTime;
public IAdjustableClock AdjustableClock; public Action<double> RequestSeek;
public IFrameBasedClock FramedClock;
private Button button; private Button button;
private Box remainingTimeBox; private Box remainingTimeBox;
@ -54,16 +52,13 @@ namespace osu.Game.Screens.Play
Origin = Anchor.Centre; Origin = Anchor.Centre;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader(true)]
private void load(OsuColour colours) private void load(OsuColour colours, GameplayClock clock)
{ {
var baseClock = Clock; var baseClock = Clock;
if (FramedClock != null) if (clock != null)
{ Clock = clock;
Clock = FramedClock;
ProcessCustomClock = false;
}
Children = new Drawable[] Children = new Drawable[]
{ {
@ -111,7 +106,7 @@ namespace osu.Game.Screens.Play
using (BeginAbsoluteSequence(beginFadeTime)) using (BeginAbsoluteSequence(beginFadeTime))
this.FadeOut(fade_time); this.FadeOut(fade_time);
button.Action = () => AdjustableClock?.Seek(startTime - skip_required_cutoff - fade_time); button.Action = () => RequestSeek?.Invoke(startTime - skip_required_cutoff - fade_time);
displayTime = Time.Current; displayTime = Time.Current;

View File

@ -10,7 +10,6 @@ using osu.Game.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using System.Linq; using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Timing;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -29,18 +28,11 @@ namespace osu.Game.Screens.Play
private readonly SongProgressGraph graph; private readonly SongProgressGraph graph;
private readonly SongProgressInfo info; private readonly SongProgressInfo info;
public Action<double> OnSeek; public Action<double> RequestSeek;
public override bool HandleNonPositionalInput => AllowSeeking; public override bool HandleNonPositionalInput => AllowSeeking;
public override bool HandlePositionalInput => AllowSeeking; public override bool HandlePositionalInput => AllowSeeking;
private IClock audioClock;
public IClock AudioClock
{
set => audioClock = info.AudioClock = value;
}
private double lastHitTime => ((objects.Last() as IHasEndTime)?.EndTime ?? objects.Last().StartTime) + 1; private double lastHitTime => ((objects.Last() as IHasEndTime)?.EndTime ?? objects.Last().StartTime) + 1;
private double firstHitTime => objects.First().StartTime; private double firstHitTime => objects.First().StartTime;
@ -63,9 +55,14 @@ namespace osu.Game.Screens.Play
private readonly BindableBool replayLoaded = new BindableBool(); private readonly BindableBool replayLoaded = new BindableBool();
[BackgroundDependencyLoader] private GameplayClock gameplayClock;
private void load(OsuColour colours)
[BackgroundDependencyLoader(true)]
private void load(OsuColour colours, GameplayClock clock)
{ {
if (clock != null)
gameplayClock = clock;
graph.FillColour = bar.FillColour = colours.BlueLighter; graph.FillColour = bar.FillColour = colours.BlueLighter;
} }
@ -99,7 +96,7 @@ namespace osu.Game.Screens.Play
Alpha = 0, Alpha = 0,
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
OnSeek = position => OnSeek?.Invoke(position), OnSeek = time => RequestSeek?.Invoke(time),
}, },
}; };
} }
@ -157,14 +154,11 @@ namespace osu.Game.Screens.Play
if (objects == null) if (objects == null)
return; return;
double position = audioClock?.CurrentTime ?? Time.Current; double position = gameplayClock?.CurrentTime ?? Time.Current;
double progress = (position - firstHitTime) / (lastHitTime - firstHitTime); double progress = Math.Min(1, (position - firstHitTime) / (lastHitTime - firstHitTime));
if (progress < 1)
{
bar.CurrentTime = position; bar.CurrentTime = position;
graph.Progress = (int)(graph.ColumnCount * progress); graph.Progress = (int)(graph.ColumnCount * progress);
} }
} }
}
} }

View File

@ -4,7 +4,6 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using System; using System;
@ -27,8 +26,6 @@ namespace osu.Game.Screens.Play
private const int margin = 10; private const int margin = 10;
public IClock AudioClock;
public double StartTime public double StartTime
{ {
set => startTime = value; set => startTime = value;
@ -39,9 +36,14 @@ namespace osu.Game.Screens.Play
set => endTime = value; set => endTime = value;
} }
[BackgroundDependencyLoader] private GameplayClock gameplayClock;
private void load(OsuColour colours)
[BackgroundDependencyLoader(true)]
private void load(OsuColour colours, GameplayClock clock)
{ {
if (clock != null)
gameplayClock = clock;
Children = new Drawable[] Children = new Drawable[]
{ {
timeCurrent = new OsuSpriteText timeCurrent = new OsuSpriteText
@ -80,7 +82,9 @@ namespace osu.Game.Screens.Play
{ {
base.Update(); base.Update();
double songCurrentTime = AudioClock.CurrentTime - startTime; var time = gameplayClock?.CurrentTime ?? Time.Current;
double songCurrentTime = time - startTime;
int currentPercent = Math.Max(0, Math.Min(100, (int)(songCurrentTime / songLength * 100))); int currentPercent = Math.Max(0, Math.Min(100, (int)(songCurrentTime / songLength * 100)));
int currentSecond = (int)Math.Floor(songCurrentTime / 1000.0); int currentSecond = (int)Math.Floor(songCurrentTime / 1000.0);
@ -93,7 +97,7 @@ namespace osu.Game.Screens.Play
if (currentSecond != previousSecond && songCurrentTime < songLength) if (currentSecond != previousSecond && songCurrentTime < songLength)
{ {
timeCurrent.Text = formatTime(TimeSpan.FromSeconds(currentSecond)); timeCurrent.Text = formatTime(TimeSpan.FromSeconds(currentSecond));
timeLeft.Text = formatTime(TimeSpan.FromMilliseconds(endTime - AudioClock.CurrentTime)); timeLeft.Text = formatTime(TimeSpan.FromMilliseconds(endTime - time));
previousSecond = currentSecond; previousSecond = currentSecond;
} }

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -21,6 +22,18 @@ namespace osu.Game.Tests.Beatmaps
HitObjects = baseBeatmap.HitObjects; HitObjects = baseBeatmap.HitObjects;
BeatmapInfo.Ruleset = ruleset; BeatmapInfo.Ruleset = ruleset;
BeatmapInfo.BeatmapSet.Metadata = BeatmapInfo.Metadata;
BeatmapInfo.BeatmapSet.Beatmaps = new List<BeatmapInfo> { BeatmapInfo };
BeatmapInfo.BeatmapSet.OnlineInfo = new BeatmapSetOnlineInfo
{
Status = BeatmapSetOnlineStatus.Ranked,
Covers = new BeatmapSetOnlineCovers
{
Cover = "https://assets.ppy.sh/beatmaps/163112/covers/cover.jpg",
Card = "https://assets.ppy.sh/beatmaps/163112/covers/card.jpg",
List = "https://assets.ppy.sh/beatmaps/163112/covers/list.jpg"
}
};
} }
private static Beatmap createTestBeatmap() private static Beatmap createTestBeatmap()