1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-15 21:42:55 +08:00

Merge branch 'master' into fix-copy-difficulty-moving-collections

This commit is contained in:
Dean Herbert 2023-01-25 14:11:32 +09:00 committed by GitHub
commit 6efab635d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 505 additions and 125 deletions

View File

@ -121,21 +121,12 @@ jobs:
build-only-ios:
name: Build only (iOS)
# change to macos-latest once GitHub finishes migrating all repositories to macOS 12.
runs-on: macos-12
runs-on: macos-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v2
# see https://github.com/actions/runner-images/issues/6771#issuecomment-1354713617
# remove once all workflow VMs use Xcode 14.1
- name: Set Xcode Version
shell: bash
run: |
sudo xcode-select -s "/Applications/Xcode_14.1.app"
echo "MD_APPLE_SDK_ROOT=/Applications/Xcode_14.1.app" >> $GITHUB_ENV
- name: Install .NET 6.0.x
uses: actions/setup-dotnet@v1
with:

View File

@ -113,6 +113,7 @@ namespace osu.Game.Rulesets.Catch
new MultiMod(new CatchModDoubleTime(), new CatchModNightcore()),
new CatchModHidden(),
new CatchModFlashlight(),
new ModAccuracyChallenge(),
};
case ModType.Conversion:

View File

@ -245,6 +245,7 @@ namespace osu.Game.Rulesets.Mania
new MultiMod(new ManiaModDoubleTime(), new ManiaModNightcore()),
new MultiMod(new ManiaModFadeIn(), new ManiaModHidden()),
new ManiaModFlashlight(),
new ModAccuracyChallenge(),
};
case ModType.Conversion:

View File

@ -22,6 +22,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
/// </summary>
public Vector2 PathStartLocation => body.PathOffset;
/// <summary>
/// Offset in absolute (local) coordinates from the end of the curve.
/// </summary>
public Vector2 PathEndLocation => body.PathEndOffset;
public SliderBodyPiece()
{
InternalChild = body = new ManualSliderBody

View File

@ -409,6 +409,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathOffset)
?? BodyPiece.ToScreenSpace(BodyPiece.PathStartLocation);
protected override Vector2[] ScreenSpaceAdditionalNodes => new[]
{
DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathEndOffset) ?? BodyPiece.ToScreenSpace(BodyPiece.PathEndLocation)
};
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true;

View File

@ -0,0 +1,14 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModAccuracyChallenge : ModAccuracyChallenge
{
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray();
}
}

View File

@ -164,7 +164,8 @@ namespace osu.Game.Rulesets.Osu
new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()),
new OsuModHidden(),
new MultiMod(new OsuModFlashlight(), new OsuModBlinds()),
new OsuModStrictTracking()
new OsuModStrictTracking(),
new OsuModAccuracyChallenge(),
};
case ModType.Conversion:

View File

@ -31,6 +31,11 @@ namespace osu.Game.Rulesets.Osu.Skinning
/// </summary>
public virtual Vector2 PathOffset => path.PositionInBoundingBox(path.Vertices[0]);
/// <summary>
/// Offset in absolute coordinates from the end of the curve.
/// </summary>
public virtual Vector2 PathEndOffset => path.PositionInBoundingBox(path.Vertices[^1]);
/// <summary>
/// Used to colour the path.
/// </summary>

View File

@ -43,6 +43,8 @@ namespace osu.Game.Rulesets.Osu.Skinning
public override Vector2 PathOffset => snakedPathOffset;
public override Vector2 PathEndOffset => snakedPathEndOffset;
/// <summary>
/// The top-left position of the path when fully snaked.
/// </summary>
@ -53,6 +55,11 @@ namespace osu.Game.Rulesets.Osu.Skinning
/// </summary>
private Vector2 snakedPathOffset;
/// <summary>
/// The offset of the end of path from <see cref="snakedPosition"/> when fully snaked.
/// </summary>
private Vector2 snakedPathEndOffset;
private DrawableSlider drawableSlider = null!;
[BackgroundDependencyLoader]
@ -109,6 +116,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
snakedPosition = Path.PositionInBoundingBox(Vector2.Zero);
snakedPathOffset = Path.PositionInBoundingBox(Path.Vertices[0]);
snakedPathEndOffset = Path.PositionInBoundingBox(Path.Vertices[^1]);
double lastSnakedStart = SnakedStart ?? 0;
double lastSnakedEnd = SnakedEnd ?? 0;

View File

@ -10,6 +10,7 @@ using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Framework.Input.StateChanges;
using osu.Game.Configuration;
using osuTK;
namespace osu.Game.Rulesets.Osu.UI
{
@ -38,6 +39,9 @@ namespace osu.Game.Rulesets.Osu.UI
mouseDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableButtons);
}
// Required to handle touches outside of the playfield when screen scaling is enabled.
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
protected override void OnTouchMove(TouchMoveEvent e)
{
base.OnTouchMove(e);

View File

@ -1,7 +1,6 @@
// 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.Diagnostics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.UI;
@ -9,30 +8,15 @@ using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Taiko.Mods
{
public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset<TaikoHitObject>, IUpdatableByPlayfield
public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset<TaikoHitObject>
{
private DrawableTaikoRuleset? drawableTaikoRuleset;
public void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)
{
drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset;
var drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset;
drawableTaikoRuleset.LockPlayfieldMaxAspect.Value = false;
var playfield = (TaikoPlayfield)drawableRuleset.Playfield;
playfield.ClassicHitTargetPosition.Value = true;
}
public void Update(Playfield playfield)
{
Debug.Assert(drawableTaikoRuleset != null);
// Classic taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened.
const float scroll_rate = 10;
// Since the time range will depend on a positional value, it is referenced to the x480 pixel space.
float ratio = drawableTaikoRuleset.DrawHeight / 480;
drawableTaikoRuleset.TimeRange.Value = (playfield.HitObjectContainer.DrawWidth / ratio) * scroll_rate;
}
}
}

