Merge branch 'master' into labelled-switch-button

This commit is contained in:
Dean Herbert 2019-10-04 11:15:54 +08:00 committed by GitHub
commit a7b58cad99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 303 additions and 213 deletions

View File

@ -62,6 +62,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.913.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2019.913.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.924.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2019.930.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -7,10 +7,16 @@ using System.Linq;
using System.Threading; using System.Threading;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -18,25 +24,49 @@ using osu.Game.Scoring;
using osu.Game.Screens; using osu.Game.Screens;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Play.PlayerSettings;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
{ {
public class TestScenePlayerLoader : ManualInputManagerTestScene public class TestScenePlayerLoader : ManualInputManagerTestScene
{ {
private TestPlayerLoader loader; private TestPlayerLoader loader;
private OsuScreenStack stack; private TestPlayerLoaderContainer container;
private TestPlayer player;
[SetUp] [Resolved]
public void Setup() => Schedule(() => private AudioManager audioManager { get; set; }
[Resolved]
private SessionStatics sessionStatics { get; set; }
/// <summary>
/// Sets the input manager child to a new test player loader container instance.
/// </summary>
/// <param name="interactive">If the test player should behave like the production one.</param>
/// <param name="beforeLoadAction">An action to run before player load but after bindable leases are returned.</param>
/// <param name="afterLoadAction">An action to run after container load.</param>
public void ResetPlayer(bool interactive, Action beforeLoadAction = null, Action afterLoadAction = null)
{ {
InputManager.Child = stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }; audioManager.Volume.SetDefault();
InputManager.Clear();
beforeLoadAction?.Invoke();
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
});
InputManager.Child = container = new TestPlayerLoaderContainer(
loader = new TestPlayerLoader(() =>
{
afterLoadAction?.Invoke();
return player = new TestPlayer(interactive, interactive);
}));
}
[Test] [Test]
public void TestBlockLoadViaMouseMovement() public void TestBlockLoadViaMouseMovement()
{ {
AddStep("load dummy beatmap", () => stack.Push(loader = new TestPlayerLoader(() => new TestPlayer(false, false)))); AddStep("load dummy beatmap", () => ResetPlayer(false));
AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddUntilStep("wait for current", () => loader.IsCurrentScreen());
AddRepeatStep("move mouse", () => InputManager.MoveMouseTo(loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft + (loader.VisualSettings.ScreenSpaceDrawQuad.BottomRight - loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft) * RNG.NextSingle()), 20); AddRepeatStep("move mouse", () => InputManager.MoveMouseTo(loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft + (loader.VisualSettings.ScreenSpaceDrawQuad.BottomRight - loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft) * RNG.NextSingle()), 20);
AddAssert("loader still active", () => loader.IsCurrentScreen()); AddAssert("loader still active", () => loader.IsCurrentScreen());
@ -46,16 +76,17 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestLoadContinuation() public void TestLoadContinuation()
{ {
Player player = null;
SlowLoadPlayer slowPlayer = null; SlowLoadPlayer slowPlayer = null;
AddStep("load dummy beatmap", () => stack.Push(loader = new TestPlayerLoader(() => player = new TestPlayer(false, false)))); AddStep("load dummy beatmap", () => ResetPlayer(false));
AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddUntilStep("wait for current", () => loader.IsCurrentScreen());
AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre));
AddUntilStep("wait for player to be current", () => player.IsCurrentScreen()); AddUntilStep("wait for player to be current", () => player.IsCurrentScreen());
AddStep("load slow dummy beatmap", () => AddStep("load slow dummy beatmap", () =>
{ {
stack.Push(loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false))); InputManager.Child = container = new TestPlayerLoaderContainer(
loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false)));
Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000); Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000);
}); });
@ -65,16 +96,11 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestModReinstantiation() public void TestModReinstantiation()
{ {
TestPlayer player = null;
TestMod gameMod = null; TestMod gameMod = null;
TestMod playerMod1 = null; TestMod playerMod1 = null;
TestMod playerMod2 = null; TestMod playerMod2 = null;
AddStep("load player", () => AddStep("load player", () => { ResetPlayer(true, () => Mods.Value = new[] { gameMod = new TestMod() }); });
{
Mods.Value = new[] { gameMod = new TestMod() };
stack.Push(loader = new TestPlayerLoader(() => player = new TestPlayer()));
});
AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen()); AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen());
AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre));
@ -97,6 +123,75 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("player mods applied", () => playerMod2.Applied); AddAssert("player mods applied", () => playerMod2.Applied);
} }
[Test]
public void TestMutedNotificationMasterVolume() => addVolumeSteps("master volume", () => audioManager.Volume.Value = 0, null, () => audioManager.Volume.IsDefault);
[Test]
public void TestMutedNotificationTrackVolume() => addVolumeSteps("music volume", () => audioManager.VolumeTrack.Value = 0, null, () => audioManager.VolumeTrack.IsDefault);
[Test]
public void TestMutedNotificationMuteButton() => addVolumeSteps("mute button", null, () => container.VolumeOverlay.IsMuted.Value = true, () => !container.VolumeOverlay.IsMuted.Value);
/// <remarks>
/// Created for avoiding copy pasting code for the same steps.
/// </remarks>
/// <param name="volumeName">What part of the volume system is checked</param>
/// <param name="beforeLoad">The action to be invoked to set the volume before loading</param>
/// <param name="afterLoad">The action to be invoked to set the volume after loading</param>
/// <param name="assert">The function to be invoked and checked</param>
private void addVolumeSteps(string volumeName, Action beforeLoad, Action afterLoad, Func<bool> assert)
{
AddStep("reset notification lock", () => sessionStatics.GetBindable<bool>(Static.MutedAudioNotificationShownOnce).Value = false);
AddStep("load player", () => ResetPlayer(false, beforeLoad, afterLoad));
AddUntilStep("wait for player", () => player.IsLoaded);
AddAssert("check for notification", () => container.NotificationOverlay.UnreadCount.Value == 1);
AddStep("click notification", () =>
{
var scrollContainer = (OsuScrollContainer)container.NotificationOverlay.Children.Last();
var flowContainer = scrollContainer.Children.OfType<FillFlowContainer<NotificationSection>>().First();
var notification = flowContainer.First();
InputManager.MoveMouseTo(notification);
InputManager.Click(MouseButton.Left);
});
AddAssert("check " + volumeName, assert);
}
private class TestPlayerLoaderContainer : Container
{
[Cached]
public readonly NotificationOverlay NotificationOverlay;
[Cached]
public readonly VolumeOverlay VolumeOverlay;
public TestPlayerLoaderContainer(IScreen screen)
{
RelativeSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
new OsuScreenStack(screen)
{
RelativeSizeAxes = Axes.Both,
},
NotificationOverlay = new NotificationOverlay
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
},
VolumeOverlay = new VolumeOverlay
{
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
}
};
}
}
private class TestPlayerLoader : PlayerLoader private class TestPlayerLoader : PlayerLoader
{ {
public new VisualSettings VisualSettings => base.VisualSettings; public new VisualSettings VisualSettings => base.VisualSettings;

View File

@ -32,6 +32,12 @@ namespace osu.Game.Tests.Visual.Online
Id = 4, Id = 4,
}; };
private readonly User longUsernameUser = new User
{
Username = "Very Long Long Username",
Id = 5,
};
[Cached] [Cached]
private ChannelManager channelManager = new ChannelManager(); private ChannelManager channelManager = new ChannelManager();
@ -99,6 +105,12 @@ namespace osu.Game.Tests.Visual.Online
Sender = admin, Sender = admin,
Content = "Okay okay, calm down guys. Let's do this!" Content = "Okay okay, calm down guys. Let's do this!"
})); }));
AddStep("message from long username", () => testChannel.AddNewMessages(new Message(sequence++)
{
Sender = longUsernameUser,
Content = "Hi guys, my new username is lit!"
}));
} }
} }
} }

