1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-13 08:32:57 +08:00

Merge branch 'master' into upgrade-to-SDL3

This commit is contained in:
Dean Herbert 2024-04-18 22:23:55 +08:00
commit c39eb5c5aa
No known key found for this signature in database
24 changed files with 368 additions and 45 deletions

View File

@ -205,7 +205,9 @@ namespace osu.Desktop
Password = room.Settings.Password, Password = room.Settings.Password,
}; };
presence.Secrets.JoinSecret = JsonConvert.SerializeObject(roomSecret); if (client.HasRegisteredUriScheme)
presence.Secrets.JoinSecret = JsonConvert.SerializeObject(roomSecret);
// discord cannot handle both secrets and buttons at the same time, so we need to choose something. // discord cannot handle both secrets and buttons at the same time, so we need to choose something.
// the multiplayer room seems more important. // the multiplayer room seems more important.
presence.Buttons = null; presence.Buttons = null;

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Runtime; using System.Runtime;
using System.Threading;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Performance; using osu.Game.Performance;
@ -11,16 +12,26 @@ namespace osu.Desktop.Performance
{ {
public class HighPerformanceSessionManager : IHighPerformanceSessionManager public class HighPerformanceSessionManager : IHighPerformanceSessionManager
{ {
public bool IsSessionActive => activeSessions > 0;
private int activeSessions;
private GCLatencyMode originalGCMode; private GCLatencyMode originalGCMode;
public IDisposable BeginSession() public IDisposable BeginSession()
{ {
enableHighPerformanceSession(); enterSession();
return new InvokeOnDisposal<HighPerformanceSessionManager>(this, static m => m.disableHighPerformanceSession()); return new InvokeOnDisposal<HighPerformanceSessionManager>(this, static m => m.exitSession());
} }
private void enableHighPerformanceSession() private void enterSession()
{ {
if (Interlocked.Increment(ref activeSessions) > 1)
{
Logger.Log($"High performance session requested ({activeSessions} running in total)");
return;
}
Logger.Log("Starting high performance session"); Logger.Log("Starting high performance session");
originalGCMode = GCSettings.LatencyMode; originalGCMode = GCSettings.LatencyMode;
@ -30,8 +41,14 @@ namespace osu.Desktop.Performance
GC.Collect(0); GC.Collect(0);
} }
private void disableHighPerformanceSession() private void exitSession()
{ {
if (Interlocked.Decrement(ref activeSessions) > 0)
{
Logger.Log($"High performance session finished ({activeSessions} others remain)");
return;
}
Logger.Log("Ending high performance session"); Logger.Log("Ending high performance session");
if (GCSettings.LatencyMode == GCLatencyMode.LowLatency) if (GCSettings.LatencyMode == GCLatencyMode.LowLatency)

View File

@ -1,9 +1,14 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Filter; using osu.Game.Rulesets.Filter;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring.Legacy; using osu.Game.Rulesets.Scoring.Legacy;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Filter; using osu.Game.Screens.Select.Filter;
@ -30,5 +35,20 @@ namespace osu.Game.Rulesets.Mania
return false; return false;
} }
public bool FilterMayChangeFromMods(ValueChangedEvent<IReadOnlyList<Mod>> mods)
{
if (keys.HasFilter)
{
// Interpreting as the Mod type is required for equality comparison.
HashSet<Mod> oldSet = mods.OldValue.OfType<ManiaKeyMod>().AsEnumerable<Mod>().ToHashSet();
HashSet<Mod> newSet = mods.NewValue.OfType<ManiaKeyMod>().AsEnumerable<Mod>().ToHashSet();
if (!oldSet.SetEquals(newSet))
return true;
}
return false;
}
} }
} }

View File