View File

@ -144,6 +144,7 @@ namespace osu.Game.Rulesets.Taiko
new MultiMod(new TaikoModDoubleTime(), new TaikoModNightcore()),
new TaikoModHidden(),
new TaikoModFlashlight(),
new ModAccuracyChallenge(),
};
case ModType.Conversion:

View File

@ -43,7 +43,6 @@ namespace osu.Game.Rulesets.Taiko.UI
: base(ruleset, beatmap, mods)
{
Direction.Value = ScrollingDirection.Left;
TimeRange.Value = 7000;
}
[BackgroundDependencyLoader]
@ -60,6 +59,19 @@ namespace osu.Game.Rulesets.Taiko.UI
KeyBindingInputManager.Add(new DrumTouchInputArea());
}
protected override void Update()
{
base.Update();
// Taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened.
const float scroll_rate = 10;
// Since the time range will depend on a positional value, it is referenced to the x480 pixel space.
float ratio = DrawHeight / 480;
TimeRange.Value = (Playfield.HitObjectContainer.DrawWidth / ratio) * scroll_rate;
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();

View File

@ -262,7 +262,7 @@ namespace osu.Game.Tests.Visual.Online
AddAssert("Nothing happened", () => this.ChildrenOfType<ReportCommentPopover>().Any());
AddStep("Set report data", () =>
{
var field = this.ChildrenOfType<OsuTextBox>().Single();
var field = this.ChildrenOfType<ReportCommentPopover>().Single().ChildrenOfType<OsuTextBox>().Single();
field.Current.Value = report_text;
var reason = this.ChildrenOfType<OsuEnumDropdown<CommentReportReason>>().Single();
reason.Current.Value = CommentReportReason.Other;

View File

@ -0,0 +1,54 @@
// 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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays.Profile.Header.Components;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Tests.Visual.Online
{
[TestFixture]
public partial class TestSceneLevelBadge : OsuTestScene
{
public TestSceneLevelBadge()
{
var levels = new List<UserStatistics.LevelInfo>();
for (int i = 0; i < 11; i++)
{
levels.Add(new UserStatistics.LevelInfo
{
Current = i * 10
});
}
levels.Add(new UserStatistics.LevelInfo { Current = 101 });
levels.Add(new UserStatistics.LevelInfo { Current = 105 });
levels.Add(new UserStatistics.LevelInfo { Current = 110 });
levels.Add(new UserStatistics.LevelInfo { Current = 115 });
levels.Add(new UserStatistics.LevelInfo { Current = 120 });
Children = new Drawable[]
{
new FillFlowContainer<LevelBadge>
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Full,
Spacing = new Vector2(5),
ChildrenEnumerable = levels.Select(level => new LevelBadge
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(60),
LevelInfo = { Value = level }
})
}
};
}
}
}

View File

@ -89,6 +89,7 @@ namespace osu.Game.Tests.Visual.Online
Groups = new[]
{
new APIUserGroup { Colour = "#EB47D0", ShortName = "DEV", Name = "Developers" },
new APIUserGroup { Colour = "#A347EB", ShortName = "BN", Name = "Beatmap Nominators", Playmodes = new[] { "mania" } },
new APIUserGroup { Colour = "#A347EB", ShortName = "BN", Name = "Beatmap Nominators", Playmodes = new[] { "osu", "taiko" } }
},
ProfileOrder = new[]

View File

@ -5,6 +5,7 @@
using System;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics.Colour;
using osu.Game.Beatmaps;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
@ -187,6 +188,41 @@ namespace osu.Game.Graphics
}
}
/// <summary>
/// Retrieves colour for a <see cref="RankingTier"/>.
/// See https://www.figma.com/file/YHWhp9wZ089YXgB7pe6L1k/Tier-Colours
/// </summary>
public ColourInfo ForRankingTier(RankingTier tier)
{
switch (tier)
{
default:
case RankingTier.Iron:
return Color4Extensions.FromHex(@"BAB3AB");
case RankingTier.Bronze:
return ColourInfo.GradientVertical(Color4Extensions.FromHex(@"B88F7A"), Color4Extensions.FromHex(@"855C47"));
case RankingTier.Silver:
return ColourInfo.GradientVertical(Color4Extensions.FromHex(@"E0E0EB"), Color4Extensions.FromHex(@"A3A3C2"));
case RankingTier.Gold:
return ColourInfo.GradientVertical(Color4Extensions.FromHex(@"F0E4A8"), Color4Extensions.FromHex(@"E0C952"));
case RankingTier.Platinum:
return ColourInfo.GradientVertical(Color4Extensions.FromHex(@"A8F0EF"), Color4Extensions.FromHex(@"52E0DF"));
case RankingTier.Rhodium:
return ColourInfo.GradientVertical(Color4Extensions.FromHex(@"D9F8D3"), Color4Extensions.FromHex(@"A0CF96"));
case RankingTier.Radiant:
return ColourInfo.GradientVertical(Color4Extensions.FromHex(@"97DCFF"), Color4Extensions.FromHex(@"ED82FF"));
case RankingTier.Lustrous:
return ColourInfo.GradientVertical(Color4Extensions.FromHex(@"FFE600"), Color4Extensions.FromHex(@"ED82FF"));
}
}
/// <summary>
/// Returns a foreground text colour that is supposed to contrast well with
/// the supplied <paramref name="backgroundColour"/>.

