Merge branch 'master' into move-difficulty-graph-toggle

This commit is contained in:
Dean Herbert
2022-04-27 17:22:25 +09:00
414 changed files with 8033 additions and 3022 deletions

View File

@ -48,7 +48,7 @@ namespace osu.Game.Screens
Scale = new Vector2(1 + x_movement_amount / DrawSize.X * 2);
}
public override void OnEntering(IScreen last)
public override void OnEntering(ScreenTransitionEvent e)
{
if (animateOnEnter)
{
@ -59,16 +59,16 @@ namespace osu.Game.Screens
this.MoveToX(0, TRANSITION_LENGTH, Easing.InOutQuart);
}
base.OnEntering(last);
base.OnEntering(e);
}
public override void OnSuspending(IScreen next)
public override void OnSuspending(ScreenTransitionEvent e)
{
this.MoveToX(-x_movement_amount, TRANSITION_LENGTH, Easing.InOutQuart);
base.OnSuspending(next);
base.OnSuspending(e);
}
public override bool OnExiting(IScreen next)
public override bool OnExiting(ScreenExitEvent e)
{
if (IsLoaded)
{
@ -76,14 +76,14 @@ namespace osu.Game.Screens
this.MoveToX(x_movement_amount, TRANSITION_LENGTH, Easing.OutExpo);
}
return base.OnExiting(next);
return base.OnExiting(e);
}
public override void OnResuming(IScreen last)
public override void OnResuming(ScreenTransitionEvent e)
{
if (IsLoaded)
this.MoveToX(0, TRANSITION_LENGTH, Easing.OutExpo);
base.OnResuming(last);
base.OnResuming(e);
}
}
}

View File

@ -19,7 +19,7 @@ namespace osu.Game.Screens.Backgrounds
};
}
public override void OnEntering(IScreen last)
public override void OnEntering(ScreenTransitionEvent e)
{
Show();
}

View File

@ -17,6 +17,7 @@ using osu.Framework.Input.Events;
using osu.Framework.Utils;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets.Edit;
using osuTK;
using osuTK.Input;
@ -358,7 +359,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (SelectedBlueprints.Count == 1)
items.AddRange(SelectedBlueprints[0].ContextMenuItems);
items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, DeleteSelected));
items.Add(new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, DeleteSelected));
return items.ToArray();
}

View File

@ -29,6 +29,7 @@ using osu.Game.Input.Bindings;
using osu.Game.Online.API;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Edit;
using osu.Game.Screens.Edit.Components;
@ -84,10 +85,10 @@ namespace osu.Game.Screens.Edit
private Storage storage { get; set; }
[Resolved(canBeNull: true)]
private DialogOverlay dialogOverlay { get; set; }
private IDialogOverlay dialogOverlay { get; set; }
[Resolved(canBeNull: true)]
private NotificationOverlay notifications { get; set; }
private INotificationOverlay notifications { get; set; }
public readonly Bindable<EditorScreenMode> Mode = new Bindable<EditorScreenMode>();
@ -252,7 +253,7 @@ namespace osu.Game.Screens.Edit
{
Items = createFileMenuItems()
},
new MenuItem("Edit")
new MenuItem(CommonStrings.ButtonsEdit)
{
Items = new[]
{
@ -559,16 +560,16 @@ namespace osu.Game.Screens.Edit
{
}
public override void OnEntering(IScreen last)
public override void OnEntering(ScreenTransitionEvent e)
{
base.OnEntering(last);
base.OnEntering(e);
dimBackground();
resetTrack(true);
}
public override void OnResuming(IScreen last)
public override void OnResuming(ScreenTransitionEvent e)
{
base.OnResuming(last);
base.OnResuming(e);
dimBackground();
}
@ -584,7 +585,7 @@ namespace osu.Game.Screens.Edit
});
}
public override bool OnExiting(IScreen next)
public override bool OnExiting(ScreenExitEvent e)
{
if (!ExitConfirmed)
{
@ -612,12 +613,12 @@ namespace osu.Game.Screens.Edit
refetchBeatmap();
return base.OnExiting(next);
return base.OnExiting(e);
}
public override void OnSuspending(IScreen next)
public override void OnSuspending(ScreenTransitionEvent e)
{
base.OnSuspending(next);
base.OnSuspending(e);
clock.Stop();
refetchBeatmap();
}

View File

@ -21,21 +21,24 @@ namespace osu.Game.Screens.Edit
{
public event Action BeatmapSkinChanged;
/// <summary>
/// The underlying beatmap skin.
/// </summary>
protected internal readonly Skin Skin;
/// <summary>
/// The combo colours of this skin.
/// If empty, the default combo colours will be used.
/// </summary>
public readonly BindableList<Colour4> ComboColours;
private readonly Skin skin;
public BindableList<Colour4> ComboColours { get; }
public EditorBeatmapSkin(Skin skin)
{
this.skin = skin;
Skin = skin;
ComboColours = new BindableList<Colour4>();
if (skin.Configuration.ComboColours != null)
ComboColours.AddRange(skin.Configuration.ComboColours.Select(c => (Colour4)c));
if (Skin.Configuration.ComboColours != null)
ComboColours.AddRange(Skin.Configuration.ComboColours.Select(c => (Colour4)c));
ComboColours.BindCollectionChanged((_, __) => updateColours());
}
@ -43,16 +46,16 @@ namespace osu.Game.Screens.Edit
private void updateColours()
{
skin.Configuration.CustomComboColours = ComboColours.Select(c => (Color4)c).ToList();
Skin.Configuration.CustomComboColours = ComboColours.Select(c => (Color4)c).ToList();
invokeSkinChanged();
}
#region Delegated ISkin implementation
public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component);
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => skin.GetTexture(componentName, wrapModeS, wrapModeT);
public ISample GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo);
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => skin.GetConfig<TLookup, TValue>(lookup);
public Drawable GetDrawableComponent(ISkinComponent component) => Skin.GetDrawableComponent(component);
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => Skin.GetTexture(componentName, wrapModeS, wrapModeT);
public ISample GetSample(ISampleInfo sampleInfo) => Skin.GetSample(sampleInfo);
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => Skin.GetConfig<TLookup, TValue>(lookup);
#endregion
}

View File

@ -16,7 +16,7 @@ namespace osu.Game.Screens.Edit
private readonly EditorBeatmapSkin? beatmapSkin;
public EditorSkinProvidingContainer(EditorBeatmap editorBeatmap)
: base(editorBeatmap.PlayableBeatmap.BeatmapInfo.Ruleset.CreateInstance(), editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin)
: base(editorBeatmap.PlayableBeatmap.BeatmapInfo.Ruleset.CreateInstance(), editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin?.Skin)
{
beatmapSkin = editorBeatmap.BeatmapSkin;
}

View File

@ -25,7 +25,7 @@ namespace osu.Game.Screens.Edit.GameplayTest
}
protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart)
=> new MasterGameplayClockContainer(beatmap, editorState.Time, true);
=> new MasterGameplayClockContainer(beatmap, gameplayStart) { StartTime = editorState.Time };
protected override void LoadComplete()
{
@ -44,9 +44,9 @@ namespace osu.Game.Screens.Edit.GameplayTest
protected override bool CheckModsAllowFailure() => false; // never fail.
public override void OnEntering(IScreen last)
public override void OnEntering(ScreenTransitionEvent e)
{
base.OnEntering(last);
base.OnEntering(e);
// finish alpha transforms on entering to avoid gameplay starting in a half-hidden state.
// the finish calls are purposefully not propagated to children to avoid messing up their state.
@ -54,13 +54,13 @@ namespace osu.Game.Screens.Edit.GameplayTest
GameplayClockContainer.FinishTransforms(false, nameof(Alpha));
}
public override bool OnExiting(IScreen next)
public override bool OnExiting(ScreenExitEvent e)
{
musicController.Stop();
editorState.Time = GameplayClockContainer.CurrentTime;
editor.RestoreState(editorState);
return base.OnExiting(next);
return base.OnExiting(e);
}
}
}

View File

