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

Merge branch 'master' into taiko-strong-state-refactor

This commit is contained in:
Bartłomiej Dach 2020-12-14 21:44:50 +01:00
commit 7ba04a2cc3
33 changed files with 472 additions and 195 deletions

View File

@ -52,6 +52,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1212.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2020.1214.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Catch.Tests
public float Position public float Position
{ {
get => HitObject?.X ?? position; get => HitObject?.EffectiveX ?? position;
set => position = value; set => position = value;
} }

View File

@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.Tests
private void attemptCatch(Fruit fruit) private void attemptCatch(Fruit fruit)
{ {
fruit.X += catcher.X; fruit.X = fruit.OriginalX + catcher.X;
fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty
{ {
CircleSize = circleSize CircleSize = circleSize

View File

@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
case JuiceStream juiceStream: case JuiceStream juiceStream:
// Todo: BUG!! Stable used the last control point as the final position of the path, but it should use the computed path instead. // Todo: BUG!! Stable used the last control point as the final position of the path, but it should use the computed path instead.
lastPosition = juiceStream.X + juiceStream.Path.ControlPoints[^1].Position.Value.X; lastPosition = juiceStream.OriginalX + juiceStream.Path.ControlPoints[^1].Position.Value.X;
// Todo: BUG!! Stable attempted to use the end time of the stream, but referenced it too early in execution and used the start time instead. // Todo: BUG!! Stable attempted to use the end time of the stream, but referenced it too early in execution and used the start time instead.
lastStartTime = juiceStream.StartTime; lastStartTime = juiceStream.StartTime;
@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
catchObject.XOffset = 0; catchObject.XOffset = 0;
if (catchObject is TinyDroplet) if (catchObject is TinyDroplet)
catchObject.XOffset = Math.Clamp(rng.Next(-20, 20), -catchObject.X, CatchPlayfield.WIDTH - catchObject.X); catchObject.XOffset = Math.Clamp(rng.Next(-20, 20), -catchObject.OriginalX, CatchPlayfield.WIDTH - catchObject.OriginalX);
else if (catchObject is Droplet) else if (catchObject is Droplet)
rng.Next(); // osu!stable retrieved a random droplet rotation rng.Next(); // osu!stable retrieved a random droplet rotation
} }
@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
private static void applyHardRockOffset(CatchHitObject hitObject, ref float? lastPosition, ref double lastStartTime, FastRandom rng) private static void applyHardRockOffset(CatchHitObject hitObject, ref float? lastPosition, ref double lastStartTime, FastRandom rng)
{ {
float offsetPosition = hitObject.X; float offsetPosition = hitObject.OriginalX;
double startTime = hitObject.StartTime; double startTime = hitObject.StartTime;
if (lastPosition == null) if (lastPosition == null)
@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
if (positionDiff == 0) if (positionDiff == 0)
{ {
applyRandomOffset(ref offsetPosition, timeDiff / 4d, rng); applyRandomOffset(ref offsetPosition, timeDiff / 4d, rng);
hitObject.XOffset = offsetPosition - hitObject.X; hitObject.XOffset = offsetPosition - hitObject.OriginalX;
return; return;
} }
@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
if (Math.Abs(positionDiff) < timeDiff / 3) if (Math.Abs(positionDiff) < timeDiff / 3)
applyOffset(ref offsetPosition, positionDiff); applyOffset(ref offsetPosition, positionDiff);
hitObject.XOffset = offsetPosition - hitObject.X; hitObject.XOffset = offsetPosition - hitObject.OriginalX;
lastPosition = offsetPosition; lastPosition = offsetPosition;
lastStartTime = startTime; lastStartTime = startTime;
@ -230,9 +230,9 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
currentObject.HyperDashTarget = null; currentObject.HyperDashTarget = null;
currentObject.DistanceToHyperDash = 0; currentObject.DistanceToHyperDash = 0;
int thisDirection = nextObject.X > currentObject.X ? 1 : -1; int thisDirection = nextObject.EffectiveX > currentObject.EffectiveX ? 1 : -1;
double timeToNext = nextObject.StartTime - currentObject.StartTime - 1000f / 60f / 4; // 1/4th of a frame of grace time, taken from osu-stable double timeToNext = nextObject.StartTime - currentObject.StartTime - 1000f / 60f / 4; // 1/4th of a frame of grace time, taken from osu-stable
double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth); double distanceToNext = Math.Abs(nextObject.EffectiveX - currentObject.EffectiveX) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth);
float distanceToHyper = (float)(timeToNext * Catcher.BASE_SPEED - distanceToNext); float distanceToHyper = (float)(timeToNext * Catcher.BASE_SPEED - distanceToNext);
if (distanceToHyper < 0) if (distanceToHyper < 0)

View File

@ -32,8 +32,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
// We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps. // We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
var scalingFactor = normalized_hitobject_radius / halfCatcherWidth; var scalingFactor = normalized_hitobject_radius / halfCatcherWidth;
NormalizedPosition = BaseObject.X * scalingFactor; NormalizedPosition = BaseObject.EffectiveX * scalingFactor;
LastNormalizedPosition = LastObject.X * scalingFactor; LastNormalizedPosition = LastObject.EffectiveX * scalingFactor;
// Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure // Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure
StrainTime = Math.Max(40, DeltaTime); StrainTime = Math.Max(40, DeltaTime);

View File

@ -4,7 +4,6 @@
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
@ -16,38 +15,46 @@ namespace osu.Game.Rulesets.Catch.Objects
{ {
public const float OBJECT_RADIUS = 64; public const float OBJECT_RADIUS = 64;
// This value is after XOffset applied. public readonly Bindable<float> OriginalXBindable = new Bindable<float>();
public readonly Bindable<float> XBindable = new Bindable<float>();
// This value is before XOffset applied.
private float originalX;
/// <summary> /// <summary>
/// The horizontal position of the fruit between 0 and <see cref="CatchPlayfield.WIDTH"/>. /// The horizontal position of the hit object between 0 and <see cref="CatchPlayfield.WIDTH"/>.
/// </summary> /// </summary>
public float X public float X
{ {
// TODO: I don't like this asymmetry. set => OriginalXBindable.Value = value;
get => XBindable.Value;
// originalX is set by `XBindable.BindValueChanged`
set => XBindable.Value = value + xOffset;
} }
private float xOffset; float IHasXPosition.X => OriginalXBindable.Value;
public readonly Bindable<float> XOffsetBindable = new Bindable<float>();
/// <summary> /// <summary>
/// A random offset applied to <see cref="X"/>, set by the <see cref="CatchBeatmapProcessor"/>. /// A random offset applied to the horizontal position, set by the beatmap processing.
/// </summary> /// </summary>
internal float XOffset public float XOffset
{ {
get => xOffset; set => XOffsetBindable.Value = value;
set
{
xOffset = value;
XBindable.Value = originalX + xOffset;
}
} }
/// <summary>
/// The horizontal position of the hit object between 0 and <see cref="CatchPlayfield.WIDTH"/>.
/// </summary>
/// <remarks>
/// This value is the original <see cref="X"/> value specified in the beatmap, not affected by the beatmap processing.
/// Use <see cref="EffectiveX"/> for a gameplay.
/// </remarks>
public float OriginalX => OriginalXBindable.Value;
/// <summary>
/// The effective horizontal position of the hit object between 0 and <see cref="CatchPlayfield.WIDTH"/>.
/// </summary>
/// <remarks>
/// This value is the original <see cref="X"/> value plus the offset applied by the beatmap processing.
/// Use <see cref="OriginalX"/> if a value not affected by the offset is desired.
/// </remarks>
public float EffectiveX => OriginalXBindable.Value + XOffsetBindable.Value;
public double TimePreempt = 1000; public double TimePreempt = 1000;
public readonly Bindable<int> IndexInBeatmapBindable = new Bindable<int>(); public readonly Bindable<int> IndexInBeatmapBindable = new Bindable<int>();
@ -113,10 +120,5 @@ namespace osu.Game.Rulesets.Catch.Objects
} }
protected override HitWindows CreateHitWindows() => HitWindows.Empty; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
protected CatchHitObject()
{
XBindable.BindValueChanged(x => originalX = x.NewValue - xOffset);
}
} }
} }

