1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-07 20:42:54 +08:00

Merge branch 'master' into taiko-don

This commit is contained in:
Dean Herbert 2020-05-13 19:27:54 +09:00 committed by GitHub
commit 6f2cc23a43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 132 additions and 62 deletions

View File

@ -52,6 +52,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.427.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.427.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.508.1" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2020.511.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -7,6 +7,7 @@ using osu.Framework.Input.Events;
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osuTK; using osuTK;
using osuTK.Input;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{ {
@ -49,6 +50,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
protected override void OnMouseUp(MouseUpEvent e) protected override void OnMouseUp(MouseUpEvent e)
{ {
if (e.Button != MouseButton.Left)
return;
base.OnMouseUp(e); base.OnMouseUp(e);
EndPlacement(true); EndPlacement(true);
} }

View File

@ -11,6 +11,7 @@ using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using osuTK; using osuTK;
using osuTK.Input;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{ {
@ -46,6 +47,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
protected override bool OnMouseDown(MouseDownEvent e) protected override bool OnMouseDown(MouseDownEvent e)
{ {
if (e.Button != MouseButton.Left)
return false;
if (Column == null) if (Column == null)
return base.OnMouseDown(e); return base.OnMouseDown(e);

View File

@ -5,6 +5,7 @@ using osu.Framework.Graphics;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osuTK.Input;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{ {
@ -30,6 +31,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
protected override bool OnMouseDown(MouseDownEvent e) protected override bool OnMouseDown(MouseDownEvent e)
{ {
if (e.Button != MouseButton.Left)
return false;
base.OnMouseDown(e); base.OnMouseDown(e);
// Place the note immediately. // Place the note immediately.

View File

@ -28,8 +28,11 @@ namespace osu.Game.Rulesets.Osu.Mods
slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
slider.NestedHitObjects.OfType<SliderRepeat>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); slider.NestedHitObjects.OfType<SliderRepeat>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
foreach (var point in slider.Path.ControlPoints) var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position.Value, p.Type.Value)).ToArray();
foreach (var point in controlPoints)
point.Position.Value = new Vector2(point.Position.Value.X, -point.Position.Value.Y); point.Position.Value = new Vector2(point.Position.Value.X, -point.Position.Value.Y);
slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value);
} }
} }
} }

View File

@ -167,13 +167,15 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
default: default:
{ {
bool isRim = samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE); bool isRimDefinition(HitSampleInfo s) => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE;
bool isRim = samples.Any(isRimDefinition);
yield return new Hit yield return new Hit
{ {
StartTime = obj.StartTime, StartTime = obj.StartTime,
Type = isRim ? HitType.Rim : HitType.Centre, Type = isRim ? HitType.Rim : HitType.Centre,
Samples = obj.Samples, Samples = samples,
IsStrong = strong IsStrong = strong
}; };

View File

@ -2,9 +2,11 @@
// 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.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
@ -47,6 +49,37 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
? new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.CentreHit), _ => new CentreHitCirclePiece(), confineMode: ConfineMode.ScaleToFit) ? new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.CentreHit), _ => new CentreHitCirclePiece(), confineMode: ConfineMode.ScaleToFit)
: new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.RimHit), _ => new RimHitCirclePiece(), confineMode: ConfineMode.ScaleToFit); : new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.RimHit), _ => new RimHitCirclePiece(), confineMode: ConfineMode.ScaleToFit);
protected override IEnumerable<HitSampleInfo> GetSamples()
{
// normal and claps are always handled by the drum (see DrumSampleMapping).
var samples = HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP);
if (HitObject.Type == HitType.Rim && HitObject.IsStrong)
{
// strong + rim always maps to whistle.
// TODO: this should really be in the legacy decoder, but can't be because legacy encoding parity would be broken.
// when we add a taiko editor, this is probably not going to play nice.
var corrected = samples.ToList();
for (var i = 0; i < corrected.Count; i++)
{
var s = corrected[i];
if (s.Name != HitSampleInfo.HIT_FINISH)
continue;
var sClone = s.Clone();
sClone.Name = HitSampleInfo.HIT_WHISTLE;
corrected[i] = sClone;
}
return corrected;
}
return samples;
}
protected override void CheckForResult(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
Debug.Assert(HitObject.HitWindows != null); Debug.Assert(HitObject.HitWindows != null);

View File

@ -165,8 +165,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
return base.CreateNestedHitObject(hitObject); return base.CreateNestedHitObject(hitObject);
} }
// Normal and clap samples are handled by the drum // Most osu!taiko hitsounds are managed by the drum (see DrumSampleMapping).
protected override IEnumerable<HitSampleInfo> GetSamples() => HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP); protected override IEnumerable<HitSampleInfo> GetSamples() => Enumerable.Empty<HitSampleInfo>();
protected abstract SkinnableDrawable CreateMainPiece(); protected abstract SkinnableDrawable CreateMainPiece();

