mirror of
https://github.com/osukey/osukey.git
synced 2025-05-23 14:37:39 +09:00
Merge branch 'master' into extract-slider-tick-creation
This commit is contained in:
commit
f59b9e933e
@ -16,18 +16,18 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestCaseGameplayCursor : OsuTestCase, IProvideCursor
|
public class TestCaseGameplayCursor : OsuTestCase, IProvideCursor
|
||||||
{
|
{
|
||||||
private GameplayCursor cursor;
|
private GameplayCursorContainer cursorContainer;
|
||||||
|
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(CursorTrail) };
|
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(CursorTrail) };
|
||||||
|
|
||||||
public CursorContainer Cursor => cursor;
|
public CursorContainer Cursor => cursorContainer;
|
||||||
|
|
||||||
public bool ProvidingUserCursor => true;
|
public bool ProvidingUserCursor => true;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Add(cursor = new GameplayCursor { RelativeSizeAxes = Axes.Both });
|
Add(cursorContainer = new GameplayCursorContainer { RelativeSizeAxes = Axes.Both });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// 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 osu.Framework.Graphics.Cursor;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
@ -16,8 +15,14 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override CursorContainer CreateCursor() => null;
|
protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor { Size = Vector2.One };
|
||||||
|
|
||||||
protected override Playfield CreatePlayfield() => new OsuPlayfield { Size = Vector2.One };
|
private class OsuPlayfieldNoCursor : OsuPlayfield
|
||||||
|
{
|
||||||
|
public OsuPlayfieldNoCursor()
|
||||||
|
{
|
||||||
|
Cursor?.Expire();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.UI.Cursor
|
namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||||
{
|
{
|
||||||
public class GameplayCursor : CursorContainer, IKeyBindingHandler<OsuAction>
|
public class GameplayCursorContainer : CursorContainer, IKeyBindingHandler<OsuAction>
|
||||||
{
|
{
|
||||||
protected override Drawable CreateCursor() => new OsuCursor();
|
protected override Drawable CreateCursor() => new OsuCursor();
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
private readonly Container<Drawable> fadeContainer;
|
private readonly Container<Drawable> fadeContainer;
|
||||||
|
|
||||||
public GameplayCursor()
|
public GameplayCursorContainer()
|
||||||
{
|
{
|
||||||
InternalChild = fadeContainer = new Container
|
InternalChild = fadeContainer = new Container
|
||||||
{
|
{
|
||||||
@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
public OsuCursor()
|
public OsuCursor()
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
Size = new Vector2(42);
|
Size = new Vector2(28);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
@ -10,7 +10,9 @@ using osu.Game.Rulesets.Osu.Objects.Drawables;
|
|||||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Connections;
|
using osu.Game.Rulesets.Osu.Objects.Drawables.Connections;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.UI
|
namespace osu.Game.Rulesets.Osu.UI
|
||||||
{
|
{
|
||||||
@ -22,6 +24,12 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
|
|
||||||
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
|
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
|
||||||
|
|
||||||
|
private readonly PlayfieldAdjustmentContainer adjustmentContainer;
|
||||||
|
|
||||||
|
protected override Container CursorTargetContainer => adjustmentContainer;
|
||||||
|
|
||||||
|
protected override CursorContainer CreateCursor() => new GameplayCursorContainer();
|
||||||
|
|
||||||
public OsuPlayfield()
|
public OsuPlayfield()
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
@ -29,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
|
|
||||||
Size = new Vector2(0.75f);
|
Size = new Vector2(0.75f);
|
||||||
|
|
||||||
InternalChild = new PlayfieldAdjustmentContainer
|
InternalChild = adjustmentContainer = new PlayfieldAdjustmentContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// 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.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics.Cursor;
|
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Input.Handlers;
|
using osu.Game.Input.Handlers;
|
||||||
@ -13,7 +12,6 @@ using osu.Game.Rulesets.Osu.Objects;
|
|||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Replays;
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
using osu.Game.Rulesets.Osu.Scoring;
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
using osu.Game.Rulesets.Osu.UI.Cursor;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
@ -59,7 +57,5 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
return first.StartTime - first.TimePreempt;
|
return first.StartTime - first.TimePreempt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override CursorContainer CreateCursor() => new GameplayCursor();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,10 +24,13 @@ namespace osu.Game.Tests.Visual
|
|||||||
public TestCaseToolbar()
|
public TestCaseToolbar()
|
||||||
{
|
{
|
||||||
var toolbar = new Toolbar { State = Visibility.Visible };
|
var toolbar = new Toolbar { State = Visibility.Visible };
|
||||||
|
ToolbarNotificationButton notificationButton = null;
|
||||||
|
|
||||||
Add(toolbar);
|
AddStep("create toolbar", () =>
|
||||||
|
{
|
||||||
var notificationButton = toolbar.Children.OfType<FillFlowContainer>().Last().Children.OfType<ToolbarNotificationButton>().First();
|
Add(toolbar);
|
||||||
|
notificationButton = toolbar.Children.OfType<FillFlowContainer>().Last().Children.OfType<ToolbarNotificationButton>().First();
|
||||||
|
});
|
||||||
|
|
||||||
void setNotifications(int count) => AddStep($"set notification count to {count}", () => notificationButton.NotificationCount.Value = count);
|
void setNotifications(int count) => AddStep($"set notification count to {count}", () => notificationButton.NotificationCount.Value = count);
|
||||||
|
|
||||||
|
@ -70,13 +70,15 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
internal new void Schedule(Action action) => base.Schedule(action);
|
internal new void Schedule(Action action) => base.Schedule(action);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Register a component to receive API events.
|
||||||
|
/// Fires <see cref="IOnlineComponent.APIStateChanged"/> once immediately to ensure a correct state.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="component"></param>
|
||||||
public void Register(IOnlineComponent component)
|
public void Register(IOnlineComponent component)
|
||||||
{
|
{
|
||||||
Scheduler.Add(delegate
|
Scheduler.Add(delegate { components.Add(component); });
|
||||||
{
|
component.APIStateChanged(this, state);
|
||||||
components.Add(component);
|
|
||||||
component.APIStateChanged(this, state);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Unregister(IOnlineComponent component)
|
public void Unregister(IOnlineComponent component)
|
||||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Toolbar
|
|||||||
|
|
||||||
public Action OnHome;
|
public Action OnHome;
|
||||||
|
|
||||||
private readonly ToolbarUserArea userArea;
|
private ToolbarUserArea userArea;
|
||||||
|
|
||||||
protected override bool BlockPositionalInput => false;
|
protected override bool BlockPositionalInput => false;
|
||||||
|
|
||||||
@ -34,6 +34,13 @@ namespace osu.Game.Overlays.Toolbar
|
|||||||
private readonly Bindable<OverlayActivation> overlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All);
|
private readonly Bindable<OverlayActivation> overlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All);
|
||||||
|
|
||||||
public Toolbar()
|
public Toolbar()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Size = new Vector2(1, HEIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader(true)]
|
||||||
|
private void load(OsuGame osuGame)
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -76,13 +83,6 @@ namespace osu.Game.Overlays.Toolbar
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
|
||||||
Size = new Vector2(1, HEIGHT);
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
|
||||||
private void load(OsuGame osuGame)
|
|
||||||
{
|
|
||||||
StateChanged += visibility =>
|
StateChanged += visibility =>
|
||||||
{
|
{
|
||||||
if (overlayActivationMode.Value == OverlayActivation.Disabled)
|
if (overlayActivationMode.Value == OverlayActivation.Disabled)
|
||||||
|
@ -10,6 +10,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -63,6 +64,10 @@ namespace osu.Game.Rulesets.UI
|
|||||||
private void load(IBindable<WorkingBeatmap> beatmap)
|
private void load(IBindable<WorkingBeatmap> beatmap)
|
||||||
{
|
{
|
||||||
this.beatmap = beatmap.Value;
|
this.beatmap = beatmap.Value;
|
||||||
|
|
||||||
|
Cursor = CreateCursor();
|
||||||
|
if (Cursor != null)
|
||||||
|
CursorTargetContainer.Add(Cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -82,6 +87,23 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// <param name="h">The DrawableHitObject to remove.</param>
|
/// <param name="h">The DrawableHitObject to remove.</param>
|
||||||
public virtual bool Remove(DrawableHitObject h) => HitObjectContainer.Remove(h);
|
public virtual bool Remove(DrawableHitObject h) => HitObjectContainer.Remove(h);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The cursor currently being used by this <see cref="Playfield"/>. May be null if no cursor is provided.
|
||||||
|
/// </summary>
|
||||||
|
public CursorContainer Cursor { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provide an optional cursor which is to be used for gameplay.
|
||||||
|
/// If providing a cursor, <see cref="CursorTargetContainer"/> must also point to a valid target container.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The cursor, or null if a cursor is not rqeuired.</returns>
|
||||||
|
protected virtual CursorContainer CreateCursor() => null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The target container to add the cursor after it is created.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual Container CursorTargetContainer => null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers a <see cref="Playfield"/> as a nested <see cref="Playfield"/>.
|
/// Registers a <see cref="Playfield"/> as a nested <see cref="Playfield"/>.
|
||||||
/// This does not add the <see cref="Playfield"/> to the draw hierarchy.
|
/// This does not add the <see cref="Playfield"/> to the draw hierarchy.
|
||||||
|
@ -16,7 +16,9 @@ using System.Linq;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Graphics.Cursor;
|
||||||
using osu.Game.Input.Handlers;
|
using osu.Game.Input.Handlers;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
@ -32,7 +34,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// Should not be derived - derive <see cref="RulesetContainer{TObject}"/> instead.
|
/// Should not be derived - derive <see cref="RulesetContainer{TObject}"/> instead.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class RulesetContainer : Container
|
public abstract class RulesetContainer : Container, IProvideCursor
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The selected variant.
|
/// The selected variant.
|
||||||
@ -74,10 +76,11 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Container Overlays { get; protected set; }
|
public Container Overlays { get; protected set; }
|
||||||
|
|
||||||
/// <summary>
|
public CursorContainer Cursor => Playfield.Cursor;
|
||||||
/// The cursor provided by this <see cref="RulesetContainer"/>. May be null if no cursor is provided.
|
|
||||||
/// </summary>
|
public bool ProvidingUserCursor => Playfield.Cursor != null && !HasReplayLoaded.Value;
|
||||||
public readonly CursorContainer Cursor;
|
|
||||||
|
protected override bool OnHover(HoverEvent e) => true; // required for IProvideCursor
|
||||||
|
|
||||||
public readonly Ruleset Ruleset;
|
public readonly Ruleset Ruleset;
|
||||||
|
|
||||||
@ -101,8 +104,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
KeyBindingInputManager.UseParentInput = !paused.NewValue;
|
KeyBindingInputManager.UseParentInput = !paused.NewValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
Cursor = CreateCursor();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||||
@ -259,9 +260,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
Playfield
|
Playfield
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Cursor != null)
|
|
||||||
KeyBindingInputManager.Add(Cursor);
|
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
KeyBindingInputManager,
|
KeyBindingInputManager,
|
||||||
|
@ -107,6 +107,14 @@ namespace osu.Game.Screens.Multi.Lounge
|
|||||||
Filter.Search.HoldFocus = false;
|
Filter.Search.HoldFocus = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void OnResuming(IScreen last)
|
||||||
|
{
|
||||||
|
base.OnResuming(last);
|
||||||
|
|
||||||
|
if (currentRoom.Value?.RoomID.Value == null)
|
||||||
|
currentRoom.Value = new Room();
|
||||||
|
}
|
||||||
|
|
||||||
private void joinRequested(Room room)
|
private void joinRequested(Room room)
|
||||||
{
|
{
|
||||||
processingOverlay.Show();
|
processingOverlay.Show();
|
||||||
|
@ -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;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
@ -13,6 +14,7 @@ using osu.Game.Beatmaps.Drawables;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Overlays.SearchableList;
|
using osu.Game.Overlays.SearchableList;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Screens.Multi.Components;
|
using osu.Game.Screens.Multi.Components;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -108,7 +110,7 @@ namespace osu.Game.Screens.Multi.Match.Components
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
CurrentItem.BindValueChanged(item => modDisplay.Current.Value = item.NewValue?.RequiredMods, true);
|
CurrentItem.BindValueChanged(item => modDisplay.Current.Value = item.NewValue?.RequiredMods ?? Enumerable.Empty<Mod>(), true);
|
||||||
|
|
||||||
beatmapButton.Action = () => RequestBeatmapSelection?.Invoke();
|
beatmapButton.Action = () => RequestBeatmapSelection?.Invoke();
|
||||||
}
|
}
|
||||||
|
151
osu.Game/Screens/Play/GameplayClockContainer.cs
Normal file
151
osu.Game/Screens/Play/GameplayClockContainer.cs
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using osu.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Play
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Encapsulates gameplay timing logic and provides a <see cref="GameplayClock"/> for children.
|
||||||
|
/// </summary>
|
||||||
|
public class GameplayClockContainer : Container
|
||||||
|
{
|
||||||
|
private readonly WorkingBeatmap beatmap;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The original source (usually a <see cref="WorkingBeatmap"/>'s track).
|
||||||
|
/// </summary>
|
||||||
|
private readonly IAdjustableClock sourceClock;
|
||||||
|
|
||||||
|
public readonly BindableBool IsPaused = new BindableBool();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The decoupled clock used for gameplay. Should be used for seeks and clock control.
|
||||||
|
/// </summary>
|
||||||
|
private readonly DecoupleableInterpolatingFramedClock adjustableClock;
|
||||||
|
|
||||||
|
public readonly Bindable<double> UserPlaybackRate = new BindableDouble(1)
|
||||||
|
{
|
||||||
|
Default = 1,
|
||||||
|
MinValue = 0.5,
|
||||||
|
MaxValue = 2,
|
||||||
|
Precision = 0.1,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The final clock which is exposed to underlying components.
|
||||||
|
/// </summary>
|
||||||
|
[Cached]
|
||||||
|
private readonly GameplayClock gameplayClock;
|
||||||
|
|
||||||
|
private Bindable<double> userAudioOffset;
|
||||||
|
|
||||||
|
private readonly FramedOffsetClock offsetClock;
|
||||||
|
|
||||||
|
public GameplayClockContainer(WorkingBeatmap beatmap, bool allowLeadIn, double gameplayStartTime)
|
||||||
|
{
|
||||||
|
this.beatmap = beatmap;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
sourceClock = (IAdjustableClock)beatmap.Track ?? new StopwatchClock();
|
||||||
|
|
||||||
|
adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
|
||||||
|
|
||||||
|
adjustableClock.Seek(allowLeadIn
|
||||||
|
? Math.Min(0, gameplayStartTime - beatmap.BeatmapInfo.AudioLeadIn)
|
||||||
|
: gameplayStartTime);
|
||||||
|
|
||||||
|
adjustableClock.ProcessFrame();
|
||||||
|
|
||||||
|
// Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited.
|
||||||
|
// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck.
|
||||||
|
var platformOffsetClock = new FramedOffsetClock(adjustableClock) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 22 : 0 };
|
||||||
|
|
||||||
|
// the final usable gameplay clock with user-set offsets applied.
|
||||||
|
offsetClock = new FramedOffsetClock(platformOffsetClock);
|
||||||
|
|
||||||
|
// the clock to be exposed via DI to children.
|
||||||
|
gameplayClock = new GameplayClock(offsetClock);
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
userAudioOffset = config.GetBindable<double>(OsuSetting.AudioOffset);
|
||||||
|
userAudioOffset.BindValueChanged(offset => offsetClock.Offset = offset.NewValue, true);
|
||||||
|
|
||||||
|
UserPlaybackRate.ValueChanged += _ => updateRate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Restart()
|
||||||
|
{
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
sourceClock.Reset();
|
||||||
|
|
||||||
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
adjustableClock.ChangeSource(sourceClock);
|
||||||
|
updateRate();
|
||||||
|
|
||||||
|
this.Delay(750).Schedule(() =>
|
||||||
|
{
|
||||||
|
if (!IsPaused.Value)
|
||||||
|
{
|
||||||
|
adjustableClock.Start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
// 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
|
||||||
|
adjustableClock.Seek(adjustableClock.CurrentTime);
|
||||||
|
adjustableClock.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Seek(double time) => adjustableClock.Seek(time);
|
||||||
|
|
||||||
|
public void Stop() => adjustableClock.Stop();
|
||||||
|
|
||||||
|
public void ResetLocalAdjustments()
|
||||||
|
{
|
||||||
|
// In the case of replays, we may have changed the playback rate.
|
||||||
|
UserPlaybackRate.Value = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
if (!IsPaused.Value)
|
||||||
|
offsetClock.ProcessFrame();
|
||||||
|
|
||||||
|
base.Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateRate()
|
||||||
|
{
|
||||||
|
if (sourceClock == null) return;
|
||||||
|
|
||||||
|
sourceClock.Rate = 1;
|
||||||
|
foreach (var mod in beatmap.Mods.Value.OfType<IApplicableToClock>())
|
||||||
|
mod.ApplyToClock(sourceClock);
|
||||||
|
|
||||||
|
sourceClock.Rate *= UserPlaybackRate.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -19,7 +19,9 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
public readonly PlaybackSettings PlaybackSettings;
|
public readonly PlaybackSettings PlaybackSettings;
|
||||||
|
|
||||||
public readonly VisualSettings VisualSettings;
|
public readonly VisualSettings VisualSettings;
|
||||||
|
|
||||||
//public readonly CollectionSettings CollectionSettings;
|
//public readonly CollectionSettings CollectionSettings;
|
||||||
|
|
||||||
//public readonly DiscussionSettings DiscussionSettings;
|
//public readonly DiscussionSettings DiscussionSettings;
|
||||||
|
|
||||||
public PlayerSettingsOverlay()
|
public PlayerSettingsOverlay()
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
// 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;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
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.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
@ -40,7 +40,9 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
private static bool hasShownNotificationOnce;
|
private static bool hasShownNotificationOnce;
|
||||||
|
|
||||||
public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, WorkingBeatmap working, IAdjustableClock adjustableClock)
|
public Action<double> RequestSeek;
|
||||||
|
|
||||||
|
public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, WorkingBeatmap working)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
@ -92,11 +94,9 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
Progress.Objects = rulesetContainer.Objects;
|
Progress.Objects = rulesetContainer.Objects;
|
||||||
Progress.AllowSeeking = rulesetContainer.HasReplayLoaded.Value;
|
Progress.AllowSeeking = rulesetContainer.HasReplayLoaded.Value;
|
||||||
Progress.RequestSeek = pos => adjustableClock.Seek(pos);
|
Progress.RequestSeek = time => RequestSeek(time);
|
||||||
|
|
||||||
ModDisplay.Current.BindTo(working.Mods);
|
ModDisplay.Current.BindTo(working.Mods);
|
||||||
|
|
||||||
PlayerSettingsOverlay.PlaybackSettings.AdjustableClock = adjustableClock;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
|
@ -7,16 +7,13 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
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.Timing;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osuTK.Graphics;
|
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 and processing the clock.
|
/// A container which handles pausing children, displaying an overlay blocking its children during paused state.
|
||||||
/// Exposes a <see cref="GameplayClock"/> to children via DI.
|
|
||||||
/// This alleviates a lot of the intricate pause logic from being in <see cref="Player"/>
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PausableGameplayContainer : Container
|
public class PausableGameplayContainer : Container
|
||||||
{
|
{
|
||||||
@ -44,46 +41,33 @@ namespace osu.Game.Screens.Play
|
|||||||
public Action OnRetry;
|
public Action OnRetry;
|
||||||
public Action OnQuit;
|
public Action OnQuit;
|
||||||
|
|
||||||
private readonly FramedClock offsetClock;
|
public Action Stop;
|
||||||
private readonly DecoupleableInterpolatingFramedClock adjustableClock;
|
public Action Start;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The final clock which is exposed to underlying components.
|
|
||||||
/// </summary>
|
|
||||||
[Cached]
|
|
||||||
private readonly GameplayClock gameplayClock;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="PausableGameplayContainer"/>.
|
/// Creates a new <see cref="PausableGameplayContainer"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="offsetClock">The gameplay clock. This is the clock that will process frames. Includes user/system offsets.</param>
|
public PausableGameplayContainer()
|
||||||
/// <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.offsetClock = offsetClock;
|
|
||||||
this.adjustableClock = adjustableClock;
|
|
||||||
|
|
||||||
gameplayClock = new GameplayClock(offsetClock);
|
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
AddInternal(content = new Container
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
Clock = this.offsetClock,
|
content = new Container
|
||||||
ProcessCustomClock = false,
|
|
||||||
RelativeSizeAxes = Axes.Both
|
|
||||||
});
|
|
||||||
|
|
||||||
AddInternal(pauseOverlay = new PauseOverlay
|
|
||||||
{
|
|
||||||
OnResume = () =>
|
|
||||||
{
|
{
|
||||||
IsResuming = true;
|
RelativeSizeAxes = Axes.Both
|
||||||
this.Delay(400).Schedule(Resume);
|
|
||||||
},
|
},
|
||||||
OnRetry = () => OnRetry(),
|
pauseOverlay = new PauseOverlay
|
||||||
OnQuit = () => OnQuit(),
|
{
|
||||||
});
|
OnResume = () =>
|
||||||
|
{
|
||||||
|
IsResuming = true;
|
||||||
|
this.Delay(400).Schedule(Resume);
|
||||||
|
},
|
||||||
|
OnRetry = () => OnRetry(),
|
||||||
|
OnQuit = () => OnQuit(),
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Pause(bool force = false) => Schedule(() => // Scheduled to ensure a stable position in execution order, no matter how it was called.
|
public void Pause(bool force = false) => Schedule(() => // Scheduled to ensure a stable position in execution order, no matter how it was called.
|
||||||
@ -93,7 +77,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)
|
||||||
adjustableClock.Stop();
|
Stop?.Invoke();
|
||||||
IsPaused.Value = true;
|
IsPaused.Value = true;
|
||||||
|
|
||||||
pauseOverlay.Show();
|
pauseOverlay.Show();
|
||||||
@ -105,14 +89,12 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
if (!IsPaused.Value) return;
|
if (!IsPaused.Value) return;
|
||||||
|
|
||||||
IsPaused.Value = false;
|
|
||||||
IsResuming = false;
|
IsResuming = false;
|
||||||
lastPauseActionTime = Time.Current;
|
lastPauseActionTime = Time.Current;
|
||||||
|
|
||||||
// Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time
|
IsPaused.Value = false;
|
||||||
// This accounts for the audio clock source potentially taking time to enter a completely stopped state
|
|
||||||
adjustableClock.Seek(adjustableClock.CurrentTime);
|
Start?.Invoke();
|
||||||
adjustableClock.Start();
|
|
||||||
|
|
||||||
pauseOverlay.Hide();
|
pauseOverlay.Hide();
|
||||||
}
|
}
|
||||||
@ -131,9 +113,6 @@ namespace osu.Game.Screens.Play
|
|||||||
if (!game.IsActive.Value && CanPause)
|
if (!game.IsActive.Value && CanPause)
|
||||||
Pause();
|
Pause();
|
||||||
|
|
||||||
if (!IsPaused.Value)
|
|
||||||
offsetClock.ProcessFrame();
|
|
||||||
|
|
||||||
base.Update();
|
base.Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,24 +3,19 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using osu.Framework;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Bindables;
|
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.Graphics.Cursor;
|
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Framework.Timing;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Cursor;
|
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
@ -34,7 +29,7 @@ using osu.Game.Storyboards.Drawables;
|
|||||||
|
|
||||||
namespace osu.Game.Screens.Play
|
namespace osu.Game.Screens.Play
|
||||||
{
|
{
|
||||||
public class Player : ScreenWithBeatmapBackground, IProvideCursor
|
public class Player : ScreenWithBeatmapBackground
|
||||||
{
|
{
|
||||||
protected override bool AllowBackButton => false; // handled by HoldForMenuButton
|
protected override bool AllowBackButton => false; // handled by HoldForMenuButton
|
||||||
|
|
||||||
@ -53,22 +48,11 @@ namespace osu.Game.Screens.Play
|
|||||||
public bool AllowResults { get; set; } = true;
|
public bool AllowResults { get; set; } = true;
|
||||||
|
|
||||||
private Bindable<bool> mouseWheelDisabled;
|
private Bindable<bool> mouseWheelDisabled;
|
||||||
private Bindable<double> userAudioOffset;
|
|
||||||
|
|
||||||
private readonly Bindable<bool> storyboardReplacesBackground = new Bindable<bool>();
|
private readonly Bindable<bool> storyboardReplacesBackground = new Bindable<bool>();
|
||||||
|
|
||||||
public int RestartCount;
|
public int RestartCount;
|
||||||
|
|
||||||
public CursorContainer Cursor => RulesetContainer.Cursor;
|
|
||||||
public bool ProvidingUserCursor => RulesetContainer?.Cursor != null && !RulesetContainer.HasReplayLoaded.Value;
|
|
||||||
|
|
||||||
private IAdjustableClock sourceClock;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The decoupled clock used for gameplay. Should be used for seeks and clock control.
|
|
||||||
/// </summary>
|
|
||||||
private DecoupleableInterpolatingFramedClock adjustableClock;
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private ScoreManager scoreManager { get; set; }
|
private ScoreManager scoreManager { get; set; }
|
||||||
|
|
||||||
@ -98,25 +82,113 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
public bool LoadedBeatmapSuccessfully => RulesetContainer?.Objects.Any() == true;
|
public bool LoadedBeatmapSuccessfully => RulesetContainer?.Objects.Any() == true;
|
||||||
|
|
||||||
|
private GameplayClockContainer gameplayClockContainer;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio, APIAccess api, OsuConfigManager config)
|
private void load(AudioManager audio, APIAccess api, OsuConfigManager config)
|
||||||
{
|
{
|
||||||
this.api = api;
|
this.api = api;
|
||||||
|
|
||||||
WorkingBeatmap working = Beatmap.Value;
|
WorkingBeatmap working = loadBeatmap();
|
||||||
if (working is DummyWorkingBeatmap)
|
|
||||||
|
if (working == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
sampleRestart = audio.Sample.Get(@"Gameplay/restart");
|
sampleRestart = audio.Sample.Get(@"Gameplay/restart");
|
||||||
|
|
||||||
mouseWheelDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableWheel);
|
mouseWheelDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableWheel);
|
||||||
userAudioOffset = config.GetBindable<double>(OsuSetting.AudioOffset);
|
|
||||||
|
|
||||||
IBeatmap beatmap;
|
ScoreProcessor = RulesetContainer.CreateScoreProcessor();
|
||||||
|
if (!ScoreProcessor.Mode.Disabled)
|
||||||
|
config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode);
|
||||||
|
|
||||||
|
InternalChild = gameplayClockContainer = new GameplayClockContainer(working, AllowLeadIn, RulesetContainer.GameplayStartTime);
|
||||||
|
|
||||||
|
gameplayClockContainer.Children = new Drawable[]
|
||||||
|
{
|
||||||
|
PausableGameplayContainer = new PausableGameplayContainer
|
||||||
|
{
|
||||||
|
Retries = RestartCount,
|
||||||
|
OnRetry = restart,
|
||||||
|
OnQuit = performUserRequestedExit,
|
||||||
|
Start = gameplayClockContainer.Start,
|
||||||
|
Stop = gameplayClockContainer.Stop,
|
||||||
|
IsPaused = { BindTarget = gameplayClockContainer.IsPaused },
|
||||||
|
CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded.Value,
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
StoryboardContainer = CreateStoryboardContainer(),
|
||||||
|
new ScalingContainer(ScalingMode.Gameplay)
|
||||||
|
{
|
||||||
|
Child = new LocalSkinOverrideContainer(working.Skin)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = RulesetContainer
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Breaks = working.Beatmap.Breaks
|
||||||
|
},
|
||||||
|
// display the cursor above some HUD elements.
|
||||||
|
RulesetContainer.Cursor?.CreateProxy() ?? new Container(),
|
||||||
|
HUDOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working)
|
||||||
|
{
|
||||||
|
HoldToQuit = { Action = performUserRequestedExit },
|
||||||
|
PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = gameplayClockContainer.UserPlaybackRate } } },
|
||||||
|
KeyCounter = { Visible = { BindTarget = RulesetContainer.HasReplayLoaded } },
|
||||||
|
RequestSeek = gameplayClockContainer.Seek,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre
|
||||||
|
},
|
||||||
|
new SkipOverlay(RulesetContainer.GameplayStartTime)
|
||||||
|
{
|
||||||
|
RequestSeek = gameplayClockContainer.Seek
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
failOverlay = new FailOverlay
|
||||||
|
{
|
||||||
|
OnRetry = restart,
|
||||||
|
OnQuit = performUserRequestedExit,
|
||||||
|
},
|
||||||
|
new HotkeyRetryOverlay
|
||||||
|
{
|
||||||
|
Action = () =>
|
||||||
|
{
|
||||||
|
if (!this.IsCurrentScreen()) return;
|
||||||
|
|
||||||
|
fadeOut(true);
|
||||||
|
restart();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// bind clock into components that require it
|
||||||
|
RulesetContainer.IsPaused.BindTo(gameplayClockContainer.IsPaused);
|
||||||
|
|
||||||
|
if (ShowStoryboard.Value)
|
||||||
|
initializeStoryboard(false);
|
||||||
|
|
||||||
|
// Bind ScoreProcessor to ourselves
|
||||||
|
ScoreProcessor.AllJudged += onCompletion;
|
||||||
|
ScoreProcessor.Failed += onFail;
|
||||||
|
|
||||||
|
foreach (var mod in Beatmap.Value.Mods.Value.OfType<IApplicableToScoreProcessor>())
|
||||||
|
mod.ApplyToScoreProcessor(ScoreProcessor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private WorkingBeatmap loadBeatmap()
|
||||||
|
{
|
||||||
|
WorkingBeatmap working = Beatmap.Value;
|
||||||
|
if (working is DummyWorkingBeatmap)
|
||||||
|
return null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
beatmap = working.Beatmap;
|
var beatmap = working.Beatmap;
|
||||||
|
|
||||||
if (beatmap == null)
|
if (beatmap == null)
|
||||||
throw new InvalidOperationException("Beatmap was not loaded");
|
throw new InvalidOperationException("Beatmap was not loaded");
|
||||||
@ -140,119 +212,17 @@ namespace osu.Game.Screens.Play
|
|||||||
if (!RulesetContainer.Objects.Any())
|
if (!RulesetContainer.Objects.Any())
|
||||||
{
|
{
|
||||||
Logger.Log("Beatmap contains no hit objects!", level: LogLevel.Error);
|
Logger.Log("Beatmap contains no hit objects!", level: LogLevel.Error);
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Error(e, "Could not load beatmap sucessfully!");
|
Logger.Error(e, "Could not load beatmap sucessfully!");
|
||||||
//couldn't load, hard abort!
|
//couldn't load, hard abort!
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceClock = (IAdjustableClock)working.Track ?? new StopwatchClock();
|
return working;
|
||||||
adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
|
|
||||||
|
|
||||||
adjustableClock.Seek(AllowLeadIn
|
|
||||||
? Math.Min(0, RulesetContainer.GameplayStartTime - beatmap.BeatmapInfo.AudioLeadIn)
|
|
||||||
: RulesetContainer.GameplayStartTime);
|
|
||||||
|
|
||||||
adjustableClock.ProcessFrame();
|
|
||||||
|
|
||||||
// Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited.
|
|
||||||
// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck.
|
|
||||||
var platformOffsetClock = new FramedOffsetClock(adjustableClock) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 22 : 0 };
|
|
||||||
|
|
||||||
// the final usable gameplay clock with user-set offsets applied.
|
|
||||||
var offsetClock = new FramedOffsetClock(platformOffsetClock);
|
|
||||||
|
|
||||||
userAudioOffset.ValueChanged += offset => offsetClock.Offset = offset.NewValue;
|
|
||||||
userAudioOffset.TriggerChange();
|
|
||||||
|
|
||||||
ScoreProcessor = RulesetContainer.CreateScoreProcessor();
|
|
||||||
if (!ScoreProcessor.Mode.Disabled)
|
|
||||||
config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode);
|
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
|
||||||
{
|
|
||||||
PausableGameplayContainer = new PausableGameplayContainer(offsetClock, adjustableClock)
|
|
||||||
{
|
|
||||||
Retries = RestartCount,
|
|
||||||
OnRetry = restart,
|
|
||||||
OnQuit = performUserRequestedExit,
|
|
||||||
CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded.Value,
|
|
||||||
Children = new Container[]
|
|
||||||
{
|
|
||||||
StoryboardContainer = CreateStoryboardContainer(),
|
|
||||||
new ScalingContainer(ScalingMode.Gameplay)
|
|
||||||
{
|
|
||||||
Child = new LocalSkinOverrideContainer(working.Skin)
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Child = RulesetContainer
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Breaks = beatmap.Breaks
|
|
||||||
},
|
|
||||||
new ScalingContainer(ScalingMode.Gameplay)
|
|
||||||
{
|
|
||||||
Child = RulesetContainer.Cursor?.CreateProxy() ?? new Container(),
|
|
||||||
},
|
|
||||||
HUDOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working, adjustableClock)
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre
|
|
||||||
},
|
|
||||||
new SkipOverlay(RulesetContainer.GameplayStartTime)
|
|
||||||
{
|
|
||||||
RequestSeek = time => adjustableClock.Seek(time)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
failOverlay = new FailOverlay
|
|
||||||
{
|
|
||||||
OnRetry = restart,
|
|
||||||
OnQuit = performUserRequestedExit,
|
|
||||||
},
|
|
||||||
new HotkeyRetryOverlay
|
|
||||||
{
|
|
||||||
Action = () =>
|
|
||||||
{
|
|
||||||
if (!this.IsCurrentScreen()) return;
|
|
||||||
|
|
||||||
fadeOut(true);
|
|
||||||
restart();
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
HUDOverlay.HoldToQuit.Action = performUserRequestedExit;
|
|
||||||
HUDOverlay.KeyCounter.Visible.BindTo(RulesetContainer.HasReplayLoaded);
|
|
||||||
|
|
||||||
RulesetContainer.IsPaused.BindTo(PausableGameplayContainer.IsPaused);
|
|
||||||
|
|
||||||
if (ShowStoryboard.Value)
|
|
||||||
initializeStoryboard(false);
|
|
||||||
|
|
||||||
// Bind ScoreProcessor to ourselves
|
|
||||||
ScoreProcessor.AllJudged += onCompletion;
|
|
||||||
ScoreProcessor.Failed += onFail;
|
|
||||||
|
|
||||||
foreach (var mod in Beatmap.Value.Mods.Value.OfType<IApplicableToScoreProcessor>())
|
|
||||||
mod.ApplyToScoreProcessor(ScoreProcessor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyRateFromMods()
|
|
||||||
{
|
|
||||||
if (sourceClock == null) return;
|
|
||||||
|
|
||||||
sourceClock.Rate = 1;
|
|
||||||
foreach (var mod in Beatmap.Value.Mods.Value.OfType<IApplicableToClock>())
|
|
||||||
mod.ApplyToClock(sourceClock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void performUserRequestedExit()
|
private void performUserRequestedExit()
|
||||||
@ -321,7 +291,7 @@ namespace osu.Game.Screens.Play
|
|||||||
if (Beatmap.Value.Mods.Value.OfType<IApplicableFailOverride>().Any(m => !m.AllowFail))
|
if (Beatmap.Value.Mods.Value.OfType<IApplicableFailOverride>().Any(m => !m.AllowFail))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
adjustableClock.Stop();
|
gameplayClockContainer.Stop();
|
||||||
|
|
||||||
HasFailed = true;
|
HasFailed = true;
|
||||||
failOverlay.Retries = RestartCount;
|
failOverlay.Retries = RestartCount;
|
||||||
@ -355,24 +325,7 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
storyboardReplacesBackground.Value = Beatmap.Value.Storyboard.ReplacesBackground && Beatmap.Value.Storyboard.HasDrawable;
|
storyboardReplacesBackground.Value = Beatmap.Value.Storyboard.ReplacesBackground && Beatmap.Value.Storyboard.HasDrawable;
|
||||||
|
|
||||||
Task.Run(() =>
|
gameplayClockContainer.Restart();
|
||||||
{
|
|
||||||
sourceClock.Reset();
|
|
||||||
|
|
||||||
Schedule(() =>
|
|
||||||
{
|
|
||||||
adjustableClock.ChangeSource(sourceClock);
|
|
||||||
applyRateFromMods();
|
|
||||||
|
|
||||||
this.Delay(750).Schedule(() =>
|
|
||||||
{
|
|
||||||
if (!PausableGameplayContainer.IsPaused.Value)
|
|
||||||
{
|
|
||||||
adjustableClock.Start();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
PausableGameplayContainer.Alpha = 0;
|
PausableGameplayContainer.Alpha = 0;
|
||||||
PausableGameplayContainer.FadeIn(750, Easing.OutQuint);
|
PausableGameplayContainer.FadeIn(750, Easing.OutQuint);
|
||||||
@ -395,8 +348,8 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
if ((!AllowPause || HasFailed || !ValidForResume || PausableGameplayContainer?.IsPaused.Value != false || RulesetContainer?.HasReplayLoaded.Value != false) && (!PausableGameplayContainer?.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.
|
gameplayClockContainer.ResetLocalAdjustments();
|
||||||
applyRateFromMods();
|
|
||||||
fadeOut();
|
fadeOut();
|
||||||
return base.OnExiting(next);
|
return base.OnExiting(next);
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using osu.Framework.Bindables;
|
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.Timing;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
|
|
||||||
@ -16,7 +15,13 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
|
|
||||||
protected override string Title => @"playback";
|
protected override string Title => @"playback";
|
||||||
|
|
||||||
public IAdjustableClock AdjustableClock { set; get; }
|
public readonly Bindable<double> UserPlaybackRate = new BindableDouble(1)
|
||||||
|
{
|
||||||
|
Default = 1,
|
||||||
|
MinValue = 0.5,
|
||||||
|
MaxValue = 2,
|
||||||
|
Precision = 0.1,
|
||||||
|
};
|
||||||
|
|
||||||
private readonly PlayerSliderBar<double> rateSlider;
|
private readonly PlayerSliderBar<double> rateSlider;
|
||||||
|
|
||||||
@ -47,31 +52,13 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
rateSlider = new PlayerSliderBar<double>
|
rateSlider = new PlayerSliderBar<double> { Bindable = UserPlaybackRate }
|
||||||
{
|
|
||||||
Bindable = new BindableDouble(1)
|
|
||||||
{
|
|
||||||
Default = 1,
|
|
||||||
MinValue = 0.5,
|
|
||||||
MaxValue = 2,
|
|
||||||
Precision = 0.1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
if (AdjustableClock == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var clockRate = AdjustableClock.Rate;
|
|
||||||
|
|
||||||
// can't trigger this line instantly as the underlying clock may not be ready to accept adjustments yet.
|
|
||||||
rateSlider.Bindable.ValueChanged += multiplier => AdjustableClock.Rate = clockRate * multiplier.NewValue;
|
|
||||||
|
|
||||||
rateSlider.Bindable.BindValueChanged(multiplier => multiplierText.Text = $"{multiplier.NewValue:0.0}x", true);
|
rateSlider.Bindable.BindValueChanged(multiplier => multiplierText.Text = $"{multiplier.NewValue:0.0}x", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -64,7 +65,7 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
Ruleset.Value = CurrentItem.Value.Ruleset;
|
Ruleset.Value = CurrentItem.Value.Ruleset;
|
||||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(CurrentItem.Value.Beatmap);
|
Beatmap.Value = beatmaps.GetWorkingBeatmap(CurrentItem.Value.Beatmap);
|
||||||
Beatmap.Value.Mods.Value = selectedMods.Value = CurrentItem.Value.RequiredMods;
|
Beatmap.Value.Mods.Value = selectedMods.Value = CurrentItem.Value.RequiredMods ?? Enumerable.Empty<Mod>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Beatmap.Disabled = true;
|
Beatmap.Disabled = true;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user