View File

@ -15,11 +15,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{ {
public abstract class DrawableCatchHitObject : DrawableHitObject<CatchHitObject> public abstract class DrawableCatchHitObject : DrawableHitObject<CatchHitObject>
{ {
public readonly Bindable<float> XBindable = new Bindable<float>(); public readonly Bindable<float> OriginalXBindable = new Bindable<float>();
public readonly Bindable<float> XOffsetBindable = new Bindable<float>();
protected override double InitialLifetimeOffset => HitObject.TimePreempt; protected override double InitialLifetimeOffset => HitObject.TimePreempt;
protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH; protected override float SamplePlaybackPosition => HitObject.EffectiveX / CatchPlayfield.WIDTH;
public int RandomSeed => HitObject?.RandomSeed ?? 0; public int RandomSeed => HitObject?.RandomSeed ?? 0;
@ -38,14 +39,16 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{ {
base.OnApply(); base.OnApply();
XBindable.BindTo(HitObject.XBindable); OriginalXBindable.BindTo(HitObject.OriginalXBindable);
XOffsetBindable.BindTo(HitObject.XOffsetBindable);
} }
protected override void OnFree() protected override void OnFree()
{ {
base.OnFree(); base.OnFree();
XBindable.UnbindFrom(HitObject.XBindable); OriginalXBindable.UnbindFrom(HitObject.OriginalXBindable);
XOffsetBindable.UnbindFrom(HitObject.XOffsetBindable);
} }
public Func<CatchHitObject, bool> CheckPosition; public Func<CatchHitObject, bool> CheckPosition;

View File

@ -55,10 +55,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
XBindable.BindValueChanged(x => OriginalXBindable.BindValueChanged(updateXPosition);
{ XOffsetBindable.BindValueChanged(updateXPosition, true);
X = x.NewValue;
}, true);
ScaleBindable.BindValueChanged(scale => ScaleBindable.BindValueChanged(scale =>
{ {
@ -69,6 +67,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
IndexInBeatmap.BindValueChanged(_ => UpdateComboColour()); IndexInBeatmap.BindValueChanged(_ => UpdateComboColour());
} }
private void updateXPosition(ValueChangedEvent<float> _)
{
X = OriginalXBindable.Value + XOffsetBindable.Value;
}
protected override void OnApply() protected override void OnApply()
{ {
base.OnApply(); base.OnApply();

View File

@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Objects
AddNested(new TinyDroplet AddNested(new TinyDroplet
{ {
StartTime = t + lastEvent.Value.Time, StartTime = t + lastEvent.Value.Time,
X = X + Path.PositionAt( X = OriginalX + Path.PositionAt(
lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X, lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X,
}); });
} }
@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Catch.Objects
{ {
Samples = dropletSamples, Samples = dropletSamples,
StartTime = e.Time, StartTime = e.Time,
X = X + Path.PositionAt(e.PathProgress).X, X = OriginalX + Path.PositionAt(e.PathProgress).X,
}); });
break; break;
@ -104,14 +104,14 @@ namespace osu.Game.Rulesets.Catch.Objects
{ {
Samples = this.GetNodeSamples(nodeIndex++), Samples = this.GetNodeSamples(nodeIndex++),
StartTime = e.Time, StartTime = e.Time,
X = X + Path.PositionAt(e.PathProgress).X, X = OriginalX + Path.PositionAt(e.PathProgress).X,
}); });
break; break;
} }
} }
} }
public float EndX => X + this.CurvePositionAt(1).X; public float EndX => OriginalX + this.CurvePositionAt(1).X;
public double Duration public double Duration
{ {

View File

@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Replays
void moveToNext(PalpableCatchHitObject h) void moveToNext(PalpableCatchHitObject h)
{ {
float positionChange = Math.Abs(lastPosition - h.X); float positionChange = Math.Abs(lastPosition - h.EffectiveX);
double timeAvailable = h.StartTime - lastTime; double timeAvailable = h.StartTime - lastTime;
// So we can either make it there without a dash or not. // So we can either make it there without a dash or not.
@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Catch.Replays
// todo: get correct catcher size, based on difficulty CS. // todo: get correct catcher size, based on difficulty CS.
const float catcher_width_half = CatcherArea.CATCHER_SIZE * 0.3f * 0.5f; const float catcher_width_half = CatcherArea.CATCHER_SIZE * 0.3f * 0.5f;
if (lastPosition - catcher_width_half < h.X && lastPosition + catcher_width_half > h.X) if (lastPosition - catcher_width_half < h.EffectiveX && lastPosition + catcher_width_half > h.EffectiveX)
{ {
// we are already in the correct range. // we are already in the correct range.
lastTime = h.StartTime; lastTime = h.StartTime;
@ -66,12 +66,12 @@ namespace osu.Game.Rulesets.Catch.Replays
if (impossibleJump) if (impossibleJump)
{ {
addFrame(h.StartTime, h.X); addFrame(h.StartTime, h.EffectiveX);
} }
else if (h.HyperDash) else if (h.HyperDash)
{ {
addFrame(h.StartTime - timeAvailable, lastPosition); addFrame(h.StartTime - timeAvailable, lastPosition);
addFrame(h.StartTime, h.X); addFrame(h.StartTime, h.EffectiveX);
} }
else if (dashRequired) else if (dashRequired)
{ {
@ -80,23 +80,23 @@ namespace osu.Game.Rulesets.Catch.Replays
double timeWeNeedToSave = timeAtNormalSpeed - timeAvailable; double timeWeNeedToSave = timeAtNormalSpeed - timeAvailable;
double timeAtDashSpeed = timeWeNeedToSave / 2; double timeAtDashSpeed = timeWeNeedToSave / 2;
float midPosition = (float)Interpolation.Lerp(lastPosition, h.X, (float)timeAtDashSpeed / timeAvailable); float midPosition = (float)Interpolation.Lerp(lastPosition, h.EffectiveX, (float)timeAtDashSpeed / timeAvailable);
// dash movement // dash movement
addFrame(h.StartTime - timeAvailable + 1, lastPosition, true); addFrame(h.StartTime - timeAvailable + 1, lastPosition, true);
addFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition); addFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition);
addFrame(h.StartTime, h.X); addFrame(h.StartTime, h.EffectiveX);
} }
else else
{ {
double timeBefore = positionChange / movement_speed; double timeBefore = positionChange / movement_speed;
addFrame(h.StartTime - timeBefore, lastPosition); addFrame(h.StartTime - timeBefore, lastPosition);
addFrame(h.StartTime, h.X); addFrame(h.StartTime, h.EffectiveX);
} }
lastTime = h.StartTime; lastTime = h.StartTime;
lastPosition = h.X; lastPosition = h.EffectiveX;
} }
foreach (var obj in Beatmap.HitObjects) foreach (var obj in Beatmap.HitObjects)

View File

@ -216,7 +216,7 @@ namespace osu.Game.Rulesets.Catch.UI
var halfCatchWidth = catchWidth * 0.5f; var halfCatchWidth = catchWidth * 0.5f;
// this stuff wil disappear once we move fruit to non-relative coordinate space in the future. // this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
var catchObjectPosition = fruit.X; var catchObjectPosition = fruit.EffectiveX;
var catcherPosition = Position.X; var catcherPosition = Position.X;
return catchObjectPosition >= catcherPosition - halfCatchWidth && return catchObjectPosition >= catcherPosition - halfCatchWidth &&
@ -250,10 +250,10 @@ namespace osu.Game.Rulesets.Catch.UI
{ {
var target = hitObject.HyperDashTarget; var target = hitObject.HyperDashTarget;
var timeDifference = target.StartTime - hitObject.StartTime; var timeDifference = target.StartTime - hitObject.StartTime;
double positionDifference = target.X - X; double positionDifference = target.EffectiveX - X;
var velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0); var velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
SetHyperDashState(Math.Abs(velocity), target.X); SetHyperDashState(Math.Abs(velocity), target.EffectiveX);
} }
else else
SetHyperDashState(); SetHyperDashState();

View File

@ -0,0 +1,90 @@
// 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.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public class LegacyManiaJudgementPiece : CompositeDrawable, IAnimatableJudgement
{
private readonly HitResult result;
private readonly Drawable animation;
public LegacyManiaJudgementPiece(HitResult result, Drawable animation)
{
this.result = result;
this.animation = animation;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
float? scorePosition = skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.ScorePosition)?.Value;
if (scorePosition != null)
scorePosition -= Stage.HIT_TARGET_POSITION + 150;
Y = scorePosition ?? 0;
if (animation != null)
{
InternalChild = animation.With(d =>
{
d.Anchor = Anchor.Centre;
d.Origin = Anchor.Centre;
});
}
}
public void PlayAnimation()
{
if (animation == null)
return;
(animation as IFramedAnimation)?.GotoFrame(0);
switch (result)
{
case HitResult.None:
break;
case HitResult.Miss:
animation.ScaleTo(1.6f);
animation.ScaleTo(1, 100, Easing.In);
animation.MoveTo(Vector2.Zero);
animation.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
animation.RotateTo(0);
animation.RotateTo(40, 800, Easing.InQuint);
this.FadeOutFromOne(800);
break;
default:
animation.ScaleTo(0.8f);
animation.ScaleTo(1, 250, Easing.OutElastic);
animation.Delay(50).ScaleTo(0.75f, 250);
this.Delay(50).FadeOut(200);
break;
}
}
public Drawable GetAboveHitObjectsProxiedContent() => null;
}
}