View File

@ -6,6 +6,7 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
@ -28,14 +29,15 @@ namespace osu.Game.Tests.Beatmaps.Formats
private static IEnumerable<string> allBeatmaps => TestResources.GetStore().GetAvailableResources().Where(res => res.EndsWith(".osu")); private static IEnumerable<string> allBeatmaps => TestResources.GetStore().GetAvailableResources().Where(res => res.EndsWith(".osu"));
[TestCaseSource(nameof(allBeatmaps))] [TestCaseSource(nameof(allBeatmaps))]
public void TestBeatmap(string name) public void TestEncodeDecodeStability(string name)
{ {
var decoded = decode(name, out var encoded); var decoded = decodeFromLegacy(TestResources.GetStore().GetStream(name));
var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded));
sort(decoded); sort(decoded);
sort(encoded); sort(decodedAfterEncode);
Assert.That(encoded.Serialize(), Is.EqualTo(decoded.Serialize())); Assert.That(decodedAfterEncode.Serialize(), Is.EqualTo(decoded.Serialize()));
} }
private void sort(IBeatmap beatmap) private void sort(IBeatmap beatmap)
@ -48,27 +50,22 @@ namespace osu.Game.Tests.Beatmaps.Formats
} }
} }
private IBeatmap decode(string filename, out IBeatmap encoded) private IBeatmap decodeFromLegacy(Stream stream)
{ {
using (var stream = TestResources.GetStore().GetStream(filename)) using (var reader = new LineBufferedReader(stream))
using (var sr = new LineBufferedReader(stream)) return convert(new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(reader));
{
var legacyDecoded = convert(new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr));
using (var ms = new MemoryStream())
using (var sw = new StreamWriter(ms))
using (var sr2 = new LineBufferedReader(ms, true))
{
new LegacyBeatmapEncoder(legacyDecoded).Encode(sw);
sw.Flush();
ms.Position = 0;
encoded = convert(new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr2));
return legacyDecoded;
}
} }
private Stream encodeToLegacy(IBeatmap beatmap)
{
var stream = new MemoryStream();
using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
new LegacyBeatmapEncoder(beatmap).Encode(writer);
stream.Position = 0;
return stream;
} }
private IBeatmap convert(IBeatmap beatmap) private IBeatmap convert(IBeatmap beatmap)

View File

@ -152,11 +152,12 @@ namespace osu.Game.Tests.Skins
} }
[Test] [Test]
public void TestSetBeatmapVersionNoFallback() public void TestSetBeatmapVersionFallsBackToUserSkin()
{ {
// completely ignoring beatmap versions for simplicity.
AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m); AddStep("Set user skin version 2.3", () => userSource.Configuration.LegacyVersion = 2.3m);
AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = 1.7m); AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = 1.7m);
AddAssert("Check legacy version lookup", () => requester.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.7m); AddAssert("Check legacy version lookup", () => requester.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value == 2.3m);
} }
[Test] [Test]
@ -172,7 +173,6 @@ namespace osu.Game.Tests.Skins
public void TestIniWithNoVersionFallsBackTo1() public void TestIniWithNoVersionFallsBackTo1()
{ {
AddStep("Parse skin with no version", () => userSource.Configuration = new LegacySkinDecoder().Decode(new LineBufferedReader(new MemoryStream()))); AddStep("Parse skin with no version", () => userSource.Configuration = new LegacySkinDecoder().Decode(new LineBufferedReader(new MemoryStream())));
AddStep("Set beatmap skin version null", () => beatmapSource.Configuration.LegacyVersion = null);
AddAssert("Check legacy version lookup", () => requester.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.0m); AddAssert("Check legacy version lookup", () => requester.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value == 1.0m);
} }

View File