@ -19,9 +19,9 @@ namespace osu.Game.Screens.Edit.GameplayTest
{
}
public override void OnEntering(IScreen last)
public override void OnEntering(ScreenTransitionEvent e)
{
base.OnEntering(last);
base.OnEntering(e);
MetadataInfo.FinishTransforms(true);
}

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Screens.Edit.Setup
{
@ -27,7 +28,7 @@ namespace osu.Game.Screens.Edit.Setup
{
circleSizeSlider = new LabelledSliderBar<float>
{
Label = "Object Size",
Label = BeatmapsetsStrings.ShowStatsCs,
FixedLabelWidth = LABEL_WIDTH,
Description = "The size of all hit objects",
Current = new BindableFloat(Beatmap.Difficulty.CircleSize)
@ -40,7 +41,7 @@ namespace osu.Game.Screens.Edit.Setup
},
healthDrainSlider = new LabelledSliderBar<float>
{
Label = "Health Drain",
Label = BeatmapsetsStrings.ShowStatsDrain,
FixedLabelWidth = LABEL_WIDTH,
Description = "The rate of passive health drain throughout playable time",
Current = new BindableFloat(Beatmap.Difficulty.DrainRate)
@ -53,7 +54,7 @@ namespace osu.Game.Screens.Edit.Setup
},
approachRateSlider = new LabelledSliderBar<float>
{
Label = "Approach Rate",
Label = BeatmapsetsStrings.ShowStatsAr,
FixedLabelWidth = LABEL_WIDTH,
Description = "The speed at which objects are presented to the player",
Current = new BindableFloat(Beatmap.Difficulty.ApproachRate)
@ -66,7 +67,7 @@ namespace osu.Game.Screens.Edit.Setup
},
overallDifficultySlider = new LabelledSliderBar<float>
{
Label = "Overall Difficulty",
Label = BeatmapsetsStrings.ShowStatsAccuracy,
FixedLabelWidth = LABEL_WIDTH,
Description = "The harshness of hit windows and difficulty of special objects (ie. spinners)",
Current = new BindableFloat(Beatmap.Difficulty.OverallDifficulty)

View File

@ -7,6 +7,7 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Screens.Edit.Setup
{
@ -48,15 +49,15 @@ namespace osu.Game.Screens.Edit.Setup
creatorTextBox = createTextBox<LabelledTextBox>("Creator", metadata.Author.Username),
difficultyTextBox = createTextBox<LabelledTextBox>("Difficulty Name", Beatmap.BeatmapInfo.DifficultyName),
sourceTextBox = createTextBox<LabelledTextBox>("Source", metadata.Source),
tagsTextBox = createTextBox<LabelledTextBox>("Tags", metadata.Tags)
sourceTextBox = createTextBox<LabelledTextBox>(BeatmapsetsStrings.ShowInfoSource, metadata.Source),
tagsTextBox = createTextBox<LabelledTextBox>(BeatmapsetsStrings.ShowInfoTags, metadata.Tags)
};
foreach (var item in Children.OfType<LabelledTextBox>())
item.OnCommit += onCommit;
}
private TTextBox createTextBox<TTextBox>(string label, string initialValue)
private TTextBox createTextBox<TTextBox>(LocalisableString label, string initialValue)
where TTextBox : LabelledTextBox, new()
=> new TTextBox
{

View File

@ -1,19 +1,16 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Overlays.Settings;
namespace osu.Game.Screens.Edit.Timing
{
internal class TimingSection : Section<TimingControlPoint>
{
private SettingsSlider<double> bpmSlider;
private LabelledTimeSignature timeSignature;
private BPMTextBox bpmTextEntry;
@ -23,7 +20,6 @@ namespace osu.Game.Screens.Edit.Timing
Flow.AddRange(new Drawable[]
{
bpmTextEntry = new BPMTextBox(),
bpmSlider = new BPMSlider(),
timeSignature = new LabelledTimeSignature
{
Label = "Time Signature"
@ -35,11 +31,8 @@ namespace osu.Game.Screens.Edit.Timing
{
if (point.NewValue != null)
{
bpmSlider.Current = point.NewValue.BeatLengthBindable;
bpmSlider.Current.BindValueChanged(_ => ChangeHandler?.SaveState());
bpmTextEntry.Bindable = point.NewValue.BeatLengthBindable;
// no need to hook change handler here as it's the same bindable as above
bpmTextEntry.Current.BindValueChanged(_ => ChangeHandler?.SaveState());
timeSignature.Current = point.NewValue.TimeSignatureBindable;
timeSignature.Current.BindValueChanged(_ => ChangeHandler?.SaveState());
@ -102,51 +95,6 @@ namespace osu.Game.Screens.Edit.Timing
}
}
private class BPMSlider : SettingsSlider<double>
{
private const double sane_minimum = 60;
private const double sane_maximum = 240;
private readonly BindableNumber<double> beatLengthBindable = new TimingControlPoint().BeatLengthBindable;
private readonly BindableDouble bpmBindable = new BindableDouble(60000 / TimingControlPoint.DEFAULT_BEAT_LENGTH)
{
MinValue = sane_minimum,
MaxValue = sane_maximum,
};
public BPMSlider()
{
beatLengthBindable.BindValueChanged(beatLength => updateCurrent(beatLengthToBpm(beatLength.NewValue)), true);
bpmBindable.BindValueChanged(bpm => beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue));
base.Current = bpmBindable;
TransferValueOnCommit = true;
}
public override Bindable<double> Current
{
get => base.Current;
set
{
// incoming will be beat length, not bpm
beatLengthBindable.UnbindBindings();
beatLengthBindable.BindTo(value);
}
}
private void updateCurrent(double newValue)
{
// we use a more sane range for the slider display unless overridden by the user.
// if a value comes in outside our range, we should expand temporarily.
bpmBindable.MinValue = Math.Min(newValue, sane_minimum);
bpmBindable.MaxValue = Math.Max(newValue, sane_maximum);
bpmBindable.Value = newValue;
}
}
private static double beatLengthToBpm(double beatLength) => 60000 / beatLength;
}
}

View File

@ -0,0 +1,26 @@
// 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.Screens;
using osu.Game.Screens.Menu;
namespace osu.Game.Screens
{
/// <summary>
/// Manages a global screen stack to allow nested components a guarantee of where work is executed.
/// </summary>
[Cached]
public interface IPerformFromScreenRunner
{
/// <summary>
/// Perform an action only after returning to a specific screen as indicated by <paramref name="validScreens"/>.
/// Eagerly tries to exit the current screen until it succeeds.
/// </summary>
/// <param name="action">The action to perform once we are in the correct state.</param>
/// <param name="validScreens">An optional collection of valid screen types. If any of these screens are already current we can perform the action immediately, else the first valid parent will be made current before performing the action. <see cref="MainMenu"/> is used if not specified.</param>
void PerformFromScreen(Action<IScreen> action, IEnumerable<Type> validScreens = null);
}
}

View File

@ -118,20 +118,20 @@ namespace osu.Game.Screens.Import
fileSelector.CurrentPath.BindValueChanged(directoryChanged);
}
public override void OnEntering(IScreen last)
public override void OnEntering(ScreenTransitionEvent e)
{
base.OnEntering(last);
base.OnEntering(e);
contentContainer.ScaleTo(0.95f).ScaleTo(1, duration, Easing.OutQuint);
this.FadeInFromZero(duration);
}
public override bool OnExiting(IScreen next)
public override bool OnExiting(ScreenExitEvent e)
{
contentContainer.ScaleTo(0.95f, duration, Easing.OutQuint);
this.FadeOut(duration, Easing.OutQuint);
return base.OnExiting(next);
return base.OnExiting(e);
}
private void directoryChanged(ValueChangedEvent<DirectoryInfo> _)

View File

@ -69,9 +69,9 @@ namespace osu.Game.Screens
private EFToRealmMigrator realmMigrator;
public override void OnEntering(IScreen last)
public override void OnEntering(ScreenTransitionEvent e)
{
base.OnEntering(last);
base.OnEntering(e);
LoadComponentAsync(precompiler = CreateShaderPrecompiler(), AddInternal);

View File

@ -26,7 +26,6 @@ using osu.Game.Input.Bindings;
using osu.Game.Localisation;
using osu.Game.Online.API;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
@ -88,6 +87,8 @@ namespace osu.Game.Screens.Menu
private readonly LogoTrackingContainer logoTrackingContainer;
public bool ReturnToTopOnIdle { get; set; } = true;
public ButtonSystem()
{
RelativeSizeAxes = Axes.Both;
@ -101,7 +102,8 @@ namespace osu.Game.Screens.Menu
buttonArea.AddRange(new Drawable[]
{
new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, FontAwesome.Solid.Cog, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O),
backButton = new MainMenuButton(ButtonSystemStrings.Back, @"button-back-select", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH)
backButton = new MainMenuButton(ButtonSystemStrings.Back, @"button-back-select", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel,
-WEDGE_WIDTH)
{
VisibleState = ButtonSystemState.Play,
},
@ -117,9 +119,6 @@ namespace osu.Game.Screens.Menu
[Resolved]
private IAPIProvider api { get; set; }
[Resolved(CanBeNull = true)]
private NotificationOverlay notifications { get; set; }
[Resolved(CanBeNull = true)]
private LoginOverlay loginOverlay { get; set; }
@ -131,9 +130,11 @@ namespace osu.Game.Screens.Menu
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-generic-select", OsuIcon.Charts, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L));
buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play);
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P));
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH,
Key.P));
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-edit-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E));
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.D));
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0,
Key.D));
if (host.CanExit)
buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q));
@ -161,17 +162,7 @@ namespace osu.Game.Screens.Menu
{
if (api.State.Value != APIState.Online)
{
notifications?.Post(new SimpleNotification
{
Text = "You gotta be online to multi 'yo!",
Icon = FontAwesome.Solid.Globe,
Activated = () =>
{
loginOverlay?.Show();
return true;
}
});
loginOverlay?.Show();
return;
}
@ -182,17 +173,7 @@ namespace osu.Game.Screens.Menu
{
if (api.State.Value != APIState.Online)
{
notifications?.Post(new SimpleNotification
{
Text = "You gotta be online to view playlists 'yo!",
Icon = FontAwesome.Solid.Globe,
Activated = () =>
{
loginOverlay?.Show();
return true;
}
});
loginOverlay?.Show();
return;
}
@ -201,6 +182,9 @@ namespace osu.Game.Screens.Menu
private void updateIdleState(bool isIdle)
{
if (!ReturnToTopOnIdle)
return;
if (isIdle && State != ButtonSystemState.Exit && State != ButtonSystemState.EnteringMode)
State = ButtonSystemState.Initial;
}
@ -212,11 +196,8 @@ namespace osu.Game.Screens.Menu
if (State == ButtonSystemState.Initial)
{
if (buttonsTopLevel.Any(b => e.Key == b.TriggerKey))
{
logo?.TriggerClick();
return true;
}
logo?.TriggerClick();
return true;
}
return base.OnKeyDown(e);

View File

@ -171,9 +171,9 @@ namespace osu.Game.Screens.Menu
((IBindable<APIUser>)currentUser).BindTo(api.LocalUser);
}
public override void OnEntering(IScreen last)
public override void OnEntering(ScreenTransitionEvent e)
{
base.OnEntering(last);
base.OnEntering(e);
icon.RotateTo(10);
icon.FadeOut();

View File

@ -57,10 +57,10 @@ namespace osu.Game.Screens.Menu
}
}
public override void OnSuspending(IScreen next)
public override void OnSuspending(ScreenTransitionEvent e)
{
this.FadeOut(300);
base.OnSuspending(next);
base.OnSuspending(e);
}
}
}

View File

@ -147,7 +147,7 @@ namespace osu.Game.Screens.Menu
bool loadThemedIntro()
{
var setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash);
var setInfo = beatmaps.QueryBeatmapSet(b => b.Protected && b.Hash == BeatmapHash);
if (setInfo == null)
return false;
@ -164,14 +164,14 @@ namespace osu.Game.Screens.Menu
}
}
public override void OnEntering(IScreen last)
public override void OnEntering(ScreenTransitionEvent e)
{
base.OnEntering(last);
base.OnEntering(e);
ensureEventuallyArrivingAtMenu();
}
[Resolved]
private NotificationOverlay notifications { get; set; }
private INotificationOverlay notifications { get; set; }
private void ensureEventuallyArrivingAtMenu()
{
@ -194,7 +194,7 @@ namespace osu.Game.Screens.Menu
}, 5000);
}
public override void OnResuming(IScreen last)
public override void OnResuming(ScreenTransitionEvent e)
{
this.FadeIn(300);
@ -237,12 +237,12 @@ namespace osu.Game.Screens.Menu
//don't want to fade out completely else we will stop running updates.
Game.FadeTo(0.01f, fadeOutTime).OnComplete(_ => this.Exit());
base.OnResuming(last);
base.OnResuming(e);
}
public override void OnSuspending(IScreen next)
public override void OnSuspending(ScreenTransitionEvent e)
{
base.OnSuspending(next);
base.OnSuspending(e);
initialBeatmap = null;
}

View File

@ -89,9 +89,9 @@ namespace osu.Game.Screens.Menu
}
}
public override void OnSuspending(IScreen next)
public override void OnSuspending(ScreenTransitionEvent e)
{
base.OnSuspending(next);
base.OnSuspending(e);
// ensure the background is shown, even if the TriangleIntroSequence failed to do so.
background.ApplyToBackground(b => b.Show());
@ -100,9 +100,9 @@ namespace osu.Game.Screens.Menu
intro.Expire();
}
public override void OnResuming(IScreen last)
public override void OnResuming(ScreenTransitionEvent e)
{
base.OnResuming(last);
base.OnResuming(e);
background.FadeOut(100);
}

View File

@ -106,9 +106,9 @@ namespace osu.Game.Screens.Menu
}
}
public override void OnResuming(IScreen last)
public override void OnResuming(ScreenTransitionEvent e)
{
base.OnResuming(last);
base.OnResuming(e);
background.FadeOut(100);
}

View File