View File

@ -136,7 +136,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
string filename = this.GetManiaSkinConfig<string>(hitresult_mapping[result])?.Value string filename = this.GetManiaSkinConfig<string>(hitresult_mapping[result])?.Value
?? default_hitresult_skin_filenames[result]; ?? default_hitresult_skin_filenames[result];
return this.GetAnimation(filename, true, true); var animation = this.GetAnimation(filename, true, true);
return animation == null ? null : new LegacyManiaJudgementPiece(result, animation);
} }
public override SampleChannel GetSample(ISampleInfo sampleInfo) public override SampleChannel GetSample(ISampleInfo sampleInfo)

View File

@ -5,7 +5,6 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osuTK;
namespace osu.Game.Rulesets.Mania.UI namespace osu.Game.Rulesets.Mania.UI
{ {
@ -20,37 +19,6 @@ namespace osu.Game.Rulesets.Mania.UI
{ {
} }
protected override void ApplyMissAnimations()
{
if (!(JudgementBody.Drawable is DefaultManiaJudgementPiece))
{
// this is temporary logic until mania's skin transformer returns IAnimatableJudgements
JudgementBody.ScaleTo(1.6f);
JudgementBody.ScaleTo(1, 100, Easing.In);
JudgementBody.MoveTo(Vector2.Zero);
JudgementBody.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
JudgementBody.RotateTo(0);
JudgementBody.RotateTo(40, 800, Easing.InQuint);
JudgementBody.FadeOutFromOne(800);
LifetimeEnd = JudgementBody.LatestTransformEndTime;
}
base.ApplyMissAnimations();
}
protected override void ApplyHitAnimations()
{
JudgementBody.ScaleTo(0.8f);
JudgementBody.ScaleTo(1, 250, Easing.OutElastic);
JudgementBody.Delay(50)
.ScaleTo(0.75f, 250)
.FadeOut(200);
}
protected override Drawable CreateDefaultJudgement(HitResult result) => new DefaultManiaJudgementPiece(result); protected override Drawable CreateDefaultJudgement(HitResult result) => new DefaultManiaJudgementPiece(result);
private class DefaultManiaJudgementPiece : DefaultJudgementPiece private class DefaultManiaJudgementPiece : DefaultJudgementPiece
@ -66,6 +34,27 @@ namespace osu.Game.Rulesets.Mania.UI
JudgementText.Font = JudgementText.Font.With(size: 25); JudgementText.Font = JudgementText.Font.With(size: 25);
} }
public override void PlayAnimation()
{
base.PlayAnimation();
switch (Result)
{
case HitResult.None:
case HitResult.Miss:
break;
default:
this.ScaleTo(0.8f);
this.ScaleTo(1, 250, Easing.OutElastic);
this.Delay(50)
.ScaleTo(0.75f, 250)
.FadeOut(200);
break;
}
}
} }
} }
} }

