mirror of
https://github.com/ppy/osu.git
synced 2025-01-15 14:12:54 +08:00
Merge branch 'master' into mania-legacyskin-scoreposition
This commit is contained in:
commit
e01b41b7d1
@ -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.1203.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1212.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -18,6 +18,7 @@ using osu.Game.Rulesets.Catch.Objects;
|
|||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
@ -28,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuConfigManager config { get; set; }
|
private OsuConfigManager config { get; set; }
|
||||||
|
|
||||||
private Container droppedObjectContainer;
|
private Container<CaughtObject> droppedObjectContainer;
|
||||||
|
|
||||||
private TestCatcher catcher;
|
private TestCatcher catcher;
|
||||||
|
|
||||||
@ -41,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
};
|
};
|
||||||
|
|
||||||
var trailContainer = new Container();
|
var trailContainer = new Container();
|
||||||
droppedObjectContainer = new Container();
|
droppedObjectContainer = new Container<CaughtObject>();
|
||||||
catcher = new TestCatcher(trailContainer, droppedObjectContainer, difficulty);
|
catcher = new TestCatcher(trailContainer, droppedObjectContainer, difficulty);
|
||||||
|
|
||||||
Child = new Container
|
Child = new Container
|
||||||
@ -65,13 +66,11 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
JudgementResult result2 = null;
|
JudgementResult result2 = null;
|
||||||
AddStep("catch hyper fruit", () =>
|
AddStep("catch hyper fruit", () =>
|
||||||
{
|
{
|
||||||
drawableObject1 = createDrawableObject(new Fruit { HyperDashTarget = new Fruit { X = 100 } });
|
attemptCatch(new Fruit { HyperDashTarget = new Fruit { X = 100 } }, out drawableObject1, out result1);
|
||||||
result1 = attemptCatch(drawableObject1);
|
|
||||||
});
|
});
|
||||||
AddStep("catch normal fruit", () =>
|
AddStep("catch normal fruit", () =>
|
||||||
{
|
{
|
||||||
drawableObject2 = createDrawableObject(new Fruit());
|
attemptCatch(new Fruit(), out drawableObject2, out result2);
|
||||||
result2 = attemptCatch(drawableObject2);
|
|
||||||
});
|
});
|
||||||
AddStep("revert second result", () =>
|
AddStep("revert second result", () =>
|
||||||
{
|
{
|
||||||
@ -92,8 +91,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
JudgementResult result = null;
|
JudgementResult result = null;
|
||||||
AddStep("catch kiai fruit", () =>
|
AddStep("catch kiai fruit", () =>
|
||||||
{
|
{
|
||||||
drawableObject = createDrawableObject(new TestKiaiFruit());
|
attemptCatch(new TestKiaiFruit(), out drawableObject, out result);
|
||||||
result = attemptCatch(drawableObject);
|
|
||||||
});
|
});
|
||||||
checkState(CatcherAnimationState.Kiai);
|
checkState(CatcherAnimationState.Kiai);
|
||||||
AddStep("revert result", () =>
|
AddStep("revert result", () =>
|
||||||
@ -200,13 +198,22 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
AddAssert("fruits are dropped", () => !catcher.CaughtObjects.Any() && droppedObjectContainer.Count == 10);
|
AddAssert("fruits are dropped", () => !catcher.CaughtObjects.Any() && droppedObjectContainer.Count == 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(true)]
|
[Test]
|
||||||
[TestCase(false)]
|
public void TestHitLightingColour()
|
||||||
public void TestHitLighting(bool enabled)
|
|
||||||
{
|
{
|
||||||
AddStep($"{(enabled ? "enable" : "disable")} hit lighting", () => config.Set(OsuSetting.HitLighting, enabled));
|
var fruitColour = SkinConfiguration.DefaultComboColours[1];
|
||||||
|
AddStep("enable hit lighting", () => config.Set(OsuSetting.HitLighting, true));
|
||||||
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
||||||
AddAssert("check hit lighting", () => catcher.ChildrenOfType<HitExplosion>().Any() == enabled);
|
AddAssert("correct hit lighting colour", () =>
|
||||||
|
catcher.ChildrenOfType<HitExplosion>().First()?.ObjectColour == fruitColour);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHitLightingDisabled()
|
||||||
|
{
|
||||||
|
AddStep("disable hit lighting", () => config.Set(OsuSetting.HitLighting, false));
|
||||||
|
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
||||||
|
AddAssert("no hit lighting", () => !catcher.ChildrenOfType<HitExplosion>().Any());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkPlate(int count) => AddAssert($"{count} objects on the plate", () => catcher.CaughtObjects.Count() == count);
|
private void checkPlate(int count) => AddAssert($"{count} objects on the plate", () => catcher.CaughtObjects.Count() == count);
|
||||||
@ -218,18 +225,34 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
private void attemptCatch(CatchHitObject hitObject, int count = 1)
|
private void attemptCatch(CatchHitObject hitObject, int count = 1)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < count; i++)
|
for (var i = 0; i < count; i++)
|
||||||
attemptCatch(createDrawableObject(hitObject));
|
attemptCatch(hitObject, out _, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
private JudgementResult attemptCatch(DrawableCatchHitObject drawableObject)
|
private void attemptCatch(CatchHitObject hitObject, out DrawableCatchHitObject drawableObject, out JudgementResult result)
|
||||||
{
|
{
|
||||||
drawableObject.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
var result = new CatchJudgementResult(drawableObject.HitObject, drawableObject.HitObject.CreateJudgement())
|
drawableObject = createDrawableObject(hitObject);
|
||||||
|
result = createResult(hitObject);
|
||||||
|
applyResult(drawableObject, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyResult(DrawableCatchHitObject drawableObject, JudgementResult result)
|
||||||
|
{
|
||||||
|
// Load DHO to set colour of hit explosion correctly
|
||||||
|
Add(drawableObject);
|
||||||
|
drawableObject.OnLoadComplete += _ =>
|
||||||
{
|
{
|
||||||
Type = catcher.CanCatch(drawableObject.HitObject) ? HitResult.Great : HitResult.Miss
|
catcher.OnNewResult(drawableObject, result);
|
||||||
|
drawableObject.Expire();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private JudgementResult createResult(CatchHitObject hitObject)
|
||||||
|
{
|
||||||
|
return new CatchJudgementResult(hitObject, hitObject.CreateJudgement())
|
||||||
|
{
|
||||||
|
Type = catcher.CanCatch(hitObject) ? HitResult.Great : HitResult.Miss
|
||||||
};
|
};
|
||||||
catcher.OnNewResult(drawableObject, result);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private DrawableCatchHitObject createDrawableObject(CatchHitObject hitObject)
|
private DrawableCatchHitObject createDrawableObject(CatchHitObject hitObject)
|
||||||
@ -252,9 +275,9 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
public class TestCatcher : Catcher
|
public class TestCatcher : Catcher
|
||||||
{
|
{
|
||||||
public IEnumerable<DrawablePalpableCatchHitObject> CaughtObjects => this.ChildrenOfType<DrawablePalpableCatchHitObject>();
|
public IEnumerable<CaughtObject> CaughtObjects => this.ChildrenOfType<CaughtObject>();
|
||||||
|
|
||||||
public TestCatcher(Container trailsTarget, Container droppedObjectTarget, BeatmapDifficulty difficulty)
|
public TestCatcher(Container trailsTarget, Container<CaughtObject> droppedObjectTarget, BeatmapDifficulty difficulty)
|
||||||
: base(trailsTarget, droppedObjectTarget, difficulty)
|
: base(trailsTarget, droppedObjectTarget, difficulty)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,10 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
SetContents(() =>
|
SetContents(() =>
|
||||||
{
|
{
|
||||||
var droppedObjectContainer = new Container();
|
var droppedObjectContainer = new Container<CaughtObject>
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
};
|
||||||
|
|
||||||
return new CatchInputManager(catchRuleset)
|
return new CatchInputManager(catchRuleset)
|
||||||
{
|
{
|
||||||
@ -99,7 +102,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
private class TestCatcherArea : CatcherArea
|
private class TestCatcherArea : CatcherArea
|
||||||
{
|
{
|
||||||
public TestCatcherArea(Container droppedObjectContainer, BeatmapDifficulty beatmapDifficulty)
|
public TestCatcherArea(Container<CaughtObject> droppedObjectContainer, BeatmapDifficulty beatmapDifficulty)
|
||||||
: base(droppedObjectContainer, beatmapDifficulty)
|
: base(droppedObjectContainer, beatmapDifficulty)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -5,19 +5,20 @@ using NUnit.Framework;
|
|||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
{
|
{
|
||||||
public class TestSceneFruitRandomness : OsuTestScene
|
public class TestSceneFruitRandomness : OsuTestScene
|
||||||
{
|
{
|
||||||
private readonly TestDrawableFruit drawableFruit;
|
private readonly DrawableFruit drawableFruit;
|
||||||
private readonly TestDrawableBanana drawableBanana;
|
private readonly DrawableBanana drawableBanana;
|
||||||
|
|
||||||
public TestSceneFruitRandomness()
|
public TestSceneFruitRandomness()
|
||||||
{
|
{
|
||||||
drawableFruit = new TestDrawableFruit(new Fruit());
|
drawableFruit = new DrawableFruit(new Fruit());
|
||||||
drawableBanana = new TestDrawableBanana(new Banana());
|
drawableBanana = new DrawableBanana(new Banana());
|
||||||
|
|
||||||
Add(new TestDrawableCatchHitObjectSpecimen(drawableFruit) { X = -200 });
|
Add(new TestDrawableCatchHitObjectSpecimen(drawableFruit) { X = -200 });
|
||||||
Add(new TestDrawableCatchHitObjectSpecimen(drawableBanana));
|
Add(new TestDrawableCatchHitObjectSpecimen(drawableBanana));
|
||||||
@ -37,16 +38,16 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
float fruitRotation = 0;
|
float fruitRotation = 0;
|
||||||
float bananaRotation = 0;
|
float bananaRotation = 0;
|
||||||
float bananaScale = 0;
|
Vector2 bananaSize = new Vector2();
|
||||||
Color4 bananaColour = new Color4();
|
Color4 bananaColour = new Color4();
|
||||||
|
|
||||||
AddStep("Initialize start time", () =>
|
AddStep("Initialize start time", () =>
|
||||||
{
|
{
|
||||||
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
|
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
|
||||||
|
|
||||||
fruitRotation = drawableFruit.InnerRotation;
|
fruitRotation = drawableFruit.DisplayRotation;
|
||||||
bananaRotation = drawableBanana.InnerRotation;
|
bananaRotation = drawableBanana.DisplayRotation;
|
||||||
bananaScale = drawableBanana.InnerScale;
|
bananaSize = drawableBanana.DisplaySize;
|
||||||
bananaColour = drawableBanana.AccentColour.Value;
|
bananaColour = drawableBanana.AccentColour.Value;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -55,9 +56,9 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = another_start_time;
|
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = another_start_time;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("fruit rotation is changed", () => drawableFruit.InnerRotation != fruitRotation);
|
AddAssert("fruit rotation is changed", () => drawableFruit.DisplayRotation != fruitRotation);
|
||||||
AddAssert("banana rotation is changed", () => drawableBanana.InnerRotation != bananaRotation);
|
AddAssert("banana rotation is changed", () => drawableBanana.DisplayRotation != bananaRotation);
|
||||||
AddAssert("banana scale is changed", () => drawableBanana.InnerScale != bananaScale);
|
AddAssert("banana size is changed", () => drawableBanana.DisplaySize != bananaSize);
|
||||||
AddAssert("banana colour is changed", () => drawableBanana.AccentColour.Value != bananaColour);
|
AddAssert("banana colour is changed", () => drawableBanana.AccentColour.Value != bananaColour);
|
||||||
|
|
||||||
AddStep("reset start time", () =>
|
AddStep("reset start time", () =>
|
||||||
@ -65,32 +66,11 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
|
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("rotation and scale restored", () =>
|
AddAssert("rotation and size restored", () =>
|
||||||
drawableFruit.InnerRotation == fruitRotation &&
|
drawableFruit.DisplayRotation == fruitRotation &&
|
||||||
drawableBanana.InnerRotation == bananaRotation &&
|
drawableBanana.DisplayRotation == bananaRotation &&
|
||||||
drawableBanana.InnerScale == bananaScale &&
|
drawableBanana.DisplaySize == bananaSize &&
|
||||||
drawableBanana.AccentColour.Value == bananaColour);
|
drawableBanana.AccentColour.Value == bananaColour);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestDrawableFruit : DrawableFruit
|
|
||||||
{
|
|
||||||
public float InnerRotation => ScaleContainer.Rotation;
|
|
||||||
|
|
||||||
public TestDrawableFruit(Fruit h)
|
|
||||||
: base(h)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestDrawableBanana : DrawableBanana
|
|
||||||
{
|
|
||||||
public float InnerRotation => ScaleContainer.Rotation;
|
|
||||||
public float InnerScale => ScaleContainer.Scale.X;
|
|
||||||
|
|
||||||
public TestDrawableBanana(Banana h)
|
|
||||||
: base(h)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
AddStep("create hyper-dashing catcher", () =>
|
AddStep("create hyper-dashing catcher", () =>
|
||||||
{
|
{
|
||||||
Child = setupSkinHierarchy(catcherArea = new CatcherArea(new Container())
|
Child = setupSkinHierarchy(catcherArea = new CatcherArea(new Container<CaughtObject>())
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
18
osu.Game.Rulesets.Catch/Objects/Drawables/CaughtBanana.cs
Normal file
18
osu.Game.Rulesets.Catch/Objects/Drawables/CaughtBanana.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Catch.Skinning.Default;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a <see cref="Banana"/> caught by the catcher.
|
||||||
|
/// </summary>
|
||||||
|
public class CaughtBanana : CaughtObject
|
||||||
|
{
|
||||||
|
public CaughtBanana()
|
||||||
|
: base(CatchSkinComponents.Banana, _ => new BananaPiece())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
osu.Game.Rulesets.Catch/Objects/Drawables/CaughtDroplet.cs
Normal file
20
osu.Game.Rulesets.Catch/Objects/Drawables/CaughtDroplet.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Catch.Skinning.Default;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a <see cref="Droplet"/> caught by the catcher.
|
||||||
|
/// </summary>
|
||||||
|
public class CaughtDroplet : CaughtObject
|
||||||
|
{
|
||||||
|
public override bool StaysOnPlate => false;
|
||||||
|
|
||||||
|
public CaughtDroplet()
|
||||||
|
: base(CatchSkinComponents.Droplet, _ => new DropletPiece())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
osu.Game.Rulesets.Catch/Objects/Drawables/CaughtFruit.cs
Normal file
29
osu.Game.Rulesets.Catch/Objects/Drawables/CaughtFruit.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Game.Rulesets.Catch.Skinning.Default;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a <see cref="Fruit"/> caught by the catcher.
|
||||||
|
/// </summary>
|
||||||
|
public class CaughtFruit : CaughtObject, IHasFruitState
|
||||||
|
{
|
||||||
|
public Bindable<FruitVisualRepresentation> VisualRepresentation { get; } = new Bindable<FruitVisualRepresentation>();
|
||||||
|
|
||||||
|
public CaughtFruit()
|
||||||
|
: base(CatchSkinComponents.Fruit, _ => new FruitPiece())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void CopyStateFrom(IHasCatchObjectState objectState)
|
||||||
|
{
|
||||||
|
base.CopyStateFrom(objectState);
|
||||||
|
|
||||||
|
var fruitState = (IHasFruitState)objectState;
|
||||||
|
VisualRepresentation.Value = fruitState.VisualRepresentation.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
64
osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs
Normal file
64
osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.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 System;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a <see cref="PalpableCatchHitObject"/> caught by the catcher.
|
||||||
|
/// </summary>
|
||||||
|
[Cached(typeof(IHasCatchObjectState))]
|
||||||
|
public abstract class CaughtObject : SkinnableDrawable, IHasCatchObjectState
|
||||||
|
{
|
||||||
|
public PalpableCatchHitObject HitObject { get; private set; }
|
||||||
|
public Bindable<Color4> AccentColour { get; } = new Bindable<Color4>();
|
||||||
|
public Bindable<bool> HyperDash { get; } = new Bindable<bool>();
|
||||||
|
|
||||||
|
public Vector2 DisplaySize => Size * Scale;
|
||||||
|
|
||||||
|
public float DisplayRotation => Rotation;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this hit object should stay on the catcher plate when the object is caught by the catcher.
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool StaysOnPlate => true;
|
||||||
|
|
||||||
|
public override bool RemoveWhenNotAlive => true;
|
||||||
|
|
||||||
|
protected CaughtObject(CatchSkinComponents skinComponent, Func<ISkinComponent, Drawable> defaultImplementation)
|
||||||
|
: base(new CatchSkinComponent(skinComponent), defaultImplementation)
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.None;
|
||||||
|
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies the hit object visual state from another <see cref="IHasCatchObjectState"/> object.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void CopyStateFrom(IHasCatchObjectState objectState)
|
||||||
|
{
|
||||||
|
HitObject = objectState.HitObject;
|
||||||
|
Scale = Vector2.Divide(objectState.DisplaySize, Size);
|
||||||
|
Rotation = objectState.DisplayRotation;
|
||||||
|
AccentColour.Value = objectState.AccentColour.Value;
|
||||||
|
HyperDash.Value = objectState.HyperDash.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void FreeAfterUse()
|
||||||
|
{
|
||||||
|
ClearTransforms();
|
||||||
|
Alpha = 1;
|
||||||
|
|
||||||
|
base.FreeAfterUse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
ScaleContainer.Child = new SkinnableDrawable(
|
ScalingContainer.Child = new SkinnableDrawable(
|
||||||
new CatchSkinComponent(CatchSkinComponents.Banana),
|
new CatchSkinComponent(CatchSkinComponents.Banana),
|
||||||
_ => new BananaPiece());
|
_ => new BananaPiece());
|
||||||
}
|
}
|
||||||
@ -44,12 +44,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
const float end_scale = 0.6f;
|
const float end_scale = 0.6f;
|
||||||
const float random_scale_range = 1.6f;
|
const float random_scale_range = 1.6f;
|
||||||
|
|
||||||
ScaleContainer.ScaleTo(HitObject.Scale * (end_scale + random_scale_range * RandomSingle(3)))
|
ScalingContainer.ScaleTo(HitObject.Scale * (end_scale + random_scale_range * RandomSingle(3)))
|
||||||
.Then().ScaleTo(HitObject.Scale * end_scale, HitObject.TimePreempt);
|
.Then().ScaleTo(HitObject.Scale * end_scale, HitObject.TimePreempt);
|
||||||
|
|
||||||
ScaleContainer.RotateTo(getRandomAngle(1))
|
ScalingContainer.RotateTo(getRandomAngle(1))
|
||||||
.Then()
|
.Then()
|
||||||
.RotateTo(getRandomAngle(2), HitObject.TimePreempt);
|
.RotateTo(getRandomAngle(2), HitObject.TimePreempt);
|
||||||
|
|
||||||
float getRandomAngle(int series) => 180 * (RandomSingle(series) * 2 - 1);
|
float getRandomAngle(int series) => 180 * (RandomSingle(series) * 2 - 1);
|
||||||
}
|
}
|
||||||
|
@ -50,10 +50,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
|
|
||||||
public Func<CatchHitObject, bool> CheckPosition;
|
public Func<CatchHitObject, bool> CheckPosition;
|
||||||
|
|
||||||
public bool IsOnPlate;
|
|
||||||
|
|
||||||
public override bool RemoveWhenNotAlive => IsOnPlate;
|
|
||||||
|
|
||||||
protected override JudgementResult CreateResult(Judgement judgement) => new CatchJudgementResult(HitObject, judgement);
|
protected override JudgementResult CreateResult(Judgement judgement) => new CatchJudgementResult(HitObject, judgement);
|
||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
|
@ -11,8 +11,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
{
|
{
|
||||||
public class DrawableDroplet : DrawablePalpableCatchHitObject
|
public class DrawableDroplet : DrawablePalpableCatchHitObject
|
||||||
{
|
{
|
||||||
public override bool StaysOnPlate => false;
|
|
||||||
|
|
||||||
public DrawableDroplet()
|
public DrawableDroplet()
|
||||||
: this(null)
|
: this(null)
|
||||||
{
|
{
|
||||||
@ -26,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
ScaleContainer.Child = new SkinnableDrawable(
|
ScalingContainer.Child = new SkinnableDrawable(
|
||||||
new CatchSkinComponent(CatchSkinComponents.Droplet),
|
new CatchSkinComponent(CatchSkinComponents.Droplet),
|
||||||
_ => new DropletPiece());
|
_ => new DropletPiece());
|
||||||
}
|
}
|
||||||
@ -39,7 +37,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
float startRotation = RandomSingle(1) * 20;
|
float startRotation = RandomSingle(1) * 20;
|
||||||
double duration = HitObject.TimePreempt + 2000;
|
double duration = HitObject.TimePreempt + 2000;
|
||||||
|
|
||||||
ScaleContainer.RotateTo(startRotation).RotateTo(startRotation + 720, duration);
|
ScalingContainer.RotateTo(startRotation).RotateTo(startRotation + 720, duration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,9 @@ using osu.Game.Skinning;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableFruit : DrawablePalpableCatchHitObject
|
public class DrawableFruit : DrawablePalpableCatchHitObject, IHasFruitState
|
||||||
{
|
{
|
||||||
public readonly Bindable<FruitVisualRepresentation> VisualRepresentation = new Bindable<FruitVisualRepresentation>();
|
public Bindable<FruitVisualRepresentation> VisualRepresentation { get; } = new Bindable<FruitVisualRepresentation>();
|
||||||
|
|
||||||
public DrawableFruit()
|
public DrawableFruit()
|
||||||
: this(null)
|
: this(null)
|
||||||
@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
VisualRepresentation.Value = (FruitVisualRepresentation)(change.NewValue % 4);
|
VisualRepresentation.Value = (FruitVisualRepresentation)(change.NewValue % 4);
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
ScaleContainer.Child = new SkinnableDrawable(
|
ScalingContainer.Child = new SkinnableDrawable(
|
||||||
new CatchSkinComponent(CatchSkinComponents.Fruit),
|
new CatchSkinComponent(CatchSkinComponents.Fruit),
|
||||||
_ => new FruitPiece());
|
_ => new FruitPiece());
|
||||||
}
|
}
|
||||||
@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
{
|
{
|
||||||
base.UpdateInitialTransforms();
|
base.UpdateInitialTransforms();
|
||||||
|
|
||||||
ScaleContainer.RotateTo((RandomSingle(1) - 0.5f) * 40);
|
ScalingContainer.RotateTo((RandomSingle(1) - 0.5f) * 40);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,32 +7,36 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
{
|
{
|
||||||
public abstract class DrawablePalpableCatchHitObject : DrawableCatchHitObject
|
[Cached(typeof(IHasCatchObjectState))]
|
||||||
|
public abstract class DrawablePalpableCatchHitObject : DrawableCatchHitObject, IHasCatchObjectState
|
||||||
{
|
{
|
||||||
public new PalpableCatchHitObject HitObject => (PalpableCatchHitObject)base.HitObject;
|
public new PalpableCatchHitObject HitObject => (PalpableCatchHitObject)base.HitObject;
|
||||||
|
|
||||||
public readonly Bindable<bool> HyperDash = new Bindable<bool>();
|
Bindable<Color4> IHasCatchObjectState.AccentColour => AccentColour;
|
||||||
|
|
||||||
public readonly Bindable<float> ScaleBindable = new Bindable<float>(1);
|
public Bindable<bool> HyperDash { get; } = new Bindable<bool>();
|
||||||
|
|
||||||
public readonly Bindable<int> IndexInBeatmap = new Bindable<int>();
|
public Bindable<float> ScaleBindable { get; } = new Bindable<float>(1);
|
||||||
|
|
||||||
|
public Bindable<int> IndexInBeatmap { get; } = new Bindable<int>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The multiplicative factor applied to <see cref="ScaleContainer"/> scale relative to <see cref="HitObject"/> scale.
|
/// The multiplicative factor applied to <see cref="Drawable.Scale"/> relative to <see cref="HitObject"/> scale.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual float ScaleFactor => 1;
|
protected virtual float ScaleFactor => 1;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this hit object should stay on the catcher plate when the object is caught by the catcher.
|
/// The container internal transforms (such as scaling based on the circle size) are applied to.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual bool StaysOnPlate => true;
|
protected readonly Container ScalingContainer;
|
||||||
|
|
||||||
public float DisplayRadius => CatchHitObject.OBJECT_RADIUS * HitObject.Scale * ScaleFactor;
|
public Vector2 DisplaySize => ScalingContainer.Size * ScalingContainer.Scale;
|
||||||
|
|
||||||
protected readonly Container ScaleContainer;
|
public float DisplayRotation => ScalingContainer.Rotation;
|
||||||
|
|
||||||
protected DrawablePalpableCatchHitObject([CanBeNull] CatchHitObject h)
|
protected DrawablePalpableCatchHitObject([CanBeNull] CatchHitObject h)
|
||||||
: base(h)
|
: base(h)
|
||||||
@ -40,11 +44,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
|
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
|
||||||
|
|
||||||
AddInternal(ScaleContainer = new Container
|
AddInternal(ScalingContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,12 +57,13 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
{
|
{
|
||||||
XBindable.BindValueChanged(x =>
|
XBindable.BindValueChanged(x =>
|
||||||
{
|
{
|
||||||
if (!IsOnPlate) X = x.NewValue;
|
X = x.NewValue;
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
ScaleBindable.BindValueChanged(scale =>
|
ScaleBindable.BindValueChanged(scale =>
|
||||||
{
|
{
|
||||||
ScaleContainer.Scale = new Vector2(scale.NewValue * ScaleFactor);
|
ScalingContainer.Scale = new Vector2(scale.NewValue * ScaleFactor);
|
||||||
|
Size = DisplaySize;
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
IndexInBeatmap.BindValueChanged(_ => UpdateComboColour());
|
IndexInBeatmap.BindValueChanged(_ => UpdateComboColour());
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a visual state of a <see cref="PalpableCatchHitObject"/>.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHasCatchObjectState
|
||||||
|
{
|
||||||
|
PalpableCatchHitObject HitObject { get; }
|
||||||
|
|
||||||
|
Bindable<Color4> AccentColour { get; }
|
||||||
|
|
||||||
|
Bindable<bool> HyperDash { get; }
|
||||||
|
|
||||||
|
Vector2 DisplaySize { get; }
|
||||||
|
|
||||||
|
float DisplayRotation { get; }
|
||||||
|
}
|
||||||
|
}
|
15
osu.Game.Rulesets.Catch/Objects/Drawables/IHasFruitState.cs
Normal file
15
osu.Game.Rulesets.Catch/Objects/Drawables/IHasFruitState.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a visual state of a <see cref="Fruit"/>.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHasFruitState : IHasCatchObjectState
|
||||||
|
{
|
||||||
|
Bindable<FruitVisualRepresentation> VisualRepresentation { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Skinning.Default
|
namespace osu.Game.Rulesets.Catch.Skinning.Default
|
||||||
@ -17,9 +16,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
|
|||||||
public readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
public readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
||||||
public readonly Bindable<bool> HyperDash = new Bindable<bool>();
|
public readonly Bindable<bool> HyperDash = new Bindable<bool>();
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved]
|
||||||
[CanBeNull]
|
protected IHasCatchObjectState ObjectState { get; private set; }
|
||||||
protected DrawableHitObject DrawableHitObject { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A part of this piece that will be faded out while falling in the playfield.
|
/// A part of this piece that will be faded out while falling in the playfield.
|
||||||
@ -37,13 +35,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
var hitObject = (DrawablePalpableCatchHitObject)DrawableHitObject;
|
AccentColour.BindTo(ObjectState.AccentColour);
|
||||||
|
HyperDash.BindTo(ObjectState.HyperDash);
|
||||||
if (hitObject != null)
|
|
||||||
{
|
|
||||||
AccentColour.BindTo(hitObject.AccentColour);
|
|
||||||
HyperDash.BindTo(hitObject.HyperDash);
|
|
||||||
}
|
|
||||||
|
|
||||||
HyperDash.BindValueChanged(hyper =>
|
HyperDash.BindValueChanged(hyper =>
|
||||||
{
|
{
|
||||||
@ -54,8 +47,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
|
|||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
if (BorderPiece != null && DrawableHitObject?.HitObject != null)
|
if (BorderPiece != null)
|
||||||
BorderPiece.Alpha = (float)Math.Clamp((DrawableHitObject.HitObject.StartTime - Time.Current) / 500, 0, 1);
|
BorderPiece.Alpha = (float)Math.Clamp((ObjectState.HitObject.StartTime - Time.Current) / 500, 0, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,10 +39,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
var fruit = (DrawableFruit)DrawableHitObject;
|
var fruitState = (IHasFruitState)ObjectState;
|
||||||
|
VisualRepresentation.BindTo(fruitState.VisualRepresentation);
|
||||||
if (fruit != null)
|
|
||||||
VisualRepresentation.BindTo(fruit.VisualRepresentation);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,10 +14,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
var fruit = (DrawableFruit)DrawableHitObject;
|
var fruitState = (IHasFruitState)ObjectState;
|
||||||
|
VisualRepresentation.BindTo(fruitState.VisualRepresentation);
|
||||||
if (fruit != null)
|
|
||||||
VisualRepresentation.BindTo(fruit.VisualRepresentation);
|
|
||||||
|
|
||||||
VisualRepresentation.BindValueChanged(visual => setTexture(visual.NewValue), true);
|
VisualRepresentation.BindValueChanged(visual => setTexture(visual.NewValue), true);
|
||||||
}
|
}
|
||||||
|
@ -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 JetBrains.Annotations;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -10,7 +9,6 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -29,9 +27,8 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
protected ISkinSource Skin { get; private set; }
|
protected ISkinSource Skin { get; private set; }
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved]
|
||||||
[CanBeNull]
|
protected IHasCatchObjectState ObjectState { get; private set; }
|
||||||
protected DrawableHitObject DrawableHitObject { get; private set; }
|
|
||||||
|
|
||||||
protected LegacyCatchHitObjectPiece()
|
protected LegacyCatchHitObjectPiece()
|
||||||
{
|
{
|
||||||
@ -65,13 +62,8 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
var hitObject = (DrawablePalpableCatchHitObject)DrawableHitObject;
|
AccentColour.BindTo(ObjectState.AccentColour);
|
||||||
|
HyperDash.BindTo(ObjectState.HyperDash);
|
||||||
if (hitObject != null)
|
|
||||||
{
|
|
||||||
AccentColour.BindTo(hitObject.AccentColour);
|
|
||||||
HyperDash.BindTo(hitObject.HyperDash);
|
|
||||||
}
|
|
||||||
|
|
||||||
hyperSprite.Colour = Skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashFruit)?.Value ??
|
hyperSprite.Colour = Skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashFruit)?.Value ??
|
||||||
Skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value ??
|
Skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value ??
|
||||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation)
|
public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation)
|
||||||
{
|
{
|
||||||
var droppedObjectContainer = new Container
|
var droppedObjectContainer = new Container<CaughtObject>
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
};
|
};
|
||||||
|
@ -53,9 +53,15 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
private CatcherTrailDisplay trails;
|
private CatcherTrailDisplay trails;
|
||||||
|
|
||||||
private readonly Container droppedObjectTarget;
|
/// <summary>
|
||||||
|
/// Contains caught objects on the plate.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Container<CaughtObject> caughtObjectContainer;
|
||||||
|
|
||||||
private readonly Container<DrawablePalpableCatchHitObject> caughtFruitContainer;
|
/// <summary>
|
||||||
|
/// Contains objects dropped from the plate.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Container<CaughtObject> droppedObjectTarget;
|
||||||
|
|
||||||
public CatcherAnimationState CurrentState { get; private set; }
|
public CatcherAnimationState CurrentState { get; private set; }
|
||||||
|
|
||||||
@ -108,7 +114,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
private readonly DrawablePool<HitExplosion> hitExplosionPool;
|
private readonly DrawablePool<HitExplosion> hitExplosionPool;
|
||||||
private readonly Container<HitExplosion> hitExplosionContainer;
|
private readonly Container<HitExplosion> hitExplosionContainer;
|
||||||
|
|
||||||
public Catcher([NotNull] Container trailsTarget, [NotNull] Container droppedObjectTarget, BeatmapDifficulty difficulty = null)
|
private readonly DrawablePool<CaughtFruit> caughtFruitPool;
|
||||||
|
private readonly DrawablePool<CaughtBanana> caughtBananaPool;
|
||||||
|
private readonly DrawablePool<CaughtDroplet> caughtDropletPool;
|
||||||
|
|
||||||
|
public Catcher([NotNull] Container trailsTarget, [NotNull] Container<CaughtObject> droppedObjectTarget, BeatmapDifficulty difficulty = null)
|
||||||
{
|
{
|
||||||
this.trailsTarget = trailsTarget;
|
this.trailsTarget = trailsTarget;
|
||||||
this.droppedObjectTarget = droppedObjectTarget;
|
this.droppedObjectTarget = droppedObjectTarget;
|
||||||
@ -124,7 +134,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
hitExplosionPool = new DrawablePool<HitExplosion>(10),
|
hitExplosionPool = new DrawablePool<HitExplosion>(10),
|
||||||
caughtFruitContainer = new Container<DrawablePalpableCatchHitObject>
|
caughtFruitPool = new DrawablePool<CaughtFruit>(50),
|
||||||
|
caughtBananaPool = new DrawablePool<CaughtBanana>(100),
|
||||||
|
// less capacity is needed compared to fruit because droplet is not stacked
|
||||||
|
caughtDropletPool = new DrawablePool<CaughtDroplet>(25),
|
||||||
|
caughtObjectContainer = new Container<CaughtObject>
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.BottomCentre,
|
Origin = Anchor.BottomCentre,
|
||||||
@ -172,7 +186,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates proxied content to be displayed beneath hitobjects.
|
/// Creates proxied content to be displayed beneath hitobjects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Drawable CreateProxiedContent() => caughtFruitContainer.CreateProxy();
|
public Drawable CreateProxiedContent() => caughtObjectContainer.CreateProxy();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the scale of the catcher based off the provided beatmap difficulty.
|
/// Calculates the scale of the catcher based off the provided beatmap difficulty.
|
||||||
@ -215,10 +229,19 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
catchResult.CatcherAnimationState = CurrentState;
|
catchResult.CatcherAnimationState = CurrentState;
|
||||||
catchResult.CatcherHyperDash = HyperDashing;
|
catchResult.CatcherHyperDash = HyperDashing;
|
||||||
|
|
||||||
if (!(drawableObject.HitObject is PalpableCatchHitObject hitObject)) return;
|
if (!(drawableObject is DrawablePalpableCatchHitObject palpableObject)) return;
|
||||||
|
|
||||||
|
var hitObject = palpableObject.HitObject;
|
||||||
|
|
||||||
if (result.IsHit)
|
if (result.IsHit)
|
||||||
placeCaughtObject(hitObject);
|
{
|
||||||
|
var positionInStack = computePositionInStack(new Vector2(palpableObject.X - X, 0), palpableObject.DisplaySize.X / 2);
|
||||||
|
|
||||||
|
placeCaughtObject(palpableObject, positionInStack);
|
||||||
|
|
||||||
|
if (hitLighting.Value)
|
||||||
|
addLighting(hitObject, positionInStack.X, drawableObject.AccentColour.Value);
|
||||||
|
}
|
||||||
|
|
||||||
// droplet doesn't affect the catcher state
|
// droplet doesn't affect the catcher state
|
||||||
if (hitObject is TinyDroplet) return;
|
if (hitObject is TinyDroplet) return;
|
||||||
@ -256,8 +279,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
SetHyperDashState();
|
SetHyperDashState();
|
||||||
}
|
}
|
||||||
|
|
||||||
caughtFruitContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject);
|
caughtObjectContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject);
|
||||||
droppedObjectTarget.RemoveAll(d => (d as DrawableCatchHitObject)?.HitObject == drawableObject.HitObject);
|
droppedObjectTarget.RemoveAll(d => d.HitObject == drawableObject.HitObject);
|
||||||
hitExplosionContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject);
|
hitExplosionContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -441,138 +464,121 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
updateCatcher();
|
updateCatcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void placeCaughtObject(PalpableCatchHitObject source)
|
private void placeCaughtObject(DrawablePalpableCatchHitObject drawableObject, Vector2 position)
|
||||||
{
|
{
|
||||||
var caughtObject = createCaughtObject(source);
|
var caughtObject = getCaughtObject(drawableObject.HitObject);
|
||||||
|
|
||||||
if (caughtObject == null) return;
|
if (caughtObject == null) return;
|
||||||
|
|
||||||
caughtObject.RelativePositionAxes = Axes.None;
|
caughtObject.CopyStateFrom(drawableObject);
|
||||||
caughtObject.X = source.X - X;
|
|
||||||
caughtObject.IsOnPlate = true;
|
|
||||||
|
|
||||||
caughtObject.Anchor = Anchor.TopCentre;
|
caughtObject.Anchor = Anchor.TopCentre;
|
||||||
caughtObject.Origin = Anchor.Centre;
|
caughtObject.Position = position;
|
||||||
caughtObject.Scale *= 0.5f;
|
caughtObject.Scale /= 2;
|
||||||
caughtObject.LifetimeStart = source.StartTime;
|
|
||||||
caughtObject.LifetimeEnd = double.MaxValue;
|
|
||||||
|
|
||||||
adjustPositionInStack(caughtObject);
|
caughtObjectContainer.Add(caughtObject);
|
||||||
|
|
||||||
caughtFruitContainer.Add(caughtObject);
|
|
||||||
|
|
||||||
addLighting(caughtObject);
|
|
||||||
|
|
||||||
if (!caughtObject.StaysOnPlate)
|
if (!caughtObject.StaysOnPlate)
|
||||||
removeFromPlate(caughtObject, DroppedObjectAnimation.Explode);
|
removeFromPlate(caughtObject, DroppedObjectAnimation.Explode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void adjustPositionInStack(DrawablePalpableCatchHitObject caughtObject)
|
private Vector2 computePositionInStack(Vector2 position, float displayRadius)
|
||||||
{
|
{
|
||||||
const float radius_div_2 = CatchHitObject.OBJECT_RADIUS / 2;
|
const float radius_div_2 = CatchHitObject.OBJECT_RADIUS / 2;
|
||||||
const float allowance = 10;
|
const float allowance = 10;
|
||||||
|
|
||||||
float caughtObjectRadius = caughtObject.DisplayRadius;
|
while (caughtObjectContainer.Any(f => Vector2Extensions.Distance(f.Position, position) < (displayRadius + radius_div_2) / (allowance / 2)))
|
||||||
|
|
||||||
while (caughtFruitContainer.Any(f => Vector2Extensions.Distance(f.Position, caughtObject.Position) < (caughtObjectRadius + radius_div_2) / (allowance / 2)))
|
|
||||||
{
|
{
|
||||||
float diff = (caughtObjectRadius + radius_div_2) / allowance;
|
float diff = (displayRadius + radius_div_2) / allowance;
|
||||||
|
|
||||||
caughtObject.X += (RNG.NextSingle() - 0.5f) * diff * 2;
|
position.X += (RNG.NextSingle() - 0.5f) * diff * 2;
|
||||||
caughtObject.Y -= RNG.NextSingle() * diff;
|
position.Y -= RNG.NextSingle() * diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
caughtObject.X = Math.Clamp(caughtObject.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2);
|
position.X = Math.Clamp(position.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2);
|
||||||
|
|
||||||
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addLighting(DrawablePalpableCatchHitObject caughtObject)
|
private void addLighting(CatchHitObject hitObject, float x, Color4 colour)
|
||||||
{
|
{
|
||||||
if (!hitLighting.Value) return;
|
|
||||||
|
|
||||||
HitExplosion hitExplosion = hitExplosionPool.Get();
|
HitExplosion hitExplosion = hitExplosionPool.Get();
|
||||||
hitExplosion.HitObject = caughtObject.HitObject;
|
hitExplosion.HitObject = hitObject;
|
||||||
hitExplosion.X = caughtObject.X;
|
hitExplosion.X = x;
|
||||||
hitExplosion.Scale = new Vector2(caughtObject.HitObject.Scale);
|
hitExplosion.Scale = new Vector2(hitObject.Scale);
|
||||||
hitExplosion.ObjectColour = caughtObject.AccentColour.Value;
|
hitExplosion.ObjectColour = colour;
|
||||||
hitExplosionContainer.Add(hitExplosion);
|
hitExplosionContainer.Add(hitExplosion);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DrawablePalpableCatchHitObject createCaughtObject(PalpableCatchHitObject source)
|
private CaughtObject getCaughtObject(PalpableCatchHitObject source)
|
||||||
{
|
{
|
||||||
switch (source)
|
switch (source)
|
||||||
{
|
{
|
||||||
case Banana banana:
|
case Fruit _:
|
||||||
return new DrawableBanana(banana);
|
return caughtFruitPool.Get();
|
||||||
|
|
||||||
case Fruit fruit:
|
case Banana _:
|
||||||
return new DrawableFruit(fruit);
|
return caughtBananaPool.Get();
|
||||||
|
|
||||||
case TinyDroplet tiny:
|
case Droplet _:
|
||||||
return new DrawableTinyDroplet(tiny);
|
return caughtDropletPool.Get();
|
||||||
|
|
||||||
case Droplet droplet:
|
|
||||||
return new DrawableDroplet(droplet);
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CaughtObject getDroppedObject(CaughtObject caughtObject)
|
||||||
|
{
|
||||||
|
var droppedObject = getCaughtObject(caughtObject.HitObject);
|
||||||
|
|
||||||
|
droppedObject.CopyStateFrom(caughtObject);
|
||||||
|
droppedObject.Anchor = Anchor.TopLeft;
|
||||||
|
droppedObject.Position = caughtObjectContainer.ToSpaceOfOtherDrawable(caughtObject.DrawPosition, droppedObjectTarget);
|
||||||
|
|
||||||
|
return droppedObject;
|
||||||
|
}
|
||||||
|
|
||||||
private void clearPlate(DroppedObjectAnimation animation)
|
private void clearPlate(DroppedObjectAnimation animation)
|
||||||
{
|
{
|
||||||
var caughtObjects = caughtFruitContainer.Children.ToArray();
|
var droppedObjects = caughtObjectContainer.Children.Select(getDroppedObject).ToArray();
|
||||||
caughtFruitContainer.Clear(false);
|
|
||||||
|
|
||||||
droppedObjectTarget.AddRange(caughtObjects);
|
caughtObjectContainer.Clear(false);
|
||||||
|
|
||||||
foreach (var caughtObject in caughtObjects)
|
droppedObjectTarget.AddRange(droppedObjects);
|
||||||
drop(caughtObject, animation);
|
|
||||||
|
foreach (var droppedObject in droppedObjects)
|
||||||
|
applyDropAnimation(droppedObject, animation);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeFromPlate(DrawablePalpableCatchHitObject caughtObject, DroppedObjectAnimation animation)
|
private void removeFromPlate(CaughtObject caughtObject, DroppedObjectAnimation animation)
|
||||||
{
|
{
|
||||||
if (!caughtFruitContainer.Remove(caughtObject))
|
var droppedObject = getDroppedObject(caughtObject);
|
||||||
throw new InvalidOperationException("Can only drop a caught object on the plate");
|
|
||||||
|
|
||||||
droppedObjectTarget.Add(caughtObject);
|
caughtObjectContainer.Remove(caughtObject);
|
||||||
|
|
||||||
drop(caughtObject, animation);
|
droppedObjectTarget.Add(droppedObject);
|
||||||
|
|
||||||
|
applyDropAnimation(droppedObject, animation);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drop(DrawablePalpableCatchHitObject d, DroppedObjectAnimation animation)
|
private void applyDropAnimation(Drawable d, DroppedObjectAnimation animation)
|
||||||
{
|
{
|
||||||
var originalX = d.X * Scale.X;
|
switch (animation)
|
||||||
var startTime = Clock.CurrentTime;
|
|
||||||
|
|
||||||
d.Anchor = Anchor.TopLeft;
|
|
||||||
d.Position = caughtFruitContainer.ToSpaceOfOtherDrawable(d.DrawPosition, droppedObjectTarget);
|
|
||||||
|
|
||||||
// we cannot just apply the transforms because DHO clears transforms when state is updated
|
|
||||||
d.ApplyCustomUpdateState += (o, state) => animate(o, animation, originalX, startTime);
|
|
||||||
if (d.IsLoaded)
|
|
||||||
animate(d, animation, originalX, startTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void animate(Drawable d, DroppedObjectAnimation animation, float originalX, double startTime)
|
|
||||||
{
|
|
||||||
using (d.BeginAbsoluteSequence(startTime))
|
|
||||||
{
|
{
|
||||||
switch (animation)
|
case DroppedObjectAnimation.Drop:
|
||||||
{
|
d.MoveToY(d.Y + 75, 750, Easing.InSine);
|
||||||
case DroppedObjectAnimation.Drop:
|
d.FadeOut(750);
|
||||||
d.MoveToY(d.Y + 75, 750, Easing.InSine);
|
break;
|
||||||
d.FadeOut(750);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DroppedObjectAnimation.Explode:
|
case DroppedObjectAnimation.Explode:
|
||||||
d.MoveToY(d.Y - 50, 250, Easing.OutSine).Then().MoveToY(d.Y + 50, 500, Easing.InSine);
|
var originalX = droppedObjectTarget.ToSpaceOfOtherDrawable(d.DrawPosition, caughtObjectContainer).X * Scale.X;
|
||||||
d.MoveToX(d.X + originalX * 6, 1000);
|
d.MoveToY(d.Y - 50, 250, Easing.OutSine).Then().MoveToY(d.Y + 50, 500, Easing.InSine);
|
||||||
d.FadeOut(750);
|
d.MoveToX(d.X + originalX * 6, 1000);
|
||||||
break;
|
d.FadeOut(750);
|
||||||
}
|
break;
|
||||||
|
|
||||||
d.Expire();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d.Expire();
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum DroppedObjectAnimation
|
private enum DroppedObjectAnimation
|
||||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
public readonly Catcher MovableCatcher;
|
public readonly Catcher MovableCatcher;
|
||||||
private readonly CatchComboDisplay comboDisplay;
|
private readonly CatchComboDisplay comboDisplay;
|
||||||
|
|
||||||
public CatcherArea(Container droppedObjectContainer, BeatmapDifficulty difficulty = null)
|
public CatcherArea(Container<CaughtObject> droppedObjectContainer, BeatmapDifficulty difficulty = null)
|
||||||
{
|
{
|
||||||
Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE);
|
Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE);
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
|
@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
{
|
{
|
||||||
public abstract class DrawableTaikoRulesetTestScene : OsuTestScene
|
public abstract class DrawableTaikoRulesetTestScene : OsuTestScene
|
||||||
{
|
{
|
||||||
|
protected const int DEFAULT_PLAYFIELD_CONTAINER_HEIGHT = 768;
|
||||||
|
|
||||||
protected DrawableTaikoRuleset DrawableRuleset { get; private set; }
|
protected DrawableTaikoRuleset DrawableRuleset { get; private set; }
|
||||||
protected Container PlayfieldContainer { get; private set; }
|
protected Container PlayfieldContainer { get; private set; }
|
||||||
|
|
||||||
@ -44,10 +46,10 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
|
|
||||||
Add(PlayfieldContainer = new Container
|
Add(PlayfieldContainer = new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.TopCentre,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = 768,
|
Height = DEFAULT_PLAYFIELD_CONTAINER_HEIGHT,
|
||||||
Children = new[] { DrawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(new TaikoRuleset().RulesetInfo)) }
|
Children = new[] { DrawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(new TaikoRuleset().RulesetInfo)) }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -13,12 +13,15 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
{
|
{
|
||||||
public readonly HitResult Type;
|
public readonly HitResult Type;
|
||||||
|
|
||||||
public DrawableTestHit(Hit hit, HitResult type = HitResult.Great)
|
public DrawableTestHit(Hit hit, HitResult type = HitResult.Great, bool kiai = false)
|
||||||
: base(hit)
|
: base(hit)
|
||||||
{
|
{
|
||||||
Type = type;
|
Type = type;
|
||||||
|
|
||||||
HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
var controlPoints = new ControlPointInfo();
|
||||||
|
controlPoints.Add(0, new EffectControlPoint { KiaiMode = kiai });
|
||||||
|
|
||||||
|
HitObject.ApplyDefaults(controlPoints, new BeatmapDifficulty());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateInitialTransforms()
|
protected override void UpdateInitialTransforms()
|
||||||
|
@ -2,14 +2,16 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.Taiko.Judgements;
|
using osu.Game.Rulesets.Taiko.Judgements;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
@ -97,7 +99,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 6:
|
case 6:
|
||||||
PlayfieldContainer.Delay(delay).ResizeTo(new Vector2(1, TaikoPlayfield.DEFAULT_HEIGHT), 500);
|
PlayfieldContainer.Delay(delay).ResizeTo(new Vector2(1, DEFAULT_PLAYFIELD_CONTAINER_HEIGHT), 500);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,13 +108,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
{
|
{
|
||||||
HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Ok : HitResult.Great;
|
HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Ok : HitResult.Great;
|
||||||
|
|
||||||
var cpi = new ControlPointInfo();
|
|
||||||
cpi.Add(0, new EffectControlPoint { KiaiMode = kiai });
|
|
||||||
|
|
||||||
Hit hit = new Hit();
|
Hit hit = new Hit();
|
||||||
hit.ApplyDefaults(cpi, new BeatmapDifficulty());
|
var h = new DrawableTestHit(hit, kiai: kiai) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) };
|
||||||
|
|
||||||
var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) };
|
|
||||||
|
|
||||||
DrawableRuleset.Playfield.Add(h);
|
DrawableRuleset.Playfield.Add(h);
|
||||||
|
|
||||||
@ -123,25 +120,27 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
{
|
{
|
||||||
HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Ok : HitResult.Great;
|
HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Ok : HitResult.Great;
|
||||||
|
|
||||||
var cpi = new ControlPointInfo();
|
Hit hit = new Hit
|
||||||
cpi.Add(0, new EffectControlPoint { KiaiMode = kiai });
|
{
|
||||||
|
IsStrong = true,
|
||||||
Hit hit = new Hit();
|
Samples = createSamples(strong: true)
|
||||||
hit.ApplyDefaults(cpi, new BeatmapDifficulty());
|
};
|
||||||
|
var h = new DrawableTestHit(hit, kiai: kiai) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) };
|
||||||
var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) };
|
|
||||||
|
|
||||||
DrawableRuleset.Playfield.Add(h);
|
DrawableRuleset.Playfield.Add(h);
|
||||||
|
|
||||||
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult });
|
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult });
|
||||||
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great });
|
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h.NestedHitObjects.Single(), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addMissJudgement()
|
private void addMissJudgement()
|
||||||
{
|
{
|
||||||
DrawableTestHit h;
|
DrawableTestHit h;
|
||||||
DrawableRuleset.Playfield.Add(h = new DrawableTestHit(new Hit(), HitResult.Miss));
|
DrawableRuleset.Playfield.Add(h = new DrawableTestHit(new Hit { StartTime = DrawableRuleset.Playfield.Time.Current }, HitResult.Miss)
|
||||||
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss });
|
{
|
||||||
|
Alpha = 0
|
||||||
|
});
|
||||||
|
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(h.HitObject, new TaikoJudgement()) { Type = HitResult.Miss });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addBarLine(bool major, double delay = scroll_time)
|
private void addBarLine(bool major, double delay = scroll_time)
|
||||||
@ -173,6 +172,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
{
|
{
|
||||||
StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time,
|
StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time,
|
||||||
IsStrong = strong,
|
IsStrong = strong,
|
||||||
|
Samples = createSamples(strong: strong),
|
||||||
Duration = duration,
|
Duration = duration,
|
||||||
TickRate = 8,
|
TickRate = 8,
|
||||||
};
|
};
|
||||||
@ -190,7 +190,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
Hit h = new Hit
|
Hit h = new Hit
|
||||||
{
|
{
|
||||||
StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time,
|
StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time,
|
||||||
IsStrong = strong
|
IsStrong = strong,
|
||||||
|
Samples = createSamples(HitType.Centre, strong)
|
||||||
};
|
};
|
||||||
|
|
||||||
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
@ -203,7 +204,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
Hit h = new Hit
|
Hit h = new Hit
|
||||||
{
|
{
|
||||||
StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time,
|
StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time,
|
||||||
IsStrong = strong
|
IsStrong = strong,
|
||||||
|
Samples = createSamples(HitType.Rim, strong)
|
||||||
};
|
};
|
||||||
|
|
||||||
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
@ -211,14 +213,18 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
DrawableRuleset.Playfield.Add(new DrawableHit(h));
|
DrawableRuleset.Playfield.Add(new DrawableHit(h));
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestStrongNestedHit : DrawableStrongNestedHit
|
// TODO: can be removed if a better way of handling colour/strong type and samples is developed
|
||||||
|
private IList<HitSampleInfo> createSamples(HitType? hitType = null, bool strong = false)
|
||||||
{
|
{
|
||||||
public TestStrongNestedHit(DrawableHitObject mainObject)
|
var samples = new List<HitSampleInfo>();
|
||||||
: base(new StrongHitObject { StartTime = mainObject.HitObject.StartTime }, mainObject)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool OnPressed(TaikoAction action) => false;
|
if (hitType == HitType.Rim)
|
||||||
|
samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP));
|
||||||
|
|
||||||
|
if (strong)
|
||||||
|
samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH));
|
||||||
|
|
||||||
|
return samples;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,7 @@ namespace osu.Game.Beatmaps
|
|||||||
string cacheFilePath = storage.GetFullPath(cache_database_name);
|
string cacheFilePath = storage.GetFullPath(cache_database_name);
|
||||||
string compressedCacheFilePath = $"{cacheFilePath}.bz2";
|
string compressedCacheFilePath = $"{cacheFilePath}.bz2";
|
||||||
|
|
||||||
cacheDownloadRequest = new FileWebRequest(compressedCacheFilePath, $"https://assets.ppy.sh/client-resources/{cache_database_name}.bz2");
|
cacheDownloadRequest = new FileWebRequest(compressedCacheFilePath, $"https://assets.ppy.sh/client-resources/{cache_database_name}.bz2?{DateTimeOffset.UtcNow:yyyyMMdd}");
|
||||||
|
|
||||||
cacheDownloadRequest.Failed += ex =>
|
cacheDownloadRequest.Failed += ex =>
|
||||||
{
|
{
|
||||||
|
@ -95,6 +95,16 @@ namespace osu.Game.Screens.Play
|
|||||||
localGameplayClock = new LocalGameplayClock(userOffsetClock);
|
localGameplayClock = new LocalGameplayClock(userOffsetClock);
|
||||||
|
|
||||||
GameplayClock.IsPaused.BindTo(IsPaused);
|
GameplayClock.IsPaused.BindTo(IsPaused);
|
||||||
|
|
||||||
|
IsPaused.BindValueChanged(onPauseChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onPauseChanged(ValueChangedEvent<bool> isPaused)
|
||||||
|
{
|
||||||
|
if (isPaused.NewValue)
|
||||||
|
this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ => adjustableClock.Stop());
|
||||||
|
else
|
||||||
|
this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset;
|
private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset;
|
||||||
@ -154,13 +164,16 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
// Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time
|
if (!adjustableClock.IsRunning)
|
||||||
// This accounts for the audio clock source potentially taking time to enter a completely stopped state
|
{
|
||||||
Seek(GameplayClock.CurrentTime);
|
// Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time
|
||||||
adjustableClock.Start();
|
// This accounts for the audio clock source potentially taking time to enter a completely stopped state
|
||||||
IsPaused.Value = false;
|
Seek(GameplayClock.CurrentTime);
|
||||||
|
|
||||||
this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In);
|
adjustableClock.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
IsPaused.Value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -199,8 +212,6 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ => adjustableClock.Stop());
|
|
||||||
|
|
||||||
IsPaused.Value = true;
|
IsPaused.Value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
@ -71,8 +72,9 @@ namespace osu.Game.Screens.Play
|
|||||||
}
|
}
|
||||||
|
|
||||||
private bool readyForPush =>
|
private bool readyForPush =>
|
||||||
|
!playerConsumed
|
||||||
// don't push unless the player is completely loaded
|
// don't push unless the player is completely loaded
|
||||||
player?.LoadState == LoadState.Ready
|
&& player?.LoadState == LoadState.Ready
|
||||||
// don't push if the user is hovering one of the panes, unless they are idle.
|
// don't push if the user is hovering one of the panes, unless they are idle.
|
||||||
&& (IsHovered || idleTracker.IsIdle.Value)
|
&& (IsHovered || idleTracker.IsIdle.Value)
|
||||||
// don't push if the user is dragging a slider or otherwise.
|
// don't push if the user is dragging a slider or otherwise.
|
||||||
@ -84,6 +86,11 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
private Player player;
|
private Player player;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the curent player instance has been consumed via <see cref="consumePlayer"/>.
|
||||||
|
/// </summary>
|
||||||
|
private bool playerConsumed;
|
||||||
|
|
||||||
private LogoTrackingContainer content;
|
private LogoTrackingContainer content;
|
||||||
|
|
||||||
private bool hideOverlays;
|
private bool hideOverlays;
|
||||||
@ -179,7 +186,10 @@ namespace osu.Game.Screens.Play
|
|||||||
contentIn();
|
contentIn();
|
||||||
|
|
||||||
MetadataInfo.Delay(750).FadeIn(500);
|
MetadataInfo.Delay(750).FadeIn(500);
|
||||||
this.Delay(1800).Schedule(pushWhenLoaded);
|
|
||||||
|
// after an initial delay, start the debounced load check.
|
||||||
|
// this will continue to execute even after resuming back on restart.
|
||||||
|
Scheduler.Add(new ScheduledDelegate(pushWhenLoaded, 1800, 0));
|
||||||
|
|
||||||
showMuteWarningIfNeeded();
|
showMuteWarningIfNeeded();
|
||||||
}
|
}
|
||||||
@ -188,17 +198,18 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
base.OnResuming(last);
|
base.OnResuming(last);
|
||||||
|
|
||||||
contentIn();
|
// prepare for a retry.
|
||||||
|
player = null;
|
||||||
|
playerConsumed = false;
|
||||||
|
cancelLoad();
|
||||||
|
|
||||||
this.Delay(400).Schedule(pushWhenLoaded);
|
contentIn();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnSuspending(IScreen next)
|
public override void OnSuspending(IScreen next)
|
||||||
{
|
{
|
||||||
base.OnSuspending(next);
|
base.OnSuspending(next);
|
||||||
|
|
||||||
cancelLoad();
|
|
||||||
|
|
||||||
BackgroundBrightnessReduction = false;
|
BackgroundBrightnessReduction = false;
|
||||||
|
|
||||||
// we're moving to player, so a period of silence is upcoming.
|
// we're moving to player, so a period of silence is upcoming.
|
||||||
@ -274,6 +285,14 @@ namespace osu.Game.Screens.Play
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Player consumePlayer()
|
||||||
|
{
|
||||||
|
Debug.Assert(!playerConsumed);
|
||||||
|
|
||||||
|
playerConsumed = true;
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
private void prepareNewPlayer()
|
private void prepareNewPlayer()
|
||||||
{
|
{
|
||||||
if (!this.IsCurrentScreen())
|
if (!this.IsCurrentScreen())
|
||||||
@ -315,64 +334,62 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
if (!this.IsCurrentScreen()) return;
|
if (!this.IsCurrentScreen()) return;
|
||||||
|
|
||||||
try
|
if (!readyForPush)
|
||||||
{
|
{
|
||||||
if (!readyForPush)
|
// as the pushDebounce below has a delay, we need to keep checking and cancel a future debounce
|
||||||
|
// if we become unready for push during the delay.
|
||||||
|
cancelLoad();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if a push has already been scheduled, no further action is required.
|
||||||
|
// this value is reset via cancelLoad() to allow a second usage of the same PlayerLoader screen.
|
||||||
|
if (scheduledPushPlayer != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
scheduledPushPlayer = Scheduler.AddDelayed(() =>
|
||||||
|
{
|
||||||
|
// ensure that once we have reached this "point of no return", readyForPush will be false for all future checks (until a new player instance is prepared).
|
||||||
|
var consumedPlayer = consumePlayer();
|
||||||
|
|
||||||
|
contentOut();
|
||||||
|
|
||||||
|
TransformSequence<PlayerLoader> pushSequence = this.Delay(250);
|
||||||
|
|
||||||
|
// only show if the warning was created (i.e. the beatmap needs it)
|
||||||
|
// and this is not a restart of the map (the warning expires after first load).
|
||||||
|
if (epilepsyWarning?.IsAlive == true)
|
||||||
{
|
{
|
||||||
// as the pushDebounce below has a delay, we need to keep checking and cancel a future debounce
|
const double epilepsy_display_length = 3000;
|
||||||
// if we become unready for push during the delay.
|
|
||||||
cancelLoad();
|
pushSequence
|
||||||
return;
|
.Schedule(() => epilepsyWarning.State.Value = Visibility.Visible)
|
||||||
|
.TransformBindableTo(volumeAdjustment, 0.25, EpilepsyWarning.FADE_DURATION, Easing.OutQuint)
|
||||||
|
.Delay(epilepsy_display_length)
|
||||||
|
.Schedule(() =>
|
||||||
|
{
|
||||||
|
epilepsyWarning.Hide();
|
||||||
|
epilepsyWarning.Expire();
|
||||||
|
})
|
||||||
|
.Delay(EpilepsyWarning.FADE_DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scheduledPushPlayer != null)
|
pushSequence.Schedule(() =>
|
||||||
return;
|
|
||||||
|
|
||||||
scheduledPushPlayer = Scheduler.AddDelayed(() =>
|
|
||||||
{
|
{
|
||||||
contentOut();
|
if (!this.IsCurrentScreen()) return;
|
||||||
|
|
||||||
TransformSequence<PlayerLoader> pushSequence = this.Delay(250);
|
LoadTask = null;
|
||||||
|
|
||||||
// only show if the warning was created (i.e. the beatmap needs it)
|
// By default, we want to load the player and never be returned to.
|
||||||
// and this is not a restart of the map (the warning expires after first load).
|
// Note that this may change if the player we load requested a re-run.
|
||||||
if (epilepsyWarning?.IsAlive == true)
|
ValidForResume = false;
|
||||||
{
|
|
||||||
const double epilepsy_display_length = 3000;
|
|
||||||
|
|
||||||
pushSequence
|
if (consumedPlayer.LoadedBeatmapSuccessfully)
|
||||||
.Schedule(() => epilepsyWarning.State.Value = Visibility.Visible)
|
this.Push(consumedPlayer);
|
||||||
.TransformBindableTo(volumeAdjustment, 0.25, EpilepsyWarning.FADE_DURATION, Easing.OutQuint)
|
else
|
||||||
.Delay(epilepsy_display_length)
|
this.Exit();
|
||||||
.Schedule(() =>
|
});
|
||||||
{
|
}, 500);
|
||||||
epilepsyWarning.Hide();
|
|
||||||
epilepsyWarning.Expire();
|
|
||||||
})
|
|
||||||
.Delay(EpilepsyWarning.FADE_DURATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
pushSequence.Schedule(() =>
|
|
||||||
{
|
|
||||||
if (!this.IsCurrentScreen()) return;
|
|
||||||
|
|
||||||
LoadTask = null;
|
|
||||||
|
|
||||||
// By default, we want to load the player and never be returned to.
|
|
||||||
// Note that this may change if the player we load requested a re-run.
|
|
||||||
ValidForResume = false;
|
|
||||||
|
|
||||||
if (player.LoadedBeatmapSuccessfully)
|
|
||||||
this.Push(player);
|
|
||||||
else
|
|
||||||
this.Exit();
|
|
||||||
});
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Schedule(pushWhenLoaded);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cancelLoad()
|
private void cancelLoad()
|
||||||
@ -390,7 +407,7 @@ namespace osu.Game.Screens.Play
|
|||||||
if (isDisposing)
|
if (isDisposing)
|
||||||
{
|
{
|
||||||
// if the player never got pushed, we should explicitly dispose it.
|
// if the player never got pushed, we should explicitly dispose it.
|
||||||
DisposalTask = LoadTask?.ContinueWith(_ => player.Dispose());
|
DisposalTask = LoadTask?.ContinueWith(_ => player?.Dispose());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.1203.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2020.1212.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" />
|
||||||
|
@ -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.1203.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.1212.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.1203.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2020.1212.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