@ -35,7 +35,7 @@ namespace osu.Game.Screens.Menu
public const float FADE_OUT_DURATION = 400;
public override bool HideOverlaysOnEnter => buttons == null || buttons.State == ButtonSystemState.Initial;
public override bool HideOverlaysOnEnter => Buttons == null || Buttons.State == ButtonSystemState.Initial;
public override bool AllowBackButton => false;
@ -45,7 +45,7 @@ namespace osu.Game.Screens.Menu
private MenuSideFlashes sideFlashes;
private ButtonSystem buttons;
protected ButtonSystem Buttons;
[Resolved]
private GameHost host { get; set; }
@ -60,7 +60,7 @@ namespace osu.Game.Screens.Menu
private IAPIProvider api { get; set; }
[Resolved(canBeNull: true)]
private DialogOverlay dialogOverlay { get; set; }
private IDialogOverlay dialogOverlay { get; set; }
private BackgroundScreenDefault background;
@ -101,7 +101,7 @@ namespace osu.Game.Screens.Menu
ParallaxAmount = 0.01f,
Children = new Drawable[]
{
buttons = new ButtonSystem
Buttons = new ButtonSystem
{
OnEdit = delegate
{
@ -125,7 +125,7 @@ namespace osu.Game.Screens.Menu
exitConfirmOverlay?.CreateProxy() ?? Empty()
});
buttons.StateChanged += state =>
Buttons.StateChanged += state =>
{
switch (state)
{
@ -140,22 +140,22 @@ namespace osu.Game.Screens.Menu
}
};
buttons.OnSettings = () => settings?.ToggleVisibility();
buttons.OnBeatmapListing = () => beatmapListing?.ToggleVisibility();
Buttons.OnSettings = () => settings?.ToggleVisibility();
Buttons.OnBeatmapListing = () => beatmapListing?.ToggleVisibility();
LoadComponentAsync(background = new BackgroundScreenDefault());
preloadSongSelect();
}
[Resolved(canBeNull: true)]
private OsuGame game { get; set; }
private IPerformFromScreenRunner performer { get; set; }
private void confirmAndExit()
{
if (exitConfirmed) return;
exitConfirmed = true;
game?.PerformFromScreen(menu => menu.Exit());
performer?.PerformFromScreen(menu => menu.Exit());
}
private void preloadSongSelect()
@ -176,12 +176,12 @@ namespace osu.Game.Screens.Menu
[Resolved]
private Storage storage { get; set; }
public override void OnEntering(IScreen last)
public override void OnEntering(ScreenTransitionEvent e)
{
base.OnEntering(last);
buttons.FadeInFromZero(500);
base.OnEntering(e);
Buttons.FadeInFromZero(500);
if (last is IntroScreen && musicController.TrackLoaded)
if (e.Last is IntroScreen && musicController.TrackLoaded)
{
var track = musicController.CurrentTrack;
@ -203,14 +203,14 @@ namespace osu.Game.Screens.Menu
{
base.LogoArriving(logo, resuming);
buttons.SetOsuLogo(logo);
Buttons.SetOsuLogo(logo);
logo.FadeColour(Color4.White, 100, Easing.OutQuint);
logo.FadeIn(100, Easing.OutQuint);
if (resuming)
{
buttons.State = ButtonSystemState.TopLevel;
Buttons.State = ButtonSystemState.TopLevel;
this.FadeIn(FADE_IN_DURATION, Easing.OutQuint);
buttonsContainer.MoveTo(new Vector2(0, 0), FADE_IN_DURATION, Easing.OutQuint);
@ -245,15 +245,15 @@ namespace osu.Game.Screens.Menu
var seq = logo.FadeOut(300, Easing.InSine)
.ScaleTo(0.2f, 300, Easing.InSine);
seq.OnComplete(_ => buttons.SetOsuLogo(null));
seq.OnAbort(_ => buttons.SetOsuLogo(null));
seq.OnComplete(_ => Buttons.SetOsuLogo(null));
seq.OnAbort(_ => Buttons.SetOsuLogo(null));
}
public override void OnSuspending(IScreen next)
public override void OnSuspending(ScreenTransitionEvent e)
{
base.OnSuspending(next);
base.OnSuspending(e);
buttons.State = ButtonSystemState.EnteringMode;
Buttons.State = ButtonSystemState.EnteringMode;
this.FadeOut(FADE_OUT_DURATION, Easing.InSine);
buttonsContainer.MoveTo(new Vector2(-800, 0), FADE_OUT_DURATION, Easing.InSine);
@ -261,9 +261,9 @@ namespace osu.Game.Screens.Menu
sideFlashes.FadeOut(64, Easing.OutQuint);
}
public override void OnResuming(IScreen last)
public override void OnResuming(ScreenTransitionEvent e)
{
base.OnResuming(last);
base.OnResuming(e);
ApplyToBackground(b => (b as BackgroundScreenDefault)?.Next());
@ -273,7 +273,7 @@ namespace osu.Game.Screens.Menu
musicController.EnsurePlayingSomething();
}
public override bool OnExiting(IScreen next)
public override bool OnExiting(ScreenExitEvent e)
{
if (!exitConfirmed && dialogOverlay != null)
{
@ -285,13 +285,13 @@ namespace osu.Game.Screens.Menu
return true;
}
buttons.State = ButtonSystemState.Exit;
Buttons.State = ButtonSystemState.Exit;
OverlayActivationMode.Value = OverlayActivation.Disabled;
songTicker.Hide();
this.FadeOut(3000);
return base.OnExiting(next);
return base.OnExiting(e);
}
public void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset)

View File

@ -185,8 +185,7 @@ namespace osu.Game.Screens.Menu
private void load(AudioManager audio)
{
sampleHover = audio.Samples.Get(@"Menu/button-hover");
if (!string.IsNullOrEmpty(sampleName))
sampleClick = audio.Samples.Get($@"Menu/{sampleName}");
sampleClick = audio.Samples.Get(!string.IsNullOrEmpty(sampleName) ? $@"Menu/{sampleName}" : @"UI/button-select");
}
protected override bool OnMouseDown(MouseDownEvent e)

View File

@ -283,9 +283,15 @@ namespace osu.Game.Screens.Menu
this.Delay(early_activation).Schedule(() =>
{
if (beatIndex % timingPoint.TimeSignature.Numerator == 0)
sampleDownbeat.Play();
{
sampleDownbeat?.Play();
}
else
sampleBeat.Play();
{
var channel = sampleBeat.GetChannel();
channel.Frequency.Value = 0.95 + RNG.NextDouble(0.1);
channel.Play();
}
});
}

View File

@ -13,7 +13,7 @@ namespace osu.Game.Screens.Menu
public class StorageErrorDialog : PopupDialog
{
[Resolved]
private DialogOverlay dialogOverlay { get; set; }
private IDialogOverlay dialogOverlay { get; set; }
public StorageErrorDialog(OsuStorage storage, OsuStorageError error)
{

View File

@ -91,15 +91,15 @@ namespace osu.Game.Screens.OnlinePlay.Components
AddInternal(background = newBackground);
}
public override void OnSuspending(IScreen next)
public override void OnSuspending(ScreenTransitionEvent e)
{
base.OnSuspending(next);
base.OnSuspending(e);
this.MoveToX(0, TRANSITION_LENGTH);
}
public override bool OnExiting(IScreen next)
public override bool OnExiting(ScreenExitEvent e)
{
bool result = base.OnExiting(next);
bool result = base.OnExiting(e);
this.MoveToX(0);
return result;
}

View File

@ -6,6 +6,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK;
@ -34,7 +35,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
private readonly Circle line;
private readonly OsuSpriteText details;
public OverlinedHeader(string title)
public OverlinedHeader(LocalisableString title)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;

View File

@ -26,6 +26,7 @@ using osu.Game.Online;
using osu.Game.Online.Chat;
using osu.Game.Online.Rooms;
using osu.Game.Overlays.BeatmapSet;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Play.HUD;
@ -449,7 +450,7 @@ namespace osu.Game.Screens.OnlinePlay
Size = new Vector2(30, 30),
Alpha = AllowEditing ? 1 : 0,
Action = () => RequestEdit?.Invoke(Item),
TooltipText = "Edit"
TooltipText = CommonStrings.ButtonsEdit
},
removeButton = new PlaylistRemoveButton
{

View File

@ -0,0 +1,29 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets.Mods;
using osuTK.Input;
namespace osu.Game.Screens.OnlinePlay
{
public class FreeModSelectScreen : ModSelectScreen
{
protected override bool AllowCustomisation => false;
protected override bool ShowTotalMultiplier => false;
public new Func<Mod, bool> IsValidMod
{
get => base.IsValidMod;
set => base.IsValidMod = m => m.HasImplementation && m.UserPlayable && value.Invoke(m);
}
public FreeModSelectScreen()
{
IsValidMod = _ => true;
}
protected override ModColumn CreateModColumn(ModType modType, Key[] toggleKeys = null) => new ModColumn(modType, true, toggleKeys);
}
}

View File

@ -418,10 +418,16 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
var retrievedBeatmap = task.GetResultSafely();
statusText.Text = "Currently playing ";
beatmapText.AddLink(retrievedBeatmap.GetDisplayTitleRomanisable(),
LinkAction.OpenBeatmap,
retrievedBeatmap.OnlineID.ToString(),
creationParameters: s => s.Truncate = true);
if (retrievedBeatmap != null)
{
beatmapText.AddLink(retrievedBeatmap.GetDisplayTitleRomanisable(),
LinkAction.OpenBeatmap,
retrievedBeatmap.OnlineID.ToString(),
creationParameters: s => s.Truncate = true);
}
else
beatmapText.AddText("unknown beatmap");
}), cancellationSource.Token);
}
}

View File

@ -33,7 +33,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
playlist.Clear();
}
public override bool OnExiting(IScreen next)
public override bool OnExiting(ScreenExitEvent e)
{
// This screen never exits.
return true;

View File

@ -238,15 +238,15 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
#endregion
public override void OnEntering(IScreen last)
public override void OnEntering(ScreenTransitionEvent e)
{
base.OnEntering(last);
base.OnEntering(e);
onReturning();
}
public override void OnResuming(IScreen last)
public override void OnResuming(ScreenTransitionEvent e)
{
base.OnResuming(last);
base.OnResuming(e);
Debug.Assert(selectionLease != null);
@ -261,16 +261,16 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
onReturning();
}
public override bool OnExiting(IScreen next)
public override bool OnExiting(ScreenExitEvent e)
{
onLeaving();
return base.OnExiting(next);
return base.OnExiting(e);
}
public override void OnSuspending(IScreen next)
public override void OnSuspending(ScreenTransitionEvent e)
{
onLeaving();
base.OnSuspending(next);
base.OnSuspending(e);
}
protected override void OnFocus(FocusEvent e)

View File

@ -6,6 +6,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics.Sprites;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Leaderboards;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Scoring;
namespace osu.Game.Screens.OnlinePlay.Match.Components
@ -30,8 +31,8 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
protected override IEnumerable<LeaderboardScoreStatistic> GetStatistics(ScoreInfo model) => new[]
{
new LeaderboardScoreStatistic(FontAwesome.Solid.Crosshairs, "Accuracy", model.DisplayAccuracy),
new LeaderboardScoreStatistic(FontAwesome.Solid.Sync, "Total Attempts", score.TotalAttempts.ToString()),
new LeaderboardScoreStatistic(FontAwesome.Solid.Crosshairs, RankingsStrings.StatAccuracy, model.DisplayAccuracy),
new LeaderboardScoreStatistic(FontAwesome.Solid.Sync, RankingsStrings.StatPlayCount, score.TotalAttempts.ToString()),
new LeaderboardScoreStatistic(FontAwesome.Solid.Check, "Completed Beatmaps", score.CompletedBeatmaps.ToString()),
};
}

View File

@ -10,6 +10,7 @@ using osu.Game.Beatmaps.Drawables;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Screens.OnlinePlay.Match.Components;
using osuTK;
@ -49,7 +50,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
{
RelativeSizeAxes = Axes.Y,
Size = new Vector2(100, 1),
Text = "Edit",
Text = CommonStrings.ButtonsEdit,
Action = () => OnEdit?.Invoke()
});
}

View File

@ -290,35 +290,35 @@ namespace osu.Game.Screens.OnlinePlay.Match
protected void ShowUserModSelect() => userModsSelectOverlay.Show();
public override void OnEntering(IScreen last)
public override void OnEntering(ScreenTransitionEvent e)
{
base.OnEntering(last);
base.OnEntering(e);
beginHandlingTrack();
}
public override void OnSuspending(IScreen next)
public override void OnSuspending(ScreenTransitionEvent e)
{
endHandlingTrack();
base.OnSuspending(next);
base.OnSuspending(e);
}
public override void OnResuming(IScreen last)
public override void OnResuming(ScreenTransitionEvent e)
{
base.OnResuming(last);
base.OnResuming(e);
updateWorkingBeatmap();
beginHandlingTrack();
Scheduler.AddOnce(UpdateMods);
Scheduler.AddOnce(updateRuleset);
}
public override bool OnExiting(IScreen next)
public override bool OnExiting(ScreenExitEvent e)
{
RoomManager?.PartRoom();
Mods.Value = Array.Empty<Mod>();
endHandlingTrack();
return base.OnExiting(next);
return base.OnExiting(e);
}
protected void StartPlay()

View File

@ -114,18 +114,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
bool isReady() => Client.LocalUser?.State == MultiplayerUserState.Ready || Client.LocalUser?.State == MultiplayerUserState.Spectating;
void toggleReady() => Client.ToggleReady().ContinueWith(_ => endOperation());
void toggleReady() => Client.ToggleReady().FireAndForget(
onSuccess: endOperation,
onError: _ => endOperation());
void startMatch() => Client.StartMatch().ContinueWith(t =>
void startMatch() => Client.StartMatch().FireAndForget(onSuccess: () =>
{
// accessing Exception here silences any potential errors from the antecedent task
if (t.Exception != null)
{
// gameplay was not started due to an exception; unblock button.
endOperation();
}
// gameplay is starting, the button will be unblocked on load requested.
}, onError: _ =>
{
// gameplay was not started due to an exception; unblock button.
endOperation();
});
}

View File