View File

@ -107,6 +107,15 @@ namespace osu.Game.Tests.Visual.Online
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg"
}, api.IsLoggedIn)); }, api.IsLoggedIn));
AddStep("Show bancho", () => profile.ShowUser(new User
{
Username = @"BanchoBot",
Id = 3,
IsBot = true,
Country = new Country { FullName = @"Saint Helena", FlagName = @"SH" },
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c4.jpg"
}, api.IsLoggedIn));
AddStep("Hide", profile.Hide); AddStep("Hide", profile.Hide);
AddStep("Show without reload", profile.Show); AddStep("Show without reload", profile.Show);
} }

View File

@ -239,6 +239,18 @@ namespace osu.Game.Tests.Visual.SongSelect
AddAssert("Selection is non-null", () => currentSelection != null); AddAssert("Selection is non-null", () => currentSelection != null);
setSelected(1, 3); setSelected(1, 3);
}
[Test]
public void TestFilterRange()
{
loadBeatmaps();
// buffer the selection
setSelected(3, 2);
setSelected(1, 3);
AddStep("Apply a range filter", () => carousel.Filter(new FilterCriteria AddStep("Apply a range filter", () => carousel.Filter(new FilterCriteria
{ {
SearchText = "#3", SearchText = "#3",
@ -249,9 +261,9 @@ namespace osu.Game.Tests.Visual.SongSelect
IsLowerInclusive = true IsLowerInclusive = true
} }
}, false)); }, false));
waitForSelection(3, 2);
AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); // should reselect the buffered selection.
waitForSelection(3, 2);
} }
/// <summary> /// <summary>

View File