View File

@ -106,7 +106,7 @@ namespace osu.Game.Rulesets.Mania.UI
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Y = HIT_TARGET_POSITION + 150, Y = HIT_TARGET_POSITION + 150
}, },
topLevelContainer = new Container { RelativeSizeAxes = Axes.Both } topLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
} }

View File

@ -0,0 +1,52 @@
// 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.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Taiko.Tests
{
public abstract class HitObjectApplicationTestScene : OsuTestScene
{
[Cached(typeof(IScrollingInfo))]
private ScrollingTestContainer.TestScrollingInfo info = new ScrollingTestContainer.TestScrollingInfo
{
Direction = { Value = ScrollingDirection.Left },
TimeRange = { Value = 1000 },
};
private ScrollingHitObjectContainer hitObjectContainer;
[SetUpSteps]
public void SetUp()
=> AddStep("create SHOC", () => Child = hitObjectContainer = new ScrollingHitObjectContainer
{
RelativeSizeAxes = Axes.X,
Height = 200,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Clock = new FramedClock(new StopwatchClock())
});
protected void AddHitObject(DrawableHitObject hitObject)
=> AddStep("add to SHOC", () => hitObjectContainer.Add(hitObject));
protected void RemoveHitObject(DrawableHitObject hitObject)
=> AddStep("remove from SHOC", () => hitObjectContainer.Remove(hitObject));
protected TObject PrepareObject<TObject>(TObject hitObject)
where TObject : TaikoHitObject
{
hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
return hitObject;
}
}
}