@ -5,6 +5,8 @@ using System;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Localisation;
using osu.Framework.Threading;
using osu.Game.Graphics;
@ -27,6 +29,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
[CanBeNull]
private MultiplayerRoom room => multiplayerClient.Room;
private Sample countdownTickSample;
private Sample countdownWarnSample;
private Sample countdownWarnFinalSample;
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
countdownTickSample = audio.Samples.Get(@"Multiplayer/countdown-tick");
countdownWarnSample = audio.Samples.Get(@"Multiplayer/countdown-warn");
countdownWarnFinalSample = audio.Samples.Get(@"Multiplayer/countdown-warn-final");
}
protected override void LoadComplete()
{
base.LoadComplete();
@ -36,7 +50,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
}
private MultiplayerCountdown countdown;
private DateTimeOffset countdownChangeTime;
private double countdownChangeTime;
private ScheduledDelegate countdownUpdateDelegate;
private void onRoomUpdated() => Scheduler.AddOnce(() =>
@ -44,20 +58,61 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
if (countdown != room?.Countdown)
{
countdown = room?.Countdown;
countdownChangeTime = DateTimeOffset.Now;
countdownChangeTime = Time.Current;
}
scheduleNextCountdownUpdate();
updateButtonText();
updateButtonColour();
});
private void scheduleNextCountdownUpdate()
{
countdownUpdateDelegate?.Cancel();
if (countdown != null)
countdownUpdateDelegate ??= Scheduler.AddDelayed(updateButtonText, 100, true);
{
// The remaining time on a countdown may be at a fractional portion between two seconds.
// We want to align certain audio/visual cues to the point at which integer seconds change.
// To do so, we schedule to the next whole second. Note that scheduler invocation isn't
// guaranteed to be accurate, so this may still occur slightly late, but even in such a case
// the next invocation will be roughly correct.
double timeToNextSecond = countdownTimeRemaining.TotalMilliseconds % 1000;
countdownUpdateDelegate = Scheduler.AddDelayed(onCountdownTick, timeToNextSecond);
}
else
{
countdownUpdateDelegate?.Cancel();
countdownUpdateDelegate = null;
}
updateButtonText();
updateButtonColour();
});
void onCountdownTick()
{
updateButtonText();
int secondsRemaining = countdownTimeRemaining.Seconds;
playTickSound(secondsRemaining);
if (secondsRemaining > 0)
scheduleNextCountdownUpdate();
}
}
private void playTickSound(int secondsRemaining)
{
if (secondsRemaining < 10) countdownTickSample?.Play();
if (secondsRemaining <= 3)
{
if (secondsRemaining > 0)
countdownWarnSample?.Play();
else
countdownWarnFinalSample?.Play();
}
}
private void updateButtonText()
{
@ -75,15 +130,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
if (countdown != null)
{
TimeSpan timeElapsed = DateTimeOffset.Now - countdownChangeTime;
TimeSpan countdownRemaining;
if (timeElapsed > countdown.TimeRemaining)
countdownRemaining = TimeSpan.Zero;
else
countdownRemaining = countdown.TimeRemaining - timeElapsed;
string countdownText = $"Starting in {countdownRemaining:mm\\:ss}";
string countdownText = $"Starting in {countdownTimeRemaining:mm\\:ss}";
switch (localUser?.State)
{
@ -116,6 +163,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
}
}
private TimeSpan countdownTimeRemaining
{
get
{
double timeElapsed = Time.Current - countdownChangeTime;
TimeSpan remaining;
if (timeElapsed > countdown.TimeRemaining.TotalMilliseconds)
remaining = TimeSpan.Zero;
else
remaining = countdown.TimeRemaining - TimeSpan.FromMilliseconds(timeElapsed);
return remaining;
}
}
private void updateButtonColour()
{
if (room == null)

View File

@ -117,8 +117,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
{
base.PlaylistItemChanged(item);
removeItemFromLists(item.ID);
addItemToLists(item);
var newApiItem = Playlist.SingleOrDefault(i => i.ID == item.ID);
var existingApiItemInQueue = queueList.Items.SingleOrDefault(i => i.ID == item.ID);
// Test if the only change between the two playlist items is the order.
if (newApiItem != null && existingApiItemInQueue != null && existingApiItemInQueue.With(playlistOrder: newApiItem.PlaylistOrder).Equals(newApiItem))
{
// Set the new playlist order directly without refreshing the DrawablePlaylistItem.
existingApiItemInQueue.PlaylistOrder = newApiItem.PlaylistOrder;
// The following isn't really required, but is here for safety and explicitness.
// MultiplayerQueueList internally binds to changes in Playlist to invalidate its own layout, which is mutated on every playlist operation.
queueList.Invalidate();
}
else
{
removeItemFromLists(item.ID);
addItemToLists(item);
}
}
private void addItemToLists(MultiplayerPlaylistItem item)

View File

@ -62,7 +62,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
{
base.LoadComplete();
RequestDeletion = item => multiplayerClient.RemovePlaylistItem(item.ID);
RequestDeletion = item => multiplayerClient.RemovePlaylistItem(item.ID).FireAndForget();
multiplayerClient.RoomUpdated += onRoomUpdated;
onRoomUpdated();

View File

@ -35,20 +35,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
transitionFromResults();
}
public override void OnResuming(IScreen last)
public override void OnResuming(ScreenTransitionEvent e)
{
base.OnResuming(last);
base.OnResuming(e);
if (client.Room == null)
return;
if (!(last is MultiplayerPlayerLoader playerLoader))
if (!(e.Last is MultiplayerPlayerLoader playerLoader))
return;
// If gameplay wasn't finished, then we have a simple path back to the idle state by aborting gameplay.
if (!playerLoader.GameplayPassed)
{
client.AbortGameplay();
client.AbortGameplay().FireAndForget();
return;
}

View File

@ -25,13 +25,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
[Resolved]
private MultiplayerClient client { get; set; }
public override void OnResuming(IScreen last)
public override void OnResuming(ScreenTransitionEvent e)
{
base.OnResuming(last);
base.OnResuming(e);
// Upon having left a room, we don't know whether we were the only participant, and whether the room is now closed as a result of leaving it.
// To work around this, temporarily remove the room and trigger an immediate listing poll.
if (last is MultiplayerMatchSubScreen match)
if (e.Last is MultiplayerMatchSubScreen match)
{
RoomManager.RemoveRoom(match.Room);
ListingPollingComponent.PollImmediately();

View File

@ -1,13 +1,9 @@
// 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.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using osu.Framework.Allocation;
using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
@ -76,40 +72,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
Task task = itemToEdit != null ? client.EditPlaylistItem(multiplayerItem) : client.AddPlaylistItem(multiplayerItem);
task.ContinueWith(t =>
task.FireAndForget(onSuccess: () => Schedule(() =>
{
Schedule(() =>
{
// If an error or server side trigger occurred this screen may have already exited by external means.
if (!this.IsCurrentScreen())
return;
loadingLayer.Hide();
if (t.IsFaulted)
{
Exception exception = t.Exception;
if (exception is AggregateException ae)
exception = ae.InnerException;
Debug.Assert(exception != null);
string message = exception is HubException
// HubExceptions arrive with additional message context added, but we want to display the human readable message:
// "An unexpected error occurred invoking 'AddPlaylistItem' on the server.InvalidStateException: Can't enqueue more than 3 items at once."
// We generally use the message field for a user-parseable error (eventually to be replaced), so drop the first part for now.
? exception.Message.Substring(exception.Message.IndexOf(':') + 1).Trim()
: exception.Message;
Logger.Log(message, level: LogLevel.Important);
Carousel.AllowSelection = true;
return;
}
loadingLayer.Hide();
// If an error or server side trigger occurred this screen may have already exited by external means.
if (this.IsCurrentScreen())
this.Exit();
});
});
}), onError: _ => Schedule(() =>
{
loadingLayer.Hide();
Carousel.AllowSelection = true;
}));
}
else
{

View File

@ -238,18 +238,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
}
[Resolved(canBeNull: true)]
private DialogOverlay dialogOverlay { get; set; }
private IDialogOverlay dialogOverlay { get; set; }
private bool exitConfirmed;
public override bool OnExiting(IScreen next)
public override bool OnExiting(ScreenExitEvent e)
{
// the room may not be left immediately after a disconnection due to async flow,
// so checking the IsConnected status is also required.
if (client.Room == null || !client.IsConnected.Value)
{
// room has not been created yet; exit immediately.
return base.OnExiting(next);
return base.OnExiting(e);
}
if (!exitConfirmed && dialogOverlay != null)
@ -268,7 +268,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
return true;
}
return base.OnExiting(next);
return base.OnExiting(e);
}
private ModSettingChangeTracker modSettingChangeTracker;
@ -281,7 +281,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
if (client.Room == null)
return;
client.ChangeUserMods(mods.NewValue);
client.ChangeUserMods(mods.NewValue).FireAndForget();
modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue);
modSettingChangeTracker.SettingChanged += onModSettingsChanged;
@ -296,7 +296,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
if (client.Room == null)
return;
client.ChangeUserMods(UserMods.Value);
client.ChangeUserMods(UserMods.Value).FireAndForget();
}, 500);
}
@ -305,7 +305,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
if (client.Room == null)
return;
client.ChangeBeatmapAvailability(availability.NewValue);
client.ChangeBeatmapAvailability(availability.NewValue).FireAndForget();
if (availability.NewValue.State != DownloadState.LocallyAvailable)
{

View File

@ -133,6 +133,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
failAndBail();
}
}), true);
}
protected override void LoadComplete()
{
base.LoadComplete();
Debug.Assert(client.Room != null);
}

View File

@ -18,10 +18,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
}
public override void OnSuspending(IScreen next)
public override void OnSuspending(ScreenTransitionEvent e)
{
base.OnSuspending(next);
player = (Player)next;
base.OnSuspending(e);
player = (Player)e.Next;
}
}
}

View File

@ -24,12 +24,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
public class MultiplayerTeamResultsScreen : MultiplayerResultsScreen
{
private readonly SortedDictionary<int, BindableInt> teamScores;
private readonly SortedDictionary<int, BindableLong> teamScores;
private Container winnerBackground;
private Drawable winnerText;
public MultiplayerTeamResultsScreen(ScoreInfo score, long roomId, PlaylistItem playlistItem, SortedDictionary<int, BindableInt> teamScores)
public MultiplayerTeamResultsScreen(ScoreInfo score, long roomId, PlaylistItem playlistItem, SortedDictionary<int, BindableLong> teamScores)
: base(score, roomId, playlistItem)
{
if (teamScores.Count != 2)

View File

@ -169,7 +169,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
Origin = Anchor.Centre,
Alpha = 0,
Margin = new MarginPadding(4),
Action = () => Client.KickUser(User.UserID),
Action = () => Client.KickUser(User.UserID).FireAndForget(),
},
},
}
@ -231,7 +231,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
if (!Client.IsHost)
return;
Client.TransferHost(targetUser);
Client.TransferHost(targetUser).FireAndForget();
}),
new OsuMenuItem("Kick", MenuItemType.Destructive, () =>
{
@ -239,7 +239,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
if (!Client.IsHost)
return;
Client.KickUser(targetUser);
Client.KickUser(targetUser).FireAndForget();
})
};
}

View File

@ -3,6 +3,7 @@
using osu.Framework.Allocation;
using osu.Game.Online.Multiplayer;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Screens.OnlinePlay.Components;
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
@ -13,7 +14,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
private MultiplayerClient client { get; set; }
public ParticipantsListHeader()
: base("Participants")
: base(RankingsStrings.SpotlightParticipants)
{
}

View File

@ -83,7 +83,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
Client.SendMatchRequest(new ChangeTeamRequest
{
TeamID = ((Client.LocalUser?.MatchState as TeamVersusUserState)?.TeamID + 1) % 2 ?? 0,
});
}).FireAndForget();
}
public int? DisplayedTeam { get; private set; }

View File

@ -17,8 +17,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
Bindable<bool> WaitingOnFrames { get; }
/// <summary>
/// Whether this clock is resynchronising to the master clock.
/// Whether this clock is behind the master clock and running at a higher rate to catch up to it.
/// </summary>
/// <remarks>
/// Of note, this will be false if this clock is *ahead* of the master clock.
/// </remarks>
bool IsCatchingUp { get; set; }
/// <summary>

View File

@ -55,12 +55,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
public SpectatorGameplayClockContainer([NotNull] IClock sourceClock)
: base(sourceClock)
{
// the container should initially be in a stopped state until the catch-up clock is started by the sync manager.
Stop();
}
protected override void Update()
{
// The SourceClock here is always a CatchUpSpectatorPlayerClock.
// The player clock's running state is controlled externally, but the local pausing state needs to be updated to stop gameplay.
if (SourceClock.IsRunning)
Start();

View File

@ -164,7 +164,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
base.LoadComplete();
masterClockContainer.Reset();
masterClockContainer.Stop();
syncManager.ReadyToStart += onReadyToStart;
syncManager.MasterState.BindValueChanged(onMasterStateChanged, true);
@ -198,8 +197,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
.DefaultIfEmpty(0)
.Min();
masterClockContainer.Seek(startTime);
masterClockContainer.Start();
masterClockContainer.StartTime = startTime;
masterClockContainer.Reset(true);
// Although the clock has been started, this flag is set to allow for later synchronisation state changes to also be able to start it.
canStartMasterClock = true;

View File

@ -110,7 +110,7 @@ namespace osu.Game.Screens.OnlinePlay
}
}
public override void OnEntering(IScreen last)
public override void OnEntering(ScreenTransitionEvent e)
{
this.FadeIn();
waves.Show();
@ -118,35 +118,35 @@ namespace osu.Game.Screens.OnlinePlay
Mods.SetDefault();
if (loungeSubScreen.IsCurrentScreen())
loungeSubScreen.OnEntering(last);
loungeSubScreen.OnEntering(e);
else
loungeSubScreen.MakeCurrent();
}
public override void OnResuming(IScreen last)
public override void OnResuming(ScreenTransitionEvent e)
{
this.FadeIn(250);
this.ScaleTo(1, 250, Easing.OutSine);
Debug.Assert(screenStack.CurrentScreen != null);
screenStack.CurrentScreen.OnResuming(last);
screenStack.CurrentScreen.OnResuming(e);
base.OnResuming(last);
base.OnResuming(e);
}
public override void OnSuspending(IScreen next)
public override void OnSuspending(ScreenTransitionEvent e)
{
this.ScaleTo(1.1f, 250, Easing.InSine);
this.FadeOut(250);
Debug.Assert(screenStack.CurrentScreen != null);
screenStack.CurrentScreen.OnSuspending(next);
screenStack.CurrentScreen.OnSuspending(e);
}
public override bool OnExiting(IScreen next)
public override bool OnExiting(ScreenExitEvent e)
{
var subScreen = screenStack.CurrentScreen as Drawable;
if (subScreen?.IsLoaded == true && screenStack.CurrentScreen.OnExiting(next))
if (subScreen?.IsLoaded == true && screenStack.CurrentScreen.OnExiting(e))
return true;
RoomManager.PartRoom();
@ -155,7 +155,7 @@ namespace osu.Game.Screens.OnlinePlay
this.Delay(WaveContainer.DISAPPEAR_DURATION).FadeOut();
base.OnExiting(next);
base.OnExiting(e);
return false;
}