@ -0,0 +1,69 @@
// 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 NUnit.Framework;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Screens.Play;
using osu.Game.Tests.Visual;
using osuTK;
using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Tests
{
public partial class TestSceneResume : PlayerTestScene
{
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, false, AllowBackwardsSeeks);
[Test]
public void TestPauseViaKeyboard()
{
AddStep("move mouse to center", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre));
AddUntilStep("wait for gameplay start", () => Player.LocalUserPlaying.Value);
AddStep("press escape", () => InputManager.PressKey(Key.Escape));
AddUntilStep("wait for pause overlay", () => Player.ChildrenOfType<PauseOverlay>().Single().State.Value, () => Is.EqualTo(Visibility.Visible));
AddStep("release escape", () => InputManager.ReleaseKey(Key.Escape));
AddStep("resume", () =>
{
InputManager.Key(Key.Down);
InputManager.Key(Key.Space);
});
AddUntilStep("pause overlay present", () => Player.DrawableRuleset.ResumeOverlay.State.Value, () => Is.EqualTo(Visibility.Visible));
}
[Test]
public void TestPauseViaKeyboardWhenMouseOutsidePlayfield()
{
AddStep("move mouse outside playfield", () => InputManager.MoveMouseTo(Player.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.BottomRight + new Vector2(1)));
AddUntilStep("wait for gameplay start", () => Player.LocalUserPlaying.Value);
AddStep("press escape", () => InputManager.PressKey(Key.Escape));
AddUntilStep("wait for pause overlay", () => Player.ChildrenOfType<PauseOverlay>().Single().State.Value, () => Is.EqualTo(Visibility.Visible));
AddStep("release escape", () => InputManager.ReleaseKey(Key.Escape));
AddStep("resume", () =>
{
InputManager.Key(Key.Down);
InputManager.Key(Key.Space);
});
AddUntilStep("pause overlay present", () => Player.DrawableRuleset.ResumeOverlay.State.Value, () => Is.EqualTo(Visibility.Visible));
}
[Test]
public void TestPauseViaKeyboardWhenMouseOutsideScreen()
{
AddStep("move mouse outside playfield", () => InputManager.MoveMouseTo(new Vector2(-20)));
AddUntilStep("wait for gameplay start", () => Player.LocalUserPlaying.Value);
AddStep("press escape", () => InputManager.PressKey(Key.Escape));
AddUntilStep("wait for pause overlay", () => Player.ChildrenOfType<PauseOverlay>().Single().State.Value, () => Is.EqualTo(Visibility.Visible));
AddStep("release escape", () => InputManager.ReleaseKey(Key.Escape));
AddStep("resume", () =>
{
InputManager.Key(Key.Down);
InputManager.Key(Key.Space);
});
AddUntilStep("pause overlay not present", () => Player.DrawableRuleset.ResumeOverlay.State.Value, () => Is.EqualTo(Visibility.Hidden));
}
}
}

View File

@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (score.Mods.Any(m => m is OsuModBlinds)) if (score.Mods.Any(m => m is OsuModBlinds))
aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * attributes.DrainRate * attributes.DrainRate); aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * attributes.DrainRate * attributes.DrainRate);
else if (score.Mods.Any(h => h is OsuModHidden)) else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable))
{ {
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
aimValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); aimValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate);
@ -162,7 +162,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Increasing the speed value by object count for Blinds isn't ideal, so the minimum buff is given. // Increasing the speed value by object count for Blinds isn't ideal, so the minimum buff is given.
speedValue *= 1.12; speedValue *= 1.12;
} }
else if (score.Mods.Any(m => m is OsuModHidden)) else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable))
{ {
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate);
@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Increasing the accuracy value by object count for Blinds isn't ideal, so the minimum buff is given. // Increasing the accuracy value by object count for Blinds isn't ideal, so the minimum buff is given.
if (score.Mods.Any(m => m is OsuModBlinds)) if (score.Mods.Any(m => m is OsuModBlinds))
accuracyValue *= 1.14; accuracyValue *= 1.14;
else if (score.Mods.Any(m => m is OsuModHidden)) else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable))
accuracyValue *= 1.08; accuracyValue *= 1.08;
if (score.Mods.Any(m => m is OsuModFlashlight)) if (score.Mods.Any(m => m is OsuModFlashlight))

View File

