1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 16:07:24 +08:00

Merge branch 'master' into drawable-hit-object-hit-state-fix

This commit is contained in:
Dan Balasescu 2020-11-06 13:19:25 +09:00 committed by GitHub
commit 71a1165209
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 221 additions and 139 deletions

View File

@ -52,6 +52,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1030.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1029.1" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1105.0" />
</ItemGroup>
</Project>

View File

@ -17,9 +17,11 @@ namespace osu.Game.Rulesets.Catch.Mods
private const double fade_out_offset_multiplier = 0.6;
private const double fade_out_duration_multiplier = 0.44;
protected override void ApplyHiddenState(DrawableHitObject drawable, ArmedState state)
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state)
{
if (!(drawable is DrawableCatchHitObject catchDrawable))
base.ApplyNormalVisibilityState(hitObject, state);
if (!(hitObject is DrawableCatchHitObject catchDrawable))
return;
if (catchDrawable.NestedHitObjects.Any())

View File

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
@ -23,28 +22,38 @@ namespace osu.Game.Rulesets.Osu.Mods
private const double fade_in_duration_multiplier = 0.4;
private const double fade_out_duration_multiplier = 0.3;
protected override bool IsFirstHideableObject(DrawableHitObject hitObject) => !(hitObject is DrawableSpinner);
protected override bool IsFirstAdjustableObject(HitObject hitObject) => !(hitObject is Spinner);
public override void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{
static void adjustFadeIn(OsuHitObject h) => h.TimeFadeIn = h.TimePreempt * fade_in_duration_multiplier;
foreach (var d in drawables.OfType<DrawableOsuHitObject>())
{
adjustFadeIn(d.HitObject);
foreach (var h in d.HitObject.NestedHitObjects.OfType<OsuHitObject>())
adjustFadeIn(h);
}
foreach (var d in drawables)
d.ApplyCustomUpdateState += applyFadeInAdjustment;
base.ApplyToDrawableHitObjects(drawables);
}
private void applyFadeInAdjustment(DrawableHitObject hitObject, ArmedState state)
{
if (!(hitObject is DrawableOsuHitObject d))
return;
d.HitObject.TimeFadeIn = d.HitObject.TimePreempt * fade_in_duration_multiplier;
}
private double lastSliderHeadFadeOutStartTime;
private double lastSliderHeadFadeOutDuration;
protected override void ApplyFirstObjectIncreaseVisibilityState(DrawableHitObject drawable, ArmedState state) => applyState(drawable, true);
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
{
base.ApplyIncreasedVisibilityState(hitObject, state);
applyState(hitObject, true);
}
protected override void ApplyHiddenState(DrawableHitObject drawable, ArmedState state) => applyState(drawable, false);
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state)
{
base.ApplyNormalVisibilityState(hitObject, state);
applyState(hitObject, false);
}
private void applyState(DrawableHitObject drawable, bool increaseVisibility)
{

View File

@ -2,11 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
@ -17,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods
/// <summary>
/// Adjusts the size of hit objects during their fade in animation.
/// </summary>
public abstract class OsuModObjectScaleTween : Mod, IReadFromConfig, IApplicableToDrawableHitObjects
public abstract class OsuModObjectScaleTween : ModWithVisibilityAdjustment
{
public override ModType Type => ModType.Fun;
@ -27,33 +24,19 @@ namespace osu.Game.Rulesets.Osu.Mods
protected virtual float EndScale => 1;
private Bindable<bool> increaseFirstObjectVisibility = new Bindable<bool>();
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpinIn), typeof(OsuModTraceable) };
public void ReadFromConfig(OsuConfigManager config)
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
{
increaseFirstObjectVisibility = config.GetBindable<bool>(OsuSetting.IncreaseFirstObjectVisibility);
}
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{
foreach (var drawable in drawables.Skip(increaseFirstObjectVisibility.Value ? 1 : 0))
{
switch (drawable)
{
case DrawableSpinner _:
continue;
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) => applyCustomState(hitObject, state);
default:
drawable.ApplyCustomUpdateState += ApplyCustomState;
break;
}
}
}
protected virtual void ApplyCustomState(DrawableHitObject drawable, ArmedState state)
private void applyCustomState(DrawableHitObject drawable, ArmedState state)
{
if (drawable is DrawableSpinner)
return;
var h = (OsuHitObject)drawable.HitObject;
// apply grow effect

View File

@ -2,12 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
@ -16,7 +12,7 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModSpinIn : Mod, IApplicableToDrawableHitObjects, IReadFromConfig
public class OsuModSpinIn : ModWithVisibilityAdjustment
{
public override string Name => "Spin In";
public override string Acronym => "SI";
@ -31,31 +27,17 @@ namespace osu.Game.Rulesets.Osu.Mods
private const int rotate_offset = 360;
private const float rotate_starting_width = 2;
private Bindable<bool> increaseFirstObjectVisibility = new Bindable<bool>();
public void ReadFromConfig(OsuConfigManager config)
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
{
increaseFirstObjectVisibility = config.GetBindable<bool>(OsuSetting.IncreaseFirstObjectVisibility);
}
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{
foreach (var drawable in drawables.Skip(increaseFirstObjectVisibility.Value ? 1 : 0))
{
switch (drawable)
{
case DrawableSpinner _:
continue;
default:
drawable.ApplyCustomUpdateState += applyZoomState;
break;
}
}
}
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) => applyZoomState(hitObject, state);
private void applyZoomState(DrawableHitObject drawable, ArmedState state)
{
if (drawable is DrawableSpinner)
return;
var h = (OsuHitObject)drawable.HitObject;
switch (drawable)

View File

@ -2,12 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Framework.Bindables;
using System.Collections.Generic;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables;
@ -15,7 +11,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Osu.Mods
{
internal class OsuModTraceable : Mod, IReadFromConfig, IApplicableToDrawableHitObjects
internal class OsuModTraceable : ModWithVisibilityAdjustment
{
public override string Name => "Traceable";
public override string Acronym => "TC";
@ -24,20 +20,14 @@ namespace osu.Game.Rulesets.Osu.Mods
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden), typeof(OsuModSpinIn), typeof(OsuModObjectScaleTween) };
private Bindable<bool> increaseFirstObjectVisibility = new Bindable<bool>();
public void ReadFromConfig(OsuConfigManager config)
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
{
increaseFirstObjectVisibility = config.GetBindable<bool>(OsuSetting.IncreaseFirstObjectVisibility);
}
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{
foreach (var drawable in drawables.Skip(increaseFirstObjectVisibility.Value ? 1 : 0))
drawable.ApplyCustomUpdateState += ApplyTraceableState;
}
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) => applyTraceableState(hitObject, state);
protected void ApplyTraceableState(DrawableHitObject drawable, ArmedState state)
private void applyTraceableState(DrawableHitObject drawable, ArmedState state)
{
if (!(drawable is DrawableOsuHitObject))
return;

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Mods;
@ -13,7 +12,7 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Mods
{
internal class OsuModTransform : Mod, IApplicableToDrawableHitObjects
internal class OsuModTransform : ModWithVisibilityAdjustment
{
public override string Name => "Transform";
public override string Acronym => "TR";
@ -25,11 +24,9 @@ namespace osu.Game.Rulesets.Osu.Mods
private float theta;
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{
foreach (var drawable in drawables)
drawable.ApplyCustomUpdateState += applyTransform;
}
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) => applyTransform(hitObject, state);
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) => applyTransform(hitObject, state);
private void applyTransform(DrawableHitObject drawable, ArmedState state)
{

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Mods;
@ -13,7 +12,7 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Mods
{
internal class OsuModWiggle : Mod, IApplicableToDrawableHitObjects
internal class OsuModWiggle : ModWithVisibilityAdjustment
{
public override string Name => "Wiggle";
public override string Acronym => "WG";
@ -26,11 +25,9 @@ namespace osu.Game.Rulesets.Osu.Mods
private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles
private const int wiggle_strength = 10; // Higher = stronger wiggles
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{
foreach (var drawable in drawables)
drawable.ApplyCustomUpdateState += drawableOnApplyCustomUpdateState;
}
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) => drawableOnApplyCustomUpdateState(hitObject, state);
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) => drawableOnApplyCustomUpdateState(hitObject, state);
private void drawableOnApplyCustomUpdateState(DrawableHitObject drawable, ArmedState state)
{

View File

@ -1,19 +1,16 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Configuration;
using System;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
namespace osu.Game.Rulesets.Mods
{
public abstract class ModHidden : Mod, IReadFromConfig, IApplicableToDrawableHitObjects, IApplicableToScoreProcessor
public abstract class ModHidden : ModWithVisibilityAdjustment, IApplicableToScoreProcessor
{
public override string Name => "Hidden";
public override string Acronym => "HD";
@ -21,37 +18,14 @@ namespace osu.Game.Rulesets.Mods
public override ModType Type => ModType.DifficultyIncrease;
public override bool Ranked => true;
protected Bindable<bool> IncreaseFirstObjectVisibility = new Bindable<bool>();
/// <summary>
/// Check whether the provided hitobject should be considered the "first" hideable object.
/// Can be used to skip spinners, for instance.
/// </summary>
/// <param name="hitObject">The hitobject to check.</param>
[Obsolete("Use IsFirstAdjustableObject() instead.")] // Can be removed 20210506
protected virtual bool IsFirstHideableObject(DrawableHitObject hitObject) => true;
public void ReadFromConfig(OsuConfigManager config)
{
IncreaseFirstObjectVisibility = config.GetBindable<bool>(OsuSetting.IncreaseFirstObjectVisibility);
}
public virtual void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{
if (IncreaseFirstObjectVisibility.Value)
{
drawables = drawables.SkipWhile(h => !IsFirstHideableObject(h));
var firstObject = drawables.FirstOrDefault();
if (firstObject != null)
firstObject.ApplyCustomUpdateState += ApplyFirstObjectIncreaseVisibilityState;
drawables = drawables.Skip(1);
}
foreach (var dho in drawables)
dho.ApplyCustomUpdateState += ApplyHiddenState;
}
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
{
// Default value of ScoreProcessor's Rank in Hidden Mod should be SS+
@ -73,11 +47,26 @@ namespace osu.Game.Rulesets.Mods
}
}
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
{
#pragma warning disable 618
ApplyFirstObjectIncreaseVisibilityState(hitObject, state);
#pragma warning restore 618
}
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state)
{
#pragma warning disable 618
ApplyHiddenState(hitObject, state);
#pragma warning restore 618
}
/// <summary>
/// Apply a special visibility state to the first object in a beatmap, if the user chooses to turn on the "increase first object visibility" setting.
/// </summary>
/// <param name="hitObject">The hit object to apply the state change to.</param>
/// <param name="state">The state of the hit object.</param>
[Obsolete("Use ApplyIncreasedVisibilityState() instead.")] // Can be removed 20210506
protected virtual void ApplyFirstObjectIncreaseVisibilityState(DrawableHitObject hitObject, ArmedState state)
{
}
@ -87,6 +76,7 @@ namespace osu.Game.Rulesets.Mods
/// </summary>
/// <param name="hitObject">The hit object to apply the state change to.</param>
/// <param name="state">The state of the hit object.</param>
[Obsolete("Use ApplyNormalVisibilityState() instead.")] // Can be removed 20210506
protected virtual void ApplyHiddenState(DrawableHitObject hitObject, ArmedState state)
{
}

View File

@ -0,0 +1,114 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mods
{
/// <summary>
/// A <see cref="Mod"/> which applies visibility adjustments to <see cref="DrawableHitObject"/>s
/// with an optional increased visibility adjustment depending on the user's "increase first object visibility" setting.
/// </summary>
public abstract class ModWithVisibilityAdjustment : Mod, IReadFromConfig, IApplicableToBeatmap, IApplicableToDrawableHitObjects
{
/// <summary>
/// The first adjustable object.
/// </summary>
protected HitObject FirstObject { get; private set; }
/// <summary>
/// Whether the visibility of <see cref="FirstObject"/> should be increased.
/// </summary>
protected readonly Bindable<bool> IncreaseFirstObjectVisibility = new Bindable<bool>();
/// <summary>
/// Check whether the provided hitobject should be considered the "first" adjustable object.
/// Can be used to skip spinners, for instance.
/// </summary>
/// <param name="hitObject">The hitobject to check.</param>
protected virtual bool IsFirstAdjustableObject(HitObject hitObject) => true;
/// <summary>
/// Apply a special increased-visibility state to the first adjustable object.
/// Only applicable if the user chooses to turn on the "increase first object visibility" setting.
/// </summary>
/// <param name="hitObject">The hit object to apply the state change to.</param>
/// <param name="state">The state of the hitobject.</param>
protected abstract void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state);
/// <summary>
/// Apply a normal visibility state adjustment to an object.
/// </summary>
/// <param name="hitObject">The hit object to apply the state change to.</param>
/// <param name="state">The state of the hitobject.</param>
protected abstract void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state);
public virtual void ReadFromConfig(OsuConfigManager config)
{
config.BindWith(OsuSetting.IncreaseFirstObjectVisibility, IncreaseFirstObjectVisibility);
}
public virtual void ApplyToBeatmap(IBeatmap beatmap)
{
FirstObject = getFirstAdjustableObjectRecursive(beatmap.HitObjects);
HitObject getFirstAdjustableObjectRecursive(IReadOnlyList<HitObject> hitObjects)
{
foreach (var h in hitObjects)
{
if (IsFirstAdjustableObject(h))
return h;
var nestedResult = getFirstAdjustableObjectRecursive(h.NestedHitObjects);
if (nestedResult != null)
return nestedResult;
}
return null;
}
}
public virtual void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{
foreach (var dho in drawables)
{
dho.ApplyCustomUpdateState += (o, state) =>
{
// Increased visibility is applied to the entire first object, including all of its nested hitobjects.
if (IncreaseFirstObjectVisibility.Value && isObjectEqualToOrNestedIn(o.HitObject, FirstObject))
ApplyIncreasedVisibilityState(o, state);
else
ApplyNormalVisibilityState(o, state);
};
}
}
/// <summary>
/// Checks whether a given object is nested within a target.
/// </summary>
/// <param name="toCheck">The <see cref="HitObject"/> to check.</param>
/// <param name="target">The <see cref="HitObject"/> which may be equal to or contain <paramref name="toCheck"/> as a nested object.</param>
/// <returns>Whether <paramref name="toCheck"/> is equal to or nested within <paramref name="target"/>.</returns>
private bool isObjectEqualToOrNestedIn(HitObject toCheck, HitObject target)
{
if (target == null)
return false;
if (toCheck == target)
return true;
foreach (var h in target.NestedHitObjects)
{
if (isObjectEqualToOrNestedIn(toCheck, h))
return true;
}
return false;
}
}
}

View File

@ -65,17 +65,15 @@ namespace osu.Game.Tests.Visual
private Drawable createProvider(Skin skin, Func<Drawable> creationFunction, IBeatmap beatmap)
{
var created = creationFunction();
createdDrawables.Add(created);
var autoSize = created.RelativeSizeAxes == Axes.None;
SkinProvidingContainer mainProvider;
Container childContainer;
OutlineBox outlineBox;
SkinProvidingContainer skinProvider;
var mainProvider = new SkinProvidingContainer(skin)
{
RelativeSizeAxes = !autoSize ? Axes.Both : Axes.None,
AutoSizeAxes = autoSize ? Axes.Both : Axes.None,
};
return new Container
var children = new Container
{
RelativeSizeAxes = Axes.Both,
BorderColour = Color4.White,
@ -96,27 +94,47 @@ namespace osu.Game.Tests.Visual
Scale = new Vector2(1.5f),
Padding = new MarginPadding(5),
},
new Container
childContainer = new Container
{
RelativeSizeAxes = !autoSize ? Axes.Both : Axes.None,
AutoSizeAxes = autoSize ? Axes.Both : Axes.None,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new OutlineBox { Alpha = autoSize ? 1 : 0 },
mainProvider.WithChild(
new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider, beatmap))
outlineBox = new OutlineBox(),
(mainProvider = new SkinProvidingContainer(skin)).WithChild(
skinProvider = new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider, beatmap))
{
Child = created,
RelativeSizeAxes = !autoSize ? Axes.Both : Axes.None,
AutoSizeAxes = autoSize ? Axes.Both : Axes.None,
}
)
}
},
}
};
// run this once initially to bring things into a sane state as early as possible.
updateSizing();
// run this once after construction to handle the case the changes are made in a BDL/LoadComplete call.
Schedule(updateSizing);
return children;
void updateSizing()
{
var autoSize = created.RelativeSizeAxes == Axes.None;
foreach (var c in new[] { mainProvider, childContainer, skinProvider })
{
c.RelativeSizeAxes = Axes.None;
c.AutoSizeAxes = Axes.None;
c.RelativeSizeAxes = !autoSize ? Axes.Both : Axes.None;
c.AutoSizeAxes = autoSize ? Axes.Both : Axes.None;
}
outlineBox.Alpha = autoSize ? 1 : 0;
}
}
/// <summary>

View File

@ -26,7 +26,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2020.1029.1" />
<PackageReference Include="ppy.osu.Framework" Version="2020.1105.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1030.0" />
<PackageReference Include="Sentry" Version="2.1.6" />
<PackageReference Include="SharpCompress" Version="0.26.0" />

View File

@ -70,7 +70,7 @@
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.1029.1" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.1105.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1030.0" />
</ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
@ -88,7 +88,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2020.1029.1" />
<PackageReference Include="ppy.osu.Framework" Version="2020.1105.0" />
<PackageReference Include="SharpCompress" Version="0.26.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" />