View File

@ -141,7 +141,7 @@ namespace osu.Game.Screens.OnlinePlay
return base.OnBackButton();
}
public override bool OnExiting(IScreen next)
public override bool OnExiting(ScreenExitEvent e)
{
if (!itemSelected)
{
@ -150,7 +150,7 @@ namespace osu.Game.Screens.OnlinePlay
Mods.Value = initialMods;
}
return base.OnExiting(next);
return base.OnExiting(e);
}
protected override ModSelectOverlay CreateModSelectOverlay() => new UserModSelectOverlay

View File

@ -27,28 +27,28 @@ namespace osu.Game.Screens.OnlinePlay
public const double DISAPPEAR_DURATION = 500;
public override void OnEntering(IScreen last)
public override void OnEntering(ScreenTransitionEvent e)
{
base.OnEntering(last);
base.OnEntering(e);
this.FadeInFromZero(APPEAR_DURATION, Easing.OutQuint);
}
public override bool OnExiting(IScreen next)
public override bool OnExiting(ScreenExitEvent e)
{
base.OnExiting(next);
base.OnExiting(e);
this.FadeOut(DISAPPEAR_DURATION, Easing.OutQuint);
return false;
}
public override void OnResuming(IScreen last)
public override void OnResuming(ScreenTransitionEvent e)
{
base.OnResuming(last);
base.OnResuming(e);
this.FadeIn(APPEAR_DURATION, Easing.OutQuint);
}
public override void OnSuspending(IScreen next)
public override void OnSuspending(ScreenTransitionEvent e)
{
base.OnSuspending(next);
base.OnSuspending(e);
this.FadeOut(DISAPPEAR_DURATION, Easing.OutQuint);
}

View File