@ -9,12 +9,12 @@ using Newtonsoft.Json.Linq;
namespace osu.Game.IO.Serialization.Converters namespace osu.Game.IO.Serialization.Converters
{ {
/// <summary> /// <summary>
/// A type of <see cref="JsonConverter"/> that serializes a <see cref="List{T}"/> alongside /// A type of <see cref="JsonConverter"/> that serializes an <see cref="IReadOnlyList{T}"/> alongside
/// a lookup table for the types contained. The lookup table is used in deserialization to /// a lookup table for the types contained. The lookup table is used in deserialization to
/// reconstruct the objects with their original types. /// reconstruct the objects with their original types.
/// </summary> /// </summary>
/// <typeparam name="T">The type of objects contained in the <see cref="List{T}"/> this attribute is attached to.</typeparam> /// <typeparam name="T">The type of objects contained in the <see cref="IReadOnlyList{T}"/> this attribute is attached to.</typeparam>
public class TypedListConverter<T> : JsonConverter public class TypedListConverter<T> : JsonConverter<IReadOnlyList<T>>
{ {
private readonly bool requiresTypeVersion; private readonly bool requiresTypeVersion;
@ -36,9 +36,7 @@ namespace osu.Game.IO.Serialization.Converters
this.requiresTypeVersion = requiresTypeVersion; this.requiresTypeVersion = requiresTypeVersion;
} }
public override bool CanConvert(Type objectType) => objectType == typeof(List<T>); public override IReadOnlyList<T> ReadJson(JsonReader reader, Type objectType, IReadOnlyList<T> existingValue, bool hasExistingValue, JsonSerializer serializer)
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{ {
var list = new List<T>(); var list = new List<T>();
@ -59,14 +57,12 @@ namespace osu.Game.IO.Serialization.Converters
return list; return list;
} }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) public override void WriteJson(JsonWriter writer, IReadOnlyList<T> value, JsonSerializer serializer)
{ {
var list = (IEnumerable<T>)value;
var lookupTable = new List<string>(); var lookupTable = new List<string>();
var objects = new List<JObject>(); var objects = new List<JObject>();
foreach (var item in list) foreach (var item in value)
{ {
var type = item.GetType(); var type = item.GetType();
var assemblyName = type.Assembly.GetName(); var assemblyName = type.Assembly.GetName();

View File

@ -11,26 +11,22 @@ namespace osu.Game.IO.Serialization.Converters
/// <summary> /// <summary>
/// A type of <see cref="JsonConverter"/> that serializes only the X and Y coordinates of a <see cref="Vector2"/>. /// A type of <see cref="JsonConverter"/> that serializes only the X and Y coordinates of a <see cref="Vector2"/>.
/// </summary> /// </summary>
public class Vector2Converter : JsonConverter public class Vector2Converter : JsonConverter<Vector2>
{ {
public override bool CanConvert(Type objectType) => objectType == typeof(Vector2); public override Vector2 ReadJson(JsonReader reader, Type objectType, Vector2 existingValue, bool hasExistingValue, JsonSerializer serializer)
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{ {
var obj = JObject.Load(reader); var obj = JObject.Load(reader);
return new Vector2((float)obj["x"], (float)obj["y"]); return new Vector2((float)obj["x"], (float)obj["y"]);
} }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) public override void WriteJson(JsonWriter writer, Vector2 value, JsonSerializer serializer)
{ {
var vector = (Vector2)value;
writer.WriteStartObject(); writer.WriteStartObject();
writer.WritePropertyName("x"); writer.WritePropertyName("x");
writer.WriteValue(vector.X); writer.WriteValue(value.X);
writer.WritePropertyName("y"); writer.WritePropertyName("y");
writer.WriteValue(vector.Y); writer.WriteValue(value.Y);
writer.WriteEndObject(); writer.WriteEndObject();
} }

View File

@ -91,7 +91,7 @@ namespace osu.Game
protected BackButton BackButton; protected BackButton BackButton;
protected SettingsPanel Settings; protected SettingsOverlay Settings;
private VolumeOverlay volume; private VolumeOverlay volume;
private OsuLogo osuLogo; private OsuLogo osuLogo;
@ -767,13 +767,20 @@ namespace osu.Game
private Task asyncLoadStream; private Task asyncLoadStream;
private T loadComponentSingleFile<T>(T d, Action<T> add, bool cache = false) /// <summary>
/// Queues loading the provided component in sequential fashion.
/// This operation is limited to a single thread to avoid saturating all cores.
/// </summary>
/// <param name="component">The component to load.</param>
/// <param name="loadCompleteAction">An action to invoke on load completion (generally to add the component to the hierarchy).</param>
/// <param name="cache">Whether to cache the component as type <typeparamref name="T"/> into the game dependencies before any scheduling.</param>
private T loadComponentSingleFile<T>(T component, Action<T> loadCompleteAction, bool cache = false)
where T : Drawable where T : Drawable
{ {
if (cache) if (cache)
dependencies.Cache(d); dependencies.CacheAs(component);
if (d is OverlayContainer overlay) if (component is OverlayContainer overlay)
overlays.Add(overlay); overlays.Add(overlay);
// schedule is here to ensure that all component loads are done after LoadComplete is run (and thus all dependencies are cached). // schedule is here to ensure that all component loads are done after LoadComplete is run (and thus all dependencies are cached).
@ -791,12 +798,12 @@ namespace osu.Game
try try
{ {
Logger.Log($"Loading {d}...", level: LogLevel.Debug); Logger.Log($"Loading {component}...", level: LogLevel.Debug);
// Since this is running in a separate thread, it is possible for OsuGame to be disposed after LoadComponentAsync has been called // Since this is running in a separate thread, it is possible for OsuGame to be disposed after LoadComponentAsync has been called
// throwing an exception. To avoid this, the call is scheduled on the update thread, which does not run if IsDisposed = true // throwing an exception. To avoid this, the call is scheduled on the update thread, which does not run if IsDisposed = true
Task task = null; Task task = null;
var del = new ScheduledDelegate(() => task = LoadComponentAsync(d, add)); var del = new ScheduledDelegate(() => task = LoadComponentAsync(component, loadCompleteAction));
Scheduler.Add(del); Scheduler.Add(del);
// The delegate won't complete if OsuGame has been disposed in the meantime // The delegate won't complete if OsuGame has been disposed in the meantime
@ -811,7 +818,7 @@ namespace osu.Game
await task; await task;
Logger.Log($"Loaded {d}!", level: LogLevel.Debug); Logger.Log($"Loaded {component}!", level: LogLevel.Debug);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@ -819,7 +826,7 @@ namespace osu.Game
}); });
}); });
return d; return component;
} }
protected override bool OnScroll(ScrollEvent e) protected override bool OnScroll(ScrollEvent e)