View File

@ -250,13 +250,16 @@ namespace osu.Game.Graphics.UserInterface
protected override void OnFocus(FocusEvent e)
{
BorderThickness = 3;
if (Masking)
BorderThickness = 3;
base.OnFocus(e);
}
protected override void OnFocusLost(FocusLostEvent e)
{
BorderThickness = 0;
if (Masking)
BorderThickness = 0;
base.OnFocusLost(e);
}

View File

@ -152,22 +152,6 @@ namespace osu.Game.Graphics.UserInterface
segments.Sort();
}
private ColourInfo getSegmentColour(SegmentInfo segment)
{
var segmentColour = new ColourInfo
{
TopLeft = DrawColourInfo.Colour.Interpolate(new Vector2(segment.Start, 0f)),
TopRight = DrawColourInfo.Colour.Interpolate(new Vector2(segment.End, 0f)),
BottomLeft = DrawColourInfo.Colour.Interpolate(new Vector2(segment.Start, 1f)),
BottomRight = DrawColourInfo.Colour.Interpolate(new Vector2(segment.End, 1f))
};
var tierColour = segment.Tier >= 0 ? tierColours[segment.Tier] : new Colour4(0, 0, 0, 0);
segmentColour.ApplyChild(tierColour);
return segmentColour;
}
protected override DrawNode CreateDrawNode() => new SegmentedGraphDrawNode(this);
protected struct SegmentInfo
@ -215,6 +199,7 @@ namespace osu.Game.Graphics.UserInterface
private IShader shader = null!;
private readonly List<SegmentInfo> segments = new List<SegmentInfo>();
private Vector2 drawSize;
private readonly List<Colour4> tierColours = new List<Colour4>();
public SegmentedGraphDrawNode(SegmentedGraph<T> source)
: base(source)
@ -228,8 +213,12 @@ namespace osu.Game.Graphics.UserInterface
texture = Source.texture;
shader = Source.shader;
drawSize = Source.DrawSize;
segments.Clear();
segments.AddRange(Source.segments.Where(s => s.Length * drawSize.X > 1));
tierColours.Clear();
tierColours.AddRange(Source.tierColours);
}
public override void Draw(IRenderer renderer)
@ -252,11 +241,27 @@ namespace osu.Game.Graphics.UserInterface
Vector2Extensions.Transform(topRight, DrawInfo.Matrix),
Vector2Extensions.Transform(bottomLeft, DrawInfo.Matrix),
Vector2Extensions.Transform(bottomRight, DrawInfo.Matrix)),
Source.getSegmentColour(segment));
getSegmentColour(segment));
}
shader.Unbind();
}
private ColourInfo getSegmentColour(SegmentInfo segment)
{
var segmentColour = new ColourInfo
{
TopLeft = DrawColourInfo.Colour.Interpolate(new Vector2(segment.Start, 0f)),
TopRight = DrawColourInfo.Colour.Interpolate(new Vector2(segment.End, 0f)),
BottomLeft = DrawColourInfo.Colour.Interpolate(new Vector2(segment.Start, 1f)),
BottomRight = DrawColourInfo.Colour.Interpolate(new Vector2(segment.End, 1f))
};
var tierColour = segment.Tier >= 0 ? tierColours[segment.Tier] : new Colour4(0, 0, 0, 0);
segmentColour.ApplyChild(tierColour);
return segmentColour;
}
}
protected class SegmentManager : IEnumerable<SegmentInfo>

View File

@ -160,9 +160,12 @@ namespace osu.Game
protected Bindable<WorkingBeatmap> Beatmap { get; private set; } // cached via load() method
/// <summary>
/// The current ruleset selection for the local user.
/// </summary>
[Cached]
[Cached(typeof(IBindable<RulesetInfo>))]
protected readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
protected internal readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
/// <summary>
/// The current mod selection for the local user.
@ -553,8 +556,8 @@ namespace osu.Game
case JoystickHandler jh:
return new JoystickSettings(jh);
case TouchHandler:
return new InputSection.HandlerSection(handler);
case TouchHandler th:
return new TouchSettings(th);
}
}

View File