@ -45,9 +45,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
throw new InvalidOperationException("Current Mods do not match PlaylistItem's RequiredMods");
}
public override bool OnExiting(IScreen next)
public override bool OnExiting(ScreenExitEvent e)
{
if (base.OnExiting(next))
if (base.OnExiting(e))
return true;
Exited?.Invoke();

View File

@ -48,7 +48,7 @@ namespace osu.Game.Screens
/// </summary>
protected virtual OverlayActivation InitialOverlayActivationMode => OverlayActivation.All;
protected readonly Bindable<OverlayActivation> OverlayActivationMode;
public readonly Bindable<OverlayActivation> OverlayActivationMode;
IBindable<OverlayActivation> IOsuScreen.OverlayActivationMode => OverlayActivationMode;
@ -171,7 +171,7 @@ namespace osu.Game.Screens
background.ApplyToBackground(action);
}
public override void OnResuming(IScreen last)
public override void OnResuming(ScreenTransitionEvent e)
{
if (PlayResumeSound)
sampleExit?.Play();
@ -183,19 +183,19 @@ namespace osu.Game.Screens
if (trackAdjustmentStateAtSuspend != null)
musicController.AllowTrackAdjustments = trackAdjustmentStateAtSuspend.Value;
base.OnResuming(last);
base.OnResuming(e);
}
public override void OnSuspending(IScreen next)
public override void OnSuspending(ScreenTransitionEvent e)
{
base.OnSuspending(next);
base.OnSuspending(e);
trackAdjustmentStateAtSuspend = musicController.AllowTrackAdjustments;
onSuspendingLogo();
}
public override void OnEntering(IScreen last)
public override void OnEntering(ScreenTransitionEvent e)
{
applyArrivingDefaults(false);
@ -210,15 +210,15 @@ namespace osu.Game.Screens
}
background = backgroundStack?.CurrentScreen as BackgroundScreen;
base.OnEntering(last);
base.OnEntering(e);
}
public override bool OnExiting(IScreen next)
public override bool OnExiting(ScreenExitEvent e)
{
if (ValidForResume && logo != null)
onExitingLogo();
if (base.OnExiting(next))
if (base.OnExiting(e))
return true;
if (ownedBackground != null && backgroundStack?.CurrentScreen == ownedBackground)

View File

@ -14,6 +14,7 @@ using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Play.HUD;
using osuTK;
@ -106,7 +107,7 @@ namespace osu.Game.Screens.Play
new Sprite
{
RelativeSizeAxes = Axes.Both,
Texture = beatmap?.Background,
Texture = beatmap.Background,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
FillMode = FillMode.Fill,
@ -126,7 +127,7 @@ namespace osu.Game.Screens.Play
{
new OsuSpriteText
{
Text = beatmap?.BeatmapInfo?.DifficultyName,
Text = beatmap.BeatmapInfo.DifficultyName,
Font = OsuFont.GetFont(size: 26, italics: true),
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
@ -158,7 +159,7 @@ namespace osu.Game.Screens.Play
{
new Drawable[]
{
new MetadataLineLabel("Source"),
new MetadataLineLabel(BeatmapsetsStrings.ShowInfoSource),
new MetadataLineInfo(metadata.Source)
},
new Drawable[]
@ -213,7 +214,7 @@ namespace osu.Game.Screens.Play
private class MetadataLineLabel : OsuSpriteText
{
public MetadataLineLabel(string text)
public MetadataLineLabel(LocalisableString text)
{
Anchor = Anchor.TopRight;
Origin = Anchor.TopRight;

View File

@ -5,6 +5,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Scoring;
using osuTK;
@ -42,8 +43,7 @@ namespace osu.Game.Screens.Play.Break
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
AccuracyDisplay = new PercentageBreakInfoLine("Accuracy"),
AccuracyDisplay = new PercentageBreakInfoLine(BeatmapsetsStrings.ShowScoreboardHeadersAccuracy),
// See https://github.com/ppy/osu/discussions/15185
// RankDisplay = new BreakInfoLine<int>("Rank"),
GradeDisplay = new BreakInfoLine<ScoreRank>("Grade"),

View File

@ -26,7 +26,7 @@ namespace osu.Game.Screens.Play.Break
private readonly string prefix;
public BreakInfoLine(string name, string prefix = @"")
public BreakInfoLine(LocalisableString name, string prefix = @"")
{
this.prefix = prefix;
@ -82,7 +82,7 @@ namespace osu.Game.Screens.Play.Break
public class PercentageBreakInfoLine : BreakInfoLine<double>
{
public PercentageBreakInfoLine(string name, string prefix = "")
public PercentageBreakInfoLine(LocalisableString name, string prefix = "")
: base(name, prefix)
{
}

View File

@ -24,7 +24,7 @@ namespace osu.Game.Screens.Play
/// <summary>
/// Whether gameplay is paused.
/// </summary>
public readonly BindableBool IsPaused = new BindableBool();
public readonly BindableBool IsPaused = new BindableBool(true);
/// <summary>
/// The adjustable source clock used for gameplay. Should be used for seeks and clock control.
@ -41,6 +41,15 @@ namespace osu.Game.Screens.Play
/// </summary>
public event Action OnSeek;
/// <summary>
/// The time from which the clock should start. Will be seeked to on calling <see cref="Reset"/>.
/// </summary>
/// <remarks>
/// If not set, a value of zero will be used.
/// Importantly, the value will be inferred from the current ruleset in <see cref="MasterGameplayClockContainer"/> unless specified.
/// </remarks>
public double? StartTime { get; set; }
/// <summary>
/// Creates a new <see cref="GameplayClockContainer"/>.
/// </summary>
@ -106,16 +115,17 @@ namespace osu.Game.Screens.Play
/// <summary>
/// Resets this <see cref="GameplayClockContainer"/> and the source to an initial state ready for gameplay.
/// </summary>
public virtual void Reset()
/// <param name="startClock">Whether to start the clock immediately, if not already started.</param>
public void Reset(bool startClock = false)
{
ensureSourceClockSet();
Seek(0);
// Manually stop the source in order to not affect the IsPaused state.
AdjustableSource.Stop();
if (!IsPaused.Value)
if (!IsPaused.Value || startClock)
Start();
ensureSourceClockSet();
Seek(StartTime ?? 0);
}
/// <summary>

View File

@ -19,8 +19,8 @@ namespace osu.Game.Screens.Play.HUD
private const float bar_height = 18;
private const float font_size = 50;
public BindableInt Team1Score = new BindableInt();
public BindableInt Team2Score = new BindableInt();
public BindableLong Team1Score = new BindableLong();
public BindableLong Team2Score = new BindableLong();
protected MatchScoreCounter Score1Text;
protected MatchScoreCounter Score2Text;
@ -133,7 +133,7 @@ namespace osu.Game.Screens.Play.HUD
var winningBar = Team1Score.Value > Team2Score.Value ? score1Bar : score2Bar;
var losingBar = Team1Score.Value <= Team2Score.Value ? score1Bar : score2Bar;
int diff = Math.Max(Team1Score.Value, Team2Score.Value) - Math.Min(Team1Score.Value, Team2Score.Value);
long diff = Math.Max(Team1Score.Value, Team2Score.Value) - Math.Min(Team1Score.Value, Team2Score.Value);
losingBar.ResizeWidthTo(0, 400, Easing.OutQuint);
winningBar.ResizeWidthTo(Math.Min(0.4f, MathF.Pow(diff / 1500000f, 0.5f) / 2), 400, Easing.OutQuint);

View File

@ -29,7 +29,7 @@ namespace osu.Game.Screens.Play.HUD
{
protected readonly Dictionary<int, TrackedUserData> UserScores = new Dictionary<int, TrackedUserData>();
public readonly SortedDictionary<int, BindableInt> TeamScores = new SortedDictionary<int, BindableInt>();
public readonly SortedDictionary<int, BindableLong> TeamScores = new SortedDictionary<int, BindableLong>();
[Resolved]
private OsuColour colours { get; set; }
@ -75,21 +75,27 @@ namespace osu.Game.Screens.Play.HUD
foreach (var user in playingUsers)
{
var trackedUser = CreateUserData(user, ruleset, scoreProcessor);
trackedUser.ScoringMode.BindTo(scoringMode);
trackedUser.Score.BindValueChanged(_ => Scheduler.AddOnce(updateTotals));
UserScores[user.UserID] = trackedUser;
if (trackedUser.Team is int team && !TeamScores.ContainsKey(team))
TeamScores.Add(team, new BindableInt());
TeamScores.Add(team, new BindableLong());
}
userLookupCache.GetUsersAsync(playingUsers.Select(u => u.UserID).ToArray()).ContinueWith(task => Schedule(() =>
{
var users = task.GetResultSafely();
foreach (var user in users)
for (int i = 0; i < users.Length; i++)
{
if (user == null)
continue;
var user = users[i] ?? new APIUser
{
Id = playingUsers[i].UserID,
Username = "Unknown user",
};
var trackedUser = UserScores[user.Id];
@ -175,8 +181,6 @@ namespace osu.Game.Screens.Play.HUD
trackedData.Frames.Add(new TimedFrame(bundle.Frames.First().Time, bundle.Header));
trackedData.UpdateScore();
updateTotals();
});
private void updateTotals()

View File

@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
@ -20,6 +19,7 @@ using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Judgements;
@ -80,13 +80,16 @@ namespace osu.Game.Screens.Play.HUD
difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, clonedMods, loadCancellationSource.Token)
.ContinueWith(task => Schedule(() =>
{
if (task.Exception != null)
return;
timedAttributes = task.GetResultSafely();
IsValid = true;
if (lastJudgement != null)
onJudgementChanged(lastJudgement);
}), TaskContinuationOptions.OnlyOnRanToCompletion);
}));
}
}
@ -198,7 +201,7 @@ namespace osu.Game.Screens.Play.HUD
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Text = @"pp",
Text = BeatmapsetsStrings.ShowScoreboardHeaderspp,
Font = OsuFont.Numeric.With(size: 8),
Padding = new MarginPadding { Bottom = 1.5f }, // align baseline better
}

View File

@ -9,6 +9,7 @@ using Newtonsoft.Json;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Logging;
using osu.Game.Configuration;
using osu.Game.Extensions;
using osu.Game.Skinning;
@ -84,9 +85,17 @@ namespace osu.Game.Screens.Play.HUD
/// <returns>The new instance.</returns>
public Drawable CreateInstance()
{
Drawable d = (Drawable)Activator.CreateInstance(Type);
d.ApplySkinnableInfo(this);
return d;
try
{
Drawable d = (Drawable)Activator.CreateInstance(Type);
d.ApplySkinnableInfo(this);
return d;
}
catch (Exception e)
{
Logger.Error(e, $"Unable to create skin component {Type.Name}");
return Drawable.Empty();
}
}
}
}

View File

@ -66,7 +66,7 @@ namespace osu.Game.Screens.Play
private readonly FillFlowContainer bottomRightElements;
private readonly FillFlowContainer topRightElements;
internal readonly IBindable<bool> IsBreakTime = new Bindable<bool>();
internal readonly IBindable<bool> IsPlaying = new Bindable<bool>();
private bool holdingForHUD;
@ -119,7 +119,7 @@ namespace osu.Game.Screens.Play
}
[BackgroundDependencyLoader(true)]
private void load(OsuConfigManager config, NotificationOverlay notificationOverlay)
private void load(OsuConfigManager config, INotificationOverlay notificationOverlay)
{
if (drawableRuleset != null)
{
@ -152,7 +152,7 @@ namespace osu.Game.Screens.Play
ShowHud.BindValueChanged(visible => hideTargets.ForEach(d => d.FadeTo(visible.NewValue ? 1 : 0, FADE_DURATION, FADE_EASING)));
IsBreakTime.BindValueChanged(_ => updateVisibility());
IsPlaying.BindValueChanged(_ => updateVisibility());
configVisibilityMode.BindValueChanged(_ => updateVisibility(), true);
replayLoaded.BindValueChanged(replayLoadedValueChanged, true);
@ -218,7 +218,7 @@ namespace osu.Game.Screens.Play
case HUDVisibilityMode.HideDuringGameplay:
// always show during replay as we want the seek bar to be visible.
ShowHud.Value = replayLoaded.Value || IsBreakTime.Value;
ShowHud.Value = replayLoaded.Value || !IsPlaying.Value;
break;
case HUDVisibilityMode.Always:

View File

@ -46,36 +46,36 @@ namespace osu.Game.Screens.Play
private double totalAppliedOffset => userBeatmapOffsetClock.RateAdjustedOffset + userGlobalOffsetClock.RateAdjustedOffset + platformOffsetClock.RateAdjustedOffset;
private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1);
private readonly BindableDouble pauseFreqAdjust = new BindableDouble(); // Important that this starts at zero, matching the paused state of the clock.
private readonly WorkingBeatmap beatmap;
private readonly double gameplayStartTime;
private readonly bool startAtGameplayStart;
private readonly double firstHitObjectTime;
private HardwareCorrectionOffsetClock userGlobalOffsetClock;
private HardwareCorrectionOffsetClock userBeatmapOffsetClock;
private HardwareCorrectionOffsetClock platformOffsetClock;
private MasterGameplayClock masterGameplayClock;
private Bindable<double> userAudioOffset;
private double startOffset;
private IDisposable beatmapOffsetSubscription;
private readonly double skipTargetTime;
[Resolved]
private RealmAccess realm { get; set; }
[Resolved]
private OsuConfigManager config { get; set; }
public MasterGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime, bool startAtGameplayStart = false)
/// <summary>
/// Create a new master gameplay clock container.
/// </summary>
/// <param name="beatmap">The beatmap to be used for time and metadata references.</param>
/// <param name="skipTargetTime">The latest time which should be used when introducing gameplay. Will be used when skipping forward.</param>
public MasterGameplayClockContainer(WorkingBeatmap beatmap, double skipTargetTime)
: base(beatmap.Track)
{
this.beatmap = beatmap;
this.gameplayStartTime = gameplayStartTime;
this.startAtGameplayStart = startAtGameplayStart;
firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime;
this.skipTargetTime = skipTargetTime;
}
protected override void LoadComplete()
@ -90,41 +90,67 @@ namespace osu.Game.Screens.Play
settings => settings.Offset,
val => userBeatmapOffsetClock.Offset = val);
// sane default provided by ruleset.
startOffset = gameplayStartTime;
// Reset may have been called externally before LoadComplete.
// If it was, and the clock is in a playing state, we want to ensure that it isn't stopped here.
bool isStarted = !IsPaused.Value;
if (!startAtGameplayStart)
{
startOffset = Math.Min(0, startOffset);
// If a custom start time was not specified, calculate the best value to use.
StartTime ??= findEarliestStartTime();
// if a storyboard is present, it may dictate the appropriate start time by having events in negative time space.
// this is commonly used to display an intro before the audio track start.
double? firstStoryboardEvent = beatmap.Storyboard.EarliestEventTime;
if (firstStoryboardEvent != null)
startOffset = Math.Min(startOffset, firstStoryboardEvent.Value);
Reset(startClock: isStarted);
}
// some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available.
// this is not available as an option in the live editor but can still be applied via .osu editing.
if (beatmap.BeatmapInfo.AudioLeadIn > 0)
startOffset = Math.Min(startOffset, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn);
}
private double findEarliestStartTime()
{
// here we are trying to find the time to start playback from the "zero" point.
// generally this is either zero, or some point earlier than zero in the case of storyboards, lead-ins etc.
Seek(startOffset);
// start with the originally provided latest time (if before zero).
double time = Math.Min(0, skipTargetTime);
// if a storyboard is present, it may dictate the appropriate start time by having events in negative time space.
// this is commonly used to display an intro before the audio track start.
double? firstStoryboardEvent = beatmap.Storyboard.EarliestEventTime;
if (firstStoryboardEvent != null)
time = Math.Min(time, firstStoryboardEvent.Value);
// some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available.
// this is not available as an option in the live editor but can still be applied via .osu editing.
double firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime;
if (beatmap.BeatmapInfo.AudioLeadIn > 0)
time = Math.Min(time, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn);
return time;
}
protected override void OnIsPausedChanged(ValueChangedEvent<bool> isPaused)
{
// The source is stopped by a frequency fade first.
if (isPaused.NewValue)
if (IsLoaded)
{
this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ =>
// During normal operation, the source is stopped after performing a frequency ramp.
if (isPaused.NewValue)
{
if (IsPaused.Value == isPaused.NewValue)
AdjustableSource.Stop();
});
this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ =>
{
if (IsPaused.Value == isPaused.NewValue)
AdjustableSource.Stop();
});
}
else
this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In);
}
else
this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In);
{
if (isPaused.NewValue)
AdjustableSource.Stop();
// If not yet loaded, we still want to ensure relevant state is correct, as it is used for offset calculations.
pauseFreqAdjust.Value = isPaused.NewValue ? 0 : 1;
// We must also process underlying gameplay clocks to update rate-adjusted offsets with the new frequency adjustment.
// Without doing this, an initial seek may be performed with the wrong offset.
GameplayClock.UnderlyingClock.ProcessFrame();
}
}
public override void Start()
@ -152,10 +178,10 @@ namespace osu.Game.Screens.Play
/// </summary>
public void Skip()
{
if (GameplayClock.CurrentTime > gameplayStartTime - MINIMUM_SKIP_TIME)
if (GameplayClock.CurrentTime > skipTargetTime - MINIMUM_SKIP_TIME)
return;
double skipTarget = gameplayStartTime - MINIMUM_SKIP_TIME;
double skipTarget = skipTargetTime - MINIMUM_SKIP_TIME;
if (GameplayClock.CurrentTime < 0 && skipTarget > 6000)
// double skip exception for storyboards with very long intros
@ -164,12 +190,6 @@ namespace osu.Game.Screens.Play
Seek(skipTarget);
}
public override void Reset()
{
base.Reset();
Seek(startOffset);
}
protected override GameplayClock CreateGameplayClock(IFrameBasedClock source)
{
// Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited.
@ -278,7 +298,6 @@ namespace osu.Game.Screens.Play
private class MasterGameplayClock : GameplayClock
{
public readonly List<Bindable<double>> MutableNonGameplayAdjustments = new List<Bindable<double>>();
public override IEnumerable<Bindable<double>> NonGameplayAdjustments => MutableNonGameplayAdjustments;
public MasterGameplayClock(FramedOffsetClock underlyingClock)

View File

@ -457,7 +457,7 @@ namespace osu.Game.Screens.Play
private void updateGameplayState()
{
bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.IsPaused.Value && !breakTracker.IsBreakTime.Value;
bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.IsPaused.Value && !breakTracker.IsBreakTime.Value && !GameplayState.HasFailed;
OverlayActivationMode.Value = inGameplay ? OverlayActivation.Disabled : OverlayActivation.UserTriggered;
localUserPlaying.Value = inGameplay;
}
@ -607,30 +607,25 @@ namespace osu.Game.Screens.Play
private ScheduledDelegate frameStablePlaybackResetDelegate;
/// <summary>
/// Seeks to a specific time in gameplay, bypassing frame stability.
/// Specify and seek to a custom start time from which gameplay should be observed.
/// </summary>
/// <remarks>
/// Intermediate hitobject judgements may not be applied or reverted correctly during this seek.
/// This performs a non-frame-stable seek. Intermediate hitobject judgements may not be applied or reverted correctly during this seek.
/// </remarks>
/// <param name="time">The destination time to seek to.</param>
internal void NonFrameStableSeek(double time)
protected void SetGameplayStartTime(double time)
{
// TODO: This schedule should not be required and is a temporary hotfix.
// See https://github.com/ppy/osu/issues/17267 for the issue.
// See https://github.com/ppy/osu/pull/17302 for a better fix which needs some more time.
ScheduleAfterChildren(() =>
{
if (frameStablePlaybackResetDelegate?.Cancelled == false && !frameStablePlaybackResetDelegate.Completed)
frameStablePlaybackResetDelegate.RunTask();
if (frameStablePlaybackResetDelegate?.Cancelled == false && !frameStablePlaybackResetDelegate.Completed)
frameStablePlaybackResetDelegate.RunTask();
bool wasFrameStable = DrawableRuleset.FrameStablePlayback;
DrawableRuleset.FrameStablePlayback = false;
bool wasFrameStable = DrawableRuleset.FrameStablePlayback;
DrawableRuleset.FrameStablePlayback = false;
Seek(time);
GameplayClockContainer.StartTime = time;
GameplayClockContainer.Reset();
// Delay resetting frame-stable playback for one frame to give the FrameStabilityContainer a chance to seek.
frameStablePlaybackResetDelegate = ScheduleAfterChildren(() => DrawableRuleset.FrameStablePlayback = wasFrameStable);
});
// Delay resetting frame-stable playback for one frame to give the FrameStabilityContainer a chance to seek.
frameStablePlaybackResetDelegate = ScheduleAfterChildren(() => DrawableRuleset.FrameStablePlayback = wasFrameStable);
}
/// <summary>
@ -817,6 +812,8 @@ namespace osu.Game.Screens.Play
GameplayState.HasFailed = true;
Score.ScoreInfo.Passed = false;
updateGameplayState();
// There is a chance that we could be in a paused state as the ruleset's internal clock (see FrameStabilityContainer)
// could process an extra frame after the GameplayClock is stopped.
// In such cases we want the fail state to precede a user triggered pause.
@ -922,9 +919,9 @@ namespace osu.Game.Screens.Play
#region Screen Logic
public override void OnEntering(IScreen last)
public override void OnEntering(ScreenTransitionEvent e)
{
base.OnEntering(last);
base.OnEntering(e);
if (!LoadedBeatmapSuccessfully)
return;
@ -950,7 +947,7 @@ namespace osu.Game.Screens.Play
failAnimationLayer.Background = b;
});
HUDOverlay.IsBreakTime.BindTo(breakTracker.IsBreakTime);
HUDOverlay.IsPlaying.BindTo(localUserPlaying);
DimmableStoryboard.IsBreakTime.BindTo(breakTracker.IsBreakTime);
DimmableStoryboard.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground);
@ -987,18 +984,18 @@ namespace osu.Game.Screens.Play
if (GameplayClockContainer.GameplayClock.IsRunning)
throw new InvalidOperationException($"{nameof(StartGameplay)} should not be called when the gameplay clock is already running");
GameplayClockContainer.Reset();
GameplayClockContainer.Reset(true);
}
public override void OnSuspending(IScreen next)
public override void OnSuspending(ScreenTransitionEvent e)
{
screenSuspension?.RemoveAndDisposeImmediately();
fadeOut();
base.OnSuspending(next);
base.OnSuspending(e);
}
public override bool OnExiting(IScreen next)
public override bool OnExiting(ScreenExitEvent e)
{
screenSuspension?.RemoveAndDisposeImmediately();
failAnimationLayer?.RemoveFilters();
@ -1029,7 +1026,7 @@ namespace osu.Game.Screens.Play
musicController.ResetTrackAdjustments();
fadeOut();
return base.OnExiting(next);
return base.OnExiting(e);
}
/// <summary>

View File

