mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 17:23:22 +08:00
Merge branch 'master' into legacy-jugement-match-stable-transforms
This commit is contained in:
commit
24d7fa03d8
17
.vscode/tasks.json
vendored
17
.vscode/tasks.json
vendored
@ -9,7 +9,6 @@
|
|||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--no-restore",
|
|
||||||
"osu.Desktop",
|
"osu.Desktop",
|
||||||
"-p:GenerateFullPaths=true",
|
"-p:GenerateFullPaths=true",
|
||||||
"-m",
|
"-m",
|
||||||
@ -24,7 +23,6 @@
|
|||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--no-restore",
|
|
||||||
"osu.Desktop",
|
"osu.Desktop",
|
||||||
"-p:Configuration=Release",
|
"-p:Configuration=Release",
|
||||||
"-p:GenerateFullPaths=true",
|
"-p:GenerateFullPaths=true",
|
||||||
@ -40,7 +38,6 @@
|
|||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--no-restore",
|
|
||||||
"osu.Game.Tests",
|
"osu.Game.Tests",
|
||||||
"-p:GenerateFullPaths=true",
|
"-p:GenerateFullPaths=true",
|
||||||
"-m",
|
"-m",
|
||||||
@ -55,7 +52,6 @@
|
|||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--no-restore",
|
|
||||||
"osu.Game.Tests",
|
"osu.Game.Tests",
|
||||||
"-p:Configuration=Release",
|
"-p:Configuration=Release",
|
||||||
"-p:GenerateFullPaths=true",
|
"-p:GenerateFullPaths=true",
|
||||||
@ -71,7 +67,6 @@
|
|||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--no-restore",
|
|
||||||
"osu.Game.Tournament.Tests",
|
"osu.Game.Tournament.Tests",
|
||||||
"-p:GenerateFullPaths=true",
|
"-p:GenerateFullPaths=true",
|
||||||
"-m",
|
"-m",
|
||||||
@ -86,7 +81,6 @@
|
|||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--no-restore",
|
|
||||||
"osu.Game.Tournament.Tests",
|
"osu.Game.Tournament.Tests",
|
||||||
"-p:Configuration=Release",
|
"-p:Configuration=Release",
|
||||||
"-p:GenerateFullPaths=true",
|
"-p:GenerateFullPaths=true",
|
||||||
@ -102,7 +96,6 @@
|
|||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--no-restore",
|
|
||||||
"osu.Game.Benchmarks",
|
"osu.Game.Benchmarks",
|
||||||
"-p:Configuration=Release",
|
"-p:Configuration=Release",
|
||||||
"-p:GenerateFullPaths=true",
|
"-p:GenerateFullPaths=true",
|
||||||
@ -111,16 +104,6 @@
|
|||||||
],
|
],
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"problemMatcher": "$msCompile"
|
"problemMatcher": "$msCompile"
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Restore (netcoreapp3.1)",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "dotnet",
|
|
||||||
"args": [
|
|
||||||
"restore",
|
|
||||||
"build/Desktop.proj"
|
|
||||||
],
|
|
||||||
"problemMatcher": []
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -75,7 +75,6 @@ git pull
|
|||||||
Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this is provided [below](#contributing).
|
Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this is provided [below](#contributing).
|
||||||
|
|
||||||
- Visual Studio / Rider users should load the project via one of the platform-specific `.slnf` files, rather than the main `.sln.` This will allow access to template run configurations.
|
- Visual Studio / Rider users should load the project via one of the platform-specific `.slnf` files, rather than the main `.sln.` This will allow access to template run configurations.
|
||||||
- Visual Studio Code users must run the `Restore` task before any build attempt.
|
|
||||||
|
|
||||||
You can also build and run *osu!* from the command-line with a single command:
|
You can also build and run *osu!* from the command-line with a single command:
|
||||||
|
|
||||||
|
@ -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.1113.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1118.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -139,7 +139,7 @@ namespace osu.Desktop
|
|||||||
|
|
||||||
// SDL2 DesktopWindow
|
// SDL2 DesktopWindow
|
||||||
case DesktopWindow desktopWindow:
|
case DesktopWindow desktopWindow:
|
||||||
desktopWindow.CursorState.Value |= CursorState.Hidden;
|
desktopWindow.CursorState |= CursorState.Hidden;
|
||||||
desktopWindow.SetIconFromStream(iconStream);
|
desktopWindow.SetIconFromStream(iconStream);
|
||||||
desktopWindow.Title = Name;
|
desktopWindow.Title = Name;
|
||||||
desktopWindow.DragDrop += f => fileDrop(new[] { f });
|
desktopWindow.DragDrop += f => fileDrop(new[] { f });
|
||||||
|
11
osu.Game.Rulesets.Catch.Tests/.vscode/tasks.json
vendored
11
osu.Game.Rulesets.Catch.Tests/.vscode/tasks.json
vendored
@ -9,7 +9,6 @@
|
|||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--no-restore",
|
|
||||||
"osu.Game.Rulesets.Catch.Tests.csproj",
|
"osu.Game.Rulesets.Catch.Tests.csproj",
|
||||||
"-p:GenerateFullPaths=true",
|
"-p:GenerateFullPaths=true",
|
||||||
"-m",
|
"-m",
|
||||||
@ -24,7 +23,6 @@
|
|||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--no-restore",
|
|
||||||
"osu.Game.Rulesets.Catch.Tests.csproj",
|
"osu.Game.Rulesets.Catch.Tests.csproj",
|
||||||
"-p:Configuration=Release",
|
"-p:Configuration=Release",
|
||||||
"-p:GenerateFullPaths=true",
|
"-p:GenerateFullPaths=true",
|
||||||
@ -33,15 +31,6 @@
|
|||||||
],
|
],
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"problemMatcher": "$msCompile"
|
"problemMatcher": "$msCompile"
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Restore",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "dotnet",
|
|
||||||
"args": [
|
|
||||||
"restore"
|
|
||||||
],
|
|
||||||
"problemMatcher": []
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -18,71 +18,42 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation)))
|
foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation)))
|
||||||
AddStep($"show {rep}", () => SetContents(() => createDrawable(rep)));
|
AddStep($"show {rep}", () => SetContents(() => createDrawableFruit(rep)));
|
||||||
|
|
||||||
AddStep("show droplet", () => SetContents(() => createDrawableDroplet()));
|
AddStep("show droplet", () => SetContents(() => createDrawableDroplet()));
|
||||||
AddStep("show tiny droplet", () => SetContents(createDrawableTinyDroplet));
|
AddStep("show tiny droplet", () => SetContents(createDrawableTinyDroplet));
|
||||||
|
|
||||||
foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation)))
|
foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation)))
|
||||||
AddStep($"show hyperdash {rep}", () => SetContents(() => createDrawable(rep, true)));
|
AddStep($"show hyperdash {rep}", () => SetContents(() => createDrawableFruit(rep, true)));
|
||||||
|
|
||||||
AddStep("show hyperdash droplet", () => SetContents(() => createDrawableDroplet(true)));
|
AddStep("show hyperdash droplet", () => SetContents(() => createDrawableDroplet(true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Drawable createDrawableTinyDroplet()
|
private Drawable createDrawableFruit(FruitVisualRepresentation rep, bool hyperdash = false) =>
|
||||||
|
setProperties(new DrawableFruit(new TestCatchFruit(rep)), hyperdash);
|
||||||
|
|
||||||
|
private Drawable createDrawableDroplet(bool hyperdash = false) => setProperties(new DrawableDroplet(new Droplet()), hyperdash);
|
||||||
|
|
||||||
|
private Drawable createDrawableTinyDroplet() => setProperties(new DrawableTinyDroplet(new TinyDroplet()));
|
||||||
|
|
||||||
|
private DrawableCatchHitObject setProperties(DrawableCatchHitObject d, bool hyperdash = false)
|
||||||
{
|
{
|
||||||
var droplet = new TestCatchTinyDroplet
|
var hitObject = d.HitObject;
|
||||||
{
|
hitObject.StartTime = 1000000000000;
|
||||||
Scale = 1.5f,
|
hitObject.Scale = 1.5f;
|
||||||
};
|
|
||||||
|
|
||||||
return new DrawableTinyDroplet(droplet)
|
if (hyperdash)
|
||||||
{
|
hitObject.HyperDashTarget = new Banana();
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
RelativePositionAxes = Axes.None,
|
|
||||||
Position = Vector2.Zero,
|
|
||||||
Alpha = 1,
|
|
||||||
LifetimeStart = double.NegativeInfinity,
|
|
||||||
LifetimeEnd = double.PositiveInfinity,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private Drawable createDrawableDroplet(bool hyperdash = false)
|
d.Anchor = Anchor.Centre;
|
||||||
{
|
d.RelativePositionAxes = Axes.None;
|
||||||
var droplet = new TestCatchDroplet
|
d.Position = Vector2.Zero;
|
||||||
|
d.HitObjectApplied += _ =>
|
||||||
{
|
{
|
||||||
Scale = 1.5f,
|
d.LifetimeStart = double.NegativeInfinity;
|
||||||
HyperDashTarget = hyperdash ? new Banana() : null
|
d.LifetimeEnd = double.PositiveInfinity;
|
||||||
};
|
|
||||||
|
|
||||||
return new DrawableDroplet(droplet)
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
RelativePositionAxes = Axes.None,
|
|
||||||
Position = Vector2.Zero,
|
|
||||||
Alpha = 1,
|
|
||||||
LifetimeStart = double.NegativeInfinity,
|
|
||||||
LifetimeEnd = double.PositiveInfinity,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private Drawable createDrawable(FruitVisualRepresentation rep, bool hyperdash = false)
|
|
||||||
{
|
|
||||||
Fruit fruit = new TestCatchFruit(rep)
|
|
||||||
{
|
|
||||||
Scale = 1.5f,
|
|
||||||
HyperDashTarget = hyperdash ? new Banana() : null
|
|
||||||
};
|
|
||||||
|
|
||||||
return new DrawableFruit(fruit)
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
RelativePositionAxes = Axes.None,
|
|
||||||
Position = Vector2.Zero,
|
|
||||||
Alpha = 1,
|
|
||||||
LifetimeStart = double.NegativeInfinity,
|
|
||||||
LifetimeEnd = double.PositiveInfinity,
|
|
||||||
};
|
};
|
||||||
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestCatchFruit : Fruit
|
public class TestCatchFruit : Fruit
|
||||||
@ -90,26 +61,9 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
public TestCatchFruit(FruitVisualRepresentation rep)
|
public TestCatchFruit(FruitVisualRepresentation rep)
|
||||||
{
|
{
|
||||||
VisualRepresentation = rep;
|
VisualRepresentation = rep;
|
||||||
StartTime = 1000000000000;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override FruitVisualRepresentation VisualRepresentation { get; }
|
public override FruitVisualRepresentation VisualRepresentation { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestCatchDroplet : Droplet
|
|
||||||
{
|
|
||||||
public TestCatchDroplet()
|
|
||||||
{
|
|
||||||
StartTime = 1000000000000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TestCatchTinyDroplet : TinyDroplet
|
|
||||||
{
|
|
||||||
public TestCatchTinyDroplet()
|
|
||||||
{
|
|
||||||
StartTime = 1000000000000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
11
osu.Game.Rulesets.Mania.Tests/.vscode/tasks.json
vendored
11
osu.Game.Rulesets.Mania.Tests/.vscode/tasks.json
vendored
@ -9,7 +9,6 @@
|
|||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--no-restore",
|
|
||||||
"osu.Game.Rulesets.Mania.Tests.csproj",
|
"osu.Game.Rulesets.Mania.Tests.csproj",
|
||||||
"-p:GenerateFullPaths=true",
|
"-p:GenerateFullPaths=true",
|
||||||
"-m",
|
"-m",
|
||||||
@ -24,7 +23,6 @@
|
|||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--no-restore",
|
|
||||||
"osu.Game.Rulesets.Mania.Tests.csproj",
|
"osu.Game.Rulesets.Mania.Tests.csproj",
|
||||||
"-p:Configuration=Release",
|
"-p:Configuration=Release",
|
||||||
"-p:GenerateFullPaths=true",
|
"-p:GenerateFullPaths=true",
|
||||||
@ -33,15 +31,6 @@
|
|||||||
],
|
],
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"problemMatcher": "$msCompile"
|
"problemMatcher": "$msCompile"
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Restore",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "dotnet",
|
|
||||||
"args": [
|
|
||||||
"restore"
|
|
||||||
],
|
|
||||||
"problemMatcher": []
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
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.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class ManiaHitObjectTestScene : ManiaSkinnableTestScene
|
public abstract class ManiaHitObjectTestScene : ManiaSkinnableTestScene
|
||||||
{
|
{
|
||||||
[BackgroundDependencyLoader]
|
[SetUp]
|
||||||
private void load()
|
public void SetUp() => Schedule(() =>
|
||||||
{
|
{
|
||||||
SetContents(() => new FillFlowContainer
|
SetContents(() => new FillFlowContainer
|
||||||
{
|
{
|
||||||
@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
protected abstract DrawableManiaHitObject CreateHitObject();
|
protected abstract DrawableManiaHitObject CreateHitObject();
|
||||||
}
|
}
|
||||||
|
@ -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.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -26,6 +27,18 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFadeOnMiss()
|
||||||
|
{
|
||||||
|
AddStep("miss tick", () =>
|
||||||
|
{
|
||||||
|
foreach (var holdNote in holdNotes)
|
||||||
|
holdNote.ChildrenOfType<DrawableHoldNoteHead>().First().MissForcefully();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<DrawableHoldNote> holdNotes => CreatedDrawables.SelectMany(d => d.ChildrenOfType<DrawableHoldNote>());
|
||||||
|
|
||||||
protected override DrawableManiaHitObject CreateHitObject()
|
protected override DrawableManiaHitObject CreateHitObject()
|
||||||
{
|
{
|
||||||
var note = new HoldNote { Duration = 1000 };
|
var note = new HoldNote { Duration = 1000 };
|
||||||
|
@ -51,9 +51,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
public double? HoldStartTime { get; private set; }
|
public double? HoldStartTime { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the hold note has been released too early and shouldn't give full score for the release.
|
/// Time at which the hold note has been broken, i.e. released too early, resulting in a reduced score.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HasBroken { get; private set; }
|
public double? HoldBrokenTime { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the hold note has been released potentially without having caused a break.
|
/// Whether the hold note has been released potentially without having caused a break.
|
||||||
@ -238,7 +238,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Tail.Judged && !Tail.IsHit)
|
if (Tail.Judged && !Tail.IsHit)
|
||||||
HasBroken = true;
|
HoldBrokenTime = Time.Current;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool OnPressed(ManiaAction action)
|
public bool OnPressed(ManiaAction action)
|
||||||
@ -298,7 +298,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
|
|
||||||
// If the key has been released too early, the user should not receive full score for the release
|
// If the key has been released too early, the user should not receive full score for the release
|
||||||
if (!Tail.IsHit)
|
if (!Tail.IsHit)
|
||||||
HasBroken = true;
|
HoldBrokenTime = Time.Current;
|
||||||
|
|
||||||
releaseTime = Time.Current;
|
releaseTime = Time.Current;
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
ApplyResult(r =>
|
ApplyResult(r =>
|
||||||
{
|
{
|
||||||
// If the head wasn't hit or the hold note was broken, cap the max score to Meh.
|
// If the head wasn't hit or the hold note was broken, cap the max score to Meh.
|
||||||
if (result > HitResult.Meh && (!holdNote.Head.IsHit || holdNote.HasBroken))
|
if (result > HitResult.Meh && (!holdNote.Head.IsHit || holdNote.HoldBrokenTime != null))
|
||||||
result = HitResult.Meh;
|
result = HitResult.Meh;
|
||||||
|
|
||||||
r.Type = result;
|
r.Type = result;
|
||||||
|
@ -16,8 +16,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class DrawableHoldNoteTick : DrawableManiaHitObject<HoldNoteTick>
|
public class DrawableHoldNoteTick : DrawableManiaHitObject<HoldNoteTick>
|
||||||
{
|
{
|
||||||
public override bool DisplayResult => false;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// References the time at which the user started holding the hold note.
|
/// References the time at which the user started holding the hold note.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -18,9 +18,17 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
{
|
{
|
||||||
public class LegacyBodyPiece : LegacyManiaColumnElement
|
public class LegacyBodyPiece : LegacyManiaColumnElement
|
||||||
{
|
{
|
||||||
|
private DrawableHoldNote holdNote;
|
||||||
|
|
||||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||||
private readonly IBindable<bool> isHitting = new Bindable<bool>();
|
private readonly IBindable<bool> isHitting = new Bindable<bool>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores the start time of the fade animation that plays when any of the nested
|
||||||
|
/// hitobjects of the hold note are missed.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Bindable<double?> missFadeTime = new Bindable<double?>();
|
||||||
|
|
||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
private Drawable bodySprite;
|
private Drawable bodySprite;
|
||||||
|
|
||||||
@ -38,6 +46,8 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(ISkinSource skin, IScrollingInfo scrollingInfo, DrawableHitObject drawableObject)
|
private void load(ISkinSource skin, IScrollingInfo scrollingInfo, DrawableHitObject drawableObject)
|
||||||
{
|
{
|
||||||
|
holdNote = (DrawableHoldNote)drawableObject;
|
||||||
|
|
||||||
string imageName = GetColumnSkinConfig<string>(skin, LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage)?.Value
|
string imageName = GetColumnSkinConfig<string>(skin, LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage)?.Value
|
||||||
?? $"mania-note{FallbackColumnIndex}L";
|
?? $"mania-note{FallbackColumnIndex}L";
|
||||||
|
|
||||||
@ -92,11 +102,26 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
InternalChild = bodySprite;
|
InternalChild = bodySprite;
|
||||||
|
|
||||||
direction.BindTo(scrollingInfo.Direction);
|
direction.BindTo(scrollingInfo.Direction);
|
||||||
direction.BindValueChanged(onDirectionChanged, true);
|
|
||||||
|
|
||||||
var holdNote = (DrawableHoldNote)drawableObject;
|
|
||||||
isHitting.BindTo(holdNote.IsHitting);
|
isHitting.BindTo(holdNote.IsHitting);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
direction.BindValueChanged(onDirectionChanged, true);
|
||||||
isHitting.BindValueChanged(onIsHittingChanged, true);
|
isHitting.BindValueChanged(onIsHittingChanged, true);
|
||||||
|
missFadeTime.BindValueChanged(onMissFadeTimeChanged, true);
|
||||||
|
|
||||||
|
holdNote.ApplyCustomUpdateState += applyCustomUpdateState;
|
||||||
|
applyCustomUpdateState(holdNote, holdNote.State.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyCustomUpdateState(DrawableHitObject hitObject, ArmedState state)
|
||||||
|
{
|
||||||
|
// ensure that the hold note is also faded out when the head/tail/any tick is missed.
|
||||||
|
if (state == ArmedState.Miss)
|
||||||
|
missFadeTime.Value ??= hitObject.HitStateUpdateTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onIsHittingChanged(ValueChangedEvent<bool> isHitting)
|
private void onIsHittingChanged(ValueChangedEvent<bool> isHitting)
|
||||||
@ -158,10 +183,38 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onMissFadeTimeChanged(ValueChangedEvent<double?> missFadeTimeChange)
|
||||||
|
{
|
||||||
|
if (missFadeTimeChange.NewValue == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// this update could come from any nested object of the hold note (or even from an input).
|
||||||
|
// make sure the transforms are consistent across all affected parts.
|
||||||
|
using (BeginAbsoluteSequence(missFadeTimeChange.NewValue.Value))
|
||||||
|
{
|
||||||
|
// colour and duration matches stable
|
||||||
|
// transforms not applied to entire hold note in order to not affect hit lighting
|
||||||
|
const double fade_duration = 60;
|
||||||
|
|
||||||
|
holdNote.Head.FadeColour(Colour4.DarkGray, fade_duration);
|
||||||
|
holdNote.Tail.FadeColour(Colour4.DarkGray, fade_duration);
|
||||||
|
bodySprite?.FadeColour(Colour4.DarkGray, fade_duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
missFadeTime.Value ??= holdNote.HoldBrokenTime;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (holdNote != null)
|
||||||
|
holdNote.ApplyCustomUpdateState -= applyCustomUpdateState;
|
||||||
|
|
||||||
lightContainer?.Expire();
|
lightContainer?.Expire();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
if (result.IsHit)
|
if (result.IsHit)
|
||||||
hitPolicy.HandleHit(judgedObject);
|
hitPolicy.HandleHit(judgedObject);
|
||||||
|
|
||||||
if (!result.IsHit || !DisplayJudgements.Value)
|
if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result)));
|
HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result)));
|
||||||
|
@ -167,6 +167,10 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
if (!judgedObject.DisplayResult || !DisplayJudgements.Value)
|
if (!judgedObject.DisplayResult || !DisplayJudgements.Value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Tick judgements should not display text.
|
||||||
|
if (judgedObject is DrawableHoldNoteTick)
|
||||||
|
return;
|
||||||
|
|
||||||
judgements.Clear(false);
|
judgements.Clear(false);
|
||||||
judgements.Add(judgementPool.Get(j =>
|
judgements.Add(judgementPool.Get(j =>
|
||||||
{
|
{
|
||||||
|
11
osu.Game.Rulesets.Osu.Tests/.vscode/tasks.json
vendored
11
osu.Game.Rulesets.Osu.Tests/.vscode/tasks.json
vendored
@ -9,7 +9,6 @@
|
|||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--no-restore",
|
|
||||||
"osu.Game.Rulesets.Osu.Tests.csproj",
|
"osu.Game.Rulesets.Osu.Tests.csproj",
|
||||||
"-p:GenerateFullPaths=true",
|
"-p:GenerateFullPaths=true",
|
||||||
"-m",
|
"-m",
|
||||||
@ -24,7 +23,6 @@
|
|||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--no-restore",
|
|
||||||
"osu.Game.Rulesets.Osu.Tests.csproj",
|
"osu.Game.Rulesets.Osu.Tests.csproj",
|
||||||
"-p:Configuration=Release",
|
"-p:Configuration=Release",
|
||||||
"-p:GenerateFullPaths=true",
|
"-p:GenerateFullPaths=true",
|
||||||
@ -33,15 +31,6 @@
|
|||||||
],
|
],
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"problemMatcher": "$msCompile"
|
"problemMatcher": "$msCompile"
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Restore",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "dotnet",
|
|
||||||
"args": [
|
|
||||||
"restore"
|
|
||||||
],
|
|
||||||
"problemMatcher": []
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -1,20 +1,30 @@
|
|||||||
// 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.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
{
|
{
|
||||||
public class TestSceneSliderApplication : OsuTestScene
|
public class TestSceneSliderApplication : OsuTestScene
|
||||||
{
|
{
|
||||||
|
[Resolved]
|
||||||
|
private SkinManager skinManager { get; set; }
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestApplyNewSlider()
|
public void TestApplyNewSlider()
|
||||||
{
|
{
|
||||||
@ -50,6 +60,41 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
}), null));
|
}), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBallTintChangedOnAccentChange()
|
||||||
|
{
|
||||||
|
DrawableSlider dho = null;
|
||||||
|
|
||||||
|
AddStep("create slider", () =>
|
||||||
|
{
|
||||||
|
var tintingSkin = skinManager.GetSkin(DefaultLegacySkin.Info);
|
||||||
|
tintingSkin.Configuration.ConfigDictionary["AllowSliderBallTint"] = "1";
|
||||||
|
|
||||||
|
Child = new SkinProvidingContainer(tintingSkin)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = dho = new DrawableSlider(prepareObject(new Slider
|
||||||
|
{
|
||||||
|
Position = new Vector2(256, 192),
|
||||||
|
IndexInCurrentCombo = 0,
|
||||||
|
StartTime = Time.Current,
|
||||||
|
Path = new SliderPath(PathType.Linear, new[]
|
||||||
|
{
|
||||||
|
Vector2.Zero,
|
||||||
|
new Vector2(150, 100),
|
||||||
|
new Vector2(300, 0),
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("set accent white", () => dho.AccentColour.Value = Color4.White);
|
||||||
|
AddAssert("ball is white", () => dho.ChildrenOfType<SliderBall>().Single().AccentColour == Color4.White);
|
||||||
|
|
||||||
|
AddStep("set accent red", () => dho.AccentColour.Value = Color4.Red);
|
||||||
|
AddAssert("ball is red", () => dho.ChildrenOfType<SliderBall>().Single().AccentColour == Color4.Red);
|
||||||
|
}
|
||||||
|
|
||||||
private Slider prepareObject(Slider slider)
|
private Slider prepareObject(Slider slider)
|
||||||
{
|
{
|
||||||
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
private readonly Path path;
|
private readonly Path path;
|
||||||
private readonly Slider slider;
|
private readonly Slider slider;
|
||||||
private readonly int controlPointIndex;
|
public int ControlPointIndex { get; set; }
|
||||||
|
|
||||||
private IBindable<Vector2> sliderPosition;
|
private IBindable<Vector2> sliderPosition;
|
||||||
private IBindable<int> pathVersion;
|
private IBindable<int> pathVersion;
|
||||||
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
public PathControlPointConnectionPiece(Slider slider, int controlPointIndex)
|
public PathControlPointConnectionPiece(Slider slider, int controlPointIndex)
|
||||||
{
|
{
|
||||||
this.slider = slider;
|
this.slider = slider;
|
||||||
this.controlPointIndex = controlPointIndex;
|
ControlPointIndex = controlPointIndex;
|
||||||
|
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
path.ClearVertices();
|
path.ClearVertices();
|
||||||
|
|
||||||
int nextIndex = controlPointIndex + 1;
|
int nextIndex = ControlPointIndex + 1;
|
||||||
if (nextIndex == 0 || nextIndex >= slider.Path.ControlPoints.Count)
|
if (nextIndex == 0 || nextIndex >= slider.Path.ControlPoints.Count)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -66,6 +66,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
switch (e.Action)
|
switch (e.Action)
|
||||||
{
|
{
|
||||||
case NotifyCollectionChangedAction.Add:
|
case NotifyCollectionChangedAction.Add:
|
||||||
|
// If inserting in the path (not appending),
|
||||||
|
// update indices of existing connections after insert location
|
||||||
|
if (e.NewStartingIndex < Pieces.Count)
|
||||||
|
{
|
||||||
|
foreach (var connection in Connections)
|
||||||
|
{
|
||||||
|
if (connection.ControlPointIndex >= e.NewStartingIndex)
|
||||||
|
connection.ControlPointIndex += e.NewItems.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < e.NewItems.Count; i++)
|
for (int i = 0; i < e.NewItems.Count; i++)
|
||||||
{
|
{
|
||||||
var point = (PathControlPoint)e.NewItems[i];
|
var point = (PathControlPoint)e.NewItems[i];
|
||||||
@ -88,6 +99,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
Connections.RemoveAll(c => c.ControlPoint == point);
|
Connections.RemoveAll(c => c.ControlPoint == point);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If removing before the end of the path,
|
||||||
|
// update indices of connections after remove location
|
||||||
|
if (e.OldStartingIndex < Pieces.Count)
|
||||||
|
{
|
||||||
|
foreach (var connection in Connections)
|
||||||
|
{
|
||||||
|
if (connection.ControlPointIndex >= e.OldStartingIndex)
|
||||||
|
connection.ControlPointIndex -= e.OldItems.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,6 +180,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
this.Delay(800).FadeOut();
|
this.Delay(800).FadeOut();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Expire();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Drawable ProxiedLayer => ApproachCircle;
|
public Drawable ProxiedLayer => ApproachCircle;
|
||||||
|
@ -11,6 +11,7 @@ using osu.Game.Rulesets.Osu.Judgements;
|
|||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
@ -60,6 +61,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
PositionBindable.BindTo(HitObject.PositionBindable);
|
PositionBindable.BindTo(HitObject.PositionBindable);
|
||||||
StackHeightBindable.BindTo(HitObject.StackHeightBindable);
|
StackHeightBindable.BindTo(HitObject.StackHeightBindable);
|
||||||
ScaleBindable.BindTo(HitObject.ScaleBindable);
|
ScaleBindable.BindTo(HitObject.ScaleBindable);
|
||||||
|
|
||||||
|
// Manually set to reduce the number of future alive objects to a bare minimum.
|
||||||
|
LifetimeStart = HitObject.StartTime - HitObject.TimePreempt;
|
||||||
|
|
||||||
|
// Arbitrary lifetime end to prevent past objects in idle states remaining alive in non-frame-stable contexts.
|
||||||
|
// An extra 1000ms is added to always overestimate the true lifetime, and a more exact value is set by hit transforms and the following expiry.
|
||||||
|
LifetimeEnd = HitObject.GetEndTime() + HitObject.HitWindows.WindowFor(HitResult.Miss) + 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnFree(HitObject hitObject)
|
protected override void OnFree(HitObject hitObject)
|
||||||
@ -85,14 +93,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
public virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength);
|
public virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength);
|
||||||
|
|
||||||
protected override void UpdateInitialTransforms()
|
|
||||||
{
|
|
||||||
base.UpdateInitialTransforms();
|
|
||||||
|
|
||||||
// Manually set to reduce the number of future alive objects to a bare minimum.
|
|
||||||
LifetimeStart = HitObject.StartTime - HitObject.TimePreempt;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Causes this <see cref="DrawableOsuHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>.
|
/// Causes this <see cref="DrawableOsuHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -80,6 +80,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
foreach (var drawableHitObject in NestedHitObjects)
|
foreach (var drawableHitObject in NestedHitObjects)
|
||||||
drawableHitObject.AccentColour.Value = colour.NewValue;
|
drawableHitObject.AccentColour.Value = colour.NewValue;
|
||||||
|
updateBallTint();
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
Tracking.BindValueChanged(updateSlidingSample);
|
Tracking.BindValueChanged(updateSlidingSample);
|
||||||
@ -192,13 +193,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
return base.CreateNestedHitObject(hitObject);
|
return base.CreateNestedHitObject(hitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateInitialTransforms()
|
|
||||||
{
|
|
||||||
base.UpdateInitialTransforms();
|
|
||||||
|
|
||||||
Body.FadeInFromZero(HitObject.TimeFadeIn);
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly Bindable<bool> Tracking = new Bindable<bool>();
|
public readonly Bindable<bool> Tracking = new Bindable<bool>();
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
@ -244,7 +238,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
base.ApplySkin(skin, allowFallback);
|
base.ApplySkin(skin, allowFallback);
|
||||||
|
|
||||||
bool allowBallTint = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false;
|
updateBallTint();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBallTint()
|
||||||
|
{
|
||||||
|
if (CurrentSkin == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool allowBallTint = CurrentSkin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false;
|
||||||
Ball.AccentColour = allowBallTint ? AccentColour.Value : Color4.White;
|
Ball.AccentColour = allowBallTint ? AccentColour.Value : Color4.White;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,6 +266,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
base.PlaySamples();
|
base.PlaySamples();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void UpdateInitialTransforms()
|
||||||
|
{
|
||||||
|
base.UpdateInitialTransforms();
|
||||||
|
|
||||||
|
Body.FadeInFromZero(HitObject.TimeFadeIn);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void UpdateStartTimeStateTransforms()
|
protected override void UpdateStartTimeStateTransforms()
|
||||||
{
|
{
|
||||||
base.UpdateStartTimeStateTransforms();
|
base.UpdateStartTimeStateTransforms();
|
||||||
@ -288,7 +297,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.FadeOut(fade_out_time, Easing.OutQuint);
|
this.FadeOut(fade_out_time, Easing.OutQuint).Expire();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Drawable ProxiedLayer => HeadCircle.ProxiedLayer;
|
public Drawable ProxiedLayer => HeadCircle.ProxiedLayer;
|
||||||
|
@ -157,7 +157,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
base.UpdateHitStateTransforms(state);
|
base.UpdateHitStateTransforms(state);
|
||||||
|
|
||||||
this.FadeOut(160);
|
this.FadeOut(160).Expire();
|
||||||
|
|
||||||
// skin change does a rewind of transforms, which will stop the spinning sound from playing if it's currently in playback.
|
// skin change does a rewind of transforms, which will stop the spinning sound from playing if it's currently in playback.
|
||||||
isSpinning?.TriggerChange();
|
isSpinning?.TriggerChange();
|
||||||
|
11
osu.Game.Rulesets.Taiko.Tests/.vscode/tasks.json
vendored
11
osu.Game.Rulesets.Taiko.Tests/.vscode/tasks.json
vendored
@ -9,7 +9,6 @@
|
|||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--no-restore",
|
|
||||||
"osu.Game.Rulesets.Taiko.Tests.csproj",
|
"osu.Game.Rulesets.Taiko.Tests.csproj",
|
||||||
"-p:GenerateFullPaths=true",
|
"-p:GenerateFullPaths=true",
|
||||||
"-m",
|
"-m",
|
||||||
@ -24,7 +23,6 @@
|
|||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--no-restore",
|
|
||||||
"osu.Game.Rulesets.Taiko.Tests.csproj",
|
"osu.Game.Rulesets.Taiko.Tests.csproj",
|
||||||
"-p:Configuration=Release",
|
"-p:Configuration=Release",
|
||||||
"-p:GenerateFullPaths=true",
|
"-p:GenerateFullPaths=true",
|
||||||
@ -33,15 +31,6 @@
|
|||||||
],
|
],
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"problemMatcher": "$msCompile"
|
"problemMatcher": "$msCompile"
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Restore",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "dotnet",
|
|
||||||
"args": [
|
|
||||||
"restore"
|
|
||||||
],
|
|
||||||
"problemMatcher": []
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -23,17 +23,15 @@ using osu.Game.Rulesets.Osu;
|
|||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens;
|
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Play.PlayerSettings;
|
using osu.Game.Screens.Play.PlayerSettings;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
{
|
{
|
||||||
public class TestScenePlayerLoader : OsuManualInputManagerTestScene
|
public class TestScenePlayerLoader : ScreenTestScene
|
||||||
{
|
{
|
||||||
private TestPlayerLoader loader;
|
private TestPlayerLoader loader;
|
||||||
private TestPlayerLoaderContainer container;
|
|
||||||
private TestPlayer player;
|
private TestPlayer player;
|
||||||
|
|
||||||
private bool epilepsyWarning;
|
private bool epilepsyWarning;
|
||||||
@ -44,21 +42,46 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private SessionStatics sessionStatics { get; set; }
|
private SessionStatics sessionStatics { get; set; }
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private readonly NotificationOverlay notificationOverlay;
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private readonly VolumeOverlay volumeOverlay;
|
||||||
|
|
||||||
|
private readonly ChangelogOverlay changelogOverlay;
|
||||||
|
|
||||||
|
public TestScenePlayerLoader()
|
||||||
|
{
|
||||||
|
AddRange(new Drawable[]
|
||||||
|
{
|
||||||
|
notificationOverlay = new NotificationOverlay
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
},
|
||||||
|
volumeOverlay = new VolumeOverlay
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
},
|
||||||
|
changelogOverlay = new ChangelogOverlay()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
player = null;
|
||||||
|
audioManager.Volume.SetDefault();
|
||||||
|
});
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the input manager child to a new test player loader container instance.
|
/// Sets the input manager child to a new test player loader container instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="interactive">If the test player should behave like the production one.</param>
|
/// <param name="interactive">If the test player should behave like the production one.</param>
|
||||||
/// <param name="beforeLoadAction">An action to run before player load but after bindable leases are returned.</param>
|
/// <param name="beforeLoadAction">An action to run before player load but after bindable leases are returned.</param>
|
||||||
public void ResetPlayer(bool interactive, Action beforeLoadAction = null)
|
private void resetPlayer(bool interactive, Action beforeLoadAction = null)
|
||||||
{
|
{
|
||||||
player = null;
|
|
||||||
|
|
||||||
audioManager.Volume.SetDefault();
|
|
||||||
|
|
||||||
InputManager.Clear();
|
|
||||||
|
|
||||||
container = new TestPlayerLoaderContainer(loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive)));
|
|
||||||
|
|
||||||
beforeLoadAction?.Invoke();
|
beforeLoadAction?.Invoke();
|
||||||
|
|
||||||
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||||
@ -67,13 +90,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
foreach (var mod in SelectedMods.Value.OfType<IApplicableToTrack>())
|
foreach (var mod in SelectedMods.Value.OfType<IApplicableToTrack>())
|
||||||
mod.ApplyToTrack(Beatmap.Value.Track);
|
mod.ApplyToTrack(Beatmap.Value.Track);
|
||||||
|
|
||||||
InputManager.Child = container;
|
LoadScreen(loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive)));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestEarlyExitBeforePlayerConstruction()
|
public void TestEarlyExitBeforePlayerConstruction()
|
||||||
{
|
{
|
||||||
AddStep("load dummy beatmap", () => ResetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() }));
|
AddStep("load dummy beatmap", () => resetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() }));
|
||||||
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
||||||
AddStep("exit loader", () => loader.Exit());
|
AddStep("exit loader", () => loader.Exit());
|
||||||
AddUntilStep("wait for not current", () => !loader.IsCurrentScreen());
|
AddUntilStep("wait for not current", () => !loader.IsCurrentScreen());
|
||||||
@ -90,7 +113,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestEarlyExitAfterPlayerConstruction()
|
public void TestEarlyExitAfterPlayerConstruction()
|
||||||
{
|
{
|
||||||
AddStep("load dummy beatmap", () => ResetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() }));
|
AddStep("load dummy beatmap", () => resetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() }));
|
||||||
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
||||||
AddAssert("mod rate applied", () => Beatmap.Value.Track.Rate != 1);
|
AddAssert("mod rate applied", () => Beatmap.Value.Track.Rate != 1);
|
||||||
AddUntilStep("wait for non-null player", () => player != null);
|
AddUntilStep("wait for non-null player", () => player != null);
|
||||||
@ -104,7 +127,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestBlockLoadViaMouseMovement()
|
public void TestBlockLoadViaMouseMovement()
|
||||||
{
|
{
|
||||||
AddStep("load dummy beatmap", () => ResetPlayer(false));
|
AddStep("load dummy beatmap", () => resetPlayer(false));
|
||||||
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
||||||
|
|
||||||
AddUntilStep("wait for load ready", () =>
|
AddUntilStep("wait for load ready", () =>
|
||||||
@ -129,20 +152,18 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestBlockLoadViaFocus()
|
public void TestBlockLoadViaFocus()
|
||||||
{
|
{
|
||||||
OsuFocusedOverlayContainer overlay = null;
|
AddStep("load dummy beatmap", () => resetPlayer(false));
|
||||||
|
|
||||||
AddStep("load dummy beatmap", () => ResetPlayer(false));
|
|
||||||
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
||||||
|
|
||||||
AddStep("show focused overlay", () => { container.Add(overlay = new ChangelogOverlay { State = { Value = Visibility.Visible } }); });
|
AddStep("show focused overlay", () => changelogOverlay.Show());
|
||||||
AddUntilStep("overlay visible", () => overlay.IsPresent);
|
AddUntilStep("overlay visible", () => changelogOverlay.IsPresent);
|
||||||
|
|
||||||
AddUntilStep("wait for load ready", () => player.LoadState == LoadState.Ready);
|
AddUntilStep("wait for load ready", () => player?.LoadState == LoadState.Ready);
|
||||||
AddRepeatStep("twiddle thumbs", () => { }, 20);
|
AddRepeatStep("twiddle thumbs", () => { }, 20);
|
||||||
|
|
||||||
AddAssert("loader still active", () => loader.IsCurrentScreen());
|
AddAssert("loader still active", () => loader.IsCurrentScreen());
|
||||||
|
|
||||||
AddStep("hide overlay", () => overlay.Hide());
|
AddStep("hide overlay", () => changelogOverlay.Hide());
|
||||||
AddUntilStep("loads after idle", () => !loader.IsCurrentScreen());
|
AddUntilStep("loads after idle", () => !loader.IsCurrentScreen());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,15 +172,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
SlowLoadPlayer slowPlayer = null;
|
SlowLoadPlayer slowPlayer = null;
|
||||||
|
|
||||||
AddStep("load dummy beatmap", () => ResetPlayer(false));
|
|
||||||
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
|
||||||
AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre));
|
|
||||||
AddUntilStep("wait for player to be current", () => player.IsCurrentScreen());
|
|
||||||
AddStep("load slow dummy beatmap", () =>
|
AddStep("load slow dummy beatmap", () =>
|
||||||
{
|
{
|
||||||
InputManager.Child = container = new TestPlayerLoaderContainer(
|
LoadScreen(loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false)));
|
||||||
loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false)));
|
|
||||||
|
|
||||||
Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000);
|
Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -173,7 +188,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
TestMod playerMod1 = null;
|
TestMod playerMod1 = null;
|
||||||
TestMod playerMod2 = null;
|
TestMod playerMod2 = null;
|
||||||
|
|
||||||
AddStep("load player", () => { ResetPlayer(true, () => SelectedMods.Value = new[] { gameMod = new TestMod() }); });
|
AddStep("load player", () => { resetPlayer(true, () => SelectedMods.Value = new[] { gameMod = new TestMod() }); });
|
||||||
|
|
||||||
AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen());
|
AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen());
|
||||||
AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre));
|
AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre));
|
||||||
@ -201,7 +216,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
var testMod = new TestMod();
|
var testMod = new TestMod();
|
||||||
|
|
||||||
AddStep("load player", () => ResetPlayer(true));
|
AddStep("load player", () => resetPlayer(true));
|
||||||
|
|
||||||
AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen());
|
AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen());
|
||||||
AddStep("set test mod in loader", () => loader.Mods.Value = new[] { testMod });
|
AddStep("set test mod in loader", () => loader.Mods.Value = new[] { testMod });
|
||||||
@ -223,7 +238,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestMutedNotificationMuteButton()
|
public void TestMutedNotificationMuteButton()
|
||||||
{
|
{
|
||||||
addVolumeSteps("mute button", () => container.VolumeOverlay.IsMuted.Value = true, () => !container.VolumeOverlay.IsMuted.Value);
|
addVolumeSteps("mute button", () => volumeOverlay.IsMuted.Value = true, () => !volumeOverlay.IsMuted.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
@ -236,13 +251,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
AddStep("reset notification lock", () => sessionStatics.GetBindable<bool>(Static.MutedAudioNotificationShownOnce).Value = false);
|
AddStep("reset notification lock", () => sessionStatics.GetBindable<bool>(Static.MutedAudioNotificationShownOnce).Value = false);
|
||||||
|
|
||||||
AddStep("load player", () => ResetPlayer(false, beforeLoad));
|
AddStep("load player", () => resetPlayer(false, beforeLoad));
|
||||||
AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready);
|
AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready);
|
||||||
|
|
||||||
AddAssert("check for notification", () => container.NotificationOverlay.UnreadCount.Value == 1);
|
AddAssert("check for notification", () => notificationOverlay.UnreadCount.Value == 1);
|
||||||
AddStep("click notification", () =>
|
AddStep("click notification", () =>
|
||||||
{
|
{
|
||||||
var scrollContainer = (OsuScrollContainer)container.NotificationOverlay.Children.Last();
|
var scrollContainer = (OsuScrollContainer)notificationOverlay.Children.Last();
|
||||||
var flowContainer = scrollContainer.Children.OfType<FillFlowContainer<NotificationSection>>().First();
|
var flowContainer = scrollContainer.Children.OfType<FillFlowContainer<NotificationSection>>().First();
|
||||||
var notification = flowContainer.First();
|
var notification = flowContainer.First();
|
||||||
|
|
||||||
@ -260,7 +275,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
public void TestEpilepsyWarning(bool warning)
|
public void TestEpilepsyWarning(bool warning)
|
||||||
{
|
{
|
||||||
AddStep("change epilepsy warning", () => epilepsyWarning = warning);
|
AddStep("change epilepsy warning", () => epilepsyWarning = warning);
|
||||||
AddStep("load dummy beatmap", () => ResetPlayer(false));
|
AddStep("load dummy beatmap", () => resetPlayer(false));
|
||||||
|
|
||||||
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
||||||
|
|
||||||
@ -277,7 +292,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
public void TestEpilepsyWarningEarlyExit()
|
public void TestEpilepsyWarningEarlyExit()
|
||||||
{
|
{
|
||||||
AddStep("set epilepsy warning", () => epilepsyWarning = true);
|
AddStep("set epilepsy warning", () => epilepsyWarning = true);
|
||||||
AddStep("load dummy beatmap", () => ResetPlayer(false));
|
AddStep("load dummy beatmap", () => resetPlayer(false));
|
||||||
|
|
||||||
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
||||||
|
|
||||||
@ -287,42 +302,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddUntilStep("sound volume restored", () => Beatmap.Value.Track.AggregateVolume.Value == 1);
|
AddUntilStep("sound volume restored", () => Beatmap.Value.Track.AggregateVolume.Value == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestPlayerLoaderContainer : Container
|
|
||||||
{
|
|
||||||
[Cached]
|
|
||||||
public readonly NotificationOverlay NotificationOverlay;
|
|
||||||
|
|
||||||
[Cached]
|
|
||||||
public readonly VolumeOverlay VolumeOverlay;
|
|
||||||
|
|
||||||
public TestPlayerLoaderContainer(IScreen screen)
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
|
||||||
|
|
||||||
OsuScreenStack stack;
|
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
|
||||||
{
|
|
||||||
stack = new OsuScreenStack
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
},
|
|
||||||
NotificationOverlay = new NotificationOverlay
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopRight,
|
|
||||||
Origin = Anchor.TopRight,
|
|
||||||
},
|
|
||||||
VolumeOverlay = new VolumeOverlay
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopLeft,
|
|
||||||
Origin = Anchor.TopLeft,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
stack.Push(screen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestPlayerLoader : PlayerLoader
|
private class TestPlayerLoader : PlayerLoader
|
||||||
{
|
{
|
||||||
public new VisualSettings VisualSettings => base.VisualSettings;
|
public new VisualSettings VisualSettings => base.VisualSettings;
|
||||||
|
11
osu.Game.Tournament.Tests/.vscode/tasks.json
vendored
11
osu.Game.Tournament.Tests/.vscode/tasks.json
vendored
@ -9,7 +9,6 @@
|
|||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--no-restore",
|
|
||||||
"osu.Game.Tournament.Tests.csproj",
|
"osu.Game.Tournament.Tests.csproj",
|
||||||
"-p:GenerateFullPaths=true",
|
"-p:GenerateFullPaths=true",
|
||||||
"-m",
|
"-m",
|
||||||
@ -24,7 +23,6 @@
|
|||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--no-restore",
|
|
||||||
"osu.Game.Tournament.Tests.csproj",
|
"osu.Game.Tournament.Tests.csproj",
|
||||||
"-p:Configuration=Release",
|
"-p:Configuration=Release",
|
||||||
"-p:GenerateFullPaths=true",
|
"-p:GenerateFullPaths=true",
|
||||||
@ -33,15 +31,6 @@
|
|||||||
],
|
],
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"problemMatcher": "$msCompile"
|
"problemMatcher": "$msCompile"
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Restore",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "dotnet",
|
|
||||||
"args": [
|
|
||||||
"restore"
|
|
||||||
],
|
|
||||||
"problemMatcher": []
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -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 System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -15,103 +14,93 @@ namespace osu.Game.Database
|
|||||||
{
|
{
|
||||||
public class UserLookupCache : MemoryCachingComponent<int, User>
|
public class UserLookupCache : MemoryCachingComponent<int, User>
|
||||||
{
|
{
|
||||||
private readonly HashSet<int> nextTaskIDs = new HashSet<int>();
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IAPIProvider api { get; set; }
|
private IAPIProvider api { get; set; }
|
||||||
|
|
||||||
private readonly object taskAssignmentLock = new object();
|
|
||||||
|
|
||||||
private Task<List<User>> pendingRequest;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether <see cref="pendingRequest"/> has already grabbed its IDs.
|
|
||||||
/// </summary>
|
|
||||||
private bool pendingRequestConsumedIDs;
|
|
||||||
|
|
||||||
public Task<User> GetUserAsync(int userId, CancellationToken token = default) => GetAsync(userId, token);
|
public Task<User> GetUserAsync(int userId, CancellationToken token = default) => GetAsync(userId, token);
|
||||||
|
|
||||||
protected override async Task<User> ComputeValueAsync(int lookup, CancellationToken token = default)
|
protected override async Task<User> ComputeValueAsync(int lookup, CancellationToken token = default)
|
||||||
{
|
=> await queryUser(lookup);
|
||||||
var users = await getQueryTaskForUser(lookup);
|
|
||||||
return users.FirstOrDefault(u => u.Id == lookup);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
private readonly Queue<(int id, TaskCompletionSource<User>)> pendingUserTasks = new Queue<(int, TaskCompletionSource<User>)>();
|
||||||
/// Return the task responsible for fetching the provided user.
|
private Task pendingRequestTask;
|
||||||
/// This may be part of a larger batch lookup to reduce web requests.
|
private readonly object taskAssignmentLock = new object();
|
||||||
/// </summary>
|
|
||||||
/// <param name="userId">The user to lookup.</param>
|
private Task<User> queryUser(int userId)
|
||||||
/// <returns>The task responsible for the lookup.</returns>
|
|
||||||
private Task<List<User>> getQueryTaskForUser(int userId)
|
|
||||||
{
|
{
|
||||||
lock (taskAssignmentLock)
|
lock (taskAssignmentLock)
|
||||||
{
|
{
|
||||||
nextTaskIDs.Add(userId);
|
var tcs = new TaskCompletionSource<User>();
|
||||||
|
|
||||||
// if there's a pending request which hasn't been started yet (and is not yet full), we can wait on it.
|
// Add to the queue.
|
||||||
if (pendingRequest != null && !pendingRequestConsumedIDs && nextTaskIDs.Count < 50)
|
pendingUserTasks.Enqueue((userId, tcs));
|
||||||
return pendingRequest;
|
|
||||||
|
|
||||||
return queueNextTask(nextLookup);
|
// Create a request task if there's not already one.
|
||||||
|
if (pendingRequestTask == null)
|
||||||
|
createNewTask();
|
||||||
|
|
||||||
|
return tcs.Task;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<User> nextLookup()
|
private void performLookup()
|
||||||
|
{
|
||||||
|
// contains at most 50 unique user IDs from userTasks, which is used to perform the lookup.
|
||||||
|
var userTasks = new Dictionary<int, List<TaskCompletionSource<User>>>();
|
||||||
|
|
||||||
|
// Grab at most 50 unique user IDs from the queue.
|
||||||
|
lock (taskAssignmentLock)
|
||||||
{
|
{
|
||||||
int[] lookupItems;
|
while (pendingUserTasks.Count > 0 && userTasks.Count < 50)
|
||||||
|
|
||||||
lock (taskAssignmentLock)
|
|
||||||
{
|
{
|
||||||
pendingRequestConsumedIDs = true;
|
(int id, TaskCompletionSource<User> task) next = pendingUserTasks.Dequeue();
|
||||||
lookupItems = nextTaskIDs.ToArray();
|
|
||||||
nextTaskIDs.Clear();
|
|
||||||
|
|
||||||
if (lookupItems.Length == 0)
|
// Perform a secondary check for existence, in case the user was queried in a previous batch.
|
||||||
|
if (CheckExists(next.id, out var existing))
|
||||||
|
next.task.SetResult(existing);
|
||||||
|
else
|
||||||
{
|
{
|
||||||
queueNextTask(null);
|
if (userTasks.TryGetValue(next.id, out var tasks))
|
||||||
return new List<User>();
|
tasks.Add(next.task);
|
||||||
|
else
|
||||||
|
userTasks[next.id] = new List<TaskCompletionSource<User>> { next.task };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var request = new GetUsersRequest(lookupItems);
|
|
||||||
|
|
||||||
// rather than queueing, we maintain our own single-threaded request stream.
|
|
||||||
api.Perform(request);
|
|
||||||
|
|
||||||
return request.Result?.Users;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
// Query the users.
|
||||||
/// Queues new work at the end of the current work tasks.
|
var request = new GetUsersRequest(userTasks.Keys.ToArray());
|
||||||
/// Ensures the provided work is eventually run.
|
|
||||||
/// </summary>
|
// rather than queueing, we maintain our own single-threaded request stream.
|
||||||
/// <param name="work">The work to run. Can be null to signify the end of available work.</param>
|
api.Perform(request);
|
||||||
/// <returns>The task tracking this work.</returns>
|
|
||||||
private Task<List<User>> queueNextTask(Func<List<User>> work)
|
// Create a new request task if there's still more users to query.
|
||||||
{
|
|
||||||
lock (taskAssignmentLock)
|
lock (taskAssignmentLock)
|
||||||
{
|
{
|
||||||
if (work == null)
|
pendingRequestTask = null;
|
||||||
{
|
if (pendingUserTasks.Count > 0)
|
||||||
pendingRequest = null;
|
createNewTask();
|
||||||
pendingRequestConsumedIDs = false;
|
}
|
||||||
}
|
|
||||||
else if (pendingRequest == null)
|
|
||||||
{
|
|
||||||
// special case for the first request ever.
|
|
||||||
pendingRequest = Task.Run(work);
|
|
||||||
pendingRequestConsumedIDs = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// append the new request on to the last to be executed.
|
|
||||||
pendingRequest = pendingRequest.ContinueWith(_ => work());
|
|
||||||
pendingRequestConsumedIDs = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pendingRequest;
|
foreach (var user in request.Result.Users)
|
||||||
|
{
|
||||||
|
if (userTasks.TryGetValue(user.Id, out var tasks))
|
||||||
|
{
|
||||||
|
foreach (var task in tasks)
|
||||||
|
task.SetResult(user);
|
||||||
|
|
||||||
|
userTasks.Remove(user.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if any tasks remain which were not satisfied, return null.
|
||||||
|
foreach (var tasks in userTasks.Values)
|
||||||
|
{
|
||||||
|
foreach (var task in tasks)
|
||||||
|
task.SetResult(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void createNewTask() => pendingRequestTask = Task.Run(performLookup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -194,6 +194,20 @@ namespace osu.Game
|
|||||||
dependencies.Cache(SkinManager = new SkinManager(Storage, contextFactory, Host, Audio, new NamespacedResourceStore<byte[]>(Resources, "Skins/Legacy")));
|
dependencies.Cache(SkinManager = new SkinManager(Storage, contextFactory, Host, Audio, new NamespacedResourceStore<byte[]>(Resources, "Skins/Legacy")));
|
||||||
dependencies.CacheAs<ISkinSource>(SkinManager);
|
dependencies.CacheAs<ISkinSource>(SkinManager);
|
||||||
|
|
||||||
|
// needs to be done here rather than inside SkinManager to ensure thread safety of CurrentSkinInfo.
|
||||||
|
SkinManager.ItemRemoved.BindValueChanged(weakRemovedInfo =>
|
||||||
|
{
|
||||||
|
if (weakRemovedInfo.NewValue.TryGetTarget(out var removedInfo))
|
||||||
|
{
|
||||||
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
// check the removed skin is not the current user choice. if it is, switch back to default.
|
||||||
|
if (removedInfo.ID == SkinManager.CurrentSkinInfo.Value.ID)
|
||||||
|
SkinManager.CurrentSkinInfo.Value = SkinInfo.Default;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
dependencies.CacheAs(API ??= new APIAccess(LocalConfig));
|
dependencies.CacheAs(API ??= new APIAccess(LocalConfig));
|
||||||
|
|
||||||
dependencies.CacheAs(spectatorStreaming = new SpectatorStreamingClient());
|
dependencies.CacheAs(spectatorStreaming = new SpectatorStreamingClient());
|
||||||
|
@ -201,6 +201,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
// Copy any existing result from the entry (required for rewind / judgement revert).
|
// Copy any existing result from the entry (required for rewind / judgement revert).
|
||||||
Result = lifetimeEntry.Result;
|
Result = lifetimeEntry.Result;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
LifetimeStart = HitObject.StartTime - InitialLifetimeOffset;
|
||||||
|
|
||||||
// Ensure this DHO has a result.
|
// Ensure this DHO has a result.
|
||||||
Result ??= CreateResult(HitObject.CreateJudgement())
|
Result ??= CreateResult(HitObject.CreateJudgement())
|
||||||
@ -646,6 +648,10 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
/// This is only used as an optimisation to delay the initial update of this <see cref="DrawableHitObject"/> and may be tuned more aggressively if required.
|
/// This is only used as an optimisation to delay the initial update of this <see cref="DrawableHitObject"/> and may be tuned more aggressively if required.
|
||||||
/// It is indirectly used to decide the automatic transform offset provided to <see cref="UpdateInitialTransforms"/>.
|
/// It is indirectly used to decide the automatic transform offset provided to <see cref="UpdateInitialTransforms"/>.
|
||||||
/// A more accurate <see cref="LifetimeStart"/> should be set for further optimisation (in <see cref="LoadComplete"/>, for example).
|
/// A more accurate <see cref="LifetimeStart"/> should be set for further optimisation (in <see cref="LoadComplete"/>, for example).
|
||||||
|
/// <para>
|
||||||
|
/// Only has an effect if this <see cref="DrawableHitObject"/> is not being pooled.
|
||||||
|
/// For pooled <see cref="DrawableHitObject"/>s, use <see cref="HitObjectLifetimeEntry.InitialLifetimeOffset"/> instead.
|
||||||
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected virtual double InitialLifetimeOffset => 10000;
|
protected virtual double InitialLifetimeOffset => 10000;
|
||||||
|
|
||||||
|
@ -10,14 +10,6 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
{
|
{
|
||||||
public static class SliderEventGenerator
|
public static class SliderEventGenerator
|
||||||
{
|
{
|
||||||
[Obsolete("Use the overload with cancellation support instead.")] // can be removed 20201115
|
|
||||||
// ReSharper disable once RedundantOverload.Global
|
|
||||||
public static IEnumerable<SliderEventDescriptor> Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount,
|
|
||||||
double? legacyLastTickOffset)
|
|
||||||
{
|
|
||||||
return Generate(startTime, spanDuration, velocity, tickDistance, totalDistance, spanCount, legacyLastTickOffset, default);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReSharper disable once MethodOverloadWithOptionalParameter
|
// ReSharper disable once MethodOverloadWithOptionalParameter
|
||||||
public static IEnumerable<SliderEventDescriptor> Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount,
|
public static IEnumerable<SliderEventDescriptor> Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount,
|
||||||
double? legacyLastTickOffset, CancellationToken cancellationToken = default)
|
double? legacyLastTickOffset, CancellationToken cancellationToken = default)
|
||||||
|
@ -218,9 +218,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
#region Pooling support
|
#region Pooling support
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
|
||||||
private IPooledHitObjectProvider parentPooledObjectProvider { get; set; }
|
|
||||||
|
|
||||||
private readonly Dictionary<Type, IDrawablePool> pools = new Dictionary<Type, IDrawablePool>();
|
private readonly Dictionary<Type, IDrawablePool> pools = new Dictionary<Type, IDrawablePool>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -320,10 +317,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pool == null)
|
return (DrawableHitObject)pool?.Get(d =>
|
||||||
return parentPooledObjectProvider?.GetPooledDrawableRepresentation(hitObject);
|
|
||||||
|
|
||||||
return (DrawableHitObject)pool.Get(d =>
|
|
||||||
{
|
{
|
||||||
var dho = (DrawableHitObject)d;
|
var dho = (DrawableHitObject)d;
|
||||||
|
|
||||||
|
@ -118,8 +118,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual Container<SelectionBlueprint> CreateSelectionBlueprintContainer() =>
|
protected virtual Container<SelectionBlueprint> CreateSelectionBlueprintContainer() => new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both };
|
||||||
new Container<SelectionBlueprint> { RelativeSizeAxes = Axes.Both };
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a <see cref="Components.SelectionHandler"/> which outlines <see cref="DrawableHitObject"/>s and handles movement of selections.
|
/// Creates a <see cref="Components.SelectionHandler"/> which outlines <see cref="DrawableHitObject"/>s and handles movement of selections.
|
||||||
@ -338,7 +337,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// <returns>Whether a selection was performed.</returns>
|
/// <returns>Whether a selection was performed.</returns>
|
||||||
private bool beginClickSelection(MouseButtonEvent e)
|
private bool beginClickSelection(MouseButtonEvent e)
|
||||||
{
|
{
|
||||||
foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren)
|
// Iterate from the top of the input stack (blueprints closest to the front of the screen first).
|
||||||
|
foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Reverse())
|
||||||
{
|
{
|
||||||
if (!blueprint.IsHovered) continue;
|
if (!blueprint.IsHovered) continue;
|
||||||
|
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
// 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.Collections.Generic;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Compose.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A container for <see cref="SelectionBlueprint"/> ordered by their <see cref="HitObject"/> start times.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class HitObjectOrderedSelectionContainer : Container<SelectionBlueprint>
|
||||||
|
{
|
||||||
|
public override void Add(SelectionBlueprint drawable)
|
||||||
|
{
|
||||||
|
base.Add(drawable);
|
||||||
|
bindStartTime(drawable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Remove(SelectionBlueprint drawable)
|
||||||
|
{
|
||||||
|
if (!base.Remove(drawable))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
unbindStartTime(drawable);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Clear(bool disposeChildren)
|
||||||
|
{
|
||||||
|
base.Clear(disposeChildren);
|
||||||
|
unbindAllStartTimes();
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Dictionary<SelectionBlueprint, IBindable> startTimeMap = new Dictionary<SelectionBlueprint, IBindable>();
|
||||||
|
|
||||||
|
private void bindStartTime(SelectionBlueprint blueprint)
|
||||||
|
{
|
||||||
|
var bindable = blueprint.HitObject.StartTimeBindable.GetBoundCopy();
|
||||||
|
|
||||||
|
bindable.BindValueChanged(_ =>
|
||||||
|
{
|
||||||
|
if (LoadState >= LoadState.Ready)
|
||||||
|
SortInternal();
|
||||||
|
});
|
||||||
|
|
||||||
|
startTimeMap[blueprint] = bindable;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unbindStartTime(SelectionBlueprint blueprint)
|
||||||
|
{
|
||||||
|
startTimeMap[blueprint].UnbindAll();
|
||||||
|
startTimeMap.Remove(blueprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unbindAllStartTimes()
|
||||||
|
{
|
||||||
|
foreach (var kvp in startTimeMap)
|
||||||
|
kvp.Value.UnbindAll();
|
||||||
|
startTimeMap.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int Compare(Drawable x, Drawable y)
|
||||||
|
{
|
||||||
|
var xObj = (SelectionBlueprint)x;
|
||||||
|
var yObj = (SelectionBlueprint)y;
|
||||||
|
|
||||||
|
// Put earlier blueprints towards the end of the list, so they handle input first
|
||||||
|
int i = yObj.HitObject.StartTime.CompareTo(xObj.HitObject.StartTime);
|
||||||
|
return i == 0 ? CompareReverseChildID(x, y) : i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -201,7 +201,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
public TimelineSelectionBlueprintContainer()
|
public TimelineSelectionBlueprintContainer()
|
||||||
{
|
{
|
||||||
AddInternal(new TimelinePart<SelectionBlueprint>(Content = new Container<SelectionBlueprint> { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both });
|
AddInternal(new TimelinePart<SelectionBlueprint>(Content = new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,16 +48,6 @@ namespace osu.Game.Skinning
|
|||||||
this.audio = audio;
|
this.audio = audio;
|
||||||
this.legacyDefaultResources = legacyDefaultResources;
|
this.legacyDefaultResources = legacyDefaultResources;
|
||||||
|
|
||||||
ItemRemoved.BindValueChanged(weakRemovedInfo =>
|
|
||||||
{
|
|
||||||
if (weakRemovedInfo.NewValue.TryGetTarget(out var removedInfo))
|
|
||||||
{
|
|
||||||
// check the removed skin is not the current user choice. if it is, switch back to default.
|
|
||||||
if (removedInfo.ID == CurrentSkinInfo.Value.ID)
|
|
||||||
CurrentSkinInfo.Value = SkinInfo.Default;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = GetSkin(skin.NewValue);
|
CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = GetSkin(skin.NewValue);
|
||||||
CurrentSkin.ValueChanged += skin =>
|
CurrentSkin.ValueChanged += skin =>
|
||||||
{
|
{
|
||||||
|
@ -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.1113.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2020.1118.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.1113.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.1118.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.1113.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2020.1118.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