View File

@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
} }
}; };
hoc.Add(new DrawableBarLineMajor(createBarLineAtCurrentTime(true)) hoc.Add(new DrawableBarLine(createBarLineAtCurrentTime(true))
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,

View File

@ -0,0 +1,32 @@
// 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.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
namespace osu.Game.Rulesets.Taiko.Tests
{
public class TestSceneBarLineApplication : HitObjectApplicationTestScene
{
[Test]
public void TestApplyNewBarLine()
{
DrawableBarLine barLine = new DrawableBarLine(PrepareObject(new BarLine
{
StartTime = 400,
Major = true
}));
AddHitObject(barLine);
RemoveHitObject(barLine);
AddStep("apply new bar line", () => barLine.Apply(PrepareObject(new BarLine
{
StartTime = 200,
Major = false
}), null));
AddHitObject(barLine);
}
}
}

View File

@ -145,9 +145,13 @@ namespace osu.Game.Rulesets.Taiko.Tests
private void addBarLine(bool major, double delay = scroll_time) private void addBarLine(bool major, double delay = scroll_time)
{ {
BarLine bl = new BarLine { StartTime = DrawableRuleset.Playfield.Time.Current + delay }; BarLine bl = new BarLine
{
StartTime = DrawableRuleset.Playfield.Time.Current + delay,
Major = major
};
DrawableRuleset.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl)); DrawableRuleset.Playfield.Add(bl);
} }
private void addSwell(double duration = default_duration) private void addSwell(double duration = default_duration)

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 osu.Framework.Bindables;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
@ -8,7 +9,13 @@ namespace osu.Game.Rulesets.Taiko.Objects
{ {
public class BarLine : TaikoHitObject, IBarLine public class BarLine : TaikoHitObject, IBarLine
{ {
public bool Major { get; set; } public bool Major
{
get => MajorBindable.Value;
set => MajorBindable.Value = value;
}
public readonly Bindable<bool> MajorBindable = new BindableBool();
public override Judgement CreateJudgement() => new IgnoreJudgement(); public override Judgement CreateJudgement() => new IgnoreJudgement();
} }

View File

@ -1,7 +1,11 @@
// 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 JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osuTK; using osuTK;
@ -15,49 +19,123 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
/// </summary> /// </summary>
public class DrawableBarLine : DrawableHitObject<HitObject> public class DrawableBarLine : DrawableHitObject<HitObject>
{ {
public new BarLine HitObject => (BarLine)base.HitObject;
/// <summary> /// <summary>
/// The width of the line tracker. /// The width of the line tracker.
/// </summary> /// </summary>
private const float tracker_width = 2f; private const float tracker_width = 2f;
/// <summary> /// <summary>
/// Fade out time calibrated to a pre-empt of 1000ms. /// The vertical offset of the triangles from the line tracker.
/// </summary> /// </summary>
private const float base_fadeout_time = 100f; private const float triangle_offset = 10f;
/// <summary>
/// The size of the triangles.
/// </summary>
private const float triangle_size = 20f;
/// <summary> /// <summary>
/// The visual line tracker. /// The visual line tracker.
/// </summary> /// </summary>
protected SkinnableDrawable Line; private SkinnableDrawable line;
/// <summary> /// <summary>
/// The bar line. /// Container with triangles. Only visible for major lines.
/// </summary> /// </summary>
protected readonly BarLine BarLine; private Container triangleContainer;
public DrawableBarLine(BarLine barLine) private readonly Bindable<bool> major = new Bindable<bool>();
public DrawableBarLine()
: this(null)
{
}
public DrawableBarLine([CanBeNull] BarLine barLine)
: base(barLine) : base(barLine)
{ {
BarLine = barLine; }
[BackgroundDependencyLoader]
private void load()
{
Anchor = Anchor.CentreLeft; Anchor = Anchor.CentreLeft;
Origin = Anchor.Centre; Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Y; RelativeSizeAxes = Axes.Y;
Width = tracker_width; Width = tracker_width;
AddInternal(Line = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.BarLine), _ => new Box AddRangeInternal(new Drawable[]
{ {
RelativeSizeAxes = Axes.Both, line = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.BarLine), _ => new Box
EdgeSmoothness = new Vector2(0.5f, 0), {
}) RelativeSizeAxes = Axes.Both,
{ EdgeSmoothness = new Vector2(0.5f, 0),
Anchor = Anchor.Centre, })
Origin = Anchor.Centre, {
Alpha = 0.75f, Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
triangleContainer = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Children = new[]
{
new EquilateralTriangle
{
Name = "Top",
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Position = new Vector2(0, -triangle_offset),
Size = new Vector2(-triangle_size),
EdgeSmoothness = new Vector2(1),
},
new EquilateralTriangle
{
Name = "Bottom",
Anchor = Anchor.BottomCentre,
Origin = Anchor.TopCentre,
Position = new Vector2(0, triangle_offset),
Size = new Vector2(triangle_size),
EdgeSmoothness = new Vector2(1),
}
}
}
}); });
} }
protected override void UpdateHitStateTransforms(ArmedState state) => this.FadeOut(150); protected override void LoadComplete()
{
base.LoadComplete();
major.BindValueChanged(updateMajor);
}
private void updateMajor(ValueChangedEvent<bool> major)
{
line.Alpha = major.NewValue ? 1f : 0.75f;
triangleContainer.Alpha = major.NewValue ? 1 : 0;
}
protected override void OnApply()
{
base.OnApply();
major.BindTo(HitObject.MajorBindable);
}
protected override void OnFree()
{
base.OnFree();
major.UnbindFrom(HitObject.MajorBindable);
}
protected override void UpdateHitStateTransforms(ArmedState state)
{
using (BeginAbsoluteSequence(HitObject.StartTime))
this.FadeOutFromOne(150).Expire();
}
} }
} }