@ -5,12 +5,14 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Game.Input.Bindings;
using osu.Game.Online.API.Requests;
@ -22,8 +24,6 @@ namespace osu.Game.Overlays
{
public partial class ChangelogOverlay : OnlineOverlay<ChangelogHeader>
{
public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
public readonly Bindable<APIChangelogBuild> Current = new Bindable<APIChangelogBuild>();
private List<APIChangelogBuild> builds;
@ -81,6 +81,8 @@ namespace osu.Game.Overlays
ArgumentNullException.ThrowIfNull(updateStream);
ArgumentNullException.ThrowIfNull(version);
Show();
performAfterFetch(() =>
{
var build = builds.Find(b => b.Version == version && b.UpdateStream.Name == updateStream)
@ -89,8 +91,6 @@ namespace osu.Game.Overlays
if (build != null)
ShowBuild(build);
});
Show();
}
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
@ -127,11 +127,16 @@ namespace osu.Game.Overlays
private Task initialFetchTask;
private void performAfterFetch(Action action) => Schedule(() =>
private void performAfterFetch(Action action)
{
fetchListing()?.ContinueWith(_ =>
Schedule(action), TaskContinuationOptions.OnlyOnRanToCompletion);
});
Debug.Assert(State.Value == Visibility.Visible);
Schedule(() =>
{
fetchListing()?.ContinueWith(_ =>
Schedule(action), TaskContinuationOptions.OnlyOnRanToCompletion);
});
}
private Task fetchListing()
{

View File

@ -147,7 +147,7 @@ namespace osu.Game.Overlays.Comments
private void updateCommitButtonState() =>
commitButton.Enabled.Value = loadingSpinner.State.Value == Visibility.Hidden && !string.IsNullOrEmpty(Current.Value);
private partial class EditorTextBox : BasicTextBox
private partial class EditorTextBox : OsuTextBox
{
protected override float LeftRightPadding => side_padding;
@ -173,12 +173,6 @@ namespace osu.Game.Overlays.Comments
{
Font = OsuFont.GetFont(weight: FontWeight.Regular),
};
protected override Drawable GetDrawableCharacter(char c) => new FallingDownContainer
{
AutoSizeAxes = Axes.Both,
Child = new OsuSpriteText { Text = c.ToString(), Font = OsuFont.GetFont(size: CalculatedTextSize) }
};
}
protected partial class EditorButton : RoundedButton

View File

@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
{
public partial class GroupBadge : Container, IHasTooltip
{
public LocalisableString TooltipText { get; }
public LocalisableString TooltipText { get; private set; }
public int TextSize { get; set; } = 12;
@ -78,6 +78,11 @@ namespace osu.Game.Overlays.Profile.Header.Components
icon.Size = new Vector2(TextSize - 1);
})).ToList()
);
var badgeModesList = group.Playmodes.Select(p => rulesets.GetRuleset(p)?.Name).ToList();
string modesDisplay = string.Join(", ", badgeModesList);
TooltipText += $" ({modesDisplay})";
}
}
}

View File