@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t); public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t);
private readonly SliderPath path = new SliderPath(); private readonly SliderPath path = new SliderPath { OptimiseCatmull = true };
public SliderPath Path public SliderPath Path
{ {

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -12,6 +10,7 @@ using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Rulesets.Osu.UI.Cursor;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osuTK.Graphics; using osuTK.Graphics;
@ -19,15 +18,18 @@ namespace osu.Game.Rulesets.Osu.UI
{ {
public partial class OsuResumeOverlay : ResumeOverlay public partial class OsuResumeOverlay : ResumeOverlay
{ {
private Container cursorScaleContainer; private Container cursorScaleContainer = null!;
private OsuClickToResumeCursor clickToResumeCursor; private OsuClickToResumeCursor clickToResumeCursor = null!;
private OsuCursorContainer localCursorContainer; private OsuCursorContainer? localCursorContainer;
public override CursorContainer LocalCursor => State.Value == Visibility.Visible ? localCursorContainer : null; public override CursorContainer? LocalCursor => State.Value == Visibility.Visible ? localCursorContainer : null;
protected override LocalisableString Message => "Click the orange cursor to resume"; protected override LocalisableString Message => "Click the orange cursor to resume";
[Resolved]
private DrawableRuleset? drawableRuleset { get; set; }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
@ -40,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.UI
protected override void PopIn() protected override void PopIn()
{ {
// Can't display if the cursor is outside the window. // Can't display if the cursor is outside the window.
if (GameplayCursor.LastFrameState == Visibility.Hidden || !Contains(GameplayCursor.ActiveCursor.ScreenSpaceDrawQuad.Centre)) if (GameplayCursor.LastFrameState == Visibility.Hidden || drawableRuleset?.Contains(GameplayCursor.ActiveCursor.ScreenSpaceDrawQuad.Centre) == false)
{ {
Resume(); Resume();
return; return;
@ -71,8 +73,8 @@ namespace osu.Game.Rulesets.Osu.UI
{ {
public override bool HandlePositionalInput => true; public override bool HandlePositionalInput => true;
public Action ResumeRequested; public Action? ResumeRequested;
private Container scaleTransitionContainer; private Container scaleTransitionContainer = null!;
public OsuClickToResumeCursor() public OsuClickToResumeCursor()
{ {

View File

@ -1,10 +1,13 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Filter; using osu.Game.Rulesets.Filter;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Carousel;
using osu.Game.Screens.Select.Filter; using osu.Game.Screens.Select.Filter;
@ -311,6 +314,8 @@ namespace osu.Game.Tests.NonVisual.Filtering
public bool Matches(BeatmapInfo beatmapInfo, FilterCriteria criteria) => match; public bool Matches(BeatmapInfo beatmapInfo, FilterCriteria criteria) => match;
public bool TryParseCustomKeywordCriteria(string key, Operator op, string value) => false; public bool TryParseCustomKeywordCriteria(string key, Operator op, string value) => false;
public bool FilterMayChangeFromMods(ValueChangedEvent<IReadOnlyList<Mod>> mods) => false;
} }
} }
} }

View File

@ -2,10 +2,13 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Filter; using osu.Game.Rulesets.Filter;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Carousel;
using osu.Game.Screens.Select.Filter; using osu.Game.Screens.Select.Filter;
@ -514,6 +517,8 @@ namespace osu.Game.Tests.NonVisual.Filtering
return false; return false;
} }
public bool FilterMayChangeFromMods(ValueChangedEvent<IReadOnlyList<Mod>> mods) => false;
} }
private static readonly object[] correct_date_query_examples = private static readonly object[] correct_date_query_examples =

View File

@ -104,6 +104,21 @@ namespace osu.Game.Tests.Visual.Ranking
}); });
} }
[Test]
public void TestPPNotShownAsProvisionalIfClassicModIsPresentDueToLegacyScore()
{
AddStep("show example score", () =>
{
var score = TestResources.CreateTestScoreInfo(createTestBeatmap(new RealmUser()));
score.PP = 400;
score.Mods = score.Mods.Append(new OsuModClassic()).ToArray();
score.IsLegacyScore = true;
showPanel(score);
});
AddAssert("pp display faded out", () => this.ChildrenOfType<PerformanceStatistic>().Single().Alpha == 1);
}
[Test] [Test]
public void TestWithDefaultDate() public void TestWithDefaultDate()
{ {

View File

@ -29,6 +29,7 @@ using osu.Game.Overlays.Dialog;
using osu.Game.Overlays.Mods; using osu.Game.Overlays.Mods;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
@ -1147,6 +1148,62 @@ namespace osu.Game.Tests.Visual.SongSelect
AddAssert("filter text cleared", () => songSelect!.FilterControl.ChildrenOfType<FilterControl.FilterControlTextBox>().First().Text, () => Is.Empty); AddAssert("filter text cleared", () => songSelect!.FilterControl.ChildrenOfType<FilterControl.FilterControlTextBox>().First().Text, () => Is.Empty);
} }
[Test]
public void TestNonFilterableModChange()
{
addRulesetImportStep(0);
createSongSelect();
// Mod that is guaranteed to never re-filter.
AddStep("add non-filterable mod", () => SelectedMods.Value = new Mod[] { new OsuModCinema() });
AddAssert("filter count is 1", () => songSelect!.FilterCount, () => Is.EqualTo(1));
// Removing the mod should still not re-filter.
AddStep("remove non-filterable mod", () => SelectedMods.Value = Array.Empty<Mod>());
AddAssert("filter count is 1", () => songSelect!.FilterCount, () => Is.EqualTo(1));
}
[Test]
public void TestFilterableModChange()
{
addRulesetImportStep(3);
createSongSelect();
// Change to mania ruleset.
AddStep("filter to mania ruleset", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.OnlineID == 3));
AddAssert("filter count is 2", () => songSelect!.FilterCount, () => Is.EqualTo(2));
// Apply a mod, but this should NOT re-filter because there's no search text.
AddStep("add filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModKey3() });
AddAssert("filter count is 2", () => songSelect!.FilterCount, () => Is.EqualTo(2));
// Set search text. Should re-filter.
AddStep("set search text to match mods", () => songSelect!.FilterControl.CurrentTextSearch.Value = "keys=3");
AddAssert("filter count is 3", () => songSelect!.FilterCount, () => Is.EqualTo(3));
// Change filterable mod. Should re-filter.
AddStep("change new filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModKey5() });
AddAssert("filter count is 4", () => songSelect!.FilterCount, () => Is.EqualTo(4));
// Add non-filterable mod. Should NOT re-filter.
AddStep("apply non-filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModNoFail(), new ManiaModKey5() });
AddAssert("filter count is 4", () => songSelect!.FilterCount, () => Is.EqualTo(4));
// Remove filterable mod. Should re-filter.
AddStep("remove filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModNoFail() });
AddAssert("filter count is 5", () => songSelect!.FilterCount, () => Is.EqualTo(5));
// Remove non-filterable mod. Should NOT re-filter.
AddStep("remove filterable mod", () => SelectedMods.Value = Array.Empty<Mod>());
AddAssert("filter count is 5", () => songSelect!.FilterCount, () => Is.EqualTo(5));
// Add filterable mod. Should re-filter.
AddStep("add filterable mod", () => SelectedMods.Value = new Mod[] { new ManiaModKey3() });
AddAssert("filter count is 6", () => songSelect!.FilterCount, () => Is.EqualTo(6));
}
private void waitForInitialSelection() private void waitForInitialSelection()
{ {
AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault); AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault);

View File

@ -16,6 +16,7 @@ using osu.Game.Extensions;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
using osu.Game.Performance;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Scoring.Legacy; using osu.Game.Scoring.Legacy;
@ -51,6 +52,9 @@ namespace osu.Game.Database
[Resolved] [Resolved]
private ILocalUserPlayInfo? localUserPlayInfo { get; set; } private ILocalUserPlayInfo? localUserPlayInfo { get; set; }
[Resolved]
private IHighPerformanceSessionManager? highPerformanceSessionManager { get; set; }
[Resolved] [Resolved]
private INotificationOverlay? notificationOverlay { get; set; } private INotificationOverlay? notificationOverlay { get; set; }
@ -493,7 +497,9 @@ namespace osu.Game.Database
private void sleepIfRequired() private void sleepIfRequired()
{ {
while (localUserPlayInfo?.IsPlaying.Value == true) // Importantly, also sleep if high performance session is active.
// If we don't do this, memory usage can become runaway due to GC running in a more lenient mode.
while (localUserPlayInfo?.IsPlaying.Value == true || highPerformanceSessionManager?.IsSessionActive == true)
{ {
Logger.Log("Background processing sleeping due to active gameplay..."); Logger.Log("Background processing sleeping due to active gameplay...");
Thread.Sleep(TimeToSleepDuringGameplay); Thread.Sleep(TimeToSleepDuringGameplay);

View File

@ -454,6 +454,7 @@ namespace osu.Game.Overlays.SkinEditor
} }
SelectedComponents.Add(component); SelectedComponents.Add(component);
SkinSelectionHandler.ApplyClosestAnchor(drawableComponent);
return true; return true;
} }
@ -666,8 +667,6 @@ namespace osu.Game.Overlays.SkinEditor
SelectedComponents.Clear(); SelectedComponents.Clear();
placeComponent(sprite, false); placeComponent(sprite, false);
SkinSelectionHandler.ApplyClosestAnchor(sprite);
}); });
return Task.CompletedTask; return Task.CompletedTask;