@ -1,42 +0,0 @@
// 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.Framework.Bindables;
namespace osu.Game.Beatmaps
{
/// <summary>
/// A <see cref="Bindable{T}"/> for the <see cref="OsuGame"/> beatmap.
/// This should be used sparingly in-favour of <see cref="IBindable{WorkingBeatmap}"/>.
/// </summary>
public abstract class BindableBeatmap : NonNullableBindable<WorkingBeatmap>
{
private WorkingBeatmap lastBeatmap;
protected BindableBeatmap(WorkingBeatmap defaultValue)
: base(defaultValue)
{
BindValueChanged(b => updateAudioTrack(b.NewValue), true);
}
private void updateAudioTrack(WorkingBeatmap beatmap)
{
var trackLoaded = lastBeatmap?.TrackLoaded ?? false;
// compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo)
if (!trackLoaded || lastBeatmap?.Track != beatmap.Track)
{
if (trackLoaded)
{
Debug.Assert(lastBeatmap != null);
Debug.Assert(lastBeatmap.Track != null);
lastBeatmap.RecycleTrack();
}
}
lastBeatmap = beatmap;
}
}
}

View File

@ -114,7 +114,7 @@ namespace osu.Game.Configuration
Set(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f); Set(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f);
Set(OsuSetting.UIHoldActivationDelay, 200, 0, 500); Set(OsuSetting.UIHoldActivationDelay, 200f, 0f, 500f, 50f);
Set(OsuSetting.IntroSequence, IntroSequence.Triangles); Set(OsuSetting.IntroSequence, IntroSequence.Triangles);
} }

View File

@ -11,11 +11,13 @@ namespace osu.Game.Configuration
protected override void InitialiseDefaults() protected override void InitialiseDefaults()
{ {
Set(Static.LoginOverlayDisplayed, false); Set(Static.LoginOverlayDisplayed, false);
Set(Static.MutedAudioNotificationShownOnce, false);
} }
} }
public enum Static public enum Static
{ {
LoginOverlayDisplayed, LoginOverlayDisplayed,
MutedAudioNotificationShownOnce
} }
} }

View File

@ -400,20 +400,17 @@ namespace osu.Game.Database
int i = 0; int i = 0;
using (ContextFactory.GetForWrite()) foreach (var b in items)
{ {
foreach (var b in items) if (notification.State == ProgressNotificationState.Cancelled)
{ // user requested abort
if (notification.State == ProgressNotificationState.Cancelled) return;
// user requested abort
return;
notification.Text = $"Deleting {HumanisedModelName}s ({++i} of {items.Count})"; notification.Text = $"Deleting {HumanisedModelName}s ({++i} of {items.Count})";
Delete(b); Delete(b);
notification.Progress = (float)i / items.Count; notification.Progress = (float)i / items.Count;
}
} }
notification.State = ProgressNotificationState.Completed; notification.State = ProgressNotificationState.Completed;
@ -439,20 +436,17 @@ namespace osu.Game.Database
int i = 0; int i = 0;
using (ContextFactory.GetForWrite()) foreach (var item in items)
{ {
foreach (var item in items) if (notification.State == ProgressNotificationState.Cancelled)
{ // user requested abort
if (notification.State == ProgressNotificationState.Cancelled) return;
// user requested abort
return;
notification.Text = $"Restoring ({++i} of {items.Count})"; notification.Text = $"Restoring ({++i} of {items.Count})";
Undelete(item); Undelete(item);
notification.Progress = (float)i / items.Count; notification.Progress = (float)i / items.Count;
}
} }
notification.State = ProgressNotificationState.Completed; notification.State = ProgressNotificationState.Completed;

View File

@ -30,12 +30,12 @@ namespace osu.Game.Graphics.Containers
public Bindable<double> Progress = new BindableDouble(); public Bindable<double> Progress = new BindableDouble();
private Bindable<int> holdActivationDelay; private Bindable<float> holdActivationDelay;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager config) private void load(OsuConfigManager config)
{ {
holdActivationDelay = config.GetBindable<int>(OsuSetting.UIHoldActivationDelay); holdActivationDelay = config.GetBindable<float>(OsuSetting.UIHoldActivationDelay);
} }
protected void BeginConfirm() protected void BeginConfirm()

View File