@ -4,6 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites;
@ -12,6 +13,7 @@ using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Scoring;
using osu.Game.Users;
namespace osu.Game.Overlays.Profile.Header.Components
@ -23,6 +25,10 @@ namespace osu.Game.Overlays.Profile.Header.Components
public LocalisableString TooltipText { get; private set; }
private OsuSpriteText levelText = null!;
private Sprite sprite = null!;
[Resolved]
private OsuColour osuColour { get; set; } = null!;
public LevelBadge()
{
@ -34,7 +40,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
{
InternalChildren = new Drawable[]
{
new Sprite
sprite = new Sprite
{
RelativeSizeAxes = Axes.Both,
Texture = textures.Get("Profile/levelbadge"),
@ -58,9 +64,34 @@ namespace osu.Game.Overlays.Profile.Header.Components
private void updateLevel(UserStatistics.LevelInfo? levelInfo)
{
string level = levelInfo?.Current.ToString() ?? "0";
levelText.Text = level;
TooltipText = UsersStrings.ShowStatsLevel(level);
int level = levelInfo?.Current ?? 0;
levelText.Text = level.ToString();
TooltipText = UsersStrings.ShowStatsLevel(level.ToString());
sprite.Colour = mapLevelToTierColour(level);
}
private ColourInfo mapLevelToTierColour(int level)
{
var tier = RankingTier.Iron;
if (level > 0)
{
tier = (RankingTier)(level / 20);
}
if (level >= 105)
{
tier = RankingTier.Radiant;
}
if (level >= 110)
{
tier = RankingTier.Lustrous;
}
return osuColour.ForRankingTier(tier);
}
}
}

View File

@ -35,6 +35,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
new SettingsCheckbox
{
LabelText = SkinSettingsStrings.GameplayCursorDuringTouch,
Keywords = new[] { @"touchscreen" },
Current = config.GetBindable<bool>(OsuSetting.GameplayCursorDuringTouch)
},
};

View File

@ -70,6 +70,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
Add(new SettingsButton
{
Text = GeneralSettingsStrings.OpenOsuFolder,
Keywords = new[] { @"logs", @"files", @"access", "directory" },
Action = () => storage.PresentExternally(),
});

View File

@ -34,6 +34,7 @@ namespace osu.Game.Overlays.Settings.Sections
new SettingsButton
{
Text = GeneralSettingsStrings.RunSetupWizard,
Keywords = new[] { @"first run", @"initial", @"getting started" },
TooltipText = FirstRunSetupOverlayStrings.FirstRunSetupDescription,
Action = () => firstRunSetupOverlay?.Show(),
},

View File

@ -133,6 +133,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
new SettingsSlider<float>
{
LabelText = GraphicsSettingsStrings.HorizontalPosition,
Keywords = new[] { "screen", "scaling" },
Current = scalingPositionX,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
@ -140,6 +141,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
new SettingsSlider<float>
{
LabelText = GraphicsSettingsStrings.VerticalPosition,
Keywords = new[] { "screen", "scaling" },
Current = scalingPositionY,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
@ -147,6 +149,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
new SettingsSlider<float>
{
LabelText = GraphicsSettingsStrings.HorizontalScale,
Keywords = new[] { "screen", "scaling" },
Current = scalingSizeX,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
@ -154,6 +157,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
new SettingsSlider<float>
{
LabelText = GraphicsSettingsStrings.VerticalScale,
Keywords = new[] { "screen", "scaling" },
Current = scalingSizeY,
KeyboardStep = 0.01f,
DisplayAsPercentage = true

View File

@ -15,7 +15,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{
protected override LocalisableString Header => BindingSettingsStrings.ShortcutAndGameplayBindings;
public override IEnumerable<LocalisableString> FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { "keybindings" });
public override IEnumerable<LocalisableString> FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { @"keybindings", @"controls", @"keyboard", @"keys" });
public BindingSettings(KeyBindingPanel keyConfig)
{

View File

@ -0,0 +1,40 @@
// 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.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Input.Handlers.Touch;
using osu.Framework.Localisation;
using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Input
{
public partial class TouchSettings : SettingsSubsection
{
private readonly TouchHandler handler;
public TouchSettings(TouchHandler handler)
{
this.handler = handler;
}
[BackgroundDependencyLoader]
private void load()
{
Children = new Drawable[]
{
new SettingsCheckbox
{
LabelText = CommonStrings.Enabled,
Current = handler.Enabled
},
};
}
public override IEnumerable<LocalisableString> FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { @"touchscreen" });
protected override LocalisableString Header => handler.Description;
}
}

View File

@ -1,8 +1,8 @@
// 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 System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -22,6 +22,8 @@ namespace osu.Game.Overlays.Settings
public LocalisableString TooltipText { get; set; }
public IEnumerable<string> Keywords { get; set; } = Array.Empty<string>();
public BindableBool CanBeShown { get; } = new BindableBool(true);
IBindable<bool> IConditionalFilterable.CanBeShown => CanBeShown;
@ -30,9 +32,13 @@ namespace osu.Game.Overlays.Settings
get
{
if (TooltipText != default)
return base.FilterTerms.Append(TooltipText);
yield return TooltipText;
return base.FilterTerms;
foreach (string s in Keywords)
yield return s;
foreach (LocalisableString s in base.FilterTerms)
yield return s;
}
}
}

View File

@ -49,7 +49,7 @@ namespace osu.Game.Overlays.Settings
Text = game.Name,
Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold),
},
new BuildDisplay(game.Version, DebugUtils.IsDebugBuild)
new BuildDisplay(game.Version)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
@ -81,15 +81,13 @@ namespace osu.Game.Overlays.Settings
private partial class BuildDisplay : OsuAnimatedButton
{
private readonly string version;
private readonly bool isDebug;
[Resolved]
private OsuColour colours { get; set; }
public BuildDisplay(string version, bool isDebug)
public BuildDisplay(string version)
{
this.version = version;
this.isDebug = isDebug;
Content.RelativeSizeAxes = Axes.Y;
Content.AutoSizeAxes = AutoSizeAxes = Axes.X;
@ -99,8 +97,7 @@ namespace osu.Game.Overlays.Settings
[BackgroundDependencyLoader(true)]
private void load(ChangelogOverlay changelog)
{
if (!isDebug)
Action = () => changelog?.ShowBuild(OsuGameBase.CLIENT_STREAM_NAME, version);
Action = () => changelog?.ShowBuild(OsuGameBase.CLIENT_STREAM_NAME, version);
Add(new OsuSpriteText
{
@ -110,7 +107,7 @@ namespace osu.Game.Overlays.Settings
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Padding = new MarginPadding(5),
Colour = isDebug ? colours.Red : Color4.White,
Colour = DebugUtils.IsDebugBuild ? colours.Red : Color4.White,
});
}
}

View File

@ -4,6 +4,7 @@
#nullable disable
using System;
using System.Linq;
using osu.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -125,10 +126,21 @@ namespace osu.Game.Rulesets.Edit
public virtual MenuItem[] ContextMenuItems => Array.Empty<MenuItem>();
/// <summary>
/// The screen-space point that causes this <see cref="HitObjectSelectionBlueprint"/> to be selected via a drag.
/// The screen-space main point that causes this <see cref="HitObjectSelectionBlueprint"/> to be selected via a drag.
/// </summary>
public virtual Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.Centre;
/// <summary>
/// Any points that should be used for snapping purposes in addition to <see cref="ScreenSpaceSelectionPoint"/>. Exposed via <see cref="ScreenSpaceSnapPoints"/>.
/// </summary>
protected virtual Vector2[] ScreenSpaceAdditionalNodes => Array.Empty<Vector2>();
/// <summary>
/// The screen-space collection of base points on this <see cref="HitObjectSelectionBlueprint"/> that other objects can be snapped to.
/// The first element of this collection is <see cref="ScreenSpaceSelectionPoint"/>
/// </summary>
public Vector2[] ScreenSpaceSnapPoints => ScreenSpaceAdditionalNodes.Prepend(ScreenSpaceSelectionPoint).ToArray();
/// <summary>
/// The screen-space quad that outlines this <see cref="HitObjectSelectionBlueprint"/> for selections.
/// </summary>

View File

@ -0,0 +1,80 @@
// 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.Globalization;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Judgements;
using osu.Game.Scoring;
namespace osu.Game.Rulesets.Mods
{
public class ModAccuracyChallenge : ModFailCondition, IApplicableToScoreProcessor
{
public override string Name => "Accuracy Challenge";
public override string Acronym => "AC";
public override LocalisableString Description => "Fail if your accuracy drops too low!";
public override ModType Type => ModType.DifficultyIncrease;
public override double ScoreMultiplier => 1.0;
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModEasyWithExtraLives), typeof(ModPerfect) }).ToArray();
public override bool RequiresConfiguration => false;
public override string SettingDescription => base.SettingDescription.Replace(MinimumAccuracy.ToString(), MinimumAccuracy.Value.ToString("##%", NumberFormatInfo.InvariantInfo));
[SettingSource("Minimum accuracy", "Trigger a failure if your accuracy goes below this value.", SettingControlType = typeof(SettingsSlider<double, PercentSlider>))]
public BindableNumber<double> MinimumAccuracy { get; } = new BindableDouble
{
MinValue = 0.60,
MaxValue = 0.99,
Precision = 0.01,
Default = 0.9,
Value = 0.9,
};
private ScoreProcessor scoreProcessor = null!;
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) => this.scoreProcessor = scoreProcessor;
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
{
if (!result.Type.AffectsAccuracy())
return false;
return getAccuracyWithImminentResultAdded(result) < MinimumAccuracy.Value;
}
private double getAccuracyWithImminentResultAdded(JudgementResult result)
{
var score = new ScoreInfo { Ruleset = scoreProcessor.Ruleset.RulesetInfo };
// This is super ugly, but if we don't do it this way we will not have the most recent result added to the accuracy value.
// Hopefully we can improve this in the future.
scoreProcessor.PopulateScore(score);
score.Statistics[result.Type]++;
return scoreProcessor.ComputeAccuracy(score);
}
}
public partial class PercentSlider : OsuSliderBar<double>
{
public PercentSlider()
{
DisplayAsPercentage = true;
}
}
}