View File

@ -1,67 +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 osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osuTK;
using osu.Framework.Graphics.Shapes;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
public class DrawableBarLineMajor : DrawableBarLine
{
/// <summary>
/// The vertical offset of the triangles from the line tracker.
/// </summary>
private const float triangle_offfset = 10f;
/// <summary>
/// The size of the triangles.
/// </summary>
private const float triangle_size = 20f;
private readonly Container triangleContainer;
public DrawableBarLineMajor(BarLine barLine)
: base(barLine)
{
AddInternal(triangleContainer = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Children = new[]
{
new EquilateralTriangle
{
Name = "Top",
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Position = new Vector2(0, -triangle_offfset),
Size = new Vector2(-triangle_size),
EdgeSmoothness = new Vector2(1),
},
new EquilateralTriangle
{
Name = "Bottom",
Anchor = Anchor.BottomCentre,
Origin = Anchor.TopCentre,
Position = new Vector2(0, triangle_offfset),
Size = new Vector2(triangle_size),
EdgeSmoothness = new Vector2(1),
}
}
});
Line.Alpha = 1f;
}
protected override void LoadComplete()
{
base.LoadComplete();
using (triangleContainer.BeginAbsoluteSequence(HitObject.StartTime))
triangleContainer.FadeOut(150);
}
}
}

