mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 11:37:28 +08:00
Merge remote-tracking branch 'upstream/master' into android-build-automation
This commit is contained in:
commit
e97aa60487
@ -51,7 +51,6 @@
|
|||||||
<None Include="$(MSBuildThisFileDirectory)\osu.licenseheader">
|
<None Include="$(MSBuildThisFileDirectory)\osu.licenseheader">
|
||||||
<Link>osu.licenseheader</Link>
|
<Link>osu.licenseheader</Link>
|
||||||
</None>
|
</None>
|
||||||
<AndroidNativeLibrary Include="$(OutputPath)\**\*.so" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
@ -63,6 +62,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.913.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.913.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.911.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.921.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -23,10 +23,10 @@
|
|||||||
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" />
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
||||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.5.0" />
|
<PackageReference Include="Microsoft.Win32.Registry" Version="4.6.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="System.IO.Packaging" Version="4.5.0" />
|
<PackageReference Include="System.IO.Packaging" Version="4.6.0" />
|
||||||
<PackageReference Include="ppy.squirrel.windows" Version="1.9.0.4" />
|
<PackageReference Include="ppy.squirrel.windows" Version="1.9.0.4" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -37,9 +37,6 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
float lastPosition = 0.5f;
|
float lastPosition = 0.5f;
|
||||||
double lastTime = 0;
|
double lastTime = 0;
|
||||||
|
|
||||||
// Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled
|
|
||||||
addFrame(-100000, lastPosition);
|
|
||||||
|
|
||||||
void moveToNext(CatchHitObject h)
|
void moveToNext(CatchHitObject h)
|
||||||
{
|
{
|
||||||
float positionChange = Math.Abs(lastPosition - h.X);
|
float positionChange = Math.Abs(lastPosition - h.X);
|
||||||
@ -127,6 +124,10 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
|
|
||||||
private void addFrame(double time, float? position = null, bool dashing = false)
|
private void addFrame(double time, float? position = null, bool dashing = false)
|
||||||
{
|
{
|
||||||
|
// todo: can be removed once FramedReplayInputHandler correctly handles rewinding before first frame.
|
||||||
|
if (Replay.Frames.Count == 0)
|
||||||
|
Replay.Frames.Add(new CatchReplayFrame(time - 1, position, false, null));
|
||||||
|
|
||||||
var last = currentFrame;
|
var last = currentFrame;
|
||||||
currentFrame = new CatchReplayFrame(time, position, dashing, last);
|
currentFrame = new CatchReplayFrame(time, position, dashing, last);
|
||||||
Replay.Frames.Add(currentFrame);
|
Replay.Frames.Add(currentFrame);
|
||||||
|
@ -16,6 +16,11 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
[HeadlessTest]
|
[HeadlessTest]
|
||||||
public class TestSceneAutoGeneration : OsuTestScene
|
public class TestSceneAutoGeneration : OsuTestScene
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The number of frames which are generated at the start of a replay regardless of hitobject content.
|
||||||
|
/// </summary>
|
||||||
|
private const int frame_offset = 1;
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSingleNote()
|
public void TestSingleNote()
|
||||||
{
|
{
|
||||||
@ -28,11 +33,11 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||||
|
|
||||||
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
|
Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
|
||||||
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
|
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||||
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
|
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||||
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Special1), "Special1 has not been pressed");
|
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed");
|
||||||
Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Special1), "Special1 has not been released");
|
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -49,11 +54,11 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||||
|
|
||||||
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
|
Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
|
||||||
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
|
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||||
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
|
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||||
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Special1), "Special1 has not been pressed");
|
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed");
|
||||||
Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Special1), "Special1 has not been released");
|
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -69,11 +74,11 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||||
|
|
||||||
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
|
Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
|
||||||
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
|
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||||
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
|
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||||
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
|
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
|
||||||
Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
|
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -91,11 +96,13 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||||
|
|
||||||
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
|
Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
|
||||||
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
|
|
||||||
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
|
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||||
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
|
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||||
Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
|
|
||||||
|
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
|
||||||
|
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -112,15 +119,15 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||||
|
|
||||||
Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames");
|
Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames");
|
||||||
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
|
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
|
||||||
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect first note release time");
|
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect first note release time");
|
||||||
Assert.AreEqual(2000, generated.Frames[3].Time, "Incorrect second note hit time");
|
Assert.AreEqual(2000, generated.Frames[frame_offset + 2].Time, "Incorrect second note hit time");
|
||||||
Assert.AreEqual(2000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time");
|
Assert.AreEqual(2000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 3].Time, "Incorrect second note release time");
|
||||||
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed");
|
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
|
||||||
Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1), "Key1 has not been released");
|
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released");
|
||||||
Assert.IsTrue(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has not been pressed");
|
Assert.IsTrue(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key2), "Key2 has not been pressed");
|
||||||
Assert.IsFalse(checkContains(generated.Frames[4], ManiaAction.Key2), "Key2 has not been released");
|
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 3], ManiaAction.Key2), "Key2 has not been released");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -139,16 +146,16 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||||
|
|
||||||
Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames");
|
Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames");
|
||||||
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
|
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
|
||||||
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect first note release time");
|
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 2].Time, "Incorrect first note release time");
|
||||||
Assert.AreEqual(2000, generated.Frames[2].Time, "Incorrect second note hit time");
|
Assert.AreEqual(2000, generated.Frames[frame_offset + 1].Time, "Incorrect second note hit time");
|
||||||
Assert.AreEqual(4000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time");
|
Assert.AreEqual(4000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 3].Time, "Incorrect second note release time");
|
||||||
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed");
|
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
|
||||||
Assert.IsTrue(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
|
Assert.IsTrue(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
|
||||||
Assert.IsFalse(checkContains(generated.Frames[3], ManiaAction.Key1), "Key1 has not been released");
|
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key1), "Key1 has not been released");
|
||||||
Assert.IsTrue(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has been released");
|
Assert.IsTrue(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key2), "Key2 has been released");
|
||||||
Assert.IsFalse(checkContains(generated.Frames[4], ManiaAction.Key2), "Key2 has not been released");
|
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 3], ManiaAction.Key2), "Key2 has not been released");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -166,14 +173,14 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||||
|
|
||||||
Assert.IsTrue(generated.Frames.Count == 4, "Replay must have 4 frames");
|
Assert.IsTrue(generated.Frames.Count == frame_offset + 3, "Replay must have 3 generated frames");
|
||||||
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
|
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
|
||||||
Assert.AreEqual(3000, generated.Frames[2].Time, "Incorrect second note press time + first note release time");
|
Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect second note press time + first note release time");
|
||||||
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect second note release time");
|
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 2].Time, "Incorrect second note release time");
|
||||||
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed");
|
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
|
||||||
Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1), "Key1 has not been released");
|
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released");
|
||||||
Assert.IsTrue(checkContains(generated.Frames[2], ManiaAction.Key2), "Key2 has not been pressed");
|
Assert.IsTrue(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key2), "Key2 has not been pressed");
|
||||||
Assert.IsFalse(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has not been released");
|
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key2), "Key2 has not been released");
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool checkContains(ReplayFrame frame, params ManiaAction[] actions) => actions.All(action => ((ManiaReplayFrame)frame).Actions.Contains(action));
|
private bool checkContains(ReplayFrame frame, params ManiaAction[] actions) => actions.All(action => ((ManiaReplayFrame)frame).Actions.Contains(action));
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -18,8 +18,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class DrawableNote : DrawableManiaHitObject<Note>, IKeyBindingHandler<ManiaAction>
|
public class DrawableNote : DrawableManiaHitObject<Note>, IKeyBindingHandler<ManiaAction>
|
||||||
{
|
{
|
||||||
public const float CORNER_RADIUS = NotePiece.NOTE_HEIGHT / 2;
|
|
||||||
|
|
||||||
private readonly NotePiece headPiece;
|
private readonly NotePiece headPiece;
|
||||||
|
|
||||||
public DrawableNote(Note hitObject)
|
public DrawableNote(Note hitObject)
|
||||||
|
@ -47,9 +47,6 @@ namespace osu.Game.Rulesets.Mania.Replays
|
|||||||
|
|
||||||
public override Replay Generate()
|
public override Replay Generate()
|
||||||
{
|
{
|
||||||
// Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled
|
|
||||||
Replay.Frames.Add(new ManiaReplayFrame(-100000, 0));
|
|
||||||
|
|
||||||
var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time);
|
var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time);
|
||||||
|
|
||||||
var actions = new List<ManiaAction>();
|
var actions = new List<ManiaAction>();
|
||||||
@ -70,6 +67,10 @@ namespace osu.Game.Rulesets.Mania.Replays
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: can be removed once FramedReplayInputHandler correctly handles rewinding before first frame.
|
||||||
|
if (Replay.Frames.Count == 0)
|
||||||
|
Replay.Frames.Add(new ManiaReplayFrame(group.First().Time - 1));
|
||||||
|
|
||||||
Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, actions.ToArray()));
|
Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, actions.ToArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (int i = 0; i < 512; i++)
|
for (int i = 0; i < 512; i++)
|
||||||
beatmap.HitObjects.Add(new HitCircle { Position = new Vector2(256, 192), StartTime = i * 100 });
|
if (i % 32 < 20)
|
||||||
|
beatmap.HitObjects.Add(new HitCircle { Position = new Vector2(256, 192), StartTime = i * 100 });
|
||||||
|
|
||||||
return beatmap;
|
return beatmap;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) };
|
||||||
|
|
||||||
public bool AllowFail => false;
|
public bool AllowFail => false;
|
||||||
|
public bool RestartOnFail => false;
|
||||||
|
|
||||||
private OsuInputManager inputManager;
|
private OsuInputManager inputManager;
|
||||||
|
|
||||||
|
@ -17,7 +17,9 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public override string Description => @"Play with no approach circles and fading circles/sliders.";
|
public override string Description => @"Play with no approach circles and fading circles/sliders.";
|
||||||
public override double ScoreMultiplier => 1.06;
|
public override double ScoreMultiplier => 1.06;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpinIn) };
|
|
||||||
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModTraceable), typeof(OsuModSpinIn) };
|
||||||
|
|
||||||
private const double fade_in_duration_multiplier = 0.4;
|
private const double fade_in_duration_multiplier = 0.4;
|
||||||
private const double fade_out_duration_multiplier = 0.3;
|
private const double fade_out_duration_multiplier = 0.3;
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
// todo: this mod should be able to be compatible with hidden with a bit of further implementation.
|
// todo: this mod should be able to be compatible with hidden with a bit of further implementation.
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModeObjectScaleTween), typeof(OsuModHidden) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModeObjectScaleTween), typeof(OsuModHidden), typeof(OsuModTraceable) };
|
||||||
|
|
||||||
private const int rotate_offset = 360;
|
private const int rotate_offset = 360;
|
||||||
private const float rotate_starting_width = 2;
|
private const float rotate_starting_width = 2;
|
||||||
|
73
osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
Normal file
73
osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
|
{
|
||||||
|
internal class OsuModTraceable : Mod, IReadFromConfig, IApplicableToDrawableHitObjects
|
||||||
|
{
|
||||||
|
public override string Name => "Traceable";
|
||||||
|
public override string Acronym => "TC";
|
||||||
|
public override IconUsage Icon => FontAwesome.Brands.SnapchatGhost;
|
||||||
|
public override ModType Type => ModType.Fun;
|
||||||
|
public override string Description => "Put your faith in the approach circles...";
|
||||||
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden), typeof(OsuModSpinIn), typeof(OsuModeObjectScaleTween) };
|
||||||
|
private Bindable<bool> increaseFirstObjectVisibility = new Bindable<bool>();
|
||||||
|
|
||||||
|
public void ReadFromConfig(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
increaseFirstObjectVisibility = config.GetBindable<bool>(OsuSetting.IncreaseFirstObjectVisibility);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
|
||||||
|
{
|
||||||
|
foreach (var drawable in drawables.Skip(increaseFirstObjectVisibility.Value ? 1 : 0))
|
||||||
|
drawable.ApplyCustomUpdateState += ApplyTraceableState;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void ApplyTraceableState(DrawableHitObject drawable, ArmedState state)
|
||||||
|
{
|
||||||
|
if (!(drawable is DrawableOsuHitObject drawableOsu))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var h = drawableOsu.HitObject;
|
||||||
|
|
||||||
|
switch (drawable)
|
||||||
|
{
|
||||||
|
case DrawableHitCircle circle:
|
||||||
|
// we only want to see the approach circle
|
||||||
|
using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
|
||||||
|
circle.CirclePiece.Hide();
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DrawableSlider slider:
|
||||||
|
slider.AccentColour.BindValueChanged(_ =>
|
||||||
|
{
|
||||||
|
//will trigger on skin change.
|
||||||
|
slider.Body.AccentColour = slider.AccentColour.Value.Opacity(0);
|
||||||
|
slider.Body.BorderColour = slider.AccentColour.Value;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DrawableSpinner spinner:
|
||||||
|
spinner.Disc.Hide();
|
||||||
|
spinner.Background.Hide();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
private Bindable<bool> increaseFirstObjectVisibility = new Bindable<bool>();
|
private Bindable<bool> increaseFirstObjectVisibility = new Bindable<bool>();
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpinIn) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpinIn), typeof(OsuModTraceable) };
|
||||||
|
|
||||||
public void ReadFromConfig(OsuConfigManager config)
|
public void ReadFromConfig(OsuConfigManager config)
|
||||||
{
|
{
|
||||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
public class DrawableHitCircle : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach
|
public class DrawableHitCircle : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach
|
||||||
{
|
{
|
||||||
public ApproachCircle ApproachCircle;
|
public ApproachCircle ApproachCircle { get; }
|
||||||
|
|
||||||
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
|
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
|
||||||
private readonly IBindable<int> stackHeightBindable = new Bindable<int>();
|
private readonly IBindable<int> stackHeightBindable = new Bindable<int>();
|
||||||
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
private readonly HitArea hitArea;
|
private readonly HitArea hitArea;
|
||||||
|
|
||||||
private readonly SkinnableDrawable mainContent;
|
public SkinnableDrawable CirclePiece { get; }
|
||||||
|
|
||||||
public DrawableHitCircle(HitCircle h)
|
public DrawableHitCircle(HitCircle h)
|
||||||
: base(h)
|
: base(h)
|
||||||
@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mainContent = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)),
|
CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)),
|
||||||
ApproachCircle = new ApproachCircle
|
ApproachCircle = new ApproachCircle
|
||||||
{
|
{
|
||||||
Alpha = 0,
|
Alpha = 0,
|
||||||
@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
base.UpdateInitialTransforms();
|
base.UpdateInitialTransforms();
|
||||||
|
|
||||||
mainContent.FadeInFromZero(HitObject.TimeFadeIn);
|
CirclePiece.FadeInFromZero(HitObject.TimeFadeIn);
|
||||||
|
|
||||||
ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadeIn * 2, HitObject.TimePreempt));
|
ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadeIn * 2, HitObject.TimePreempt));
|
||||||
ApproachCircle.ScaleTo(1f, HitObject.TimePreempt);
|
ApproachCircle.ScaleTo(1f, HitObject.TimePreempt);
|
||||||
|
@ -1,22 +1,66 @@
|
|||||||
// 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 osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableOsuJudgement : DrawableJudgement
|
public class DrawableOsuJudgement : DrawableJudgement
|
||||||
{
|
{
|
||||||
|
private SkinnableSprite lighting;
|
||||||
|
private Bindable<Color4> lightingColour;
|
||||||
|
|
||||||
public DrawableOsuJudgement(JudgementResult result, DrawableHitObject judgedObject)
|
public DrawableOsuJudgement(JudgementResult result, DrawableHitObject judgedObject)
|
||||||
: base(result, judgedObject)
|
: base(result, judgedObject)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
if (config.Get<bool>(OsuSetting.HitLighting) && Result.Type != HitResult.Miss)
|
||||||
|
{
|
||||||
|
AddInternal(lighting = new SkinnableSprite("lighting")
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Depth = float.MaxValue
|
||||||
|
});
|
||||||
|
|
||||||
|
if (JudgedObject != null)
|
||||||
|
{
|
||||||
|
lightingColour = JudgedObject.AccentColour.GetBoundCopy();
|
||||||
|
lightingColour.BindValueChanged(colour => lighting.Colour = colour.NewValue, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lighting.Colour = Color4.White;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override double FadeOutDelay => lighting == null ? base.FadeOutDelay : 1400;
|
||||||
|
|
||||||
protected override void ApplyHitAnimations()
|
protected override void ApplyHitAnimations()
|
||||||
{
|
{
|
||||||
|
if (lighting != null)
|
||||||
|
{
|
||||||
|
JudgementBody.Delay(FadeInDuration).FadeOut(400);
|
||||||
|
|
||||||
|
lighting.ScaleTo(0.8f).ScaleTo(1.2f, 600, Easing.Out);
|
||||||
|
lighting.FadeIn(200).Then().Delay(200).FadeOut(1000);
|
||||||
|
}
|
||||||
|
|
||||||
JudgementText?.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint);
|
JudgementText?.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint);
|
||||||
base.ApplyHitAnimations();
|
base.ApplyHitAnimations();
|
||||||
}
|
}
|
||||||
|
@ -163,9 +163,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
private float sliderPathRadius;
|
private float sliderPathRadius;
|
||||||
|
|
||||||
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
protected override void ApplySkin(ISkinSource skin, bool allowFallback)
|
||||||
{
|
{
|
||||||
base.SkinChanged(skin, allowFallback);
|
base.ApplySkin(skin, allowFallback);
|
||||||
|
|
||||||
Body.BorderSize = skin.GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.SliderBorderSize)?.Value ?? SliderBody.DEFAULT_BORDER_SIZE;
|
Body.BorderSize = skin.GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.SliderBorderSize)?.Value ?? SliderBody.DEFAULT_BORDER_SIZE;
|
||||||
sliderPathRadius = skin.GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
|
sliderPathRadius = skin.GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
|
||||||
@ -173,6 +173,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
Body.AccentColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderTrackOverride)?.Value ?? AccentColour.Value;
|
Body.AccentColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderTrackOverride)?.Value ?? AccentColour.Value;
|
||||||
Body.BorderColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderBorder)?.Value ?? Color4.White;
|
Body.BorderColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderBorder)?.Value ?? Color4.White;
|
||||||
|
|
||||||
|
bool allowBallTint = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false;
|
||||||
|
Ball.Colour = allowBallTint ? AccentColour.Value : Color4.White;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius;
|
private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius;
|
||||||
|
@ -139,6 +139,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
new OsuModSpinIn(),
|
new OsuModSpinIn(),
|
||||||
new MultiMod(new OsuModGrow(), new OsuModDeflate()),
|
new MultiMod(new OsuModGrow(), new OsuModDeflate()),
|
||||||
new MultiMod(new ModWindUp(), new ModWindDown()),
|
new MultiMod(new ModWindUp(), new ModWindDown()),
|
||||||
|
new OsuModTraceable(),
|
||||||
};
|
};
|
||||||
|
|
||||||
case ModType.System:
|
case ModType.System:
|
||||||
|
@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
HitCircleOverlap,
|
HitCircleOverlap,
|
||||||
SliderBorderSize,
|
SliderBorderSize,
|
||||||
SliderPathRadius,
|
SliderPathRadius,
|
||||||
|
AllowSliderBallTint,
|
||||||
CursorExpand,
|
CursorExpand,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition,
|
Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition,
|
||||||
Scale = new Vector2(((OsuHitObject)judgedObject.HitObject).Scale * 1.65f)
|
Scale = new Vector2(((OsuHitObject)judgedObject.HitObject).Scale)
|
||||||
};
|
};
|
||||||
|
|
||||||
judgementLayer.Add(explosion);
|
judgementLayer.Add(explosion);
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -15,7 +15,10 @@ using osu.Framework.Logging;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
|
using SharpCompress.Archives;
|
||||||
using SharpCompress.Archives.Zip;
|
using SharpCompress.Archives.Zip;
|
||||||
|
using SharpCompress.Common;
|
||||||
|
using SharpCompress.Writers.Zip;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Beatmaps.IO
|
namespace osu.Game.Tests.Beatmaps.IO
|
||||||
{
|
{
|
||||||
@ -87,6 +90,48 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task TestImportCorruptThenImport()
|
||||||
|
{
|
||||||
|
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||||
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImport"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var osu = loadOsu(host);
|
||||||
|
|
||||||
|
var imported = await LoadOszIntoOsu(osu);
|
||||||
|
|
||||||
|
var firstFile = imported.Files.First();
|
||||||
|
|
||||||
|
var files = osu.Dependencies.Get<FileStore>();
|
||||||
|
|
||||||
|
long originalLength;
|
||||||
|
using (var stream = files.Storage.GetStream(firstFile.FileInfo.StoragePath))
|
||||||
|
originalLength = stream.Length;
|
||||||
|
|
||||||
|
using (var stream = files.Storage.GetStream(firstFile.FileInfo.StoragePath, FileAccess.Write, FileMode.Create))
|
||||||
|
stream.WriteByte(0);
|
||||||
|
|
||||||
|
var importedSecondTime = await LoadOszIntoOsu(osu);
|
||||||
|
|
||||||
|
using (var stream = files.Storage.GetStream(firstFile.FileInfo.StoragePath))
|
||||||
|
Assert.AreEqual(stream.Length, originalLength, "Corruption was not fixed on second import");
|
||||||
|
|
||||||
|
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
||||||
|
Assert.IsTrue(imported.ID == importedSecondTime.ID);
|
||||||
|
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
||||||
|
|
||||||
|
checkBeatmapSetCount(osu, 1);
|
||||||
|
checkSingleReferencedFileCount(osu, 18);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
host.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task TestRollbackOnFailure()
|
public async Task TestRollbackOnFailure()
|
||||||
{
|
{
|
||||||
@ -135,7 +180,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
using (var zip = ZipArchive.Open(brokenOsz))
|
using (var zip = ZipArchive.Open(brokenOsz))
|
||||||
{
|
{
|
||||||
zip.AddEntry("broken.osu", brokenOsu, false);
|
zip.AddEntry("broken.osu", brokenOsu, false);
|
||||||
zip.SaveTo(outStream, SharpCompress.Common.CompressionType.Deflate);
|
zip.SaveTo(outStream, CompressionType.Deflate);
|
||||||
}
|
}
|
||||||
|
|
||||||
// this will trigger purging of the existing beatmap (online set id match) but should rollback due to broken osu.
|
// this will trigger purging of the existing beatmap (online set id match) but should rollback due to broken osu.
|
||||||
@ -366,6 +411,51 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task TestImportNestedStructure()
|
||||||
|
{
|
||||||
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportNestedStructure"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var osu = loadOsu(host);
|
||||||
|
|
||||||
|
var temp = TestResources.GetTestBeatmapForImport();
|
||||||
|
|
||||||
|
string extractedFolder = $"{temp}_extracted";
|
||||||
|
string subfolder = Path.Combine(extractedFolder, "subfolder");
|
||||||
|
|
||||||
|
Directory.CreateDirectory(subfolder);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var zip = ZipArchive.Open(temp))
|
||||||
|
zip.WriteToDirectory(subfolder);
|
||||||
|
|
||||||
|
using (var zip = ZipArchive.Create())
|
||||||
|
{
|
||||||
|
zip.AddAllFromDirectory(extractedFolder);
|
||||||
|
zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
|
||||||
|
}
|
||||||
|
|
||||||
|
var imported = await osu.Dependencies.Get<BeatmapManager>().Import(temp);
|
||||||
|
|
||||||
|
ensureLoaded(osu);
|
||||||
|
|
||||||
|
Assert.IsFalse(imported.Files.Any(f => f.Filename.Contains("subfolder")), "Files contain common subfolder");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Directory.Delete(extractedFolder, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
host.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task<BeatmapSetInfo> LoadOszIntoOsu(OsuGameBase osu, string path = null)
|
public static async Task<BeatmapSetInfo> LoadOszIntoOsu(OsuGameBase osu, string path = null)
|
||||||
{
|
{
|
||||||
var temp = path ?? TestResources.GetTestBeatmapForImport();
|
var temp = path ?? TestResources.GetTestBeatmapForImport();
|
||||||
|
201
osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
Normal file
201
osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Screens.Select;
|
||||||
|
using osu.Game.Screens.Select.Carousel;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.NonVisual.Filtering
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class FilterMatchingTest
|
||||||
|
{
|
||||||
|
private BeatmapInfo getExampleBeatmap() => new BeatmapInfo
|
||||||
|
{
|
||||||
|
Ruleset = new RulesetInfo { ID = 5 },
|
||||||
|
StarDifficulty = 4.0d,
|
||||||
|
BaseDifficulty = new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
ApproachRate = 5.0f,
|
||||||
|
DrainRate = 3.0f,
|
||||||
|
CircleSize = 2.0f,
|
||||||
|
},
|
||||||
|
Metadata = new BeatmapMetadata
|
||||||
|
{
|
||||||
|
Artist = "The Artist",
|
||||||
|
ArtistUnicode = "check unicode too",
|
||||||
|
Title = "Title goes here",
|
||||||
|
TitleUnicode = "Title goes here",
|
||||||
|
AuthorString = "The Author",
|
||||||
|
Source = "unit tests",
|
||||||
|
Tags = "look for tags too",
|
||||||
|
},
|
||||||
|
Version = "version as well",
|
||||||
|
Length = 2500,
|
||||||
|
BPM = 160,
|
||||||
|
BeatDivisor = 12,
|
||||||
|
Status = BeatmapSetOnlineStatus.Loved
|
||||||
|
};
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCriteriaMatchingNoRuleset()
|
||||||
|
{
|
||||||
|
var exampleBeatmapInfo = getExampleBeatmap();
|
||||||
|
var criteria = new FilterCriteria();
|
||||||
|
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
|
||||||
|
carouselItem.Filter(criteria);
|
||||||
|
Assert.IsFalse(carouselItem.Filtered.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCriteriaMatchingSpecificRuleset()
|
||||||
|
{
|
||||||
|
var exampleBeatmapInfo = getExampleBeatmap();
|
||||||
|
var criteria = new FilterCriteria
|
||||||
|
{
|
||||||
|
Ruleset = new RulesetInfo { ID = 6 }
|
||||||
|
};
|
||||||
|
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
|
||||||
|
carouselItem.Filter(criteria);
|
||||||
|
Assert.IsTrue(carouselItem.Filtered.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCriteriaMatchingConvertedBeatmaps()
|
||||||
|
{
|
||||||
|
var exampleBeatmapInfo = getExampleBeatmap();
|
||||||
|
var criteria = new FilterCriteria
|
||||||
|
{
|
||||||
|
Ruleset = new RulesetInfo { ID = 6 },
|
||||||
|
AllowConvertedBeatmaps = true
|
||||||
|
};
|
||||||
|
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
|
||||||
|
carouselItem.Filter(criteria);
|
||||||
|
Assert.IsFalse(carouselItem.Filtered.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCase(true)]
|
||||||
|
[TestCase(false)]
|
||||||
|
public void TestCriteriaMatchingRangeMin(bool inclusive)
|
||||||
|
{
|
||||||
|
var exampleBeatmapInfo = getExampleBeatmap();
|
||||||
|
var criteria = new FilterCriteria
|
||||||
|
{
|
||||||
|
Ruleset = new RulesetInfo { ID = 6 },
|
||||||
|
AllowConvertedBeatmaps = true,
|
||||||
|
ApproachRate = new FilterCriteria.OptionalRange<float>
|
||||||
|
{
|
||||||
|
IsLowerInclusive = inclusive,
|
||||||
|
Min = 5.0f
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
|
||||||
|
carouselItem.Filter(criteria);
|
||||||
|
Assert.AreEqual(!inclusive, carouselItem.Filtered.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCase(true)]
|
||||||
|
[TestCase(false)]
|
||||||
|
public void TestCriteriaMatchingRangeMax(bool inclusive)
|
||||||
|
{
|
||||||
|
var exampleBeatmapInfo = getExampleBeatmap();
|
||||||
|
var criteria = new FilterCriteria
|
||||||
|
{
|
||||||
|
Ruleset = new RulesetInfo { ID = 6 },
|
||||||
|
AllowConvertedBeatmaps = true,
|
||||||
|
BPM = new FilterCriteria.OptionalRange<double>
|
||||||
|
{
|
||||||
|
IsUpperInclusive = inclusive,
|
||||||
|
Max = 160d
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
|
||||||
|
carouselItem.Filter(criteria);
|
||||||
|
Assert.AreEqual(!inclusive, carouselItem.Filtered.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCase("artist", false)]
|
||||||
|
[TestCase("artist title author", false)]
|
||||||
|
[TestCase("an artist", true)]
|
||||||
|
[TestCase("tags too", false)]
|
||||||
|
[TestCase("version", false)]
|
||||||
|
[TestCase("an auteur", true)]
|
||||||
|
public void TestCriteriaMatchingTerms(string terms, bool filtered)
|
||||||
|
{
|
||||||
|
var exampleBeatmapInfo = getExampleBeatmap();
|
||||||
|
var criteria = new FilterCriteria
|
||||||
|
{
|
||||||
|
Ruleset = new RulesetInfo { ID = 6 },
|
||||||
|
AllowConvertedBeatmaps = true,
|
||||||
|
SearchText = terms
|
||||||
|
};
|
||||||
|
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
|
||||||
|
carouselItem.Filter(criteria);
|
||||||
|
Assert.AreEqual(filtered, carouselItem.Filtered.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCase("", false)]
|
||||||
|
[TestCase("The", false)]
|
||||||
|
[TestCase("THE", false)]
|
||||||
|
[TestCase("author", false)]
|
||||||
|
[TestCase("the author", false)]
|
||||||
|
[TestCase("the author AND then something else", true)]
|
||||||
|
[TestCase("unknown", true)]
|
||||||
|
public void TestCriteriaMatchingCreator(string creatorName, bool filtered)
|
||||||
|
{
|
||||||
|
var exampleBeatmapInfo = getExampleBeatmap();
|
||||||
|
var criteria = new FilterCriteria
|
||||||
|
{
|
||||||
|
Creator = new FilterCriteria.OptionalTextFilter { SearchTerm = creatorName }
|
||||||
|
};
|
||||||
|
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
|
||||||
|
carouselItem.Filter(criteria);
|
||||||
|
Assert.AreEqual(filtered, carouselItem.Filtered.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCase("", false)]
|
||||||
|
[TestCase("The", false)]
|
||||||
|
[TestCase("THE", false)]
|
||||||
|
[TestCase("artist", false)]
|
||||||
|
[TestCase("the artist", false)]
|
||||||
|
[TestCase("the artist AND then something else", true)]
|
||||||
|
[TestCase("unicode too", false)]
|
||||||
|
[TestCase("unknown", true)]
|
||||||
|
public void TestCriteriaMatchingArtist(string artistName, bool filtered)
|
||||||
|
{
|
||||||
|
var exampleBeatmapInfo = getExampleBeatmap();
|
||||||
|
var criteria = new FilterCriteria
|
||||||
|
{
|
||||||
|
Artist = new FilterCriteria.OptionalTextFilter { SearchTerm = artistName }
|
||||||
|
};
|
||||||
|
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
|
||||||
|
carouselItem.Filter(criteria);
|
||||||
|
Assert.AreEqual(filtered, carouselItem.Filtered.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCase("", false)]
|
||||||
|
[TestCase("artist", false)]
|
||||||
|
[TestCase("unknown", true)]
|
||||||
|
public void TestCriteriaMatchingArtistWithNullUnicodeName(string artistName, bool filtered)
|
||||||
|
{
|
||||||
|
var exampleBeatmapInfo = getExampleBeatmap();
|
||||||
|
exampleBeatmapInfo.Metadata.ArtistUnicode = null;
|
||||||
|
|
||||||
|
var criteria = new FilterCriteria
|
||||||
|
{
|
||||||
|
Artist = new FilterCriteria.OptionalTextFilter { SearchTerm = artistName }
|
||||||
|
};
|
||||||
|
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
|
||||||
|
carouselItem.Filter(criteria);
|
||||||
|
Assert.AreEqual(filtered, carouselItem.Filtered.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
184
osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
Normal file
184
osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Screens.Select;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.NonVisual.Filtering
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class FilterQueryParserTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestApplyQueriesBareWords()
|
||||||
|
{
|
||||||
|
const string query = "looking for a beatmap";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.AreEqual("looking for a beatmap", filterCriteria.SearchText);
|
||||||
|
Assert.AreEqual(4, filterCriteria.SearchTerms.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The following tests have been written a bit strangely (they don't check exact
|
||||||
|
* bound equality with what the filter says).
|
||||||
|
* This is to account for floating-point arithmetic issues.
|
||||||
|
* For example, specifying a bpm<140 filter would previously match beatmaps with BPM
|
||||||
|
* of 139.99999, which would be displayed in the UI as 140.
|
||||||
|
* Due to this the tests check the last tick inside the range and the first tick
|
||||||
|
* outside of the range.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestApplyStarQueries()
|
||||||
|
{
|
||||||
|
const string query = "stars<4 easy";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.AreEqual("easy", filterCriteria.SearchText.Trim());
|
||||||
|
Assert.AreEqual(1, filterCriteria.SearchTerms.Length);
|
||||||
|
Assert.IsNotNull(filterCriteria.StarDifficulty.Max);
|
||||||
|
Assert.Greater(filterCriteria.StarDifficulty.Max, 3.99d);
|
||||||
|
Assert.Less(filterCriteria.StarDifficulty.Max, 4.00d);
|
||||||
|
Assert.IsNull(filterCriteria.StarDifficulty.Min);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestApplyApproachRateQueries()
|
||||||
|
{
|
||||||
|
const string query = "ar>=9 difficult";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.AreEqual("difficult", filterCriteria.SearchText.Trim());
|
||||||
|
Assert.AreEqual(1, filterCriteria.SearchTerms.Length);
|
||||||
|
Assert.IsNotNull(filterCriteria.ApproachRate.Min);
|
||||||
|
Assert.Greater(filterCriteria.ApproachRate.Min, 8.9f);
|
||||||
|
Assert.Less(filterCriteria.ApproachRate.Min, 9.0f);
|
||||||
|
Assert.IsNull(filterCriteria.ApproachRate.Max);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestApplyDrainRateQueries()
|
||||||
|
{
|
||||||
|
const string query = "dr>2 quite specific dr<:6";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.AreEqual("quite specific", filterCriteria.SearchText.Trim());
|
||||||
|
Assert.AreEqual(2, filterCriteria.SearchTerms.Length);
|
||||||
|
Assert.Greater(filterCriteria.DrainRate.Min, 2.0f);
|
||||||
|
Assert.Less(filterCriteria.DrainRate.Min, 2.1f);
|
||||||
|
Assert.Greater(filterCriteria.DrainRate.Max, 6.0f);
|
||||||
|
Assert.Less(filterCriteria.DrainRate.Min, 6.1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestApplyBPMQueries()
|
||||||
|
{
|
||||||
|
const string query = "bpm>:200 gotta go fast";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.AreEqual("gotta go fast", filterCriteria.SearchText.Trim());
|
||||||
|
Assert.AreEqual(3, filterCriteria.SearchTerms.Length);
|
||||||
|
Assert.IsNotNull(filterCriteria.BPM.Min);
|
||||||
|
Assert.Greater(filterCriteria.BPM.Min, 199.99d);
|
||||||
|
Assert.Less(filterCriteria.BPM.Min, 200.00d);
|
||||||
|
Assert.IsNull(filterCriteria.BPM.Max);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object[] lengthQueryExamples =
|
||||||
|
{
|
||||||
|
new object[] { "6ms", TimeSpan.FromMilliseconds(6), TimeSpan.FromMilliseconds(1) },
|
||||||
|
new object[] { "23s", TimeSpan.FromSeconds(23), TimeSpan.FromSeconds(1) },
|
||||||
|
new object[] { "9m", TimeSpan.FromMinutes(9), TimeSpan.FromMinutes(1) },
|
||||||
|
new object[] { "0.25h", TimeSpan.FromHours(0.25), TimeSpan.FromHours(1) },
|
||||||
|
new object[] { "70", TimeSpan.FromSeconds(70), TimeSpan.FromSeconds(1) },
|
||||||
|
};
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCaseSource(nameof(lengthQueryExamples))]
|
||||||
|
public void TestApplyLengthQueries(string lengthQuery, TimeSpan expectedLength, TimeSpan scale)
|
||||||
|
{
|
||||||
|
string query = $"length={lengthQuery} time";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.AreEqual("time", filterCriteria.SearchText.Trim());
|
||||||
|
Assert.AreEqual(1, filterCriteria.SearchTerms.Length);
|
||||||
|
Assert.AreEqual(expectedLength.TotalMilliseconds - scale.TotalMilliseconds / 2.0, filterCriteria.Length.Min);
|
||||||
|
Assert.AreEqual(expectedLength.TotalMilliseconds + scale.TotalMilliseconds / 2.0, filterCriteria.Length.Max);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestApplyDivisorQueries()
|
||||||
|
{
|
||||||
|
const string query = "that's a time signature alright! divisor:12";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.AreEqual("that's a time signature alright!", filterCriteria.SearchText.Trim());
|
||||||
|
Assert.AreEqual(5, filterCriteria.SearchTerms.Length);
|
||||||
|
Assert.AreEqual(12, filterCriteria.BeatDivisor.Min);
|
||||||
|
Assert.IsTrue(filterCriteria.BeatDivisor.IsLowerInclusive);
|
||||||
|
Assert.AreEqual(12, filterCriteria.BeatDivisor.Max);
|
||||||
|
Assert.IsTrue(filterCriteria.BeatDivisor.IsUpperInclusive);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestApplyStatusQueries()
|
||||||
|
{
|
||||||
|
const string query = "I want the pp status=ranked";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.AreEqual("I want the pp", filterCriteria.SearchText.Trim());
|
||||||
|
Assert.AreEqual(4, filterCriteria.SearchTerms.Length);
|
||||||
|
Assert.AreEqual(BeatmapSetOnlineStatus.Ranked, filterCriteria.OnlineStatus.Min);
|
||||||
|
Assert.IsTrue(filterCriteria.OnlineStatus.IsLowerInclusive);
|
||||||
|
Assert.AreEqual(BeatmapSetOnlineStatus.Ranked, filterCriteria.OnlineStatus.Max);
|
||||||
|
Assert.IsTrue(filterCriteria.OnlineStatus.IsUpperInclusive);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestApplyCreatorQueries()
|
||||||
|
{
|
||||||
|
const string query = "beatmap specifically by creator=my_fav";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.AreEqual("beatmap specifically by", filterCriteria.SearchText.Trim());
|
||||||
|
Assert.AreEqual(3, filterCriteria.SearchTerms.Length);
|
||||||
|
Assert.AreEqual("my_fav", filterCriteria.Creator.SearchTerm);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestApplyArtistQueries()
|
||||||
|
{
|
||||||
|
const string query = "find me songs by artist=singer please";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.AreEqual("find me songs by please", filterCriteria.SearchText.Trim());
|
||||||
|
Assert.AreEqual(5, filterCriteria.SearchTerms.Length);
|
||||||
|
Assert.AreEqual("singer", filterCriteria.Artist.SearchTerm);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestApplyArtistQueriesWithSpaces()
|
||||||
|
{
|
||||||
|
const string query = "really like artist=\"name with space\" yes";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.AreEqual("really like yes", filterCriteria.SearchText.Trim());
|
||||||
|
Assert.AreEqual(3, filterCriteria.SearchTerms.Length);
|
||||||
|
Assert.AreEqual("name with space", filterCriteria.Artist.SearchTerm);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestApplyArtistQueriesOneDoubleQuote()
|
||||||
|
{
|
||||||
|
const string query = "weird artist=double\"quote";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.AreEqual("weird", filterCriteria.SearchText.Trim());
|
||||||
|
Assert.AreEqual(1, filterCriteria.SearchTerms.Length);
|
||||||
|
Assert.AreEqual("double\"quote", filterCriteria.Artist.SearchTerm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -117,17 +117,57 @@ namespace osu.Game.Tests.Scores.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task TestImportWithDeletedBeatmapSet()
|
||||||
|
{
|
||||||
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDeletedBeatmapSet"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var osu = await loadOsu(host);
|
||||||
|
|
||||||
|
var toImport = new ScoreInfo
|
||||||
|
{
|
||||||
|
Hash = Guid.NewGuid().ToString(),
|
||||||
|
Statistics = new Dictionary<HitResult, int>
|
||||||
|
{
|
||||||
|
{ HitResult.Perfect, 100 },
|
||||||
|
{ HitResult.Miss, 50 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var imported = await loadIntoOsu(osu, toImport);
|
||||||
|
|
||||||
|
var beatmapManager = osu.Dependencies.Get<BeatmapManager>();
|
||||||
|
var scoreManager = osu.Dependencies.Get<ScoreManager>();
|
||||||
|
|
||||||
|
beatmapManager.Delete(beatmapManager.QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == imported.Beatmap.ID)));
|
||||||
|
Assert.That(scoreManager.Query(s => s.ID == imported.ID).DeletePending, Is.EqualTo(true));
|
||||||
|
|
||||||
|
var secondImport = await loadIntoOsu(osu, imported);
|
||||||
|
Assert.That(secondImport, Is.Null);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
host.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<ScoreInfo> loadIntoOsu(OsuGameBase osu, ScoreInfo score)
|
private async Task<ScoreInfo> loadIntoOsu(OsuGameBase osu, ScoreInfo score)
|
||||||
{
|
{
|
||||||
var beatmapManager = osu.Dependencies.Get<BeatmapManager>();
|
var beatmapManager = osu.Dependencies.Get<BeatmapManager>();
|
||||||
|
|
||||||
score.Beatmap = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First();
|
if (score.Beatmap == null)
|
||||||
score.Ruleset = new OsuRuleset().RulesetInfo;
|
score.Beatmap = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First();
|
||||||
|
|
||||||
|
if (score.Ruleset == null)
|
||||||
|
score.Ruleset = new OsuRuleset().RulesetInfo;
|
||||||
|
|
||||||
var scoreManager = osu.Dependencies.Get<ScoreManager>();
|
var scoreManager = osu.Dependencies.Get<ScoreManager>();
|
||||||
await scoreManager.Import(score);
|
await scoreManager.Import(score);
|
||||||
|
|
||||||
return scoreManager.GetAllUsableScores().First();
|
return scoreManager.GetAllUsableScores().FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<OsuGameBase> loadOsu(GameHost host)
|
private async Task<OsuGameBase> loadOsu(GameHost host)
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
@ -12,6 +13,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Description("Player instantiated with an autoplay mod.")]
|
[Description("Player instantiated with an autoplay mod.")]
|
||||||
public class TestSceneAutoplay : AllPlayersTestScene
|
public class TestSceneAutoplay : AllPlayersTestScene
|
||||||
{
|
{
|
||||||
|
private ClockBackedTestWorkingBeatmap.TrackVirtualManual track;
|
||||||
|
|
||||||
protected override Player CreatePlayer(Ruleset ruleset)
|
protected override Player CreatePlayer(Ruleset ruleset)
|
||||||
{
|
{
|
||||||
Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
|
Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
|
||||||
@ -21,7 +24,18 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
protected override void AddCheckSteps()
|
protected override void AddCheckSteps()
|
||||||
{
|
{
|
||||||
AddUntilStep("score above zero", () => ((ScoreAccessiblePlayer)Player).ScoreProcessor.TotalScore.Value > 0);
|
AddUntilStep("score above zero", () => ((ScoreAccessiblePlayer)Player).ScoreProcessor.TotalScore.Value > 0);
|
||||||
AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0));
|
AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2));
|
||||||
|
AddStep("rewind", () => track.Seek(-10000));
|
||||||
|
AddUntilStep("key counter reset", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
var working = base.CreateWorkingBeatmap(beatmap);
|
||||||
|
|
||||||
|
track = (ClockBackedTestWorkingBeatmap.TrackVirtualManual)working.Track;
|
||||||
|
|
||||||
|
return working;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ScoreAccessiblePlayer : TestPlayer
|
private class ScoreAccessiblePlayer : TestPlayer
|
||||||
@ -29,6 +43,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||||
public new HUDOverlay HUDOverlay => base.HUDOverlay;
|
public new HUDOverlay HUDOverlay => base.HUDOverlay;
|
||||||
|
|
||||||
|
public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
|
||||||
|
|
||||||
public ScoreAccessiblePlayer()
|
public ScoreAccessiblePlayer()
|
||||||
: base(false, false)
|
: base(false, false)
|
||||||
{
|
{
|
||||||
|
@ -6,8 +6,6 @@ using System.Linq;
|
|||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
|
||||||
using osu.Game.Scoring;
|
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
@ -17,9 +15,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
protected override Player CreatePlayer(Ruleset ruleset)
|
protected override Player CreatePlayer(Ruleset ruleset)
|
||||||
{
|
{
|
||||||
Mods.Value = Array.Empty<Mod>();
|
Mods.Value = Array.Empty<Mod>();
|
||||||
|
return new FailPlayer();
|
||||||
var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty<Mod>());
|
|
||||||
return new FailPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void AddCheckSteps()
|
protected override void AddCheckSteps()
|
||||||
@ -29,16 +25,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("total judgements == 1", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits == 1);
|
AddAssert("total judgements == 1", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FailPlayer : ReplayPlayer
|
private class FailPlayer : TestPlayer
|
||||||
{
|
{
|
||||||
public new DrawableRuleset DrawableRuleset => base.DrawableRuleset;
|
|
||||||
|
|
||||||
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||||
|
|
||||||
protected override bool PauseOnFocusLost => false;
|
public FailPlayer()
|
||||||
|
: base(false, false)
|
||||||
public FailPlayer(Score score)
|
|
||||||
: base(score, false, false)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ using osu.Game.Rulesets;
|
|||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -47,9 +48,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddUntilStep("wait for track to start running", () => track.IsRunning);
|
AddUntilStep("wait for track to start running", () => track.IsRunning);
|
||||||
addSeekStep(3000);
|
addSeekStep(3000);
|
||||||
AddAssert("all judged", () => player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
|
AddAssert("all judged", () => player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
|
||||||
|
AddUntilStep("key counter counted keys", () => player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses >= 7));
|
||||||
AddStep("clear results", () => player.AppliedResults.Clear());
|
AddStep("clear results", () => player.AppliedResults.Clear());
|
||||||
addSeekStep(0);
|
addSeekStep(0);
|
||||||
AddAssert("none judged", () => player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged));
|
AddAssert("none judged", () => player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged));
|
||||||
|
AddUntilStep("key counters reset", () => player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
|
||||||
AddAssert("no results triggered", () => player.AppliedResults.Count == 0);
|
AddAssert("no results triggered", () => player.AppliedResults.Count == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,6 +93,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
public readonly List<JudgementResult> AppliedResults = new List<JudgementResult>();
|
public readonly List<JudgementResult> AppliedResults = new List<JudgementResult>();
|
||||||
|
|
||||||
|
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||||
|
|
||||||
|
public new HUDOverlay HUDOverlay => base.HUDOverlay;
|
||||||
|
|
||||||
public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
|
public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
|
||||||
|
|
||||||
public new DrawableRuleset DrawableRuleset => base.DrawableRuleset;
|
public new DrawableRuleset DrawableRuleset => base.DrawableRuleset;
|
||||||
|
@ -7,7 +7,6 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.MathUtils;
|
using osu.Framework.MathUtils;
|
||||||
using osu.Framework.Timing;
|
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
@ -25,14 +24,15 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
public TestSceneKeyCounter()
|
public TestSceneKeyCounter()
|
||||||
{
|
{
|
||||||
KeyCounterKeyboard rewindTestKeyCounterKeyboard;
|
KeyCounterKeyboard testCounter;
|
||||||
|
|
||||||
KeyCounterDisplay kc = new KeyCounterDisplay
|
KeyCounterDisplay kc = new KeyCounterDisplay
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Children = new KeyCounter[]
|
Children = new KeyCounter[]
|
||||||
{
|
{
|
||||||
rewindTestKeyCounterKeyboard = new KeyCounterKeyboard(Key.X),
|
testCounter = new KeyCounterKeyboard(Key.X),
|
||||||
new KeyCounterKeyboard(Key.X),
|
new KeyCounterKeyboard(Key.X),
|
||||||
new KeyCounterMouse(MouseButton.Left),
|
new KeyCounterMouse(MouseButton.Left),
|
||||||
new KeyCounterMouse(MouseButton.Right),
|
new KeyCounterMouse(MouseButton.Right),
|
||||||
@ -44,10 +44,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
Key key = (Key)((int)Key.A + RNG.Next(26));
|
Key key = (Key)((int)Key.A + RNG.Next(26));
|
||||||
kc.Add(new KeyCounterKeyboard(key));
|
kc.Add(new KeyCounterKeyboard(key));
|
||||||
});
|
});
|
||||||
AddSliderStep("Fade time", 0, 200, 50, v => kc.FadeTime = v);
|
|
||||||
|
|
||||||
Key testKey = ((KeyCounterKeyboard)kc.Children.First()).Key;
|
Key testKey = ((KeyCounterKeyboard)kc.Children.First()).Key;
|
||||||
double time1 = 0;
|
|
||||||
|
|
||||||
AddStep($"Press {testKey} key", () =>
|
AddStep($"Press {testKey} key", () =>
|
||||||
{
|
{
|
||||||
@ -55,48 +53,17 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
InputManager.ReleaseKey(testKey);
|
InputManager.ReleaseKey(testKey);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert($"Check {testKey} counter after keypress", () => rewindTestKeyCounterKeyboard.CountPresses == 1);
|
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 1);
|
||||||
|
|
||||||
AddStep($"Press {testKey} key", () =>
|
AddStep($"Press {testKey} key", () =>
|
||||||
{
|
{
|
||||||
InputManager.PressKey(testKey);
|
InputManager.PressKey(testKey);
|
||||||
InputManager.ReleaseKey(testKey);
|
InputManager.ReleaseKey(testKey);
|
||||||
time1 = Clock.CurrentTime;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert($"Check {testKey} counter after keypress", () => rewindTestKeyCounterKeyboard.CountPresses == 2);
|
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 2);
|
||||||
|
|
||||||
IFrameBasedClock oldClock = null;
|
|
||||||
|
|
||||||
AddStep($"Rewind {testKey} counter once", () =>
|
|
||||||
{
|
|
||||||
oldClock = rewindTestKeyCounterKeyboard.Clock;
|
|
||||||
rewindTestKeyCounterKeyboard.Clock = new FramedOffsetClock(new FixedClock(time1 - 10));
|
|
||||||
});
|
|
||||||
|
|
||||||
AddAssert($"Check {testKey} counter after rewind", () => rewindTestKeyCounterKeyboard.CountPresses == 1);
|
|
||||||
|
|
||||||
AddStep($"Rewind {testKey} counter to zero", () => rewindTestKeyCounterKeyboard.Clock = new FramedOffsetClock(new FixedClock(0)));
|
|
||||||
|
|
||||||
AddAssert($"Check {testKey} counter after rewind", () => rewindTestKeyCounterKeyboard.CountPresses == 0);
|
|
||||||
|
|
||||||
AddStep("Restore clock", () => rewindTestKeyCounterKeyboard.Clock = oldClock);
|
|
||||||
|
|
||||||
Add(kc);
|
Add(kc);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FixedClock : IClock
|
|
||||||
{
|
|
||||||
private readonly double time;
|
|
||||||
|
|
||||||
public FixedClock(double time)
|
|
||||||
{
|
|
||||||
this.time = time;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double CurrentTime => time;
|
|
||||||
public double Rate => 1;
|
|
||||||
public bool IsRunning => false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,12 +26,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0);
|
AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0);
|
||||||
AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0));
|
AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0));
|
||||||
|
AddAssert("cannot fail", () => !((ScoreAccessibleReplayPlayer)Player).AllowFail);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ScoreAccessibleReplayPlayer : ReplayPlayer
|
private class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||||
{
|
{
|
||||||
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||||
public new HUDOverlay HUDOverlay => base.HUDOverlay;
|
public new HUDOverlay HUDOverlay => base.HUDOverlay;
|
||||||
|
public new bool AllowFail => base.AllowFail;
|
||||||
|
|
||||||
protected override bool PauseOnFocusLost => false;
|
protected override bool PauseOnFocusLost => false;
|
||||||
|
|
||||||
|
@ -68,6 +68,34 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
changelog.ShowListing();
|
changelog.ShowListing();
|
||||||
changelog.Show();
|
changelog.Show();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddStep(@"Ensure HTML string unescaping", () =>
|
||||||
|
{
|
||||||
|
changelog.ShowBuild(new APIChangelogBuild
|
||||||
|
{
|
||||||
|
Version = "2019.920.0",
|
||||||
|
DisplayVersion = "2019.920.0",
|
||||||
|
UpdateStream = new APIUpdateStream
|
||||||
|
{
|
||||||
|
Name = "Test",
|
||||||
|
DisplayName = "Test"
|
||||||
|
},
|
||||||
|
ChangelogEntries = new List<APIChangelogEntry>
|
||||||
|
{
|
||||||
|
new APIChangelogEntry
|
||||||
|
{
|
||||||
|
Category = "Testing HTML strings unescaping",
|
||||||
|
Title = "Ensuring HTML strings are being unescaped",
|
||||||
|
MessageHtml = """"This text should appear triple-quoted""" >_<",
|
||||||
|
GithubUser = new APIChangelogUser
|
||||||
|
{
|
||||||
|
DisplayName = "Dummy",
|
||||||
|
OsuUsername = "Dummy",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -242,6 +242,21 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false));
|
AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false));
|
||||||
|
|
||||||
AddAssert("Selection is non-null", () => currentSelection != null);
|
AddAssert("Selection is non-null", () => currentSelection != null);
|
||||||
|
|
||||||
|
setSelected(1, 3);
|
||||||
|
AddStep("Apply a range filter", () => carousel.Filter(new FilterCriteria
|
||||||
|
{
|
||||||
|
SearchText = "#3",
|
||||||
|
StarDifficulty = new FilterCriteria.OptionalRange<double>
|
||||||
|
{
|
||||||
|
Min = 2,
|
||||||
|
Max = 5.5,
|
||||||
|
IsLowerInclusive = true
|
||||||
|
}
|
||||||
|
}, false));
|
||||||
|
checkSelected(3, 2);
|
||||||
|
|
||||||
|
AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -3,10 +3,12 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Leaderboards;
|
using osu.Game.Online.Leaderboards;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Select.Leaderboards;
|
using osu.Game.Screens.Select.Leaderboards;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
@ -14,19 +16,20 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Tests.Visual.SongSelect
|
namespace osu.Game.Tests.Visual.SongSelect
|
||||||
{
|
{
|
||||||
[Description("PlaySongSelect leaderboard")]
|
public class TestSceneBeatmapLeaderboard : OsuTestScene
|
||||||
public class TestSceneLeaderboard : OsuTestScene
|
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
{
|
{
|
||||||
typeof(Placeholder),
|
typeof(Placeholder),
|
||||||
typeof(MessagePlaceholder),
|
typeof(MessagePlaceholder),
|
||||||
typeof(RetrievalFailurePlaceholder),
|
typeof(RetrievalFailurePlaceholder),
|
||||||
|
typeof(UserTopScoreContainer),
|
||||||
|
typeof(Leaderboard<BeatmapLeaderboardScope, ScoreInfo>),
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly FailableLeaderboard leaderboard;
|
private readonly FailableLeaderboard leaderboard;
|
||||||
|
|
||||||
public TestSceneLeaderboard()
|
public TestSceneBeatmapLeaderboard()
|
||||||
{
|
{
|
||||||
Add(leaderboard = new FailableLeaderboard
|
Add(leaderboard = new FailableLeaderboard
|
||||||
{
|
{
|
||||||
@ -37,6 +40,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
});
|
});
|
||||||
|
|
||||||
AddStep(@"New Scores", newScores);
|
AddStep(@"New Scores", newScores);
|
||||||
|
AddStep(@"Show personal best", showPersonalBest);
|
||||||
AddStep(@"Empty Scores", () => leaderboard.SetRetrievalState(PlaceholderState.NoScores));
|
AddStep(@"Empty Scores", () => leaderboard.SetRetrievalState(PlaceholderState.NoScores));
|
||||||
AddStep(@"Network failure", () => leaderboard.SetRetrievalState(PlaceholderState.NetworkFailure));
|
AddStep(@"Network failure", () => leaderboard.SetRetrievalState(PlaceholderState.NetworkFailure));
|
||||||
AddStep(@"No supporter", () => leaderboard.SetRetrievalState(PlaceholderState.NotSupporter));
|
AddStep(@"No supporter", () => leaderboard.SetRetrievalState(PlaceholderState.NotSupporter));
|
||||||
@ -47,6 +51,32 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddStep($"{status} beatmap", () => showBeatmapWithStatus(status));
|
AddStep($"{status} beatmap", () => showBeatmapWithStatus(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showPersonalBest()
|
||||||
|
{
|
||||||
|
leaderboard.TopScore = new APILegacyUserTopScoreInfo
|
||||||
|
{
|
||||||
|
Position = 999,
|
||||||
|
Score = new APILegacyScoreInfo
|
||||||
|
{
|
||||||
|
Rank = ScoreRank.XH,
|
||||||
|
Accuracy = 1,
|
||||||
|
MaxCombo = 244,
|
||||||
|
TotalScore = 1707827,
|
||||||
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
|
User = new User
|
||||||
|
{
|
||||||
|
Id = 6602580,
|
||||||
|
Username = @"waaiiru",
|
||||||
|
Country = new Country
|
||||||
|
{
|
||||||
|
FullName = @"Spain",
|
||||||
|
FlagName = @"ES",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private void newScores()
|
private void newScores()
|
||||||
{
|
{
|
||||||
var scores = new[]
|
var scores = new[]
|
@ -0,0 +1,119 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Screens.Select.Leaderboards;
|
||||||
|
using osu.Game.Users;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.SongSelect
|
||||||
|
{
|
||||||
|
public class TestSceneUserTopScoreContainer : OsuTestScene
|
||||||
|
{
|
||||||
|
public TestSceneUserTopScoreContainer()
|
||||||
|
{
|
||||||
|
UserTopScoreContainer topScoreContainer;
|
||||||
|
|
||||||
|
Add(new Container
|
||||||
|
{
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Width = 500,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.DarkGreen,
|
||||||
|
},
|
||||||
|
topScoreContainer = new UserTopScoreContainer
|
||||||
|
{
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var scores = new[]
|
||||||
|
{
|
||||||
|
new APILegacyUserTopScoreInfo
|
||||||
|
{
|
||||||
|
Position = 999,
|
||||||
|
Score = new APILegacyScoreInfo
|
||||||
|
{
|
||||||
|
Rank = ScoreRank.XH,
|
||||||
|
Accuracy = 1,
|
||||||
|
MaxCombo = 244,
|
||||||
|
TotalScore = 1707827,
|
||||||
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
|
User = new User
|
||||||
|
{
|
||||||
|
Id = 6602580,
|
||||||
|
Username = @"waaiiru",
|
||||||
|
Country = new Country
|
||||||
|
{
|
||||||
|
FullName = @"Spain",
|
||||||
|
FlagName = @"ES",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new APILegacyUserTopScoreInfo
|
||||||
|
{
|
||||||
|
Position = 110000,
|
||||||
|
Score = new APILegacyScoreInfo
|
||||||
|
{
|
||||||
|
Rank = ScoreRank.X,
|
||||||
|
Accuracy = 1,
|
||||||
|
MaxCombo = 244,
|
||||||
|
TotalScore = 1707827,
|
||||||
|
User = new User
|
||||||
|
{
|
||||||
|
Id = 4608074,
|
||||||
|
Username = @"Skycries",
|
||||||
|
Country = new Country
|
||||||
|
{
|
||||||
|
FullName = @"Brazil",
|
||||||
|
FlagName = @"BR",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new APILegacyUserTopScoreInfo
|
||||||
|
{
|
||||||
|
Position = 22333,
|
||||||
|
Score = new APILegacyScoreInfo
|
||||||
|
{
|
||||||
|
Rank = ScoreRank.S,
|
||||||
|
Accuracy = 1,
|
||||||
|
MaxCombo = 244,
|
||||||
|
TotalScore = 1707827,
|
||||||
|
User = new User
|
||||||
|
{
|
||||||
|
Id = 1541390,
|
||||||
|
Username = @"Toukai",
|
||||||
|
Country = new Country
|
||||||
|
{
|
||||||
|
FullName = @"Canada",
|
||||||
|
FlagName = @"CA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AddStep(@"Trigger visibility", topScoreContainer.ToggleVisibility);
|
||||||
|
AddStep(@"Add score(rank 999)", () => topScoreContainer.Score.Value = scores[0]);
|
||||||
|
AddStep(@"Add score(rank 110000)", () => topScoreContainer.Score.Value = scores[1]);
|
||||||
|
AddStep(@"Add score(rank 22333)", () => topScoreContainer.Score.Value = scores[2]);
|
||||||
|
AddStep(@"Add null score", () => topScoreContainer.Score.Value = null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -52,19 +52,23 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddStep("start confirming", () => overlay.Begin());
|
AddStep("start confirming", () => overlay.Begin());
|
||||||
AddStep("abort confirming", () => overlay.Abort());
|
AddStep("abort confirming", () => overlay.Abort());
|
||||||
|
|
||||||
|
AddAssert("ensure not fired internally", () => !overlay.Fired);
|
||||||
AddAssert("ensure aborted", () => !fired);
|
AddAssert("ensure aborted", () => !fired);
|
||||||
|
|
||||||
AddStep("start confirming", () => overlay.Begin());
|
AddStep("start confirming", () => overlay.Begin());
|
||||||
|
|
||||||
AddUntilStep("wait until confirmed", () => fired);
|
AddUntilStep("wait until confirmed", () => fired);
|
||||||
|
AddAssert("ensure fired internally", () => overlay.Fired);
|
||||||
|
|
||||||
|
AddStep("abort after fire", () => overlay.Abort());
|
||||||
|
AddAssert("ensure not fired internally", () => !overlay.Fired);
|
||||||
|
AddStep("start confirming", () => overlay.Begin());
|
||||||
|
AddUntilStep("wait until fired again", () => overlay.Fired);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestHoldToConfirmOverlay : ExitConfirmOverlay
|
private class TestHoldToConfirmOverlay : ExitConfirmOverlay
|
||||||
{
|
{
|
||||||
protected override bool AllowMultipleFires => true;
|
|
||||||
|
|
||||||
public void Begin() => BeginConfirm();
|
public void Begin() => BeginConfirm();
|
||||||
public void Abort() => AbortConfirm();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Screens.Edit.Setup.Components.LabelledComponents;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
public class TestSceneLabelledComponent : OsuTestScene
|
||||||
|
{
|
||||||
|
[TestCase(false)]
|
||||||
|
[TestCase(true)]
|
||||||
|
public void TestPadded(bool hasDescription) => createPaddedComponent(hasDescription);
|
||||||
|
|
||||||
|
[TestCase(false)]
|
||||||
|
[TestCase(true)]
|
||||||
|
public void TestNonPadded(bool hasDescription) => createPaddedComponent(hasDescription, false);
|
||||||
|
|
||||||
|
private void createPaddedComponent(bool hasDescription = false, bool padded = true)
|
||||||
|
{
|
||||||
|
AddStep("create component", () =>
|
||||||
|
{
|
||||||
|
LabelledComponent component;
|
||||||
|
|
||||||
|
Child = new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Width = 500,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Child = component = padded ? (LabelledComponent)new PaddedLabelledComponent() : new NonPaddedLabelledComponent(),
|
||||||
|
};
|
||||||
|
|
||||||
|
component.Label = "a sample component";
|
||||||
|
component.Description = hasDescription ? "this text describes the component" : string.Empty;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PaddedLabelledComponent : LabelledComponent
|
||||||
|
{
|
||||||
|
public PaddedLabelledComponent()
|
||||||
|
: base(true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Drawable CreateComponent() => new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Colour = Color4.Red,
|
||||||
|
Text = @"(( Component ))"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NonPaddedLabelledComponent : LabelledComponent
|
||||||
|
{
|
||||||
|
public NonPaddedLabelledComponent()
|
||||||
|
: base(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Drawable CreateComponent() => new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 40,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.SlateGray
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Colour = Color4.Red,
|
||||||
|
Text = @"(( Component ))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,7 @@
|
|||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="DeepEqual" Version="2.0.0" />
|
<PackageReference Include="DeepEqual" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -11,6 +11,6 @@
|
|||||||
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
|
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.5.0" />
|
<PackageReference Include="Microsoft.Win32.Registry" Version="4.6.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
@ -89,7 +89,7 @@ namespace osu.Game.Beatmaps
|
|||||||
protected override Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default)
|
protected override Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (archive != null)
|
if (archive != null)
|
||||||
beatmapSet.Beatmaps = createBeatmapDifficulties(archive);
|
beatmapSet.Beatmaps = createBeatmapDifficulties(beatmapSet.Files);
|
||||||
|
|
||||||
foreach (BeatmapInfo b in beatmapSet.Beatmaps)
|
foreach (BeatmapInfo b in beatmapSet.Beatmaps)
|
||||||
{
|
{
|
||||||
@ -279,13 +279,13 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create all required <see cref="BeatmapInfo"/>s for the provided archive.
|
/// Create all required <see cref="BeatmapInfo"/>s for the provided archive.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private List<BeatmapInfo> createBeatmapDifficulties(ArchiveReader reader)
|
private List<BeatmapInfo> createBeatmapDifficulties(List<BeatmapSetFileInfo> files)
|
||||||
{
|
{
|
||||||
var beatmapInfos = new List<BeatmapInfo>();
|
var beatmapInfos = new List<BeatmapInfo>();
|
||||||
|
|
||||||
foreach (var name in reader.Filenames.Where(f => f.EndsWith(".osu")))
|
foreach (var file in files.Where(f => f.Filename.EndsWith(".osu")))
|
||||||
{
|
{
|
||||||
using (var raw = reader.GetStream(name))
|
using (var raw = Files.Store.GetStream(file.FileInfo.StoragePath))
|
||||||
using (var ms = new MemoryStream()) //we need a memory stream so we can seek
|
using (var ms = new MemoryStream()) //we need a memory stream so we can seek
|
||||||
using (var sr = new StreamReader(ms))
|
using (var sr = new StreamReader(ms))
|
||||||
{
|
{
|
||||||
@ -295,12 +295,13 @@ namespace osu.Game.Beatmaps
|
|||||||
var decoder = Decoder.GetDecoder<Beatmap>(sr);
|
var decoder = Decoder.GetDecoder<Beatmap>(sr);
|
||||||
IBeatmap beatmap = decoder.Decode(sr);
|
IBeatmap beatmap = decoder.Decode(sr);
|
||||||
|
|
||||||
beatmap.BeatmapInfo.Path = name;
|
beatmap.BeatmapInfo.Path = file.Filename;
|
||||||
beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
|
beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
|
||||||
beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash();
|
beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash();
|
||||||
|
|
||||||
var ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID);
|
var ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID);
|
||||||
beatmap.BeatmapInfo.Ruleset = ruleset;
|
beatmap.BeatmapInfo.Ruleset = ruleset;
|
||||||
|
|
||||||
// TODO: this should be done in a better place once we actually need to dynamically update it.
|
// TODO: this should be done in a better place once we actually need to dynamically update it.
|
||||||
beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating ?? 0;
|
beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating ?? 0;
|
||||||
beatmap.BeatmapInfo.Length = calculateLength(beatmap);
|
beatmap.BeatmapInfo.Length = calculateLength(beatmap);
|
||||||
|
@ -81,6 +81,8 @@ namespace osu.Game.Configuration
|
|||||||
Set(OsuSetting.DimLevel, 0.3, 0, 1, 0.01);
|
Set(OsuSetting.DimLevel, 0.3, 0, 1, 0.01);
|
||||||
Set(OsuSetting.BlurLevel, 0, 0, 1, 0.01);
|
Set(OsuSetting.BlurLevel, 0, 0, 1, 0.01);
|
||||||
|
|
||||||
|
Set(OsuSetting.HitLighting, true);
|
||||||
|
|
||||||
Set(OsuSetting.ShowInterface, true);
|
Set(OsuSetting.ShowInterface, true);
|
||||||
Set(OsuSetting.ShowHealthDisplayWhenCantFail, true);
|
Set(OsuSetting.ShowHealthDisplayWhenCantFail, true);
|
||||||
Set(OsuSetting.KeyOverlay, false);
|
Set(OsuSetting.KeyOverlay, false);
|
||||||
@ -112,6 +114,8 @@ namespace osu.Game.Configuration
|
|||||||
|
|
||||||
Set(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f);
|
Set(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f);
|
||||||
|
|
||||||
|
Set(OsuSetting.UIHoldActivationDelay, 200, 0, 500);
|
||||||
|
|
||||||
Set(OsuSetting.IntroSequence, IntroSequence.Triangles);
|
Set(OsuSetting.IntroSequence, IntroSequence.Triangles);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,6 +184,8 @@ namespace osu.Game.Configuration
|
|||||||
ScalingSizeX,
|
ScalingSizeX,
|
||||||
ScalingSizeY,
|
ScalingSizeY,
|
||||||
UIScale,
|
UIScale,
|
||||||
IntroSequence
|
IntroSequence,
|
||||||
|
UIHoldActivationDelay,
|
||||||
|
HitLighting
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,12 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Humanizer;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using osu.Framework;
|
using osu.Framework;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.IO.File;
|
using osu.Framework.IO.File;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
@ -109,7 +111,7 @@ namespace osu.Game.Database
|
|||||||
protected async Task Import(ProgressNotification notification, params string[] paths)
|
protected async Task Import(ProgressNotification notification, params string[] paths)
|
||||||
{
|
{
|
||||||
notification.Progress = 0;
|
notification.Progress = 0;
|
||||||
notification.Text = "Import is initialising...";
|
notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is initialising...";
|
||||||
|
|
||||||
int current = 0;
|
int current = 0;
|
||||||
|
|
||||||
@ -145,7 +147,7 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
if (imported.Count == 0)
|
if (imported.Count == 0)
|
||||||
{
|
{
|
||||||
notification.Text = "Import failed!";
|
notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import failed!";
|
||||||
notification.State = ProgressNotificationState.Cancelled;
|
notification.State = ProgressNotificationState.Cancelled;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -481,12 +483,16 @@ namespace osu.Game.Database
|
|||||||
{
|
{
|
||||||
var fileInfos = new List<TFileModel>();
|
var fileInfos = new List<TFileModel>();
|
||||||
|
|
||||||
|
string prefix = reader.Filenames.GetCommonPrefix();
|
||||||
|
if (!(prefix.EndsWith("/") || prefix.EndsWith("\\")))
|
||||||
|
prefix = string.Empty;
|
||||||
|
|
||||||
// import files to manager
|
// import files to manager
|
||||||
foreach (string file in reader.Filenames)
|
foreach (string file in reader.Filenames)
|
||||||
using (Stream s = reader.GetStream(file))
|
using (Stream s = reader.GetStream(file))
|
||||||
fileInfos.Add(new TFileModel
|
fileInfos.Add(new TFileModel
|
||||||
{
|
{
|
||||||
Filename = FileSafety.PathStandardise(file),
|
Filename = FileSafety.PathStandardise(file.Substring(prefix.Length)),
|
||||||
FileInfo = files.Add(s)
|
FileInfo = files.Add(s)
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -585,7 +591,7 @@ namespace osu.Game.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="existing">The existing model.</param>
|
/// <param name="existing">The existing model.</param>
|
||||||
/// <param name="import">The newly imported model.</param>
|
/// <param name="import">The newly imported model.</param>
|
||||||
/// <returns>Whether the existing model should be restored and used. Returning false will delete the existing a force a re-import.</returns>
|
/// <returns>Whether the existing model should be restored and used. Returning false will delete the existing and force a re-import.</returns>
|
||||||
protected virtual bool CanUndelete(TModel existing, TModel import) => true;
|
protected virtual bool CanUndelete(TModel existing, TModel import) => true;
|
||||||
|
|
||||||
private DbSet<TModel> queryModel() => ContextFactory.Get().Set<TModel>();
|
private DbSet<TModel> queryModel() => ContextFactory.Get().Set<TModel>();
|
||||||
|
@ -2,9 +2,11 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.Containers
|
namespace osu.Game.Graphics.Containers
|
||||||
{
|
{
|
||||||
@ -12,12 +14,13 @@ namespace osu.Game.Graphics.Containers
|
|||||||
{
|
{
|
||||||
public Action Action;
|
public Action Action;
|
||||||
|
|
||||||
private const int default_activation_delay = 200;
|
|
||||||
private const int fadeout_delay = 200;
|
private const int fadeout_delay = 200;
|
||||||
|
|
||||||
private readonly double activationDelay;
|
/// <summary>
|
||||||
|
/// Whether currently in a fired state (and the confirm <see cref="Action"/> has been sent).
|
||||||
|
/// </summary>
|
||||||
|
public bool Fired { get; private set; }
|
||||||
|
|
||||||
private bool fired;
|
|
||||||
private bool confirming;
|
private bool confirming;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -27,35 +30,35 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
public Bindable<double> Progress = new BindableDouble();
|
public Bindable<double> Progress = new BindableDouble();
|
||||||
|
|
||||||
/// <summary>
|
private Bindable<int> holdActivationDelay;
|
||||||
/// Create a new instance.
|
|
||||||
/// </summary>
|
[BackgroundDependencyLoader]
|
||||||
/// <param name="activationDelay">The time requried before an action is confirmed.</param>
|
private void load(OsuConfigManager config)
|
||||||
protected HoldToConfirmContainer(double activationDelay = default_activation_delay)
|
|
||||||
{
|
{
|
||||||
this.activationDelay = activationDelay;
|
holdActivationDelay = config.GetBindable<int>(OsuSetting.UIHoldActivationDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void BeginConfirm()
|
protected void BeginConfirm()
|
||||||
{
|
{
|
||||||
if (confirming || (!AllowMultipleFires && fired)) return;
|
if (confirming || (!AllowMultipleFires && Fired)) return;
|
||||||
|
|
||||||
confirming = true;
|
confirming = true;
|
||||||
|
|
||||||
this.TransformBindableTo(Progress, 1, activationDelay * (1 - Progress.Value), Easing.Out).OnComplete(_ => Confirm());
|
this.TransformBindableTo(Progress, 1, holdActivationDelay.Value * (1 - Progress.Value), Easing.Out).OnComplete(_ => Confirm());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Confirm()
|
protected virtual void Confirm()
|
||||||
{
|
{
|
||||||
Action?.Invoke();
|
Action?.Invoke();
|
||||||
fired = true;
|
Fired = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void AbortConfirm()
|
protected void AbortConfirm()
|
||||||
{
|
{
|
||||||
if (!AllowMultipleFires && fired) return;
|
if (!AllowMultipleFires && Fired) return;
|
||||||
|
|
||||||
confirming = false;
|
confirming = false;
|
||||||
|
Fired = false;
|
||||||
|
|
||||||
this.TransformBindableTo(Progress, 0, fadeout_delay, Easing.Out);
|
this.TransformBindableTo(Progress, 0, fadeout_delay, Easing.Out);
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,16 @@ namespace osu.Game.IO
|
|||||||
string path = info.StoragePath;
|
string path = info.StoragePath;
|
||||||
|
|
||||||
// we may be re-adding a file to fix missing store entries.
|
// we may be re-adding a file to fix missing store entries.
|
||||||
if (!Storage.Exists(path))
|
bool requiresCopy = !Storage.Exists(path);
|
||||||
|
|
||||||
|
if (!requiresCopy)
|
||||||
|
{
|
||||||
|
// even if the file already exists, check the existing checksum for safety.
|
||||||
|
using (var stream = Storage.GetStream(path))
|
||||||
|
requiresCopy |= stream.ComputeSHA2Hash() != hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requiresCopy)
|
||||||
{
|
{
|
||||||
data.Seek(0, SeekOrigin.Begin);
|
data.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
public string Url { get; set; }
|
public string Url { get; set; }
|
||||||
|
|
||||||
[JsonProperty("type")]
|
[JsonProperty("type")]
|
||||||
public string Type { get; set; }
|
public ChangelogEntryType Type { get; set; }
|
||||||
|
|
||||||
[JsonProperty("category")]
|
[JsonProperty("category")]
|
||||||
public string Category { get; set; }
|
public string Category { get; set; }
|
||||||
@ -44,4 +44,10 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
[JsonProperty("github_user")]
|
[JsonProperty("github_user")]
|
||||||
public APIChangelogUser GithubUser { get; set; }
|
public APIChangelogUser GithubUser { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum ChangelogEntryType
|
||||||
|
{
|
||||||
|
Add,
|
||||||
|
Fix
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,10 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
|
|
||||||
private bool scoresLoadedOnce;
|
private bool scoresLoadedOnce;
|
||||||
|
|
||||||
|
private readonly Container content;
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content => content;
|
||||||
|
|
||||||
private IEnumerable<ScoreInfo> scores;
|
private IEnumerable<ScoreInfo> scores;
|
||||||
|
|
||||||
public IEnumerable<ScoreInfo> Scores
|
public IEnumerable<ScoreInfo> Scores
|
||||||
@ -60,13 +64,13 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
// ensure placeholder is hidden when displaying scores
|
// ensure placeholder is hidden when displaying scores
|
||||||
PlaceholderState = PlaceholderState.Successful;
|
PlaceholderState = PlaceholderState.Successful;
|
||||||
|
|
||||||
var sf = CreateScoreFlow();
|
var scoreFlow = CreateScoreFlow();
|
||||||
sf.ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1));
|
scoreFlow.ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1));
|
||||||
|
|
||||||
// schedule because we may not be loaded yet (LoadComponentAsync complains).
|
// schedule because we may not be loaded yet (LoadComponentAsync complains).
|
||||||
showScoresDelegate = Schedule(() => LoadComponentAsync(sf, _ =>
|
showScoresDelegate = Schedule(() => LoadComponentAsync(scoreFlow, _ =>
|
||||||
{
|
{
|
||||||
scrollContainer.Add(scrollFlow = sf);
|
scrollContainer.Add(scrollFlow = scoreFlow);
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
@ -116,9 +120,7 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
{
|
{
|
||||||
if (value != PlaceholderState.Successful)
|
if (value != PlaceholderState.Successful)
|
||||||
{
|
{
|
||||||
getScoresRequest?.Cancel();
|
Reset();
|
||||||
getScoresRequest = null;
|
|
||||||
Scores = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value == placeholderState)
|
if (value == placeholderState)
|
||||||
@ -162,12 +164,35 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
|
|
||||||
protected Leaderboard()
|
protected Leaderboard()
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
scrollContainer = new OsuScrollContainer
|
new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
ScrollbarVisible = false,
|
RowDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(),
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
scrollContainer = new OsuScrollContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
ScrollbarVisible = false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
content = new Container
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
loading = new LoadingAnimation(),
|
loading = new LoadingAnimation(),
|
||||||
placeholderContainer = new Container
|
placeholderContainer = new Container
|
||||||
@ -177,6 +202,13 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual void Reset()
|
||||||
|
{
|
||||||
|
getScoresRequest?.Cancel();
|
||||||
|
getScoresRequest = null;
|
||||||
|
Scores = null;
|
||||||
|
}
|
||||||
|
|
||||||
private IAPIProvider api;
|
private IAPIProvider api;
|
||||||
|
|
||||||
private ScheduledDelegate pendingUpdateScores;
|
private ScheduledDelegate pendingUpdateScores;
|
||||||
|
@ -20,23 +20,23 @@ using osu.Game.Scoring;
|
|||||||
using osu.Game.Users.Drawables;
|
using osu.Game.Users.Drawables;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
using Humanizer;
|
||||||
|
|
||||||
namespace osu.Game.Online.Leaderboards
|
namespace osu.Game.Online.Leaderboards
|
||||||
{
|
{
|
||||||
public class LeaderboardScore : OsuClickableContainer
|
public class LeaderboardScore : OsuClickableContainer
|
||||||
{
|
{
|
||||||
public readonly int RankPosition;
|
|
||||||
|
|
||||||
public const float HEIGHT = 60;
|
public const float HEIGHT = 60;
|
||||||
|
|
||||||
private const float corner_radius = 5;
|
private const float corner_radius = 5;
|
||||||
private const float edge_margin = 5;
|
private const float edge_margin = 5;
|
||||||
private const float background_alpha = 0.25f;
|
private const float background_alpha = 0.25f;
|
||||||
private const float rank_width = 30;
|
private const float rank_width = 35;
|
||||||
|
|
||||||
protected Container RankContainer { get; private set; }
|
protected Container RankContainer { get; private set; }
|
||||||
|
|
||||||
private readonly ScoreInfo score;
|
private readonly ScoreInfo score;
|
||||||
|
private readonly int rank;
|
||||||
|
|
||||||
private Box background;
|
private Box background;
|
||||||
private Container content;
|
private Container content;
|
||||||
@ -52,7 +52,7 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
public LeaderboardScore(ScoreInfo score, int rank)
|
public LeaderboardScore(ScoreInfo score, int rank)
|
||||||
{
|
{
|
||||||
this.score = score;
|
this.score = score;
|
||||||
RankPosition = rank;
|
this.rank = rank;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = HEIGHT;
|
Height = HEIGHT;
|
||||||
@ -79,8 +79,8 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Font = OsuFont.GetFont(size: 22, italics: true),
|
Font = OsuFont.GetFont(size: 20, italics: true),
|
||||||
Text = RankPosition.ToString(),
|
Text = rank.ToMetric(decimals: rank < 100000 ? 1 : 0),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -14,6 +14,8 @@ using osu.Game.Graphics.Sprites;
|
|||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using System.Net;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Changelog
|
namespace osu.Game.Overlays.Changelog
|
||||||
{
|
{
|
||||||
@ -66,22 +68,34 @@ namespace osu.Game.Overlays.Changelog
|
|||||||
|
|
||||||
foreach (APIChangelogEntry entry in categoryEntries)
|
foreach (APIChangelogEntry entry in categoryEntries)
|
||||||
{
|
{
|
||||||
LinkFlowContainer title = new LinkFlowContainer
|
|
||||||
{
|
|
||||||
Direction = FillDirection.Full,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Margin = new MarginPadding { Vertical = 5 },
|
|
||||||
};
|
|
||||||
|
|
||||||
var entryColour = entry.Major ? colours.YellowLight : Color4.White;
|
var entryColour = entry.Major ? colours.YellowLight : Color4.White;
|
||||||
|
|
||||||
title.AddIcon(FontAwesome.Solid.Check, t =>
|
LinkFlowContainer title;
|
||||||
|
|
||||||
|
Container titleContainer = new Container
|
||||||
{
|
{
|
||||||
t.Font = fontSmall;
|
AutoSizeAxes = Axes.Y,
|
||||||
t.Colour = entryColour;
|
RelativeSizeAxes = Axes.X,
|
||||||
t.Padding = new MarginPadding { Left = -17, Right = 5 };
|
Margin = new MarginPadding { Vertical = 5 },
|
||||||
});
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
Size = new Vector2(fontSmall.Size),
|
||||||
|
Icon = entry.Type == ChangelogEntryType.Fix ? FontAwesome.Solid.Check : FontAwesome.Solid.Plus,
|
||||||
|
Colour = entryColour,
|
||||||
|
Margin = new MarginPadding { Right = 5 },
|
||||||
|
},
|
||||||
|
title = new LinkFlowContainer
|
||||||
|
{
|
||||||
|
Direction = FillDirection.Full,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
title.AddText(entry.Title, t =>
|
title.AddText(entry.Title, t =>
|
||||||
{
|
{
|
||||||
@ -138,7 +152,7 @@ namespace osu.Game.Overlays.Changelog
|
|||||||
t.Colour = entryColour;
|
t.Colour = entryColour;
|
||||||
});
|
});
|
||||||
|
|
||||||
ChangelogEntries.Add(title);
|
ChangelogEntries.Add(titleContainer);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(entry.MessageHtml))
|
if (!string.IsNullOrEmpty(entry.MessageHtml))
|
||||||
{
|
{
|
||||||
@ -149,7 +163,7 @@ namespace osu.Game.Overlays.Changelog
|
|||||||
};
|
};
|
||||||
|
|
||||||
// todo: use markdown parsing once API returns markdown
|
// todo: use markdown parsing once API returns markdown
|
||||||
message.AddText(Regex.Replace(entry.MessageHtml, @"<(.|\n)*?>", string.Empty), t =>
|
message.AddText(WebUtility.HtmlDecode(Regex.Replace(entry.MessageHtml, @"<(.|\n)*?>", string.Empty)), t =>
|
||||||
{
|
{
|
||||||
t.Font = fontSmall;
|
t.Font = fontSmall;
|
||||||
t.Colour = new Color4(235, 184, 254, 255);
|
t.Colour = new Color4(235, 184, 254, 255);
|
||||||
|
@ -13,7 +13,8 @@ namespace osu.Game.Overlays
|
|||||||
public class DialogOverlay : OsuFocusedOverlayContainer
|
public class DialogOverlay : OsuFocusedOverlayContainer
|
||||||
{
|
{
|
||||||
private readonly Container dialogContainer;
|
private readonly Container dialogContainer;
|
||||||
private PopupDialog currentDialog;
|
|
||||||
|
public PopupDialog CurrentDialog { get; private set; }
|
||||||
|
|
||||||
public DialogOverlay()
|
public DialogOverlay()
|
||||||
{
|
{
|
||||||
@ -31,15 +32,15 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
public void Push(PopupDialog dialog)
|
public void Push(PopupDialog dialog)
|
||||||
{
|
{
|
||||||
if (dialog == currentDialog) return;
|
if (dialog == CurrentDialog) return;
|
||||||
|
|
||||||
currentDialog?.Hide();
|
CurrentDialog?.Hide();
|
||||||
currentDialog = dialog;
|
CurrentDialog = dialog;
|
||||||
|
|
||||||
dialogContainer.Add(currentDialog);
|
dialogContainer.Add(CurrentDialog);
|
||||||
|
|
||||||
currentDialog.Show();
|
CurrentDialog.Show();
|
||||||
currentDialog.State.ValueChanged += state => onDialogOnStateChanged(dialog, state.NewValue);
|
CurrentDialog.State.ValueChanged += state => onDialogOnStateChanged(dialog, state.NewValue);
|
||||||
Show();
|
Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,8 +53,11 @@ namespace osu.Game.Overlays
|
|||||||
//handle the dialog being dismissed.
|
//handle the dialog being dismissed.
|
||||||
dialog.Delay(PopupDialog.EXIT_DURATION).Expire();
|
dialog.Delay(PopupDialog.EXIT_DURATION).Expire();
|
||||||
|
|
||||||
if (dialog == currentDialog)
|
if (dialog == CurrentDialog)
|
||||||
|
{
|
||||||
Hide();
|
Hide();
|
||||||
|
CurrentDialog = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void PopIn()
|
protected override void PopIn()
|
||||||
@ -66,9 +70,9 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
base.PopOut();
|
base.PopOut();
|
||||||
|
|
||||||
if (currentDialog?.State.Value == Visibility.Visible)
|
if (CurrentDialog?.State.Value == Visibility.Visible)
|
||||||
{
|
{
|
||||||
currentDialog.Hide();
|
CurrentDialog.Hide();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +84,7 @@ namespace osu.Game.Overlays
|
|||||||
switch (action)
|
switch (action)
|
||||||
{
|
{
|
||||||
case GlobalAction.Select:
|
case GlobalAction.Select:
|
||||||
currentDialog?.Buttons.OfType<PopupDialogOkButton>().FirstOrDefault()?.Click();
|
CurrentDialog?.Buttons.OfType<PopupDialogOkButton>().FirstOrDefault()?.Click();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
audio.Tracks.RemoveAdjustment(AdjustableProperty.Volume, audioVolume);
|
audio?.Tracks.RemoveAdjustment(AdjustableProperty.Volume, audioVolume);
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
@ -18,7 +19,6 @@ namespace osu.Game.Overlays.Music
|
|||||||
public class PlaylistList : CompositeDrawable
|
public class PlaylistList : CompositeDrawable
|
||||||
{
|
{
|
||||||
public Action<BeatmapSetInfo> Selected;
|
public Action<BeatmapSetInfo> Selected;
|
||||||
public Action<BeatmapSetInfo, int> OrderChanged;
|
|
||||||
|
|
||||||
private readonly ItemsScrollContainer items;
|
private readonly ItemsScrollContainer items;
|
||||||
|
|
||||||
@ -28,7 +28,6 @@ namespace osu.Game.Overlays.Music
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Selected = set => Selected?.Invoke(set),
|
Selected = set => Selected?.Invoke(set),
|
||||||
OrderChanged = (s, i) => OrderChanged?.Invoke(s, i)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,13 +44,17 @@ namespace osu.Game.Overlays.Music
|
|||||||
private class ItemsScrollContainer : OsuScrollContainer
|
private class ItemsScrollContainer : OsuScrollContainer
|
||||||
{
|
{
|
||||||
public Action<BeatmapSetInfo> Selected;
|
public Action<BeatmapSetInfo> Selected;
|
||||||
public Action<BeatmapSetInfo, int> OrderChanged;
|
|
||||||
|
|
||||||
private readonly SearchContainer search;
|
private readonly SearchContainer search;
|
||||||
private readonly FillFlowContainer<PlaylistItem> items;
|
private readonly FillFlowContainer<PlaylistItem> items;
|
||||||
|
|
||||||
private readonly IBindable<WorkingBeatmap> beatmapBacking = new Bindable<WorkingBeatmap>();
|
private readonly IBindable<WorkingBeatmap> beatmapBacking = new Bindable<WorkingBeatmap>();
|
||||||
|
|
||||||
|
private IBindableList<BeatmapSetInfo> beatmaps;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private MusicController musicController { get; set; }
|
||||||
|
|
||||||
public ItemsScrollContainer()
|
public ItemsScrollContainer()
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
@ -73,27 +76,35 @@ namespace osu.Game.Overlays.Music
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(BeatmapManager beatmaps, IBindable<WorkingBeatmap> beatmap)
|
private void load(IBindable<WorkingBeatmap> beatmap)
|
||||||
{
|
{
|
||||||
beatmaps.GetAllUsableBeatmapSets().ForEach(addBeatmapSet);
|
beatmaps = musicController.BeatmapSets.GetBoundCopy();
|
||||||
beatmaps.ItemAdded += addBeatmapSet;
|
beatmaps.ItemsAdded += i => i.ForEach(addBeatmapSet);
|
||||||
beatmaps.ItemRemoved += removeBeatmapSet;
|
beatmaps.ItemsRemoved += i => i.ForEach(removeBeatmapSet);
|
||||||
|
beatmaps.ForEach(addBeatmapSet);
|
||||||
|
|
||||||
beatmapBacking.BindTo(beatmap);
|
beatmapBacking.BindTo(beatmap);
|
||||||
beatmapBacking.ValueChanged += _ => updateSelectedSet();
|
beatmapBacking.ValueChanged += _ => updateSelectedSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addBeatmapSet(BeatmapSetInfo obj) => Schedule(() =>
|
private void addBeatmapSet(BeatmapSetInfo obj)
|
||||||
{
|
{
|
||||||
items.Insert(items.Count - 1, new PlaylistItem(obj) { OnSelect = set => Selected?.Invoke(set) });
|
if (obj == draggedItem?.BeatmapSetInfo) return;
|
||||||
});
|
|
||||||
|
|
||||||
private void removeBeatmapSet(BeatmapSetInfo obj) => Schedule(() =>
|
Schedule(() => items.Insert(items.Count - 1, new PlaylistItem(obj) { OnSelect = set => Selected?.Invoke(set) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeBeatmapSet(BeatmapSetInfo obj)
|
||||||
{
|
{
|
||||||
var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == obj.ID);
|
if (obj == draggedItem?.BeatmapSetInfo) return;
|
||||||
if (itemToRemove != null)
|
|
||||||
items.Remove(itemToRemove);
|
Schedule(() =>
|
||||||
});
|
{
|
||||||
|
var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == obj.ID);
|
||||||
|
if (itemToRemove != null)
|
||||||
|
items.Remove(itemToRemove);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void updateSelectedSet()
|
private void updateSelectedSet()
|
||||||
{
|
{
|
||||||
@ -112,6 +123,8 @@ namespace osu.Game.Overlays.Music
|
|||||||
private Vector2 nativeDragPosition;
|
private Vector2 nativeDragPosition;
|
||||||
private PlaylistItem draggedItem;
|
private PlaylistItem draggedItem;
|
||||||
|
|
||||||
|
private int? dragDestination;
|
||||||
|
|
||||||
protected override bool OnDragStart(DragStartEvent e)
|
protected override bool OnDragStart(DragStartEvent e)
|
||||||
{
|
{
|
||||||
nativeDragPosition = e.ScreenSpaceMousePosition;
|
nativeDragPosition = e.ScreenSpaceMousePosition;
|
||||||
@ -131,10 +144,17 @@ namespace osu.Game.Overlays.Music
|
|||||||
protected override bool OnDragEnd(DragEndEvent e)
|
protected override bool OnDragEnd(DragEndEvent e)
|
||||||
{
|
{
|
||||||
nativeDragPosition = e.ScreenSpaceMousePosition;
|
nativeDragPosition = e.ScreenSpaceMousePosition;
|
||||||
var handled = draggedItem != null || base.OnDragEnd(e);
|
|
||||||
draggedItem = null;
|
|
||||||
|
|
||||||
return handled;
|
if (draggedItem == null)
|
||||||
|
return base.OnDragEnd(e);
|
||||||
|
|
||||||
|
if (dragDestination != null)
|
||||||
|
musicController.ChangeBeatmapSetPosition(draggedItem.BeatmapSetInfo, dragDestination.Value);
|
||||||
|
|
||||||
|
draggedItem = null;
|
||||||
|
dragDestination = null;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
@ -210,7 +230,7 @@ namespace osu.Game.Overlays.Music
|
|||||||
}
|
}
|
||||||
|
|
||||||
items.SetLayoutPosition(draggedItem, dstIndex);
|
items.SetLayoutPosition(draggedItem, dstIndex);
|
||||||
OrderChanged?.Invoke(draggedItem.BeatmapSetInfo, dstIndex);
|
dragDestination = dstIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ItemSearchContainer : FillFlowContainer<PlaylistItem>, IHasFilterableChildren
|
private class ItemSearchContainer : FillFlowContainer<PlaylistItem>, IHasFilterableChildren
|
||||||
|
@ -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.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -22,12 +21,6 @@ namespace osu.Game.Overlays.Music
|
|||||||
private const float transition_duration = 600;
|
private const float transition_duration = 600;
|
||||||
private const float playlist_height = 510;
|
private const float playlist_height = 510;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invoked when the order of an item in the list has changed.
|
|
||||||
/// The second parameter indicates the new index of the item.
|
|
||||||
/// </summary>
|
|
||||||
public Action<BeatmapSetInfo, int> OrderChanged;
|
|
||||||
|
|
||||||
private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
|
private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
|
||||||
private BeatmapManager beatmaps;
|
private BeatmapManager beatmaps;
|
||||||
|
|
||||||
@ -65,7 +58,6 @@ namespace osu.Game.Overlays.Music
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Top = 95, Bottom = 10, Right = 10 },
|
Padding = new MarginPadding { Top = 95, Bottom = 10, Right = 10 },
|
||||||
Selected = itemSelected,
|
Selected = itemSelected,
|
||||||
OrderChanged = (s, i) => OrderChanged?.Invoke(s, i)
|
|
||||||
},
|
},
|
||||||
filter = new FilterControl
|
filter = new FilterControl
|
||||||
{
|
{
|
||||||
|
@ -8,6 +8,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.MathUtils;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
@ -24,7 +25,9 @@ namespace osu.Game.Overlays
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private BeatmapManager beatmaps { get; set; }
|
private BeatmapManager beatmaps { get; set; }
|
||||||
|
|
||||||
private List<BeatmapSetInfo> beatmapSets;
|
public IBindableList<BeatmapSetInfo> BeatmapSets => beatmapSets;
|
||||||
|
|
||||||
|
private readonly BindableList<BeatmapSetInfo> beatmapSets = new BindableList<BeatmapSetInfo>();
|
||||||
|
|
||||||
public bool IsUserPaused { get; private set; }
|
public bool IsUserPaused { get; private set; }
|
||||||
|
|
||||||
@ -46,7 +49,7 @@ namespace osu.Game.Overlays
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
beatmapSets = beatmaps.GetAllUsableBeatmapSets();
|
beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next()));
|
||||||
beatmaps.ItemAdded += handleBeatmapAdded;
|
beatmaps.ItemAdded += handleBeatmapAdded;
|
||||||
beatmaps.ItemRemoved += handleBeatmapRemoved;
|
beatmaps.ItemRemoved += handleBeatmapRemoved;
|
||||||
}
|
}
|
||||||
@ -140,7 +143,7 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
queuedDirection = TrackChangeDirection.Prev;
|
queuedDirection = TrackChangeDirection.Prev;
|
||||||
|
|
||||||
var playable = beatmapSets.TakeWhile(i => i.ID != current.BeatmapSetInfo.ID).LastOrDefault() ?? beatmapSets.LastOrDefault();
|
var playable = BeatmapSets.TakeWhile(i => i.ID != current.BeatmapSetInfo.ID).LastOrDefault() ?? BeatmapSets.LastOrDefault();
|
||||||
|
|
||||||
if (playable != null)
|
if (playable != null)
|
||||||
{
|
{
|
||||||
@ -165,7 +168,7 @@ namespace osu.Game.Overlays
|
|||||||
if (!instant)
|
if (!instant)
|
||||||
queuedDirection = TrackChangeDirection.Next;
|
queuedDirection = TrackChangeDirection.Next;
|
||||||
|
|
||||||
var playable = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? beatmapSets.FirstOrDefault();
|
var playable = BeatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? BeatmapSets.FirstOrDefault();
|
||||||
|
|
||||||
if (playable != null)
|
if (playable != null)
|
||||||
{
|
{
|
||||||
@ -200,8 +203,8 @@ namespace osu.Game.Overlays
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
//figure out the best direction based on order in playlist.
|
//figure out the best direction based on order in playlist.
|
||||||
var last = beatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count();
|
var last = BeatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count();
|
||||||
var next = beatmap.NewValue == null ? -1 : beatmapSets.TakeWhile(b => b.ID != beatmap.NewValue.BeatmapSetInfo?.ID).Count();
|
var next = beatmap.NewValue == null ? -1 : BeatmapSets.TakeWhile(b => b.ID != beatmap.NewValue.BeatmapSetInfo?.ID).Count();
|
||||||
|
|
||||||
direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next;
|
direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next;
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,6 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Y = player_height + 10,
|
Y = player_height + 10,
|
||||||
OrderChanged = musicController.ChangeBeatmapSetPosition
|
|
||||||
},
|
},
|
||||||
playerContainer = new Container
|
playerContainer = new Container
|
||||||
{
|
{
|
||||||
|
@ -28,8 +28,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
},
|
},
|
||||||
new SettingsCheckbox
|
new SettingsCheckbox
|
||||||
{
|
{
|
||||||
LabelText = "Rotate cursor when dragging",
|
LabelText = "Hit Lighting",
|
||||||
Bindable = config.GetBindable<bool>(OsuSetting.CursorRotation)
|
Bindable = config.GetBindable<bool>(OsuSetting.HitLighting)
|
||||||
},
|
},
|
||||||
new SettingsEnumDropdown<ScreenshotFormat>
|
new SettingsEnumDropdown<ScreenshotFormat>
|
||||||
{
|
{
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Game.Configuration;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|
||||||
{
|
|
||||||
public class MainMenuSettings : SettingsSubsection
|
|
||||||
{
|
|
||||||
protected override string Header => "User Interface";
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OsuConfigManager config)
|
|
||||||
{
|
|
||||||
Children = new[]
|
|
||||||
{
|
|
||||||
new SettingsCheckbox
|
|
||||||
{
|
|
||||||
LabelText = "Parallax",
|
|
||||||
Bindable = config.GetBindable<bool>(OsuSetting.MenuParallax)
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,44 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||||
|
{
|
||||||
|
public class UserInterfaceSettings : SettingsSubsection
|
||||||
|
{
|
||||||
|
protected override string Header => "User Interface";
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = "Rotate cursor when dragging",
|
||||||
|
Bindable = config.GetBindable<bool>(OsuSetting.CursorRotation)
|
||||||
|
},
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = "Parallax",
|
||||||
|
Bindable = config.GetBindable<bool>(OsuSetting.MenuParallax)
|
||||||
|
},
|
||||||
|
new SettingsSlider<int, TimeSlider>
|
||||||
|
{
|
||||||
|
LabelText = "Hold-to-confirm activation time",
|
||||||
|
Bindable = config.GetBindable<int>(OsuSetting.UIHoldActivationDelay),
|
||||||
|
KeyboardStep = 50
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TimeSlider : OsuSliderBar<int>
|
||||||
|
{
|
||||||
|
public override string TooltipText => Current.Value.ToString("N0") + "ms";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections
|
|||||||
new RendererSettings(),
|
new RendererSettings(),
|
||||||
new LayoutSettings(),
|
new LayoutSettings(),
|
||||||
new DetailSettings(),
|
new DetailSettings(),
|
||||||
new MainMenuSettings(),
|
new UserInterfaceSettings(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Judgements
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class DrawableJudgement : CompositeDrawable
|
public class DrawableJudgement : CompositeDrawable
|
||||||
{
|
{
|
||||||
private const float judgement_size = 80;
|
private const float judgement_size = 128;
|
||||||
|
|
||||||
private OsuColour colours;
|
private OsuColour colours;
|
||||||
|
|
||||||
@ -34,10 +34,14 @@ namespace osu.Game.Rulesets.Judgements
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Duration of initial fade in.
|
/// Duration of initial fade in.
|
||||||
/// Default fade out will start immediately after this duration.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual double FadeInDuration => 100;
|
protected virtual double FadeInDuration => 100;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Duration to wait until fade out begins. Defaults to <see cref="FadeInDuration"/>.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual double FadeOutDelay => FadeInDuration;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a drawable which visualises a <see cref="Judgements.Judgement"/>.
|
/// Creates a drawable which visualises a <see cref="Judgements.Judgement"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -64,10 +68,10 @@ namespace osu.Game.Rulesets.Judgements
|
|||||||
Child = new SkinnableDrawable(new GameplaySkinComponent<HitResult>(Result.Type), _ => JudgementText = new OsuSpriteText
|
Child = new SkinnableDrawable(new GameplaySkinComponent<HitResult>(Result.Type), _ => JudgementText = new OsuSpriteText
|
||||||
{
|
{
|
||||||
Text = Result.Type.GetDescription().ToUpperInvariant(),
|
Text = Result.Type.GetDescription().ToUpperInvariant(),
|
||||||
Font = OsuFont.Numeric.With(size: 12),
|
Font = OsuFont.Numeric.With(size: 20),
|
||||||
Colour = judgementColour(Result.Type),
|
Colour = judgementColour(Result.Type),
|
||||||
Scale = new Vector2(0.85f, 1),
|
Scale = new Vector2(0.85f, 1),
|
||||||
})
|
}, confineMode: ConfineMode.NoScaling)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,7 +80,7 @@ namespace osu.Game.Rulesets.Judgements
|
|||||||
JudgementBody.ScaleTo(0.9f);
|
JudgementBody.ScaleTo(0.9f);
|
||||||
JudgementBody.ScaleTo(1, 500, Easing.OutElastic);
|
JudgementBody.ScaleTo(1, 500, Easing.OutElastic);
|
||||||
|
|
||||||
this.Delay(FadeInDuration).FadeOut(400);
|
this.Delay(FadeOutDelay).FadeOut(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
|
@ -12,5 +12,10 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
/// Whether we should allow failing at the current point in time.
|
/// Whether we should allow failing at the current point in time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool AllowFail { get; }
|
bool AllowFail { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether we want to restart on fail. Only used if <see cref="AllowFail"/> is true.
|
||||||
|
/// </summary>
|
||||||
|
bool RestartOnFail { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,10 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
public override ModType Type => ModType.Automation;
|
public override ModType Type => ModType.Automation;
|
||||||
public override string Description => "Watch a perfect automated play through the song.";
|
public override string Description => "Watch a perfect automated play through the song.";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
public bool AllowFail => false;
|
public bool AllowFail => false;
|
||||||
|
public bool RestartOnFail => false;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) };
|
||||||
|
|
||||||
public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0;
|
public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0;
|
||||||
|
@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AllowFail => false;
|
public bool AllowFail => false;
|
||||||
|
|
||||||
|
public bool RestartOnFail => false;
|
||||||
|
|
||||||
public void ReadFromConfig(OsuConfigManager config)
|
public void ReadFromConfig(OsuConfigManager config)
|
||||||
{
|
{
|
||||||
showHealthBar = config.GetBindable<bool>(OsuSetting.ShowHealthDisplayWhenCantFail);
|
showHealthBar = config.GetBindable<bool>(OsuSetting.ShowHealthDisplayWhenCantFail);
|
||||||
|
@ -2,13 +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 osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mods
|
namespace osu.Game.Rulesets.Mods
|
||||||
{
|
{
|
||||||
public abstract class ModEasy : Mod, IApplicableToDifficulty
|
public abstract class ModEasy : Mod, IApplicableToDifficulty, IApplicableFailOverride, IApplicableToScoreProcessor
|
||||||
{
|
{
|
||||||
public override string Name => "Easy";
|
public override string Name => "Easy";
|
||||||
public override string Acronym => "EZ";
|
public override string Acronym => "EZ";
|
||||||
@ -18,6 +21,10 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
public override bool Ranked => true;
|
public override bool Ranked => true;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModHardRock) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModHardRock) };
|
||||||
|
|
||||||
|
private int retries = 2;
|
||||||
|
|
||||||
|
private BindableNumber<double> health;
|
||||||
|
|
||||||
public void ApplyToDifficulty(BeatmapDifficulty difficulty)
|
public void ApplyToDifficulty(BeatmapDifficulty difficulty)
|
||||||
{
|
{
|
||||||
const float ratio = 0.5f;
|
const float ratio = 0.5f;
|
||||||
@ -26,5 +33,27 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
difficulty.DrainRate *= ratio;
|
difficulty.DrainRate *= ratio;
|
||||||
difficulty.OverallDifficulty *= ratio;
|
difficulty.OverallDifficulty *= ratio;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool AllowFail
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (retries == 0) return true;
|
||||||
|
|
||||||
|
health.Value = health.MaxValue;
|
||||||
|
retries--;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RestartOnFail => false;
|
||||||
|
|
||||||
|
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
||||||
|
{
|
||||||
|
health = scoreProcessor.Health.GetBoundCopy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ using osu.Game.Scoring;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mods
|
namespace osu.Game.Rulesets.Mods
|
||||||
{
|
{
|
||||||
public abstract class ModSuddenDeath : Mod, IApplicableToScoreProcessor
|
public abstract class ModSuddenDeath : Mod, IApplicableToScoreProcessor, IApplicableFailOverride
|
||||||
{
|
{
|
||||||
public override string Name => "Sudden Death";
|
public override string Name => "Sudden Death";
|
||||||
public override string Acronym => "SD";
|
public override string Acronym => "SD";
|
||||||
@ -21,6 +21,9 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
public override bool Ranked => true;
|
public override bool Ranked => true;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) };
|
||||||
|
|
||||||
|
public bool AllowFail => true;
|
||||||
|
public bool RestartOnFail => true;
|
||||||
|
|
||||||
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
||||||
{
|
{
|
||||||
scoreProcessor.FailConditions += FailCondition;
|
scoreProcessor.FailConditions += FailCondition;
|
||||||
|
@ -240,7 +240,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
protected sealed override void SkinChanged(ISkinSource skin, bool allowFallback)
|
||||||
{
|
{
|
||||||
base.SkinChanged(skin, allowFallback);
|
base.SkinChanged(skin, allowFallback);
|
||||||
|
|
||||||
@ -250,6 +250,20 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
|
|
||||||
AccentColour.Value = comboColours?.Count > 0 ? comboColours[combo.ComboIndex % comboColours.Count] : Color4.White;
|
AccentColour.Value = comboColours?.Count > 0 ? comboColours[combo.ComboIndex % comboColours.Count] : Color4.White;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ApplySkin(skin, allowFallback);
|
||||||
|
|
||||||
|
if (IsLoaded)
|
||||||
|
updateState(State.Value, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when a change is made to the skin.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="skin">The new skin.</param>
|
||||||
|
/// <param name="allowFallback">Whether fallback to default skin should be allowed if the custom skin is missing this resource.</param>
|
||||||
|
protected virtual void ApplySkin(ISkinSource skin, bool allowFallback)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -84,7 +84,7 @@ namespace osu.Game.Rulesets
|
|||||||
|
|
||||||
public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.QuestionCircle };
|
public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.QuestionCircle };
|
||||||
|
|
||||||
public virtual IResourceStore<byte[]> CreateReourceStore() => new NamespacedResourceStore<byte[]>(new DllResourceStore(GetType().Assembly.Location), @"Resources");
|
public virtual IResourceStore<byte[]> CreateResourceStore() => new NamespacedResourceStore<byte[]>(new DllResourceStore(GetType().Assembly.Location), @"Resources");
|
||||||
|
|
||||||
public abstract string Description { get; }
|
public abstract string Description { get; }
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
{
|
{
|
||||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||||
|
|
||||||
var resources = Ruleset.CreateReourceStore();
|
var resources = Ruleset.CreateResourceStore();
|
||||||
|
|
||||||
if (resources != null)
|
if (resources != null)
|
||||||
{
|
{
|
||||||
|
@ -137,9 +137,9 @@ namespace osu.Game.Rulesets.UI
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool OnPressed(T action) => Target.Children.OfType<KeyCounterAction<T>>().Any(c => c.OnPressed(action));
|
public bool OnPressed(T action) => Target.Children.OfType<KeyCounterAction<T>>().Any(c => c.OnPressed(action, Clock.ElapsedFrameTime > 0));
|
||||||
|
|
||||||
public bool OnReleased(T action) => Target.Children.OfType<KeyCounterAction<T>>().Any(c => c.OnReleased(action));
|
public bool OnReleased(T action) => Target.Children.OfType<KeyCounterAction<T>>().Any(c => c.OnReleased(action, Clock.ElapsedFrameTime > 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -22,6 +22,6 @@ namespace osu.Game.Scoring.Legacy
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override Ruleset GetRuleset(int rulesetId) => rulesets.GetRuleset(rulesetId).CreateInstance();
|
protected override Ruleset GetRuleset(int rulesetId) => rulesets.GetRuleset(rulesetId).CreateInstance();
|
||||||
protected override WorkingBeatmap GetBeatmap(string md5Hash) => beatmaps.GetWorkingBeatmap(beatmaps.QueryBeatmap(b => b.MD5Hash == md5Hash));
|
protected override WorkingBeatmap GetBeatmap(string md5Hash) => beatmaps.GetWorkingBeatmap(beatmaps.QueryBeatmap(b => !b.BeatmapSet.DeletePending && b.MD5Hash == md5Hash));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,132 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents
|
||||||
|
{
|
||||||
|
public abstract class LabelledComponent : CompositeDrawable
|
||||||
|
{
|
||||||
|
protected const float CONTENT_PADDING_VERTICAL = 10;
|
||||||
|
protected const float CONTENT_PADDING_HORIZONTAL = 15;
|
||||||
|
protected const float CORNER_RADIUS = 15;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The component that is being displayed.
|
||||||
|
/// </summary>
|
||||||
|
protected readonly Drawable Component;
|
||||||
|
|
||||||
|
private readonly OsuTextFlowContainer labelText;
|
||||||
|
private readonly OsuTextFlowContainer descriptionText;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="LabelledComponent"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="padded">Whether the component should be padded or should be expanded to the bounds of this <see cref="LabelledComponent"/>.</param>
|
||||||
|
protected LabelledComponent(bool padded)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
CornerRadius = CORNER_RADIUS;
|
||||||
|
Masking = true;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = OsuColour.FromHex("1c2125"),
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Padding = padded
|
||||||
|
? new MarginPadding { Horizontal = CONTENT_PADDING_HORIZONTAL, Vertical = CONTENT_PADDING_VERTICAL }
|
||||||
|
: new MarginPadding { Left = CONTENT_PADDING_HORIZONTAL },
|
||||||
|
Spacing = new Vector2(0, 12),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
labelText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(weight: FontWeight.Bold))
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding { Right = 20 }
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Child = Component = CreateComponent().With(d =>
|
||||||
|
{
|
||||||
|
d.Anchor = Anchor.CentreRight;
|
||||||
|
d.Origin = Anchor.CentreRight;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
||||||
|
ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
|
||||||
|
},
|
||||||
|
descriptionText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold, italics: true))
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Padding = new MarginPadding { Bottom = padded ? 0 : CONTENT_PADDING_VERTICAL },
|
||||||
|
Alpha = 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour osuColour)
|
||||||
|
{
|
||||||
|
descriptionText.Colour = osuColour.Yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Label
|
||||||
|
{
|
||||||
|
set => labelText.Text = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Description
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
descriptionText.Text = value;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(value))
|
||||||
|
descriptionText.Show();
|
||||||
|
else
|
||||||
|
descriptionText.Hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates the component that should be displayed.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The component.</returns>
|
||||||
|
protected abstract Drawable CreateComponent();
|
||||||
|
}
|
||||||
|
}
|
@ -31,6 +31,8 @@ namespace osu.Game.Screens.Menu
|
|||||||
{
|
{
|
||||||
public event Action<ButtonState> StateChanged;
|
public event Action<ButtonState> StateChanged;
|
||||||
|
|
||||||
|
public readonly Key TriggerKey;
|
||||||
|
|
||||||
private readonly Container iconText;
|
private readonly Container iconText;
|
||||||
private readonly Container box;
|
private readonly Container box;
|
||||||
private readonly Box boxHoverLayer;
|
private readonly Box boxHoverLayer;
|
||||||
@ -43,7 +45,6 @@ namespace osu.Game.Screens.Menu
|
|||||||
public ButtonSystemState VisibleState = ButtonSystemState.TopLevel;
|
public ButtonSystemState VisibleState = ButtonSystemState.TopLevel;
|
||||||
|
|
||||||
private readonly Action clickAction;
|
private readonly Action clickAction;
|
||||||
private readonly Key triggerKey;
|
|
||||||
private SampleChannel sampleClick;
|
private SampleChannel sampleClick;
|
||||||
private SampleChannel sampleHover;
|
private SampleChannel sampleHover;
|
||||||
|
|
||||||
@ -53,7 +54,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
{
|
{
|
||||||
this.sampleName = sampleName;
|
this.sampleName = sampleName;
|
||||||
this.clickAction = clickAction;
|
this.clickAction = clickAction;
|
||||||
this.triggerKey = triggerKey;
|
TriggerKey = triggerKey;
|
||||||
|
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
Alpha = 0;
|
Alpha = 0;
|
||||||
@ -210,7 +211,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
if (e.Repeat || e.ControlPressed || e.ShiftPressed || e.AltPressed)
|
if (e.Repeat || e.ControlPressed || e.ShiftPressed || e.AltPressed)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (triggerKey == e.Key && triggerKey != Key.Unknown)
|
if (TriggerKey == e.Key && TriggerKey != Key.Unknown)
|
||||||
{
|
{
|
||||||
trigger();
|
trigger();
|
||||||
return true;
|
return true;
|
||||||
|
@ -14,6 +14,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
@ -180,6 +181,20 @@ namespace osu.Game.Screens.Menu
|
|||||||
State = ButtonSystemState.Initial;
|
State = ButtonSystemState.Initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
|
{
|
||||||
|
if (State == ButtonSystemState.Initial)
|
||||||
|
{
|
||||||
|
if (buttonsTopLevel.Any(b => e.Key == b.TriggerKey))
|
||||||
|
{
|
||||||
|
logo?.Click();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnKeyDown(e);
|
||||||
|
}
|
||||||
|
|
||||||
public bool OnPressed(GlobalAction action)
|
public bool OnPressed(GlobalAction action)
|
||||||
{
|
{
|
||||||
switch (action)
|
switch (action)
|
||||||
|
@ -173,7 +173,11 @@ namespace osu.Game.Screens.Menu
|
|||||||
.Then(5500)
|
.Then(5500)
|
||||||
.FadeOut(250)
|
.FadeOut(250)
|
||||||
.ScaleTo(0.9f, 250, Easing.InQuint)
|
.ScaleTo(0.9f, 250, Easing.InQuint)
|
||||||
.Finally(d => this.Push(nextScreen));
|
.Finally(d =>
|
||||||
|
{
|
||||||
|
if (nextScreen != null)
|
||||||
|
this.Push(nextScreen);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,10 @@ namespace osu.Game.Screens.Menu
|
|||||||
{
|
{
|
||||||
public class ExitConfirmOverlay : HoldToConfirmOverlay, IKeyBindingHandler<GlobalAction>
|
public class ExitConfirmOverlay : HoldToConfirmOverlay, IKeyBindingHandler<GlobalAction>
|
||||||
{
|
{
|
||||||
|
protected override bool AllowMultipleFires => true;
|
||||||
|
|
||||||
|
public void Abort() => AbortConfirm();
|
||||||
|
|
||||||
public bool OnPressed(GlobalAction action)
|
public bool OnPressed(GlobalAction action)
|
||||||
{
|
{
|
||||||
if (action == GlobalAction.Back)
|
if (action == GlobalAction.Back)
|
||||||
@ -24,7 +28,8 @@ namespace osu.Game.Screens.Menu
|
|||||||
{
|
{
|
||||||
if (action == GlobalAction.Back)
|
if (action == GlobalAction.Back)
|
||||||
{
|
{
|
||||||
AbortConfirm();
|
if (!Fired)
|
||||||
|
AbortConfirm();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
// 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 osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Dialog;
|
||||||
using osu.Game.Screens.Backgrounds;
|
using osu.Game.Screens.Backgrounds;
|
||||||
using osu.Game.Screens.Charts;
|
using osu.Game.Screens.Charts;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
@ -51,15 +55,35 @@ namespace osu.Game.Screens.Menu
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private IAPIProvider api { get; set; }
|
private IAPIProvider api { get; set; }
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private DialogOverlay dialogOverlay { get; set; }
|
||||||
|
|
||||||
private BackgroundScreenDefault background;
|
private BackgroundScreenDefault background;
|
||||||
|
|
||||||
protected override BackgroundScreen CreateBackground() => background;
|
protected override BackgroundScreen CreateBackground() => background;
|
||||||
|
|
||||||
|
private Bindable<int> holdDelay;
|
||||||
|
|
||||||
|
private ExitConfirmOverlay exitConfirmOverlay;
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(DirectOverlay direct, SettingsOverlay settings)
|
private void load(DirectOverlay direct, SettingsOverlay settings, OsuConfigManager config)
|
||||||
{
|
{
|
||||||
|
holdDelay = config.GetBindable<int>(OsuSetting.UIHoldActivationDelay);
|
||||||
|
|
||||||
if (host.CanExit)
|
if (host.CanExit)
|
||||||
AddInternal(new ExitConfirmOverlay { Action = this.Exit });
|
{
|
||||||
|
AddInternal(exitConfirmOverlay = new ExitConfirmOverlay
|
||||||
|
{
|
||||||
|
Action = () =>
|
||||||
|
{
|
||||||
|
if (holdDelay.Value > 0)
|
||||||
|
confirmAndExit();
|
||||||
|
else
|
||||||
|
this.Exit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
AddRangeInternal(new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
{
|
{
|
||||||
@ -74,7 +98,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
OnEdit = delegate { this.Push(new Editor()); },
|
OnEdit = delegate { this.Push(new Editor()); },
|
||||||
OnSolo = onSolo,
|
OnSolo = onSolo,
|
||||||
OnMulti = delegate { this.Push(new Multiplayer()); },
|
OnMulti = delegate { this.Push(new Multiplayer()); },
|
||||||
OnExit = this.Exit,
|
OnExit = confirmAndExit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -103,6 +127,12 @@ namespace osu.Game.Screens.Menu
|
|||||||
preloadSongSelect();
|
preloadSongSelect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void confirmAndExit()
|
||||||
|
{
|
||||||
|
exitConfirmed = true;
|
||||||
|
this.Exit();
|
||||||
|
}
|
||||||
|
|
||||||
private void preloadSongSelect()
|
private void preloadSongSelect()
|
||||||
{
|
{
|
||||||
if (songSelect == null)
|
if (songSelect == null)
|
||||||
@ -141,6 +171,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
}
|
}
|
||||||
|
|
||||||
private bool loginDisplayed;
|
private bool loginDisplayed;
|
||||||
|
private bool exitConfirmed;
|
||||||
|
|
||||||
protected override void LogoArriving(OsuLogo logo, bool resuming)
|
protected override void LogoArriving(OsuLogo logo, bool resuming)
|
||||||
{
|
{
|
||||||
@ -221,9 +252,40 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
public override bool OnExiting(IScreen next)
|
public override bool OnExiting(IScreen next)
|
||||||
{
|
{
|
||||||
|
if (!exitConfirmed && dialogOverlay != null && !(dialogOverlay.CurrentDialog is ConfirmExitDialog))
|
||||||
|
{
|
||||||
|
dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
buttons.State = ButtonSystemState.Exit;
|
buttons.State = ButtonSystemState.Exit;
|
||||||
this.FadeOut(3000);
|
this.FadeOut(3000);
|
||||||
return base.OnExiting(next);
|
return base.OnExiting(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ConfirmExitDialog : PopupDialog
|
||||||
|
{
|
||||||
|
public ConfirmExitDialog(Action confirm, Action cancel)
|
||||||
|
{
|
||||||
|
HeaderText = "Are you sure you want to exit?";
|
||||||
|
BodyText = "Last chance to back out.";
|
||||||
|
|
||||||
|
Icon = FontAwesome.Solid.ExclamationTriangle;
|
||||||
|
|
||||||
|
Buttons = new PopupDialogButton[]
|
||||||
|
{
|
||||||
|
new PopupDialogOkButton
|
||||||
|
{
|
||||||
|
Text = @"Good bye",
|
||||||
|
Action = confirm
|
||||||
|
},
|
||||||
|
new PopupDialogCancelButton
|
||||||
|
{
|
||||||
|
Text = @"Just a little more",
|
||||||
|
Action = cancel
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,5 +42,7 @@ namespace osu.Game.Screens.Play
|
|||||||
public double FramesPerSecond => underlyingClock.FramesPerSecond;
|
public double FramesPerSecond => underlyingClock.FramesPerSecond;
|
||||||
|
|
||||||
public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo;
|
public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo;
|
||||||
|
|
||||||
|
public IClock Source => underlyingClock;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -231,7 +231,6 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
protected virtual KeyCounterDisplay CreateKeyCounter() => new KeyCounterDisplay
|
protected virtual KeyCounterDisplay CreateKeyCounter() => new KeyCounterDisplay
|
||||||
{
|
{
|
||||||
FadeTime = 50,
|
|
||||||
Anchor = Anchor.BottomRight,
|
Anchor = Anchor.BottomRight,
|
||||||
Origin = Anchor.BottomRight,
|
Origin = Anchor.BottomRight,
|
||||||
Margin = new MarginPadding(10),
|
Margin = new MarginPadding(10),
|
||||||
|
@ -1,8 +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.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -22,9 +20,6 @@ namespace osu.Game.Screens.Play
|
|||||||
private Container textLayer;
|
private Container textLayer;
|
||||||
private SpriteText countSpriteText;
|
private SpriteText countSpriteText;
|
||||||
|
|
||||||
private readonly List<KeyCounterState> states = new List<KeyCounterState>();
|
|
||||||
private KeyCounterState currentState;
|
|
||||||
|
|
||||||
public bool IsCounting { get; set; } = true;
|
public bool IsCounting { get; set; } = true;
|
||||||
private int countPresses;
|
private int countPresses;
|
||||||
|
|
||||||
@ -52,20 +47,30 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
isLit = value;
|
isLit = value;
|
||||||
updateGlowSprite(value);
|
updateGlowSprite(value);
|
||||||
|
|
||||||
if (value && IsCounting)
|
|
||||||
{
|
|
||||||
CountPresses++;
|
|
||||||
saveState();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Increment()
|
||||||
|
{
|
||||||
|
if (!IsCounting)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CountPresses++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Decrement()
|
||||||
|
{
|
||||||
|
if (!IsCounting)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CountPresses--;
|
||||||
|
}
|
||||||
|
|
||||||
//further: change default values here and in KeyCounterCollection if needed, instead of passing them in every constructor
|
//further: change default values here and in KeyCounterCollection if needed, instead of passing them in every constructor
|
||||||
public Color4 KeyDownTextColor { get; set; } = Color4.DarkGray;
|
public Color4 KeyDownTextColor { get; set; } = Color4.DarkGray;
|
||||||
public Color4 KeyUpTextColor { get; set; } = Color4.White;
|
public Color4 KeyUpTextColor { get; set; } = Color4.White;
|
||||||
public int FadeTime { get; set; }
|
public double FadeTime { get; set; }
|
||||||
|
|
||||||
protected KeyCounter(string name)
|
protected KeyCounter(string name)
|
||||||
{
|
{
|
||||||
@ -73,11 +78,8 @@ namespace osu.Game.Screens.Play
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(TextureStore textures, GameplayClock clock)
|
private void load(TextureStore textures)
|
||||||
{
|
{
|
||||||
if (clock != null)
|
|
||||||
Clock = clock;
|
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
buttonSprite = new Sprite
|
buttonSprite = new Sprite
|
||||||
@ -132,42 +134,16 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
if (show)
|
if (show)
|
||||||
{
|
{
|
||||||
glowSprite.FadeIn(FadeTime);
|
double remainingFadeTime = FadeTime * (1 - glowSprite.Alpha);
|
||||||
textLayer.FadeColour(KeyDownTextColor, FadeTime);
|
glowSprite.FadeIn(remainingFadeTime, Easing.OutQuint);
|
||||||
|
textLayer.FadeColour(KeyDownTextColor, remainingFadeTime, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
glowSprite.FadeOut(FadeTime);
|
double remainingFadeTime = 8 * FadeTime * glowSprite.Alpha;
|
||||||
textLayer.FadeColour(KeyUpTextColor, FadeTime);
|
glowSprite.FadeOut(remainingFadeTime, Easing.OutQuint);
|
||||||
|
textLayer.FadeColour(KeyUpTextColor, remainingFadeTime, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetCount()
|
|
||||||
{
|
|
||||||
CountPresses = 0;
|
|
||||||
states.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
|
|
||||||
if (currentState?.Time > Clock.CurrentTime)
|
|
||||||
restoreStateTo(Clock.CurrentTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveState()
|
|
||||||
{
|
|
||||||
if (currentState == null || currentState.Time < Clock.CurrentTime)
|
|
||||||
states.Add(currentState = new KeyCounterState(Clock.CurrentTime, CountPresses));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void restoreStateTo(double time)
|
|
||||||
{
|
|
||||||
states.RemoveAll(state => state.Time > time);
|
|
||||||
|
|
||||||
currentState = states.LastOrDefault();
|
|
||||||
CountPresses = currentState?.Count ?? 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
// 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.Input.Bindings;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play
|
namespace osu.Game.Screens.Play
|
||||||
{
|
{
|
||||||
public class KeyCounterAction<T> : KeyCounter, IKeyBindingHandler<T>
|
public class KeyCounterAction<T> : KeyCounter
|
||||||
where T : struct
|
where T : struct
|
||||||
{
|
{
|
||||||
public T Action { get; }
|
public T Action { get; }
|
||||||
@ -16,15 +14,25 @@ namespace osu.Game.Screens.Play
|
|||||||
Action = action;
|
Action = action;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool OnPressed(T action)
|
public bool OnPressed(T action, bool forwards)
|
||||||
{
|
{
|
||||||
if (action.Equals(Action)) IsLit = true;
|
if (!action.Equals(Action))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
IsLit = true;
|
||||||
|
if (forwards)
|
||||||
|
Increment();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool OnReleased(T action)
|
public bool OnReleased(T action, bool forwards)
|
||||||
{
|
{
|
||||||
if (action.Equals(Action)) IsLit = false;
|
if (!action.Equals(Action))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
IsLit = false;
|
||||||
|
if (!forwards)
|
||||||
|
Decrement();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ namespace osu.Game.Screens.Play
|
|||||||
public class KeyCounterDisplay : FillFlowContainer<KeyCounter>
|
public class KeyCounterDisplay : FillFlowContainer<KeyCounter>
|
||||||
{
|
{
|
||||||
private const int duration = 100;
|
private const int duration = 100;
|
||||||
|
private const double key_fade_time = 80;
|
||||||
|
|
||||||
public readonly Bindable<bool> Visible = new Bindable<bool>(true);
|
public readonly Bindable<bool> Visible = new Bindable<bool>(true);
|
||||||
private readonly Bindable<bool> configVisibility = new Bindable<bool>();
|
private readonly Bindable<bool> configVisibility = new Bindable<bool>();
|
||||||
@ -33,17 +34,11 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
base.Add(key);
|
base.Add(key);
|
||||||
key.IsCounting = IsCounting;
|
key.IsCounting = IsCounting;
|
||||||
key.FadeTime = FadeTime;
|
key.FadeTime = key_fade_time;
|
||||||
key.KeyDownTextColor = KeyDownTextColor;
|
key.KeyDownTextColor = KeyDownTextColor;
|
||||||
key.KeyUpTextColor = KeyUpTextColor;
|
key.KeyUpTextColor = KeyUpTextColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetCount()
|
|
||||||
{
|
|
||||||
foreach (var counter in Children)
|
|
||||||
counter.ResetCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuConfigManager config)
|
private void load(OsuConfigManager config)
|
||||||
{
|
{
|
||||||
@ -68,22 +63,6 @@ namespace osu.Game.Screens.Play
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int fadeTime;
|
|
||||||
|
|
||||||
public int FadeTime
|
|
||||||
{
|
|
||||||
get => fadeTime;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value != fadeTime)
|
|
||||||
{
|
|
||||||
fadeTime = value;
|
|
||||||
foreach (var child in Children)
|
|
||||||
child.FadeTime = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Color4 keyDownTextColor = Color4.DarkGray;
|
private Color4 keyDownTextColor = Color4.DarkGray;
|
||||||
|
|
||||||
public Color4 KeyDownTextColor
|
public Color4 KeyDownTextColor
|
||||||
@ -123,11 +102,6 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
private Receptor receptor;
|
private Receptor receptor;
|
||||||
|
|
||||||
public Receptor GetReceptor()
|
|
||||||
{
|
|
||||||
return receptor ?? (receptor = new Receptor(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetReceptor(Receptor receptor)
|
public void SetReceptor(Receptor receptor)
|
||||||
{
|
{
|
||||||
if (this.receptor != null)
|
if (this.receptor != null)
|
||||||
|
@ -18,7 +18,12 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
{
|
{
|
||||||
if (e.Key == Key) IsLit = true;
|
if (e.Key == Key)
|
||||||
|
{
|
||||||
|
IsLit = true;
|
||||||
|
Increment();
|
||||||
|
}
|
||||||
|
|
||||||
return base.OnKeyDown(e);
|
return base.OnKeyDown(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,12 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
{
|
{
|
||||||
if (e.Button == Button) IsLit = true;
|
if (e.Button == Button)
|
||||||
|
{
|
||||||
|
IsLit = true;
|
||||||
|
Increment();
|
||||||
|
}
|
||||||
|
|
||||||
return base.OnMouseDown(e);
|
return base.OnMouseDown(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,6 +86,12 @@ namespace osu.Game.Screens.Play
|
|||||||
[Cached(Type = typeof(IBindable<IReadOnlyList<Mod>>))]
|
[Cached(Type = typeof(IBindable<IReadOnlyList<Mod>>))]
|
||||||
protected new readonly Bindable<IReadOnlyList<Mod>> Mods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
protected new readonly Bindable<IReadOnlyList<Mod>> Mods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether failing should be allowed.
|
||||||
|
/// By default, this checks whether all selected mods allow failing.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual bool AllowFail => Mods.Value.OfType<IApplicableFailOverride>().All(m => m.AllowFail);
|
||||||
|
|
||||||
private readonly bool allowPause;
|
private readonly bool allowPause;
|
||||||
private readonly bool showResults;
|
private readonly bool showResults;
|
||||||
|
|
||||||
@ -360,7 +366,7 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
private bool onFail()
|
private bool onFail()
|
||||||
{
|
{
|
||||||
if (Mods.Value.OfType<IApplicableFailOverride>().Any(m => !m.AllowFail))
|
if (!AllowFail)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
HasFailed = true;
|
HasFailed = true;
|
||||||
@ -372,6 +378,10 @@ namespace osu.Game.Screens.Play
|
|||||||
PauseOverlay.Hide();
|
PauseOverlay.Hide();
|
||||||
|
|
||||||
failAnimation.Start();
|
failAnimation.Start();
|
||||||
|
|
||||||
|
if (Mods.Value.OfType<IApplicableFailOverride>().Any(m => m.RestartOnFail))
|
||||||
|
Restart();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,9 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
private readonly Score score;
|
private readonly Score score;
|
||||||
|
|
||||||
|
// Disallow replays from failing. (see https://github.com/ppy/osu/issues/6108)
|
||||||
|
protected override bool AllowFail => false;
|
||||||
|
|
||||||
public ReplayPlayer(Score score, bool allowPause = true, bool showResults = true)
|
public ReplayPlayer(Score score, bool allowPause = true, bool showResults = true)
|
||||||
: base(allowPause, showResults)
|
: base(allowPause, showResults)
|
||||||
{
|
{
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.MathUtils;
|
using osu.Framework.MathUtils;
|
||||||
|
using osu.Framework.Threading;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play
|
namespace osu.Game.Screens.Play
|
||||||
{
|
{
|
||||||
@ -121,6 +122,12 @@ namespace osu.Game.Screens.Play
|
|||||||
handleBase.X = newX;
|
handleBase.X = newX;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnUserChange(double value) => OnSeek?.Invoke(value);
|
private ScheduledDelegate scheduledSeek;
|
||||||
|
|
||||||
|
protected override void OnUserChange(double value)
|
||||||
|
{
|
||||||
|
scheduledSeek?.Cancel();
|
||||||
|
scheduledSeek = Schedule(() => OnSeek?.Invoke(value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.BottomRight,
|
Anchor = Anchor.BottomRight,
|
||||||
Origin = Anchor.BottomRight,
|
Origin = Anchor.BottomRight,
|
||||||
Text = @"Mods",
|
Text = @"Selected Mods",
|
||||||
Alpha = 0,
|
Alpha = 0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -24,12 +24,30 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
{
|
{
|
||||||
base.Filter(criteria);
|
base.Filter(criteria);
|
||||||
|
|
||||||
bool match = criteria.Ruleset == null || Beatmap.RulesetID == criteria.Ruleset.ID || (Beatmap.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps);
|
bool match =
|
||||||
|
criteria.Ruleset == null ||
|
||||||
|
Beatmap.RulesetID == criteria.Ruleset.ID ||
|
||||||
|
(Beatmap.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps);
|
||||||
|
|
||||||
foreach (var criteriaTerm in criteria.SearchTerms)
|
match &= criteria.StarDifficulty.IsInRange(Beatmap.StarDifficulty);
|
||||||
match &=
|
match &= criteria.ApproachRate.IsInRange(Beatmap.BaseDifficulty.ApproachRate);
|
||||||
Beatmap.Metadata.SearchableTerms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0) ||
|
match &= criteria.DrainRate.IsInRange(Beatmap.BaseDifficulty.DrainRate);
|
||||||
Beatmap.Version.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0;
|
match &= criteria.CircleSize.IsInRange(Beatmap.BaseDifficulty.CircleSize);
|
||||||
|
match &= criteria.Length.IsInRange(Beatmap.Length);
|
||||||
|
match &= criteria.BPM.IsInRange(Beatmap.BPM);
|
||||||
|
|
||||||
|
match &= criteria.BeatDivisor.IsInRange(Beatmap.BeatDivisor);
|
||||||
|
match &= criteria.OnlineStatus.IsInRange(Beatmap.Status);
|
||||||
|
|
||||||
|
match &= criteria.Creator.Matches(Beatmap.Metadata.AuthorString);
|
||||||
|
match &= criteria.Artist.Matches(Beatmap.Metadata.Artist) ||
|
||||||
|
criteria.Artist.Matches(Beatmap.Metadata.ArtistUnicode);
|
||||||
|
|
||||||
|
if (match)
|
||||||
|
foreach (var criteriaTerm in criteria.SearchTerms)
|
||||||
|
match &=
|
||||||
|
Beatmap.Metadata.SearchableTerms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0) ||
|
||||||
|
Beatmap.Version.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0;
|
||||||
|
|
||||||
Filtered.Value = !match;
|
Filtered.Value = !match;
|
||||||
}
|
}
|
||||||
|
@ -33,14 +33,21 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
private Bindable<GroupMode> groupMode;
|
private Bindable<GroupMode> groupMode;
|
||||||
|
|
||||||
public FilterCriteria CreateCriteria() => new FilterCriteria
|
public FilterCriteria CreateCriteria()
|
||||||
{
|
{
|
||||||
Group = groupMode.Value,
|
var query = searchTextBox.Text;
|
||||||
Sort = sortMode.Value,
|
|
||||||
SearchText = searchTextBox.Text,
|
var criteria = new FilterCriteria
|
||||||
AllowConvertedBeatmaps = showConverted.Value,
|
{
|
||||||
Ruleset = ruleset.Value
|
Group = groupMode.Value,
|
||||||
};
|
Sort = sortMode.Value,
|
||||||
|
AllowConvertedBeatmaps = showConverted.Value,
|
||||||
|
Ruleset = ruleset.Value
|
||||||
|
};
|
||||||
|
|
||||||
|
FilterQueryParser.ApplyQueries(criteria, query);
|
||||||
|
return criteria;
|
||||||
|
}
|
||||||
|
|
||||||
public Action Exit;
|
public Action Exit;
|
||||||
|
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
// 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 System.Linq;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Screens.Select.Filter;
|
using osu.Game.Screens.Select.Filter;
|
||||||
|
|
||||||
@ -13,6 +15,17 @@ namespace osu.Game.Screens.Select
|
|||||||
public GroupMode Group;
|
public GroupMode Group;
|
||||||
public SortMode Sort;
|
public SortMode Sort;
|
||||||
|
|
||||||
|
public OptionalRange<double> StarDifficulty;
|
||||||
|
public OptionalRange<float> ApproachRate;
|
||||||
|
public OptionalRange<float> DrainRate;
|
||||||
|
public OptionalRange<float> CircleSize;
|
||||||
|
public OptionalRange<double> Length;
|
||||||
|
public OptionalRange<double> BPM;
|
||||||
|
public OptionalRange<int> BeatDivisor;
|
||||||
|
public OptionalRange<BeatmapSetOnlineStatus> OnlineStatus;
|
||||||
|
public OptionalTextFilter Creator;
|
||||||
|
public OptionalTextFilter Artist;
|
||||||
|
|
||||||
public string[] SearchTerms = Array.Empty<string>();
|
public string[] SearchTerms = Array.Empty<string>();
|
||||||
|
|
||||||
public RulesetInfo Ruleset;
|
public RulesetInfo Ruleset;
|
||||||
@ -26,8 +39,69 @@ namespace osu.Game.Screens.Select
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
searchText = value;
|
searchText = value;
|
||||||
SearchTerms = searchText.Split(',', ' ', '!').Where(s => !string.IsNullOrEmpty(s)).ToArray();
|
SearchTerms = searchText.Split(new[] { ',', ' ', '!' }, StringSplitOptions.RemoveEmptyEntries).ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct OptionalRange<T> : IEquatable<OptionalRange<T>>
|
||||||
|
where T : struct, IComparable
|
||||||
|
{
|
||||||
|
public bool IsInRange(T value)
|
||||||
|
{
|
||||||
|
if (Min != null)
|
||||||
|
{
|
||||||
|
int comparison = Comparer<T>.Default.Compare(value, Min.Value);
|
||||||
|
|
||||||
|
if (comparison < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (comparison == 0 && !IsLowerInclusive)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Max != null)
|
||||||
|
{
|
||||||
|
int comparison = Comparer<T>.Default.Compare(value, Max.Value);
|
||||||
|
|
||||||
|
if (comparison > 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (comparison == 0 && !IsUpperInclusive)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T? Min;
|
||||||
|
public T? Max;
|
||||||
|
public bool IsLowerInclusive;
|
||||||
|
public bool IsUpperInclusive;
|
||||||
|
|
||||||
|
public bool Equals(OptionalRange<T> other)
|
||||||
|
=> Min.Equals(other.Min)
|
||||||
|
&& Max.Equals(other.Max)
|
||||||
|
&& IsLowerInclusive.Equals(other.IsLowerInclusive)
|
||||||
|
&& IsUpperInclusive.Equals(other.IsUpperInclusive);
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct OptionalTextFilter : IEquatable<OptionalTextFilter>
|
||||||
|
{
|
||||||
|
public bool Matches(string value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(SearchTerm))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// search term is guaranteed to be non-empty, so if the string we're comparing is empty, it's not matching
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return value.IndexOf(SearchTerm, StringComparison.InvariantCultureIgnoreCase) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string SearchTerm;
|
||||||
|
|
||||||
|
public bool Equals(OptionalTextFilter other) => SearchTerm?.Equals(other.SearchTerm) ?? true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
211
osu.Game/Screens/Select/FilterQueryParser.cs
Normal file
211
osu.Game/Screens/Select/FilterQueryParser.cs
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Select
|
||||||
|
{
|
||||||
|
internal static class FilterQueryParser
|
||||||
|
{
|
||||||
|
private static readonly Regex query_syntax_regex = new Regex(
|
||||||
|
@"\b(?<key>stars|ar|dr|cs|divisor|length|objects|bpm|status|creator|artist)(?<op>[=:><]+)(?<value>("".*"")|(\S*))",
|
||||||
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
internal static void ApplyQueries(FilterCriteria criteria, string query)
|
||||||
|
{
|
||||||
|
foreach (Match match in query_syntax_regex.Matches(query))
|
||||||
|
{
|
||||||
|
var key = match.Groups["key"].Value.ToLower();
|
||||||
|
var op = match.Groups["op"].Value;
|
||||||
|
var value = match.Groups["value"].Value;
|
||||||
|
|
||||||
|
parseKeywordCriteria(criteria, key, value, op);
|
||||||
|
|
||||||
|
query = query.Replace(match.ToString(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
criteria.SearchText = query;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void parseKeywordCriteria(FilterCriteria criteria, string key, string value, string op)
|
||||||
|
{
|
||||||
|
switch (key)
|
||||||
|
{
|
||||||
|
case "stars" when parseFloatWithPoint(value, out var stars):
|
||||||
|
updateCriteriaRange(ref criteria.StarDifficulty, op, stars, 0.01f / 2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ar" when parseFloatWithPoint(value, out var ar):
|
||||||
|
updateCriteriaRange(ref criteria.ApproachRate, op, ar, 0.1f / 2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "dr" when parseFloatWithPoint(value, out var dr):
|
||||||
|
updateCriteriaRange(ref criteria.DrainRate, op, dr, 0.1f / 2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "cs" when parseFloatWithPoint(value, out var cs):
|
||||||
|
updateCriteriaRange(ref criteria.CircleSize, op, cs, 0.1f / 2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "bpm" when parseDoubleWithPoint(value, out var bpm):
|
||||||
|
updateCriteriaRange(ref criteria.BPM, op, bpm, 0.01d / 2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "length" when parseDoubleWithPoint(value.TrimEnd('m', 's', 'h'), out var length):
|
||||||
|
var scale = getLengthScale(value);
|
||||||
|
updateCriteriaRange(ref criteria.Length, op, length * scale, scale / 2.0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "divisor" when parseInt(value, out var divisor):
|
||||||
|
updateCriteriaRange(ref criteria.BeatDivisor, op, divisor);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "status" when Enum.TryParse<BeatmapSetOnlineStatus>(value, true, out var statusValue):
|
||||||
|
updateCriteriaRange(ref criteria.OnlineStatus, op, statusValue);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "creator":
|
||||||
|
updateCriteriaText(ref criteria.Creator, op, value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "artist":
|
||||||
|
updateCriteriaText(ref criteria.Artist, op, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getLengthScale(string value) =>
|
||||||
|
value.EndsWith("ms") ? 1 :
|
||||||
|
value.EndsWith("s") ? 1000 :
|
||||||
|
value.EndsWith("m") ? 60000 :
|
||||||
|
value.EndsWith("h") ? 3600000 : 1000;
|
||||||
|
|
||||||
|
private static bool parseFloatWithPoint(string value, out float result) =>
|
||||||
|
float.TryParse(value, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out result);
|
||||||
|
|
||||||
|
private static bool parseDoubleWithPoint(string value, out double result) =>
|
||||||
|
double.TryParse(value, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out result);
|
||||||
|
|
||||||
|
private static bool parseInt(string value, out int result) =>
|
||||||
|
int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out result);
|
||||||
|
|
||||||
|
private static void updateCriteriaText(ref FilterCriteria.OptionalTextFilter textFilter, string op, string value)
|
||||||
|
{
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case "=":
|
||||||
|
case ":":
|
||||||
|
textFilter.SearchTerm = value.Trim('"');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void updateCriteriaRange(ref FilterCriteria.OptionalRange<float> range, string op, float value, float tolerance = 0.05f)
|
||||||
|
{
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
|
||||||
|
case "=":
|
||||||
|
case ":":
|
||||||
|
range.Min = value - tolerance;
|
||||||
|
range.Max = value + tolerance;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ">":
|
||||||
|
range.Min = value + tolerance;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ">=":
|
||||||
|
case ">:":
|
||||||
|
range.Min = value - tolerance;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "<":
|
||||||
|
range.Max = value - tolerance;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "<=":
|
||||||
|
case "<:":
|
||||||
|
range.Max = value + tolerance;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void updateCriteriaRange(ref FilterCriteria.OptionalRange<double> range, string op, double value, double tolerance = 0.05)
|
||||||
|
{
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
|
||||||
|
case "=":
|
||||||
|
case ":":
|
||||||
|
range.Min = value - tolerance;
|
||||||
|
range.Max = value + tolerance;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ">":
|
||||||
|
range.Min = value + tolerance;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ">=":
|
||||||
|
case ">:":
|
||||||
|
range.Min = value - tolerance;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "<":
|
||||||
|
range.Max = value - tolerance;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "<=":
|
||||||
|
case "<:":
|
||||||
|
range.Max = value + tolerance;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void updateCriteriaRange<T>(ref FilterCriteria.OptionalRange<T> range, string op, T value)
|
||||||
|
where T : struct, IComparable
|
||||||
|
{
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
|
||||||
|
case "=":
|
||||||
|
case ":":
|
||||||
|
range.IsLowerInclusive = range.IsUpperInclusive = true;
|
||||||
|
range.Min = value;
|
||||||
|
range.Max = value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ">":
|
||||||
|
range.IsLowerInclusive = false;
|
||||||
|
range.Min = value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ">=":
|
||||||
|
case ">:":
|
||||||
|
range.IsLowerInclusive = true;
|
||||||
|
range.Min = value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "<":
|
||||||
|
range.IsUpperInclusive = false;
|
||||||
|
range.Max = value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "<=":
|
||||||
|
case "<:":
|
||||||
|
range.IsUpperInclusive = true;
|
||||||
|
range.Max = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Leaderboards;
|
using osu.Game.Online.Leaderboards;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -37,8 +38,25 @@ namespace osu.Game.Screens.Select.Leaderboards
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public APILegacyUserTopScoreInfo TopScore
|
||||||
|
{
|
||||||
|
get => topScoreContainer.Score.Value;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
topScoreContainer.Hide();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
topScoreContainer.Show();
|
||||||
|
topScoreContainer.Score.Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool filterMods;
|
private bool filterMods;
|
||||||
|
|
||||||
|
private UserTopScoreContainer topScoreContainer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether to apply the game's currently selected mods as a filter when retrieving scores.
|
/// Whether to apply the game's currently selected mods as a filter when retrieving scores.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -77,6 +95,17 @@ namespace osu.Game.Screens.Select.Leaderboards
|
|||||||
if (filterMods)
|
if (filterMods)
|
||||||
UpdateScores();
|
UpdateScores();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Content.Add(topScoreContainer = new UserTopScoreContainer
|
||||||
|
{
|
||||||
|
ScoreSelected = s => ScoreSelected?.Invoke(s)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Reset()
|
||||||
|
{
|
||||||
|
base.Reset();
|
||||||
|
TopScore = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local;
|
protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local;
|
||||||
@ -141,7 +170,11 @@ namespace osu.Game.Screens.Select.Leaderboards
|
|||||||
|
|
||||||
var req = new GetScoresRequest(Beatmap, ruleset.Value ?? Beatmap.Ruleset, Scope, requestMods);
|
var req = new GetScoresRequest(Beatmap, ruleset.Value ?? Beatmap.Ruleset, Scope, requestMods);
|
||||||
|
|
||||||
req.Success += r => scoresCallback?.Invoke(r.Scores);
|
req.Success += r =>
|
||||||
|
{
|
||||||
|
scoresCallback?.Invoke(r.Scores);
|
||||||
|
TopScore = r.UserScore;
|
||||||
|
};
|
||||||
|
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,94 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Online.Leaderboards;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Select.Leaderboards
|
||||||
|
{
|
||||||
|
public class UserTopScoreContainer : VisibilityContainer
|
||||||
|
{
|
||||||
|
private const int duration = 500;
|
||||||
|
|
||||||
|
private readonly Container scoreContainer;
|
||||||
|
|
||||||
|
public Bindable<APILegacyUserTopScoreInfo> Score = new Bindable<APILegacyUserTopScoreInfo>();
|
||||||
|
|
||||||
|
public Action<ScoreInfo> ScoreSelected;
|
||||||
|
|
||||||
|
protected override bool StartHidden => true;
|
||||||
|
|
||||||
|
public UserTopScoreContainer()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
Margin = new MarginPadding { Vertical = 5 };
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(5),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Text = @"your personal best".ToUpper(),
|
||||||
|
Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold),
|
||||||
|
},
|
||||||
|
scoreContainer = new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Score.BindValueChanged(onScoreChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CancellationTokenSource loadScoreCancellation;
|
||||||
|
|
||||||
|
private void onScoreChanged(ValueChangedEvent<APILegacyUserTopScoreInfo> score)
|
||||||
|
{
|
||||||
|
var newScore = score.NewValue;
|
||||||
|
|
||||||
|
scoreContainer.Clear();
|
||||||
|
loadScoreCancellation?.Cancel();
|
||||||
|
|
||||||
|
if (newScore == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
LoadComponentAsync(new LeaderboardScore(newScore.Score, newScore.Position)
|
||||||
|
{
|
||||||
|
Action = () => ScoreSelected?.Invoke(newScore.Score)
|
||||||
|
}, drawableScore =>
|
||||||
|
{
|
||||||
|
scoreContainer.Child = drawableScore;
|
||||||
|
drawableScore.FadeInFromZero(duration, Easing.OutQuint);
|
||||||
|
}, (loadScoreCancellation = new CancellationTokenSource()).Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopIn() => this.FadeIn(duration, Easing.OutQuint);
|
||||||
|
|
||||||
|
protected override void PopOut() => this.FadeOut(duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
@ -223,7 +223,7 @@ namespace osu.Game.Screens.Select
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
BeatmapDetails.Leaderboard.ScoreSelected += s => this.Push(new SoloResults(s));
|
BeatmapDetails.Leaderboard.ScoreSelected += score => this.Push(new SoloResults(score));
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
|
@ -13,6 +13,13 @@ namespace osu.Game.Skinning
|
|||||||
: base(Info, storage, audioManager, string.Empty)
|
: base(Info, storage, audioManager, string.Empty)
|
||||||
{
|
{
|
||||||
Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255);
|
Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255);
|
||||||
|
Configuration.ComboColours.AddRange(new[]
|
||||||
|
{
|
||||||
|
new Color4(255, 192, 0, 255),
|
||||||
|
new Color4(0, 202, 0, 255),
|
||||||
|
new Color4(18, 124, 255, 255),
|
||||||
|
new Color4(242, 24, 57, 255),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SkinInfo Info { get; } = new SkinInfo
|
public static SkinInfo Info { get; } = new SkinInfo
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// 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;
|
||||||
|
@ -26,10 +26,10 @@
|
|||||||
<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.2" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.913.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.913.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2019.911.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2019.921.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.24.0" />
|
<PackageReference Include="SharpCompress" Version="0.24.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" />
|
||||||
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
|
<PackageReference Include="System.ComponentModel.Annotations" Version="4.6.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -118,12 +118,12 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.913.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.913.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2019.911.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2019.921.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2019.911.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2019.921.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.24.0" />
|
<PackageReference Include="SharpCompress" Version="0.24.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.11.0" />
|
<PackageReference Include="NUnit" Version="3.11.0" />
|
||||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||||
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
|
<PackageReference Include="System.ComponentModel.Annotations" Version="4.6.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.NativeLibs" Version="2019.813.0" ExcludeAssets="all" />
|
<PackageReference Include="ppy.osu.Framework.NativeLibs" Version="2019.813.0" ExcludeAssets="all" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
Loading…
Reference in New Issue
Block a user