@ -18,7 +18,7 @@ namespace osu.Game.Graphics.UserInterface
public BackButton(Receptor receptor) public BackButton(Receptor receptor)
{ {
receptor.OnBackPressed = () => Action?.Invoke(); receptor.OnBackPressed = () => button.Click();
Size = TwoLayerButton.SIZE_EXTENDED; Size = TwoLayerButton.SIZE_EXTENDED;

View File

@ -2,22 +2,20 @@
// 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 osuTK.Graphics; using osuTK.Graphics;
using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osuTK.Input; using osuTK.Input;
using osu.Framework.Input.Bindings;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
/// <summary> /// <summary>
/// A textbox which holds focus eagerly. /// A textbox which holds focus eagerly.
/// </summary> /// </summary>
public class FocusedTextBox : OsuTextBox public class FocusedTextBox : OsuTextBox, IKeyBindingHandler<GlobalAction>
{ {
public Action Exit;
private bool focus; private bool focus;
private bool allowImmediateFocus => host?.OnScreenKeyboardOverlapsGameWindow != true; private bool allowImmediateFocus => host?.OnScreenKeyboardOverlapsGameWindow != true;
@ -63,12 +61,12 @@ namespace osu.Game.Graphics.UserInterface
if (!HasFocus) return false; if (!HasFocus) return false;
if (e.Key == Key.Escape) if (e.Key == Key.Escape)
return false; // disable the framework-level handling of escape key for confority (we use GlobalAction.Back). return false; // disable the framework-level handling of escape key for conformity (we use GlobalAction.Back).
return base.OnKeyDown(e); return base.OnKeyDown(e);
} }
public override bool OnPressed(GlobalAction action) public bool OnPressed(GlobalAction action)
{ {
if (action == GlobalAction.Back) if (action == GlobalAction.Back)
{ {
@ -79,14 +77,10 @@ namespace osu.Game.Graphics.UserInterface
} }
} }
return base.OnPressed(action); return false;
} }
protected override void KillFocus() public bool OnReleased(GlobalAction action) => false;
{
base.KillFocus();
Exit?.Invoke();
}
public override bool RequestsFocus => HoldFocus; public override bool RequestsFocus => HoldFocus;
} }

View File

@ -8,13 +8,11 @@ using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Input.Bindings;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
public class OsuTextBox : TextBox, IKeyBindingHandler<GlobalAction> public class OsuTextBox : TextBox
{ {
protected override float LeftRightPadding => 10; protected override float LeftRightPadding => 10;
@ -57,18 +55,5 @@ namespace osu.Game.Graphics.UserInterface
} }
protected override Drawable GetDrawableCharacter(char c) => new OsuSpriteText { Text = c.ToString(), Font = OsuFont.GetFont(size: CalculatedTextSize) }; protected override Drawable GetDrawableCharacter(char c) => new OsuSpriteText { Text = c.ToString(), Font = OsuFont.GetFont(size: CalculatedTextSize) };
public virtual bool OnPressed(GlobalAction action)
{
if (action == GlobalAction.Back)
{
KillFocus();
return true;
}
return false;
}
public bool OnReleased(GlobalAction action) => false;
} }
} }

View File

@ -21,8 +21,6 @@ namespace osu.Game.Online.Chat
{ {
public readonly Bindable<Channel> Channel = new Bindable<Channel>(); public readonly Bindable<Channel> Channel = new Bindable<Channel>();
public Action Exit;
private readonly FocusedTextBox textbox; private readonly FocusedTextBox textbox;
protected ChannelManager ChannelManager; protected ChannelManager ChannelManager;
@ -66,8 +64,6 @@ namespace osu.Game.Online.Chat
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
}); });
textbox.Exit += () => Exit?.Invoke();
} }
Channel.BindValueChanged(channelChanged); Channel.BindValueChanged(channelChanged);
@ -146,6 +142,7 @@ namespace osu.Game.Online.Chat
protected override float HorizontalPadding => 10; protected override float HorizontalPadding => 10;
protected override float MessagePadding => 120; protected override float MessagePadding => 120;
protected override float TimestampPadding => 50;
public StandAloneMessage(Message message) public StandAloneMessage(Message message)
: base(message) : base(message)

View File

@ -488,7 +488,8 @@ namespace osu.Game
toolbarElements.Add(d); toolbarElements.Add(d);
}); });
loadComponentSingleFile(volume = new VolumeOverlay(), leftFloatingOverlayContent.Add); loadComponentSingleFile(volume = new VolumeOverlay(), leftFloatingOverlayContent.Add, true);
loadComponentSingleFile(new OnScreenDisplay(), Add, true); loadComponentSingleFile(new OnScreenDisplay(), Add, true);
loadComponentSingleFile(musicController = new MusicController(), Add, true); loadComponentSingleFile(musicController = new MusicController(), Add, true);

View File