View File

@ -250,6 +250,28 @@ namespace osu.Game.Overlays.KeyBinding
finalise(); finalise();
} }
protected override bool OnMidiDown(MidiDownEvent e)
{
if (!HasFocus)
return false;
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState));
finalise();
return true;
}
protected override void OnMidiUp(MidiUpEvent e)
{
if (!HasFocus)
{
base.OnMidiUp(e);
return;
}
finalise();
}
private void clear() private void clear()
{ {
bindTarget.UpdateKeyCombination(InputKey.None); bindTarget.UpdateKeyCombination(InputKey.None);

View File

@ -86,7 +86,7 @@ namespace osu.Game.Screens.Select
{ {
base.Dispose(isDisposing); base.Dispose(isDisposing);
api.Unregister(this); api?.Unregister(this);
} }
} }
} }

View File

@ -27,9 +27,11 @@ namespace osu.Game.Skinning
switch (lookup) switch (lookup)
{ {
case LegacySkinConfiguration.LegacySetting s when s == LegacySkinConfiguration.LegacySetting.Version: case LegacySkinConfiguration.LegacySetting s when s == LegacySkinConfiguration.LegacySetting.Version:
if (Configuration.LegacyVersion is decimal version) // For lookup simplicity, ignore beatmap-level versioning completely.
return SkinUtils.As<TValue>(new Bindable<decimal>(version));
// If it is decided that we need this due to beatmaps somehow using it, the default (1.0 specified in LegacySkinDecoder.CreateTemplateObject)
// needs to be removed else it will cause incorrect skin behaviours. This is due to the config lookup having no context of which skin
// it should be returning the version for.
return null; return null;
} }

View File

@ -24,7 +24,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.508.1" /> <PackageReference Include="ppy.osu.Framework" Version="2020.511.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.427.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.427.0" />
<PackageReference Include="Sentry" Version="2.1.1" /> <PackageReference Include="Sentry" Version="2.1.1" />
<PackageReference Include="SharpCompress" Version="0.25.0" /> <PackageReference Include="SharpCompress" Version="0.25.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.508.1" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2020.511.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.427.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.427.0" />
</ItemGroup> </ItemGroup>
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. --> <!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
@ -80,7 +80,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.508.1" /> <PackageReference Include="ppy.osu.Framework" Version="2020.511.0" />
<PackageReference Include="SharpCompress" Version="0.25.0" /> <PackageReference Include="SharpCompress" Version="0.25.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" />