View File

@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using Humanizer;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
@ -19,6 +21,7 @@ namespace osu.Game.Rulesets.Mods
};
public override string SettingDescription => Retries.IsDefault ? string.Empty : $"{"lives".ToQuantity(Retries.Value)}";
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModAccuracyChallenge)).ToArray();
private int retries;

View File

@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mods
public override double ScoreMultiplier => 1;
public override LocalisableString Description => "SS or quit.";
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModSuddenDeath)).ToArray();
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModSuddenDeath), typeof(ModAccuracyChallenge) }).ToArray();
protected ModPerfect()
{

View File

@ -97,7 +97,11 @@ namespace osu.Game.Rulesets.Scoring
/// </summary>
protected virtual double ClassicScoreMultiplier => 36;
private readonly Ruleset ruleset;
/// <summary>
/// The ruleset this score processor is valid for.
/// </summary>
public readonly Ruleset Ruleset;
private readonly double accuracyPortion;
private readonly double comboPortion;
@ -145,7 +149,7 @@ namespace osu.Game.Rulesets.Scoring
public ScoreProcessor(Ruleset ruleset)
{
this.ruleset = ruleset;
Ruleset = ruleset;
accuracyPortion = DefaultAccuracyPortion;
comboPortion = DefaultComboPortion;
@ -291,8 +295,8 @@ namespace osu.Game.Rulesets.Scoring
[Pure]
public double ComputeAccuracy(ScoreInfo scoreInfo)
{
if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset))
throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\".");
if (!Ruleset.RulesetInfo.Equals(scoreInfo.Ruleset))
throw new ArgumentException($"Unexpected score ruleset. Expected \"{Ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\".");
// We only extract scoring values from the score's statistics. This is because accuracy is always relative to the point of pass or fail rather than relative to the whole beatmap.
extractScoringValues(scoreInfo.Statistics, out var current, out var maximum);
@ -312,8 +316,8 @@ namespace osu.Game.Rulesets.Scoring
[Pure]
public long ComputeScore(ScoringMode mode, ScoreInfo scoreInfo)
{
if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset))
throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\".");
if (!Ruleset.RulesetInfo.Equals(scoreInfo.Ruleset))
throw new ArgumentException($"Unexpected score ruleset. Expected \"{Ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\".");
extractScoringValues(scoreInfo, out var current, out var maximum);
@ -552,7 +556,7 @@ namespace osu.Game.Rulesets.Scoring
break;
default:
maxResult = maxBasicResult ??= ruleset.GetHitResults().MaxBy(kvp => Judgement.ToNumericResult(kvp.result)).result;
maxResult = maxBasicResult ??= Ruleset.GetHitResults().MaxBy(kvp => Judgement.ToNumericResult(kvp.result)).result;
break;
}

