1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-19 21:01:01 +08:00

Compare commits

...

44 Commits

28 changed files with 565 additions and 108 deletions
@@ -25,6 +25,7 @@ using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Taiko;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osu.Game.Screens.Edit.Setup;
using osu.Game.Storyboards;
using osu.Game.Tests.Resources;
@@ -94,8 +95,11 @@ namespace osu.Game.Tests.Visual.Editing
[Test]
public void TestAddAudioTrack()
{
AddAssert("track is virtual", () => Beatmap.Value.Track is TrackVirtual);
AddStep("enter compose mode", () => InputManager.Key(Key.F1));
AddUntilStep("wait for timeline load", () => Editor.ChildrenOfType<Timeline>().FirstOrDefault()?.IsLoaded == true);
AddStep("enter setup mode", () => InputManager.Key(Key.F4));
AddAssert("track is virtual", () => Beatmap.Value.Track is TrackVirtual);
AddAssert("switch track to real track", () =>
{
var setup = Editor.ChildrenOfType<SetupScreen>().First();
@@ -12,6 +12,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Scoring;
using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Tests.Resources;
using osu.Game.Tests.Visual.Ranking;
namespace osu.Game.Tests.Visual.Gameplay
@@ -44,7 +45,23 @@ namespace osu.Game.Tests.Visual.Gameplay
{
offsetControl.ReferenceScore.Value = new ScoreInfo
{
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(0, 2)
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(0, 2),
BeatmapInfo = Beatmap.Value.BeatmapInfo,
};
});
AddAssert("No calibration button", () => !offsetControl.ChildrenOfType<SettingsButton>().Any());
}
[Test]
public void TestScoreFromDifferentBeatmap()
{
AddStep("Set short reference score", () =>
{
offsetControl.ReferenceScore.Value = new ScoreInfo
{
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(10),
BeatmapInfo = TestResources.CreateTestBeatmapSetInfo().Beatmaps.First(),
};
});
@@ -59,7 +76,8 @@ namespace osu.Game.Tests.Visual.Gameplay
offsetControl.ReferenceScore.Value = new ScoreInfo
{
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(10),
Mods = new Mod[] { new OsuModRelax() }
Mods = new Mod[] { new OsuModRelax() },
BeatmapInfo = Beatmap.Value.BeatmapInfo,
};
});
@@ -77,7 +95,8 @@ namespace osu.Game.Tests.Visual.Gameplay
{
offsetControl.ReferenceScore.Value = new ScoreInfo
{
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error)
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error),
BeatmapInfo = Beatmap.Value.BeatmapInfo,
};
});
@@ -105,7 +124,8 @@ namespace osu.Game.Tests.Visual.Gameplay
{
offsetControl.ReferenceScore.Value = new ScoreInfo
{
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error)
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error),
BeatmapInfo = Beatmap.Value.BeatmapInfo,
};
});
@@ -3,28 +3,40 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Screens.Menu;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Menus
{
public partial class TestSceneMainMenu : OsuGameTestScene
{
private SystemTitle systemTitle => Game.ChildrenOfType<SystemTitle>().Single();
[Test]
public void TestSystemTitle()
{
AddStep("set system title", () => Game.ChildrenOfType<SystemTitle>().Single().Current.Value = new APISystemTitle
AddStep("set system title", () => systemTitle.Current.Value = new APISystemTitle
{
Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png",
Url = @"https://osu.ppy.sh/home/news/2023-12-21-project-loved-december-2023",
});
AddStep("set another title", () => Game.ChildrenOfType<SystemTitle>().Single().Current.Value = new APISystemTitle
AddAssert("system title not visible", () => systemTitle.State.Value, () => Is.EqualTo(Visibility.Hidden));
AddStep("enter menu", () => InputManager.Key(Key.Enter));
AddUntilStep("system title visible", () => systemTitle.State.Value, () => Is.EqualTo(Visibility.Visible));
AddStep("set another title", () => systemTitle.Current.Value = new APISystemTitle
{
Image = @"https://assets.ppy.sh/main-menu/wf2023-vote@2x.png",
Url = @"https://osu.ppy.sh/community/contests/189",
});
AddStep("unset system title", () => Game.ChildrenOfType<SystemTitle>().Single().Current.Value = null);
AddStep("set title with nonexistent image", () => systemTitle.Current.Value = new APISystemTitle
{
Image = @"https://test.invalid/@2x", // .invalid TLD reserved by https://datatracker.ietf.org/doc/html/rfc2606#section-2
Url = @"https://osu.ppy.sh/community/contests/189",
});
AddStep("unset system title", () => systemTitle.Current.Value = null);
}
}
}
@@ -0,0 +1,63 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Overlays.Settings.Sections.Audio;
using osu.Game.Scoring;
using osu.Game.Tests.Visual.Ranking;
namespace osu.Game.Tests.Visual.Settings
{
public partial class TestSceneAudioOffsetAdjustControl : OsuTestScene
{
[Resolved]
private SessionStatics statics { get; set; } = null!;
[Cached]
private SessionAverageHitErrorTracker tracker = new SessionAverageHitErrorTracker();
private Container content = null!;
protected override Container Content => content;
[BackgroundDependencyLoader]
private void load()
{
base.Content.AddRange(new Drawable[]
{
tracker,
content = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 400,
AutoSizeAxes = Axes.Y
}
});
}
[Test]
public void TestBehaviour()
{
AddStep("create control", () => Child = new AudioOffsetAdjustControl
{
Current = new BindableDouble
{
MinValue = -500,
MaxValue = 500
}
});
AddStep("set new score", () => statics.SetValue(Static.LastLocalUserScore, new ScoreInfo
{
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(RNG.NextDouble(-100, 100)),
BeatmapInfo = Beatmap.Value.BeatmapInfo,
}));
AddStep("clear history", () => tracker.ClearHistory());
}
}
}
@@ -9,6 +9,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using osu.Game.Overlays;
using osu.Game.Overlays.Dialog;
@@ -19,11 +20,15 @@ namespace osu.Game.Tests.Visual.UserInterface
{
private DialogOverlay overlay;
[SetUpSteps]
public void SetUpSteps()
{
AddStep("create dialog overlay", () => Child = overlay = new DialogOverlay());
}
[Test]
public void TestBasic()
{
AddStep("create dialog overlay", () => Child = overlay = new DialogOverlay());
TestPopupDialog firstDialog = null;
TestPopupDialog secondDialog = null;
@@ -84,7 +89,31 @@ namespace osu.Game.Tests.Visual.UserInterface
}));
AddAssert("second dialog displayed", () => overlay.CurrentDialog == secondDialog);
AddAssert("first dialog is not part of hierarchy", () => firstDialog.Parent == null);
AddUntilStep("first dialog is not part of hierarchy", () => firstDialog.Parent == null);
}
[Test]
public void TestTooMuchText()
{
AddStep("dialog #1", () => overlay.Push(new TestPopupDialog
{
Icon = FontAwesome.Regular.TrashAlt,
HeaderText = @"Confirm deletion ofConfirm deletion ofConfirm deletion ofConfirm deletion ofConfirm deletion ofConfirm deletion of",
BodyText = @"Ayase Rie - Yuima-ru*World TVver.Ayase Rie - Yuima-ru*World TVver.Ayase Rie - Yuima-ru*World TVver.Ayase Rie - Yuima-ru*World TVver.Ayase Rie - Yuima-ru*World TVver.Ayase Rie - Yuima-ru*World TVver.Ayase Rie - Yuima-ru*World TVver.Ayase Rie - Yuima-ru*World TVver.Ayase Rie - Yuima-ru*World TVver. ",
Buttons = new PopupDialogButton[]
{
new PopupDialogOkButton
{
Text = @"I never want to see this again.",
Action = () => Console.WriteLine(@"OK"),
},
new PopupDialogCancelButton
{
Text = @"Firetruck, I still want quick ranks!",
Action = () => Console.WriteLine(@"Cancel"),
},
},
}));
}
[Test]
@@ -92,7 +121,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{
PopupDialog dialog = null;
AddStep("create dialog overlay", () => overlay = new SlowLoadingDialogOverlay());
AddStep("create slow loading dialog overlay", () => overlay = new SlowLoadingDialogOverlay());
AddStep("start loading overlay", () => LoadComponentAsync(overlay, Add));
@@ -128,8 +157,6 @@ namespace osu.Game.Tests.Visual.UserInterface
[Test]
public void TestDismissBeforePush()
{
AddStep("create dialog overlay", () => Child = overlay = new DialogOverlay());
TestPopupDialog testDialog = null;
AddStep("dismissed dialog push", () =>
{
@@ -146,8 +173,6 @@ namespace osu.Game.Tests.Visual.UserInterface
[Test]
public void TestDismissBeforePushViaButtonPress()
{
AddStep("create dialog overlay", () => Child = overlay = new DialogOverlay());
TestPopupDialog testDialog = null;
AddStep("dismissed dialog push", () =>
{
@@ -163,7 +188,7 @@ namespace osu.Game.Tests.Visual.UserInterface
});
AddAssert("no dialog pushed", () => overlay.CurrentDialog == null);
AddAssert("dialog is not part of hierarchy", () => testDialog.Parent == null);
AddUntilStep("dialog is not part of hierarchy", () => testDialog.Parent == null);
}
private partial class TestPopupDialog : PopupDialog
@@ -1,10 +1,7 @@
// 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.
#nullable disable
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using osu.Game.Overlays.Dialog;
@@ -15,24 +12,25 @@ namespace osu.Game.Tests.Visual.UserInterface
{
public partial class TestScenePopupDialog : OsuManualInputManagerTestScene
{
private TestPopupDialog dialog;
private TestPopupDialog dialog = null!;
[SetUpSteps]
public void SetUpSteps()
{
AddStep("new popup", () =>
{
Add(dialog = new TestPopupDialog
Child = dialog = new TestPopupDialog
{
RelativeSizeAxes = Axes.Both,
State = { Value = Framework.Graphics.Containers.Visibility.Visible },
});
};
});
}
[Test]
public void TestDangerousButton([Values(false, true)] bool atEdge)
{
AddStep("finish transforms", () => dialog.FinishTransforms(true));
if (atEdge)
{
AddStep("move mouse to button edge", () =>
@@ -0,0 +1,73 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
namespace osu.Game.Configuration
{
/// <summary>
/// Tracks the local user's average hit error during the ongoing play session.
/// </summary>
[Cached]
public partial class SessionAverageHitErrorTracker : Component
{
public IBindableList<DataPoint> AverageHitErrorHistory => averageHitErrorHistory;
private readonly BindableList<DataPoint> averageHitErrorHistory = new BindableList<DataPoint>();
private readonly Bindable<ScoreInfo?> latestScore = new Bindable<ScoreInfo?>();
[Resolved]
private OsuConfigManager configManager { get; set; } = null!;
[BackgroundDependencyLoader]
private void load(SessionStatics statics)
{
statics.BindWith(Static.LastLocalUserScore, latestScore);
latestScore.BindValueChanged(score => calculateAverageHitError(score.NewValue), true);
}
private void calculateAverageHitError(ScoreInfo? newScore)
{
if (newScore == null)
return;
if (newScore.Mods.Any(m => !m.UserPlayable || m is IHasNoTimedInputs))
return;
if (newScore.HitEvents.Count < 10)
return;
if (newScore.HitEvents.CalculateAverageHitError() is not double averageError)
return;
// keep a sane maximum number of entries.
if (averageHitErrorHistory.Count >= 50)
averageHitErrorHistory.RemoveAt(0);
double globalOffset = configManager.Get<double>(OsuSetting.AudioOffset);
averageHitErrorHistory.Add(new DataPoint(averageError, globalOffset));
}
public void ClearHistory() => averageHitErrorHistory.Clear();
public readonly struct DataPoint
{
public double AverageHitError { get; }
public double GlobalAudioOffset { get; }
public double SuggestedGlobalAudioOffset => GlobalAudioOffset - AverageHitError;
public DataPoint(double averageHitError, double globalOffset)
{
AverageHitError = averageHitError;
GlobalAudioOffset = globalOffset;
}
}
}
}
+7
View File
@@ -9,6 +9,7 @@ using osu.Game.Input;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Scoring;
namespace osu.Game.Configuration
{
@@ -27,6 +28,7 @@ namespace osu.Game.Configuration
SetDefault(Static.LastModSelectPanelSamplePlaybackTime, (double?)null);
SetDefault<APISeasonalBackgrounds>(Static.SeasonalBackgrounds, null);
SetDefault(Static.TouchInputActive, RuntimeInfo.IsMobile);
SetDefault<ScoreInfo>(Static.LastLocalUserScore, null);
}
/// <summary>
@@ -73,5 +75,10 @@ namespace osu.Game.Configuration
/// Used in touchscreen detection scenarios (<see cref="TouchInputInterceptor"/>).
/// </summary>
TouchInputActive,
/// <summary>
/// Stores the local user's last score (can be completed or aborted).
/// </summary>
LastLocalUserScore,
}
}
@@ -25,7 +25,7 @@ namespace osu.Game.Graphics.UserInterface
private const float idle_width = 0.8f;
private const float hover_width = 0.9f;
private const float hover_duration = 500;
private const float hover_duration = 300;
private const float click_duration = 200;
public event Action<SelectionState>? StateChanged;
@@ -54,7 +54,7 @@ namespace osu.Game.Graphics.UserInterface
private readonly Box rightGlow;
private readonly Box background;
private readonly SpriteText spriteText;
private Vector2 hoverSpacing => new Vector2(3f, 0f);
private Vector2 hoverSpacing => new Vector2(1.4f, 0f);
public DialogButton(HoverSampleSet sampleSet = HoverSampleSet.Button)
: base(sampleSet)
@@ -279,15 +279,15 @@ namespace osu.Game.Graphics.UserInterface
if (newState == SelectionState.Selected)
{
spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic);
ColourContainer.ResizeWidthTo(hover_width, hover_duration, Easing.OutElastic);
spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutQuint);
ColourContainer.ResizeWidthTo(hover_width, hover_duration, Easing.OutQuint);
glowContainer.FadeIn(hover_duration, Easing.OutQuint);
}
else
{
ColourContainer.ResizeWidthTo(idle_width, hover_duration, Easing.OutElastic);
spriteText.TransformSpacingTo(Vector2.Zero, hover_duration, Easing.OutElastic);
glowContainer.FadeOut(hover_duration, Easing.OutQuint);
ColourContainer.ResizeWidthTo(idle_width, hover_duration / 2, Easing.OutQuint);
spriteText.TransformSpacingTo(Vector2.Zero, hover_duration / 2, Easing.OutQuint);
glowContainer.FadeOut(hover_duration / 2, Easing.OutQuint);
}
}
@@ -363,6 +363,7 @@ namespace osu.Game.Graphics.UserInterface
base.LoadComplete();
SearchBar.State.ValueChanged += _ => updateColour();
Enabled.BindValueChanged(_ => updateColour());
updateColour();
}
@@ -383,6 +384,9 @@ namespace osu.Game.Graphics.UserInterface
var hoveredColour = colourProvider?.Light4 ?? colours.PinkDarker;
var unhoveredColour = colourProvider?.Background5 ?? Color4.Black.Opacity(0.5f);
Colour = Color4.White;
Alpha = Enabled.Value ? 1 : 0.3f;
if (SearchBar.State.Value == Visibility.Visible)
{
Icon.Colour = hovered ? hoveredColour.Lighten(0.5f) : Colour4.White;
+1 -1
View File
@@ -994,7 +994,7 @@ namespace osu.Game
Margin = new MarginPadding(5),
}, topMostOverlayContent.Add);
if (!args?.Any(a => a == @"--no-version-overlay") ?? true)
if (!IsDeployedBuild)
{
dependencies.Cache(versionManager = new VersionManager { Depth = int.MinValue });
loadComponentSingleFile(versionManager, ScreenContainer.Add);
+4
View File
@@ -200,6 +200,8 @@ namespace osu.Game
private RulesetConfigCache rulesetConfigCache;
private SessionAverageHitErrorTracker hitErrorTracker;
protected SpectatorClient SpectatorClient { get; private set; }
protected MultiplayerClient MultiplayerClient { get; private set; }
@@ -349,6 +351,7 @@ namespace osu.Game
dependencies.CacheAs(powerStatus);
dependencies.Cache(SessionStatics = new SessionStatics());
dependencies.Cache(hitErrorTracker = new SessionAverageHitErrorTracker());
dependencies.Cache(Colours = new OsuColour());
RegisterImportHandler(BeatmapManager);
@@ -408,6 +411,7 @@ namespace osu.Game
});
base.Content.Add(new TouchInputInterceptor());
base.Content.Add(hitErrorTracker);
KeyBindingStore = new RealmKeyBindingStore(realm, keyCombinationProvider);
KeyBindingStore.Register(globalBindings, RulesetStore.AvailableRulesets);
+46 -33
View File
@@ -14,6 +14,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Containers;
using osuTK;
@@ -25,11 +26,10 @@ namespace osu.Game.Overlays.Dialog
public abstract partial class PopupDialog : VisibilityContainer
{
public const float ENTER_DURATION = 500;
public const float EXIT_DURATION = 200;
public const float EXIT_DURATION = 500;
private readonly Vector2 ringSize = new Vector2(100f);
private readonly Vector2 ringMinifiedSize = new Vector2(20f);
private readonly Vector2 buttonsEnterSpacing = new Vector2(0f, 50f);
private readonly Box flashLayer;
private Sample flashSample = null!;
@@ -108,13 +108,20 @@ namespace osu.Game.Overlays.Dialog
protected PopupDialog()
{
RelativeSizeAxes = Axes.Both;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
Children = new Drawable[]
{
content = new Container
{
RelativeSizeAxes = Axes.Both,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0f,
Children = new Drawable[]
{
@@ -122,11 +129,13 @@ namespace osu.Game.Overlays.Dialog
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = 20,
CornerExponent = 2.5f,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.5f),
Radius = 8,
Colour = Color4.Black.Opacity(0.2f),
Radius = 14,
},
Children = new Drawable[]
{
@@ -142,23 +151,29 @@ namespace osu.Game.Overlays.Dialog
ColourDark = Color4Extensions.FromHex(@"1e171e"),
TriangleScale = 4,
},
flashLayer = new Box
{
Alpha = 0,
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive,
Colour = Color4Extensions.FromHex(@"221a21"),
},
},
},
new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0f, 10f),
Padding = new MarginPadding { Bottom = 10 },
Padding = new MarginPadding { Vertical = 60 },
Children = new Drawable[]
{
new Container
{
Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
Padding = new MarginPadding { Bottom = 30 },
Size = ringSize,
Children = new Drawable[]
{
@@ -181,6 +196,7 @@ namespace osu.Game.Overlays.Dialog
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Icon = FontAwesome.Solid.TimesCircle,
Y = -2,
Size = new Vector2(50),
},
},
@@ -194,6 +210,7 @@ namespace osu.Game.Overlays.Dialog
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
TextAnchor = Anchor.TopCentre,
Padding = new MarginPadding { Horizontal = 5 },
},
body = new OsuTextFlowContainer(t => t.Font = t.Font.With(size: 18))
{
@@ -202,25 +219,19 @@ namespace osu.Game.Overlays.Dialog
TextAnchor = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding(5),
Padding = new MarginPadding { Horizontal = 5 },
},
buttonsContainer = new FillFlowContainer<PopupDialogButton>
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Padding = new MarginPadding { Top = 30 },
},
},
},
buttonsContainer = new FillFlowContainer<PopupDialogButton>
{
Anchor = Anchor.Centre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
},
flashLayer = new Box
{
Alpha = 0,
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive,
Colour = Color4Extensions.FromHex(@"221a21"),
},
},
},
};
@@ -231,7 +242,7 @@ namespace osu.Game.Overlays.Dialog
}
[BackgroundDependencyLoader]
private void load(AudioManager audio)
private void load(AudioManager audio, OsuColour colours)
{
flashSample = audio.Samples.Get(@"UI/default-select-disabled");
}
@@ -288,15 +299,15 @@ namespace osu.Game.Overlays.Dialog
// Reset various animations but only if the dialog animation fully completed
if (content.Alpha == 0)
{
buttonsContainer.TransformSpacingTo(buttonsEnterSpacing);
buttonsContainer.MoveToY(buttonsEnterSpacing.Y);
content.ScaleTo(0.7f);
ring.ResizeTo(ringMinifiedSize);
}
content.FadeIn(ENTER_DURATION, Easing.OutQuint);
ring.ResizeTo(ringSize, ENTER_DURATION, Easing.OutQuint);
buttonsContainer.TransformSpacingTo(Vector2.Zero, ENTER_DURATION, Easing.OutQuint);
buttonsContainer.MoveToY(0, ENTER_DURATION, Easing.OutQuint);
content
.ScaleTo(1, 750, Easing.OutElasticHalf)
.FadeIn(ENTER_DURATION, Easing.OutQuint);
ring.ResizeTo(ringSize, ENTER_DURATION * 1.5f, Easing.OutQuint);
}
protected override void PopOut()
@@ -306,7 +317,9 @@ namespace osu.Game.Overlays.Dialog
// This is presumed to always be a sane default "cancel" action.
buttonsContainer.Last().TriggerClick();
content.FadeOut(EXIT_DURATION, Easing.InSine);
content
.ScaleTo(0.7f, EXIT_DURATION, Easing.Out)
.FadeOut(EXIT_DURATION, Easing.OutQuint);
}
private void pressButtonAtIndex(int index)
+7 -5
View File
@@ -29,16 +29,18 @@ namespace osu.Game.Overlays
public DialogOverlay()
{
RelativeSizeAxes = Axes.Both;
AutoSizeAxes = Axes.Y;
Child = dialogContainer = new Container
{
RelativeSizeAxes = Axes.Both,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
};
Width = 0.4f;
Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre;
Width = 500;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
}
[BackgroundDependencyLoader]
@@ -0,0 +1,160 @@
// 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.Specialized;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Localisation;
using osuTK;
namespace osu.Game.Overlays.Settings.Sections.Audio
{
public partial class AudioOffsetAdjustControl : SettingsItem<double>
{
[BackgroundDependencyLoader]
private void load()
{
LabelText = AudioSettingsStrings.AudioOffset;
}
protected override Drawable CreateControl() => new AudioOffsetPreview();
private partial class AudioOffsetPreview : CompositeDrawable, IHasCurrentValue<double>
{
public Bindable<double> Current
{
get => current.Current;
set => current.Current = value;
}
private readonly BindableNumberWithCurrent<double> current = new BindableNumberWithCurrent<double>();
private readonly IBindableList<SessionAverageHitErrorTracker.DataPoint> averageHitErrorHistory = new BindableList<SessionAverageHitErrorTracker.DataPoint>();
private readonly Bindable<double?> suggestedOffset = new Bindable<double?>();
private Container<Box> notchContainer = null!;
private TextFlowContainer hintText = null!;
private RoundedButton applySuggestion = null!;
[BackgroundDependencyLoader]
private void load(SessionAverageHitErrorTracker hitErrorTracker)
{
averageHitErrorHistory.BindTo(hitErrorTracker.AverageHitErrorHistory);
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChild = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(10),
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new TimeSlider
{
RelativeSizeAxes = Axes.X,
Current = { BindTarget = Current },
KeyboardStep = 1,
},
notchContainer = new Container<Box>
{
RelativeSizeAxes = Axes.X,
Height = 10,
Padding = new MarginPadding { Horizontal = Nub.DEFAULT_EXPANDED_SIZE / 2 },
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
},
hintText = new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(size: 16))
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
},
applySuggestion = new RoundedButton
{
RelativeSizeAxes = Axes.X,
Text = "Apply suggested offset",
Action = () =>
{
if (suggestedOffset.Value.HasValue)
current.Value = suggestedOffset.Value.Value;
hitErrorTracker.ClearHistory();
}
}
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
averageHitErrorHistory.BindCollectionChanged(updateDisplay, true);
suggestedOffset.BindValueChanged(_ => updateHintText(), true);
}
private void updateDisplay(object? _, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (SessionAverageHitErrorTracker.DataPoint dataPoint in e.NewItems!)
{
notchContainer.ForEach(n => n.Alpha *= 0.95f);
notchContainer.Add(new Box
{
RelativeSizeAxes = Axes.Y,
Width = 2,
RelativePositionAxes = Axes.X,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
X = getXPositionForOffset(dataPoint.SuggestedGlobalAudioOffset)
});
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (SessionAverageHitErrorTracker.DataPoint dataPoint in e.OldItems!)
{
var notch = notchContainer.FirstOrDefault(n => n.X == getXPositionForOffset(dataPoint.SuggestedGlobalAudioOffset));
Debug.Assert(notch != null);
notchContainer.Remove(notch, true);
}
break;
case NotifyCollectionChangedAction.Reset:
notchContainer.Clear();
break;
}
suggestedOffset.Value = averageHitErrorHistory.Any() ? -averageHitErrorHistory.Average(dataPoint => dataPoint.SuggestedGlobalAudioOffset) : null;
}
private float getXPositionForOffset(double offset) => (float)(Math.Clamp(offset, current.MinValue, current.MaxValue) / (2 * current.MaxValue));
private void updateHintText()
{
hintText.Text = suggestedOffset.Value == null
? @"Play a few beatmaps to receive a suggested offset!"
: $@"Based on the last {averageHitErrorHistory.Count} play(s), the suggested offset is {suggestedOffset.Value:N0} ms.";
applySuggestion.Enabled.Value = suggestedOffset.Value != null;
}
}
}
}
@@ -7,7 +7,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Audio
@@ -16,23 +15,17 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
{
protected override LocalisableString Header => AudioSettingsStrings.OffsetHeader;
public override IEnumerable<LocalisableString> FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { "universal", "uo", "timing", "delay", "latency" });
public override IEnumerable<LocalisableString> FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { "universal", "uo", "timing", "delay", "latency", "wizard" });
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
Children = new Drawable[]
{
new SettingsSlider<double, TimeSlider>
new AudioOffsetAdjustControl
{
LabelText = AudioSettingsStrings.AudioOffset,
Current = config.GetBindable<double>(OsuSetting.AudioOffset),
KeyboardStep = 1f
},
new SettingsButton
{
Text = AudioSettingsStrings.OffsetWizard
}
};
}
}
+23 -6
View File
@@ -3,19 +3,21 @@
#nullable disable
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Framework.Testing;
using osu.Game.Graphics;
using osu.Game.Localisation;
using osu.Game.Overlays.Settings;
using osu.Game.Overlays.Settings.Sections;
using osu.Game.Overlays.Settings.Sections.Input;
using osuTK.Graphics;
using System.Collections.Generic;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Localisation;
namespace osu.Game.Overlays
{
@@ -55,6 +57,21 @@ namespace osu.Game.Overlays
public override bool AcceptsFocus => lastOpenedSubPanel == null || lastOpenedSubPanel.State.Value == Visibility.Hidden;
public void ShowAtControl<T>()
where T : Drawable
{
Show();
// wait for load of sections
if (!SectionsContainer.Any())
{
Scheduler.Add(ShowAtControl<T>);
return;
}
SectionsContainer.ScrollTo(SectionsContainer.ChildrenOfType<T>().Single());
}
private T createSubPanel<T>(T subPanel)
where T : SettingsSubPanel
{
@@ -40,8 +40,6 @@ namespace osu.Game.Overlays.Toolbar
[BackgroundDependencyLoader]
private void load(OsuColour colours, IAPIProvider api, LoginOverlay? login)
{
BackgroundContent.Add(new OpaqueBackground { Depth = 1 });
Flow.Add(new Container
{
Masking = true,
+1 -1
View File
@@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Mods
/// <summary>
/// Whether all settings in this mod are set to their default state.
/// </summary>
protected virtual bool UsesDefaultConfiguration => SettingsBindables.All(s => s.IsDefault);
public virtual bool UsesDefaultConfiguration => SettingsBindables.All(s => s.IsDefault);
/// <summary>
/// Creates a copy of this <see cref="Mod"/> initialised to a default state.
+1
View File
@@ -207,6 +207,7 @@ namespace osu.Game.Scoring
clone.Statistics = new Dictionary<HitResult, int>(clone.Statistics);
clone.MaximumStatistics = new Dictionary<HitResult, int>(clone.MaximumStatistics);
clone.HitEvents = new List<HitEvent>(clone.HitEvents);
// Ensure we have fresh mods to avoid any references (ie. after gameplay).
clone.clearAllMods();
@@ -144,13 +144,26 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
track.BindValueChanged(_ =>
{
waveform.Waveform = beatmap.Value.Waveform;
waveform.RelativePositionAxes = Axes.X;
waveform.X = -(float)(Editor.WAVEFORM_VISUAL_OFFSET / beatmap.Value.Track.Length);
Scheduler.AddOnce(applyVisualOffset, beatmap);
}, true);
Zoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom);
}
private void applyVisualOffset(IBindable<WorkingBeatmap> beatmap)
{
waveform.RelativePositionAxes = Axes.X;
if (beatmap.Value.Track.Length > 0)
waveform.X = -(float)(Editor.WAVEFORM_VISUAL_OFFSET / beatmap.Value.Track.Length);
else
{
// sometimes this can be the case immediately after a track switch.
// reschedule with the hope that the track length eventually populates.
Scheduler.AddOnce(applyVisualOffset, beatmap);
}
}
protected override void LoadComplete()
{
base.LoadComplete();
+4 -1
View File
@@ -94,6 +94,7 @@ namespace osu.Game.Screens.Menu
private ParallaxContainer buttonsContainer;
private SongTicker songTicker;
private Container logoTarget;
private SystemTitle systemTitle;
private MenuTip menuTip;
private FillFlowContainer bottomElementsFlow;
private SupporterDisplay supporterDisplay;
@@ -173,7 +174,7 @@ namespace osu.Game.Screens.Menu
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
},
new SystemTitle
systemTitle = new SystemTitle
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
@@ -196,10 +197,12 @@ namespace osu.Game.Screens.Menu
case ButtonSystemState.Initial:
case ButtonSystemState.Exit:
ApplyToBackground(b => b.FadeColour(Color4.White, 500, Easing.OutSine));
systemTitle.State.Value = Visibility.Hidden;
break;
default:
ApplyToBackground(b => b.FadeColour(OsuColour.Gray(0.8f), 500, Easing.OutSine));
systemTitle.State.Value = Visibility.Visible;
break;
}
};
+16 -4
View File
@@ -94,22 +94,34 @@ namespace osu.Game.Screens.Menu
{
string[] tips =
{
"You can press Ctrl-T anywhere in the game to toggle the toolbar!",
"You can press Ctrl-O anywhere in the game to access options!",
"Press Ctrl-T anywhere in the game to toggle the toolbar!",
"Press Ctrl-O anywhere in the game to access options!",
"All settings are dynamic and take effect in real-time. Try changing the skin while watching autoplay!",
"New features are coming online every update. Make sure to stay up-to-date!",
"If you find the UI too large or small, try adjusting UI scale in settings!",
"Try adjusting the \"Screen Scaling\" mode to change your gameplay or UI area, even in fullscreen!",
"What used to be \"osu!direct\" is available to all users just like on the website. You can access it anywhere using Ctrl-B!",
"Seeking in replays is available by dragging on the difficulty bar at the bottom of the screen!",
"Seeking in replays is available by dragging on the progress bar at the bottom of the screen or by using the left and right arrow keys!",
"Multithreading support means that even with low \"FPS\" your input and judgements will be accurate!",
"Try scrolling down in the mod select panel to find a bunch of new fun mods!",
"Try scrolling right in mod select to find a bunch of new fun mods!",
"Most of the web content (profiles, rankings, etc.) are available natively in-game from the icons on the toolbar!",
"Get more details, hide or delete a beatmap by right-clicking on its panel at song select!",
"All delete operations are temporary until exiting. Restore accidentally deleted content from the maintenance settings!",
"Check out the \"playlists\" system, which lets users create their own custom and permanent leaderboards!",
"Toggle advanced frame / thread statistics with Ctrl-F11!",
"Take a look under the hood at performance counters and enable verbose performance logging with Ctrl-F2!",
"You can pause during a replay by pressing Space!",
"Most of the hotkeys in the game are configurable and can be changed to anything you want. Check the bindings panel under input settings!",
"When your gameplay HUD is hidden, you can press and hold Ctrl to view it temporarily!",
"Your gameplay HUD can be customized by using the skin layout editor. Open it at any time via Ctrl-Shift-S!",
"Drag and drop any image into the skin editor to load it in quickly!",
"You can create mod presets to make toggling your favorite mod combinations easier!",
"Many mods have customisation settings that drastically change how they function. Click the Mod Customisation button in mod select to view settings!",
"Press Ctrl-Shift-R to switch to a random skin!",
"Press Ctrl-Shift-F to toggle the FPS Counter. But make sure not to pay too much attention to it!",
"While watching a replay, press Ctrl-H to toggle replay settings!",
"You can easily copy the mods from scores on a leaderboard by right-clicking on them!",
"Ctrl-Enter at song select will start a beatmap in autoplay mode!"
};
return tips[RNG.Next(0, tips.Length)];
+13 -3
View File
@@ -18,10 +18,12 @@ using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Screens.Menu
{
public partial class SystemTitle : CompositeDrawable
public partial class SystemTitle : VisibilityContainer
{
internal Bindable<APISystemTitle?> Current { get; } = new Bindable<APISystemTitle?>();
private const float transition_duration = 500;
private Container content = null!;
private CancellationTokenSource? cancellationTokenSource;
private SystemTitleImage? currentImage;
@@ -32,9 +34,13 @@ namespace osu.Game.Screens.Menu
private void load(OsuGame? game)
{
AutoSizeAxes = Axes.Both;
AutoSizeDuration = transition_duration;
AutoSizeEasing = Easing.OutQuint;
InternalChild = content = new OsuClickableContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Action = () =>
{
@@ -51,6 +57,10 @@ namespace osu.Game.Screens.Menu
};
}
protected override void PopIn() => content.FadeInFromZero(transition_duration, Easing.OutQuint);
protected override void PopOut() => content.FadeOut(transition_duration, Easing.OutQuint);
protected override bool OnHover(HoverEvent e)
{
content.ScaleTo(1.05f, 2000, Easing.OutQuint);
@@ -138,8 +148,8 @@ namespace osu.Game.Screens.Menu
[BackgroundDependencyLoader]
private void load(LargeTextureStore textureStore)
{
var texture = textureStore.Get(SystemTitle.Image);
if (SystemTitle.Image.Contains(@"@2x"))
Texture? texture = textureStore.Get(SystemTitle.Image);
if (texture != null && SystemTitle.Image.Contains(@"@2x"))
texture.ScaleAdjust *= 2;
AutoSizeAxes = Axes.Both;
-4
View File
@@ -263,10 +263,6 @@ namespace osu.Game.Screens.Play
Debug.Assert(CurrentPlayer != null);
var lastScore = CurrentPlayer.Score;
AudioSettings.ReferenceScore.Value = lastScore?.ScoreInfo;
// prepare for a retry.
CurrentPlayer = null;
playerConsumed = false;
@@ -14,7 +14,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
{
public partial class AudioSettings : PlayerSettingsGroup
{
public Bindable<ScoreInfo> ReferenceScore { get; } = new Bindable<ScoreInfo>();
private Bindable<ScoreInfo> referenceScore { get; } = new Bindable<ScoreInfo>();
private readonly PlayerCheckbox beatmapHitsoundsToggle;
@@ -26,15 +26,16 @@ namespace osu.Game.Screens.Play.PlayerSettings
beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = SkinSettingsStrings.BeatmapHitsounds },
new BeatmapOffsetControl
{
ReferenceScore = { BindTarget = ReferenceScore },
ReferenceScore = { BindTarget = referenceScore },
},
};
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
private void load(OsuConfigManager config, SessionStatics statics)
{
beatmapHitsoundsToggle.Current = config.GetBindable<bool>(OsuSetting.BeatmapHitsounds);
statics.BindWith(Static.LastLocalUserScore, referenceScore);
}
}
}
@@ -7,6 +7,7 @@ using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
@@ -20,7 +21,9 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Input.Bindings;
using osu.Game.Localisation;
using osu.Game.Overlays;
using osu.Game.Overlays.Settings;
using osu.Game.Overlays.Settings.Sections.Audio;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
@@ -157,11 +160,11 @@ namespace osu.Game.Screens.Play.PlayerSettings
// Apply to all difficulties in a beatmap set for now (they generally always share timing).
foreach (var b in setInfo.Beatmaps)
{
BeatmapUserSettings settings = b.UserSettings;
BeatmapUserSettings userSettings = b.UserSettings;
double val = Current.Value;
if (settings.Offset != val)
settings.Offset = val;
if (userSettings.Offset != val)
userSettings.Offset = val;
}
});
}
@@ -174,6 +177,9 @@ namespace osu.Game.Screens.Play.PlayerSettings
if (score.NewValue == null)
return;
if (!score.NewValue.BeatmapInfo.AsNonNull().Equals(beatmap.Value.BeatmapInfo))
return;
if (score.NewValue.Mods.Any(m => !m.UserPlayable || m is IHasNoTimedInputs))
return;
@@ -209,6 +215,8 @@ namespace osu.Game.Screens.Play.PlayerSettings
lastPlayAverage = average;
lastPlayBeatmapOffset = Current.Value;
LinkFlowContainer globalOffsetText;
referenceScoreContainer.AddRange(new Drawable[]
{
lastPlayGraph = new HitEventTimingDistributionGraph(hitEvents)
@@ -222,9 +230,24 @@ namespace osu.Game.Screens.Play.PlayerSettings
Text = BeatmapOffsetControlStrings.CalibrateUsingLastPlay,
Action = () => Current.Value = lastPlayBeatmapOffset - lastPlayAverage
},
globalOffsetText = new LinkFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
}
});
if (settings != null)
{
globalOffsetText.AddText("You can also ");
globalOffsetText.AddLink("adjust the global offset", () => settings.ShowAtControl<AudioOffsetAdjustControl>());
globalOffsetText.AddText(" based off this play.");
}
}
[Resolved]
private SettingsOverlay? settings { get; set; }
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
@@ -11,6 +11,7 @@ using osu.Framework.Allocation;
using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Online.API;
using osu.Game.Online.Multiplayer;
@@ -37,6 +38,9 @@ namespace osu.Game.Screens.Play
[Resolved]
private SpectatorClient spectatorClient { get; set; }
[Resolved]
private SessionStatics statics { get; set; }
private TaskCompletionSource<bool> scoreSubmissionSource;
protected SubmittingPlayer(PlayerConfiguration configuration = null)
@@ -176,6 +180,7 @@ namespace osu.Game.Screens.Play
{
bool exiting = base.OnExiting(e);
submitFromFailOrQuit();
statics.SetValue(Static.LastLocalUserScore, Score?.ScoreInfo.DeepClone());
return exiting;
}