@ -202,7 +202,13 @@ namespace osu.Game
// this adds a global reduction of track volume for the time being. // this adds a global reduction of track volume for the time being.
Audio.Tracks.AddAdjustment(AdjustableProperty.Volume, new BindableDouble(0.8)); Audio.Tracks.AddAdjustment(AdjustableProperty.Volume, new BindableDouble(0.8));
beatmap = new OsuBindableBeatmap(defaultBeatmap); beatmap = new NonNullableBindable<WorkingBeatmap>(defaultBeatmap);
beatmap.BindValueChanged(b => ScheduleAfterChildren(() =>
{
// compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo)
if (b.OldValue?.TrackLoaded == true && b.OldValue?.Track != b.NewValue?.Track)
b.OldValue.RecycleTrack();
}));
dependencies.CacheAs<IBindable<WorkingBeatmap>>(beatmap); dependencies.CacheAs<IBindable<WorkingBeatmap>>(beatmap);
dependencies.CacheAs(beatmap); dependencies.CacheAs(beatmap);
@ -292,14 +298,6 @@ namespace osu.Game
public string[] HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions).ToArray(); public string[] HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions).ToArray();
private class OsuBindableBeatmap : BindableBeatmap
{
public OsuBindableBeatmap(WorkingBeatmap defaultValue)
: base(defaultValue)
{
}
}
private class OsuUserInputManager : UserInputManager private class OsuUserInputManager : UserInputManager
{ {
protected override MouseButtonEventManager CreateButtonManagerFor(MouseButton button) protected override MouseButtonEventManager CreateButtonManagerFor(MouseButton button)

View File

@ -31,7 +31,9 @@ namespace osu.Game.Overlays.Chat
protected virtual float MessagePadding => default_message_padding; protected virtual float MessagePadding => default_message_padding;
private const float timestamp_padding = 65; private const float default_timestamp_padding = 65;
protected virtual float TimestampPadding => default_timestamp_padding;
private const float default_horizontal_padding = 15; private const float default_horizontal_padding = 15;
@ -94,7 +96,7 @@ namespace osu.Game.Overlays.Chat
Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Bold, italics: true), Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Bold, italics: true),
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
Origin = Anchor.TopRight, Origin = Anchor.TopRight,
MaxWidth = default_message_padding - timestamp_padding MaxWidth = MessagePadding - TimestampPadding
}; };
if (hasBackground) if (hasBackground)
@ -149,7 +151,6 @@ namespace osu.Game.Overlays.Chat
new MessageSender(message.Sender) new MessageSender(message.Sender)
{ {
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = timestamp_padding },
Origin = Anchor.TopRight, Origin = Anchor.TopRight,
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
Child = effectedUsername, Child = effectedUsername,

View File

@ -119,7 +119,6 @@ namespace osu.Game.Overlays.Chat.Selection
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
PlaceholderText = @"Search", PlaceholderText = @"Search",
Exit = Hide,
}, },
}, },
}, },

View File

@ -138,7 +138,6 @@ namespace osu.Game.Overlays
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Height = 1, Height = 1,
PlaceholderText = "type your message", PlaceholderText = "type your message",
Exit = Hide,
OnCommit = postMessage, OnCommit = postMessage,
ReleaseFocusOnCommit = false, ReleaseFocusOnCommit = false,
HoldFocus = true, HoldFocus = true,

View File

@ -31,7 +31,6 @@ namespace osu.Game.Overlays.Music
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = 40, Height = 40,
Exit = () => ExitRequested?.Invoke(),
}, },
new CollectionsDropdown<PlaylistCollection> new CollectionsDropdown<PlaylistCollection>
{ {
@ -47,8 +46,6 @@ namespace osu.Game.Overlays.Music
private void current_ValueChanged(ValueChangedEvent<string> e) => FilterChanged?.Invoke(e.NewValue); private void current_ValueChanged(ValueChangedEvent<string> e) => FilterChanged?.Invoke(e.NewValue);
public Action ExitRequested;
public Action<string> FilterChanged; public Action<string> FilterChanged;
public class FilterTextBox : SearchTextBox public class FilterTextBox : SearchTextBox

View File

@ -63,7 +63,6 @@ namespace osu.Game.Overlays.Music
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
ExitRequested = Hide,
FilterChanged = search => list.Filter(search), FilterChanged = search => list.Filter(search),
Padding = new MarginPadding(10), Padding = new MarginPadding(10),
}, },

View File

@ -75,7 +75,7 @@ namespace osu.Game.Overlays
/// <summary> /// <summary>
/// Returns whether the current beatmap track is playing. /// Returns whether the current beatmap track is playing.
/// </summary> /// </summary>
public bool IsPlaying => beatmap.Value.Track.IsRunning; public bool IsPlaying => current?.Track.IsRunning ?? false;
private void handleBeatmapAdded(BeatmapSetInfo set) => private void handleBeatmapAdded(BeatmapSetInfo set) =>
Schedule(() => beatmapSets.Add(set)); Schedule(() => beatmapSets.Add(set));

View File

@ -88,8 +88,6 @@ namespace osu.Game.Overlays.SearchableList
}, },
}, },
}; };
Filter.Search.Exit = Hide;
} }
protected override void Update() protected override void Update()

View File

@ -27,16 +27,16 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
LabelText = "Parallax", LabelText = "Parallax",
Bindable = config.GetBindable<bool>(OsuSetting.MenuParallax) Bindable = config.GetBindable<bool>(OsuSetting.MenuParallax)
}, },
new SettingsSlider<int, TimeSlider> new SettingsSlider<float, TimeSlider>
{ {
LabelText = "Hold-to-confirm activation time", LabelText = "Hold-to-confirm activation time",
Bindable = config.GetBindable<int>(OsuSetting.UIHoldActivationDelay), Bindable = config.GetBindable<float>(OsuSetting.UIHoldActivationDelay),
KeyboardStep = 50 KeyboardStep = 50
}, },
}; };
} }
private class TimeSlider : OsuSliderBar<int> private class TimeSlider : OsuSliderBar<float>
{ {
public override string TooltipText => Current.Value.ToString("N0") + "ms"; public override string TooltipText => Current.Value.ToString("N0") + "ms";
} }