View File

@ -0,0 +1,17 @@
// 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.
namespace osu.Game.Scoring
{
public enum RankingTier
{
Iron,
Bronze,
Silver,
Gold,
Platinum,
Rhodium,
Radiant,
Lustrous
}
}

View File

@ -439,7 +439,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
#region Selection Movement
private Vector2[] movementBlueprintOriginalPositions;
private Vector2[][] movementBlueprintsOriginalPositions;
private SelectionBlueprint<T>[] movementBlueprints;
private bool isDraggingBlueprint;
@ -459,7 +459,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
// Movement is tracked from the blueprint of the earliest item, since it only makes sense to distance snap from that item
movementBlueprints = SortForMovement(SelectionHandler.SelectedBlueprints).ToArray();
movementBlueprintOriginalPositions = movementBlueprints.Select(m => m.ScreenSpaceSelectionPoint).ToArray();
movementBlueprintsOriginalPositions = movementBlueprints.Select(m => m.ScreenSpaceSnapPoints).ToArray();
return true;
}
@ -480,26 +480,15 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (movementBlueprints == null)
return false;
Debug.Assert(movementBlueprintOriginalPositions != null);
Debug.Assert(movementBlueprintsOriginalPositions != null);
Vector2 distanceTravelled = e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition;
if (snapProvider != null)
{
// check for positional snap for every object in selection (for things like object-object snapping)
for (int i = 0; i < movementBlueprintOriginalPositions.Length; i++)
for (int i = 0; i < movementBlueprints.Length; i++)
{
Vector2 originalPosition = movementBlueprintOriginalPositions[i];
var testPosition = originalPosition + distanceTravelled;
var positionalResult = snapProvider.FindSnappedPositionAndTime(testPosition, SnapType.NearbyObjects);
if (positionalResult.ScreenSpacePosition == testPosition) continue;
var delta = positionalResult.ScreenSpacePosition - movementBlueprints[i].ScreenSpaceSelectionPoint;
// attempt to move the objects, and abort any time based snapping if we can.
if (SelectionHandler.HandleMovement(new MoveSelectionEvent<T>(movementBlueprints[i], delta)))
if (checkSnappingBlueprintToNearbyObjects(movementBlueprints[i], distanceTravelled, movementBlueprintsOriginalPositions[i]))
return true;
}
}
@ -508,7 +497,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
// item in the selection.
// The final movement position, relative to movementBlueprintOriginalPosition.
Vector2 movePosition = movementBlueprintOriginalPositions.First() + distanceTravelled;
Vector2 movePosition = movementBlueprintsOriginalPositions.First().First() + distanceTravelled;
// Retrieve a snapped position.
var result = snapProvider?.FindSnappedPositionAndTime(movePosition, ~SnapType.NearbyObjects);
@ -521,6 +510,36 @@ namespace osu.Game.Screens.Edit.Compose.Components
return ApplySnapResult(movementBlueprints, result);
}
/// <summary>
/// Check for positional snap for given blueprint.
/// </summary>
/// <param name="blueprint">The blueprint to check for snapping.</param>
/// <param name="distanceTravelled">Distance travelled since start of dragging action.</param>
/// <param name="originalPositions">The snap positions of blueprint before start of dragging action.</param>
/// <returns>Whether an object to snap to was found.</returns>
private bool checkSnappingBlueprintToNearbyObjects(SelectionBlueprint<T> blueprint, Vector2 distanceTravelled, Vector2[] originalPositions)
{
var currentPositions = blueprint.ScreenSpaceSnapPoints;
for (int i = 0; i < originalPositions.Length; i++)
{
Vector2 originalPosition = originalPositions[i];
var testPosition = originalPosition + distanceTravelled;
var positionalResult = snapProvider.FindSnappedPositionAndTime(testPosition, SnapType.NearbyObjects);
if (positionalResult.ScreenSpacePosition == testPosition) continue;
var delta = positionalResult.ScreenSpacePosition - currentPositions[i];
// attempt to move the objects, and abort any time based snapping if we can.
if (SelectionHandler.HandleMovement(new MoveSelectionEvent<T>(blueprint, delta)))
return true;
}
return false;
}
protected virtual bool ApplySnapResult(SelectionBlueprint<T>[] blueprints, SnapResult result) =>
SelectionHandler.HandleMovement(new MoveSelectionEvent<T>(blueprints.First(), result.ScreenSpacePosition - blueprints.First().ScreenSpaceSelectionPoint));
@ -533,7 +552,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (movementBlueprints == null)
return false;
movementBlueprintOriginalPositions = null;
movementBlueprintsOriginalPositions = null;
movementBlueprints = null;
return true;

View File

