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

Merge branch 'master' into hitobject-pooling-base

This commit is contained in:
Dan Balasescu 2020-11-12 23:17:36 +09:00 committed by GitHub
commit 2840fd0f8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 233 additions and 57 deletions

View File

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

View File

@ -1,6 +1,7 @@
// 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
@ -54,12 +55,77 @@ namespace osu.Game.Rulesets.Mania.Tests
} }
} }
[Test]
public void TestHoldNoteMissAfterNextObjectStartTime()
{
var objects = new List<ManiaHitObject>
{
new HoldNote
{
StartTime = 1000,
EndTime = 1010,
},
new HoldNote
{
StartTime = 1020,
EndTime = 1030
}
};
performTest(objects, new List<ReplayFrame>());
addJudgementAssert(objects[0], HitResult.IgnoreHit);
addJudgementAssert(objects[1], HitResult.IgnoreHit);
}
[Test]
public void TestHoldNoteReleasedHitAfterNextObjectStartTime()
{
var objects = new List<ManiaHitObject>
{
new HoldNote
{
StartTime = 1000,
EndTime = 1010,
},
new HoldNote
{
StartTime = 1020,
EndTime = 1030
}
};
var frames = new List<ReplayFrame>
{
new ManiaReplayFrame(1000, ManiaAction.Key1),
new ManiaReplayFrame(1030),
new ManiaReplayFrame(1040, ManiaAction.Key1),
new ManiaReplayFrame(1050)
};
performTest(objects, frames);
addJudgementAssert(objects[0], HitResult.IgnoreHit);
addJudgementAssert("first head", () => ((HoldNote)objects[0]).Head, HitResult.Perfect);
addJudgementAssert("first tail", () => ((HoldNote)objects[0]).Tail, HitResult.Perfect);
addJudgementAssert(objects[1], HitResult.IgnoreHit);
addJudgementAssert("second head", () => ((HoldNote)objects[1]).Head, HitResult.Great);
addJudgementAssert("second tail", () => ((HoldNote)objects[1]).Tail, HitResult.Perfect);
}
private void addJudgementAssert(ManiaHitObject hitObject, HitResult result) private void addJudgementAssert(ManiaHitObject hitObject, HitResult result)
{ {
AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}", AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}",
() => judgementResults.Single(r => r.HitObject == hitObject).Type == result); () => judgementResults.Single(r => r.HitObject == hitObject).Type == result);
} }
private void addJudgementAssert(string name, Func<ManiaHitObject> hitObject, HitResult result)
{
AddAssert($"{name} judgement is {result}",
() => judgementResults.Single(r => r.HitObject == hitObject()).Type == result);
}
private void addJudgementOffsetAssert(ManiaHitObject hitObject, double offset) private void addJudgementOffsetAssert(ManiaHitObject hitObject, double offset)
{ {
AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judged at {offset}", AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judged at {offset}",

View File

@ -1,7 +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.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
@ -44,9 +43,6 @@ namespace osu.Game.Rulesets.Mania.UI
/// <param name="hitObject">The <see cref="HitObject"/> that was hit.</param> /// <param name="hitObject">The <see cref="HitObject"/> that was hit.</param>
public void HandleHit(DrawableHitObject hitObject) public void HandleHit(DrawableHitObject hitObject)
{ {
if (!IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset))
throw new InvalidOperationException($"A {hitObject} was hit before it became hittable!");
foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime)) foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime))
{ {
if (obj.Judged) if (obj.Judged)

View File

@ -125,6 +125,9 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
if (!enabled) return null; if (!enabled) return null;
if (component is OsuSkinComponent osuComponent && osuComponent.Component == OsuSkinComponents.SliderBody)
return null;
return new OsuSpriteText return new OsuSpriteText
{ {
Text = identifier, Text = identifier,

View File

@ -27,17 +27,23 @@ namespace osu.Game.Rulesets.Osu.Mods
public override void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables) public override void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{ {
foreach (var d in drawables) foreach (var d in drawables)
d.ApplyCustomUpdateState += applyFadeInAdjustment; {
d.HitObjectApplied += applyFadeInAdjustment;
applyFadeInAdjustment(d);
}
base.ApplyToDrawableHitObjects(drawables); base.ApplyToDrawableHitObjects(drawables);
} }
private void applyFadeInAdjustment(DrawableHitObject hitObject, ArmedState state) private void applyFadeInAdjustment(DrawableHitObject hitObject)
{ {
if (!(hitObject is DrawableOsuHitObject d)) if (!(hitObject is DrawableOsuHitObject d))
return; return;
d.HitObject.TimeFadeIn = d.HitObject.TimePreempt * fade_in_duration_multiplier; d.HitObject.TimeFadeIn = d.HitObject.TimePreempt * fade_in_duration_multiplier;
foreach (var nested in d.NestedHitObjects)
applyFadeInAdjustment(nested);
} }
private double lastSliderHeadFadeOutStartTime; private double lastSliderHeadFadeOutStartTime;

View File

@ -0,0 +1,37 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.UI;
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
{
[TestFixture]
public class TestSceneKiaiHitExplosion : TaikoSkinnableTestScene
{
[Test]
public void TestKiaiHits()
{
AddStep("rim hit", () => SetContents(() => getContentFor(createHit(HitType.Rim))));
AddStep("centre hit", () => SetContents(() => getContentFor(createHit(HitType.Centre))));
}
private Drawable getContentFor(DrawableTestHit hit)
{
return new Container
{
RelativeSizeAxes = Axes.Both,
Child = new KiaiHitExplosion(hit, hit.HitObject.Type)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
};
}
private DrawableTestHit createHit(HitType type) => new DrawableTestHit(new Hit { StartTime = Time.Current, Type = type });
}
}

View File

@ -114,6 +114,14 @@ namespace osu.Game.Rulesets.Taiko.Skinning
return null; return null;
case TaikoSkinComponents.TaikoExplosionKiai:
// suppress the default kiai explosion if the skin brings its own sprites.
// the drawable needs to expire as soon as possible to avoid accumulating empty drawables on the playfield.
if (hasExplosion.Value)
return Drawable.Empty().With(d => d.LifetimeEnd = double.MinValue);
return null;
case TaikoSkinComponents.Scroller: case TaikoSkinComponents.Scroller:
if (GetTexture("taiko-slider") != null) if (GetTexture("taiko-slider") != null)
return new LegacyTaikoScroller(); return new LegacyTaikoScroller();

View File

@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Taiko
TaikoExplosionMiss, TaikoExplosionMiss,
TaikoExplosionOk, TaikoExplosionOk,
TaikoExplosionGreat, TaikoExplosionGreat,
TaikoExplosionKiai,
Scroller, Scroller,
Mascot, Mascot,
} }

View File

@ -0,0 +1,64 @@
// 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 osuTK;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.UI
{
public class DefaultKiaiHitExplosion : CircularContainer
{
public override bool RemoveWhenNotAlive => true;
private readonly HitType type;
public DefaultKiaiHitExplosion(HitType type)
{
this.type = type;
RelativeSizeAxes = Axes.Both;
Blending = BlendingParameters.Additive;
Masking = true;
Alpha = 0.25f;
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = type == HitType.Rim ? colours.BlueDarker : colours.PinkDarker,
Radius = 60,
};
}
protected override void LoadComplete()
{
base.LoadComplete();
this.ScaleTo(new Vector2(1, 3f), 500, Easing.OutQuint);
this.FadeOut(250);
Expire(true);
}
}
}