View File

@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Toolbar
{ {
public partial class ToolbarMusicButton : ToolbarOverlayToggleButton public partial class ToolbarMusicButton : ToolbarOverlayToggleButton
{ {
private Circle volumeBar; private Box volumeBar;
protected override Anchor TooltipAnchor => Anchor.TopRight; protected override Anchor TooltipAnchor => Anchor.TopRight;
@ -37,7 +37,7 @@ namespace osu.Game.Overlays.Toolbar
StateContainer = music; StateContainer = music;
Flow.Padding = new MarginPadding { Horizontal = Toolbar.HEIGHT / 4 }; Flow.Padding = new MarginPadding { Horizontal = Toolbar.HEIGHT / 4 };
Flow.Add(volumeDisplay = new Container Flow.Add(volumeDisplay = new CircularContainer
{ {
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
@ -47,12 +47,12 @@ namespace osu.Game.Overlays.Toolbar
Masking = true, Masking = true,
Children = new[] Children = new[]
{ {
new Circle new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.White.Opacity(0.25f), Colour = Color4.White.Opacity(0.25f),
}, },
volumeBar = new Circle volumeBar = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Height = 0f, Height = 0f,

View File

@ -40,10 +40,12 @@ namespace osu.Game.Overlays
protected override void PopOut() protected override void PopOut()
{ {
base.PopOut();
Waves.Hide(); Waves.Hide();
this.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InQuint); this.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InQuint)
// base call is responsible for stopping preview tracks.
// delay it until the fade has concluded to ensure that nothing inside the overlay has triggered
// another preview track playback in the meantime, leaving an "orphaned" preview playing.
.OnComplete(_ => base.PopOut());
} }
} }
} }

View File

@ -14,6 +14,11 @@ namespace osu.Game.Performance
/// </summary> /// </summary>
public interface IHighPerformanceSessionManager public interface IHighPerformanceSessionManager
{ {
/// <summary>
/// Whether a high performance session is currently active.
/// </summary>
bool IsSessionActive { get; }
/// <summary> /// <summary>
/// Start a new high performance session. /// Start a new high performance session.
/// </summary> /// </summary>

View File

@ -1,7 +1,10 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Framework.Bindables;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Filter; using osu.Game.Screens.Select.Filter;
@ -52,5 +55,12 @@ namespace osu.Game.Rulesets.Filter
/// while ignored criteria are included in <see cref="FilterCriteria.SearchText"/>. /// while ignored criteria are included in <see cref="FilterCriteria.SearchText"/>.
/// </returns> /// </returns>
bool TryParseCustomKeywordCriteria(string key, Operator op, string value); bool TryParseCustomKeywordCriteria(string key, Operator op, string value);
/// <summary>
/// Whether to reapply the filter as a result of the given change in applied mods.
/// </summary>
/// <param name="mods">The change in mods.</param>
/// <returns>Whether the filter should be re-applied.</returns>
bool FilterMayChangeFromMods(ValueChangedEvent<IReadOnlyList<Mod>> mods);
} }
} }

