mirror of
https://github.com/ppy/osu.git
synced 2025-01-15 16:42:57 +08:00
Merge branch 'hitobject-pooling-base' into osu-hitobject-pooling
This commit is contained in:
commit
7085b25898
@ -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>
|
||||||
|
@ -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}",
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
@ -21,12 +21,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
base.OnSelectionChanged();
|
base.OnSelectionChanged();
|
||||||
|
|
||||||
bool canOperate = EditorBeatmap.SelectedHitObjects.Count > 1 || EditorBeatmap.SelectedHitObjects.Any(s => s is Slider);
|
Quad quad = selectedMovableObjects.Length > 0 ? getSurroundingQuad(selectedMovableObjects) : new Quad();
|
||||||
|
|
||||||
SelectionBox.CanRotate = canOperate;
|
SelectionBox.CanRotate = quad.Width > 0 || quad.Height > 0;
|
||||||
SelectionBox.CanScaleX = canOperate;
|
SelectionBox.CanScaleX = quad.Width > 0;
|
||||||
SelectionBox.CanScaleY = canOperate;
|
SelectionBox.CanScaleY = quad.Height > 0;
|
||||||
SelectionBox.CanReverse = canOperate;
|
SelectionBox.CanReverse = EditorBeatmap.SelectedHitObjects.Count > 1 || EditorBeatmap.SelectedHitObjects.Any(s => s is Slider);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnOperationEnded()
|
protected override void OnOperationEnded()
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -27,21 +28,24 @@ 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;
|
||||||
}
|
|
||||||
|
|
||||||
private double lastSliderHeadFadeOutStartTime;
|
foreach (var nested in d.NestedHitObjects)
|
||||||
private double lastSliderHeadFadeOutDuration;
|
applyFadeInAdjustment(nested);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||||
{
|
{
|
||||||
@ -72,33 +76,24 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
case DrawableSliderTail sliderTail:
|
case DrawableSliderTail sliderTail:
|
||||||
// use stored values from head circle to achieve same fade sequence.
|
// use stored values from head circle to achieve same fade sequence.
|
||||||
fadeOutDuration = lastSliderHeadFadeOutDuration;
|
var tailFadeOutParameters = getFadeOutParametersFromSliderHead(h);
|
||||||
fadeOutStartTime = lastSliderHeadFadeOutStartTime;
|
|
||||||
|
|
||||||
using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true))
|
using (drawable.BeginAbsoluteSequence(tailFadeOutParameters.startTime, true))
|
||||||
sliderTail.FadeOut(fadeOutDuration);
|
sliderTail.FadeOut(tailFadeOutParameters.duration);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DrawableSliderRepeat sliderRepeat:
|
case DrawableSliderRepeat sliderRepeat:
|
||||||
// use stored values from head circle to achieve same fade sequence.
|
// use stored values from head circle to achieve same fade sequence.
|
||||||
fadeOutDuration = lastSliderHeadFadeOutDuration;
|
var repeatFadeOutParameters = getFadeOutParametersFromSliderHead(h);
|
||||||
fadeOutStartTime = lastSliderHeadFadeOutStartTime;
|
|
||||||
|
|
||||||
using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true))
|
using (drawable.BeginAbsoluteSequence(repeatFadeOutParameters.startTime, true))
|
||||||
// only apply to circle piece – reverse arrow is not affected by hidden.
|
// only apply to circle piece – reverse arrow is not affected by hidden.
|
||||||
sliderRepeat.CirclePiece.FadeOut(fadeOutDuration);
|
sliderRepeat.CirclePiece.FadeOut(repeatFadeOutParameters.duration);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DrawableHitCircle circle:
|
case DrawableHitCircle circle:
|
||||||
|
|
||||||
if (circle is DrawableSliderHead)
|
|
||||||
{
|
|
||||||
lastSliderHeadFadeOutDuration = fadeOutDuration;
|
|
||||||
lastSliderHeadFadeOutStartTime = fadeOutStartTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
Drawable fadeTarget = circle;
|
Drawable fadeTarget = circle;
|
||||||
|
|
||||||
if (increaseVisibility)
|
if (increaseVisibility)
|
||||||
@ -119,6 +114,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case DrawableSlider slider:
|
case DrawableSlider slider:
|
||||||
|
associateNestedSliderCirclesWithHead(slider.HitObject);
|
||||||
|
|
||||||
using (slider.BeginAbsoluteSequence(fadeOutStartTime, true))
|
using (slider.BeginAbsoluteSequence(fadeOutStartTime, true))
|
||||||
slider.Body.FadeOut(longFadeDuration, Easing.Out);
|
slider.Body.FadeOut(longFadeDuration, Easing.Out);
|
||||||
|
|
||||||
@ -143,5 +140,24 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly Dictionary<HitObject, SliderHeadCircle> correspondingSliderHeadForObject = new Dictionary<HitObject, SliderHeadCircle>();
|
||||||
|
|
||||||
|
private void associateNestedSliderCirclesWithHead(Slider slider)
|
||||||
|
{
|
||||||
|
var sliderHead = slider.NestedHitObjects.Single(obj => obj is SliderHeadCircle);
|
||||||
|
|
||||||
|
foreach (var nested in slider.NestedHitObjects)
|
||||||
|
{
|
||||||
|
if ((nested is SliderRepeat || nested is SliderEndCircle) && !correspondingSliderHeadForObject.ContainsKey(nested))
|
||||||
|
correspondingSliderHeadForObject[nested] = (SliderHeadCircle)sliderHead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private (double startTime, double duration) getFadeOutParametersFromSliderHead(OsuHitObject h)
|
||||||
|
{
|
||||||
|
var sliderHead = correspondingSliderHeadForObject[h];
|
||||||
|
return (sliderHead.StartTime - sliderHead.TimePreempt + sliderHead.TimeFadeIn, sliderHead.TimePreempt * fade_out_duration_multiplier);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 });
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
TaikoExplosionMiss,
|
TaikoExplosionMiss,
|
||||||
TaikoExplosionOk,
|
TaikoExplosionOk,
|
||||||
TaikoExplosionGreat,
|
TaikoExplosionGreat,
|
||||||
|
TaikoExplosionKiai,
|
||||||
Scroller,
|
Scroller,
|
||||||
Mascot,
|
Mascot,
|
||||||
}
|
}
|
||||||
|
64
osu.Game.Rulesets.Taiko/UI/DefaultKiaiHitExplosion.cs
Normal file
64
osu.Game.Rulesets.Taiko/UI/DefaultKiaiHitExplosion.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,6 +142,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
protected override HitObjectLifetimeEntry CreateLifetimeEntry(TestHitObject hitObject) => new TestHitObjectLifetimeEntry(hitObject);
|
protected override HitObjectLifetimeEntry CreateLifetimeEntry(TestHitObject hitObject) => new TestHitObjectLifetimeEntry(hitObject);
|
||||||
|
|
||||||
|
public override DrawableHitObject<TestHitObject> CreateDrawableRepresentation(TestHitObject h) => null;
|
||||||
|
|
||||||
protected override PassThroughInputManager CreateInputManager() => new PassThroughInputManager();
|
protected override PassThroughInputManager CreateInputManager() => new PassThroughInputManager();
|
||||||
|
|
||||||
protected override Playfield CreatePlayfield() => new TestPlayfield();
|
protected override Playfield CreatePlayfield() => new TestPlayfield();
|
||||||
|
@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
}
|
}
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public void SetUpSteps()
|
public virtual void SetUpSteps()
|
||||||
{
|
{
|
||||||
AddStep("Create new game instance", () =>
|
AddStep("Create new game instance", () =>
|
||||||
{
|
{
|
||||||
|
@ -2,23 +2,33 @@
|
|||||||
// 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 NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Screens;
|
||||||
using osu.Game.Screens.Menu;
|
using osu.Game.Screens.Menu;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Navigation
|
namespace osu.Game.Tests.Visual.Navigation
|
||||||
{
|
{
|
||||||
public class TestScenePerformFromScreen : OsuGameTestScene
|
public class TestScenePerformFromScreen : OsuGameTestScene
|
||||||
{
|
{
|
||||||
|
private bool actionPerformed;
|
||||||
|
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("reset status", () => actionPerformed = false);
|
||||||
|
|
||||||
|
base.SetUpSteps();
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestPerformAtMenu()
|
public void TestPerformAtMenu()
|
||||||
{
|
{
|
||||||
AddAssert("could perform immediately", () =>
|
AddStep("perform immediately", () => Game.PerformFromScreen(_ => actionPerformed = true));
|
||||||
{
|
AddAssert("did perform", () => actionPerformed);
|
||||||
bool actionPerformed = false;
|
|
||||||
Game.PerformFromScreen(_ => actionPerformed = true);
|
|
||||||
return actionPerformed;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -26,12 +36,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
{
|
{
|
||||||
PushAndConfirm(() => new PlaySongSelect());
|
PushAndConfirm(() => new PlaySongSelect());
|
||||||
|
|
||||||
AddAssert("could perform immediately", () =>
|
AddStep("perform immediately", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) }));
|
||||||
{
|
AddAssert("did perform", () => actionPerformed);
|
||||||
bool actionPerformed = false;
|
AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
||||||
Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) });
|
|
||||||
return actionPerformed;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -39,7 +46,6 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
{
|
{
|
||||||
PushAndConfirm(() => new PlaySongSelect());
|
PushAndConfirm(() => new PlaySongSelect());
|
||||||
|
|
||||||
bool actionPerformed = false;
|
|
||||||
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
|
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
|
||||||
AddUntilStep("returned to menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
AddUntilStep("returned to menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
||||||
AddAssert("did perform", () => actionPerformed);
|
AddAssert("did perform", () => actionPerformed);
|
||||||
@ -51,7 +57,6 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
PushAndConfirm(() => new PlaySongSelect());
|
PushAndConfirm(() => new PlaySongSelect());
|
||||||
PushAndConfirm(() => new PlayerLoader(() => new Player()));
|
PushAndConfirm(() => new PlayerLoader(() => new Player()));
|
||||||
|
|
||||||
bool actionPerformed = false;
|
|
||||||
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) }));
|
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) }));
|
||||||
AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
||||||
AddAssert("did perform", () => actionPerformed);
|
AddAssert("did perform", () => actionPerformed);
|
||||||
@ -63,10 +68,105 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
PushAndConfirm(() => new PlaySongSelect());
|
PushAndConfirm(() => new PlaySongSelect());
|
||||||
PushAndConfirm(() => new PlayerLoader(() => new Player()));
|
PushAndConfirm(() => new PlayerLoader(() => new Player()));
|
||||||
|
|
||||||
bool actionPerformed = false;
|
|
||||||
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
|
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
|
||||||
AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
||||||
AddAssert("did perform", () => actionPerformed);
|
AddAssert("did perform", () => actionPerformed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase(true)]
|
||||||
|
[TestCase(false)]
|
||||||
|
public void TestPerformBlockedByDialog(bool confirmed)
|
||||||
|
{
|
||||||
|
DialogBlockingScreen blocker = null;
|
||||||
|
|
||||||
|
PushAndConfirm(() => blocker = new DialogBlockingScreen());
|
||||||
|
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
|
||||||
|
|
||||||
|
AddWaitStep("wait a bit", 10);
|
||||||
|
|
||||||
|
AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen is DialogBlockingScreen);
|
||||||
|
AddAssert("did not perform", () => !actionPerformed);
|
||||||
|
AddAssert("only one exit attempt", () => blocker.ExitAttempts == 1);
|
||||||
|
|
||||||
|
AddUntilStep("wait for dialog display", () => Game.Dependencies.Get<DialogOverlay>().IsLoaded);
|
||||||
|
|
||||||
|
if (confirmed)
|
||||||
|
{
|
||||||
|
AddStep("accept dialog", () => InputManager.Key(Key.Number1));
|
||||||
|
AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get<DialogOverlay>().CurrentDialog == null);
|
||||||
|
AddUntilStep("did perform", () => actionPerformed);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddStep("cancel dialog", () => InputManager.Key(Key.Number2));
|
||||||
|
AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen is DialogBlockingScreen);
|
||||||
|
AddAssert("did not perform", () => !actionPerformed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(true)]
|
||||||
|
[TestCase(false)]
|
||||||
|
public void TestPerformBlockedByDialogNested(bool confirmSecond)
|
||||||
|
{
|
||||||
|
DialogBlockingScreen blocker = null;
|
||||||
|
DialogBlockingScreen blocker2 = null;
|
||||||
|
|
||||||
|
PushAndConfirm(() => blocker = new DialogBlockingScreen());
|
||||||
|
PushAndConfirm(() => blocker2 = new DialogBlockingScreen());
|
||||||
|
|
||||||
|
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
|
||||||
|
|
||||||
|
AddUntilStep("wait for dialog", () => blocker2.ExitAttempts == 1);
|
||||||
|
|
||||||
|
AddWaitStep("wait a bit", 10);
|
||||||
|
|
||||||
|
AddUntilStep("wait for dialog display", () => Game.Dependencies.Get<DialogOverlay>().IsLoaded);
|
||||||
|
|
||||||
|
AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen == blocker2);
|
||||||
|
AddAssert("did not perform", () => !actionPerformed);
|
||||||
|
AddAssert("only one exit attempt", () => blocker2.ExitAttempts == 1);
|
||||||
|
|
||||||
|
AddStep("accept dialog", () => InputManager.Key(Key.Number1));
|
||||||
|
AddUntilStep("screen changed", () => Game.ScreenStack.CurrentScreen == blocker);
|
||||||
|
|
||||||
|
AddUntilStep("wait for second dialog", () => blocker.ExitAttempts == 1);
|
||||||
|
AddAssert("did not perform", () => !actionPerformed);
|
||||||
|
AddAssert("only one exit attempt", () => blocker.ExitAttempts == 1);
|
||||||
|
|
||||||
|
if (confirmSecond)
|
||||||
|
{
|
||||||
|
AddStep("accept dialog", () => InputManager.Key(Key.Number1));
|
||||||
|
AddUntilStep("did perform", () => actionPerformed);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddStep("cancel dialog", () => InputManager.Key(Key.Number2));
|
||||||
|
AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen == blocker);
|
||||||
|
AddAssert("did not perform", () => !actionPerformed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DialogBlockingScreen : OsuScreen
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private DialogOverlay dialogOverlay { get; set; }
|
||||||
|
|
||||||
|
private int dialogDisplayCount;
|
||||||
|
|
||||||
|
public int ExitAttempts { get; private set; }
|
||||||
|
|
||||||
|
public override bool OnExiting(IScreen next)
|
||||||
|
{
|
||||||
|
ExitAttempts++;
|
||||||
|
|
||||||
|
if (dialogDisplayCount++ < 1)
|
||||||
|
{
|
||||||
|
dialogOverlay.Push(new ConfirmExitDialog(this.Exit, () => { }));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnExiting(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,24 @@
|
|||||||
// 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 NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Ranking.Statistics;
|
using osu.Game.Screens.Ranking.Statistics;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Ranking
|
namespace osu.Game.Tests.Visual.Ranking
|
||||||
{
|
{
|
||||||
public class TestSceneStatisticsPanel : OsuTestScene
|
public class TestSceneStatisticsPanel : OsuTestScene
|
||||||
{
|
{
|
||||||
[Test]
|
[Test]
|
||||||
public void TestScoreWithStatistics()
|
public void TestScoreWithTimeStatistics()
|
||||||
{
|
{
|
||||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo)
|
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo)
|
||||||
{
|
{
|
||||||
@ -23,6 +28,17 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
loadPanel(score);
|
loadPanel(score);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScoreWithPositionStatistics()
|
||||||
|
{
|
||||||
|
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo)
|
||||||
|
{
|
||||||
|
HitEvents = createPositionDistributedHitEvents()
|
||||||
|
};
|
||||||
|
|
||||||
|
loadPanel(score);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestScoreWithoutStatistics()
|
public void TestScoreWithoutStatistics()
|
||||||
{
|
{
|
||||||
@ -44,5 +60,24 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
Score = { Value = score }
|
Score = { Value = score }
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private static List<HitEvent> createPositionDistributedHitEvents()
|
||||||
|
{
|
||||||
|
var hitEvents = new List<HitEvent>();
|
||||||
|
// Use constant seed for reproducibility
|
||||||
|
var random = new Random(0);
|
||||||
|
|
||||||
|
for (int i = 0; i < 500; i++)
|
||||||
|
{
|
||||||
|
double angle = random.NextDouble() * 2 * Math.PI;
|
||||||
|
double radius = random.NextDouble() * 0.5f * OsuHitObject.OBJECT_RADIUS;
|
||||||
|
|
||||||
|
var position = new Vector2((float)(radius * Math.Cos(angle)), (float)(radius * Math.Sin(angle)));
|
||||||
|
|
||||||
|
hitEvents.Add(new HitEvent(0, HitResult.Perfect, new HitCircle(), new HitCircle(), position));
|
||||||
|
}
|
||||||
|
|
||||||
|
return hitEvents;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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)
|
||||||
|
@ -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);
|
||||||
|
@ -463,7 +463,7 @@ namespace osu.Game
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private ScheduledDelegate performFromMainMenuTask;
|
private PerformFromMenuRunner performFromMainMenuTask;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Perform an action only after returning to a specific screen as indicated by <paramref name="validScreens"/>.
|
/// Perform an action only after returning to a specific screen as indicated by <paramref name="validScreens"/>.
|
||||||
@ -474,34 +474,7 @@ namespace osu.Game
|
|||||||
public void PerformFromScreen(Action<IScreen> action, IEnumerable<Type> validScreens = null)
|
public void PerformFromScreen(Action<IScreen> action, IEnumerable<Type> validScreens = null)
|
||||||
{
|
{
|
||||||
performFromMainMenuTask?.Cancel();
|
performFromMainMenuTask?.Cancel();
|
||||||
|
Add(performFromMainMenuTask = new PerformFromMenuRunner(action, validScreens, () => ScreenStack.CurrentScreen));
|
||||||
validScreens ??= Enumerable.Empty<Type>();
|
|
||||||
validScreens = validScreens.Append(typeof(MainMenu));
|
|
||||||
|
|
||||||
CloseAllOverlays(false);
|
|
||||||
|
|
||||||
// we may already be at the target screen type.
|
|
||||||
if (validScreens.Contains(ScreenStack.CurrentScreen?.GetType()) && !Beatmap.Disabled)
|
|
||||||
{
|
|
||||||
action(ScreenStack.CurrentScreen);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// find closest valid target
|
|
||||||
IScreen screen = ScreenStack.CurrentScreen;
|
|
||||||
|
|
||||||
while (screen != null)
|
|
||||||
{
|
|
||||||
if (validScreens.Contains(screen.GetType()))
|
|
||||||
{
|
|
||||||
screen.MakeCurrent();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
screen = screen.GetParentScreen();
|
|
||||||
}
|
|
||||||
|
|
||||||
performFromMainMenuTask = Schedule(() => PerformFromScreen(action, validScreens));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -681,7 +654,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;
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
145
osu.Game/PerformFromMenuRunner.cs
Normal file
145
osu.Game/PerformFromMenuRunner.cs
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
// 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;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using osu.Framework.Threading;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Dialog;
|
||||||
|
using osu.Game.Overlays.Notifications;
|
||||||
|
using osu.Game.Screens.Menu;
|
||||||
|
|
||||||
|
namespace osu.Game
|
||||||
|
{
|
||||||
|
internal class PerformFromMenuRunner : Component
|
||||||
|
{
|
||||||
|
private readonly Action<IScreen> finalAction;
|
||||||
|
private readonly Type[] validScreens;
|
||||||
|
private readonly Func<IScreen> getCurrentScreen;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private NotificationOverlay notifications { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private DialogOverlay dialogOverlay { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IBindable<WorkingBeatmap> beatmap { get; set; }
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private OsuGame game { get; set; }
|
||||||
|
|
||||||
|
private readonly ScheduledDelegate task;
|
||||||
|
|
||||||
|
private PopupDialog lastEncounteredDialog;
|
||||||
|
private IScreen lastEncounteredDialogScreen;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Perform an action only after returning to a specific screen as indicated by <paramref name="validScreens"/>.
|
||||||
|
/// Eagerly tries to exit the current screen until it succeeds.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="finalAction">The action to perform once we are in the correct state.</param>
|
||||||
|
/// <param name="validScreens">An optional collection of valid screen types. If any of these screens are already current we can perform the action immediately, else the first valid parent will be made current before performing the action. <see cref="MainMenu"/> is used if not specified.</param>
|
||||||
|
/// <param name="getCurrentScreen">A function to retrieve the currently displayed game screen.</param>
|
||||||
|
public PerformFromMenuRunner(Action<IScreen> finalAction, IEnumerable<Type> validScreens, Func<IScreen> getCurrentScreen)
|
||||||
|
{
|
||||||
|
validScreens ??= Enumerable.Empty<Type>();
|
||||||
|
validScreens = validScreens.Append(typeof(MainMenu));
|
||||||
|
|
||||||
|
this.finalAction = finalAction;
|
||||||
|
this.validScreens = validScreens.ToArray();
|
||||||
|
this.getCurrentScreen = getCurrentScreen;
|
||||||
|
|
||||||
|
Scheduler.Add(task = new ScheduledDelegate(checkCanComplete, 0, 200));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancel this runner from running.
|
||||||
|
/// </summary>
|
||||||
|
public void Cancel()
|
||||||
|
{
|
||||||
|
task.Cancel();
|
||||||
|
Expire();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkCanComplete()
|
||||||
|
{
|
||||||
|
// find closest valid target
|
||||||
|
IScreen current = getCurrentScreen();
|
||||||
|
|
||||||
|
// a dialog may be blocking the execution for now.
|
||||||
|
if (checkForDialog(current)) return;
|
||||||
|
|
||||||
|
// we may already be at the target screen type.
|
||||||
|
if (validScreens.Contains(getCurrentScreen().GetType()) && !beatmap.Disabled)
|
||||||
|
{
|
||||||
|
complete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
game.CloseAllOverlays(false);
|
||||||
|
|
||||||
|
while (current != null)
|
||||||
|
{
|
||||||
|
if (validScreens.Contains(current.GetType()))
|
||||||
|
{
|
||||||
|
current.MakeCurrent();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = current.GetParentScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check whether there is currently a dialog requiring user interaction.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="current"></param>
|
||||||
|
/// <returns>Whether a dialog blocked interaction.</returns>
|
||||||
|
private bool checkForDialog(IScreen current)
|
||||||
|
{
|
||||||
|
var currentDialog = dialogOverlay.CurrentDialog;
|
||||||
|
|
||||||
|
if (lastEncounteredDialog != null)
|
||||||
|
{
|
||||||
|
if (lastEncounteredDialog == currentDialog)
|
||||||
|
// still waiting on user interaction
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (lastEncounteredDialogScreen != current)
|
||||||
|
{
|
||||||
|
// a dialog was previously encountered but has since been dismissed.
|
||||||
|
// if the screen changed, the user likely confirmed an exit dialog and we should continue attempting the action.
|
||||||
|
lastEncounteredDialog = null;
|
||||||
|
lastEncounteredDialogScreen = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the last dialog encountered has been dismissed but the screen has not changed, abort.
|
||||||
|
Cancel();
|
||||||
|
notifications.Post(new SimpleNotification { Text = @"An action was interrupted due to a dialog being displayed." });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentDialog == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// a new dialog was encountered.
|
||||||
|
lastEncounteredDialog = currentDialog;
|
||||||
|
lastEncounteredDialogScreen = current;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void complete()
|
||||||
|
{
|
||||||
|
finalAction(getCurrentScreen());
|
||||||
|
Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,8 +28,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>
|
||||||
@ -166,7 +174,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
StartTimeBindable.BindValueChanged(_ => updateState(State.Value, true));
|
|
||||||
comboIndexBindable.BindValueChanged(_ => updateComboColour(), true);
|
comboIndexBindable.BindValueChanged(_ => updateComboColour(), true);
|
||||||
|
|
||||||
updateState(ArmedState.Idle, true);
|
updateState(ArmedState.Idle, true);
|
||||||
@ -220,6 +227,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
}
|
}
|
||||||
|
|
||||||
StartTimeBindable.BindTo(HitObject.StartTimeBindable);
|
StartTimeBindable.BindTo(HitObject.StartTimeBindable);
|
||||||
|
StartTimeBindable.BindValueChanged(onStartTimeChanged);
|
||||||
|
|
||||||
if (HitObject is IHasComboInformation combo)
|
if (HitObject is IHasComboInformation combo)
|
||||||
comboIndexBindable.BindTo(combo.ComboIndexBindable);
|
comboIndexBindable.BindTo(combo.ComboIndexBindable);
|
||||||
|
|
||||||
@ -229,6 +238,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)
|
||||||
@ -248,9 +258,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
StartTimeBindable.UnbindFrom(HitObject.StartTimeBindable);
|
StartTimeBindable.UnbindFrom(HitObject.StartTimeBindable);
|
||||||
if (HitObject is IHasComboInformation combo)
|
if (HitObject is IHasComboInformation combo)
|
||||||
comboIndexBindable.UnbindFrom(combo.ComboIndexBindable);
|
comboIndexBindable.UnbindFrom(combo.ComboIndexBindable);
|
||||||
|
|
||||||
samplesBindable.UnbindFrom(HitObject.SamplesBindable);
|
samplesBindable.UnbindFrom(HitObject.SamplesBindable);
|
||||||
|
|
||||||
|
// Changes in start time trigger state updates. When a new hitobject is applied, OnApply() automatically performs a state update anyway.
|
||||||
|
StartTimeBindable.ValueChanged -= onStartTimeChanged;
|
||||||
|
|
||||||
// When a new hitobject is applied, the samples will be cleared before re-populating.
|
// When a new hitobject is applied, the samples will be cleared before re-populating.
|
||||||
// In order to stop this needless update, the event is unbound and re-bound as late as possible in Apply().
|
// In order to stop this needless update, the event is unbound and re-bound as late as possible in Apply().
|
||||||
samplesBindable.CollectionChanged -= onSamplesChanged;
|
samplesBindable.CollectionChanged -= onSamplesChanged;
|
||||||
@ -337,6 +349,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
|
|
||||||
private void onSamplesChanged(object sender, NotifyCollectionChangedEventArgs e) => LoadSamples();
|
private void onSamplesChanged(object sender, NotifyCollectionChangedEventArgs e) => LoadSamples();
|
||||||
|
|
||||||
|
private void onStartTimeChanged(ValueChangedEvent<double> startTime) => updateState(State.Value, true);
|
||||||
|
|
||||||
private void onNewResult(DrawableHitObject drawableHitObject, JudgementResult result) => OnNewResult?.Invoke(drawableHitObject, result);
|
private void onNewResult(DrawableHitObject drawableHitObject, JudgementResult result) => OnNewResult?.Invoke(drawableHitObject, result);
|
||||||
|
|
||||||
private void onRevertResult(DrawableHitObject drawableHitObject, JudgementResult result) => OnRevertResult?.Invoke(drawableHitObject, result);
|
private void onRevertResult(DrawableHitObject drawableHitObject, JudgementResult result) => OnRevertResult?.Invoke(drawableHitObject, result);
|
||||||
|
@ -324,11 +324,16 @@ namespace osu.Game.Rulesets.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a DrawableHitObject from a HitObject.
|
/// Creates a <see cref="DrawableHitObject{TObject}"/> to represent a <see cref="HitObject"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="h">The HitObject to make drawable.</param>
|
/// <remarks>
|
||||||
/// <returns>The DrawableHitObject.</returns>
|
/// If this method returns <c>null</c>, then this <see cref="DrawableRuleset"/> will assume the requested <see cref="HitObject"/> type is being pooled,
|
||||||
public virtual DrawableHitObject<TObject> CreateDrawableRepresentation(TObject h) => null;
|
/// and will instead attempt to retrieve the <see cref="DrawableHitObject"/>s at the point they should become alive via pools registered through
|
||||||
|
/// <see cref="DrawableRuleset.RegisterPool{TObject, TDrawable}(int, int?)"/> or <see cref="DrawableRuleset.RegisterPool{TObject, TDrawable}(DrawablePool{TDrawable})"/>.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="h">The <see cref="HitObject"/> to represent.</param>
|
||||||
|
/// <returns>The representing <see cref="DrawableHitObject{TObject}"/>.</returns>
|
||||||
|
public abstract DrawableHitObject<TObject> CreateDrawableRepresentation(TObject h);
|
||||||
|
|
||||||
public void Attach(KeyCounterDisplay keyCounter) =>
|
public void Attach(KeyCounterDisplay keyCounter) =>
|
||||||
(KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(keyCounter);
|
(KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(keyCounter);
|
||||||
|
@ -136,8 +136,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Dictionary<HitObject, HitObjectLifetimeEntry> lifetimeEntryMap = new Dictionary<HitObject, HitObjectLifetimeEntry>();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a <see cref="HitObjectLifetimeEntry"/> for a pooled <see cref="HitObject"/> to this <see cref="Playfield"/>.
|
/// Adds a <see cref="HitObjectLifetimeEntry"/> for a pooled <see cref="HitObject"/> to this <see cref="Playfield"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -145,7 +143,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
public virtual void Add(HitObjectLifetimeEntry entry)
|
public virtual void Add(HitObjectLifetimeEntry entry)
|
||||||
{
|
{
|
||||||
HitObjectContainer.Add(entry);
|
HitObjectContainer.Add(entry);
|
||||||
lifetimeEntryMap[entry.HitObject] = entry;
|
|
||||||
OnHitObjectAdded(entry.HitObject);
|
OnHitObjectAdded(entry.HitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,9 +153,8 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// <returns>Whether the <see cref="HitObject"/> was successfully removed.</returns>
|
/// <returns>Whether the <see cref="HitObject"/> was successfully removed.</returns>
|
||||||
public virtual bool Remove(HitObjectLifetimeEntry entry)
|
public virtual bool Remove(HitObjectLifetimeEntry entry)
|
||||||
{
|
{
|
||||||
if (lifetimeEntryMap.Remove(entry.HitObject))
|
if (HitObjectContainer.Remove(entry))
|
||||||
{
|
{
|
||||||
HitObjectContainer.Remove(entry);
|
|
||||||
OnHitObjectRemoved(entry.HitObject);
|
OnHitObjectRemoved(entry.HitObject);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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" />
|
||||||
|
@ -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" />
|
||||||
|
Loading…
Reference in New Issue
Block a user