View File

@ -1,71 +1,47 @@
// 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 osuTK;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Taiko.UI namespace osu.Game.Rulesets.Taiko.UI
{ {
public class KiaiHitExplosion : CircularContainer public class KiaiHitExplosion : Container
{ {
public override bool RemoveWhenNotAlive => true; public override bool RemoveWhenNotAlive => true;
[Cached(typeof(DrawableHitObject))]
public readonly DrawableHitObject JudgedObject; public readonly DrawableHitObject JudgedObject;
private readonly HitType type;
public KiaiHitExplosion(DrawableHitObject judgedObject, HitType type) private readonly HitType hitType;
private SkinnableDrawable skinnable;
public override double LifetimeStart => skinnable.Drawable.LifetimeStart;
public override double LifetimeEnd => skinnable.Drawable.LifetimeEnd;
public KiaiHitExplosion(DrawableHitObject judgedObject, HitType hitType)
{ {
JudgedObject = judgedObject; JudgedObject = judgedObject;
this.type = type; this.hitType = hitType;
Anchor = Anchor.CentreLeft; Anchor = Anchor.CentreLeft;
Origin = Anchor.Centre; Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
Size = new Vector2(TaikoHitObject.DEFAULT_SIZE, 1); Size = new Vector2(TaikoHitObject.DEFAULT_SIZE, 1);
Blending = BlendingParameters.Additive;
Masking = true;
Alpha = 0.25f;
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
};
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load()
{ {
EdgeEffect = new EdgeEffectParameters Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.TaikoExplosionKiai), _ => new DefaultKiaiHitExplosion(hitType));
{
Type = EdgeEffectType.Glow,
Colour = type == HitType.Rim ? colours.BlueDarker : colours.PinkDarker,
Radius = 60,
};
}
protected override void LoadComplete()
{
base.LoadComplete();
this.ScaleTo(new Vector2(1, 3f), 500, Easing.OutQuint);
this.FadeOut(250);
Expire(true);
} }
} }
} }