@ -124,7 +124,7 @@ namespace osu.Game.Screens.Play
private EpilepsyWarning? epilepsyWarning;
[Resolved(CanBeNull = true)]
private NotificationOverlay? notificationOverlay { get; set; }
private INotificationOverlay? notificationOverlay { get; set; }
[Resolved(CanBeNull = true)]
private VolumeOverlay? volumeOverlay { get; set; }
@ -210,9 +210,9 @@ namespace osu.Game.Screens.Play
#region Screen handling
public override void OnEntering(IScreen last)
public override void OnEntering(ScreenTransitionEvent e)
{
base.OnEntering(last);
base.OnEntering(e);
ApplyToBackground(b =>
{
@ -236,9 +236,9 @@ namespace osu.Game.Screens.Play
showBatteryWarningIfNeeded();
}
public override void OnResuming(IScreen last)
public override void OnResuming(ScreenTransitionEvent e)
{
base.OnResuming(last);
base.OnResuming(e);
Debug.Assert(CurrentPlayer != null);
@ -254,9 +254,9 @@ namespace osu.Game.Screens.Play
contentIn();
}
public override void OnSuspending(IScreen next)
public override void OnSuspending(ScreenTransitionEvent e)
{
base.OnSuspending(next);
base.OnSuspending(e);
BackgroundBrightnessReduction = false;
@ -268,7 +268,7 @@ namespace osu.Game.Screens.Play
highPassFilter.CutoffTo(0);
}
public override bool OnExiting(IScreen next)
public override bool OnExiting(ScreenExitEvent e)
{
cancelLoad();
ContentOut();
@ -284,7 +284,7 @@ namespace osu.Game.Screens.Play
BackgroundBrightnessReduction = false;
Beatmap.Value.Track.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment);
return base.OnExiting(next);
return base.OnExiting(e);
}
protected override void LogoArriving(OsuLogo logo, bool resuming)
@ -515,7 +515,7 @@ namespace osu.Game.Screens.Play
}
[BackgroundDependencyLoader]
private void load(OsuColour colours, AudioManager audioManager, NotificationOverlay notificationOverlay, VolumeOverlay volumeOverlay)
private void load(OsuColour colours, AudioManager audioManager, INotificationOverlay notificationOverlay, VolumeOverlay volumeOverlay)
{
Icon = FontAwesome.Solid.VolumeMute;
IconBackground.Colour = colours.RedDark;
@ -567,7 +567,7 @@ namespace osu.Game.Screens.Play
}
[BackgroundDependencyLoader]
private void load(OsuColour colours, NotificationOverlay notificationOverlay)
private void load(OsuColour colours, INotificationOverlay notificationOverlay)
{
Icon = FontAwesome.Solid.BatteryQuarter;
IconBackground.Colour = colours.RedDark;

View File

@ -5,6 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Configuration;
using osu.Game.Localisation;
using osu.Game.Scoring;
namespace osu.Game.Screens.Play.PlayerSettings
@ -20,7 +21,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
{
Children = new Drawable[]
{
beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hitsounds" },
beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = SkinSettingsStrings.BeatmapHitsounds },
new BeatmapOffsetControl
{
ReferenceScore = { BindTarget = ReferenceScore },

View File

@ -5,6 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Configuration;
using osu.Game.Graphics.Sprites;
using osu.Game.Localisation;
namespace osu.Game.Screens.Play.PlayerSettings
{
@ -23,7 +24,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
{
new OsuSpriteText
{
Text = "Background dim:"
Text = GameplaySettingsStrings.BackgroundDim
},
dimSliderBar = new PlayerSliderBar<double>
{
@ -31,7 +32,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
},
new OsuSpriteText
{
Text = "Background blur:"
Text = GameplaySettingsStrings.BackgroundBlur
},
blurSliderBar = new PlayerSliderBar<double>
{
@ -41,9 +42,9 @@ namespace osu.Game.Screens.Play.PlayerSettings
{
Text = "Toggles:"
},
showStoryboardToggle = new PlayerCheckbox { LabelText = "Storyboard / Video" },
beatmapSkinsToggle = new PlayerCheckbox { LabelText = "Beatmap skins" },
beatmapColorsToggle = new PlayerCheckbox { LabelText = "Beatmap colours" },
showStoryboardToggle = new PlayerCheckbox { LabelText = GraphicsSettingsStrings.StoryboardVideo },
beatmapSkinsToggle = new PlayerCheckbox { LabelText = SkinSettingsStrings.BeatmapSkins },
beatmapColorsToggle = new PlayerCheckbox { LabelText = SkinSettingsStrings.BeatmapColours },
};
}

View File

@ -20,13 +20,13 @@ namespace osu.Game.Screens.Play
Score = score.ScoreInfo;
}
public override void OnEntering(IScreen last)
public override void OnEntering(ScreenTransitionEvent e)
{
// these will be reverted thanks to PlayerLoader's lease.
Mods.Value = Score.Mods;
Ruleset.Value = Score.Ruleset;
base.OnEntering(last);
base.OnEntering(e);
}
}
}

View File

@ -249,10 +249,10 @@ namespace osu.Game.Screens.Play
beatmapDownloader.Download(beatmapSet);
}
public override bool OnExiting(IScreen next)
public override bool OnExiting(ScreenExitEvent e)
{
previewTrackManager.StopAnyPlaying(this);
return base.OnExiting(next);
return base.OnExiting(e);
}
}
}

View File

@ -24,11 +24,11 @@ namespace osu.Game.Screens.Play
SpectatorClient.OnUserBeganPlaying += userBeganPlaying;
}
public override bool OnExiting(IScreen next)
public override bool OnExiting(ScreenExitEvent e)
{
SpectatorClient.OnUserBeganPlaying -= userBeganPlaying;
return base.OnExiting(next);
return base.OnExiting(e);
}
private void userBeganPlaying(int userId, SpectatorState state)

View File

@ -78,7 +78,7 @@ namespace osu.Game.Screens.Play
}
if (isFirstBundle && score.Replay.Frames.Count > 0)
NonFrameStableSeek(score.Replay.Frames[0].Time);
SetGameplayStartTime(score.Replay.Frames[0].Time);
}
protected override Score CreateScore(IBeatmap beatmap) => score;
@ -91,11 +91,11 @@ namespace osu.Game.Screens.Play
DrawableRuleset?.SetReplayScore(score);
}
public override bool OnExiting(IScreen next)
public override bool OnExiting(ScreenExitEvent e)
{
SpectatorClient.OnNewFrames -= userSentFrames;
return base.OnExiting(next);
return base.OnExiting(e);
}
protected override void Dispose(bool isDisposing)

View File

@ -20,13 +20,13 @@ namespace osu.Game.Screens.Play
Score = score.ScoreInfo;
}
public override void OnEntering(IScreen last)
public override void OnEntering(ScreenTransitionEvent e)
{
// these will be reverted thanks to PlayerLoader's lease.
Mods.Value = Score.Mods;
Ruleset.Value = Score.Ruleset;
base.OnEntering(last);
base.OnEntering(e);
}
}
}

View File

@ -115,9 +115,9 @@ namespace osu.Game.Screens.Play
await submitScore(score).ConfigureAwait(false);
}
public override bool OnExiting(IScreen next)
public override bool OnExiting(ScreenExitEvent e)
{
bool exiting = base.OnExiting(next);
bool exiting = base.OnExiting(e);
if (LoadedBeatmapSuccessfully)
submitScore(Score.DeepClone());

View File

@ -9,9 +9,11 @@ using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Leaderboards;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Play.HUD;
@ -127,8 +129,8 @@ namespace osu.Game.Screens.Ranking.Contracted
Spacing = new Vector2(0, 5),
Children = new[]
{
createStatistic("Max Combo", $"x{score.MaxCombo}"),
createStatistic("Accuracy", $"{score.Accuracy.FormatAccuracy()}"),
createStatistic(BeatmapsetsStrings.ShowScoreboardHeadersCombo, $"x{score.MaxCombo}"),
createStatistic(BeatmapsetsStrings.ShowScoreboardHeadersAccuracy, $"{score.Accuracy.FormatAccuracy()}"),
}
},
new ModFlowDisplay
@ -200,7 +202,7 @@ namespace osu.Game.Screens.Ranking.Contracted
private Drawable createStatistic(HitResultDisplayStatistic result)
=> createStatistic(result.DisplayName, result.MaxCount == null ? $"{result.Count}" : $"{result.Count}/{result.MaxCount}");
private Drawable createStatistic(string key, string value) => new Container
private Drawable createStatistic(LocalisableString key, string value) => new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,

View File

@ -212,12 +212,12 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
Padding = new MarginPadding { Vertical = -15, Horizontal = -20 },
Children = new[]
{
new RankBadge(1f, getRank(ScoreRank.X)),
new RankBadge(0.95f, getRank(ScoreRank.S)),
new RankBadge(0.9f, getRank(ScoreRank.A)),
new RankBadge(0.8f, getRank(ScoreRank.B)),
new RankBadge(0.7f, getRank(ScoreRank.C)),
new RankBadge(0.35f, getRank(ScoreRank.D)),
new RankBadge(1, getRank(ScoreRank.X)),
new RankBadge(0.95, getRank(ScoreRank.S)),
new RankBadge(0.9, getRank(ScoreRank.A)),
new RankBadge(0.8, getRank(ScoreRank.B)),
new RankBadge(0.7, getRank(ScoreRank.C)),
new RankBadge(0.35, getRank(ScoreRank.D)),
}
},
rankText = new RankText(score.Rank)

View File

@ -23,7 +23,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
/// <summary>
/// The accuracy value corresponding to the <see cref="ScoreRank"/> displayed by this badge.
/// </summary>
public readonly float Accuracy;
public readonly double Accuracy;
private readonly ScoreRank rank;
@ -35,7 +35,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
/// </summary>
/// <param name="accuracy">The accuracy value corresponding to <paramref name="rank"/>.</param>
/// <param name="rank">The <see cref="ScoreRank"/> to be displayed in this <see cref="RankBadge"/>.</param>
public RankBadge(float accuracy, ScoreRank rank)
public RankBadge(double accuracy, ScoreRank rank)
{
Accuracy = accuracy;
this.rank = rank;
@ -90,7 +90,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
base.Update();
// Starts at -90deg (top) and moves counter-clockwise by the accuracy
rankContainer.Position = circlePosition(-MathF.PI / 2 - (1 - Accuracy) * MathF.PI * 2);
rankContainer.Position = circlePosition(-MathF.PI / 2 - (1 - (float)Accuracy) * MathF.PI * 2);
}
private Vector2 circlePosition(float t)

View File

@ -6,6 +6,7 @@ using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Screens.Ranking.Expanded.Accuracy;
using osu.Game.Utils;
using osuTK;
@ -26,7 +27,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics
/// </summary>
/// <param name="accuracy">The accuracy to display.</param>
public AccuracyStatistic(double accuracy)
: base("accuracy")
: base(BeatmapsetsStrings.ShowScoreboardHeadersAccuracy)
{
this.accuracy = accuracy;
}

View File

@ -7,6 +7,7 @@ using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Screens.Ranking.Expanded.Accuracy;
using osuTK;
@ -27,7 +28,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics
/// <param name="combo">The combo to be displayed.</param>
/// <param name="maxCombo">The maximum value of <paramref name="combo"/>.</param>
public ComboStatistic(int combo, int? maxCombo)
: base("combo", combo, maxCombo)
: base(BeatmapsetsStrings.ShowScoreboardHeadersCombo, combo, maxCombo)
{
isPerfect = combo == maxCombo;
}

View File

@ -3,6 +3,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
@ -26,7 +27,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics
/// <param name="header">The name of the statistic.</param>
/// <param name="count">The value to display.</param>
/// <param name="maxCount">The maximum value of <paramref name="count"/>. Not displayed if null.</param>
public CounterStatistic(string header, int count, int? maxCount = null)
public CounterStatistic(LocalisableString header, int count, int? maxCount = null)
: base(header)
{
this.count = count;

View File

@ -8,6 +8,7 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Scoring;
namespace osu.Game.Screens.Ranking.Expanded.Statistics
@ -23,7 +24,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics
private RollingCounter<int> counter;
public PerformanceStatistic(ScoreInfo score)
: base("PP")
: base(BeatmapsetsStrings.ShowScoreboardHeaderspp)
{
this.score = score;
}

View File

@ -3,10 +3,12 @@
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
@ -19,14 +21,14 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics
{
protected SpriteText HeaderText { get; private set; }
private readonly string header;
private readonly LocalisableString header;
private Drawable content;
/// <summary>
/// Creates a new <see cref="StatisticDisplay"/>.
/// </summary>
/// <param name="header">The name of the statistic.</param>
protected StatisticDisplay(string header)
protected StatisticDisplay(LocalisableString header)
{
this.header = header;
RelativeSizeAxes = Axes.X;
@ -60,7 +62,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold),
Text = header.ToUpperInvariant(),
Text = header.ToUpper(),
}
}
},

View File

@ -87,31 +87,33 @@ namespace osu.Game.Screens.Ranking
});
}
button.Enabled.Value = replayAvailability != ReplayAvailability.NotAvailable;
updateTooltip();
updateState();
}, true);
State.BindValueChanged(state =>
{
button.State.Value = state.NewValue;
updateTooltip();
updateState();
}, true);
}
private void updateTooltip()
private void updateState()
{
switch (replayAvailability)
{
case ReplayAvailability.Local:
button.TooltipText = @"watch replay";
button.Enabled.Value = true;
break;
case ReplayAvailability.Online:
button.TooltipText = @"download replay";
button.Enabled.Value = true;
break;
default:
button.TooltipText = @"replay unavailable";
button.Enabled.Value = false;
break;
}
}