View File

@ -42,6 +42,17 @@ namespace osu.Game.Rulesets.Objects
private readonly List<double> cumulativeLength = new List<double>(); private readonly List<double> cumulativeLength = new List<double>();
private readonly Cached pathCache = new Cached(); private readonly Cached pathCache = new Cached();
/// <summary>
/// Any additional length of the path which was optimised out during piecewise approximation, but should still be considered as part of <see cref="calculatedLength"/>.
/// </summary>
/// <remarks>
/// This is a hack for Catmull paths.
/// </remarks>
private double optimisedLength;
/// <summary>
/// The final calculated length of the path.
/// </summary>
private double calculatedLength; private double calculatedLength;
private readonly List<int> segmentEnds = new List<int>(); private readonly List<int> segmentEnds = new List<int>();
@ -123,6 +134,24 @@ namespace osu.Game.Rulesets.Objects
} }
} }
private bool optimiseCatmull;
/// <summary>
/// Whether to optimise Catmull path segments, usually resulting in removing bulbs around stacked knots.
/// </summary>
/// <remarks>
/// This changes the path shape and should therefore not be used.
/// </remarks>
public bool OptimiseCatmull
{
get => optimiseCatmull;
set
{
optimiseCatmull = value;
invalidate();
}
}
/// <summary> /// <summary>
/// Computes the slider path until a given progress that ranges from 0 (beginning of the slider) /// Computes the slider path until a given progress that ranges from 0 (beginning of the slider)
/// to 1 (end of the slider) and stores the generated path in the given list. /// to 1 (end of the slider) and stores the generated path in the given list.
@ -244,6 +273,7 @@ namespace osu.Game.Rulesets.Objects
{ {
calculatedPath.Clear(); calculatedPath.Clear();
segmentEnds.Clear(); segmentEnds.Clear();
optimisedLength = 0;
if (ControlPoints.Count == 0) if (ControlPoints.Count == 0)
return; return;
@ -269,6 +299,7 @@ namespace osu.Game.Rulesets.Objects
else if (segmentVertices.Length > 1) else if (segmentVertices.Length > 1)
{ {
List<Vector2> subPath = calculateSubPath(segmentVertices, segmentType); List<Vector2> subPath = calculateSubPath(segmentVertices, segmentType);
// Skip the first vertex if it is the same as the last vertex from the previous segment // Skip the first vertex if it is the same as the last vertex from the previous segment
bool skipFirst = calculatedPath.Count > 0 && subPath.Count > 0 && calculatedPath.Last() == subPath[0]; bool skipFirst = calculatedPath.Count > 0 && subPath.Count > 0 && calculatedPath.Last() == subPath[0];
@ -295,6 +326,7 @@ namespace osu.Game.Rulesets.Objects
return PathApproximator.LinearToPiecewiseLinear(subControlPoints); return PathApproximator.LinearToPiecewiseLinear(subControlPoints);
case SplineType.PerfectCurve: case SplineType.PerfectCurve:
{
if (subControlPoints.Length != 3) if (subControlPoints.Length != 3)
break; break;
@ -305,9 +337,58 @@ namespace osu.Game.Rulesets.Objects
break; break;
return subPath; return subPath;
}
case SplineType.Catmull: case SplineType.Catmull:
return PathApproximator.CatmullToPiecewiseLinear(subControlPoints); {
List<Vector2> subPath = PathApproximator.CatmullToPiecewiseLinear(subControlPoints);
if (!OptimiseCatmull)
return subPath;
// At draw time, osu!stable optimises paths by only keeping piecewise segments that are 6px apart.
// For the most part we don't care about this optimisation, and its additional heuristics are hard to reproduce in every implementation.
//
// However, it matters for Catmull paths which form "bulbs" around sequential knots with identical positions,
// so we'll apply a very basic form of the optimisation here and return a length representing the optimised portion.
// The returned length is important so that the optimisation doesn't cause the path to get extended to match the value of ExpectedDistance.
List<Vector2> optimisedPath = new List<Vector2>(subPath.Count);
Vector2? lastStart = null;
double lengthRemovedSinceStart = 0;
for (int i = 0; i < subPath.Count; i++)
{
if (lastStart == null)
{
optimisedPath.Add(subPath[i]);
lastStart = subPath[i];
continue;
}
Debug.Assert(i > 0);
double distFromStart = Vector2.Distance(lastStart.Value, subPath[i]);
lengthRemovedSinceStart += Vector2.Distance(subPath[i - 1], subPath[i]);
// See PathApproximator.catmull_detail.
const int catmull_detail = 50;
const int catmull_segment_length = catmull_detail * 2;
// Either 6px from the start, the last vertex at every knot, or the end of the path.
if (distFromStart > 6 || (i + 1) % catmull_segment_length == 0 || i == subPath.Count - 1)
{
optimisedPath.Add(subPath[i]);
optimisedLength += lengthRemovedSinceStart - distFromStart;
lastStart = null;
lengthRemovedSinceStart = 0;
}
}
return optimisedPath;
}
} }
return PathApproximator.BSplineToPiecewiseLinear(subControlPoints, type.Degree ?? subControlPoints.Length); return PathApproximator.BSplineToPiecewiseLinear(subControlPoints, type.Degree ?? subControlPoints.Length);
@ -315,7 +396,7 @@ namespace osu.Game.Rulesets.Objects
private void calculateLength() private void calculateLength()
{ {
calculatedLength = 0; calculatedLength = optimisedLength;
cumulativeLength.Clear(); cumulativeLength.Clear();
cumulativeLength.Add(0); cumulativeLength.Add(0);

View File

@ -152,7 +152,7 @@ namespace osu.Game.Screens.Play
Logger.Log($"Please ensure that you are using the latest version of the official game releases.\n\n{whatWillHappen}", level: LogLevel.Important); Logger.Log($"Please ensure that you are using the latest version of the official game releases.\n\n{whatWillHappen}", level: LogLevel.Important);
break; break;
case @"invalid beatmap hash": case @"invalid beatmap_hash":
Logger.Log($"This beatmap does not match the online version. Please update or redownload it.\n\n{whatWillHappen}", level: LogLevel.Important); Logger.Log($"This beatmap does not match the online version. Please update or redownload it.\n\n{whatWillHappen}", level: LogLevel.Important);
break; break;

View File

@ -4,6 +4,7 @@
#nullable disable #nullable disable
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -17,6 +18,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Resources.Localisation.Web; using osu.Game.Resources.Localisation.Web;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Localisation; using osu.Game.Localisation;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Screens.Ranking.Expanded.Statistics namespace osu.Game.Screens.Ranking.Expanded.Statistics
{ {
@ -74,7 +76,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics
Alpha = 0.5f; Alpha = 0.5f;
TooltipText = ResultsScreenStrings.NoPPForUnrankedBeatmaps; TooltipText = ResultsScreenStrings.NoPPForUnrankedBeatmaps;
} }
else if (scoreInfo.Mods.Any(m => !m.Ranked)) else if (hasUnrankedMods(scoreInfo))
{ {
Alpha = 0.5f; Alpha = 0.5f;
TooltipText = ResultsScreenStrings.NoPPForUnrankedMods; TooltipText = ResultsScreenStrings.NoPPForUnrankedMods;
@ -87,6 +89,16 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics
} }
} }
private static bool hasUnrankedMods(ScoreInfo scoreInfo)
{
IEnumerable<Mod> modsToCheck = scoreInfo.Mods;
if (scoreInfo.IsLegacyScore)
modsToCheck = modsToCheck.Where(m => m is not ModClassic);
return modsToCheck.Any(m => !m.Ranked);
}
public override void Appear() public override void Appear()
{ {
base.Appear(); base.Appear();

View File

@ -7,6 +7,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -49,15 +50,14 @@ namespace osu.Game.Screens.Select
} }
private OsuTabControl<SortMode> sortTabs; private OsuTabControl<SortMode> sortTabs;
private Bindable<SortMode> sortMode; private Bindable<SortMode> sortMode;
private Bindable<GroupMode> groupMode; private Bindable<GroupMode> groupMode;
private FilterControlTextBox searchTextBox; private FilterControlTextBox searchTextBox;
private CollectionDropdown collectionDropdown; private CollectionDropdown collectionDropdown;
[CanBeNull]
private FilterCriteria currentCriteria;
public FilterCriteria CreateCriteria() public FilterCriteria CreateCriteria()
{ {
string query = searchTextBox.Text; string query = searchTextBox.Text;
@ -228,7 +228,8 @@ namespace osu.Game.Screens.Select
if (m.NewValue.SequenceEqual(m.OldValue)) if (m.NewValue.SequenceEqual(m.OldValue))
return; return;
updateCriteria(); if (currentCriteria?.RulesetCriteria?.FilterMayChangeFromMods(m) == true)
updateCriteria();
}); });
groupMode.BindValueChanged(_ => updateCriteria()); groupMode.BindValueChanged(_ => updateCriteria());
@ -263,7 +264,7 @@ namespace osu.Game.Screens.Select
private readonly Bindable<double> minimumStars = new BindableDouble(); private readonly Bindable<double> minimumStars = new BindableDouble();
private readonly Bindable<double> maximumStars = new BindableDouble(); private readonly Bindable<double> maximumStars = new BindableDouble();
private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria()); private void updateCriteria() => FilterChanged?.Invoke(currentCriteria = CreateCriteria());
protected override bool OnClick(ClickEvent e) => true; protected override bool OnClick(ClickEvent e) => true;