View File

@ -91,7 +91,6 @@ namespace osu.Game.Overlays
Top = 20, Top = 20,
Bottom = 20 Bottom = 20
}, },
Exit = Hide,
}, },
Footer = CreateFooter() Footer = CreateFooter()
}, },

View File

@ -44,16 +44,21 @@ namespace osu.Game.Overlays
Clear(); Clear();
lastSection = null; lastSection = null;
sections = new ProfileSection[] sections = !user.IsBot
{ ? new ProfileSection[]
//new AboutSection(), {
new RecentSection(), //new AboutSection(),
new RanksSection(), new RecentSection(),
//new MedalsSection(), new RanksSection(),
new HistoricalSection(), //new MedalsSection(),
new BeatmapsSection(), new HistoricalSection(),
new KudosuSection() new BeatmapsSection(),
}; new KudosuSection()
}
: new ProfileSection[]
{
//new AboutSection(),
};
tabs = new ProfileTabControl tabs = new ProfileTabControl
{ {

View File

@ -32,6 +32,9 @@ namespace osu.Game.Overlays
private readonly BindableDouble muteAdjustment = new BindableDouble(); private readonly BindableDouble muteAdjustment = new BindableDouble();
private readonly Bindable<bool> isMuted = new Bindable<bool>();
public Bindable<bool> IsMuted => isMuted;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(AudioManager audio, OsuColour colours) private void load(AudioManager audio, OsuColour colours)
{ {
@ -64,7 +67,8 @@ namespace osu.Game.Overlays
volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker), volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker),
muteButton = new MuteButton muteButton = new MuteButton
{ {
Margin = new MarginPadding { Top = 100 } Margin = new MarginPadding { Top = 100 },
Current = { BindTarget = isMuted }
} }
} }
}, },
@ -74,13 +78,13 @@ namespace osu.Game.Overlays
volumeMeterEffect.Bindable.BindTo(audio.VolumeSample); volumeMeterEffect.Bindable.BindTo(audio.VolumeSample);
volumeMeterMusic.Bindable.BindTo(audio.VolumeTrack); volumeMeterMusic.Bindable.BindTo(audio.VolumeTrack);
muteButton.Current.ValueChanged += muted => isMuted.BindValueChanged(muted =>
{ {
if (muted.NewValue) if (muted.NewValue)
audio.AddAdjustment(AdjustableProperty.Volume, muteAdjustment); audio.AddAdjustment(AdjustableProperty.Volume, muteAdjustment);
else else
audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment); audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment);
}; });
} }
protected override void LoadComplete() protected override void LoadComplete()

View File

@ -135,9 +135,9 @@ namespace osu.Game.Rulesets
foreach (string file in files.Where(f => !Path.GetFileName(f).Contains("Tests"))) foreach (string file in files.Where(f => !Path.GetFileName(f).Contains("Tests")))
loadRulesetFromFile(file); loadRulesetFromFile(file);
} }
catch catch (Exception e)
{ {
Logger.Log($"Could not load rulesets from directory {Environment.CurrentDirectory}"); Logger.Error(e, $"Could not load rulesets from directory {Environment.CurrentDirectory}");
} }
} }

View File

@ -62,7 +62,7 @@ namespace osu.Game.Screens.Menu
protected override BackgroundScreen CreateBackground() => background; protected override BackgroundScreen CreateBackground() => background;
private Bindable<int> holdDelay; private Bindable<float> holdDelay;
private Bindable<bool> loginDisplayed; private Bindable<bool> loginDisplayed;
private ExitConfirmOverlay exitConfirmOverlay; private ExitConfirmOverlay exitConfirmOverlay;
@ -70,7 +70,7 @@ namespace osu.Game.Screens.Menu
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(DirectOverlay direct, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics) private void load(DirectOverlay direct, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics)
{ {
holdDelay = config.GetBindable<int>(OsuSetting.UIHoldActivationDelay); holdDelay = config.GetBindable<float>(OsuSetting.UIHoldActivationDelay);
loginDisplayed = statics.GetBindable<bool>(Static.LoginOverlayDisplayed); loginDisplayed = statics.GetBindable<bool>(Static.LoginOverlayDisplayed);
if (host.CanExit) if (host.CanExit)

View File

@ -69,8 +69,6 @@ namespace osu.Game.Screens.Multi.Lounge
}, },
}, },
}; };
Filter.Search.Exit += this.Exit;
} }
protected override void UpdateAfterChildren() protected override void UpdateAfterChildren()

View File

