mirror of
https://github.com/ppy/osu.git
synced 2025-02-13 17:53:21 +08:00
Merge branch 'master' into Save-Score-Failed
This commit is contained in:
commit
ab6665d88c
@ -9,7 +9,6 @@
|
|||||||
<GenerateProgramFile>false</GenerateProgramFile>
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
<GenerateProgramFile>false</GenerateProgramFile>
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
<GenerateProgramFile>false</GenerateProgramFile>
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
<GenerateProgramFile>false</GenerateProgramFile>
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
|
@ -52,7 +52,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.702.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.702.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.707.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.713.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||||
|
@ -14,6 +14,7 @@ using osu.Framework.Platform;
|
|||||||
using osu.Game;
|
using osu.Game;
|
||||||
using osu.Game.IPC;
|
using osu.Game.IPC;
|
||||||
using osu.Game.Tournament;
|
using osu.Game.Tournament;
|
||||||
|
using SDL2;
|
||||||
using Squirrel;
|
using Squirrel;
|
||||||
|
|
||||||
namespace osu.Desktop
|
namespace osu.Desktop
|
||||||
@ -29,7 +30,21 @@ namespace osu.Desktop
|
|||||||
{
|
{
|
||||||
// run Squirrel first, as the app may exit after these run
|
// run Squirrel first, as the app may exit after these run
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
var windowsVersion = Environment.OSVersion.Version;
|
||||||
|
|
||||||
|
// While .NET 6 still supports Windows 7 and above, we are limited by realm currently, as they choose to only support 8.1 and higher.
|
||||||
|
// See https://www.mongodb.com/docs/realm/sdk/dotnet/#supported-platforms
|
||||||
|
if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 2))
|
||||||
|
{
|
||||||
|
SDL.SDL_ShowSimpleMessageBox(SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR,
|
||||||
|
"Your operating system is too old to run osu!",
|
||||||
|
"This version of osu! requires at least Windows 8.1 to run.\nPlease upgrade your operating system or consider using an older version of osu!.", IntPtr.Zero);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setupSquirrel();
|
setupSquirrel();
|
||||||
|
}
|
||||||
|
|
||||||
// Back up the cwd before DesktopGameHost changes it
|
// Back up the cwd before DesktopGameHost changes it
|
||||||
string cwd = Environment.CurrentDirectory;
|
string cwd = Environment.CurrentDirectory;
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Clowd.Squirrel" Version="2.9.40" />
|
<PackageReference Include="Clowd.Squirrel" Version="2.9.42" />
|
||||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||||
<PackageReference Include="System.IO.Packaging" Version="6.0.0" />
|
<PackageReference Include="System.IO.Packaging" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
|
||||||
|
@ -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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
|
@ -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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
@ -11,7 +9,7 @@ namespace osu.Game.Benchmarks
|
|||||||
{
|
{
|
||||||
public class BenchmarkMod : BenchmarkTest
|
public class BenchmarkMod : BenchmarkTest
|
||||||
{
|
{
|
||||||
private OsuModDoubleTime mod;
|
private OsuModDoubleTime mod = null!;
|
||||||
|
|
||||||
[Params(1, 10, 100)]
|
[Params(1, 10, 100)]
|
||||||
public int Times { get; set; }
|
public int Times { get; set; }
|
||||||
|
@ -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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
@ -17,9 +15,9 @@ namespace osu.Game.Benchmarks
|
|||||||
{
|
{
|
||||||
public class BenchmarkRealmReads : BenchmarkTest
|
public class BenchmarkRealmReads : BenchmarkTest
|
||||||
{
|
{
|
||||||
private TemporaryNativeStorage storage;
|
private TemporaryNativeStorage storage = null!;
|
||||||
private RealmAccess realm;
|
private RealmAccess realm = null!;
|
||||||
private UpdateThread updateThread;
|
private UpdateThread updateThread = null!;
|
||||||
|
|
||||||
[Params(1, 100, 1000)]
|
[Params(1, 100, 1000)]
|
||||||
public int ReadsPerFetch { get; set; }
|
public int ReadsPerFetch { get; set; }
|
||||||
@ -135,9 +133,9 @@ namespace osu.Game.Benchmarks
|
|||||||
[GlobalCleanup]
|
[GlobalCleanup]
|
||||||
public void Cleanup()
|
public void Cleanup()
|
||||||
{
|
{
|
||||||
realm?.Dispose();
|
realm.Dispose();
|
||||||
storage?.Dispose();
|
storage.Dispose();
|
||||||
updateThread?.Exit();
|
updateThread.Exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Engines;
|
using BenchmarkDotNet.Engines;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
@ -13,9 +11,9 @@ namespace osu.Game.Benchmarks
|
|||||||
{
|
{
|
||||||
public class BenchmarkRuleset : BenchmarkTest
|
public class BenchmarkRuleset : BenchmarkTest
|
||||||
{
|
{
|
||||||
private OsuRuleset ruleset;
|
private OsuRuleset ruleset = null!;
|
||||||
private APIMod apiModDoubleTime;
|
private APIMod apiModDoubleTime = null!;
|
||||||
private APIMod apiModDifficultyAdjust;
|
private APIMod apiModDifficultyAdjust = null!;
|
||||||
|
|
||||||
public override void SetUp()
|
public override void SetUp()
|
||||||
{
|
{
|
||||||
|
@ -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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Running;
|
using BenchmarkDotNet.Running;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
@ -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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using BenchmarkDotNet.Configs;
|
using BenchmarkDotNet.Configs;
|
||||||
using BenchmarkDotNet.Running;
|
using BenchmarkDotNet.Running;
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<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="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<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="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
|
27
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs
Normal file
27
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
|
{
|
||||||
|
public class TestSceneOsuModRepel : OsuModTestScene
|
||||||
|
{
|
||||||
|
[TestCase(0.1f)]
|
||||||
|
[TestCase(0.5f)]
|
||||||
|
[TestCase(1)]
|
||||||
|
public void TestRepel(float strength)
|
||||||
|
{
|
||||||
|
CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModRepel
|
||||||
|
{
|
||||||
|
RepulsionStrength = { Value = strength },
|
||||||
|
},
|
||||||
|
PassCondition = () => true,
|
||||||
|
Autoplay = false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
175
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs
Normal file
175
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.Timing;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
|
using osu.Game.Rulesets.Replays;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
|
{
|
||||||
|
public class TestSceneOsuModSingleTap : OsuModTestScene
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestInputSingular() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModSingleTap(),
|
||||||
|
PassCondition = () => Player.ScoreProcessor.Combo.Value == 2,
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 500,
|
||||||
|
Position = new Vector2(100),
|
||||||
|
},
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 1000,
|
||||||
|
Position = new Vector2(200, 100),
|
||||||
|
},
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 1500,
|
||||||
|
Position = new Vector2(300, 100),
|
||||||
|
},
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 2000,
|
||||||
|
Position = new Vector2(400, 100),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(501, new Vector2(100)),
|
||||||
|
new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.LeftButton),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInputAlternating() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModSingleTap(),
|
||||||
|
PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 1,
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 500,
|
||||||
|
Position = new Vector2(100),
|
||||||
|
},
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 1000,
|
||||||
|
Position = new Vector2(200, 100),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(501, new Vector2(100)),
|
||||||
|
new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.RightButton),
|
||||||
|
new OsuReplayFrame(1001, new Vector2(200, 100)),
|
||||||
|
new OsuReplayFrame(1500, new Vector2(300, 100), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(1501, new Vector2(300, 100)),
|
||||||
|
new OsuReplayFrame(2000, new Vector2(400, 100), OsuAction.RightButton),
|
||||||
|
new OsuReplayFrame(2001, new Vector2(400, 100)),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures singletapping is reset before the first hitobject after intro.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestInputAlternatingAtIntro() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModSingleTap(),
|
||||||
|
PassCondition = () => Player.ScoreProcessor.Combo.Value == 1,
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 1000,
|
||||||
|
Position = new Vector2(100),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
// first press during intro.
|
||||||
|
new OsuReplayFrame(500, new Vector2(200), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(501, new Vector2(200)),
|
||||||
|
// press different key at hitobject and ensure it has been hit.
|
||||||
|
new OsuReplayFrame(1000, new Vector2(100), OsuAction.RightButton),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures singletapping is reset before the first hitobject after a break.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestInputAlternatingWithBreak() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModSingleTap(),
|
||||||
|
PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 2,
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
Breaks = new List<BreakPeriod>
|
||||||
|
{
|
||||||
|
new BreakPeriod(500, 2000),
|
||||||
|
},
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 500,
|
||||||
|
Position = new Vector2(100),
|
||||||
|
},
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 2500,
|
||||||
|
Position = new Vector2(500, 100),
|
||||||
|
},
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 3000,
|
||||||
|
Position = new Vector2(500, 100),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
// first press to start singletap lock.
|
||||||
|
new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(501, new Vector2(100)),
|
||||||
|
// press different key after break but before hit object.
|
||||||
|
new OsuReplayFrame(2250, new Vector2(300, 100), OsuAction.RightButton),
|
||||||
|
new OsuReplayFrame(2251, new Vector2(300, 100)),
|
||||||
|
// press same key at second hitobject and ensure it has been hit.
|
||||||
|
new OsuReplayFrame(2500, new Vector2(500, 100), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(2501, new Vector2(500, 100)),
|
||||||
|
// press different key at third hitobject and ensure it has been missed.
|
||||||
|
new OsuReplayFrame(3000, new Vector2(500, 100), OsuAction.RightButton),
|
||||||
|
new OsuReplayFrame(3001, new Vector2(500, 100)),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,8 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<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="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="Moq" Version="4.17.2" />
|
<PackageReference Include="Moq" Version="4.18.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
114
osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs
Normal file
114
osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Beatmaps.Timing;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
|
{
|
||||||
|
public abstract class InputBlockingMod : Mod, IApplicableToDrawableRuleset<OsuHitObject>
|
||||||
|
{
|
||||||
|
public override double ScoreMultiplier => 1.0;
|
||||||
|
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax), typeof(OsuModCinema) };
|
||||||
|
public override ModType Type => ModType.Conversion;
|
||||||
|
|
||||||
|
private const double flash_duration = 1000;
|
||||||
|
|
||||||
|
private DrawableRuleset<OsuHitObject> ruleset = null!;
|
||||||
|
|
||||||
|
protected OsuAction? LastAcceptedAction { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A tracker for periods where alternate should not be forced (i.e. non-gameplay periods).
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is different from <see cref="Player.IsBreakTime"/> in that the periods here end strictly at the first object after the break, rather than the break's end time.
|
||||||
|
/// </remarks>
|
||||||
|
private PeriodTracker nonGameplayPeriods = null!;
|
||||||
|
|
||||||
|
private IFrameStableClock gameplayClock = null!;
|
||||||
|
|
||||||
|
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||||
|
{
|
||||||
|
ruleset = drawableRuleset;
|
||||||
|
drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this));
|
||||||
|
|
||||||
|
var periods = new List<Period>();
|
||||||
|
|
||||||
|
if (drawableRuleset.Objects.Any())
|
||||||
|
{
|
||||||
|
periods.Add(new Period(int.MinValue, getValidJudgementTime(ruleset.Objects.First()) - 1));
|
||||||
|
|
||||||
|
foreach (BreakPeriod b in drawableRuleset.Beatmap.Breaks)
|
||||||
|
periods.Add(new Period(b.StartTime, getValidJudgementTime(ruleset.Objects.First(h => h.StartTime >= b.EndTime)) - 1));
|
||||||
|
|
||||||
|
static double getValidJudgementTime(HitObject hitObject) => hitObject.StartTime - hitObject.HitWindows.WindowFor(HitResult.Meh);
|
||||||
|
}
|
||||||
|
|
||||||
|
nonGameplayPeriods = new PeriodTracker(periods);
|
||||||
|
|
||||||
|
gameplayClock = drawableRuleset.FrameStableClock;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract bool CheckValidNewAction(OsuAction action);
|
||||||
|
|
||||||
|
private bool checkCorrectAction(OsuAction action)
|
||||||
|
{
|
||||||
|
if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime))
|
||||||
|
{
|
||||||
|
LastAcceptedAction = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (action)
|
||||||
|
{
|
||||||
|
case OsuAction.LeftButton:
|
||||||
|
case OsuAction.RightButton:
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Any action which is not left or right button should be ignored.
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CheckValidNewAction(action))
|
||||||
|
{
|
||||||
|
LastAcceptedAction = action;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InputInterceptor : Component, IKeyBindingHandler<OsuAction>
|
||||||
|
{
|
||||||
|
private readonly InputBlockingMod mod;
|
||||||
|
|
||||||
|
public InputInterceptor(InputBlockingMod mod)
|
||||||
|
{
|
||||||
|
this.mod = mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
||||||
|
// if the pressed action is incorrect, block it from reaching gameplay.
|
||||||
|
=> !mod.checkCorrectAction(e.Action);
|
||||||
|
|
||||||
|
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,119 +1,20 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.Bindings;
|
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
using osu.Game.Beatmaps.Timing;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
using osu.Game.Rulesets.UI;
|
|
||||||
using osu.Game.Screens.Play;
|
|
||||||
using osu.Game.Utils;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
{
|
{
|
||||||
public class OsuModAlternate : Mod, IApplicableToDrawableRuleset<OsuHitObject>
|
public class OsuModAlternate : InputBlockingMod
|
||||||
{
|
{
|
||||||
public override string Name => @"Alternate";
|
public override string Name => @"Alternate";
|
||||||
public override string Acronym => @"AL";
|
public override string Acronym => @"AL";
|
||||||
public override string Description => @"Don't use the same key twice in a row!";
|
public override string Description => @"Don't use the same key twice in a row!";
|
||||||
public override double ScoreMultiplier => 1.0;
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax) };
|
|
||||||
public override ModType Type => ModType.Conversion;
|
|
||||||
public override IconUsage? Icon => FontAwesome.Solid.Keyboard;
|
public override IconUsage? Icon => FontAwesome.Solid.Keyboard;
|
||||||
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModSingleTap) }).ToArray();
|
||||||
|
|
||||||
private const double flash_duration = 1000;
|
protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction != action;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A tracker for periods where alternate should not be forced (i.e. non-gameplay periods).
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This is different from <see cref="Player.IsBreakTime"/> in that the periods here end strictly at the first object after the break, rather than the break's end time.
|
|
||||||
/// </remarks>
|
|
||||||
private PeriodTracker nonGameplayPeriods;
|
|
||||||
|
|
||||||
private OsuAction? lastActionPressed;
|
|
||||||
private DrawableRuleset<OsuHitObject> ruleset;
|
|
||||||
|
|
||||||
private IFrameStableClock gameplayClock;
|
|
||||||
|
|
||||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
|
||||||
{
|
|
||||||
ruleset = drawableRuleset;
|
|
||||||
drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this));
|
|
||||||
|
|
||||||
var periods = new List<Period>();
|
|
||||||
|
|
||||||
if (drawableRuleset.Objects.Any())
|
|
||||||
{
|
|
||||||
periods.Add(new Period(int.MinValue, getValidJudgementTime(ruleset.Objects.First()) - 1));
|
|
||||||
|
|
||||||
foreach (BreakPeriod b in drawableRuleset.Beatmap.Breaks)
|
|
||||||
periods.Add(new Period(b.StartTime, getValidJudgementTime(ruleset.Objects.First(h => h.StartTime >= b.EndTime)) - 1));
|
|
||||||
|
|
||||||
static double getValidJudgementTime(HitObject hitObject) => hitObject.StartTime - hitObject.HitWindows.WindowFor(HitResult.Meh);
|
|
||||||
}
|
|
||||||
|
|
||||||
nonGameplayPeriods = new PeriodTracker(periods);
|
|
||||||
|
|
||||||
gameplayClock = drawableRuleset.FrameStableClock;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool checkCorrectAction(OsuAction action)
|
|
||||||
{
|
|
||||||
if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime))
|
|
||||||
{
|
|
||||||
lastActionPressed = null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (action)
|
|
||||||
{
|
|
||||||
case OsuAction.LeftButton:
|
|
||||||
case OsuAction.RightButton:
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Any action which is not left or right button should be ignored.
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastActionPressed != action)
|
|
||||||
{
|
|
||||||
// User alternated correctly.
|
|
||||||
lastActionPressed = action;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class InputInterceptor : Component, IKeyBindingHandler<OsuAction>
|
|
||||||
{
|
|
||||||
private readonly OsuModAlternate mod;
|
|
||||||
|
|
||||||
public InputInterceptor(OsuModAlternate mod)
|
|
||||||
{
|
|
||||||
this.mod = mod;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
|
||||||
// if the pressed action is incorrect, block it from reaching gameplay.
|
|
||||||
=> !mod.checkCorrectAction(e.Action);
|
|
||||||
|
|
||||||
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override ModType Type => ModType.Automation;
|
public override ModType Type => ModType.Automation;
|
||||||
public override string Description => @"Automatic cursor movement - just follow the rhythm.";
|
public override string Description => @"Automatic cursor movement - just follow the rhythm.";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModMagnetised) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModRepel) };
|
||||||
|
|
||||||
public bool PerformFail() => false;
|
public bool PerformFail() => false;
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public class OsuModAutoplay : ModAutoplay
|
public class OsuModAutoplay : ModAutoplay
|
||||||
{
|
{
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate) }).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray();
|
||||||
|
|
||||||
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
=> new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" });
|
=> new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" });
|
||||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
public class OsuModCinema : ModCinema<OsuHitObject>
|
public class OsuModCinema : ModCinema<OsuHitObject>
|
||||||
{
|
{
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate) }).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray();
|
||||||
|
|
||||||
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
=> new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" });
|
=> new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" });
|
||||||
|
@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override string Description => "No need to chase the circles – your cursor is a magnet!";
|
public override string Description => "No need to chase the circles – your cursor is a magnet!";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel) };
|
||||||
|
|
||||||
private IFrameStableClock gameplayClock;
|
private IFrameStableClock gameplayClock;
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToPlayer
|
public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToPlayer
|
||||||
{
|
{
|
||||||
public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
|
public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate) }).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How early before a hitobject's start time to trigger a hit.
|
/// How early before a hitobject's start time to trigger a hit.
|
||||||
|
98
osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs
Normal file
98
osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
// 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.Diagnostics;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Rulesets.Osu.Utils;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
|
{
|
||||||
|
internal class OsuModRepel : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>
|
||||||
|
{
|
||||||
|
public override string Name => "Repel";
|
||||||
|
public override string Acronym => "RP";
|
||||||
|
public override ModType Type => ModType.Fun;
|
||||||
|
public override string Description => "Hit objects run away!";
|
||||||
|
public override double ScoreMultiplier => 1;
|
||||||
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised) };
|
||||||
|
|
||||||
|
private IFrameStableClock? gameplayClock;
|
||||||
|
|
||||||
|
[SettingSource("Repulsion strength", "How strong the repulsion is.", 0)]
|
||||||
|
public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.5f)
|
||||||
|
{
|
||||||
|
Precision = 0.05f,
|
||||||
|
MinValue = 0.05f,
|
||||||
|
MaxValue = 1.0f,
|
||||||
|
};
|
||||||
|
|
||||||
|
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||||
|
{
|
||||||
|
gameplayClock = drawableRuleset.FrameStableClock;
|
||||||
|
|
||||||
|
// Hide judgment displays and follow points as they won't make any sense.
|
||||||
|
// Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart.
|
||||||
|
drawableRuleset.Playfield.DisplayJudgements.Value = false;
|
||||||
|
(drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(Playfield playfield)
|
||||||
|
{
|
||||||
|
var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition;
|
||||||
|
|
||||||
|
foreach (var drawable in playfield.HitObjectContainer.AliveObjects)
|
||||||
|
{
|
||||||
|
var destination = Vector2.Clamp(2 * drawable.Position - cursorPos, Vector2.Zero, OsuPlayfield.BASE_SIZE);
|
||||||
|
|
||||||
|
if (drawable.HitObject is Slider thisSlider)
|
||||||
|
{
|
||||||
|
var possibleMovementBounds = OsuHitObjectGenerationUtils.CalculatePossibleMovementBounds(thisSlider);
|
||||||
|
|
||||||
|
destination = Vector2.Clamp(
|
||||||
|
destination,
|
||||||
|
new Vector2(possibleMovementBounds.Left, possibleMovementBounds.Top),
|
||||||
|
new Vector2(possibleMovementBounds.Right, possibleMovementBounds.Bottom)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (drawable)
|
||||||
|
{
|
||||||
|
case DrawableHitCircle circle:
|
||||||
|
easeTo(circle, destination, cursorPos);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DrawableSlider slider:
|
||||||
|
|
||||||
|
if (!slider.HeadCircle.Result.HasResult)
|
||||||
|
easeTo(slider, destination, cursorPos);
|
||||||
|
else
|
||||||
|
easeTo(slider, destination - slider.Ball.DrawPosition, cursorPos);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void easeTo(DrawableHitObject hitObject, Vector2 destination, Vector2 cursorPos)
|
||||||
|
{
|
||||||
|
Debug.Assert(gameplayClock != null);
|
||||||
|
|
||||||
|
double dampLength = Vector2.Distance(hitObject.Position, cursorPos) / (0.04 * RepulsionStrength.Value + 0.04);
|
||||||
|
|
||||||
|
float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime);
|
||||||
|
float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime);
|
||||||
|
|
||||||
|
hitObject.Position = new Vector2(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs
Normal file
18
osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
|
{
|
||||||
|
public class OsuModSingleTap : InputBlockingMod
|
||||||
|
{
|
||||||
|
public override string Name => @"Single Tap";
|
||||||
|
public override string Acronym => @"ST";
|
||||||
|
public override string Description => @"You must only use one key!";
|
||||||
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAlternate) }).ToArray();
|
||||||
|
|
||||||
|
protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction == null || LastAcceptedAction == action;
|
||||||
|
}
|
||||||
|
}
|
@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override string Description => "Everything rotates. EVERYTHING.";
|
public override string Description => "Everything rotates. EVERYTHING.";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle), typeof(OsuModMagnetised) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle), typeof(OsuModMagnetised), typeof(OsuModRepel) };
|
||||||
|
|
||||||
private float theta;
|
private float theta;
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public override ModType Type => ModType.Fun;
|
public override ModType Type => ModType.Fun;
|
||||||
public override string Description => "They just won't stay still...";
|
public override string Description => "They just won't stay still...";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised) };
|
public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised), typeof(OsuModRepel) };
|
||||||
|
|
||||||
private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles
|
private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles
|
||||||
private const int wiggle_strength = 10; // Higher = stronger wiggles
|
private const int wiggle_strength = 10; // Higher = stronger wiggles
|
||||||
|
@ -319,13 +319,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
const float fade_out_time = 450;
|
const float fade_out_time = 450;
|
||||||
|
|
||||||
// intentionally pile on an extra FadeOut to make it happen much faster.
|
|
||||||
Ball.FadeOut(fade_out_time / 4, Easing.Out);
|
|
||||||
|
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case ArmedState.Hit:
|
case ArmedState.Hit:
|
||||||
Ball.ScaleTo(HitObject.Scale * 1.4f, fade_out_time, Easing.Out);
|
|
||||||
if (SliderBody?.SnakingOut.Value == true)
|
if (SliderBody?.SnakingOut.Value == true)
|
||||||
Body.FadeOut(40); // short fade to allow for any body colour to smoothly disappear.
|
Body.FadeOut(40); // short fade to allow for any body colour to smoothly disappear.
|
||||||
break;
|
break;
|
||||||
|
@ -23,6 +23,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
public class DrawableSliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition, IHasAccentColour
|
public class DrawableSliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition, IHasAccentColour
|
||||||
{
|
{
|
||||||
|
public const float FOLLOW_AREA = 2.4f;
|
||||||
|
|
||||||
public Func<OsuAction?> GetInitialHitAction;
|
public Func<OsuAction?> GetInitialHitAction;
|
||||||
|
|
||||||
public Color4 AccentColour
|
public Color4 AccentColour
|
||||||
@ -31,7 +33,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
set => ball.Colour = value;
|
set => ball.Colour = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Drawable followCircle;
|
|
||||||
private Drawable followCircleReceptor;
|
private Drawable followCircleReceptor;
|
||||||
private DrawableSlider drawableSlider;
|
private DrawableSlider drawableSlider;
|
||||||
private Drawable ball;
|
private Drawable ball;
|
||||||
@ -47,12 +48,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
followCircle = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle())
|
new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle())
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Alpha = 0,
|
|
||||||
},
|
},
|
||||||
followCircleReceptor = new CircularContainer
|
followCircleReceptor = new CircularContainer
|
||||||
{
|
{
|
||||||
@ -103,10 +103,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
tracking = value;
|
tracking = value;
|
||||||
|
|
||||||
followCircleReceptor.Scale = new Vector2(tracking ? 2.4f : 1f);
|
followCircleReceptor.Scale = new Vector2(tracking ? FOLLOW_AREA : 1f);
|
||||||
|
|
||||||
followCircle.ScaleTo(tracking ? 2.4f : 1f, 300, Easing.OutQuint);
|
|
||||||
followCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
new OsuModClassic(),
|
new OsuModClassic(),
|
||||||
new OsuModRandom(),
|
new OsuModRandom(),
|
||||||
new OsuModMirror(),
|
new OsuModMirror(),
|
||||||
new OsuModAlternate(),
|
new MultiMod(new OsuModAlternate(), new OsuModSingleTap())
|
||||||
};
|
};
|
||||||
|
|
||||||
case ModType.Automation:
|
case ModType.Automation:
|
||||||
@ -197,7 +197,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
new OsuModApproachDifferent(),
|
new OsuModApproachDifferent(),
|
||||||
new OsuModMuted(),
|
new OsuModMuted(),
|
||||||
new OsuModNoScope(),
|
new OsuModNoScope(),
|
||||||
new OsuModMagnetised(),
|
new MultiMod(new OsuModMagnetised(), new OsuModRepel()),
|
||||||
new ModAdaptiveSpeed()
|
new ModAdaptiveSpeed()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||||
{
|
{
|
||||||
public class DefaultFollowCircle : CompositeDrawable
|
public class DefaultFollowCircle : FollowCircle
|
||||||
{
|
{
|
||||||
public DefaultFollowCircle()
|
public DefaultFollowCircle()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
|
||||||
|
|
||||||
InternalChild = new CircularContainer
|
InternalChild = new CircularContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
@ -29,5 +29,22 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnTrackingChanged(ValueChangedEvent<bool> tracking)
|
||||||
|
{
|
||||||
|
const float scale_duration = 300f;
|
||||||
|
const float fade_duration = 300f;
|
||||||
|
|
||||||
|
this.ScaleTo(tracking.NewValue ? DrawableSliderBall.FOLLOW_AREA : 1f, scale_duration, Easing.OutQuint)
|
||||||
|
.FadeTo(tracking.NewValue ? 1f : 0, fade_duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSliderEnd()
|
||||||
|
{
|
||||||
|
const float fade_duration = 450f;
|
||||||
|
|
||||||
|
// intentionally pile on an extra FadeOut to make it happen much faster
|
||||||
|
this.FadeOut(fade_duration / 4, Easing.Out);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -19,13 +17,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
{
|
{
|
||||||
public class DefaultSliderBall : CompositeDrawable
|
public class DefaultSliderBall : CompositeDrawable
|
||||||
{
|
{
|
||||||
private Box box;
|
private Box box = null!;
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private DrawableHitObject? parentObject { get; set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(DrawableHitObject drawableObject, ISkinSource skin)
|
private void load(ISkinSource skin)
|
||||||
{
|
{
|
||||||
var slider = (DrawableSlider)drawableObject;
|
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
float radius = skin.GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
|
float radius = skin.GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
|
||||||
@ -51,10 +50,62 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (parentObject != null)
|
||||||
|
{
|
||||||
|
var slider = (DrawableSlider)parentObject;
|
||||||
slider.Tracking.BindValueChanged(trackingChanged, true);
|
slider.Tracking.BindValueChanged(trackingChanged, true);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
if (parentObject != null)
|
||||||
|
{
|
||||||
|
parentObject.ApplyCustomUpdateState += updateStateTransforms;
|
||||||
|
updateStateTransforms(parentObject, parentObject.State.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void trackingChanged(ValueChangedEvent<bool> tracking) =>
|
private void trackingChanged(ValueChangedEvent<bool> tracking) =>
|
||||||
box.FadeTo(tracking.NewValue ? 0.3f : 0.05f, 200, Easing.OutQuint);
|
box.FadeTo(tracking.NewValue ? 0.3f : 0.05f, 200, Easing.OutQuint);
|
||||||
|
|
||||||
|
private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state)
|
||||||
|
{
|
||||||
|
// Gets called by slider ticks, tails, etc., leading to duplicated
|
||||||
|
// animations which may negatively affect performance
|
||||||
|
if (drawableObject is not DrawableSlider)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const float fade_duration = 450f;
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(drawableObject.StateUpdateTime))
|
||||||
|
{
|
||||||
|
this.FadeIn()
|
||||||
|
.ScaleTo(1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
|
||||||
|
{
|
||||||
|
// intentionally pile on an extra FadeOut to make it happen much faster
|
||||||
|
this.FadeOut(fade_duration / 4, Easing.Out);
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case ArmedState.Hit:
|
||||||
|
this.ScaleTo(1.4f, fade_duration, Easing.Out);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (parentObject != null)
|
||||||
|
parentObject.ApplyCustomUpdateState -= updateStateTransforms;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
75
osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs
Normal file
75
osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning
|
||||||
|
{
|
||||||
|
public abstract class FollowCircle : CompositeDrawable
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
protected DrawableHitObject? ParentObject { get; private set; }
|
||||||
|
|
||||||
|
protected FollowCircle()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
((DrawableSlider?)ParentObject)?.Tracking.BindValueChanged(OnTrackingChanged, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
if (ParentObject != null)
|
||||||
|
{
|
||||||
|
ParentObject.HitObjectApplied += onHitObjectApplied;
|
||||||
|
onHitObjectApplied(ParentObject);
|
||||||
|
|
||||||
|
ParentObject.ApplyCustomUpdateState += updateStateTransforms;
|
||||||
|
updateStateTransforms(ParentObject, ParentObject.State.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onHitObjectApplied(DrawableHitObject drawableObject)
|
||||||
|
{
|
||||||
|
this.ScaleTo(1f)
|
||||||
|
.FadeOut();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state)
|
||||||
|
{
|
||||||
|
// Gets called by slider ticks, tails, etc., leading to duplicated
|
||||||
|
// animations which may negatively affect performance
|
||||||
|
if (drawableObject is not DrawableSlider)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
|
||||||
|
OnSliderEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (ParentObject != null)
|
||||||
|
{
|
||||||
|
ParentObject.HitObjectApplied -= onHitObjectApplied;
|
||||||
|
ParentObject.ApplyCustomUpdateState -= updateStateTransforms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void OnTrackingChanged(ValueChangedEvent<bool> tracking);
|
||||||
|
|
||||||
|
protected abstract void OnSliderEnd();
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,14 @@
|
|||||||
// 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.Diagnostics;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||||
{
|
{
|
||||||
public class LegacyFollowCircle : CompositeDrawable
|
public class LegacyFollowCircle : FollowCircle
|
||||||
{
|
{
|
||||||
public LegacyFollowCircle(Drawable animationContent)
|
public LegacyFollowCircle(Drawable animationContent)
|
||||||
{
|
{
|
||||||
@ -18,5 +20,36 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
InternalChild = animationContent;
|
InternalChild = animationContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnTrackingChanged(ValueChangedEvent<bool> tracking)
|
||||||
|
{
|
||||||
|
Debug.Assert(ParentObject != null);
|
||||||
|
|
||||||
|
if (ParentObject.Judged)
|
||||||
|
return;
|
||||||
|
|
||||||
|
double remainingTime = ParentObject.HitStateUpdateTime - Time.Current;
|
||||||
|
|
||||||
|
// Note that the scale adjust here is 2 instead of DrawableSliderBall.FOLLOW_AREA to match legacy behaviour.
|
||||||
|
// This means the actual tracking area for gameplay purposes is larger than the sprite (but skins may be accounting for this).
|
||||||
|
if (tracking.NewValue)
|
||||||
|
{
|
||||||
|
// TODO: Follow circle should bounce on each slider tick.
|
||||||
|
this.ScaleTo(0.5f).ScaleTo(2f, Math.Min(180f, remainingTime), Easing.Out)
|
||||||
|
.FadeTo(0).FadeTo(1f, Math.Min(60f, remainingTime));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: Should animate only at the next slider tick if we want to match stable perfectly.
|
||||||
|
this.ScaleTo(4f, 100)
|
||||||
|
.FadeTo(0f, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSliderEnd()
|
||||||
|
{
|
||||||
|
this.ScaleTo(1.6f, 200, Easing.Out)
|
||||||
|
.FadeOut(200, Easing.In);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -194,7 +194,28 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
private static Vector2 clampSliderToPlayfield(WorkingObject workingObject)
|
private static Vector2 clampSliderToPlayfield(WorkingObject workingObject)
|
||||||
{
|
{
|
||||||
var slider = (Slider)workingObject.HitObject;
|
var slider = (Slider)workingObject.HitObject;
|
||||||
var possibleMovementBounds = calculatePossibleMovementBounds(slider);
|
var possibleMovementBounds = CalculatePossibleMovementBounds(slider);
|
||||||
|
|
||||||
|
// The slider rotation applied in computeModifiedPosition might make it impossible to fit the slider into the playfield
|
||||||
|
// For example, a long horizontal slider will be off-screen when rotated by 90 degrees
|
||||||
|
// In this case, limit the rotation to either 0 or 180 degrees
|
||||||
|
if (possibleMovementBounds.Width < 0 || possibleMovementBounds.Height < 0)
|
||||||
|
{
|
||||||
|
float currentRotation = getSliderRotation(slider);
|
||||||
|
float diff1 = getAngleDifference(workingObject.RotationOriginal, currentRotation);
|
||||||
|
float diff2 = getAngleDifference(workingObject.RotationOriginal + MathF.PI, currentRotation);
|
||||||
|
|
||||||
|
if (diff1 < diff2)
|
||||||
|
{
|
||||||
|
RotateSlider(slider, workingObject.RotationOriginal - getSliderRotation(slider));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RotateSlider(slider, workingObject.RotationOriginal + MathF.PI - getSliderRotation(slider));
|
||||||
|
}
|
||||||
|
|
||||||
|
possibleMovementBounds = CalculatePossibleMovementBounds(slider);
|
||||||
|
}
|
||||||
|
|
||||||
var previousPosition = workingObject.PositionModified;
|
var previousPosition = workingObject.PositionModified;
|
||||||
|
|
||||||
@ -239,10 +260,12 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
/// Calculates a <see cref="RectangleF"/> which contains all of the possible movements of the slider (in relative X/Y coordinates)
|
/// Calculates a <see cref="RectangleF"/> which contains all of the possible movements of the slider (in relative X/Y coordinates)
|
||||||
/// such that the entire slider is inside the playfield.
|
/// such that the entire slider is inside the playfield.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="slider">The <see cref="Slider"/> for which to calculate a movement bounding box.</param>
|
||||||
|
/// <returns>A <see cref="RectangleF"/> which contains all of the possible movements of the slider such that the entire slider is inside the playfield.</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// If the slider is larger than the playfield, the returned <see cref="RectangleF"/> may have negative width/height.
|
/// If the slider is larger than the playfield, the returned <see cref="RectangleF"/> may have negative width/height.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private static RectangleF calculatePossibleMovementBounds(Slider slider)
|
public static RectangleF CalculatePossibleMovementBounds(Slider slider)
|
||||||
{
|
{
|
||||||
var pathPositions = new List<Vector2>();
|
var pathPositions = new List<Vector2>();
|
||||||
slider.Path.GetPathToProgress(pathPositions, 0, 1);
|
slider.Path.GetPathToProgress(pathPositions, 0, 1);
|
||||||
@ -353,6 +376,18 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
return MathF.Atan2(endPositionVector.Y, endPositionVector.X);
|
return MathF.Atan2(endPositionVector.Y, endPositionVector.X);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the absolute difference between 2 angles measured in Radians.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="angle1">The first angle</param>
|
||||||
|
/// <param name="angle2">The second angle</param>
|
||||||
|
/// <returns>The absolute difference with interval <c>[0, MathF.PI)</c></returns>
|
||||||
|
private static float getAngleDifference(float angle1, float angle2)
|
||||||
|
{
|
||||||
|
float diff = MathF.Abs(angle1 - angle2) % (MathF.PI * 2);
|
||||||
|
return MathF.Min(diff, MathF.PI * 2 - diff);
|
||||||
|
}
|
||||||
|
|
||||||
public class ObjectPositionInfo
|
public class ObjectPositionInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -395,6 +430,7 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
|
|
||||||
private class WorkingObject
|
private class WorkingObject
|
||||||
{
|
{
|
||||||
|
public float RotationOriginal { get; }
|
||||||
public Vector2 PositionOriginal { get; }
|
public Vector2 PositionOriginal { get; }
|
||||||
public Vector2 PositionModified { get; set; }
|
public Vector2 PositionModified { get; set; }
|
||||||
public Vector2 EndPositionModified { get; set; }
|
public Vector2 EndPositionModified { get; set; }
|
||||||
@ -405,6 +441,7 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
public WorkingObject(ObjectPositionInfo positionInfo)
|
public WorkingObject(ObjectPositionInfo positionInfo)
|
||||||
{
|
{
|
||||||
PositionInfo = positionInfo;
|
PositionInfo = positionInfo;
|
||||||
|
RotationOriginal = HitObject is Slider slider ? getSliderRotation(slider) : 0;
|
||||||
PositionModified = PositionOriginal = HitObject.Position;
|
PositionModified = PositionOriginal = HitObject.Position;
|
||||||
EndPositionModified = HitObject.EndPosition;
|
EndPositionModified = HitObject.EndPosition;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<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="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
|
@ -4,9 +4,12 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
|
using osu.Game.Models;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
namespace osu.Game.Tests.NonVisual
|
namespace osu.Game.Tests.NonVisual
|
||||||
{
|
{
|
||||||
@ -23,6 +26,47 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
Assert.IsTrue(ourInfo.MatchesOnlineID(otherInfo));
|
Assert.IsTrue(ourInfo.MatchesOnlineID(otherInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAudioEqualityNoFile()
|
||||||
|
{
|
||||||
|
var beatmapSetA = TestResources.CreateTestBeatmapSetInfo(1);
|
||||||
|
var beatmapSetB = TestResources.CreateTestBeatmapSetInfo(1);
|
||||||
|
|
||||||
|
Assert.AreNotEqual(beatmapSetA, beatmapSetB);
|
||||||
|
Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAudioEqualitySameHash()
|
||||||
|
{
|
||||||
|
var beatmapSetA = TestResources.CreateTestBeatmapSetInfo(1);
|
||||||
|
var beatmapSetB = TestResources.CreateTestBeatmapSetInfo(1);
|
||||||
|
|
||||||
|
addAudioFile(beatmapSetA, "abc");
|
||||||
|
addAudioFile(beatmapSetB, "abc");
|
||||||
|
|
||||||
|
Assert.AreNotEqual(beatmapSetA, beatmapSetB);
|
||||||
|
Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAudioEqualityDifferentHash()
|
||||||
|
{
|
||||||
|
var beatmapSetA = TestResources.CreateTestBeatmapSetInfo(1);
|
||||||
|
var beatmapSetB = TestResources.CreateTestBeatmapSetInfo(1);
|
||||||
|
|
||||||
|
addAudioFile(beatmapSetA);
|
||||||
|
addAudioFile(beatmapSetB);
|
||||||
|
|
||||||
|
Assert.AreNotEqual(beatmapSetA, beatmapSetB);
|
||||||
|
Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addAudioFile(BeatmapSetInfo beatmapSetInfo, string hash = null)
|
||||||
|
{
|
||||||
|
beatmapSetInfo.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = hash ?? Guid.NewGuid().ToString() }, "audio.mp3"));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestDatabasedWithDatabased()
|
public void TestDatabasedWithDatabased()
|
||||||
{
|
{
|
||||||
|
@ -134,6 +134,7 @@ namespace osu.Game.Tests.Resources
|
|||||||
DifficultyName = $"{version} {beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})",
|
DifficultyName = $"{version} {beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})",
|
||||||
StarRating = diff,
|
StarRating = diff,
|
||||||
Length = length,
|
Length = length,
|
||||||
|
BeatmapSet = beatmapSet,
|
||||||
BPM = bpm,
|
BPM = bpm,
|
||||||
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
||||||
Ruleset = rulesetInfo,
|
Ruleset = rulesetInfo,
|
||||||
|
@ -6,6 +6,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
@ -103,6 +104,8 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
*/
|
*/
|
||||||
public void TestAddAudioTrack()
|
public void TestAddAudioTrack()
|
||||||
{
|
{
|
||||||
|
AddAssert("track is virtual", () => Beatmap.Value.Track is TrackVirtual);
|
||||||
|
|
||||||
AddAssert("switch track to real track", () =>
|
AddAssert("switch track to real track", () =>
|
||||||
{
|
{
|
||||||
var setup = Editor.ChildrenOfType<SetupScreen>().First();
|
var setup = Editor.ChildrenOfType<SetupScreen>().First();
|
||||||
@ -131,6 +134,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddAssert("track is not virtual", () => Beatmap.Value.Track is not TrackVirtual);
|
||||||
AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000);
|
AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,11 +6,13 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||||
using osu.Game.Screens.Select;
|
using osu.Game.Screens.Select;
|
||||||
@ -23,7 +25,9 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestCantExitWithoutSaving()
|
public void TestCantExitWithoutSaving()
|
||||||
{
|
{
|
||||||
|
AddUntilStep("Wait for dialog overlay load", () => ((Drawable)Game.Dependencies.Get<IDialogOverlay>()).IsLoaded);
|
||||||
AddRepeatStep("Exit", () => InputManager.Key(Key.Escape), 10);
|
AddRepeatStep("Exit", () => InputManager.Key(Key.Escape), 10);
|
||||||
|
AddAssert("Sample playback disabled", () => Editor.SamplePlaybackDisabled.Value);
|
||||||
AddAssert("Editor is still active screen", () => Game.ScreenStack.CurrentScreen is Editor);
|
AddAssert("Editor is still active screen", () => Game.ScreenStack.CurrentScreen is Editor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,122 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.Ranking;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
|
{
|
||||||
|
public class TestScenePlayerLocalScoreImport : PlayerTestScene
|
||||||
|
{
|
||||||
|
private BeatmapManager beatmaps = null!;
|
||||||
|
private RulesetStore rulesets = null!;
|
||||||
|
|
||||||
|
private BeatmapSetInfo? importedSet;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(GameHost host, AudioManager audio)
|
||||||
|
{
|
||||||
|
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||||
|
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||||
|
Dependencies.Cache(new ScoreManager(rulesets, () => beatmaps, LocalStorage, Realm, Scheduler, API));
|
||||||
|
Dependencies.Cache(Realm);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
|
||||||
|
AddStep("import beatmap", () =>
|
||||||
|
{
|
||||||
|
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||||
|
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => beatmaps.GetWorkingBeatmap(importedSet?.Beatmaps.First()).Beatmap;
|
||||||
|
|
||||||
|
private Ruleset? customRuleset;
|
||||||
|
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => customRuleset ?? new OsuRuleset();
|
||||||
|
|
||||||
|
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false);
|
||||||
|
|
||||||
|
protected override bool HasCustomSteps => true;
|
||||||
|
|
||||||
|
protected override bool AllowFail => false;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLastPlayedUpdated()
|
||||||
|
{
|
||||||
|
DateTimeOffset? getLastPlayed() => Realm.Run(r => r.Find<BeatmapInfo>(Beatmap.Value.BeatmapInfo.ID)?.LastPlayed);
|
||||||
|
|
||||||
|
AddStep("set no custom ruleset", () => customRuleset = null);
|
||||||
|
AddAssert("last played is null", () => getLastPlayed() == null);
|
||||||
|
|
||||||
|
CreateTest();
|
||||||
|
|
||||||
|
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||||
|
AddUntilStep("wait for last played to update", () => getLastPlayed() != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScoreStoredLocally()
|
||||||
|
{
|
||||||
|
AddStep("set no custom ruleset", () => customRuleset = null);
|
||||||
|
|
||||||
|
CreateTest();
|
||||||
|
|
||||||
|
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||||
|
|
||||||
|
AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime()));
|
||||||
|
|
||||||
|
AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen);
|
||||||
|
AddUntilStep("score in database", () => Realm.Run(r => r.Find<ScoreInfo>(Player.Score.ScoreInfo.ID) != null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScoreStoredLocallyCustomRuleset()
|
||||||
|
{
|
||||||
|
Ruleset createCustomRuleset() => new CustomRuleset();
|
||||||
|
|
||||||
|
AddStep("import custom ruleset", () => Realm.Write(r => r.Add(createCustomRuleset().RulesetInfo)));
|
||||||
|
AddStep("set custom ruleset", () => customRuleset = createCustomRuleset());
|
||||||
|
|
||||||
|
CreateTest();
|
||||||
|
|
||||||
|
AddAssert("score has custom ruleset", () => Player.Score.ScoreInfo.Ruleset.Equals(customRuleset.AsNonNull().RulesetInfo));
|
||||||
|
|
||||||
|
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||||
|
|
||||||
|
AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime()));
|
||||||
|
|
||||||
|
AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen);
|
||||||
|
AddUntilStep("score in database", () => Realm.Run(r => r.Find<ScoreInfo>(Player.Score.ScoreInfo.ID) != null));
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CustomRuleset : OsuRuleset, ILegacyRuleset
|
||||||
|
{
|
||||||
|
public override string Description => "custom";
|
||||||
|
public override string ShortName => "custom";
|
||||||
|
|
||||||
|
int ILegacyRuleset.LegacyID => -1;
|
||||||
|
|
||||||
|
public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -365,21 +365,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
ImportedScore = score;
|
ImportedScore = score;
|
||||||
|
|
||||||
// It was discovered that Score members could sometimes be half-populated.
|
// Calling base.ImportScore is omitted as it will fail for the test method which uses a custom ruleset.
|
||||||
// In particular, the RulesetID property could be set to 0 even on non-osu! maps.
|
// This can be resolved by doing something similar to what TestScenePlayerLocalScoreImport is doing,
|
||||||
// We want to test that the state of that property is consistent in this test.
|
// but requires a bit of restructuring.
|
||||||
// EF makes this impossible.
|
|
||||||
//
|
|
||||||
// First off, because of the EF navigational property-explicit foreign key field duality,
|
|
||||||
// it can happen that - for example - the Ruleset navigational property is correctly initialised to mania,
|
|
||||||
// but the RulesetID foreign key property is not initialised and remains 0.
|
|
||||||
// EF silently bypasses this by prioritising the Ruleset navigational property over the RulesetID foreign key one.
|
|
||||||
//
|
|
||||||
// Additionally, adding an entity to an EF DbSet CAUSES SIDE EFFECTS with regard to the foreign key property.
|
|
||||||
// In the above instance, if a ScoreInfo with Ruleset = {mania} and RulesetID = 0 is attached to an EF context,
|
|
||||||
// RulesetID WILL BE SILENTLY SET TO THE CORRECT VALUE of 3.
|
|
||||||
//
|
|
||||||
// For the above reasons, actual importing is disabled in this test.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,6 +142,28 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType<DownloadButton>().First().Enabled.Value);
|
AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType<DownloadButton>().First().Enabled.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLocallyAvailableWithoutReplay()
|
||||||
|
{
|
||||||
|
Live<ScoreInfo> imported = null;
|
||||||
|
|
||||||
|
AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(false, false)));
|
||||||
|
|
||||||
|
AddStep("create button without replay", () =>
|
||||||
|
{
|
||||||
|
Child = downloadButton = new TestReplayDownloadButton(imported.Value)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for load", () => downloadButton.IsLoaded);
|
||||||
|
|
||||||
|
AddUntilStep("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded);
|
||||||
|
AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType<DownloadButton>().First().Enabled.Value);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestScoreImportThenDelete()
|
public void TestScoreImportThenDelete()
|
||||||
{
|
{
|
||||||
@ -189,11 +211,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType<DownloadButton>().First().Enabled.Value);
|
AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType<DownloadButton>().First().Enabled.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScoreInfo getScoreInfo(bool replayAvailable)
|
private ScoreInfo getScoreInfo(bool replayAvailable, bool hasOnlineId = true)
|
||||||
{
|
{
|
||||||
return new APIScore
|
return new APIScore
|
||||||
{
|
{
|
||||||
OnlineID = online_score_id,
|
OnlineID = hasOnlineId ? online_score_id : 0,
|
||||||
RulesetID = 0,
|
RulesetID = 0,
|
||||||
Beatmap = CreateAPIBeatmapSet(new OsuRuleset().RulesetInfo).Beatmaps.First(),
|
Beatmap = CreateAPIBeatmapSet(new OsuRuleset().RulesetInfo).Beatmaps.First(),
|
||||||
HasReplay = replayAvailable,
|
HasReplay = replayAvailable,
|
||||||
|
@ -18,6 +18,7 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Beatmaps.Drawables;
|
using osu.Game.Beatmaps.Drawables;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Cursor;
|
||||||
using osu.Game.Models;
|
using osu.Game.Models;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
@ -195,12 +196,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestDownloadButtonHiddenWhenBeatmapExists()
|
public void TestDownloadButtonHiddenWhenBeatmapExists()
|
||||||
{
|
{
|
||||||
var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo;
|
|
||||||
Live<BeatmapSetInfo> imported = null;
|
Live<BeatmapSetInfo> imported = null;
|
||||||
|
|
||||||
Debug.Assert(beatmap.BeatmapSet != null);
|
AddStep("import beatmap", () =>
|
||||||
|
{
|
||||||
|
var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo;
|
||||||
|
|
||||||
AddStep("import beatmap", () => imported = manager.Import(beatmap.BeatmapSet));
|
Debug.Assert(beatmap.BeatmapSet != null);
|
||||||
|
imported = manager.Import(beatmap.BeatmapSet);
|
||||||
|
});
|
||||||
|
|
||||||
createPlaylistWithBeatmaps(() => imported.PerformRead(s => s.Beatmaps.Detach()));
|
createPlaylistWithBeatmaps(() => imported.PerformRead(s => s.Beatmaps.Detach()));
|
||||||
|
|
||||||
@ -245,14 +249,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestExpiredItems()
|
public void TestExpiredItems()
|
||||||
{
|
{
|
||||||
AddStep("create playlist", () =>
|
createPlaylist(p =>
|
||||||
{
|
{
|
||||||
Child = playlist = new TestPlaylist
|
p.Items.Clear();
|
||||||
{
|
p.Items.AddRange(new[]
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Size = new Vector2(500, 300),
|
|
||||||
Items =
|
|
||||||
{
|
{
|
||||||
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
|
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
|
||||||
{
|
{
|
||||||
@ -277,8 +277,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
new APIMod(new OsuModAutoplay())
|
new APIMod(new OsuModAutoplay())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
|
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
|
||||||
@ -321,19 +320,44 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
=> AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible",
|
=> AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible",
|
||||||
() => (playlist.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistRemoveButton>().ElementAt(2 + index * 2).Alpha > 0) == visible);
|
() => (playlist.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistRemoveButton>().ElementAt(2 + index * 2).Alpha > 0) == visible);
|
||||||
|
|
||||||
|
private void createPlaylistWithBeatmaps(Func<IEnumerable<IBeatmapInfo>> beatmaps) => createPlaylist(p =>
|
||||||
|
{
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
p.Items.Clear();
|
||||||
|
|
||||||
|
foreach (var b in beatmaps())
|
||||||
|
{
|
||||||
|
p.Items.Add(new PlaylistItem(b)
|
||||||
|
{
|
||||||
|
ID = index++,
|
||||||
|
OwnerID = 2,
|
||||||
|
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||||
|
RequiredMods = new[]
|
||||||
|
{
|
||||||
|
new APIMod(new OsuModHardRock()),
|
||||||
|
new APIMod(new OsuModDoubleTime()),
|
||||||
|
new APIMod(new OsuModAutoplay())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
private void createPlaylist(Action<TestPlaylist> setupPlaylist = null)
|
private void createPlaylist(Action<TestPlaylist> setupPlaylist = null)
|
||||||
{
|
{
|
||||||
AddStep("create playlist", () =>
|
AddStep("create playlist", () =>
|
||||||
{
|
{
|
||||||
|
Child = new OsuContextMenuContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = playlist = new TestPlaylist
|
Child = playlist = new TestPlaylist
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(500, 300)
|
Size = new Vector2(500, 300)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
setupPlaylist?.Invoke(playlist);
|
|
||||||
|
|
||||||
for (int i = 0; i < 20; i++)
|
for (int i = 0; i < 20; i++)
|
||||||
{
|
{
|
||||||
playlist.Items.Add(new PlaylistItem(i % 2 == 1
|
playlist.Items.Add(new PlaylistItem(i % 2 == 1
|
||||||
@ -360,39 +384,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
|
setupPlaylist?.Invoke(playlist);
|
||||||
}
|
|
||||||
|
|
||||||
private void createPlaylistWithBeatmaps(Func<IEnumerable<IBeatmapInfo>> beatmaps)
|
|
||||||
{
|
|
||||||
AddStep("create playlist", () =>
|
|
||||||
{
|
|
||||||
Child = playlist = new TestPlaylist
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Size = new Vector2(500, 300)
|
|
||||||
};
|
|
||||||
|
|
||||||
int index = 0;
|
|
||||||
|
|
||||||
foreach (var b in beatmaps())
|
|
||||||
{
|
|
||||||
playlist.Items.Add(new PlaylistItem(b)
|
|
||||||
{
|
|
||||||
ID = index++,
|
|
||||||
OwnerID = 2,
|
|
||||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
|
||||||
RequiredMods = new[]
|
|
||||||
{
|
|
||||||
new APIMod(new OsuModHardRock()),
|
|
||||||
new APIMod(new OsuModDoubleTime()),
|
|
||||||
new APIMod(new OsuModAutoplay())
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
|
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Graphics.Cursor;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online;
|
using osu.Game.Online;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
@ -368,12 +369,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
ParticipantsList? participantsList = null;
|
ParticipantsList? participantsList = null;
|
||||||
|
|
||||||
AddStep("create new list", () => Child = participantsList = new ParticipantsList
|
AddStep("create new list", () => Child = new OsuContextMenuContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = participantsList = new ParticipantsList
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
Size = new Vector2(380, 0.7f)
|
Size = new Vector2(380, 0.7f)
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for list to load", () => participantsList?.IsLoaded == true);
|
AddUntilStep("wait for list to load", () => participantsList?.IsLoaded == true);
|
||||||
|
@ -18,6 +18,7 @@ using osu.Game.Online.API.Requests.Responses;
|
|||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.BeatmapSet.Scores;
|
using osu.Game.Overlays.BeatmapSet.Scores;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -146,12 +147,12 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
{
|
{
|
||||||
var scores = new APIScoresCollection
|
var scores = new APIScoresCollection
|
||||||
{
|
{
|
||||||
Scores = new List<APIScore>
|
Scores = new List<SoloScoreInfo>
|
||||||
{
|
{
|
||||||
new APIScore
|
new SoloScoreInfo
|
||||||
{
|
{
|
||||||
Date = DateTimeOffset.Now,
|
EndedAt = DateTimeOffset.Now,
|
||||||
OnlineID = onlineID++,
|
ID = onlineID++,
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
{
|
{
|
||||||
Id = 6602580,
|
Id = 6602580,
|
||||||
@ -175,10 +176,10 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
TotalScore = 1234567890,
|
TotalScore = 1234567890,
|
||||||
Accuracy = 1,
|
Accuracy = 1,
|
||||||
},
|
},
|
||||||
new APIScore
|
new SoloScoreInfo
|
||||||
{
|
{
|
||||||
Date = DateTimeOffset.Now,
|
EndedAt = DateTimeOffset.Now,
|
||||||
OnlineID = onlineID++,
|
ID = onlineID++,
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
{
|
{
|
||||||
Id = 4608074,
|
Id = 4608074,
|
||||||
@ -201,10 +202,10 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
TotalScore = 1234789,
|
TotalScore = 1234789,
|
||||||
Accuracy = 0.9997,
|
Accuracy = 0.9997,
|
||||||
},
|
},
|
||||||
new APIScore
|
new SoloScoreInfo
|
||||||
{
|
{
|
||||||
Date = DateTimeOffset.Now,
|
EndedAt = DateTimeOffset.Now,
|
||||||
OnlineID = onlineID++,
|
ID = onlineID++,
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
{
|
{
|
||||||
Id = 1014222,
|
Id = 1014222,
|
||||||
@ -226,10 +227,10 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
TotalScore = 12345678,
|
TotalScore = 12345678,
|
||||||
Accuracy = 0.9854,
|
Accuracy = 0.9854,
|
||||||
},
|
},
|
||||||
new APIScore
|
new SoloScoreInfo
|
||||||
{
|
{
|
||||||
Date = DateTimeOffset.Now,
|
EndedAt = DateTimeOffset.Now,
|
||||||
OnlineID = onlineID++,
|
ID = onlineID++,
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
{
|
{
|
||||||
Id = 1541390,
|
Id = 1541390,
|
||||||
@ -250,10 +251,10 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
TotalScore = 1234567,
|
TotalScore = 1234567,
|
||||||
Accuracy = 0.8765,
|
Accuracy = 0.8765,
|
||||||
},
|
},
|
||||||
new APIScore
|
new SoloScoreInfo
|
||||||
{
|
{
|
||||||
Date = DateTimeOffset.Now,
|
EndedAt = DateTimeOffset.Now,
|
||||||
OnlineID = onlineID++,
|
ID = onlineID++,
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
{
|
{
|
||||||
Id = 7151382,
|
Id = 7151382,
|
||||||
@ -275,12 +276,12 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
|
|
||||||
foreach (var s in scores.Scores)
|
foreach (var s in scores.Scores)
|
||||||
{
|
{
|
||||||
s.Statistics = new Dictionary<string, int>
|
s.Statistics = new Dictionary<HitResult, int>
|
||||||
{
|
{
|
||||||
{ "count_300", RNG.Next(2000) },
|
{ HitResult.Great, RNG.Next(2000) },
|
||||||
{ "count_100", RNG.Next(2000) },
|
{ HitResult.Ok, RNG.Next(2000) },
|
||||||
{ "count_50", RNG.Next(2000) },
|
{ HitResult.Meh, RNG.Next(2000) },
|
||||||
{ "count_miss", RNG.Next(2000) }
|
{ HitResult.Miss, RNG.Next(2000) }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,10 +290,10 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
|
|
||||||
private APIScoreWithPosition createUserBest() => new APIScoreWithPosition
|
private APIScoreWithPosition createUserBest() => new APIScoreWithPosition
|
||||||
{
|
{
|
||||||
Score = new APIScore
|
Score = new SoloScoreInfo
|
||||||
{
|
{
|
||||||
Date = DateTimeOffset.Now,
|
EndedAt = DateTimeOffset.Now,
|
||||||
OnlineID = onlineID++,
|
ID = onlineID++,
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
{
|
{
|
||||||
Id = 7151382,
|
Id = 7151382,
|
||||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm));
|
dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm));
|
||||||
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
|
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
|
||||||
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, Scheduler));
|
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, Scheduler, API));
|
||||||
Dependencies.Cache(Realm);
|
Dependencies.Cache(Realm);
|
||||||
|
|
||||||
return dependencies;
|
return dependencies;
|
||||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
{
|
{
|
||||||
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
|
||||||
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
|
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||||
Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, Realm, Scheduler));
|
Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, Realm, Scheduler, API));
|
||||||
Dependencies.Cache(Realm);
|
Dependencies.Cache(Realm);
|
||||||
|
|
||||||
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||||
|
@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
|
|
||||||
dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm));
|
dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm));
|
||||||
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
|
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
|
||||||
dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get<RulesetStore>(), () => beatmapManager, LocalStorage, Realm, Scheduler));
|
dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get<RulesetStore>(), () => beatmapManager, LocalStorage, Realm, Scheduler, API));
|
||||||
Dependencies.Cache(Realm);
|
Dependencies.Cache(Realm);
|
||||||
|
|
||||||
return dependencies;
|
return dependencies;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
@ -13,10 +13,24 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
{
|
{
|
||||||
public class TestSceneModIcon : OsuTestScene
|
public class TestSceneModIcon : OsuTestScene
|
||||||
{
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestShowAllMods()
|
||||||
|
{
|
||||||
|
AddStep("create mod icons", () =>
|
||||||
|
{
|
||||||
|
Child = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Full,
|
||||||
|
ChildrenEnumerable = Ruleset.Value.CreateInstance().CreateAllMods().Select(m => new ModIcon(m)),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestChangeModType()
|
public void TestChangeModType()
|
||||||
{
|
{
|
||||||
ModIcon icon = null;
|
ModIcon icon = null!;
|
||||||
|
|
||||||
AddStep("create mod icon", () => Child = icon = new ModIcon(new OsuModDoubleTime()));
|
AddStep("create mod icon", () => Child = icon = new ModIcon(new OsuModDoubleTime()));
|
||||||
AddStep("change mod", () => icon.Mod = new OsuModEasy());
|
AddStep("change mod", () => icon.Mod = new OsuModEasy());
|
||||||
@ -25,7 +39,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestInterfaceModType()
|
public void TestInterfaceModType()
|
||||||
{
|
{
|
||||||
ModIcon icon = null;
|
ModIcon icon = null!;
|
||||||
|
|
||||||
var ruleset = new OsuRuleset();
|
var ruleset = new OsuRuleset();
|
||||||
|
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<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="DeepEqual" Version="2.0.0" />
|
<PackageReference Include="DeepEqual" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
|
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
<PackageReference Include="Moq" Version="4.17.2" />
|
<PackageReference Include="Moq" Version="4.18.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
<StartupObject>osu.Game.Tournament.Tests.TournamentTestRunner</StartupObject>
|
<StartupObject>osu.Game.Tournament.Tests.TournamentTestRunner</StartupObject>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
|
@ -12,7 +12,8 @@ namespace osu.Game.Tournament.Components
|
|||||||
{
|
{
|
||||||
public class TournamentSpriteTextWithBackground : CompositeDrawable
|
public class TournamentSpriteTextWithBackground : CompositeDrawable
|
||||||
{
|
{
|
||||||
protected readonly TournamentSpriteText Text;
|
public readonly TournamentSpriteText Text;
|
||||||
|
|
||||||
protected readonly Box Background;
|
protected readonly Box Background;
|
||||||
|
|
||||||
public TournamentSpriteTextWithBackground(string text = "")
|
public TournamentSpriteTextWithBackground(string text = "")
|
||||||
|
@ -22,6 +22,8 @@ namespace osu.Game.Tournament.Components
|
|||||||
private Video video;
|
private Video video;
|
||||||
private ManualClock manualClock;
|
private ManualClock manualClock;
|
||||||
|
|
||||||
|
public bool VideoAvailable => video != null;
|
||||||
|
|
||||||
public TourneyVideo(string filename, bool drawFallbackGradient = false)
|
public TourneyVideo(string filename, bool drawFallbackGradient = false)
|
||||||
{
|
{
|
||||||
this.filename = filename;
|
this.filename = filename;
|
||||||
|
@ -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.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
|
||||||
@ -13,7 +11,7 @@ namespace osu.Game.Tournament.Models
|
|||||||
public int ID;
|
public int ID;
|
||||||
|
|
||||||
[JsonProperty("BeatmapInfo")]
|
[JsonProperty("BeatmapInfo")]
|
||||||
public TournamentBeatmap Beatmap;
|
public TournamentBeatmap? Beatmap;
|
||||||
|
|
||||||
public long Score;
|
public long Score;
|
||||||
|
|
||||||
|
101
osu.Game.Tournament/SaveChangesOverlay.cs
Normal file
101
osu.Game.Tournament/SaveChangesOverlay.cs
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
// 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.Threading.Tasks;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tournament
|
||||||
|
{
|
||||||
|
internal class SaveChangesOverlay : CompositeDrawable
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private TournamentGame tournamentGame { get; set; } = null!;
|
||||||
|
|
||||||
|
private string? lastSerialisedLadder;
|
||||||
|
private readonly TourneyButton saveChangesButton;
|
||||||
|
|
||||||
|
public SaveChangesOverlay()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChild = new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
Position = new Vector2(5),
|
||||||
|
CornerRadius = 10,
|
||||||
|
Masking = true,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = OsuColour.Gray(0.2f),
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
saveChangesButton = new TourneyButton
|
||||||
|
{
|
||||||
|
Text = "Save Changes",
|
||||||
|
Width = 140,
|
||||||
|
Height = 50,
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Top = 10,
|
||||||
|
Left = 10,
|
||||||
|
},
|
||||||
|
Margin = new MarginPadding
|
||||||
|
{
|
||||||
|
Right = 10,
|
||||||
|
Bottom = 10,
|
||||||
|
},
|
||||||
|
Action = saveChanges,
|
||||||
|
// Enabled = { Value = false },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
scheduleNextCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task checkForChanges()
|
||||||
|
{
|
||||||
|
string serialisedLadder = await Task.Run(() => tournamentGame.GetSerialisedLadder());
|
||||||
|
|
||||||
|
// If a save hasn't been triggered by the user yet, populate the initial value
|
||||||
|
lastSerialisedLadder ??= serialisedLadder;
|
||||||
|
|
||||||
|
if (lastSerialisedLadder != serialisedLadder && !saveChangesButton.Enabled.Value)
|
||||||
|
{
|
||||||
|
saveChangesButton.Enabled.Value = true;
|
||||||
|
saveChangesButton.Background
|
||||||
|
.FadeColour(saveChangesButton.BackgroundColour.Lighten(0.5f), 500, Easing.In).Then()
|
||||||
|
.FadeColour(saveChangesButton.BackgroundColour, 500, Easing.Out)
|
||||||
|
.Loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleNextCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleNextCheck() => Scheduler.AddDelayed(() => checkForChanges().FireAndForget(), 1000);
|
||||||
|
|
||||||
|
private void saveChanges()
|
||||||
|
{
|
||||||
|
tournamentGame.SaveChanges();
|
||||||
|
lastSerialisedLadder = tournamentGame.GetSerialisedLadder();
|
||||||
|
|
||||||
|
saveChangesButton.Enabled.Value = false;
|
||||||
|
saveChangesButton.Background.FadeColour(saveChangesButton.BackgroundColour, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,8 +12,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Framework.Graphics.Textures;
|
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
@ -45,7 +43,7 @@ namespace osu.Game.Tournament.Screens.Drawings
|
|||||||
public ITeamList TeamList;
|
public ITeamList TeamList;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(TextureStore textures, Storage storage)
|
private void load(Storage storage)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
@ -91,11 +89,10 @@ namespace osu.Game.Tournament.Screens.Drawings
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Sprite
|
new TourneyVideo("drawings")
|
||||||
{
|
{
|
||||||
|
Loop = true,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
FillMode = FillMode.Fill,
|
|
||||||
Texture = textures.Get(@"Backgrounds/Drawings/background.png")
|
|
||||||
},
|
},
|
||||||
// Visualiser
|
// Visualiser
|
||||||
new VisualiserContainer
|
new VisualiserContainer
|
||||||
|
@ -298,10 +298,10 @@ namespace osu.Game.Tournament.Screens.Editors
|
|||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePanel()
|
private void updatePanel() => Scheduler.AddOnce(() =>
|
||||||
{
|
{
|
||||||
drawableContainer.Child = new UserGridPanel(user.ToAPIUser()) { Width = 300 };
|
drawableContainer.Child = new UserGridPanel(user.ToAPIUser()) { Width = 300 };
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Tournament.Screens.Editors
|
namespace osu.Game.Tournament.Screens.Editors
|
||||||
{
|
{
|
||||||
public abstract class TournamentEditorScreen<TDrawable, TModel> : TournamentScreen, IProvideVideo
|
public abstract class TournamentEditorScreen<TDrawable, TModel> : TournamentScreen
|
||||||
where TDrawable : Drawable, IModelBacked<TModel>
|
where TDrawable : Drawable, IModelBacked<TModel>
|
||||||
where TModel : class, new()
|
where TModel : class, new()
|
||||||
{
|
{
|
||||||
|
@ -16,6 +16,10 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
|
|||||||
{
|
{
|
||||||
private readonly TeamScore score;
|
private readonly TeamScore score;
|
||||||
|
|
||||||
|
private readonly TournamentSpriteTextWithBackground teamText;
|
||||||
|
|
||||||
|
private readonly Bindable<string> teamName = new Bindable<string>("???");
|
||||||
|
|
||||||
private bool showScore;
|
private bool showScore;
|
||||||
|
|
||||||
public bool ShowScore
|
public bool ShowScore
|
||||||
@ -93,7 +97,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new TournamentSpriteTextWithBackground(team?.FullName.Value ?? "???")
|
teamText = new TournamentSpriteTextWithBackground
|
||||||
{
|
{
|
||||||
Scale = new Vector2(0.5f),
|
Scale = new Vector2(0.5f),
|
||||||
Origin = anchor,
|
Origin = anchor,
|
||||||
@ -113,6 +117,11 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
|
|||||||
|
|
||||||
updateDisplay();
|
updateDisplay();
|
||||||
FinishTransforms(true);
|
FinishTransforms(true);
|
||||||
|
|
||||||
|
if (Team != null)
|
||||||
|
teamName.BindTo(Team.FullName);
|
||||||
|
|
||||||
|
teamName.BindValueChanged(name => teamText.Text.Text = name.NewValue, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateDisplay()
|
private void updateDisplay()
|
||||||
|
@ -42,6 +42,8 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
|
|||||||
currentMatch.BindTo(ladder.CurrentMatch);
|
currentMatch.BindTo(ladder.CurrentMatch);
|
||||||
currentMatch.BindValueChanged(matchChanged);
|
currentMatch.BindValueChanged(matchChanged);
|
||||||
|
|
||||||
|
currentTeam.BindValueChanged(teamChanged);
|
||||||
|
|
||||||
updateMatch();
|
updateMatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +69,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
|
|||||||
|
|
||||||
// team may change to same team, which means score is not in a good state.
|
// team may change to same team, which means score is not in a good state.
|
||||||
// thus we handle this manually.
|
// thus we handle this manually.
|
||||||
teamChanged(currentTeam.Value);
|
currentTeam.TriggerChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
@ -88,11 +90,11 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
|
|||||||
return base.OnMouseDown(e);
|
return base.OnMouseDown(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void teamChanged(TournamentTeam team)
|
private void teamChanged(ValueChangedEvent<TournamentTeam> team)
|
||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
teamDisplay = new TeamDisplay(team, teamColour, currentTeamScore, currentMatch.Value?.PointsToWin ?? 0),
|
teamDisplay = new TeamDisplay(team.NewValue, teamColour, currentTeamScore, currentMatch.Value?.PointsToWin ?? 0),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Tournament.Screens.Gameplay
|
namespace osu.Game.Tournament.Screens.Gameplay
|
||||||
{
|
{
|
||||||
public class GameplayScreen : BeatmapInfoScreen, IProvideVideo
|
public class GameplayScreen : BeatmapInfoScreen
|
||||||
{
|
{
|
||||||
private readonly BindableBool warmup = new BindableBool();
|
private readonly BindableBool warmup = new BindableBool();
|
||||||
|
|
||||||
|
@ -1,14 +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.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace osu.Game.Tournament.Screens
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Marker interface for a screen which provides its own local video background.
|
|
||||||
/// </summary>
|
|
||||||
public interface IProvideVideo
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
@ -53,6 +53,9 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
|
|||||||
|
|
||||||
editorInfo.Selected.ValueChanged += selection =>
|
editorInfo.Selected.ValueChanged += selection =>
|
||||||
{
|
{
|
||||||
|
// ensure any ongoing edits are committed out to the *current* selection before changing to a new one.
|
||||||
|
GetContainingInputManager().TriggerFocusContention(null);
|
||||||
|
|
||||||
roundDropdown.Current = selection.NewValue?.Round;
|
roundDropdown.Current = selection.NewValue?.Round;
|
||||||
losersCheckbox.Current = selection.NewValue?.Losers;
|
losersCheckbox.Current = selection.NewValue?.Losers;
|
||||||
dateTimeBox.Current = selection.NewValue?.Date;
|
dateTimeBox.Current = selection.NewValue?.Date;
|
||||||
|
@ -19,7 +19,7 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Tournament.Screens.Ladder
|
namespace osu.Game.Tournament.Screens.Ladder
|
||||||
{
|
{
|
||||||
public class LadderScreen : TournamentScreen, IProvideVideo
|
public class LadderScreen : TournamentScreen
|
||||||
{
|
{
|
||||||
protected Container<DrawableTournamentMatch> MatchesContainer;
|
protected Container<DrawableTournamentMatch> MatchesContainer;
|
||||||
private Container<Path> paths;
|
private Container<Path> paths;
|
||||||
|
@ -19,7 +19,7 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Tournament.Screens.Schedule
|
namespace osu.Game.Tournament.Screens.Schedule
|
||||||
{
|
{
|
||||||
public class ScheduleScreen : TournamentScreen // IProvidesVideo
|
public class ScheduleScreen : TournamentScreen
|
||||||
{
|
{
|
||||||
private readonly Bindable<TournamentMatch> currentMatch = new Bindable<TournamentMatch>();
|
private readonly Bindable<TournamentMatch> currentMatch = new Bindable<TournamentMatch>();
|
||||||
private Container mainContainer;
|
private Container mainContainer;
|
||||||
|
@ -9,6 +9,8 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Configuration;
|
using osu.Framework.Configuration;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
@ -19,7 +21,7 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Tournament.Screens.Setup
|
namespace osu.Game.Tournament.Screens.Setup
|
||||||
{
|
{
|
||||||
public class SetupScreen : TournamentScreen, IProvideVideo
|
public class SetupScreen : TournamentScreen
|
||||||
{
|
{
|
||||||
private FillFlowContainer fillFlow;
|
private FillFlowContainer fillFlow;
|
||||||
|
|
||||||
@ -48,13 +50,21 @@ namespace osu.Game.Tournament.Screens.Setup
|
|||||||
{
|
{
|
||||||
windowSize = frameworkConfig.GetBindable<Size>(FrameworkSetting.WindowedSize);
|
windowSize = frameworkConfig.GetBindable<Size>(FrameworkSetting.WindowedSize);
|
||||||
|
|
||||||
InternalChild = fillFlow = new FillFlowContainer
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = OsuColour.Gray(0.2f),
|
||||||
|
},
|
||||||
|
fillFlow = new FillFlowContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
Padding = new MarginPadding(10),
|
Padding = new MarginPadding(10),
|
||||||
Spacing = new Vector2(10),
|
Spacing = new Vector2(10),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
api.LocalUser.BindValueChanged(_ => Schedule(reload));
|
api.LocalUser.BindValueChanged(_ => Schedule(reload));
|
||||||
@ -74,7 +84,8 @@ namespace osu.Game.Tournament.Screens.Setup
|
|||||||
Action = () => sceneManager?.SetScreen(new StablePathSelectScreen()),
|
Action = () => sceneManager?.SetScreen(new StablePathSelectScreen()),
|
||||||
Value = fileBasedIpc?.IPCStorage?.GetFullPath(string.Empty) ?? "Not found",
|
Value = fileBasedIpc?.IPCStorage?.GetFullPath(string.Empty) ?? "Not found",
|
||||||
Failing = fileBasedIpc?.IPCStorage == null,
|
Failing = fileBasedIpc?.IPCStorage == null,
|
||||||
Description = "The osu!stable installation which is currently being used as a data source. If a source is not found, make sure you have created an empty ipc.txt in your stable cutting-edge installation."
|
Description =
|
||||||
|
"The osu!stable installation which is currently being used as a data source. If a source is not found, make sure you have created an empty ipc.txt in your stable cutting-edge installation."
|
||||||
},
|
},
|
||||||
new ActionableInfo
|
new ActionableInfo
|
||||||
{
|
{
|
||||||
|
@ -14,7 +14,7 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Tournament.Screens.Showcase
|
namespace osu.Game.Tournament.Screens.Showcase
|
||||||
{
|
{
|
||||||
public class ShowcaseScreen : BeatmapInfoScreen // IProvideVideo
|
public class ShowcaseScreen : BeatmapInfoScreen
|
||||||
{
|
{
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -19,7 +20,7 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Tournament.Screens.TeamIntro
|
namespace osu.Game.Tournament.Screens.TeamIntro
|
||||||
{
|
{
|
||||||
public class SeedingScreen : TournamentMatchScreen, IProvideVideo
|
public class SeedingScreen : TournamentMatchScreen
|
||||||
{
|
{
|
||||||
private Container mainContainer;
|
private Container mainContainer;
|
||||||
|
|
||||||
@ -69,7 +70,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro
|
|||||||
currentTeam.BindValueChanged(teamChanged, true);
|
currentTeam.BindValueChanged(teamChanged, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void teamChanged(ValueChangedEvent<TournamentTeam> team)
|
private void teamChanged(ValueChangedEvent<TournamentTeam> team) => Scheduler.AddOnce(() =>
|
||||||
{
|
{
|
||||||
if (team.NewValue == null)
|
if (team.NewValue == null)
|
||||||
{
|
{
|
||||||
@ -78,7 +79,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro
|
|||||||
}
|
}
|
||||||
|
|
||||||
showTeam(team.NewValue);
|
showTeam(team.NewValue);
|
||||||
}
|
});
|
||||||
|
|
||||||
protected override void CurrentMatchChanged(ValueChangedEvent<TournamentMatch> match)
|
protected override void CurrentMatchChanged(ValueChangedEvent<TournamentMatch> match)
|
||||||
{
|
{
|
||||||
@ -120,15 +121,23 @@ namespace osu.Game.Tournament.Screens.TeamIntro
|
|||||||
foreach (var seeding in team.SeedingResults)
|
foreach (var seeding in team.SeedingResults)
|
||||||
{
|
{
|
||||||
fill.Add(new ModRow(seeding.Mod.Value, seeding.Seed.Value));
|
fill.Add(new ModRow(seeding.Mod.Value, seeding.Seed.Value));
|
||||||
|
|
||||||
foreach (var beatmap in seeding.Beatmaps)
|
foreach (var beatmap in seeding.Beatmaps)
|
||||||
|
{
|
||||||
|
if (beatmap.Beatmap == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
fill.Add(new BeatmapScoreRow(beatmap));
|
fill.Add(new BeatmapScoreRow(beatmap));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class BeatmapScoreRow : CompositeDrawable
|
private class BeatmapScoreRow : CompositeDrawable
|
||||||
{
|
{
|
||||||
public BeatmapScoreRow(SeedingBeatmap beatmap)
|
public BeatmapScoreRow(SeedingBeatmap beatmap)
|
||||||
{
|
{
|
||||||
|
Debug.Assert(beatmap.Beatmap != null);
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
@ -157,7 +166,8 @@ namespace osu.Game.Tournament.Screens.TeamIntro
|
|||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new TournamentSpriteText { Text = beatmap.Score.ToString("#,0"), Colour = TournamentGame.TEXT_COLOUR, Width = 80 },
|
new TournamentSpriteText { Text = beatmap.Score.ToString("#,0"), Colour = TournamentGame.TEXT_COLOUR, Width = 80 },
|
||||||
new TournamentSpriteText { Text = "#" + beatmap.Seed.Value.ToString("#,0"), Colour = TournamentGame.TEXT_COLOUR, Font = OsuFont.Torus.With(weight: FontWeight.Regular) },
|
new TournamentSpriteText
|
||||||
|
{ Text = "#" + beatmap.Seed.Value.ToString("#,0"), Colour = TournamentGame.TEXT_COLOUR, Font = OsuFont.Torus.With(weight: FontWeight.Regular) },
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -13,7 +13,7 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Tournament.Screens.TeamIntro
|
namespace osu.Game.Tournament.Screens.TeamIntro
|
||||||
{
|
{
|
||||||
public class TeamIntroScreen : TournamentMatchScreen, IProvideVideo
|
public class TeamIntroScreen : TournamentMatchScreen
|
||||||
{
|
{
|
||||||
private Container mainContainer;
|
private Container mainContainer;
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Tournament.Screens.TeamWin
|
namespace osu.Game.Tournament.Screens.TeamWin
|
||||||
{
|
{
|
||||||
public class TeamWinScreen : TournamentMatchScreen, IProvideVideo
|
public class TeamWinScreen : TournamentMatchScreen
|
||||||
{
|
{
|
||||||
private Container mainContainer;
|
private Container mainContainer;
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ namespace osu.Game.Tournament.Screens.TeamWin
|
|||||||
|
|
||||||
private bool firstDisplay = true;
|
private bool firstDisplay = true;
|
||||||
|
|
||||||
private void update() => Schedule(() =>
|
private void update() => Scheduler.AddOnce(() =>
|
||||||
{
|
{
|
||||||
var match = CurrentMatch.Value;
|
var match = CurrentMatch.Value;
|
||||||
|
|
||||||
|
@ -11,8 +11,6 @@ using osu.Framework.Configuration;
|
|||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Input.Handlers.Mouse;
|
using osu.Framework.Input.Handlers.Mouse;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
@ -20,11 +18,11 @@ using osu.Game.Graphics;
|
|||||||
using osu.Game.Graphics.Cursor;
|
using osu.Game.Graphics.Cursor;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Tournament.Models;
|
using osu.Game.Tournament.Models;
|
||||||
using osuTK;
|
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Tournament
|
namespace osu.Game.Tournament
|
||||||
{
|
{
|
||||||
|
[Cached]
|
||||||
public class TournamentGame : TournamentGameBase
|
public class TournamentGame : TournamentGameBase
|
||||||
{
|
{
|
||||||
public static ColourInfo GetTeamColour(TeamColour teamColour) => teamColour == TeamColour.Red ? COLOUR_RED : COLOUR_BLUE;
|
public static ColourInfo GetTeamColour(TeamColour teamColour) => teamColour == TeamColour.Red ? COLOUR_RED : COLOUR_BLUE;
|
||||||
@ -78,40 +76,9 @@ namespace osu.Game.Tournament
|
|||||||
|
|
||||||
LoadComponentsAsync(new[]
|
LoadComponentsAsync(new[]
|
||||||
{
|
{
|
||||||
new Container
|
new SaveChangesOverlay
|
||||||
{
|
{
|
||||||
CornerRadius = 10,
|
|
||||||
Depth = float.MinValue,
|
Depth = float.MinValue,
|
||||||
Position = new Vector2(5),
|
|
||||||
Masking = true,
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Anchor = Anchor.BottomRight,
|
|
||||||
Origin = Anchor.BottomRight,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
Colour = OsuColour.Gray(0.2f),
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
},
|
|
||||||
new TourneyButton
|
|
||||||
{
|
|
||||||
Text = "Save Changes",
|
|
||||||
Width = 140,
|
|
||||||
Height = 50,
|
|
||||||
Padding = new MarginPadding
|
|
||||||
{
|
|
||||||
Top = 10,
|
|
||||||
Left = 10,
|
|
||||||
},
|
|
||||||
Margin = new MarginPadding
|
|
||||||
{
|
|
||||||
Right = 10,
|
|
||||||
Bottom = 10,
|
|
||||||
},
|
|
||||||
Action = SaveChanges,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
heightWarning = new WarningBox("Please make the window wider")
|
heightWarning = new WarningBox("Please make the window wider")
|
||||||
{
|
{
|
||||||
|
@ -295,7 +295,7 @@ namespace osu.Game.Tournament
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void SaveChanges()
|
public void SaveChanges()
|
||||||
{
|
{
|
||||||
if (!bracketLoadTaskCompletionSource.Task.IsCompletedSuccessfully)
|
if (!bracketLoadTaskCompletionSource.Task.IsCompletedSuccessfully)
|
||||||
{
|
{
|
||||||
@ -311,7 +311,16 @@ namespace osu.Game.Tournament
|
|||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// Serialise before opening stream for writing, so if there's a failure it will leave the file in the previous state.
|
// Serialise before opening stream for writing, so if there's a failure it will leave the file in the previous state.
|
||||||
string serialisedLadder = JsonConvert.SerializeObject(ladder,
|
string serialisedLadder = GetSerialisedLadder();
|
||||||
|
|
||||||
|
using (var stream = storage.CreateFileSafely(BRACKET_FILENAME))
|
||||||
|
using (var sw = new StreamWriter(stream))
|
||||||
|
sw.Write(serialisedLadder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetSerialisedLadder()
|
||||||
|
{
|
||||||
|
return JsonConvert.SerializeObject(ladder,
|
||||||
new JsonSerializerSettings
|
new JsonSerializerSettings
|
||||||
{
|
{
|
||||||
Formatting = Formatting.Indented,
|
Formatting = Formatting.Indented,
|
||||||
@ -319,10 +328,6 @@ namespace osu.Game.Tournament
|
|||||||
DefaultValueHandling = DefaultValueHandling.Ignore,
|
DefaultValueHandling = DefaultValueHandling.Ignore,
|
||||||
Converters = new JsonConverter[] { new JsonPointConverter() }
|
Converters = new JsonConverter[] { new JsonPointConverter() }
|
||||||
});
|
});
|
||||||
|
|
||||||
using (var stream = storage.CreateFileSafely(BRACKET_FILENAME))
|
|
||||||
using (var sw = new StreamWriter(stream))
|
|
||||||
sw.Write(serialisedLadder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override UserInputManager CreateUserInputManager() => new TournamentInputManager();
|
protected override UserInputManager CreateUserInputManager() => new TournamentInputManager();
|
||||||
|
@ -10,6 +10,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -186,7 +187,7 @@ namespace osu.Game.Tournament
|
|||||||
var lastScreen = currentScreen;
|
var lastScreen = currentScreen;
|
||||||
currentScreen = target;
|
currentScreen = target;
|
||||||
|
|
||||||
if (currentScreen is IProvideVideo)
|
if (currentScreen.ChildrenOfType<TourneyVideo>().FirstOrDefault()?.VideoAvailable == true)
|
||||||
{
|
{
|
||||||
video.FadeOut(200);
|
video.FadeOut(200);
|
||||||
|
|
||||||
|
@ -3,12 +3,15 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
|
||||||
namespace osu.Game.Tournament
|
namespace osu.Game.Tournament
|
||||||
{
|
{
|
||||||
public class TourneyButton : OsuButton
|
public class TourneyButton : OsuButton
|
||||||
{
|
{
|
||||||
|
public new Box Background => base.Background;
|
||||||
|
|
||||||
public TourneyButton()
|
public TourneyButton()
|
||||||
: base(null)
|
: base(null)
|
||||||
{
|
{
|
||||||
|
@ -80,9 +80,8 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
if (beatmapSet.OnlineID > 0)
|
if (beatmapSet.OnlineID > 0)
|
||||||
{
|
{
|
||||||
var existingSetWithSameOnlineID = realm.All<BeatmapSetInfo>().SingleOrDefault(b => b.OnlineID == beatmapSet.OnlineID);
|
// OnlineID should really be unique, but to avoid catastrophic failure let's iterate just to be sure.
|
||||||
|
foreach (var existingSetWithSameOnlineID in realm.All<BeatmapSetInfo>().Where(b => b.OnlineID == beatmapSet.OnlineID))
|
||||||
if (existingSetWithSameOnlineID != null)
|
|
||||||
{
|
{
|
||||||
existingSetWithSameOnlineID.DeletePending = true;
|
existingSetWithSameOnlineID.DeletePending = true;
|
||||||
existingSetWithSameOnlineID.OnlineID = -1;
|
existingSetWithSameOnlineID.OnlineID = -1;
|
||||||
@ -90,7 +89,7 @@ namespace osu.Game.Beatmaps
|
|||||||
foreach (var b in existingSetWithSameOnlineID.Beatmaps)
|
foreach (var b in existingSetWithSameOnlineID.Beatmaps)
|
||||||
b.OnlineID = -1;
|
b.OnlineID = -1;
|
||||||
|
|
||||||
LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineID ({beatmapSet.OnlineID}). It will be deleted.");
|
LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineID ({beatmapSet.OnlineID}). It will be disassociated and marked for deletion.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@ -110,6 +111,11 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public bool SamplesMatchPlaybackRate { get; set; } = true;
|
public bool SamplesMatchPlaybackRate { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time at which this beatmap was last played by the local user.
|
||||||
|
/// </summary>
|
||||||
|
public DateTimeOffset? LastPlayed { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The ratio of distance travelled per time unit.
|
/// The ratio of distance travelled per time unit.
|
||||||
/// Generally used to decouple the spacing between hit objects from the enforced "velocity" of the beatmap (see <see cref="DifficultyControlPoint.SliderVelocity"/>).
|
/// Generally used to decouple the spacing between hit objects from the enforced "velocity" of the beatmap (see <see cref="DifficultyControlPoint.SliderVelocity"/>).
|
||||||
@ -151,14 +157,23 @@ namespace osu.Game.Beatmaps
|
|||||||
public bool AudioEquals(BeatmapInfo? other) => other != null
|
public bool AudioEquals(BeatmapInfo? other) => other != null
|
||||||
&& BeatmapSet != null
|
&& BeatmapSet != null
|
||||||
&& other.BeatmapSet != null
|
&& other.BeatmapSet != null
|
||||||
&& BeatmapSet.Hash == other.BeatmapSet.Hash
|
&& compareFiles(this, other, m => m.AudioFile);
|
||||||
&& Metadata.AudioFile == other.Metadata.AudioFile;
|
|
||||||
|
|
||||||
public bool BackgroundEquals(BeatmapInfo? other) => other != null
|
public bool BackgroundEquals(BeatmapInfo? other) => other != null
|
||||||
&& BeatmapSet != null
|
&& BeatmapSet != null
|
||||||
&& other.BeatmapSet != null
|
&& other.BeatmapSet != null
|
||||||
&& BeatmapSet.Hash == other.BeatmapSet.Hash
|
&& compareFiles(this, other, m => m.BackgroundFile);
|
||||||
&& Metadata.BackgroundFile == other.Metadata.BackgroundFile;
|
|
||||||
|
private static bool compareFiles(BeatmapInfo x, BeatmapInfo y, Func<IBeatmapMetadataInfo, string> getFilename)
|
||||||
|
{
|
||||||
|
Debug.Assert(x.BeatmapSet != null);
|
||||||
|
Debug.Assert(y.BeatmapSet != null);
|
||||||
|
|
||||||
|
string? fileHashX = x.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(x.BeatmapSet.Metadata))?.File.Hash;
|
||||||
|
string? fileHashY = y.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(y.BeatmapSet.Metadata))?.File.Hash;
|
||||||
|
|
||||||
|
return fileHashX == fileHashY;
|
||||||
|
}
|
||||||
|
|
||||||
IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata;
|
IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata;
|
||||||
IBeatmapSetInfo? IBeatmapInfo.BeatmapSet => BeatmapSet;
|
IBeatmapSetInfo? IBeatmapInfo.BeatmapSet => BeatmapSet;
|
||||||
|
23
osu.Game/Collections/CollectionToggleMenuItem.cs
Normal file
23
osu.Game/Collections/CollectionToggleMenuItem.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
|
||||||
|
namespace osu.Game.Collections
|
||||||
|
{
|
||||||
|
public class CollectionToggleMenuItem : ToggleMenuItem
|
||||||
|
{
|
||||||
|
public CollectionToggleMenuItem(BeatmapCollection collection, IBeatmapInfo beatmap)
|
||||||
|
: base(collection.Name.Value, MenuItemType.Standard, state =>
|
||||||
|
{
|
||||||
|
if (state)
|
||||||
|
collection.BeatmapHashes.Add(beatmap.MD5Hash);
|
||||||
|
else
|
||||||
|
collection.BeatmapHashes.Remove(beatmap.MD5Hash);
|
||||||
|
})
|
||||||
|
{
|
||||||
|
State.Value = collection.BeatmapHashes.Contains(beatmap.MD5Hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -58,8 +58,9 @@ namespace osu.Game.Database
|
|||||||
/// 12 2021-11-24 Add Status to RealmBeatmapSet.
|
/// 12 2021-11-24 Add Status to RealmBeatmapSet.
|
||||||
/// 13 2022-01-13 Final migration of beatmaps and scores to realm (multiple new storage fields).
|
/// 13 2022-01-13 Final migration of beatmaps and scores to realm (multiple new storage fields).
|
||||||
/// 14 2022-03-01 Added BeatmapUserSettings to BeatmapInfo.
|
/// 14 2022-03-01 Added BeatmapUserSettings to BeatmapInfo.
|
||||||
|
/// 15 2022-07-13 Added LastPlayed to BeatmapInfo.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const int schema_version = 14;
|
private const int schema_version = 15;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
||||||
|
@ -338,11 +338,11 @@ namespace osu.Game.Database
|
|||||||
// import to store
|
// import to store
|
||||||
realm.Add(item);
|
realm.Add(item);
|
||||||
|
|
||||||
|
PostImport(item, realm);
|
||||||
|
|
||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
PostImport(item, realm);
|
|
||||||
|
|
||||||
LogForModel(item, @"Import successfully completed!");
|
LogForModel(item, @"Import successfully completed!");
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@ -479,7 +479,7 @@ namespace osu.Game.Database
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Perform any final actions after the import has been committed to the database.
|
/// Perform any final actions before the import has been committed to the database.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">The model prepared for import.</param>
|
/// <param name="model">The model prepared for import.</param>
|
||||||
/// <param name="realm">The current realm context.</param>
|
/// <param name="realm">The current realm context.</param>
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.IO
|
|||||||
|
|
||||||
public LineBufferedReader(Stream stream, bool leaveOpen = false)
|
public LineBufferedReader(Stream stream, bool leaveOpen = false)
|
||||||
{
|
{
|
||||||
streamReader = new StreamReader(stream, Encoding.UTF8, true, -1, leaveOpen);
|
streamReader = new StreamReader(stream, Encoding.UTF8, true, 1024, leaveOpen);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -94,6 +94,8 @@ namespace osu.Game.IO
|
|||||||
error = OsuStorageError.None;
|
error = OsuStorageError.None;
|
||||||
Storage lastStorage = UnderlyingStorage;
|
Storage lastStorage = UnderlyingStorage;
|
||||||
|
|
||||||
|
Logger.Log($"Attempting to use custom storage location {CustomStoragePath}");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Storage userStorage = host.GetStorage(CustomStoragePath);
|
Storage userStorage = host.GetStorage(CustomStoragePath);
|
||||||
@ -102,6 +104,7 @@ namespace osu.Game.IO
|
|||||||
error = OsuStorageError.AccessibleButEmpty;
|
error = OsuStorageError.AccessibleButEmpty;
|
||||||
|
|
||||||
ChangeTargetStorage(userStorage);
|
ChangeTargetStorage(userStorage);
|
||||||
|
Logger.Log($"Storage successfully changed to {CustomStoragePath}.");
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@ -109,6 +112,9 @@ namespace osu.Game.IO
|
|||||||
ChangeTargetStorage(lastStorage);
|
ChangeTargetStorage(lastStorage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (error != OsuStorageError.None)
|
||||||
|
Logger.Log($"Custom storage location could not be used ({error}).");
|
||||||
|
|
||||||
return error == OsuStorageError.None;
|
return error == OsuStorageError.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
public string WebsiteRootUrl { get; }
|
public string WebsiteRootUrl { get; }
|
||||||
|
|
||||||
public int APIVersion => 20220217; // We may want to pull this from the game version eventually.
|
public int APIVersion => 20220705; // We may want to pull this from the game version eventually.
|
||||||
|
|
||||||
public Exception LastLoginError { get; private set; }
|
public Exception LastLoginError { get; private set; }
|
||||||
|
|
||||||
|
@ -16,11 +16,11 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
public int? Position;
|
public int? Position;
|
||||||
|
|
||||||
[JsonProperty(@"score")]
|
[JsonProperty(@"score")]
|
||||||
public APIScore Score;
|
public SoloScoreInfo Score;
|
||||||
|
|
||||||
public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null)
|
public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null)
|
||||||
{
|
{
|
||||||
var score = Score.CreateScoreInfo(rulesets, beatmap);
|
var score = Score.ToScoreInfo(rulesets, beatmap);
|
||||||
score.Position = Position;
|
score.Position = Position;
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
public class APIScoresCollection
|
public class APIScoresCollection
|
||||||
{
|
{
|
||||||
[JsonProperty(@"scores")]
|
[JsonProperty(@"scores")]
|
||||||
public List<APIScore> Scores;
|
public List<SoloScoreInfo> Scores;
|
||||||
|
|
||||||
[JsonProperty(@"userScore")]
|
[JsonProperty(@"userScore")]
|
||||||
public APIScoreWithPosition UserScore;
|
public APIScoreWithPosition UserScore;
|
||||||
|
143
osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs
Normal file
143
osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Converters;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.API.Requests.Responses
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
public class SoloScoreInfo : IHasOnlineID<long>
|
||||||
|
{
|
||||||
|
[JsonProperty("replay")]
|
||||||
|
public bool HasReplay { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("beatmap_id")]
|
||||||
|
public int BeatmapID { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("ruleset_id")]
|
||||||
|
public int RulesetID { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("build_id")]
|
||||||
|
public int? BuildID { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("passed")]
|
||||||
|
public bool Passed { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("total_score")]
|
||||||
|
public int TotalScore { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("accuracy")]
|
||||||
|
public double Accuracy { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("user_id")]
|
||||||
|
public int UserID { get; set; }
|
||||||
|
|
||||||
|
// TODO: probably want to update this column to match user stats (short)?
|
||||||
|
[JsonProperty("max_combo")]
|
||||||
|
public int MaxCombo { get; set; }
|
||||||
|
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
|
[JsonProperty("rank")]
|
||||||
|
public ScoreRank Rank { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("started_at")]
|
||||||
|
public DateTimeOffset? StartedAt { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("ended_at")]
|
||||||
|
public DateTimeOffset? EndedAt { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("mods")]
|
||||||
|
public APIMod[] Mods { get; set; } = Array.Empty<APIMod>();
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
[JsonProperty("created_at")]
|
||||||
|
public DateTimeOffset CreatedAt { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
[JsonProperty("updated_at")]
|
||||||
|
public DateTimeOffset UpdatedAt { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
[JsonProperty("deleted_at")]
|
||||||
|
public DateTimeOffset? DeletedAt { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("statistics")]
|
||||||
|
public Dictionary<HitResult, int> Statistics { get; set; } = new Dictionary<HitResult, int>();
|
||||||
|
|
||||||
|
#region osu-web API additions (not stored to database).
|
||||||
|
|
||||||
|
[JsonProperty("id")]
|
||||||
|
public long? ID { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("user")]
|
||||||
|
public APIUser? User { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("pp")]
|
||||||
|
public double? PP { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public override string ToString() => $"score_id: {ID} user_id: {UserID}";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a <see cref="ScoreInfo"/> from an API score instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rulesets">A ruleset store, used to populate a ruleset instance in the returned score.</param>
|
||||||
|
/// <param name="beatmap">An optional beatmap, copied into the returned score (for cases where the API does not populate the beatmap).</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public ScoreInfo ToScoreInfo(RulesetStore rulesets, BeatmapInfo? beatmap = null)
|
||||||
|
{
|
||||||
|
var ruleset = rulesets.GetRuleset(RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {RulesetID} not found locally");
|
||||||
|
|
||||||
|
var rulesetInstance = ruleset.CreateInstance();
|
||||||
|
|
||||||
|
var mods = Mods.Select(apiMod => rulesetInstance.CreateModFromAcronym(apiMod.Acronym)).Where(m => m != null).ToArray();
|
||||||
|
|
||||||
|
// all API scores provided by this class are considered to be legacy.
|
||||||
|
mods = mods.Append(rulesetInstance.CreateMod<ModClassic>()).ToArray();
|
||||||
|
|
||||||
|
var scoreInfo = ToScoreInfo(mods);
|
||||||
|
|
||||||
|
scoreInfo.Ruleset = ruleset;
|
||||||
|
if (beatmap != null) scoreInfo.BeatmapInfo = beatmap;
|
||||||
|
|
||||||
|
return scoreInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a <see cref="ScoreInfo"/> from an API score instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mods">The mod instances, resolved from a ruleset.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public ScoreInfo ToScoreInfo(Mod[] mods) => new ScoreInfo
|
||||||
|
{
|
||||||
|
OnlineID = OnlineID,
|
||||||
|
User = User ?? new APIUser { Id = UserID },
|
||||||
|
BeatmapInfo = new BeatmapInfo { OnlineID = BeatmapID },
|
||||||
|
Ruleset = new RulesetInfo { OnlineID = RulesetID },
|
||||||
|
Passed = Passed,
|
||||||
|
TotalScore = TotalScore,
|
||||||
|
Accuracy = Accuracy,
|
||||||
|
MaxCombo = MaxCombo,
|
||||||
|
Rank = Rank,
|
||||||
|
Statistics = Statistics,
|
||||||
|
Date = EndedAt ?? DateTimeOffset.Now,
|
||||||
|
Hash = "online", // TODO: temporary?
|
||||||
|
HasReplay = HasReplay,
|
||||||
|
Mods = mods,
|
||||||
|
PP = PP,
|
||||||
|
};
|
||||||
|
|
||||||
|
public long OnlineID => ID ?? -1;
|
||||||
|
}
|
||||||
|
}
|
@ -272,7 +272,7 @@ namespace osu.Game
|
|||||||
dependencies.Cache(difficultyCache = new BeatmapDifficultyCache());
|
dependencies.Cache(difficultyCache = new BeatmapDifficultyCache());
|
||||||
|
|
||||||
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
|
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
|
||||||
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, difficultyCache, LocalConfig));
|
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, API, difficultyCache, LocalConfig));
|
||||||
|
|
||||||
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true));
|
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true));
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
MD5Hash = apiBeatmap.MD5Hash
|
MD5Hash = apiBeatmap.MD5Hash
|
||||||
};
|
};
|
||||||
|
|
||||||
scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets, beatmapInfo)).ToArray(), loadCancellationSource.Token)
|
scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.ToScoreInfo(rulesets, beatmapInfo)).ToArray(), loadCancellationSource.Token)
|
||||||
.ContinueWith(task => Schedule(() =>
|
.ContinueWith(task => Schedule(() =>
|
||||||
{
|
{
|
||||||
if (loadCancellationSource.IsCancellationRequested)
|
if (loadCancellationSource.IsCancellationRequested)
|
||||||
@ -101,7 +101,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
scoreTable.Show();
|
scoreTable.Show();
|
||||||
|
|
||||||
var userScore = value.UserScore;
|
var userScore = value.UserScore;
|
||||||
var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets, beatmapInfo);
|
var userScoreInfo = userScore?.Score.ToScoreInfo(rulesets, beatmapInfo);
|
||||||
|
|
||||||
topScoresContainer.Add(new DrawableTopScore(topScore));
|
topScoresContainer.Add(new DrawableTopScore(topScore));
|
||||||
|
|
||||||
|
@ -49,43 +49,54 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
public void Push(PopupDialog dialog)
|
public void Push(PopupDialog dialog)
|
||||||
{
|
{
|
||||||
if (dialog == CurrentDialog || dialog.State.Value != Visibility.Visible) return;
|
if (dialog == CurrentDialog || dialog.State.Value == Visibility.Hidden) return;
|
||||||
|
|
||||||
var lastDialog = CurrentDialog;
|
|
||||||
|
|
||||||
// Immediately update the externally accessible property as this may be used for checks even before
|
// Immediately update the externally accessible property as this may be used for checks even before
|
||||||
// a DialogOverlay instance has finished loading.
|
// a DialogOverlay instance has finished loading.
|
||||||
|
var lastDialog = CurrentDialog;
|
||||||
CurrentDialog = dialog;
|
CurrentDialog = dialog;
|
||||||
|
|
||||||
Scheduler.Add(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
// if any existing dialog is being displayed, dismiss it before showing a new one.
|
// if any existing dialog is being displayed, dismiss it before showing a new one.
|
||||||
lastDialog?.Hide();
|
lastDialog?.Hide();
|
||||||
dialog.State.ValueChanged += state => onDialogOnStateChanged(dialog, state.NewValue);
|
|
||||||
dialogContainer.Add(dialog);
|
|
||||||
|
|
||||||
|
// if the new dialog is hidden before added to the dialogContainer, bypass any further operations.
|
||||||
|
if (dialog.State.Value == Visibility.Hidden)
|
||||||
|
{
|
||||||
|
dismiss();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogContainer.Add(dialog);
|
||||||
Show();
|
Show();
|
||||||
}, false);
|
|
||||||
|
dialog.State.BindValueChanged(state =>
|
||||||
|
{
|
||||||
|
if (state.NewValue != Visibility.Hidden) return;
|
||||||
|
|
||||||
|
// Trigger the demise of the dialog as soon as it hides.
|
||||||
|
dialog.Delay(PopupDialog.EXIT_DURATION).Expire();
|
||||||
|
|
||||||
|
dismiss();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
void dismiss()
|
||||||
|
{
|
||||||
|
if (dialog != CurrentDialog) return;
|
||||||
|
|
||||||
|
// Handle the case where the dialog is the currently displayed dialog.
|
||||||
|
// In this scenario, the overlay itself should also be hidden.
|
||||||
|
Hide();
|
||||||
|
CurrentDialog = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool IsPresent => Scheduler.HasPendingTasks || dialogContainer.Children.Count > 0;
|
public override bool IsPresent => Scheduler.HasPendingTasks || dialogContainer.Children.Count > 0;
|
||||||
|
|
||||||
protected override bool BlockNonPositionalInput => true;
|
protected override bool BlockNonPositionalInput => true;
|
||||||
|
|
||||||
private void onDialogOnStateChanged(VisibilityContainer dialog, Visibility v)
|
|
||||||
{
|
|
||||||
if (v != Visibility.Hidden) return;
|
|
||||||
|
|
||||||
// handle the dialog being dismissed.
|
|
||||||
dialog.Delay(PopupDialog.EXIT_DURATION).Expire();
|
|
||||||
|
|
||||||
if (dialog == CurrentDialog)
|
|
||||||
{
|
|
||||||
Hide();
|
|
||||||
CurrentDialog = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void PopIn()
|
protected override void PopIn()
|
||||||
{
|
{
|
||||||
base.PopIn();
|
base.PopIn();
|
||||||
@ -97,7 +108,8 @@ namespace osu.Game.Overlays
|
|||||||
base.PopOut();
|
base.PopOut();
|
||||||
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 100, Easing.InCubic);
|
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 100, Easing.InCubic);
|
||||||
|
|
||||||
if (CurrentDialog?.State.Value == Visibility.Visible)
|
// PopOut gets called initially, but we only want to hide dialog when we have been loaded and are present.
|
||||||
|
if (IsLoaded && CurrentDialog?.State.Value == Visibility.Visible)
|
||||||
CurrentDialog.Hide();
|
CurrentDialog.Hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,14 +1,24 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Configuration;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Framework.Threading;
|
||||||
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.FirstRunSetup
|
namespace osu.Game.Overlays.FirstRunSetup
|
||||||
{
|
{
|
||||||
@ -19,6 +29,19 @@ namespace osu.Game.Overlays.FirstRunSetup
|
|||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Content.Children = new Drawable[]
|
Content.Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
RowDimensions = new[]
|
||||||
|
{
|
||||||
|
// Avoid height changes when changing language.
|
||||||
|
new Dimension(GridSizeMode.AutoSize, minSize: 100),
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
{
|
{
|
||||||
new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE))
|
new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE))
|
||||||
{
|
{
|
||||||
@ -26,7 +49,156 @@ namespace osu.Game.Overlays.FirstRunSetup
|
|||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y
|
AutoSizeAxes = Axes.Y
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new LanguageSelectionFlow
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class LanguageSelectionFlow : FillFlowContainer
|
||||||
|
{
|
||||||
|
private Bindable<string> frameworkLocale = null!;
|
||||||
|
|
||||||
|
private ScheduledDelegate? updateSelectedDelegate;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(FrameworkConfigManager frameworkConfig)
|
||||||
|
{
|
||||||
|
Direction = FillDirection.Full;
|
||||||
|
Spacing = new Vector2(5);
|
||||||
|
|
||||||
|
ChildrenEnumerable = Enum.GetValues(typeof(Language))
|
||||||
|
.Cast<Language>()
|
||||||
|
.Select(l => new LanguageButton(l)
|
||||||
|
{
|
||||||
|
Action = () => frameworkLocale.Value = l.ToCultureCode()
|
||||||
|
});
|
||||||
|
|
||||||
|
frameworkLocale = frameworkConfig.GetBindable<string>(FrameworkSetting.Locale);
|
||||||
|
frameworkLocale.BindValueChanged(locale =>
|
||||||
|
{
|
||||||
|
if (!LanguageExtensions.TryParseCultureCode(locale.NewValue, out var language))
|
||||||
|
language = Language.en;
|
||||||
|
|
||||||
|
// Changing language may cause a short period of blocking the UI thread while the new glyphs are loaded.
|
||||||
|
// Scheduling ensures the button animation plays smoothly after any blocking operation completes.
|
||||||
|
// Note that a delay is required (the alternative would be a double-schedule; delay feels better).
|
||||||
|
updateSelectedDelegate?.Cancel();
|
||||||
|
updateSelectedDelegate = Scheduler.AddDelayed(() => updateSelectedStates(language), 50);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSelectedStates(Language language)
|
||||||
|
{
|
||||||
|
foreach (var c in Children.OfType<LanguageButton>())
|
||||||
|
c.Selected = c.Language == language;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LanguageButton : OsuClickableContainer
|
||||||
|
{
|
||||||
|
public readonly Language Language;
|
||||||
|
|
||||||
|
private Box backgroundBox = null!;
|
||||||
|
|
||||||
|
private OsuSpriteText text = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||||
|
|
||||||
|
private bool selected;
|
||||||
|
|
||||||
|
public bool Selected
|
||||||
|
{
|
||||||
|
get => selected;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (selected == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
selected = value;
|
||||||
|
|
||||||
|
if (IsLoaded)
|
||||||
|
updateState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LanguageButton(Language language)
|
||||||
|
{
|
||||||
|
Language = language;
|
||||||
|
|
||||||
|
Size = new Vector2(160, 50);
|
||||||
|
Masking = true;
|
||||||
|
CornerRadius = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
backgroundBox = new Box
|
||||||
|
{
|
||||||
|
Alpha = 0,
|
||||||
|
Colour = colourProvider.Background5,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
text = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Colour = colourProvider.Light1,
|
||||||
|
Text = Language.GetDescription(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
if (!selected)
|
||||||
|
updateState();
|
||||||
|
return base.OnHover(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
if (!selected)
|
||||||
|
updateState();
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState()
|
||||||
|
{
|
||||||
|
if (selected)
|
||||||
|
{
|
||||||
|
const double selected_duration = 1000;
|
||||||
|
|
||||||
|
backgroundBox.FadeTo(1, selected_duration, Easing.OutQuint);
|
||||||
|
backgroundBox.FadeColour(colourProvider.Background2, selected_duration, Easing.OutQuint);
|
||||||
|
text.FadeColour(colourProvider.Content1, selected_duration, Easing.OutQuint);
|
||||||
|
text.ScaleTo(1.2f, selected_duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const double duration = 500;
|
||||||
|
|
||||||
|
backgroundBox.FadeTo(IsHovered ? 1 : 0, duration, Easing.OutQuint);
|
||||||
|
backgroundBox.FadeColour(colourProvider.Background5, duration, Easing.OutQuint);
|
||||||
|
text.FadeColour(colourProvider.Light1, duration, Easing.OutQuint);
|
||||||
|
text.ScaleTo(1, duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ using osu.Game.Graphics.Sprites;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.UI
|
namespace osu.Game.Rulesets.UI
|
||||||
@ -53,7 +54,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
private OsuColour colours { get; set; }
|
private OsuColour colours { get; set; }
|
||||||
|
|
||||||
private Color4 backgroundColour;
|
private Color4 backgroundColour;
|
||||||
private Color4 highlightedColour;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Construct a new instance.
|
/// Construct a new instance.
|
||||||
@ -123,47 +123,13 @@ namespace osu.Game.Rulesets.UI
|
|||||||
modAcronym.FadeOut();
|
modAcronym.FadeOut();
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (value.Type)
|
backgroundColour = colours.ForModType(value.Type);
|
||||||
{
|
|
||||||
default:
|
|
||||||
case ModType.DifficultyIncrease:
|
|
||||||
backgroundColour = colours.Yellow;
|
|
||||||
highlightedColour = colours.YellowLight;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ModType.DifficultyReduction:
|
|
||||||
backgroundColour = colours.Green;
|
|
||||||
highlightedColour = colours.GreenLight;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ModType.Automation:
|
|
||||||
backgroundColour = colours.Blue;
|
|
||||||
highlightedColour = colours.BlueLight;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ModType.Conversion:
|
|
||||||
backgroundColour = colours.Purple;
|
|
||||||
highlightedColour = colours.PurpleLight;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ModType.Fun:
|
|
||||||
backgroundColour = colours.Pink;
|
|
||||||
highlightedColour = colours.PinkLight;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ModType.System:
|
|
||||||
backgroundColour = colours.Gray6;
|
|
||||||
highlightedColour = colours.Gray7;
|
|
||||||
modIcon.Colour = colours.Yellow;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateColour();
|
updateColour();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateColour()
|
private void updateColour()
|
||||||
{
|
{
|
||||||
background.Colour = Selected.Value ? highlightedColour : backgroundColour;
|
background.Colour = Selected.Value ? backgroundColour.Lighten(0.2f) : backgroundColour;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,9 @@ using osu.Game.Database;
|
|||||||
using osu.Game.IO.Archives;
|
using osu.Game.IO.Archives;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Scoring.Legacy;
|
using osu.Game.Scoring.Legacy;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.API.Requests;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using Realms;
|
using Realms;
|
||||||
|
|
||||||
namespace osu.Game.Scoring
|
namespace osu.Game.Scoring
|
||||||
@ -26,11 +29,14 @@ namespace osu.Game.Scoring
|
|||||||
private readonly RulesetStore rulesets;
|
private readonly RulesetStore rulesets;
|
||||||
private readonly Func<BeatmapManager> beatmaps;
|
private readonly Func<BeatmapManager> beatmaps;
|
||||||
|
|
||||||
public ScoreImporter(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, RealmAccess realm)
|
private readonly IAPIProvider api;
|
||||||
|
|
||||||
|
public ScoreImporter(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, RealmAccess realm, IAPIProvider api)
|
||||||
: base(storage, realm)
|
: base(storage, realm)
|
||||||
{
|
{
|
||||||
this.rulesets = rulesets;
|
this.rulesets = rulesets;
|
||||||
this.beatmaps = beatmaps;
|
this.beatmaps = beatmaps;
|
||||||
|
this.api = api;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ScoreInfo? CreateModel(ArchiveReader archive)
|
protected override ScoreInfo? CreateModel(ArchiveReader archive)
|
||||||
@ -68,5 +74,17 @@ namespace osu.Game.Scoring
|
|||||||
if (string.IsNullOrEmpty(model.StatisticsJson))
|
if (string.IsNullOrEmpty(model.StatisticsJson))
|
||||||
model.StatisticsJson = JsonConvert.SerializeObject(model.Statistics);
|
model.StatisticsJson = JsonConvert.SerializeObject(model.Statistics);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void PostImport(ScoreInfo model, Realm realm)
|
||||||
|
{
|
||||||
|
base.PostImport(model, realm);
|
||||||
|
|
||||||
|
var userRequest = new GetUserRequest(model.RealmUser.Username);
|
||||||
|
|
||||||
|
api.Perform(userRequest);
|
||||||
|
|
||||||
|
if (userRequest.Response is APIUser user)
|
||||||
|
model.RealmUser.OnlineID = user.Id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ using osu.Game.IO.Archives;
|
|||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
|
||||||
namespace osu.Game.Scoring
|
namespace osu.Game.Scoring
|
||||||
{
|
{
|
||||||
@ -31,7 +32,7 @@ namespace osu.Game.Scoring
|
|||||||
private readonly OsuConfigManager configManager;
|
private readonly OsuConfigManager configManager;
|
||||||
private readonly ScoreImporter scoreImporter;
|
private readonly ScoreImporter scoreImporter;
|
||||||
|
|
||||||
public ScoreManager(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, RealmAccess realm, Scheduler scheduler,
|
public ScoreManager(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, RealmAccess realm, Scheduler scheduler, IAPIProvider api,
|
||||||
BeatmapDifficultyCache difficultyCache = null, OsuConfigManager configManager = null)
|
BeatmapDifficultyCache difficultyCache = null, OsuConfigManager configManager = null)
|
||||||
: base(storage, realm)
|
: base(storage, realm)
|
||||||
{
|
{
|
||||||
@ -39,7 +40,7 @@ namespace osu.Game.Scoring
|
|||||||
this.difficultyCache = difficultyCache;
|
this.difficultyCache = difficultyCache;
|
||||||
this.configManager = configManager;
|
this.configManager = configManager;
|
||||||
|
|
||||||
scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm)
|
scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm, api)
|
||||||
{
|
{
|
||||||
PostNotification = obj => PostNotification?.Invoke(obj)
|
PostNotification = obj => PostNotification?.Invoke(obj)
|
||||||
};
|
};
|
||||||
|
@ -186,7 +186,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
loadableBeatmap = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value);
|
loadableBeatmap = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value);
|
||||||
|
|
||||||
// required so we can get the track length in EditorClock.
|
// required so we can get the track length in EditorClock.
|
||||||
// this is safe as nothing has yet got a reference to this new beatmap.
|
// this is ONLY safe because the track being provided is a `TrackVirtual` which we don't really care about disposing.
|
||||||
loadableBeatmap.LoadTrack();
|
loadableBeatmap.LoadTrack();
|
||||||
|
|
||||||
// this is a bit haphazard, but guards against setting the lease Beatmap bindable if
|
// this is a bit haphazard, but guards against setting the lease Beatmap bindable if
|
||||||
@ -921,7 +921,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
private void cancelExit()
|
private void cancelExit()
|
||||||
{
|
{
|
||||||
samplePlaybackDisabled.Value = false;
|
updateSampleDisabledState();
|
||||||
loader?.CancelPendingDifficultySwitch();
|
loader?.CancelPendingDifficultySwitch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,13 @@ namespace osu.Game.Screens.Edit.GameplayTest
|
|||||||
ScoreProcessor.HasCompleted.BindValueChanged(completed =>
|
ScoreProcessor.HasCompleted.BindValueChanged(completed =>
|
||||||
{
|
{
|
||||||
if (completed.NewValue)
|
if (completed.NewValue)
|
||||||
Scheduler.AddDelayed(this.Exit, RESULTS_DISPLAY_DELAY);
|
{
|
||||||
|
Scheduler.AddDelayed(() =>
|
||||||
|
{
|
||||||
|
if (this.IsCurrentScreen())
|
||||||
|
this.Exit();
|
||||||
|
}, RESULTS_DISPLAY_DELAY);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,9 +72,17 @@ namespace osu.Game.Screens.Menu
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Clock = decoupledClock,
|
Clock = decoupledClock,
|
||||||
LoadMenu = LoadMenu
|
LoadMenu = LoadMenu
|
||||||
}, t =>
|
}, _ =>
|
||||||
{
|
{
|
||||||
AddInternal(t);
|
AddInternal(intro);
|
||||||
|
|
||||||
|
// There is a chance that the intro timed out before being displayed, and this scheduled callback could
|
||||||
|
// happen during the outro rather than intro.
|
||||||
|
// In such a scenario, we don't want to play the intro sample, nor attempt to start the intro track
|
||||||
|
// (that may have already been since disposed by MusicController).
|
||||||
|
if (DidLoadMenu)
|
||||||
|
return;
|
||||||
|
|
||||||
if (!UsingThemedIntro)
|
if (!UsingThemedIntro)
|
||||||
welcome?.Play();
|
welcome?.Play();
|
||||||
|
|
||||||
|
@ -9,17 +9,20 @@ using System.Linq;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.Drawables;
|
using osu.Game.Beatmaps.Drawables;
|
||||||
|
using osu.Game.Collections;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
@ -27,6 +30,7 @@ using osu.Game.Graphics.UserInterface;
|
|||||||
using osu.Game.Online;
|
using osu.Game.Online;
|
||||||
using osu.Game.Online.Chat;
|
using osu.Game.Online.Chat;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.BeatmapSet;
|
using osu.Game.Overlays.BeatmapSet;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
@ -38,7 +42,7 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay
|
namespace osu.Game.Screens.OnlinePlay
|
||||||
{
|
{
|
||||||
public class DrawableRoomPlaylistItem : OsuRearrangeableListItem<PlaylistItem>
|
public class DrawableRoomPlaylistItem : OsuRearrangeableListItem<PlaylistItem>, IHasContextMenu
|
||||||
{
|
{
|
||||||
public const float HEIGHT = 50;
|
public const float HEIGHT = 50;
|
||||||
|
|
||||||
@ -93,6 +97,9 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private RulesetStore rulesets { get; set; }
|
private RulesetStore rulesets { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private BeatmapManager beatmaps { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; }
|
private OsuColour colours { get; set; }
|
||||||
|
|
||||||
@ -102,6 +109,15 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private BeatmapLookupCache beatmapLookupCache { get; set; }
|
private BeatmapLookupCache beatmapLookupCache { get; set; }
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private BeatmapSetOverlay beatmapOverlay { get; set; }
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private CollectionManager collectionManager { get; set; }
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private ManageCollectionsDialog manageCollectionsDialog { get; set; }
|
||||||
|
|
||||||
protected override bool ShouldBeConsideredForInput(Drawable child) => AllowReordering || AllowDeletion || !AllowSelection || SelectedItem.Value == Model;
|
protected override bool ShouldBeConsideredForInput(Drawable child) => AllowReordering || AllowDeletion || !AllowSelection || SelectedItem.Value == Model;
|
||||||
|
|
||||||
public DrawableRoomPlaylistItem(PlaylistItem item)
|
public DrawableRoomPlaylistItem(PlaylistItem item)
|
||||||
@ -433,7 +449,7 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -470,6 +486,31 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MenuItem[] ContextMenuItems
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
List<MenuItem> items = new List<MenuItem>();
|
||||||
|
|
||||||
|
if (beatmapOverlay != null)
|
||||||
|
items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(Item.Beatmap.OnlineID)));
|
||||||
|
|
||||||
|
if (collectionManager != null && beatmap != null)
|
||||||
|
{
|
||||||
|
if (beatmaps.QueryBeatmap(b => b.OnlineID == beatmap.OnlineID) is BeatmapInfo local && !local.BeatmapSet.AsNonNull().DeletePending)
|
||||||
|
{
|
||||||
|
var collectionItems = collectionManager.Collections.Select(c => new CollectionToggleMenuItem(c, beatmap)).Cast<OsuMenuItem>().ToList();
|
||||||
|
if (manageCollectionsDialog != null)
|
||||||
|
collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show));
|
||||||
|
|
||||||
|
items.Add(new OsuMenuItem("Collections") { Items = collectionItems });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class PlaylistEditButton : GrayButton
|
public class PlaylistEditButton : GrayButton
|
||||||
{
|
{
|
||||||
public PlaylistEditButton()
|
public PlaylistEditButton()
|
||||||
|
@ -314,6 +314,9 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
|||||||
|
|
||||||
public override void OnSuspending(ScreenTransitionEvent e)
|
public override void OnSuspending(ScreenTransitionEvent e)
|
||||||
{
|
{
|
||||||
|
// Should be a noop in most cases, but let's ensure beyond doubt that the beatmap is in a correct state.
|
||||||
|
updateWorkingBeatmap();
|
||||||
|
|
||||||
onLeaving();
|
onLeaving();
|
||||||
base.OnSuspending(e);
|
base.OnSuspending(e);
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user