View File

@ -39,12 +39,13 @@ namespace osu.Game.Utils
public SentryLogger(OsuGame game) public SentryLogger(OsuGame game)
{ {
this.game = game; this.game = game;
if (!game.IsDeployedBuild || !game.CreateEndpoints().WebsiteRootUrl.EndsWith(@".ppy.sh", StringComparison.Ordinal))
return;
sentrySession = SentrySdk.Init(options => sentrySession = SentrySdk.Init(options =>
{ {
// Not setting the dsn will completely disable sentry. options.Dsn = "https://ad9f78529cef40ac874afb95a9aca04e@sentry.ppy.sh/2";
if (game.IsDeployedBuild && game.CreateEndpoints().WebsiteRootUrl.EndsWith(@".ppy.sh", StringComparison.Ordinal))
options.Dsn = "https://ad9f78529cef40ac874afb95a9aca04e@sentry.ppy.sh/2";
options.AutoSessionTracking = true; options.AutoSessionTracking = true;
options.IsEnvironmentUser = false; options.IsEnvironmentUser = false;
options.IsGlobalModeEnabled = true; options.IsGlobalModeEnabled = true;
@ -59,12 +60,15 @@ namespace osu.Game.Utils
public void AttachUser(IBindable<APIUser> user) public void AttachUser(IBindable<APIUser> user)
{ {
if (sentrySession == null)
return;
Debug.Assert(localUser == null); Debug.Assert(localUser == null);
localUser = user.GetBoundCopy(); localUser = user.GetBoundCopy();
localUser.BindValueChanged(u => localUser.BindValueChanged(u =>
{ {
SentrySdk.ConfigureScope(scope => scope.User = new User SentrySdk.ConfigureScope(scope => scope.User = new SentryUser
{ {
Username = u.NewValue.Username, Username = u.NewValue.Username,
Id = u.NewValue.Id.ToString(), Id = u.NewValue.Id.ToString(),

View File

@ -36,8 +36,8 @@
</PackageReference> </PackageReference>
<PackageReference Include="Realm" Version="11.5.0" /> <PackageReference Include="Realm" Version="11.5.0" />
<PackageReference Include="ppy.osu.Framework" Version="2024.418.0" /> <PackageReference Include="ppy.osu.Framework" Version="2024.418.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.404.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2024.410.0" />
<PackageReference Include="Sentry" Version="3.41.3" /> <PackageReference Include="Sentry" Version="4.3.0" />
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. --> <!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
<PackageReference Include="SharpCompress" Version="0.36.0" /> <PackageReference Include="SharpCompress" Version="0.36.0" />
<PackageReference Include="NUnit" Version="3.14.0" /> <PackageReference Include="NUnit" Version="3.14.0" />

View File

@ -774,9 +774,19 @@ See the LICENCE file in the repository root for full licence text.&#xD;
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=StaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=StaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypeParameters/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypeParameters/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=15b5b1f1_002D457c_002D4ca6_002Db278_002D5615aedc07d3/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=236f7aa5_002D7b06_002D43ca_002Dbf2a_002D9b31bfcff09a/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="CONSTANT_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=2c62818f_002D621b_002D4425_002Dadc9_002D78611099bfcb/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Type parameters"&gt;&lt;ElementKinds&gt;&lt;Kind Name="TYPE_PARAMETER" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=4a98fdf6_002D7d98_002D4f5a_002Dafeb_002Dea44ad98c70c/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=669e5282_002Dfb4b_002D4e90_002D91e7_002D07d269d04b60/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Constant fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="CONSTANT_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=76f79b1e_002Dece7_002D4df2_002Da322_002D1bd7fea25eb7/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local functions"&gt;&lt;ElementKinds&gt;&lt;Kind Name="LOCAL_FUNCTION" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=8b8504e3_002Df0be_002D4c14_002D9103_002Dc732f2bddc15/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Enum members"&gt;&lt;ElementKinds&gt;&lt;Kind Name="ENUM_MEMBER" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=9d1af99b_002Dbefe_002D48a4_002D9eb3_002D661384e29869/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private methods"&gt;&lt;ElementKinds&gt;&lt;Kind Name="ASYNC_METHOD" /&gt;&lt;Kind Name="METHOD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=9d1af99b_002Dbefe_002D48a4_002D9eb3_002D661384e29869/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private methods"&gt;&lt;ElementKinds&gt;&lt;Kind Name="ASYNC_METHOD" /&gt;&lt;Kind Name="METHOD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=9ffbe43b_002Dc610_002D411b_002D9839_002D1416a146d9b0/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public methods"&gt;&lt;ElementKinds&gt;&lt;Kind Name="ASYNC_METHOD" /&gt;&lt;Kind Name="METHOD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=9ffbe43b_002Dc610_002D411b_002D9839_002D1416a146d9b0/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public methods"&gt;&lt;ElementKinds&gt;&lt;Kind Name="ASYNC_METHOD" /&gt;&lt;Kind Name="METHOD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a4c2df6c_002Db202_002D48d5_002Db077_002De678cb548c25/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private properties"&gt;&lt;ElementKinds&gt;&lt;Kind Name="PROPERTY" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a4c2df6c_002Db202_002D48d5_002Db077_002De678cb548c25/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private properties"&gt;&lt;ElementKinds&gt;&lt;Kind Name="PROPERTY" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a4f433b8_002Dabcd_002D4e55_002Da08f_002D82e78cef0f0c/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local constants"&gt;&lt;ElementKinds&gt;&lt;Kind Name="LOCAL_CONSTANT" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=c873eafb_002Dd57f_002D481d_002D8c93_002D77f6863c2f88/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static readonly fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=f9fce829_002De6f4_002D4cb2_002D80f1_002D5497c44f51df/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=fd562728_002Dc23d_002D417f_002Da19f_002D9d854247fbea/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public properties"&gt;&lt;ElementKinds&gt;&lt;Kind Name="PROPERTY" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=fd562728_002Dc23d_002D417f_002Da19f_002D9d854247fbea/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public properties"&gt;&lt;ElementKinds&gt;&lt;Kind Name="PROPERTY" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FCONSTANT/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FCONSTANT/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FFUNCTION/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FFUNCTION/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
@ -841,6 +851,7 @@ See the LICENCE file in the repository root for full licence text.&#xD;
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/Environment/UnitTesting/NUnitProvider/SetCurrentDirectoryTo/@EntryValue">TestFolder</s:String> <s:String x:Key="/Default/Environment/UnitTesting/NUnitProvider/SetCurrentDirectoryTo/@EntryValue">TestFolder</s:String>
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/@KeyIndexDefined">True</s:Boolean> <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/@KeyIndexDefined">True</s:Boolean>
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Applicability/=Live/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>