View File

@ -0,0 +1,19 @@
// 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.Framework.Allocation;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Taiko.UI
{
public class BarLinePlayfield : ScrollingPlayfield
{
[BackgroundDependencyLoader]
private void load()
{
RegisterPool<BarLine, DrawableBarLine>(15);
}
}
}

View File

@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.UI
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
new BarLineGenerator<BarLine>(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar))); new BarLineGenerator<BarLine>(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar));
FrameStableComponents.Add(scroller = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Scroller), _ => Empty()) FrameStableComponents.Add(scroller = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Scroller), _ => Empty())
{ {

View File

@ -10,6 +10,7 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
@ -38,10 +39,15 @@ namespace osu.Game.Rulesets.Taiko.UI
private SkinnableDrawable mascot; private SkinnableDrawable mascot;
private ProxyContainer topLevelHitContainer; private ProxyContainer topLevelHitContainer;
private ScrollingHitObjectContainer barlineContainer;
private Container rightArea; private Container rightArea;
private Container leftArea; private Container leftArea;
/// <remarks>
/// <see cref="Playfield.AddNested"/> is purposefully not called on this to prevent i.e. being able to interact
/// with bar lines in the editor.
/// </remarks>
private BarLinePlayfield barLinePlayfield;
private Container hitTargetOffsetContent; private Container hitTargetOffsetContent;
public TaikoPlayfield(ControlPointInfo controlPoints) public TaikoPlayfield(ControlPointInfo controlPoints)
@ -84,7 +90,7 @@ namespace osu.Game.Rulesets.Taiko.UI
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Children = new Drawable[] Children = new Drawable[]
{ {
barlineContainer = new ScrollingHitObjectContainer(), barLinePlayfield = new BarLinePlayfield(),
new Container new Container
{ {
Name = "Hit objects", Name = "Hit objects",
@ -155,12 +161,50 @@ namespace osu.Game.Rulesets.Taiko.UI
mascot.Scale = new Vector2(DrawHeight / DEFAULT_HEIGHT); mascot.Scale = new Vector2(DrawHeight / DEFAULT_HEIGHT);
} }
#region Pooling support
public override void Add(HitObject h)
{
switch (h)
{
case BarLine barLine:
barLinePlayfield.Add(barLine);
break;
case TaikoHitObject taikoHitObject:
base.Add(taikoHitObject);
break;
default:
throw new ArgumentException($"Unsupported {nameof(HitObject)} type: {h.GetType()}");
}
}
public override bool Remove(HitObject h)
{
switch (h)
{
case BarLine barLine:
return barLinePlayfield.Remove(barLine);
case TaikoHitObject taikoHitObject:
return base.Remove(taikoHitObject);
default:
throw new ArgumentException($"Unsupported {nameof(HitObject)} type: {h.GetType()}");
}
}
#endregion
#region Non-pooling support
public override void Add(DrawableHitObject h) public override void Add(DrawableHitObject h)
{ {
switch (h) switch (h)
{ {
case DrawableBarLine barline: case DrawableBarLine barLine:
barlineContainer.Add(barline); barLinePlayfield.Add(barLine);
break; break;
case DrawableTaikoHitObject taikoObject: case DrawableTaikoHitObject taikoObject:
@ -170,7 +214,7 @@ namespace osu.Game.Rulesets.Taiko.UI
break; break;
default: default:
throw new ArgumentException($"Unsupported {nameof(DrawableHitObject)} type"); throw new ArgumentException($"Unsupported {nameof(DrawableHitObject)} type: {h.GetType()}");
} }
} }
@ -178,8 +222,8 @@ namespace osu.Game.Rulesets.Taiko.UI
{ {
switch (h) switch (h)
{ {
case DrawableBarLine barline: case DrawableBarLine barLine:
return barlineContainer.Remove(barline); return barLinePlayfield.Remove(barLine);
case DrawableTaikoHitObject _: case DrawableTaikoHitObject _:
h.OnNewResult -= OnNewResult; h.OnNewResult -= OnNewResult;
@ -187,10 +231,12 @@ namespace osu.Game.Rulesets.Taiko.UI
return base.Remove(h); return base.Remove(h);
default: default:
throw new ArgumentException($"Unsupported {nameof(DrawableHitObject)} type"); throw new ArgumentException($"Unsupported {nameof(DrawableHitObject)} type: {h.GetType()}");
} }
} }
#endregion
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result) internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
{ {
if (!DisplayJudgements.Value) if (!DisplayJudgements.Value)

View File

@ -191,7 +191,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Y = -separation, Y = -separation,
HandleDrag = e => OnRotation?.Invoke(e.Delta.X), HandleDrag = e => OnRotation?.Invoke(convertDragEventToAngleOfRotation(e)),
OperationStarted = operationStarted, OperationStarted = operationStarted,
OperationEnded = operationEnded OperationEnded = operationEnded
} }
@ -242,6 +242,15 @@ namespace osu.Game.Screens.Edit.Compose.Components
private int activeOperations; private int activeOperations;
private float convertDragEventToAngleOfRotation(DragEvent e)
{
// Adjust coordinate system to the center of SelectionBox
float startAngle = MathF.Atan2(e.LastMousePosition.Y - DrawHeight / 2, e.LastMousePosition.X - DrawWidth / 2);
float endAngle = MathF.Atan2(e.MousePosition.Y - DrawHeight / 2, e.MousePosition.X - DrawWidth / 2);
return (endAngle - startAngle) * 180 / MathF.PI;
}
private void operationEnded() private void operationEnded()
{ {
if (--activeOperations == 0) if (--activeOperations == 0)

View File

@ -189,7 +189,7 @@ namespace osu.Game.Screens.Play
// after an initial delay, start the debounced load check. // after an initial delay, start the debounced load check.
// this will continue to execute even after resuming back on restart. // this will continue to execute even after resuming back on restart.
Scheduler.Add(new ScheduledDelegate(pushWhenLoaded, 1800, 0)); Scheduler.Add(new ScheduledDelegate(pushWhenLoaded, Clock.CurrentTime + 1800, 0));
showMuteWarningIfNeeded(); showMuteWarningIfNeeded();
} }

View File

@ -35,6 +35,7 @@ namespace osu.Game.Skinning
public float HitPosition = (480 - 402) * POSITION_SCALE_FACTOR; public float HitPosition = (480 - 402) * POSITION_SCALE_FACTOR;
public float LightPosition = (480 - 413) * POSITION_SCALE_FACTOR; public float LightPosition = (480 - 413) * POSITION_SCALE_FACTOR;
public float ScorePosition = 300 * POSITION_SCALE_FACTOR;
public bool ShowJudgementLine = true; public bool ShowJudgementLine = true;
public bool KeysUnderNotes; public bool KeysUnderNotes;

View File

@ -25,6 +25,7 @@ namespace osu.Game.Skinning
LeftLineWidth, LeftLineWidth,
RightLineWidth, RightLineWidth,
HitPosition, HitPosition,
ScorePosition,
LightPosition, LightPosition,
HitTargetImage, HitTargetImage,
ShowJudgementLine, ShowJudgementLine,

View File

@ -94,6 +94,10 @@ namespace osu.Game.Skinning
currentConfig.LightPosition = (480 - float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; currentConfig.LightPosition = (480 - float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR;
break; break;
case "ScorePosition":
currentConfig.ScorePosition = (float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR;
break;
case "JudgementLine": case "JudgementLine":
currentConfig.ShowJudgementLine = pair.Value == "1"; currentConfig.ShowJudgementLine = pair.Value == "1";
break; break;

View File

@ -169,6 +169,9 @@ namespace osu.Game.Skinning
case LegacyManiaSkinConfigurationLookups.HitPosition: case LegacyManiaSkinConfigurationLookups.HitPosition:
return SkinUtils.As<TValue>(new Bindable<float>(existing.HitPosition)); return SkinUtils.As<TValue>(new Bindable<float>(existing.HitPosition));
case LegacyManiaSkinConfigurationLookups.ScorePosition:
return SkinUtils.As<TValue>(new Bindable<float>(existing.ScorePosition));
case LegacyManiaSkinConfigurationLookups.LightPosition: case LegacyManiaSkinConfigurationLookups.LightPosition:
return SkinUtils.As<TValue>(new Bindable<float>(existing.LightPosition)); return SkinUtils.As<TValue>(new Bindable<float>(existing.LightPosition));

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.1212.0" /> <PackageReference Include="ppy.osu.Framework" Version="2020.1214.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" />
<PackageReference Include="Sentry" Version="2.1.8" /> <PackageReference Include="Sentry" Version="2.1.8" />
<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.1212.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2020.1214.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.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.1212.0" /> <PackageReference Include="ppy.osu.Framework" Version="2020.1214.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" />