View File

@ -16,8 +16,6 @@ namespace osu.Game.Beatmaps.Formats
{ {
public class LegacyBeatmapDecoder : LegacyDecoder<Beatmap> public class LegacyBeatmapDecoder : LegacyDecoder<Beatmap>
{ {
public const int LATEST_VERSION = 14;
private Beatmap beatmap; private Beatmap beatmap;
private ConvertHitObjectParser parser; private ConvertHitObjectParser parser;

View File

@ -16,6 +16,8 @@ namespace osu.Game.Beatmaps.Formats
public abstract class LegacyDecoder<T> : Decoder<T> public abstract class LegacyDecoder<T> : Decoder<T>
where T : new() where T : new()
{ {
public const int LATEST_VERSION = 14;
protected readonly int FormatVersion; protected readonly int FormatVersion;
protected LegacyDecoder(int version) protected LegacyDecoder(int version)

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Legacy;
@ -23,15 +24,15 @@ namespace osu.Game.Beatmaps.Formats
private readonly Dictionary<string, string> variables = new Dictionary<string, string>(); private readonly Dictionary<string, string> variables = new Dictionary<string, string>();
public LegacyStoryboardDecoder() public LegacyStoryboardDecoder(int version = LATEST_VERSION)
: base(0) : base(version)
{ {
} }
public static void Register() public static void Register()
{ {
// note that this isn't completely correct // note that this isn't completely correct
AddDecoder<Storyboard>(@"osu file format v", m => new LegacyStoryboardDecoder()); AddDecoder<Storyboard>(@"osu file format v", m => new LegacyStoryboardDecoder(Parsing.ParseInt(m.Split('v').Last())));
AddDecoder<Storyboard>(@"[Events]", m => new LegacyStoryboardDecoder()); AddDecoder<Storyboard>(@"[Events]", m => new LegacyStoryboardDecoder());
SetFallbackDecoder<Storyboard>(() => new LegacyStoryboardDecoder()); SetFallbackDecoder<Storyboard>(() => new LegacyStoryboardDecoder());
} }
@ -133,6 +134,11 @@ namespace osu.Game.Beatmaps.Formats
var y = Parsing.ParseFloat(split[5], Parsing.MAX_COORDINATE_VALUE); var y = Parsing.ParseFloat(split[5], Parsing.MAX_COORDINATE_VALUE);
var frameCount = Parsing.ParseInt(split[6]); var frameCount = Parsing.ParseInt(split[6]);
var frameDelay = Parsing.ParseDouble(split[7]); var frameDelay = Parsing.ParseDouble(split[7]);
if (FormatVersion < 6)
// this is random as hell but taken straight from osu-stable.
frameDelay = Math.Round(0.015 * frameDelay) * 1.186 * (1000 / 60f);
var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever; var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever;
storyboardSprite = new StoryboardAnimation(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType); storyboardSprite = new StoryboardAnimation(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType);
storyboard.GetLayer(layer).Add(storyboardSprite); storyboard.GetLayer(layer).Add(storyboardSprite);

View File

@ -681,7 +681,6 @@ namespace osu.Game
loadComponentSingleFile(new AccountCreationOverlay(), topMostOverlayContent.Add, true); loadComponentSingleFile(new AccountCreationOverlay(), topMostOverlayContent.Add, true);
loadComponentSingleFile(new DialogOverlay(), topMostOverlayContent.Add, true); loadComponentSingleFile(new DialogOverlay(), topMostOverlayContent.Add, true);
loadComponentSingleFile(externalLinkOpener = new ExternalLinkOpener(), topMostOverlayContent.Add);
chatOverlay.State.ValueChanged += state => channelManager.HighPollRate.Value = state.NewValue == Visibility.Visible; chatOverlay.State.ValueChanged += state => channelManager.HighPollRate.Value = state.NewValue == Visibility.Visible;

View File

@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer<APIUserMostPlayedBeatmap> public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer<APIUserMostPlayedBeatmap>
{ {
public PaginatedMostPlayedBeatmapContainer(Bindable<User> user) public PaginatedMostPlayedBeatmapContainer(Bindable<User> user)
: base(user, "Most Played Beatmaps", "No records. :(") : base(user, "Most Played Beatmaps", "No records. :(", CounterVisibilityState.AlwaysVisible)
{ {
ItemsPerPage = 5; ItemsPerPage = 5;
} }
@ -27,6 +27,8 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
ItemsContainer.Direction = FillDirection.Vertical; ItemsContainer.Direction = FillDirection.Vertical;
} }
protected override int GetCount(User user) => user.BeatmapPlaycountsCount;
protected override APIRequest<List<APIUserMostPlayedBeatmap>> CreateRequest() => protected override APIRequest<List<APIUserMostPlayedBeatmap>> CreateRequest() =>
new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++, ItemsPerPage); new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++, ItemsPerPage);

View File

@ -27,8 +27,16 @@ namespace osu.Game.Rulesets.Objects.Drawables
[Cached(typeof(DrawableHitObject))] [Cached(typeof(DrawableHitObject))]
public abstract class DrawableHitObject : SkinReloadableDrawable public abstract class DrawableHitObject : SkinReloadableDrawable
{ {
/// <summary>
/// Invoked after this <see cref="DrawableHitObject"/>'s applied <see cref="HitObject"/> has had its defaults applied.
/// </summary>
public event Action<DrawableHitObject> DefaultsApplied; public event Action<DrawableHitObject> DefaultsApplied;
/// <summary>
/// Invoked after a <see cref="HitObject"/> has been applied to this <see cref="DrawableHitObject"/>.
/// </summary>
public event Action<DrawableHitObject> HitObjectApplied;
/// <summary> /// <summary>
/// The <see cref="HitObject"/> currently represented by this <see cref="DrawableHitObject"/>. /// The <see cref="HitObject"/> currently represented by this <see cref="DrawableHitObject"/>.
/// </summary> /// </summary>
@ -221,6 +229,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
HitObject.DefaultsApplied += onDefaultsApplied; HitObject.DefaultsApplied += onDefaultsApplied;
OnApply(hitObject); OnApply(hitObject);
HitObjectApplied?.Invoke(this);
// If not loaded, the state update happens in LoadComplete(). Otherwise, the update is scheduled to allow for lifetime updates. // If not loaded, the state update happens in LoadComplete(). Otherwise, the update is scheduled to allow for lifetime updates.
if (IsLoaded) if (IsLoaded)

View File

@ -144,6 +144,9 @@ namespace osu.Game.Users
[JsonProperty(@"scores_first_count")] [JsonProperty(@"scores_first_count")]
public int ScoresFirstCount; public int ScoresFirstCount;
[JsonProperty(@"beatmap_playcounts_count")]
public int BeatmapPlaycountsCount;
[JsonProperty] [JsonProperty]
private string[] playstyle private string[] playstyle
{ {

View File

@ -26,7 +26,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<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.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2020.1110.0" /> <PackageReference Include="ppy.osu.Framework" Version="2020.1111.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1030.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.1030.0" />
<PackageReference Include="Sentry" Version="2.1.6" /> <PackageReference Include="Sentry" Version="2.1.6" />
<PackageReference Include="SharpCompress" Version="0.26.0" /> <PackageReference Include="SharpCompress" Version="0.26.0" />

View File

@ -70,7 +70,7 @@
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.1110.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2020.1111.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1030.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.1030.0" />
</ItemGroup> </ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) --> <!-- 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" Version="2.2.6" />
<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.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2020.1110.0" /> <PackageReference Include="ppy.osu.Framework" Version="2020.1111.0" />
<PackageReference Include="SharpCompress" Version="0.26.0" /> <PackageReference Include="SharpCompress" Version="0.26.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" />