@ -62,7 +62,6 @@ namespace osu.Game.Screens.Multi.Match
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
MatchChatDisplay chat;
Components.Header header; Components.Header header;
Info info; Info info;
GridContainer bottomRow; GridContainer bottomRow;
@ -122,7 +121,7 @@ namespace osu.Game.Screens.Multi.Match
Vertical = 10, Vertical = 10,
}, },
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Child = chat = new MatchChatDisplay Child = new MatchChatDisplay
{ {
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both
} }
@ -159,12 +158,6 @@ namespace osu.Game.Screens.Multi.Match
bottomRow.FadeTo(settingsDisplayed ? 0 : 1, fade_duration, Easing.OutQuint); bottomRow.FadeTo(settingsDisplayed ? 0 : 1, fade_duration, Easing.OutQuint);
}, true); }, true);
chat.Exit += () =>
{
if (this.IsCurrentScreen())
this.Exit();
};
beatmapManager.ItemAdded += beatmapAdded; beatmapManager.ItemAdded += beatmapAdded;
} }

View File

@ -63,11 +63,11 @@ namespace osu.Game.Screens.Play.HUD
[Resolved] [Resolved]
private OsuConfigManager config { get; set; } private OsuConfigManager config { get; set; }
private Bindable<int> activationDelay; private Bindable<float> activationDelay;
protected override void LoadComplete() protected override void LoadComplete()
{ {
activationDelay = config.GetBindable<int>(OsuSetting.UIHoldActivationDelay); activationDelay = config.GetBindable<float>(OsuSetting.UIHoldActivationDelay);
activationDelay.BindValueChanged(v => activationDelay.BindValueChanged(v =>
{ {
text.Text = v.NewValue > 0 text.Text = v.NewValue > 0

View File

@ -6,6 +6,8 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
@ -14,11 +16,14 @@ using osu.Framework.Localisation;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Input; using osu.Game.Input;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
@ -53,9 +58,19 @@ namespace osu.Game.Screens.Play
private Task loadTask; private Task loadTask;
private InputManager inputManager; private InputManager inputManager;
private IdleTracker idleTracker; private IdleTracker idleTracker;
[Resolved(CanBeNull = true)]
private NotificationOverlay notificationOverlay { get; set; }
[Resolved(CanBeNull = true)]
private VolumeOverlay volumeOverlay { get; set; }
[Resolved]
private AudioManager audioManager { get; set; }
private Bindable<bool> muteWarningShownOnce;
public PlayerLoader(Func<Player> createPlayer) public PlayerLoader(Func<Player> createPlayer)
{ {
this.createPlayer = createPlayer; this.createPlayer = createPlayer;
@ -68,8 +83,10 @@ namespace osu.Game.Screens.Play
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load(SessionStatics sessionStatics)
{ {
muteWarningShownOnce = sessionStatics.GetBindable<bool>(Static.MutedAudioNotificationShownOnce);
InternalChild = (content = new LogoTrackingContainer InternalChild = (content = new LogoTrackingContainer
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
@ -103,7 +120,22 @@ namespace osu.Game.Screens.Play
loadNewPlayer(); loadNewPlayer();
} }
private void playerLoaded(Player player) => info.Loading = false; protected override void LoadComplete()
{
base.LoadComplete();
inputManager = GetContainingInputManager();
if (!muteWarningShownOnce.Value)
{
//Checks if the notification has not been shown yet and also if master volume is muted, track/music volume is muted or if the whole game is muted.
if (volumeOverlay?.IsMuted.Value == true || audioManager.Volume.Value <= audioManager.Volume.MinValue || audioManager.VolumeTrack.Value <= audioManager.VolumeTrack.MinValue)
{
notificationOverlay?.Post(new MutedNotification());
muteWarningShownOnce.Value = true;
}
}
}
public override void OnResuming(IScreen last) public override void OnResuming(IScreen last)
{ {
@ -127,7 +159,7 @@ namespace osu.Game.Screens.Play
player.RestartCount = restartCount; player.RestartCount = restartCount;
player.RestartRequested = restartRequested; player.RestartRequested = restartRequested;
loadTask = LoadComponentAsync(player, playerLoaded); loadTask = LoadComponentAsync(player, _ => info.Loading = false);
} }
private void contentIn() private void contentIn()
@ -185,12 +217,6 @@ namespace osu.Game.Screens.Play
content.StopTracking(); content.StopTracking();
} }
protected override void LoadComplete()
{
inputManager = GetContainingInputManager();
base.LoadComplete();
}
private ScheduledDelegate pushDebounce; private ScheduledDelegate pushDebounce;
protected VisualSettings VisualSettings; protected VisualSettings VisualSettings;
@ -473,5 +499,33 @@ namespace osu.Game.Screens.Play
Loading = true; Loading = true;
} }
} }
private class MutedNotification : SimpleNotification
{
public MutedNotification()
{
Text = "Your music volume is set to 0%! Click here to restore it.";
}
public override bool IsImportant => true;
[BackgroundDependencyLoader]
private void load(OsuColour colours, AudioManager audioManager, NotificationOverlay notificationOverlay, VolumeOverlay volumeOverlay)
{
Icon = FontAwesome.Solid.VolumeMute;
IconBackgound.Colour = colours.RedDark;
Activated = delegate
{
notificationOverlay.Hide();
volumeOverlay.IsMuted.Value = false;
audioManager.Volume.SetDefault();
audioManager.VolumeTrack.SetDefault();
return true;
};
}
}
} }
} }