View File

@ -231,9 +231,9 @@ namespace osu.Game.Screens.Ranking
lastFetchCompleted = true;
});
public override void OnEntering(IScreen last)
public override void OnEntering(ScreenTransitionEvent e)
{
base.OnEntering(last);
base.OnEntering(e);
ApplyToBackground(b =>
{
@ -244,9 +244,9 @@ namespace osu.Game.Screens.Ranking
bottomPanel.FadeTo(1, 250);
}
public override bool OnExiting(IScreen next)
public override bool OnExiting(ScreenExitEvent e)
{
if (base.OnExiting(next))
if (base.OnExiting(e))
return true;
this.FadeOut(100);

View File

@ -28,25 +28,25 @@ namespace osu.Game.Screens
protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg2");
public override bool OnExiting(IScreen next)
public override bool OnExiting(ScreenExitEvent e)
{
message.TextContainer.MoveTo(new Vector2(DrawSize.X / 16, 0), transition_time, Easing.OutExpo);
this.FadeOut(transition_time, Easing.OutExpo);
return base.OnExiting(next);
return base.OnExiting(e);
}
public override void OnSuspending(IScreen next)
public override void OnSuspending(ScreenTransitionEvent e)
{
base.OnSuspending(next);
base.OnSuspending(e);
message.TextContainer.MoveTo(new Vector2(-(DrawSize.X / 16), 0), transition_time, Easing.OutExpo);
this.FadeOut(transition_time, Easing.OutExpo);
}
public override void OnResuming(IScreen last)
public override void OnResuming(ScreenTransitionEvent e)
{
base.OnResuming(last);
base.OnResuming(e);
message.TextContainer.MoveTo(Vector2.Zero, transition_time, Easing.OutExpo);
this.FadeIn(transition_time, Easing.OutExpo);

View File

@ -26,7 +26,7 @@ namespace osu.Game.Screens.Select
HeaderText = @"Confirm deletion of";
Buttons = new PopupDialogButton[]
{
new PopupDialogOkButton
new PopupDialogDangerousButton
{
Text = @"Yes. Totally. Delete it.",
Action = () => manager?.Delete(beatmap),

View File

@ -16,6 +16,7 @@ using osu.Game.Online;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Overlays.BeatmapSet;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Screens.Select.Details;
using osuTK;
using osuTK.Graphics;
@ -155,7 +156,7 @@ namespace osu.Game.Screens.Select
{
new OsuSpriteText
{
Text = "Points of Failure",
Text = BeatmapsetsStrings.ShowInfoPointsOfFailure,
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14),
},
failRetryGraph = new FailRetryGraph

View File

@ -24,6 +24,7 @@ using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osu.Game.Resources.Localisation.Web;
using osuTK;
using osuTK.Graphics;
@ -136,14 +137,7 @@ namespace osu.Game.Screens.Select.Carousel
},
new OsuSpriteText
{
Text = "mapped by",
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft
},
new OsuSpriteText
{
Text = $"{beatmapInfo.Metadata.Author.Username}",
Font = OsuFont.GetFont(italics: true),
Text = BeatmapsetsStrings.ShowDetailsMappedBy(beatmapInfo.Metadata.Author.Username),
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft
},
@ -235,7 +229,7 @@ namespace osu.Game.Screens.Select.Carousel
items.Add(new OsuMenuItem("Play", MenuItemType.Highlighted, () => startRequested(beatmapInfo)));
if (editRequested != null)
items.Add(new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested(beatmapInfo)));
items.Add(new OsuMenuItem(CommonStrings.ButtonsEdit, MenuItemType.Standard, () => editRequested(beatmapInfo)));
if (beatmapInfo.OnlineID > 0 && beatmapOverlay != null)
items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmapInfo.OnlineID)));

View File

@ -28,7 +28,7 @@ namespace osu.Game.Screens.Select.Carousel
private Action<int> viewDetails;
[Resolved(CanBeNull = true)]
private DialogOverlay dialogOverlay { get; set; }
private IDialogOverlay dialogOverlay { get; set; }
[Resolved(CanBeNull = true)]
private CollectionManager collectionManager { get; set; }

View File

@ -21,6 +21,7 @@ using osu.Framework.Localisation;
using osu.Framework.Threading;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets;
namespace osu.Game.Screens.Select.Details
@ -63,10 +64,10 @@ namespace osu.Game.Screens.Select.Details
Children = new[]
{
FirstValue = new StatisticRow(), // circle size/key amount
HpDrain = new StatisticRow { Title = "HP Drain" },
Accuracy = new StatisticRow { Title = "Accuracy" },
ApproachRate = new StatisticRow { Title = "Approach Rate" },
starDifficulty = new StatisticRow(10, true) { Title = "Star Difficulty" },
HpDrain = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsDrain },
Accuracy = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsAccuracy },
ApproachRate = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsAr },
starDifficulty = new StatisticRow(10, true) { Title = BeatmapsetsStrings.ShowStatsStars },
},
};
}
@ -120,12 +121,12 @@ namespace osu.Game.Screens.Select.Details
case 3:
// Account for mania differences locally for now
// Eventually this should be handled in a more modular way, allowing rulesets to return arbitrary difficulty attributes
FirstValue.Title = "Key Count";
FirstValue.Title = BeatmapsetsStrings.ShowStatsCsMania;
FirstValue.Value = (baseDifficulty?.CircleSize ?? 0, null);
break;
default:
FirstValue.Title = "Circle Size";
FirstValue.Title = BeatmapsetsStrings.ShowStatsCs;
FirstValue.Value = (baseDifficulty?.CircleSize ?? 0, adjustedDifficulty?.CircleSize);
break;
}

View File

@ -2,36 +2,38 @@
// See the LICENCE file in the repository root for full licence text.
using System.ComponentModel;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Screens.Select.Filter
{
public enum SortMode
{
[Description("Artist")]
[LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchSortingArtist))]
Artist,
[Description("Author")]
Author,
[Description("BPM")]
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowStatsBpm))]
BPM,
[Description("Date Added")]
DateAdded,
[Description("Difficulty")]
[LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchSortingDifficulty))]
Difficulty,
[Description("Length")]
[LocalisableDescription(typeof(SortStrings), nameof(SortStrings.ArtistTracksLength))]
Length,
[Description("Rank Achieved")]
[LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchFiltersRank))]
RankAchieved,
[Description("Source")]
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowInfoSource))]
Source,
[Description("Title")]
[LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchSortingTitle))]
Title,
}
}

View File

@ -14,6 +14,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets;
using osu.Game.Screens.Select.Filter;
using osuTK;
@ -139,7 +140,7 @@ namespace osu.Game.Screens.Select
},
new OsuSpriteText
{
Text = "Sort by",
Text = SortStrings.Default,
Font = OsuFont.GetFont(size: 14),
Margin = new MarginPadding(5),
Anchor = Anchor.BottomRight,

View File

@ -174,7 +174,7 @@ namespace osu.Game.Screens.Select
public virtual bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{
if (e.Action == Hotkey)
if (e.Action == Hotkey && !e.Repeat)
{
TriggerClick();
return true;

View File

@ -38,7 +38,7 @@ namespace osu.Game.Screens.Select
HeaderText = "Confirm deletion of local score";
Buttons = new PopupDialogButton[]
{
new PopupDialogOkButton
new PopupDialogDangerousButton
{
Text = "Yes. Please.",
Action = () => scoreManager?.Delete(score)

View File

@ -13,6 +13,7 @@ using osuTK.Input;
using osu.Game.Graphics.Containers;
using osu.Framework.Input.Events;
using System.Linq;
using osu.Framework.Localisation;
namespace osu.Game.Screens.Select.Options
{
@ -63,7 +64,7 @@ namespace osu.Game.Screens.Select.Options
/// <param name="colour">Colour of the button.</param>
/// <param name="icon">Icon of the button.</param>
/// <param name="action">Binding the button does.</param>
public void AddButton(string firstLine, string secondLine, IconUsage icon, Color4 colour, Action action)
public void AddButton(LocalisableString firstLine, string secondLine, IconUsage icon, Color4 colour, Action action)
{
var button = new BeatmapOptionsButton
{

View File

@ -25,7 +25,7 @@ namespace osu.Game.Screens.Select
private OsuScreen playerLoader;
[Resolved(CanBeNull = true)]
private NotificationOverlay notifications { get; set; }
private INotificationOverlay notifications { get; set; }
public override bool AllowExternalScreenChange => true;
@ -109,9 +109,9 @@ namespace osu.Game.Screens.Select
}
}
public override void OnResuming(IScreen last)
public override void OnResuming(ScreenTransitionEvent e)
{
base.OnResuming(last);
base.OnResuming(e);
if (playerLoader != null)
{

View File

@ -50,6 +50,12 @@ namespace osu.Game.Screens.Select
public FilterControl FilterControl { get; private set; }
/// <summary>
/// Whether this song select instance should take control of the global track,
/// applying looping and preview offsets.
/// </summary>
protected virtual bool ControlGlobalMusic => true;
protected virtual bool ShowFooter => true;
protected virtual bool DisplayStableImportPrompt => legacyImportManager?.SupportsImportFromStable == true;
@ -87,7 +93,7 @@ namespace osu.Game.Screens.Select
protected Container LeftArea { get; private set; }
private BeatmapInfoWedge beatmapInfoWedge;
private DialogOverlay dialogOverlay;
private IDialogOverlay dialogOverlay;
[Resolved]
private BeatmapManager beatmaps { get; set; }
@ -114,7 +120,7 @@ namespace osu.Game.Screens.Select
private MusicController music { get; set; }
[BackgroundDependencyLoader(true)]
private void load(AudioManager audio, DialogOverlay dialog, OsuColour colours, ManageCollectionsDialog manageCollectionsDialog, DifficultyRecommender recommender)
private void load(AudioManager audio, IDialogOverlay dialog, OsuColour colours, ManageCollectionsDialog manageCollectionsDialog, DifficultyRecommender recommender)
{
// initial value transfer is required for FilterControl (it uses our re-cached bindables in its async load for the initial filter).
transferRulesetValue();
@ -543,9 +549,9 @@ namespace osu.Game.Screens.Select
}
}
public override void OnEntering(IScreen last)
public override void OnEntering(ScreenTransitionEvent e)
{
base.OnEntering(last);
base.OnEntering(e);
this.FadeInFromZero(250);
FilterControl.Activate();
@ -591,9 +597,9 @@ namespace osu.Game.Screens.Select
logo.FadeOut(logo_transition / 2, Easing.Out);
}
public override void OnResuming(IScreen last)
public override void OnResuming(ScreenTransitionEvent e)
{
base.OnResuming(last);
base.OnResuming(e);
// required due to https://github.com/ppy/osu-framework/issues/3218
ModSelect.SelectedMods.Disabled = false;
@ -604,15 +610,18 @@ namespace osu.Game.Screens.Select
BeatmapDetails.Refresh();
beginLooping();
music.ResetTrackAdjustments();
if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending)
{
updateComponentFromBeatmap(Beatmap.Value);
// restart playback on returning to song select, regardless.
// not sure this should be a permanent thing (we may want to leave a user pause paused even on returning)
music.Play(requestedByUser: true);
if (ControlGlobalMusic)
{
// restart playback on returning to song select, regardless.
// not sure this should be a permanent thing (we may want to leave a user pause paused even on returning)
music.ResetTrackAdjustments();
music.Play(requestedByUser: true);
}
}
this.FadeIn(250);
@ -622,7 +631,7 @@ namespace osu.Game.Screens.Select
FilterControl.Activate();
}
public override void OnSuspending(IScreen next)
public override void OnSuspending(ScreenTransitionEvent e)
{
// Handle the case where FinaliseSelection is never called (ie. when a screen is pushed externally).
// Without this, it's possible for a transfer to happen while we are not the current screen.
@ -640,12 +649,12 @@ namespace osu.Game.Screens.Select
this.FadeOut(250);
FilterControl.Deactivate();
base.OnSuspending(next);
base.OnSuspending(e);
}
public override bool OnExiting(IScreen next)
public override bool OnExiting(ScreenExitEvent e)
{
if (base.OnExiting(next))
if (base.OnExiting(e))
return true;
beatmapInfoWedge.Hide();
@ -663,6 +672,9 @@ namespace osu.Game.Screens.Select
private void beginLooping()
{
if (!ControlGlobalMusic)
return;
Debug.Assert(!isHandlingLooping);
isHandlingLooping = true;
@ -733,6 +745,9 @@ namespace osu.Game.Screens.Select
/// </summary>
private void ensurePlayingSelected()
{
if (!ControlGlobalMusic)
return;
ITrack track = music.CurrentTrack;
bool isNewTrack = !lastTrack.TryGetTarget(out var last) || last != track;