@ -51,6 +51,7 @@ namespace osu.Game.Screens.Play
private const float duration = 2500;
private ISample? failSample;
private SampleChannel? failSampleChannel;
[Resolved]
private OsuConfigManager config { get; set; } = null!;
@ -119,13 +120,13 @@ namespace osu.Game.Screens.Play
this.TransformBindableTo(trackFreq, 0, duration).OnComplete(_ =>
{
// Don't reset frequency as the pause screen may appear post transform, causing a second frequency sweep.
RemoveFilters(false);
removeFilters(false);
OnComplete?.Invoke();
});
failHighPassFilter.CutoffTo(300);
failLowPassFilter.CutoffTo(300, duration, Easing.OutCubic);
failSample?.Play();
failSampleChannel = failSample?.Play();
track.AddAdjustment(AdjustableProperty.Frequency, trackFreq);
track.AddAdjustment(AdjustableProperty.Volume, volumeAdjustment);
@ -153,7 +154,16 @@ namespace osu.Game.Screens.Play
Background?.FadeColour(OsuColour.Gray(0.3f), 60);
}
public void RemoveFilters(bool resetTrackFrequency = true)
/// <summary>
/// Stops any and all persistent effects added by the ongoing fail animation.
/// </summary>
public void Stop()
{
failSampleChannel?.Stop();
removeFilters();
}
private void removeFilters(bool resetTrackFrequency = true)
{
filtersRemoved = true;

View File

@ -1072,7 +1072,7 @@ namespace osu.Game.Screens.Play
public override bool OnExiting(ScreenExitEvent e)
{
screenSuspension?.RemoveAndDisposeImmediately();
failAnimationLayer?.RemoveFilters();
failAnimationLayer?.Stop();
if (LoadedBeatmapSuccessfully)
{

View File

@ -30,14 +30,16 @@ namespace osu.Game.Screens.Select.Details
{
public partial class AdvancedStats : Container
{
[Resolved]
private BeatmapDifficultyCache difficultyCache { get; set; }
[Resolved]
private IBindable<IReadOnlyList<Mod>> mods { get; set; }
[Resolved]
private IBindable<RulesetInfo> ruleset { get; set; }
private OsuGameBase game { get; set; }
[Resolved]
private BeatmapDifficultyCache difficultyCache { get; set; }
private IBindable<RulesetInfo> gameRuleset;
protected readonly StatisticRow FirstValue, HpDrain, Accuracy, ApproachRate;
private readonly StatisticRow starDifficulty;
@ -84,7 +86,13 @@ namespace osu.Game.Screens.Select.Details
{
base.LoadComplete();
ruleset.BindValueChanged(_ => updateStatistics());
// the cached ruleset bindable might be a decoupled bindable provided by SongSelect,
// which we can't rely on in combination with the game-wide selected mods list,
// since mods could be updated to the new ruleset instances while the decoupled bindable is held behind,
// therefore resulting in performing difficulty calculation with invalid states.
gameRuleset = game.Ruleset.GetBoundCopy();
gameRuleset.BindValueChanged(_ => updateStatistics());
mods.BindValueChanged(modsChanged, true);
}
@ -142,7 +150,14 @@ namespace osu.Game.Screens.Select.Details
private CancellationTokenSource starDifficultyCancellationSource;
private void updateStarDifficulty()
/// <summary>
/// Updates the displayed star difficulty statistics with the values provided by the currently-selected beatmap, ruleset, and selected mods.
/// </summary>
/// <remarks>
/// This is scheduled to avoid scenarios wherein a ruleset changes first before selected mods do,
/// potentially resulting in failure during difficulty calculation due to incomplete bindable state updates.
/// </remarks>
private void updateStarDifficulty() => Scheduler.AddOnce(() =>
{
starDifficultyCancellationSource?.Cancel();
@ -151,8 +166,8 @@ namespace osu.Game.Screens.Select.Details
starDifficultyCancellationSource = new CancellationTokenSource();
var normalStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, ruleset.Value, null, starDifficultyCancellationSource.Token);
var moddedStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, ruleset.Value, mods.Value, starDifficultyCancellationSource.Token);
var normalStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, gameRuleset.Value, null, starDifficultyCancellationSource.Token);
var moddedStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, gameRuleset.Value, mods.Value, starDifficultyCancellationSource.Token);
Task.WhenAll(normalStarDifficultyTask, moddedStarDifficultyTask).ContinueWith(_ => Schedule(() =>
{
@ -164,7 +179,7 @@ namespace osu.Game.Screens.Select.Details
starDifficulty.Value = ((float)normalDifficulty.Value.Stars, (float)moddedDifficulty.Value.Stars);
}), starDifficultyCancellationSource.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current);
}
});
protected override void Dispose(bool isDisposing)
{

View File

@ -104,6 +104,9 @@ namespace osu.Game.Screens.Select.Leaderboards
protected override APIRequest? FetchScores(CancellationToken cancellationToken)
{
scoreRetrievalRequest?.Cancel();
scoreRetrievalRequest = null;
var fetchBeatmapInfo = BeatmapInfo;
if (fetchBeatmapInfo == null)
@ -152,8 +155,6 @@ namespace osu.Game.Screens.Select.Leaderboards
else if (filterMods)
requestMods = mods.Value;
scoreRetrievalRequest?.Cancel();
var newRequest = new GetScoresRequest(fetchBeatmapInfo, fetchRuleset, Scope, requestMods);
newRequest.Success += response => Schedule(() =>
{