View File

@ -49,8 +49,6 @@ namespace osu.Game.Screens.Select
return criteria; return criteria;
} }
public Action Exit;
private readonly SearchTextBox searchTextBox; private readonly SearchTextBox searchTextBox;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
@ -75,11 +73,7 @@ namespace osu.Game.Screens.Select
Origin = Anchor.TopRight, Origin = Anchor.TopRight,
Children = new Drawable[] Children = new Drawable[]
{ {
searchTextBox = new SearchTextBox searchTextBox = new SearchTextBox { RelativeSizeAxes = Axes.X },
{
RelativeSizeAxes = Axes.X,
Exit = () => Exit?.Invoke(),
},
new Box new Box
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,

View File

@ -171,11 +171,6 @@ namespace osu.Game.Screens.Select
Height = FilterControl.HEIGHT, Height = FilterControl.HEIGHT,
FilterChanged = c => Carousel.Filter(c), FilterChanged = c => Carousel.Filter(c),
Background = { Width = 2 }, Background = { Width = 2 },
Exit = () =>
{
if (this.IsCurrentScreen())
this.Exit();
},
}, },
} }
}, },

View File

@ -14,10 +14,10 @@ namespace osu.Game.Skinning
{ {
ComboColours.AddRange(new[] ComboColours.AddRange(new[]
{ {
new Color4(17, 136, 170, 255), new Color4(255, 192, 0, 255),
new Color4(102, 136, 0, 255), new Color4(0, 202, 0, 255),
new Color4(204, 102, 0, 255), new Color4(18, 124, 255, 255),
new Color4(121, 9, 13, 255) new Color4(242, 24, 57, 255),
}); });
} }
} }

View File

@ -28,9 +28,9 @@ namespace osu.Game.Tests.Visual
{ {
[Cached(typeof(Bindable<WorkingBeatmap>))] [Cached(typeof(Bindable<WorkingBeatmap>))]
[Cached(typeof(IBindable<WorkingBeatmap>))] [Cached(typeof(IBindable<WorkingBeatmap>))]
private OsuTestBeatmap beatmap; private NonNullableBindable<WorkingBeatmap> beatmap;
protected BindableBeatmap Beatmap => beatmap; protected Bindable<WorkingBeatmap> Beatmap => beatmap;
[Cached] [Cached]
[Cached(typeof(IBindable<RulesetInfo>))] [Cached(typeof(IBindable<RulesetInfo>))]
@ -73,10 +73,13 @@ namespace osu.Game.Tests.Visual
// This is the earliest we can get OsuGameBase, which is used by the dummy working beatmap to find textures // This is the earliest we can get OsuGameBase, which is used by the dummy working beatmap to find textures
var working = new DummyWorkingBeatmap(parent.Get<AudioManager>(), parent.Get<TextureStore>()); var working = new DummyWorkingBeatmap(parent.Get<AudioManager>(), parent.Get<TextureStore>());
beatmap = new OsuTestBeatmap(working) beatmap = new NonNullableBindable<WorkingBeatmap>(working) { Default = working };
beatmap.BindValueChanged(b => ScheduleAfterChildren(() =>
{ {
Default = working // compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo)
}; if (b.OldValue?.TrackLoaded == true && b.OldValue?.Track != b.NewValue?.Track)
b.OldValue.RecycleTrack();
}));
Dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); Dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
@ -317,13 +320,5 @@ namespace osu.Game.Tests.Visual
public void RunTestBlocking(TestScene test) => runner.RunTestBlocking(test); public void RunTestBlocking(TestScene test) => runner.RunTestBlocking(test);
} }
private class OsuTestBeatmap : BindableBeatmap
{
public OsuTestBeatmap(WorkingBeatmap defaultValue)
: base(defaultValue)
{
}
}
} }
} }

View File

@ -78,6 +78,9 @@ namespace osu.Game.Users
[JsonProperty(@"is_bng")] [JsonProperty(@"is_bng")]
public bool IsBNG; public bool IsBNG;
[JsonProperty(@"is_bot")]
public bool IsBot;
[JsonProperty(@"is_active")] [JsonProperty(@"is_active")]
public bool Active; public bool Active;

View File

@ -26,7 +26,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.913.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2019.913.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.924.0" /> <PackageReference Include="ppy.osu.Framework" Version="2019.930.0" />
<PackageReference Include="SharpCompress" Version="0.24.0" /> <PackageReference Include="SharpCompress" Version="0.24.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />

View File

@ -118,8 +118,8 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.913.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2019.913.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.924.0" /> <PackageReference Include="ppy.osu.Framework" Version="2019.930.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2019.924.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2019.930.0" />
<PackageReference Include="SharpCompress" Version="0.24.0" /> <PackageReference Include="SharpCompress" Version="0.24.0" />
<PackageReference Include="NUnit" Version="3.11.0" /> <PackageReference Include="NUnit" Version="3.11.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />