1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 07:33:20 +08:00

Merge remote-tracking branch 'upstream/master' into test-scene-create-ruleset

# Conflicts:
#	osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
#	osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
#	osu.Game/Tests/Visual/ModTestScene.cs
#	osu.Game/Tests/Visual/OsuTestScene.cs
This commit is contained in:
Salman Ahmed 2020-06-03 22:00:02 +03:00
commit 84b40ea2c6
No known key found for this signature in database
GPG Key ID: ED81FD33FD9B58BC
578 changed files with 10062 additions and 4242 deletions

View File

@ -4,3 +4,5 @@ M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals(
M:System.Nullable`1.Equals(System.Object)~System.Boolean;Use == instead. M:System.Nullable`1.Equals(System.Object)~System.Boolean;Use == instead.
T:System.IComparable;Don't use non-generic IComparable. Use generic version instead. T:System.IComparable;Don't use non-generic IComparable. Use generic version instead.
M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText. M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText.
T:Microsoft.EntityFrameworkCore.Internal.EnumerableExtensions;Don't use internal extension methods.
T:Microsoft.EntityFrameworkCore.Internal.TypeExtensions;Don't use internal extension methods.

View File

@ -16,9 +16,9 @@
<EmbeddedResource Include="Resources\**\*.*" /> <EmbeddedResource Include="Resources\**\*.*" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Code Analysis"> <ItemGroup Label="Code Analysis">
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="2.9.8" PrivateAssets="All" /> <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.0.0" PrivateAssets="All" />
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" /> <AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" /> <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.0.0" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Code Analysis"> <PropertyGroup Label="Code Analysis">
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset</CodeAnalysisRuleSet>

View File

@ -15,6 +15,8 @@ Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the
This project is under heavy development, but is in a stable state. Users are encouraged to try it out and keep it installed alongside the stable *osu!* client. It will continue to evolve to the point of eventually replacing the existing stable client as an update. This project is under heavy development, but is in a stable state. Users are encouraged to try it out and keep it installed alongside the stable *osu!* client. It will continue to evolve to the point of eventually replacing the existing stable client as an update.
**IMPORTANT:** Gameplay mechanics (and other features which you may have come to know and love) are in a constant state of flux. Game balance and final quality-of-life passses come at the end of development, preceeded by experimentation and changes which may potentially **reduce playability or usability**. This is done in order to allow us to move forward as developers and designers more efficiently. If this offends you, please consider sticking to the stable releases of osu! (found on the website). We are not yet open to heated discussion over game mechanics and will not be using github as a forum for such discussions just yet.
We are accepting bug reports (please report with as much detail as possible and follow the existing issue templates). Feature requests are also welcome, but understand that our focus is on completing the game to feature parity before adding new features. A few resources are available as starting points to getting involved and understanding the project: We are accepting bug reports (please report with as much detail as possible and follow the existing issue templates). Feature requests are also welcome, but understand that our focus is on completing the game to feature parity before adding new features. A few resources are available as starting points to getting involved and understanding the project:
- Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer). - Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer).

View File

@ -51,7 +51,7 @@
<Reference Include="Java.Interop" /> <Reference Include="Java.Interop" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.412.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.602.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.421.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2020.601.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -18,7 +18,8 @@ namespace osu.Android
try try
{ {
string versionName = packageInfo.VersionCode.ToString(); // todo: needs checking before play store redeploy.
string versionName = packageInfo.VersionName;
// undo play store version garbling // undo play store version garbling
return new Version(int.Parse(versionName.Substring(0, 4)), int.Parse(versionName.Substring(4, 4)), int.Parse(versionName.Substring(8, 1))); return new Version(int.Parse(versionName.Substring(0, 4)), int.Parse(versionName.Substring(4, 4)), int.Parse(versionName.Substring(8, 1)));
} }

View File

@ -6,15 +6,14 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Win32;
using osu.Desktop.Overlays; using osu.Desktop.Overlays;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game; using osu.Game;
using osuTK.Input; using osuTK.Input;
using Microsoft.Win32;
using osu.Desktop.Updater; using osu.Desktop.Updater;
using osu.Framework; using osu.Framework;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Platform.Windows;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
using osu.Game.Updater; using osu.Game.Updater;
@ -37,7 +36,11 @@ namespace osu.Desktop
try try
{ {
if (Host is DesktopGameHost desktopHost) if (Host is DesktopGameHost desktopHost)
return new StableStorage(desktopHost); {
string stablePath = getStableInstallPath();
if (!string.IsNullOrEmpty(stablePath))
return new DesktopStorage(stablePath, desktopHost);
}
} }
catch (Exception) catch (Exception)
{ {
@ -47,6 +50,35 @@ namespace osu.Desktop
return null; return null;
} }
private string getStableInstallPath()
{
static bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs"));
string stableInstallPath;
try
{
using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", "");
if (checkExists(stableInstallPath))
return stableInstallPath;
}
catch
{
}
stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!");
if (checkExists(stableInstallPath))
return stableInstallPath;
stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu");
if (checkExists(stableInstallPath))
return stableInstallPath;
return null;
}
protected override UpdateManager CreateUpdateManager() protected override UpdateManager CreateUpdateManager()
{ {
switch (RuntimeInfo.OS) switch (RuntimeInfo.OS)
@ -111,45 +143,5 @@ namespace osu.Desktop
Task.Factory.StartNew(() => Import(filePaths), TaskCreationOptions.LongRunning); Task.Factory.StartNew(() => Import(filePaths), TaskCreationOptions.LongRunning);
} }
/// <summary>
/// A method of accessing an osu-stable install in a controlled fashion.
/// </summary>
private class StableStorage : WindowsStorage
{
protected override string LocateBasePath()
{
static bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs"));
string stableInstallPath;
try
{
using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", "");
if (checkExists(stableInstallPath))
return stableInstallPath;
}
catch
{
}
stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!");
if (checkExists(stableInstallPath))
return stableInstallPath;
stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu");
if (checkExists(stableInstallPath))
return stableInstallPath;
return null;
}
public StableStorage(DesktopGameHost host)
: base(string.Empty, host)
{
}
}
} }
} }

View File

@ -33,13 +33,11 @@ namespace osu.Desktop
if (args.Length > 0 && args[0].Contains('.')) // easy way to check for a file import in args if (args.Length > 0 && args[0].Contains('.')) // easy way to check for a file import in args
{ {
var importer = new ArchiveImportIPCChannel(host); var importer = new ArchiveImportIPCChannel(host);
// Restore the cwd so relative paths given at the command line work correctly
Directory.SetCurrentDirectory(cwd);
foreach (var file in args) foreach (var file in args)
{ {
Console.WriteLine(@"Importing {0}", file); Console.WriteLine(@"Importing {0}", file);
if (!importer.ImportAsync(Path.GetFullPath(file)).Wait(3000)) if (!importer.ImportAsync(Path.GetFullPath(file, cwd)).Wait(3000))
throw new TimeoutException(@"IPC took too long to send"); throw new TimeoutException(@"IPC took too long to send");
} }

View File

@ -43,7 +43,7 @@ namespace osu.Desktop.Updater
private async void checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null) private async void checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null)
{ {
//should we schedule a retry on completion of this check? // should we schedule a retry on completion of this check?
bool scheduleRecheck = true; bool scheduleRecheck = true;
try try
@ -52,7 +52,7 @@ namespace osu.Desktop.Updater
var info = await updateManager.CheckForUpdate(!useDeltaPatching); var info = await updateManager.CheckForUpdate(!useDeltaPatching);
if (info.ReleasesToApply.Count == 0) if (info.ReleasesToApply.Count == 0)
//no updates available. bail and retry later. // no updates available. bail and retry later.
return; return;
if (notification == null) if (notification == null)
@ -81,8 +81,8 @@ namespace osu.Desktop.Updater
{ {
logger.Add(@"delta patching failed; will attempt full download!"); logger.Add(@"delta patching failed; will attempt full download!");
//could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959) // could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959)
//try again without deltas. // try again without deltas.
checkForUpdateAsync(false, notification); checkForUpdateAsync(false, notification);
scheduleRecheck = false; scheduleRecheck = false;
} }
@ -101,7 +101,7 @@ namespace osu.Desktop.Updater
{ {
if (scheduleRecheck) if (scheduleRecheck)
{ {
//check again in 30 minutes. // check again in 30 minutes.
Scheduler.AddDelayed(() => checkForUpdateAsync(), 60000 * 30); Scheduler.AddDelayed(() => checkForUpdateAsync(), 60000 * 30);
} }
} }

View File

@ -8,7 +8,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.IO.Archives; using osu.Game.IO.Archives;
using osu.Game.Resources; using osu.Game.Tests.Resources;
namespace osu.Game.Benchmarks namespace osu.Game.Benchmarks
{ {
@ -18,8 +18,8 @@ namespace osu.Game.Benchmarks
public override void SetUp() public override void SetUp()
{ {
using (var resources = new DllResourceStore(OsuResources.ResourceAssembly)) using (var resources = new DllResourceStore(typeof(TestResources).Assembly))
using (var archive = resources.GetStream("Beatmaps/241526 Soleily - Renatus.osz")) using (var archive = resources.GetStream("Resources/Archives/241526 Soleily - Renatus.osz"))
using (var reader = new ZipArchiveReader(archive)) using (var reader = new ZipArchiveReader(archive))
reader.GetStream("Soleily - Renatus (Gamu) [Insane].osu").CopyTo(beatmapStream); reader.GetStream("Soleily - Renatus (Gamu) [Insane].osu").CopyTo(beatmapStream);
} }

View File

@ -13,6 +13,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\osu.Game.Tests\osu.Game.Tests.csproj" />
<ProjectReference Include="..\osu.Game\osu.Game.csproj" /> <ProjectReference Include="..\osu.Game\osu.Game.csproj" />
</ItemGroup> </ItemGroup>

View File

@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
[TestCase(4.2058561036909863d, "diffcalc-test")] [TestCase(4.050601681491468d, "diffcalc-test")]
public void Test(double expected, string name) public void Test(double expected, string name)
=> base.Test(expected, name); => base.Test(expected, name);

View File

@ -0,0 +1,36 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.IO.Stores;
using osu.Game.Rulesets.Catch.Skinning;
using osu.Game.Skinning;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
public class CatchSkinColourDecodingTest
{
[Test]
public void TestCatchSkinColourDecoding()
{
var store = new NamespacedResourceStore<byte[]>(new DllResourceStore(GetType().Assembly), "Resources/special-skin");
var rawSkin = new TestLegacySkin(new SkinInfo { Name = "special-skin" }, store);
var skin = new CatchLegacySkinTransformer(rawSkin);
Assert.AreEqual(new Color4(232, 185, 35, 255), skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value);
Assert.AreEqual(new Color4(232, 74, 35, 255), skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashAfterImage)?.Value);
Assert.AreEqual(new Color4(0, 255, 255, 255), skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashFruit)?.Value);
}
private class TestLegacySkin : LegacySkin
{
public TestLegacySkin(SkinInfo skin, IResourceStore<byte[]> storage)
// Bypass LegacySkinResourceStore to avoid returning null for retrieving files due to bad skin info (SkinInfo.Files = null).
: base(skin, storage, null, "skin.ini")
{
}
}
}
}

View File

@ -1,21 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Game.Rulesets.Catch.Skinning;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests namespace osu.Game.Rulesets.Catch.Tests
{ {
public abstract class CatchSkinnableTestScene : SkinnableTestScene public abstract class CatchSkinnableTestScene : SkinnableTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(CatchRuleset),
typeof(CatchLegacySkinTransformer),
};
protected override Ruleset CreateRulesetForSkinProvider() => new CatchRuleset(); protected override Ruleset CreateRulesetForSkinProvider() => new CatchRuleset();
} }
} }

View File

@ -0,0 +1,4 @@
[CatchTheBeat]
HyperDash: 232,185,35
HyperDashFruit: 0,255,255
HyperDashAfterImage: 232,74,35

View File

@ -1,30 +1,15 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.UI;
namespace osu.Game.Rulesets.Catch.Tests namespace osu.Game.Rulesets.Catch.Tests
{ {
[TestFixture] [TestFixture]
public class TestSceneBananaShower : TestSceneCatchPlayer public class TestSceneBananaShower : TestSceneCatchPlayer
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(BananaShower),
typeof(Banana),
typeof(DrawableBananaShower),
typeof(DrawableBanana),
typeof(CatchRuleset),
typeof(DrawableCatchRuleset),
};
[Test] [Test]
public void TestBananaShower() public void TestBananaShower()
{ {

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
@ -11,11 +9,6 @@ namespace osu.Game.Rulesets.Catch.Tests
[TestFixture] [TestFixture]
public class TestSceneCatchPlayer : PlayerTestScene public class TestSceneCatchPlayer : PlayerTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(CatchRuleset),
};
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset(); protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
} }
} }

View File

@ -4,26 +4,18 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Rulesets.Catch.Tests namespace osu.Game.Rulesets.Catch.Tests
{ {
[TestFixture] [TestFixture]
public class TestSceneCatcher : CatchSkinnableTestScene public class TestSceneCatcher : CatchSkinnableTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
{
typeof(CatcherArea),
typeof(CatcherSprite)
}).ToList();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
SetContents(() => new Catcher SetContents(() => new Catcher(new Container())
{ {
RelativePositionAxes = Axes.None, RelativePositionAxes = Axes.None,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,

View File

@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
@ -22,15 +21,6 @@ namespace osu.Game.Rulesets.Catch.Tests
{ {
public class TestSceneDrawableHitObjects : OsuTestScene public class TestSceneDrawableHitObjects : OsuTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Catcher),
typeof(DrawableCatchRuleset),
typeof(DrawableFruit),
typeof(DrawableJuiceStream),
typeof(DrawableBanana)
};
private DrawableCatchRuleset drawableRuleset; private DrawableCatchRuleset drawableRuleset;
private double playfieldTime => drawableRuleset.Playfield.Time.Current; private double playfieldTime => drawableRuleset.Playfield.Time.Current;

View File

@ -1,9 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Rulesets.Catch.Mods; using osu.Game.Rulesets.Catch.Mods;
@ -11,8 +8,6 @@ namespace osu.Game.Rulesets.Catch.Tests
{ {
public class TestSceneDrawableHitObjectsHidden : TestSceneDrawableHitObjects public class TestSceneDrawableHitObjectsHidden : TestSceneDrawableHitObjects
{ {
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(CatchModHidden) }).ToList();
[SetUp] [SetUp]
public void SetUp() => Schedule(() => public void SetUp() => Schedule(() =>
{ {

View File

@ -2,13 +2,10 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Catch.Tests namespace osu.Game.Rulesets.Catch.Tests
@ -16,22 +13,6 @@ namespace osu.Game.Rulesets.Catch.Tests
[TestFixture] [TestFixture]
public class TestSceneFruitObjects : CatchSkinnableTestScene public class TestSceneFruitObjects : CatchSkinnableTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
{
typeof(CatchHitObject),
typeof(Fruit),
typeof(FruitPiece),
typeof(Droplet),
typeof(Banana),
typeof(BananaShower),
typeof(DrawableCatchHitObject),
typeof(DrawableFruit),
typeof(DrawableDroplet),
typeof(DrawableBanana),
typeof(DrawableBananaShower),
typeof(Pulp),
}).ToList();
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Testing; using osu.Framework.Testing;
@ -17,10 +16,6 @@ namespace osu.Game.Rulesets.Catch.Tests
[TestFixture] [TestFixture]
public class TestSceneHyperDash : TestSceneCatchPlayer public class TestSceneHyperDash : TestSceneCatchPlayer
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(CatcherArea),
};
protected override bool Autoplay => true; protected override bool Autoplay => true;

View File

@ -26,6 +26,48 @@ namespace osu.Game.Rulesets.Catch.Tests
[Resolved] [Resolved]
private SkinManager skins { get; set; } private SkinManager skins { get; set; }
[Test]
public void TestDefaultCatcherColour()
{
var skin = new TestSkin();
checkHyperDashCatcherColour(skin, Catcher.DEFAULT_HYPER_DASH_COLOUR);
}
[Test]
public void TestCustomCatcherColour()
{
var skin = new TestSkin
{
HyperDashColour = Color4.Goldenrod
};
checkHyperDashCatcherColour(skin, skin.HyperDashColour);
}
[Test]
public void TestCustomEndGlowColour()
{
var skin = new TestSkin
{
HyperDashAfterImageColour = Color4.Lime
};
checkHyperDashCatcherColour(skin, Catcher.DEFAULT_HYPER_DASH_COLOUR, skin.HyperDashAfterImageColour);
}
[Test]
public void TestCustomEndGlowColourPriority()
{
var skin = new TestSkin
{
HyperDashColour = Color4.Goldenrod,
HyperDashAfterImageColour = Color4.Lime
};
checkHyperDashCatcherColour(skin, skin.HyperDashColour, skin.HyperDashAfterImageColour);
}
[Test] [Test]
public void TestDefaultFruitColour() public void TestDefaultFruitColour()
{ {
@ -68,6 +110,38 @@ namespace osu.Game.Rulesets.Catch.Tests
checkHyperDashFruitColour(skin, skin.HyperDashColour); checkHyperDashFruitColour(skin, skin.HyperDashColour);
} }
private void checkHyperDashCatcherColour(ISkin skin, Color4 expectedCatcherColour, Color4? expectedEndGlowColour = null)
{
CatcherArea catcherArea = null;
CatcherTrailDisplay trails = null;
AddStep("create hyper-dashing catcher", () =>
{
Child = setupSkinHierarchy(catcherArea = new CatcherArea
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(4f),
}, skin);
trails = catcherArea.OfType<CatcherTrailDisplay>().Single();
catcherArea.MovableCatcher.SetHyperDashState(2);
});
AddUntilStep("catcher colour is correct", () => catcherArea.MovableCatcher.Colour == expectedCatcherColour);
AddAssert("catcher trails colours are correct", () => trails.HyperDashTrailsColour == expectedCatcherColour);
AddAssert("catcher end-glow colours are correct", () => trails.EndGlowSpritesColour == (expectedEndGlowColour ?? expectedCatcherColour));
AddStep("finish hyper-dashing", () =>
{
catcherArea.MovableCatcher.SetHyperDashState(1);
catcherArea.MovableCatcher.FinishTransforms();
});
AddAssert("catcher colour returned to white", () => catcherArea.MovableCatcher.Colour == Color4.White);
}
private void checkHyperDashFruitColour(ISkin skin, Color4 expectedColour) private void checkHyperDashFruitColour(ISkin skin, Color4 expectedColour)
{ {
DrawableFruit drawableFruit = null; DrawableFruit drawableFruit = null;

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" /> <Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" /> <PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
switch (obj) switch (obj)
{ {
case IHasCurve curveData: case IHasPathWithRepeats curveData:
return new JuiceStream return new JuiceStream
{ {
StartTime = obj.StartTime, StartTime = obj.StartTime,
@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0 LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0
}.Yield(); }.Yield();
case IHasEndTime endTime: case IHasDuration endTime:
return new BananaShower return new BananaShower
{ {
StartTime = obj.StartTime, StartTime = obj.StartTime,

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{ {
public class CatchDifficultyCalculator : DifficultyCalculator public class CatchDifficultyCalculator : DifficultyCalculator
{ {
private const double star_scaling_factor = 0.145; private const double star_scaling_factor = 0.153;
protected override int SectionLength => 750; protected override int SectionLength => 750;
@ -73,6 +73,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{ {
halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) * 0.5f; halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) * 0.5f;
// For circle sizes above 5.5, reduce the catcher width further to simulate imperfect gameplay.
halfCatcherWidth *= 1 - (Math.Max(0, beatmap.BeatmapInfo.BaseDifficulty.CircleSize - 5.5f) * 0.0625f);
return new Skill[] return new Skill[]
{ {
new Movement(halfCatcherWidth), new Movement(halfCatcherWidth),

View File

@ -4,12 +4,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Extensions;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Scoring.Legacy;
namespace osu.Game.Rulesets.Catch.Difficulty namespace osu.Game.Rulesets.Catch.Difficulty
{ {
@ -34,11 +34,11 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{ {
mods = Score.Mods; mods = Score.Mods;
fruitsHit = Score?.GetCount300() ?? Score.Statistics[HitResult.Perfect]; fruitsHit = Score.Statistics.GetOrDefault(HitResult.Perfect);
ticksHit = Score?.GetCount100() ?? 0; ticksHit = Score.Statistics.GetOrDefault(HitResult.LargeTickHit);
tinyTicksHit = Score?.GetCount50() ?? 0; tinyTicksHit = Score.Statistics.GetOrDefault(HitResult.SmallTickHit);
tinyTicksMissed = Score?.GetCountKatu() ?? 0; tinyTicksMissed = Score.Statistics.GetOrDefault(HitResult.SmallTickMiss);
misses = Score.Statistics[HitResult.Miss]; misses = Score.Statistics.GetOrDefault(HitResult.Miss);
// Don't count scores made with supposedly unranked mods // Don't count scores made with supposedly unranked mods
if (mods.Any(m => !m.Ranked)) if (mods.Any(m => !m.Ranked))
@ -52,8 +52,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
// Longer maps are worth more // Longer maps are worth more
double lengthBonus = double lengthBonus =
0.95 + 0.4 * Math.Min(1.0, numTotalHits / 3000.0) + 0.95 + 0.3 * Math.Min(1.0, numTotalHits / 2500.0) +
(numTotalHits > 3000 ? Math.Log10(numTotalHits / 3000.0) * 0.5 : 0.0); (numTotalHits > 2500 ? Math.Log10(numTotalHits / 2500.0) * 0.475 : 0.0);
// Longer maps are worth more // Longer maps are worth more
value *= lengthBonus; value *= lengthBonus;
@ -63,19 +63,28 @@ namespace osu.Game.Rulesets.Catch.Difficulty
// Combo scaling // Combo scaling
if (Attributes.MaxCombo > 0) if (Attributes.MaxCombo > 0)
value *= Math.Min(Math.Pow(Attributes.MaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0); value *= Math.Min(Math.Pow(Score.MaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
double approachRate = Attributes.ApproachRate;
double approachRateFactor = 1.0; double approachRateFactor = 1.0;
if (Attributes.ApproachRate > 9.0) if (approachRate > 9.0)
approachRateFactor += 0.1 * (Attributes.ApproachRate - 9.0); // 10% for each AR above 9 approachRateFactor += 0.1 * (approachRate - 9.0); // 10% for each AR above 9
else if (Attributes.ApproachRate < 8.0) if (approachRate > 10.0)
approachRateFactor += 0.025 * (8.0 - Attributes.ApproachRate); // 2.5% for each AR below 8 approachRateFactor += 0.1 * (approachRate - 10.0); // Additional 10% at AR 11, 30% total
else if (approachRate < 8.0)
approachRateFactor += 0.025 * (8.0 - approachRate); // 2.5% for each AR below 8
value *= approachRateFactor; value *= approachRateFactor;
if (mods.Any(m => m is ModHidden)) if (mods.Any(m => m is ModHidden))
// Hiddens gives nothing on max approach rate, and more the lower it is {
value *= 1.05 + 0.075 * (10.0 - Math.Min(10.0, Attributes.ApproachRate)); // 7.5% for each AR below 10 value *= 1.05 + 0.075 * (10.0 - Math.Min(10.0, Attributes.ApproachRate)); // 7.5% for each AR below 10
// Hiddens gives almost nothing on max approach rate, and more the lower it is
if (approachRate <= 10.0)
value *= 1.05 + 0.075 * (10.0 - approachRate); // 7.5% for each AR below 10
else if (approachRate > 10.0)
value *= 1.01 + 0.04 * (11.0 - Math.Min(11.0, approachRate)); // 5% at AR 10, 1% at AR 11
}
if (mods.Any(m => m is ModFlashlight)) if (mods.Any(m => m is ModFlashlight))
// Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps. // Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps.
@ -91,7 +100,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
return value; return value;
} }
private float accuracy() => totalHits() == 0 ? 0 : Math.Clamp((float)totalSuccessfulHits() / totalHits(), 0, 1); private double accuracy() => totalHits() == 0 ? 0 : Math.Clamp((double)totalSuccessfulHits() / totalHits(), 0, 1);
private int totalHits() => tinyTicksHit + ticksHit + fruitsHit + misses + tinyTicksMissed; private int totalHits() => tinyTicksHit + ticksHit + fruitsHit + misses + tinyTicksMissed;
private int totalSuccessfulHits() => tinyTicksHit + ticksHit + fruitsHit; private int totalSuccessfulHits() => tinyTicksHit + ticksHit + fruitsHit;
private int totalComboHits() => misses + ticksHit + fruitsHit; private int totalComboHits() => misses + ticksHit + fruitsHit;

View File

@ -21,10 +21,12 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
public readonly float LastNormalizedPosition; public readonly float LastNormalizedPosition;
/// <summary> /// <summary>
/// Milliseconds elapsed since the start time of the previous <see cref="CatchDifficultyHitObject"/>, with a minimum of 25ms. /// Milliseconds elapsed since the start time of the previous <see cref="CatchDifficultyHitObject"/>, with a minimum of 40ms.
/// </summary> /// </summary>
public readonly double StrainTime; public readonly double StrainTime;
public readonly double ClockRate;
public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, float halfCatcherWidth) public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, float halfCatcherWidth)
: base(hitObject, lastObject, clockRate) : base(hitObject, lastObject, clockRate)
{ {
@ -34,8 +36,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
NormalizedPosition = BaseObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor; NormalizedPosition = BaseObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor;
LastNormalizedPosition = LastObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor; LastNormalizedPosition = LastObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor;
// Every strain interval is hard capped at the equivalent of 600 BPM streaming speed as a safety measure // Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure
StrainTime = Math.Max(25, DeltaTime); StrainTime = Math.Max(40, DeltaTime);
ClockRate = clockRate;
} }
} }
} }

View File

@ -13,9 +13,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
{ {
private const float absolute_player_positioning_error = 16f; private const float absolute_player_positioning_error = 16f;
private const float normalized_hitobject_radius = 41.0f; private const float normalized_hitobject_radius = 41.0f;
private const double direction_change_bonus = 12.5; private const double direction_change_bonus = 21.0;
protected override double SkillMultiplier => 850; protected override double SkillMultiplier => 900;
protected override double StrainDecayBase => 0.2; protected override double StrainDecayBase => 0.2;
protected override double DecayWeight => 0.94; protected override double DecayWeight => 0.94;
@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
private float? lastPlayerPosition; private float? lastPlayerPosition;
private float lastDistanceMoved; private float lastDistanceMoved;
private double lastStrainTime;
public Movement(float halfCatcherWidth) public Movement(float halfCatcherWidth)
{ {
@ -45,47 +46,47 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
float distanceMoved = playerPosition - lastPlayerPosition.Value; float distanceMoved = playerPosition - lastPlayerPosition.Value;
double distanceAddition = Math.Pow(Math.Abs(distanceMoved), 1.3) / 500; double weightedStrainTime = catchCurrent.StrainTime + 13 + (3 / catchCurrent.ClockRate);
double sqrtStrain = Math.Sqrt(catchCurrent.StrainTime);
double bonus = 0; double distanceAddition = (Math.Pow(Math.Abs(distanceMoved), 1.3) / 510);
double sqrtStrain = Math.Sqrt(weightedStrainTime);
// Direction changes give an extra point! double edgeDashBonus = 0;
// Direction change bonus.
if (Math.Abs(distanceMoved) > 0.1) if (Math.Abs(distanceMoved) > 0.1)
{ {
if (Math.Abs(lastDistanceMoved) > 0.1 && Math.Sign(distanceMoved) != Math.Sign(lastDistanceMoved)) if (Math.Abs(lastDistanceMoved) > 0.1 && Math.Sign(distanceMoved) != Math.Sign(lastDistanceMoved))
{ {
double bonusFactor = Math.Min(absolute_player_positioning_error, Math.Abs(distanceMoved)) / absolute_player_positioning_error; double bonusFactor = Math.Min(50, Math.Abs(distanceMoved)) / 50;
double antiflowFactor = Math.Max(Math.Min(70, Math.Abs(lastDistanceMoved)) / 70, 0.38);
distanceAddition += direction_change_bonus / sqrtStrain * bonusFactor; distanceAddition += direction_change_bonus / Math.Sqrt(lastStrainTime + 16) * bonusFactor * antiflowFactor * Math.Max(1 - Math.Pow(weightedStrainTime / 1000, 3), 0);
// Bonus for tougher direction switches and "almost" hyperdashes at this point
if (catchCurrent.LastObject.DistanceToHyperDash <= 10 / CatchPlayfield.BASE_WIDTH)
bonus = 0.3 * bonusFactor;
} }
// Base bonus for every movement, giving some weight to streams. // Base bonus for every movement, giving some weight to streams.
distanceAddition += 7.5 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain; distanceAddition += 12.5 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain;
} }
// Bonus for "almost" hyperdashes at corner points // Bonus for edge dashes.
if (catchCurrent.LastObject.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH) if (catchCurrent.LastObject.DistanceToHyperDash <= 20.0f / CatchPlayfield.BASE_WIDTH)
{ {
if (!catchCurrent.LastObject.HyperDash) if (!catchCurrent.LastObject.HyperDash)
bonus += 1.0; edgeDashBonus += 5.7;
else else
{ {
// After a hyperdash we ARE in the correct position. Always! // After a hyperdash we ARE in the correct position. Always!
playerPosition = catchCurrent.NormalizedPosition; playerPosition = catchCurrent.NormalizedPosition;
} }
distanceAddition *= 1.0 + bonus * ((10 - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10); distanceAddition *= 1.0 + edgeDashBonus * ((20 - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 20) * Math.Pow((Math.Min(catchCurrent.StrainTime * catchCurrent.ClockRate, 265) / 265), 1.5); // Edge Dashes are easier at lower ms values
} }
lastPlayerPosition = playerPosition; lastPlayerPosition = playerPosition;
lastDistanceMoved = distanceMoved; lastDistanceMoved = distanceMoved;
lastStrainTime = catchCurrent.StrainTime;
return distanceAddition / catchCurrent.StrainTime; return distanceAddition / weightedStrainTime;
} }
} }
} }

View File

@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Catch.Mods
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
} }
//disable keyboard controls // disable keyboard controls
public bool OnPressed(CatchAction action) => true; public bool OnPressed(CatchAction action) => true;
public void OnReleased(CatchAction action) public void OnReleased(CatchAction action)

View File

@ -1,12 +1,13 @@
// 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.Threading;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Catch.Objects namespace osu.Game.Rulesets.Catch.Objects
{ {
public class BananaShower : CatchHitObject, IHasEndTime public class BananaShower : CatchHitObject, IHasDuration
{ {
public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana; public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
@ -14,13 +15,13 @@ namespace osu.Game.Rulesets.Catch.Objects
public override Judgement CreateJudgement() => new IgnoreJudgement(); public override Judgement CreateJudgement() => new IgnoreJudgement();
protected override void CreateNestedHitObjects() protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
{ {
base.CreateNestedHitObjects(); base.CreateNestedHitObjects(cancellationToken);
createBananas(); createBananas(cancellationToken);
} }
private void createBananas() private void createBananas(CancellationToken cancellationToken)
{ {
double spacing = Duration; double spacing = Duration;
while (spacing > 100) while (spacing > 100)
@ -31,6 +32,8 @@ namespace osu.Game.Rulesets.Catch.Objects
for (double i = StartTime; i <= EndTime; i += spacing) for (double i = StartTime; i <= EndTime; i += spacing)
{ {
cancellationToken.ThrowIfCancellationRequested();
AddNested(new Banana AddNested(new Banana
{ {
Samples = Samples, Samples = Samples,

View File

@ -3,6 +3,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
@ -13,7 +14,7 @@ using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Catch.Objects namespace osu.Game.Rulesets.Catch.Objects
{ {
public class JuiceStream : CatchHitObject, IHasCurve public class JuiceStream : CatchHitObject, IHasPathWithRepeats
{ {
/// <summary> /// <summary>
/// Positional distance that results in a duration of one second, before any speed adjustments. /// Positional distance that results in a duration of one second, before any speed adjustments.
@ -45,9 +46,9 @@ namespace osu.Game.Rulesets.Catch.Objects
TickDistance = scoringDistance / difficulty.SliderTickRate; TickDistance = scoringDistance / difficulty.SliderTickRate;
} }
protected override void CreateNestedHitObjects() protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
{ {
base.CreateNestedHitObjects(); base.CreateNestedHitObjects(cancellationToken);
var dropletSamples = Samples.Select(s => new HitSampleInfo var dropletSamples = Samples.Select(s => new HitSampleInfo
{ {
@ -58,7 +59,7 @@ namespace osu.Game.Rulesets.Catch.Objects
SliderEventDescriptor? lastEvent = null; SliderEventDescriptor? lastEvent = null;
foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset)) foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken))
{ {
// generate tiny droplets since the last point // generate tiny droplets since the last point
if (lastEvent != null) if (lastEvent != null)
@ -73,6 +74,8 @@ namespace osu.Game.Rulesets.Catch.Objects
for (double t = timeBetweenTiny; t < sinceLastTick; t += timeBetweenTiny) for (double t = timeBetweenTiny; t < sinceLastTick; t += timeBetweenTiny)
{ {
cancellationToken.ThrowIfCancellationRequested();
AddNested(new TinyDroplet AddNested(new TinyDroplet
{ {
StartTime = t + lastEvent.Value.Time, StartTime = t + lastEvent.Value.Time,
@ -112,15 +115,15 @@ namespace osu.Game.Rulesets.Catch.Objects
} }
} }
public double EndTime public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH;
public double Duration
{ {
get => StartTime + this.SpanCount() * Path.Distance / Velocity; get => this.SpanCount() * Path.Distance / Velocity;
set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed.
} }
public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH; public double EndTime => StartTime + Duration;
public double Duration => EndTime - StartTime;
private readonly SliderPath path = new SliderPath(); private readonly SliderPath path = new SliderPath();

View File

@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.Replays
if (lastPosition - catcher_width_half < h.X && lastPosition + catcher_width_half > h.X) if (lastPosition - catcher_width_half < h.X && lastPosition + catcher_width_half > h.X)
{ {
//we are already in the correct range. // we are already in the correct range.
lastTime = h.StartTime; lastTime = h.StartTime;
addFrame(h.StartTime, lastPosition); addFrame(h.StartTime, lastPosition);
return; return;
@ -72,14 +72,14 @@ namespace osu.Game.Rulesets.Catch.Replays
} }
else if (dashRequired) else if (dashRequired)
{ {
//we do a movement in two parts - the dash part then the normal part... // we do a movement in two parts - the dash part then the normal part...
double timeAtNormalSpeed = positionChange / movement_speed; double timeAtNormalSpeed = positionChange / movement_speed;
double timeWeNeedToSave = timeAtNormalSpeed - timeAvailable; double timeWeNeedToSave = timeAtNormalSpeed - timeAvailable;
double timeAtDashSpeed = timeWeNeedToSave / 2; double timeAtDashSpeed = timeWeNeedToSave / 2;
float midPosition = (float)Interpolation.Lerp(lastPosition, h.X, (float)timeAtDashSpeed / timeAvailable); float midPosition = (float)Interpolation.Lerp(lastPosition, h.X, (float)timeAtDashSpeed / timeAvailable);
//dash movement // dash movement
addFrame(h.StartTime - timeAvailable + 1, lastPosition, true); addFrame(h.StartTime - timeAvailable + 1, lastPosition, true);
addFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition); addFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition);
addFrame(h.StartTime, h.X); addFrame(h.StartTime, h.X);

View File

@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Skinning
{ {
private readonly ISkin source; private readonly ISkin source;
public CatchLegacySkinTransformer(ISkinSource source) public CatchLegacySkinTransformer(ISkin source)
{ {
this.source = source; this.source = source;
} }

View File

@ -3,26 +3,37 @@
using System; using System;
using System.Linq; using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Skinning;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Skinning;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.UI namespace osu.Game.Rulesets.Catch.UI
{ {
public class Catcher : Container, IKeyBindingHandler<CatchAction> public class Catcher : SkinReloadableDrawable, IKeyBindingHandler<CatchAction>
{ {
/// <summary>
/// The default colour used to tint hyper-dash fruit, along with the moving catcher, its trail
/// and end glow/after-image during a hyper-dash.
/// </summary>
public static readonly Color4 DEFAULT_HYPER_DASH_COLOUR = Color4.Red; public static readonly Color4 DEFAULT_HYPER_DASH_COLOUR = Color4.Red;
/// <summary>
/// The duration between transitioning to hyper-dash state.
/// </summary>
public const double HYPER_DASH_TRANSITION_DURATION = 180;
/// <summary> /// <summary>
/// Whether we are hyper-dashing or not. /// Whether we are hyper-dashing or not.
/// </summary> /// </summary>
@ -35,7 +46,10 @@ namespace osu.Game.Rulesets.Catch.UI
public Container ExplodingFruitTarget; public Container ExplodingFruitTarget;
public Container AdditiveTarget; [NotNull]
private readonly Container trailsTarget;
private CatcherTrailDisplay trails;
public CatcherAnimationState CurrentState { get; private set; } public CatcherAnimationState CurrentState { get; private set; }
@ -44,33 +58,23 @@ namespace osu.Game.Rulesets.Catch.UI
/// </summary> /// </summary>
private const float allowed_catch_range = 0.8f; private const float allowed_catch_range = 0.8f;
protected bool Dashing /// <summary>
/// The drawable catcher for <see cref="CurrentState"/>.
/// </summary>
internal Drawable CurrentDrawableCatcher => currentCatcher.Drawable;
private bool dashing;
public bool Dashing
{ {
get => dashing; get => dashing;
set protected set
{ {
if (value == dashing) return; if (value == dashing) return;
dashing = value; dashing = value;
Trail |= dashing; updateTrailVisibility();
}
}
/// <summary>
/// Activate or deactivate the trail. Will be automatically deactivated when conditions to keep the trail displayed are no longer met.
/// </summary>
protected bool Trail
{
get => trail;
set
{
if (value == trail || AdditiveTarget == null) return;
trail = value;
if (Trail)
beginTrail();
} }
} }
@ -87,18 +91,19 @@ namespace osu.Game.Rulesets.Catch.UI
private CatcherSprite currentCatcher; private CatcherSprite currentCatcher;
private Color4 hyperDashColour = DEFAULT_HYPER_DASH_COLOUR;
private Color4 hyperDashEndGlowColour = DEFAULT_HYPER_DASH_COLOUR;
private int currentDirection; private int currentDirection;
private bool dashing;
private bool trail;
private double hyperDashModifier = 1; private double hyperDashModifier = 1;
private int hyperDashDirection; private int hyperDashDirection;
private float hyperDashTargetPosition; private float hyperDashTargetPosition;
public Catcher(BeatmapDifficulty difficulty = null) public Catcher([NotNull] Container trailsTarget, BeatmapDifficulty difficulty = null)
{ {
this.trailsTarget = trailsTarget;
RelativePositionAxes = Axes.X; RelativePositionAxes = Axes.X;
X = 0.5f; X = 0.5f;
@ -114,7 +119,7 @@ namespace osu.Game.Rulesets.Catch.UI
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Children = new Drawable[] InternalChildren = new Drawable[]
{ {
caughtFruit = new Container<DrawableHitObject> caughtFruit = new Container<DrawableHitObject>
{ {
@ -138,6 +143,8 @@ namespace osu.Game.Rulesets.Catch.UI
} }
}; };
trailsTarget.Add(trails = new CatcherTrailDisplay(this));
updateCatcher(); updateCatcher();
} }
@ -185,7 +192,7 @@ namespace osu.Game.Rulesets.Catch.UI
caughtFruit.Add(fruit); caughtFruit.Add(fruit);
Add(new HitExplosion(fruit) AddInternal(new HitExplosion(fruit)
{ {
X = fruit.X, X = fruit.X,
Scale = new Vector2(fruit.HitObject.Scale) Scale = new Vector2(fruit.HitObject.Scale)
@ -240,8 +247,6 @@ namespace osu.Game.Rulesets.Catch.UI
/// <param name="targetPosition">When this catcher crosses this position, this catcher ends hyper-dashing.</param> /// <param name="targetPosition">When this catcher crosses this position, this catcher ends hyper-dashing.</param>
public void SetHyperDashState(double modifier = 1, float targetPosition = -1) public void SetHyperDashState(double modifier = 1, float targetPosition = -1)
{ {
const float hyper_dash_transition_length = 180;
var wasHyperDashing = HyperDashing; var wasHyperDashing = HyperDashing;
if (modifier <= 1 || X == targetPosition) if (modifier <= 1 || X == targetPosition)
@ -250,11 +255,7 @@ namespace osu.Game.Rulesets.Catch.UI
hyperDashDirection = 0; hyperDashDirection = 0;
if (wasHyperDashing) if (wasHyperDashing)
{ runHyperDashStateTransition(false);
this.FadeColour(Color4.White, hyper_dash_transition_length, Easing.OutQuint);
this.FadeTo(1, hyper_dash_transition_length, Easing.OutQuint);
Trail &= Dashing;
}
} }
else else
{ {
@ -264,20 +265,32 @@ namespace osu.Game.Rulesets.Catch.UI
if (!wasHyperDashing) if (!wasHyperDashing)
{ {
this.FadeColour(Color4.OrangeRed, hyper_dash_transition_length, Easing.OutQuint); trails.DisplayEndGlow();
this.FadeTo(0.2f, hyper_dash_transition_length, Easing.OutQuint); runHyperDashStateTransition(true);
Trail = true; }
}
}
var hyperDashEndGlow = createAdditiveSprite(); private void runHyperDashStateTransition(bool hyperDashing)
{
trails.HyperDashTrailsColour = hyperDashColour;
trails.EndGlowSpritesColour = hyperDashEndGlowColour;
updateTrailVisibility();
hyperDashEndGlow.MoveToOffset(new Vector2(0, -10), 1200, Easing.In); if (hyperDashing)
hyperDashEndGlow.ScaleTo(hyperDashEndGlow.Scale * 0.95f).ScaleTo(hyperDashEndGlow.Scale * 1.2f, 1200, Easing.In); {
hyperDashEndGlow.FadeOut(1200); this.FadeColour(hyperDashColour, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
hyperDashEndGlow.Expire(true); this.FadeTo(0.2f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
} }
else
{
this.FadeColour(Color4.White, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
this.FadeTo(1f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
} }
} }
private void updateTrailVisibility() => trails.DisplayTrail = Dashing || HyperDashing;
public bool OnPressed(CatchAction action) public bool OnPressed(CatchAction action)
{ {
switch (action) switch (action)
@ -366,6 +379,21 @@ namespace osu.Game.Rulesets.Catch.UI
}); });
} }
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
hyperDashColour =
skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value ??
DEFAULT_HYPER_DASH_COLOUR;
hyperDashEndGlowColour =
skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashAfterImage)?.Value ??
hyperDashColour;
runHyperDashStateTransition(HyperDashing);
}
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
@ -411,22 +439,6 @@ namespace osu.Game.Rulesets.Catch.UI
(currentCatcher.Drawable as IFramedAnimation)?.GotoFrame(0); (currentCatcher.Drawable as IFramedAnimation)?.GotoFrame(0);
} }
private void beginTrail()
{
if (!dashing && !HyperDashing)
{
Trail = false;
return;
}
var additive = createAdditiveSprite();
additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint);
additive.Expire(true);
Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50);
}
private void updateState(CatcherAnimationState state) private void updateState(CatcherAnimationState state)
{ {
if (CurrentState == state) if (CurrentState == state)
@ -436,25 +448,6 @@ namespace osu.Game.Rulesets.Catch.UI
updateCatcher(); updateCatcher();
} }
private CatcherTrailSprite createAdditiveSprite()
{
var tex = (currentCatcher.Drawable as TextureAnimation)?.CurrentFrame ?? ((Sprite)currentCatcher.Drawable).Texture;
var sprite = new CatcherTrailSprite(tex)
{
Anchor = Anchor,
Scale = Scale,
Colour = HyperDashing ? Color4.Red : Color4.White,
Blending = BlendingParameters.Additive,
RelativePositionAxes = RelativePositionAxes,
Position = Position
};
AdditiveTarget?.Add(sprite);
return sprite;
}
private void removeFromPlateWithTransform(DrawableHitObject fruit, Action<DrawableHitObject> action) private void removeFromPlateWithTransform(DrawableHitObject fruit, Action<DrawableHitObject> action)
{ {
if (ExplodingFruitTarget != null) if (ExplodingFruitTarget != null)

View File

@ -33,10 +33,7 @@ namespace osu.Game.Rulesets.Catch.UI
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Height = CATCHER_SIZE; Height = CATCHER_SIZE;
Child = MovableCatcher = new Catcher(difficulty) Child = MovableCatcher = new Catcher(this, difficulty);
{
AdditiveTarget = this,
};
} }
public static float GetCatcherSize(BeatmapDifficulty difficulty) public static float GetCatcherSize(BeatmapDifficulty difficulty)

View File

@ -0,0 +1,135 @@
// 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 JetBrains.Annotations;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.UI
{
/// <summary>
/// Represents a component responsible for displaying
/// the appropriate catcher trails when requested to.
/// </summary>
public class CatcherTrailDisplay : CompositeDrawable
{
private readonly Catcher catcher;
private readonly Container<CatcherTrailSprite> dashTrails;
private readonly Container<CatcherTrailSprite> hyperDashTrails;
private readonly Container<CatcherTrailSprite> endGlowSprites;
private Color4 hyperDashTrailsColour;
public Color4 HyperDashTrailsColour
{
get => hyperDashTrailsColour;
set
{
if (hyperDashTrailsColour == value)
return;
hyperDashTrailsColour = value;
hyperDashTrails.FadeColour(hyperDashTrailsColour, Catcher.HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
}
}
private Color4 endGlowSpritesColour;
public Color4 EndGlowSpritesColour
{
get => endGlowSpritesColour;
set
{
if (endGlowSpritesColour == value)
return;
endGlowSpritesColour = value;
endGlowSprites.FadeColour(endGlowSpritesColour, Catcher.HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
}
}
private bool trail;
/// <summary>
/// Whether to start displaying trails following the catcher.
/// </summary>
public bool DisplayTrail
{
get => trail;
set
{
if (trail == value)
return;
trail = value;
if (trail)
displayTrail();
}
}
public CatcherTrailDisplay([NotNull] Catcher catcher)
{
this.catcher = catcher ?? throw new ArgumentNullException(nameof(catcher));
RelativeSizeAxes = Axes.Both;
InternalChildren = new[]
{
dashTrails = new Container<CatcherTrailSprite> { RelativeSizeAxes = Axes.Both },
hyperDashTrails = new Container<CatcherTrailSprite> { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR },
endGlowSprites = new Container<CatcherTrailSprite> { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR },
};
}
/// <summary>
/// Displays a single end-glow catcher sprite.
/// </summary>
public void DisplayEndGlow()
{
var endGlow = createTrailSprite(endGlowSprites);
endGlow.MoveToOffset(new Vector2(0, -10), 1200, Easing.In);
endGlow.ScaleTo(endGlow.Scale * 0.95f).ScaleTo(endGlow.Scale * 1.2f, 1200, Easing.In);
endGlow.FadeOut(1200);
endGlow.Expire(true);
}
private void displayTrail()
{
if (!DisplayTrail)
return;
var sprite = createTrailSprite(catcher.HyperDashing ? hyperDashTrails : dashTrails);
sprite.FadeTo(0.4f).FadeOut(800, Easing.OutQuint);
sprite.Expire(true);
Scheduler.AddDelayed(displayTrail, catcher.HyperDashing ? 25 : 50);
}
private CatcherTrailSprite createTrailSprite(Container<CatcherTrailSprite> target)
{
var texture = (catcher.CurrentDrawableCatcher as TextureAnimation)?.CurrentFrame ?? ((Sprite)catcher.CurrentDrawableCatcher).Texture;
var sprite = new CatcherTrailSprite(texture)
{
Anchor = catcher.Anchor,
Scale = catcher.Scale,
Blending = BlendingParameters.Additive,
RelativePositionAxes = catcher.RelativePositionAxes,
Position = catcher.Position
};
target.Add(sprite);
return sprite;
}
}
}

View File

@ -7,20 +7,18 @@ 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.Timing; using osu.Framework.Timing;
using osu.Game.Rulesets.Mania.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests namespace osu.Game.Rulesets.Mania.Tests
{ {
[Cached(Type = typeof(IManiaHitObjectComposer))] public abstract class ManiaPlacementBlueprintTestScene : PlacementBlueprintTestScene
public abstract class ManiaPlacementBlueprintTestScene : PlacementBlueprintTestScene, IManiaHitObjectComposer
{ {
private readonly Column column; private readonly Column column;
@ -41,16 +39,20 @@ namespace osu.Game.Rulesets.Mania.Tests
AccentColour = Color4.OrangeRed, AccentColour = Color4.OrangeRed,
Clock = new FramedClock(new StopwatchClock()), // No scroll Clock = new FramedClock(new StopwatchClock()), // No scroll
}); });
}
AddStep("change direction", () => ((ScrollingTestContainer)HitObjectContainer).Flip()); protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint)
{
var time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position);
var pos = column.ScreenSpacePositionAtTime(time);
return new SnapResult(pos, time, column);
} }
protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both }; protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both };
protected override void AddHitObject(DrawableHitObject hitObject) => column.Add((DrawableManiaHitObject)hitObject); protected override void AddHitObject(DrawableHitObject hitObject) => column.Add((DrawableManiaHitObject)hitObject);
public Column ColumnAt(Vector2 screenSpacePosition) => column; public ManiaPlayfield Playfield => null;
public int TotalColumns => 1;
} }
} }

View File

@ -4,25 +4,20 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Rulesets.Mania.Edit;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests namespace osu.Game.Rulesets.Mania.Tests
{ {
[Cached(Type = typeof(IManiaHitObjectComposer))] public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene
public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene, IManiaHitObjectComposer
{ {
[Cached(Type = typeof(IAdjustableClock))] [Cached(Type = typeof(IAdjustableClock))]
private readonly IAdjustableClock clock = new StopwatchClock(); private readonly IAdjustableClock clock = new StopwatchClock();
private readonly Column column;
protected ManiaSelectionBlueprintTestScene() protected ManiaSelectionBlueprintTestScene()
{ {
Add(column = new Column(0) Add(new Column(0)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
@ -31,8 +26,6 @@ namespace osu.Game.Rulesets.Mania.Tests
}); });
} }
public Column ColumnAt(Vector2 screenSpacePosition) => column; public ManiaPlayfield Playfield => null;
public int TotalColumns => 1;
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 899 B

View File

@ -1,15 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Mania.Skinning;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Rulesets.UI.Scrolling.Algorithms; using osu.Game.Rulesets.UI.Scrolling.Algorithms;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
@ -27,13 +24,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
[Cached(Type = typeof(IScrollingInfo))] [Cached(Type = typeof(IScrollingInfo))]
private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo();
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(ManiaRuleset),
typeof(ManiaLegacySkinTransformer),
typeof(ManiaSettingsSubsection)
};
protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset(); protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset();
protected ManiaSkinnableTestScene() protected ManiaSkinnableTestScene()

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -15,12 +14,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{ {
public class TestSceneDrawableJudgement : ManiaSkinnableTestScene public class TestSceneDrawableJudgement : ManiaSkinnableTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(DrawableJudgement),
typeof(DrawableManiaJudgement)
};
public TestSceneDrawableJudgement() public TestSceneDrawableJudgement()
{ {
foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Skip(1)) foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Skip(1))

View File

@ -1,15 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -21,12 +18,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
[TestFixture] [TestFixture]
public class TestSceneHitExplosion : ManiaSkinnableTestScene public class TestSceneHitExplosion : ManiaSkinnableTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(DrawableNote),
typeof(DrawableManiaHitObject),
};
public TestSceneHitExplosion() public TestSceneHitExplosion()
{ {
int runcount = 0; int runcount = 0;

View File

@ -1,12 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Skinning;
using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK; using osuTK;
@ -15,12 +12,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{ {
public class TestSceneKeyArea : ManiaSkinnableTestScene public class TestSceneKeyArea : ManiaSkinnableTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(DefaultKeyArea),
typeof(LegacyKeyArea)
};
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {

View File

@ -1,12 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Skinning;
using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -14,12 +10,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
{ {
public class TestSceneStageBackground : ManiaSkinnableTestScene public class TestSceneStageBackground : ManiaSkinnableTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
{
typeof(DefaultStageBackground),
typeof(LegacyStageBackground),
}).ToList();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {

View File

@ -1,23 +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.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Skinning;
using osu.Game.Skinning; using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania.Tests.Skinning namespace osu.Game.Rulesets.Mania.Tests.Skinning
{ {
public class TestSceneStageForeground : ManiaSkinnableTestScene public class TestSceneStageForeground : ManiaSkinnableTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
{
typeof(LegacyStageForeground),
}).ToList();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {

View File

@ -12,7 +12,6 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
@ -24,15 +23,6 @@ namespace osu.Game.Rulesets.Mania.Tests
[TestFixture] [TestFixture]
public class TestSceneColumn : ManiaInputTestScene public class TestSceneColumn : ManiaInputTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Column),
typeof(ColumnBackground),
typeof(ColumnHitObjectArea),
typeof(DefaultKeyArea),
typeof(DefaultHitTarget)
};
[Cached(typeof(IReadOnlyList<Mod>))] [Cached(typeof(IReadOnlyList<Mod>))]
private IReadOnlyList<Mod> mods { get; set; } = Array.Empty<Mod>(); private IReadOnlyList<Mod> mods { get; set; } = Array.Empty<Mod>();

View File

@ -0,0 +1,124 @@
// 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 System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Framework.Timing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Edit;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Screens.Edit;
using osu.Game.Tests.Visual;
using osuTK;
namespace osu.Game.Rulesets.Mania.Tests
{
public class TestSceneManiaBeatSnapGrid : EditorClockTestScene
{
[Cached(typeof(IScrollingInfo))]
private ScrollingTestContainer.TestScrollingInfo scrollingInfo = new ScrollingTestContainer.TestScrollingInfo();
[Cached(typeof(EditorBeatmap))]
private EditorBeatmap editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition()));
private readonly ManiaBeatSnapGrid beatSnapGrid;
public TestSceneManiaBeatSnapGrid()
{
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 200 });
editorBeatmap.ControlPointInfo.Add(10000, new TimingControlPoint { BeatLength = 200 });
BeatDivisor.Value = 3;
// Some sane defaults
scrollingInfo.Algorithm.Algorithm = ScrollVisualisationMethod.Constant;
scrollingInfo.Direction.Value = ScrollingDirection.Up;
scrollingInfo.TimeRange.Value = 1000;
Children = new Drawable[]
{
Playfield = new ManiaPlayfield(new List<StageDefinition>
{
new StageDefinition { Columns = 4 },
new StageDefinition { Columns = 3 }
})
{
Clock = new FramedClock(new StopwatchClock())
},
new TestHitObjectComposer(Playfield)
{
Child = beatSnapGrid = new ManiaBeatSnapGrid()
}
};
}
protected override bool OnMouseMove(MouseMoveEvent e)
{
// We're providing a constant scroll algorithm.
float relativePosition = Playfield.Stages[0].HitObjectContainer.ToLocalSpace(e.ScreenSpaceMousePosition).Y / Playfield.Stages[0].HitObjectContainer.DrawHeight;
double timeValue = scrollingInfo.TimeRange.Value * relativePosition;
beatSnapGrid.SelectionTimeRange = (timeValue, timeValue);
return true;
}
public ManiaPlayfield Playfield { get; }
}
public class TestHitObjectComposer : HitObjectComposer
{
public override Playfield Playfield { get; }
public override IEnumerable<DrawableHitObject> HitObjects => Enumerable.Empty<DrawableHitObject>();
public override bool CursorInPlacementArea => false;
public TestHitObjectComposer(Playfield playfield)
{
Playfield = playfield;
}
public Drawable Child
{
set => InternalChild = value;
}
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
{
throw new System.NotImplementedException();
}
public override float GetBeatSnapDistanceAt(double referenceTime)
{
throw new System.NotImplementedException();
}
public override float DurationToDistance(double referenceTime, double duration)
{
throw new System.NotImplementedException();
}
public override double DistanceToDuration(double referenceTime, float distance)
{
throw new System.NotImplementedException();
}
public override double GetSnappedDurationFromDistance(double referenceTime, float distance)
{
throw new System.NotImplementedException();
}
public override float GetSnappedDistanceFromDistance(double referenceTime, float distance)
{
throw new System.NotImplementedException();
}
}
}

View File

@ -0,0 +1,218 @@
// 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.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Screens.Edit;
using osu.Game.Tests.Visual;
using osuTK;
using osuTK.Input;
namespace osu.Game.Rulesets.Mania.Tests
{
public class TestSceneManiaHitObjectComposer : EditorClockTestScene
{
private TestComposer composer;
[SetUp]
public void Setup() => Schedule(() =>
{
BeatDivisor.Value = 8;
Clock.Seek(0);
Child = composer = new TestComposer { RelativeSizeAxes = Axes.Both };
});
[Test]
public void TestDragOffscreenSelectionVerticallyUpScroll()
{
DrawableHitObject lastObject = null;
double originalTime = 0;
Vector2 originalPosition = Vector2.Zero;
setScrollStep(ScrollingDirection.Up);
AddStep("seek to last object", () =>
{
lastObject = this.ChildrenOfType<DrawableHitObject>().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last());
originalTime = lastObject.HitObject.StartTime;
Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime);
});
AddStep("select all objects", () => composer.EditorBeatmap.SelectedHitObjects.AddRange(composer.EditorBeatmap.HitObjects));
AddStep("click last object", () =>
{
originalPosition = lastObject.DrawPosition;
InputManager.MoveMouseTo(lastObject);
InputManager.PressButton(MouseButton.Left);
});
AddStep("move mouse downwards", () =>
{
InputManager.MoveMouseTo(lastObject, new Vector2(0, lastObject.ScreenSpaceDrawQuad.Height * 4));
InputManager.ReleaseButton(MouseButton.Left);
});
AddAssert("hitobjects not moved columns", () => composer.EditorBeatmap.HitObjects.All(h => ((ManiaHitObject)h).Column == 0));
AddAssert("hitobjects moved downwards", () => lastObject.DrawPosition.Y - originalPosition.Y > 0);
AddAssert("hitobject has moved time", () => lastObject.HitObject.StartTime == originalTime + 125);
}
[Test]
public void TestDragOffscreenSelectionVerticallyDownScroll()
{
DrawableHitObject lastObject = null;
double originalTime = 0;
Vector2 originalPosition = Vector2.Zero;
setScrollStep(ScrollingDirection.Down);
AddStep("seek to last object", () =>
{
lastObject = this.ChildrenOfType<DrawableHitObject>().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last());
originalTime = lastObject.HitObject.StartTime;
Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime);
});
AddStep("select all objects", () => composer.EditorBeatmap.SelectedHitObjects.AddRange(composer.EditorBeatmap.HitObjects));
AddStep("click last object", () =>
{
originalPosition = lastObject.DrawPosition;
InputManager.MoveMouseTo(lastObject);
InputManager.PressButton(MouseButton.Left);
});
AddStep("move mouse upwards", () =>
{
InputManager.MoveMouseTo(lastObject, new Vector2(0, -lastObject.ScreenSpaceDrawQuad.Height * 4));
InputManager.ReleaseButton(MouseButton.Left);
});
AddAssert("hitobjects not moved columns", () => composer.EditorBeatmap.HitObjects.All(h => ((ManiaHitObject)h).Column == 0));
AddAssert("hitobjects moved upwards", () => originalPosition.Y - lastObject.DrawPosition.Y > 0);
AddAssert("hitobject has moved time", () => lastObject.HitObject.StartTime == originalTime + 125);
}
[Test]
public void TestDragOffscreenSelectionHorizontally()
{
DrawableHitObject lastObject = null;
Vector2 originalPosition = Vector2.Zero;
setScrollStep(ScrollingDirection.Down);
AddStep("seek to last object", () =>
{
lastObject = this.ChildrenOfType<DrawableHitObject>().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last());
Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime);
});
AddStep("select all objects", () => composer.EditorBeatmap.SelectedHitObjects.AddRange(composer.EditorBeatmap.HitObjects));
AddStep("click last object", () =>
{
originalPosition = lastObject.DrawPosition;
InputManager.MoveMouseTo(lastObject);
InputManager.PressButton(MouseButton.Left);
});
AddStep("move mouse right", () =>
{
var firstColumn = composer.Composer.Playfield.GetColumn(0);
var secondColumn = composer.Composer.Playfield.GetColumn(1);
InputManager.MoveMouseTo(lastObject, new Vector2(secondColumn.ScreenSpaceDrawQuad.Centre.X - firstColumn.ScreenSpaceDrawQuad.Centre.X + 1, 0));
InputManager.ReleaseButton(MouseButton.Left);
});
AddAssert("hitobjects moved columns", () => composer.EditorBeatmap.HitObjects.All(h => ((ManiaHitObject)h).Column == 1));
// Todo: They'll move vertically by the height of a note since there's no snapping and the selection point is the middle of the note.
AddAssert("hitobjects not moved vertically", () => lastObject.DrawPosition.Y - originalPosition.Y <= DefaultNotePiece.NOTE_HEIGHT);
}
[Test]
public void TestDragHoldNoteSelectionVertically()
{
setScrollStep(ScrollingDirection.Down);
AddStep("setup beatmap", () =>
{
composer.EditorBeatmap.Clear();
composer.EditorBeatmap.Add(new HoldNote
{
Column = 1,
EndTime = 200
});
});
DrawableHoldNote holdNote = null;
AddStep("grab hold note", () =>
{
holdNote = this.ChildrenOfType<DrawableHoldNote>().Single();
InputManager.MoveMouseTo(holdNote);
InputManager.PressButton(MouseButton.Left);
});
AddStep("move drag upwards", () =>
{
InputManager.MoveMouseTo(holdNote, new Vector2(0, -100));
InputManager.ReleaseButton(MouseButton.Left);
});
AddAssert("head note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.BottomLeft, holdNote.Head.ScreenSpaceDrawQuad.BottomLeft));
AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.TopLeft, holdNote.Tail.ScreenSpaceDrawQuad.BottomLeft));
AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType<HoldNoteNoteSelectionBlueprint>().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition);
AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType<HoldNoteNoteSelectionBlueprint>().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition);
}
private void setScrollStep(ScrollingDirection direction)
=> AddStep($"set scroll direction = {direction}", () => ((Bindable<ScrollingDirection>)composer.Composer.ScrollingInfo.Direction).Value = direction);
private class TestComposer : CompositeDrawable
{
[Cached(typeof(EditorBeatmap))]
[Cached(typeof(IBeatSnapProvider))]
public readonly EditorBeatmap EditorBeatmap;
public readonly ManiaHitObjectComposer Composer;
public TestComposer()
{
InternalChildren = new Drawable[]
{
EditorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 }))
{
BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }
},
Composer = new ManiaHitObjectComposer(new ManiaRuleset())
};
for (int i = 0; i < 10; i++)
EditorBeatmap.Add(new Note { StartTime = 125 * i });
}
}
}
}

View File

@ -1,19 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests namespace osu.Game.Rulesets.Mania.Tests
{ {
public class TestSceneManiaPlayer : PlayerTestScene public class TestSceneManiaPlayer : PlayerTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(ManiaRuleset),
};
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
} }
} }

View File

@ -1,17 +1,59 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Testing;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual;
using osuTK;
using osuTK.Input;
namespace osu.Game.Rulesets.Mania.Tests namespace osu.Game.Rulesets.Mania.Tests
{ {
public class TestSceneNotePlacementBlueprint : ManiaPlacementBlueprintTestScene public class TestSceneNotePlacementBlueprint : ManiaPlacementBlueprintTestScene
{ {
[SetUp]
public void Setup() => Schedule(() =>
{
this.ChildrenOfType<HitObjectContainer>().ForEach(c => c.Clear());
ResetPlacement();
((ScrollingTestContainer)HitObjectContainer).Direction = ScrollingDirection.Down;
});
[Test]
public void TestPlaceBeforeCurrentTimeDownwards()
{
AddStep("move mouse before current time", () => InputManager.MoveMouseTo(this.ChildrenOfType<Column>().Single().ScreenSpaceDrawQuad.BottomLeft - new Vector2(0, 10)));
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddAssert("note start time < 0", () => getNote().StartTime < 0);
}
[Test]
public void TestPlaceAfterCurrentTimeDownwards()
{
AddStep("move mouse after current time", () => InputManager.MoveMouseTo(this.ChildrenOfType<Column>().Single()));
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddAssert("note start time > 0", () => getNote().StartTime > 0);
}
private Note getNote() => this.ChildrenOfType<DrawableNote>().FirstOrDefault()?.HitObject;
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject); protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject);
protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint(); protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint();
} }

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -30,12 +28,6 @@ namespace osu.Game.Rulesets.Mania.Tests
[TestFixture] [TestFixture]
public class TestSceneNotes : OsuTestScene public class TestSceneNotes : OsuTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(DrawableNote),
typeof(DrawableHoldNote)
};
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
@ -164,7 +156,7 @@ namespace osu.Game.Rulesets.Mania.Tests
foreach (var obj in content.OfType<DrawableHitObject>()) foreach (var obj in content.OfType<DrawableHitObject>())
{ {
if (!(obj.HitObject is IHasEndTime endTime)) if (!(obj.HitObject is IHasDuration endTime))
continue; continue;
foreach (var nested in obj.NestedHitObjects) foreach (var nested in obj.NestedHitObjects)

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" /> <Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" /> <PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -13,7 +13,6 @@ using osu.Game.Rulesets.Mania.Beatmaps.Patterns;
using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy; using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy;
using osuTK; using osuTK;
using osu.Game.Audio;
namespace osu.Game.Rulesets.Mania.Beatmaps namespace osu.Game.Rulesets.Mania.Beatmaps
{ {
@ -55,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
} }
else else
{ {
float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasEndTime) / beatmap.HitObjects.Count; float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasDuration) / beatmap.HitObjects.Count;
if (percentSliderOrSpinner < 0.2) if (percentSliderOrSpinner < 0.2)
TargetColumns = 7; TargetColumns = 7;
else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5) else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5)
@ -67,7 +66,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
} }
} }
public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition || h is ManiaHitObject); public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition);
protected override Beatmap<ManiaHitObject> ConvertBeatmap(IBeatmap original) protected override Beatmap<ManiaHitObject> ConvertBeatmap(IBeatmap original)
{ {
@ -176,7 +175,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
break; break;
} }
case IHasEndTime endTimeData: case IHasDuration endTimeData:
{ {
conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap); conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap);
@ -232,15 +231,15 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
var pattern = new Pattern(); var pattern = new Pattern();
if (HitObject is IHasEndTime endTimeData) if (HitObject is IHasDuration endTimeData)
{ {
pattern.Add(new HoldNote pattern.Add(new HoldNote
{ {
StartTime = HitObject.StartTime, StartTime = HitObject.StartTime,
Duration = endTimeData.Duration, Duration = endTimeData.Duration,
Column = column, Column = column,
Head = { Samples = sampleInfoListAt(HitObject.StartTime) }, Samples = HitObject.Samples,
Tail = { Samples = sampleInfoListAt(endTimeData.EndTime) }, NodeSamples = (HitObject as IHasRepeats)?.NodeSamples
}); });
} }
else if (HitObject is IHasXPosition) else if (HitObject is IHasXPosition)
@ -255,22 +254,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
return pattern; return pattern;
} }
/// <summary>
/// Retrieves the sample info list at a point in time.
/// </summary>
/// <param name="time">The time to retrieve the sample info list from.</param>
/// <returns></returns>
private IList<HitSampleInfo> sampleInfoListAt(double time)
{
if (!(HitObject is IHasCurve curveData))
return HitObject.Samples;
double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.SpanCount();
int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime);
return curveData.NodeSamples[index];
}
} }
} }
} }

View File

@ -474,7 +474,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// <returns></returns> /// <returns></returns>
private IList<HitSampleInfo> sampleInfoListAt(double time) private IList<HitSampleInfo> sampleInfoListAt(double time)
{ {
if (!(HitObject is IHasCurve curveData)) if (!(HitObject is IHasPathWithRepeats curveData))
return HitObject.Samples; return HitObject.Samples;
double segmentTime = (EndTime - HitObject.StartTime) / spanCount; double segmentTime = (EndTime - HitObject.StartTime) / spanCount;
@ -505,16 +505,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
} }
else else
{ {
var holdNote = new HoldNote newObject = new HoldNote
{ {
StartTime = startTime, StartTime = startTime,
Column = column,
Duration = endTime - startTime, Duration = endTime - startTime,
Head = { Samples = sampleInfoListAt(startTime) }, Column = column,
Tail = { Samples = sampleInfoListAt(endTime) } Samples = HitObject.Samples,
NodeSamples = (HitObject as IHasRepeats)?.NodeSamples
}; };
newObject = holdNote;
} }
pattern.Add(newObject); pattern.Add(newObject);

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, IBeatmap originalBeatmap) public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, IBeatmap originalBeatmap)
: base(random, hitObject, beatmap, new Pattern(), originalBeatmap) : base(random, hitObject, beatmap, new Pattern(), originalBeatmap)
{ {
endTime = (HitObject as IHasEndTime)?.EndTime ?? 0; endTime = (HitObject as IHasDuration)?.EndTime ?? 0;
} }
public override IEnumerable<Pattern> Generate() public override IEnumerable<Pattern> Generate()
@ -64,21 +64,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (holdNote) if (holdNote)
{ {
var hold = new HoldNote newObject = new HoldNote
{ {
StartTime = HitObject.StartTime, StartTime = HitObject.StartTime,
Duration = endTime - HitObject.StartTime,
Column = column, Column = column,
Duration = endTime - HitObject.StartTime Samples = HitObject.Samples,
NodeSamples = (HitObject as IHasRepeats)?.NodeSamples
}; };
if (hold.Head.Samples == null)
hold.Head.Samples = new List<HitSampleInfo>();
hold.Head.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_NORMAL });
hold.Tail.Samples = HitObject.Samples;
newObject = hold;
} }
else else
{ {

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Extensions;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -37,12 +38,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty
{ {
mods = Score.Mods; mods = Score.Mods;
scaledScore = Score.TotalScore; scaledScore = Score.TotalScore;
countPerfect = Score.Statistics[HitResult.Perfect]; countPerfect = Score.Statistics.GetOrDefault(HitResult.Perfect);
countGreat = Score.Statistics[HitResult.Great]; countGreat = Score.Statistics.GetOrDefault(HitResult.Great);
countGood = Score.Statistics[HitResult.Good]; countGood = Score.Statistics.GetOrDefault(HitResult.Good);
countOk = Score.Statistics[HitResult.Ok]; countOk = Score.Statistics.GetOrDefault(HitResult.Ok);
countMeh = Score.Statistics[HitResult.Meh]; countMeh = Score.Statistics.GetOrDefault(HitResult.Meh);
countMiss = Score.Statistics[HitResult.Miss]; countMiss = Score.Statistics.GetOrDefault(HitResult.Miss);
if (mods.Any(m => !m.Ranked)) if (mods.Any(m => !m.Ranked))
return 0; return 0;

View File

@ -2,10 +2,15 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK; using osuTK;
using osuTK.Input;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{ {
@ -15,6 +20,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
private readonly EditNotePiece headPiece; private readonly EditNotePiece headPiece;
private readonly EditNotePiece tailPiece; private readonly EditNotePiece tailPiece;
[Resolved]
private IScrollingInfo scrollingInfo { get; set; }
public HoldNotePlacementBlueprint() public HoldNotePlacementBlueprint()
: base(new HoldNote()) : base(new HoldNote())
{ {
@ -34,8 +42,21 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
if (Column != null) if (Column != null)
{ {
headPiece.Y = PositionAt(HitObject.StartTime); headPiece.Y = Parent.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.StartTime)).Y;
tailPiece.Y = PositionAt(HitObject.EndTime); tailPiece.Y = Parent.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.EndTime)).Y;
switch (scrollingInfo.Direction.Value)
{
case ScrollingDirection.Down:
headPiece.Y -= headPiece.DrawHeight / 2;
tailPiece.Y -= tailPiece.DrawHeight / 2;
break;
case ScrollingDirection.Up:
headPiece.Y += headPiece.DrawHeight / 2;
tailPiece.Y += tailPiece.DrawHeight / 2;
break;
}
} }
var topPosition = new Vector2(headPiece.DrawPosition.X, Math.Min(headPiece.DrawPosition.Y, tailPiece.DrawPosition.Y)); var topPosition = new Vector2(headPiece.DrawPosition.X, Math.Min(headPiece.DrawPosition.Y, tailPiece.DrawPosition.Y));
@ -46,25 +67,39 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
bodyPiece.Height = (bottomPosition - topPosition).Y; bodyPiece.Height = (bottomPosition - topPosition).Y;
} }
protected override void OnMouseUp(MouseUpEvent e)
{
if (e.Button != MouseButton.Left)
return;
base.OnMouseUp(e);
EndPlacement(true);
}
private double originalStartTime; private double originalStartTime;
public override void UpdatePosition(Vector2 screenSpacePosition) public override void UpdatePosition(SnapResult result)
{ {
base.UpdatePosition(screenSpacePosition); base.UpdatePosition(result);
if (PlacementActive) if (PlacementActive)
{ {
var endTime = TimeAt(screenSpacePosition); if (result.Time is double endTime)
{
HitObject.StartTime = endTime < originalStartTime ? endTime : originalStartTime; HitObject.StartTime = endTime < originalStartTime ? endTime : originalStartTime;
HitObject.Duration = Math.Abs(endTime - originalStartTime); HitObject.Duration = Math.Abs(endTime - originalStartTime);
} }
}
else else
{ {
headPiece.Width = tailPiece.Width = SnappedWidth; if (result.Playfield != null)
headPiece.X = tailPiece.X = SnappedMousePosition.X; {
headPiece.Width = tailPiece.Width = result.Playfield.DrawWidth;
headPiece.X = tailPiece.X = ToLocalSpace(result.ScreenSpacePosition).X;
}
originalStartTime = HitObject.StartTime = TimeAt(screenSpacePosition); if (result.Time is double startTime)
originalStartTime = HitObject.StartTime = startTime;
} }
} }
} }

View File

@ -76,5 +76,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
} }
public override Quad SelectionQuad => ScreenSpaceDrawQuad; public override Quad SelectionQuad => ScreenSpaceDrawQuad;
public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.Head.ScreenSpaceDrawQuad.Centre;
} }
} }

View File

@ -1,42 +1,34 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling; using osuTK.Input;
using osuTK;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{ {
public abstract class ManiaPlacementBlueprint<T> : PlacementBlueprint, public abstract class ManiaPlacementBlueprint<T> : PlacementBlueprint
IRequireHighFrequencyMousePosition // the playfield could be moving behind us
where T : ManiaHitObject where T : ManiaHitObject
{ {
protected new T HitObject => (T)base.HitObject; protected new T HitObject => (T)base.HitObject;
protected Column Column; private Column column;
/// <summary> public Column Column
/// The current mouse position, snapped to the closest column. {
/// </summary> get => column;
protected Vector2 SnappedMousePosition { get; private set; } set
{
if (value == column)
return;
/// <summary> column = value;
/// The width of the closest column to the current mouse position. HitObject.Column = column.Index;
/// </summary> }
protected float SnappedWidth { get; private set; } }
[Resolved]
private IManiaHitObjectComposer composer { get; set; }
[Resolved]
private IScrollingInfo scrollingInfo { get; set; }
protected ManiaPlacementBlueprint(T hitObject) protected ManiaPlacementBlueprint(T hitObject)
: base(hitObject) : base(hitObject)
@ -46,112 +38,22 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
protected override bool OnMouseDown(MouseDownEvent e) protected override bool OnMouseDown(MouseDownEvent e)
{ {
if (Column == null) if (e.Button != MouseButton.Left)
return base.OnMouseDown(e); return false;
HitObject.Column = Column.Index; if (Column == null)
BeginPlacement(TimeAt(e.ScreenSpaceMousePosition)); return false;
BeginPlacement(true);
return true; return true;
} }
protected override void OnMouseUp(MouseUpEvent e) public override void UpdatePosition(SnapResult result)
{ {
EndPlacement(true); base.UpdatePosition(result);
base.OnMouseUp(e);
}
public override void UpdatePosition(Vector2 screenSpacePosition)
{
if (!PlacementActive) if (!PlacementActive)
Column = ColumnAt(screenSpacePosition); Column = result.Playfield as Column;
if (Column == null) return;
SnappedWidth = Column.DrawWidth;
// Snap to the column
var parentPos = Parent.ToLocalSpace(Column.ToScreenSpace(new Vector2(Column.DrawWidth / 2, 0)));
SnappedMousePosition = new Vector2(parentPos.X, Parent.ToLocalSpace(screenSpacePosition).Y);
}
protected double TimeAt(Vector2 screenSpacePosition)
{
if (Column == null)
return 0;
var hitObjectContainer = Column.HitObjectContainer;
// If we're scrolling downwards, a position of 0 is actually further away from the hit target
// so we need to flip the vertical coordinate in the hitobject container's space
var hitObjectPos = mouseToHitObjectPosition(Column.HitObjectContainer.ToLocalSpace(screenSpacePosition)).Y;
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
hitObjectPos = hitObjectContainer.DrawHeight - hitObjectPos;
return scrollingInfo.Algorithm.TimeAt(hitObjectPos,
EditorClock.CurrentTime,
scrollingInfo.TimeRange.Value,
hitObjectContainer.DrawHeight);
}
protected float PositionAt(double time)
{
var pos = scrollingInfo.Algorithm.PositionAt(time,
EditorClock.CurrentTime,
scrollingInfo.TimeRange.Value,
Column.HitObjectContainer.DrawHeight);
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
pos = Column.HitObjectContainer.DrawHeight - pos;
return hitObjectToMousePosition(Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent)).Y;
}
protected Column ColumnAt(Vector2 screenSpacePosition)
=> composer.ColumnAt(screenSpacePosition);
/// <summary>
/// Converts a mouse position to a hitobject position.
/// </summary>
/// <remarks>
/// Blueprints are centred on the mouse position, such that the hitobject position is anchored at the top or bottom of the blueprint depending on the scroll direction.
/// </remarks>
/// <param name="mousePosition">The mouse position.</param>
/// <returns>The resulting hitobject position, acnhored at the top or bottom of the blueprint depending on the scroll direction.</returns>
private Vector2 mouseToHitObjectPosition(Vector2 mousePosition)
{
switch (scrollingInfo.Direction.Value)
{
case ScrollingDirection.Up:
mousePosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2;
break;
case ScrollingDirection.Down:
mousePosition.Y += DefaultNotePiece.NOTE_HEIGHT / 2;
break;
}
return mousePosition;
}
/// <summary>
/// Converts a hitobject position to a mouse position.
/// </summary>
/// <param name="hitObjectPosition">The hitobject position.</param>
/// <returns>The resulting mouse position, anchored at the centre of the hitobject.</returns>
private Vector2 hitObjectToMousePosition(Vector2 hitObjectPosition)
{
switch (scrollingInfo.Direction.Value)
{
case ScrollingDirection.Up:
hitObjectPosition.Y += DefaultNotePiece.NOTE_HEIGHT / 2;
break;
case ScrollingDirection.Down:
hitObjectPosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2;
break;
}
return hitObjectPosition;
} }
} }
} }

View File

@ -3,8 +3,6 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Framework.Timing;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
@ -13,33 +11,19 @@ using osuTK;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{ {
public class ManiaSelectionBlueprint : OverlaySelectionBlueprint public abstract class ManiaSelectionBlueprint : OverlaySelectionBlueprint
{ {
public Vector2 ScreenSpaceDragPosition { get; private set; }
public Vector2 DragPosition { get; private set; }
public new DrawableManiaHitObject DrawableObject => (DrawableManiaHitObject)base.DrawableObject; public new DrawableManiaHitObject DrawableObject => (DrawableManiaHitObject)base.DrawableObject;
protected IClock EditorClock { get; private set; }
[Resolved] [Resolved]
private IScrollingInfo scrollingInfo { get; set; } private IScrollingInfo scrollingInfo { get; set; }
[Resolved] protected ManiaSelectionBlueprint(DrawableHitObject drawableObject)
private IManiaHitObjectComposer composer { get; set; }
public ManiaSelectionBlueprint(DrawableHitObject drawableObject)
: base(drawableObject) : base(drawableObject)
{ {
RelativeSizeAxes = Axes.None; RelativeSizeAxes = Axes.None;
} }
[BackgroundDependencyLoader]
private void load(IAdjustableClock clock)
{
EditorClock = clock;
}
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
@ -47,22 +31,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
Position = Parent.ToLocalSpace(DrawableObject.ToScreenSpace(Vector2.Zero)); Position = Parent.ToLocalSpace(DrawableObject.ToScreenSpace(Vector2.Zero));
} }
protected override bool OnMouseDown(MouseDownEvent e)
{
ScreenSpaceDragPosition = e.ScreenSpaceMousePosition;
DragPosition = DrawableObject.ToLocalSpace(e.ScreenSpaceMousePosition);
return base.OnMouseDown(e);
}
protected override void OnDrag(DragEvent e)
{
base.OnDrag(e);
ScreenSpaceDragPosition = e.ScreenSpaceMousePosition;
DragPosition = DrawableObject.ToLocalSpace(e.ScreenSpaceMousePosition);
}
public override void Show() public override void Show()
{ {
DrawableObject.AlwaysAlive = true; DrawableObject.AlwaysAlive = true;

View File

@ -2,29 +2,48 @@
// 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.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osuTK.Input;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{ {
public class NotePlacementBlueprint : ManiaPlacementBlueprint<Note> public class NotePlacementBlueprint : ManiaPlacementBlueprint<Note>
{ {
private readonly EditNotePiece piece;
public NotePlacementBlueprint() public NotePlacementBlueprint()
: base(new Note()) : base(new Note())
{ {
Origin = Anchor.Centre; RelativeSizeAxes = Axes.Both;
AutoSizeAxes = Axes.Y; InternalChild = piece = new EditNotePiece { Origin = Anchor.Centre };
InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X };
} }
protected override void Update() public override void UpdatePosition(SnapResult result)
{ {
base.Update(); base.UpdatePosition(result);
Width = SnappedWidth; if (result.Playfield != null)
Position = SnappedMousePosition; {
piece.Width = result.Playfield.DrawWidth;
piece.Position = ToLocalSpace(result.ScreenSpacePosition);
}
}
protected override bool OnMouseDown(MouseDownEvent e)
{
if (e.Button != MouseButton.Left)
return false;
base.OnMouseDown(e);
// Place the note immediately.
EndPlacement(true);
return true;
} }
} }
} }

View File

@ -1,15 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Mania.UI;
using osuTK;
namespace osu.Game.Rulesets.Mania.Edit
{
public interface IManiaHitObjectComposer
{
Column ColumnAt(Vector2 screenSpacePosition);
int TotalColumns { get; }
}
}

View File

@ -0,0 +1,215 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Caching;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Screens.Edit;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Edit
{
/// <summary>
/// A grid which displays coloured beat divisor lines in proximity to the selection or placement cursor.
/// </summary>
public class ManiaBeatSnapGrid : Component
{
private const double visible_range = 750;
/// <summary>
/// The range of time values of the current selection.
/// </summary>
public (double start, double end)? SelectionTimeRange
{
set
{
if (value == selectionTimeRange)
return;
selectionTimeRange = value;
lineCache.Invalidate();
}
}
[Resolved]
private EditorBeatmap beatmap { get; set; }
[Resolved]
private IScrollingInfo scrollingInfo { get; set; }
[Resolved]
private Bindable<WorkingBeatmap> working { get; set; }
[Resolved]
private OsuColour colours { get; set; }
[Resolved]
private BindableBeatDivisor beatDivisor { get; set; }
private readonly List<ScrollingHitObjectContainer> grids = new List<ScrollingHitObjectContainer>();
private readonly Cached lineCache = new Cached();
private (double start, double end)? selectionTimeRange;
[BackgroundDependencyLoader]
private void load(HitObjectComposer composer)
{
foreach (var stage in ((ManiaPlayfield)composer.Playfield).Stages)
{
foreach (var column in stage.Columns)
{
var lineContainer = new ScrollingHitObjectContainer();
grids.Add(lineContainer);
column.UnderlayElements.Add(lineContainer);
}
}
beatDivisor.BindValueChanged(_ => createLines(), true);
}
protected override void Update()
{
base.Update();
if (!lineCache.IsValid)
{
lineCache.Validate();
createLines();
}
}
private readonly Stack<DrawableGridLine> availableLines = new Stack<DrawableGridLine>();
private void createLines()
{
foreach (var grid in grids)
{
foreach (var line in grid.Objects.OfType<DrawableGridLine>())
availableLines.Push(line);
grid.Clear(false);
}
if (selectionTimeRange == null)
return;
var range = selectionTimeRange.Value;
var timingPoint = beatmap.ControlPointInfo.TimingPointAt(range.start - visible_range);
double time = timingPoint.Time;
int beat = 0;
// progress time until in the visible range.
while (time < range.start - visible_range)
{
time += timingPoint.BeatLength / beatDivisor.Value;
beat++;
}
while (time < range.end + visible_range)
{
var nextTimingPoint = beatmap.ControlPointInfo.TimingPointAt(time);
// switch to the next timing point if we have reached it.
if (nextTimingPoint.Time > timingPoint.Time)
{
beat = 0;
time = nextTimingPoint.Time;
timingPoint = nextTimingPoint;
}
Color4 colour = BindableBeatDivisor.GetColourFor(
BindableBeatDivisor.GetDivisorForBeatIndex(Math.Max(1, beat), beatDivisor.Value), colours);
foreach (var grid in grids)
{
if (!availableLines.TryPop(out var line))
line = new DrawableGridLine();
line.HitObject.StartTime = time;
line.Colour = colour;
grid.Add(line);
}
beat++;
time += timingPoint.BeatLength / beatDivisor.Value;
}
foreach (var grid in grids)
{
// required to update ScrollingHitObjectContainer's cache.
grid.UpdateSubTree();
foreach (var line in grid.Objects.OfType<DrawableGridLine>())
{
time = line.HitObject.StartTime;
if (time >= range.start && time <= range.end)
line.Alpha = 1;
else
{
double timeSeparation = time < range.start ? range.start - time : time - range.end;
line.Alpha = (float)Math.Max(0, 1 - timeSeparation / visible_range);
}
}
}
}
private class DrawableGridLine : DrawableHitObject
{
[Resolved]
private IScrollingInfo scrollingInfo { get; set; }
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
public DrawableGridLine()
: base(new HitObject())
{
RelativeSizeAxes = Axes.X;
Height = 2;
AddInternal(new Box { RelativeSizeAxes = Axes.Both });
}
[BackgroundDependencyLoader]
private void load()
{
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
}
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
{
Origin = Anchor = direction.NewValue == ScrollingDirection.Up
? Anchor.TopLeft
: Anchor.BottomLeft;
}
protected override void UpdateInitialTransforms()
{
// don't perform any fading we are handling that ourselves.
}
protected override void UpdateStateTransforms(ArmedState state)
{
LifetimeEnd = HitObject.StartTime + visible_range;
}
}
}
}

View File

@ -30,5 +30,7 @@ namespace osu.Game.Rulesets.Mania.Edit
return base.CreateBlueprintFor(hitObject); return base.CreateBlueprintFor(hitObject);
} }
protected override SelectionHandler CreateSelectionHandler() => new ManiaSelectionHandler();
} }
} }

View File

@ -6,38 +6,78 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Input;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Mania.Edit namespace osu.Game.Rulesets.Mania.Edit
{ {
[Cached(Type = typeof(IManiaHitObjectComposer))] public class ManiaHitObjectComposer : HitObjectComposer<ManiaHitObject>
public class ManiaHitObjectComposer : HitObjectComposer<ManiaHitObject>, IManiaHitObjectComposer
{ {
private DrawableManiaEditRuleset drawableRuleset; private DrawableManiaEditRuleset drawableRuleset;
private ManiaBeatSnapGrid beatSnapGrid;
private InputManager inputManager;
public ManiaHitObjectComposer(Ruleset ruleset) public ManiaHitObjectComposer(Ruleset ruleset)
: base(ruleset) : base(ruleset)
{ {
} }
/// <summary> [BackgroundDependencyLoader]
/// Retrieves the column that intersects a screen-space position. private void load()
/// </summary> {
/// <param name="screenSpacePosition">The screen-space position.</param> AddInternal(beatSnapGrid = new ManiaBeatSnapGrid());
/// <returns>The column which intersects with <paramref name="screenSpacePosition"/>.</returns> }
public Column ColumnAt(Vector2 screenSpacePosition) => drawableRuleset.GetColumnByPosition(screenSpacePosition);
protected override void LoadComplete()
{
base.LoadComplete();
inputManager = GetContainingInputManager();
}
private DependencyContainer dependencies; private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); => dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
public int TotalColumns => ((ManiaPlayfield)drawableRuleset.Playfield).TotalColumns; public new ManiaPlayfield Playfield => ((ManiaPlayfield)drawableRuleset.Playfield);
public IScrollingInfo ScrollingInfo => drawableRuleset.ScrollingInfo;
protected override Playfield PlayfieldAtScreenSpacePosition(Vector2 screenSpacePosition) =>
Playfield.GetColumnByPosition(screenSpacePosition);
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
{
var result = base.SnapScreenSpacePositionToValidTime(screenSpacePosition);
switch (ScrollingInfo.Direction.Value)
{
case ScrollingDirection.Down:
result.ScreenSpacePosition -= new Vector2(0, getNoteHeight() / 2);
break;
case ScrollingDirection.Up:
result.ScreenSpacePosition += new Vector2(0, getNoteHeight() / 2);
break;
}
return result;
}
private float getNoteHeight() =>
Playfield.GetColumn(0).ToScreenSpace(new Vector2(DefaultNotePiece.NOTE_HEIGHT)).Y -
Playfield.GetColumn(0).ToScreenSpace(Vector2.Zero).Y;
protected override DrawableRuleset<ManiaHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null) protected override DrawableRuleset<ManiaHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
{ {
@ -49,12 +89,36 @@ namespace osu.Game.Rulesets.Mania.Edit
return drawableRuleset; return drawableRuleset;
} }
protected override ComposeBlueprintContainer CreateBlueprintContainer() => new ManiaBlueprintContainer(drawableRuleset.Playfield.AllHitObjects); protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable<DrawableHitObject> hitObjects)
=> new ManiaBlueprintContainer(hitObjects);
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[] protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[]
{ {
new NoteCompositionTool(), new NoteCompositionTool(),
new HoldNoteCompositionTool() new HoldNoteCompositionTool()
}; };
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
if (BlueprintContainer.CurrentTool is SelectTool)
{
if (EditorBeatmap.SelectedHitObjects.Any())
{
beatSnapGrid.SelectionTimeRange = (EditorBeatmap.SelectedHitObjects.Min(h => h.StartTime), EditorBeatmap.SelectedHitObjects.Max(h => h.GetEndTime()));
}
else
beatSnapGrid.SelectionTimeRange = null;
}
else
{
var result = SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position);
if (result.Time is double time)
beatSnapGrid.SelectionTimeRange = (time, time);
else
beatSnapGrid.SelectionTimeRange = null;
}
}
} }
} }

View File

@ -4,11 +4,9 @@
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Timing;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
@ -20,90 +18,23 @@ namespace osu.Game.Rulesets.Mania.Edit
private IScrollingInfo scrollingInfo { get; set; } private IScrollingInfo scrollingInfo { get; set; }
[Resolved] [Resolved]
private IManiaHitObjectComposer composer { get; set; } private HitObjectComposer composer { get; set; }
private IClock editorClock;
[BackgroundDependencyLoader]
private void load(IAdjustableClock clock)
{
editorClock = clock;
}
public override bool HandleMovement(MoveSelectionEvent moveEvent) public override bool HandleMovement(MoveSelectionEvent moveEvent)
{ {
var maniaBlueprint = (ManiaSelectionBlueprint)moveEvent.Blueprint; var maniaBlueprint = (ManiaSelectionBlueprint)moveEvent.Blueprint;
int lastColumn = maniaBlueprint.DrawableObject.HitObject.Column; int lastColumn = maniaBlueprint.DrawableObject.HitObject.Column;
adjustOrigins(maniaBlueprint);
performDragMovement(moveEvent);
performColumnMovement(lastColumn, moveEvent); performColumnMovement(lastColumn, moveEvent);
return true; return true;
} }
/// <summary>
/// Ensures that the position of hitobjects remains centred to the mouse position.
/// E.g. The hitobject position will change if the editor scrolls while a hitobject is dragged.
/// </summary>
/// <param name="reference">The <see cref="ManiaSelectionBlueprint"/> that received the drag event.</param>
private void adjustOrigins(ManiaSelectionBlueprint reference)
{
var referenceParent = (HitObjectContainer)reference.DrawableObject.Parent;
float offsetFromReferenceOrigin = reference.DragPosition.Y - reference.DrawableObject.OriginPosition.Y;
float targetPosition = referenceParent.ToLocalSpace(reference.ScreenSpaceDragPosition).Y - offsetFromReferenceOrigin;
// Flip the vertical coordinate space when scrolling downwards
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
targetPosition -= referenceParent.DrawHeight;
float movementDelta = targetPosition - reference.DrawableObject.Position.Y;
foreach (var b in SelectedBlueprints.OfType<ManiaSelectionBlueprint>())
b.DrawableObject.Y += movementDelta;
}
private void performDragMovement(MoveSelectionEvent moveEvent)
{
float delta = moveEvent.InstantDelta.Y;
// When scrolling downwards the anchor position is at the bottom of the screen, however the movement event assumes the anchor is at the top of the screen.
// This causes the delta to assume a positive hitobject position, and which can be corrected for by subtracting the parent height.
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
delta -= moveEvent.Blueprint.Parent.DrawHeight; // todo: probably wrong
foreach (var selectionBlueprint in SelectedBlueprints)
{
var b = (OverlaySelectionBlueprint)selectionBlueprint;
var hitObject = b.DrawableObject;
var objectParent = (HitObjectContainer)hitObject.Parent;
// StartTime could be used to adjust the position if only one movement event was received per frame.
// However this is not the case and ScrollingHitObjectContainer performs movement in UpdateAfterChildren() so the position must also be updated to be valid for further movement events
hitObject.Y += delta;
float targetPosition = hitObject.Position.Y;
// The scrolling algorithm always assumes an anchor at the top of the screen, so the position must be flipped when scrolling downwards to reflect a top anchor
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
targetPosition = -targetPosition;
objectParent.Remove(hitObject);
hitObject.HitObject.StartTime = scrollingInfo.Algorithm.TimeAt(targetPosition,
editorClock.CurrentTime,
scrollingInfo.TimeRange.Value,
objectParent.DrawHeight);
objectParent.Add(hitObject);
}
}
private void performColumnMovement(int lastColumn, MoveSelectionEvent moveEvent) private void performColumnMovement(int lastColumn, MoveSelectionEvent moveEvent)
{ {
var currentColumn = composer.ColumnAt(moveEvent.ScreenSpacePosition); var maniaPlayfield = ((ManiaHitObjectComposer)composer).Playfield;
var currentColumn = maniaPlayfield.GetColumnByPosition(moveEvent.ScreenSpacePosition);
if (currentColumn == null) if (currentColumn == null)
return; return;
@ -122,7 +53,7 @@ namespace osu.Game.Rulesets.Mania.Edit
maxColumn = obj.Column; maxColumn = obj.Column;
} }
columnDelta = Math.Clamp(columnDelta, -minColumn, composer.TotalColumns - 1 - maxColumn); columnDelta = Math.Clamp(columnDelta, -minColumn, maniaPlayfield.TotalColumns - 1 - maxColumn);
foreach (var obj in SelectedHitObjects.OfType<ManiaHitObject>()) foreach (var obj in SelectedHitObjects.OfType<ManiaHitObject>())
obj.Column += columnDelta; obj.Column += columnDelta;

View File

@ -1,18 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Edit.Masks
{
public abstract class ManiaSelectionBlueprint : OverlaySelectionBlueprint
{
protected ManiaSelectionBlueprint(DrawableHitObject drawableObject)
: base(drawableObject)
{
RelativeSizeAxes = Axes.None;
}
}
}

View File

@ -51,7 +51,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
AddRangeInternal(new[] AddRangeInternal(new[]
{ {
bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece()) bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece
{
RelativeSizeAxes = Axes.Both
})
{ {
RelativeSizeAxes = Axes.X RelativeSizeAxes = Axes.X
}, },
@ -127,6 +130,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
bodyPiece.Anchor = bodyPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; bodyPiece.Anchor = bodyPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
} }
public override void PlaySamples()
{
// Samples are played by the head/tail notes.
}
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();

View File

@ -13,11 +13,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
public abstract class DrawableManiaHitObject : DrawableHitObject<ManiaHitObject> public abstract class DrawableManiaHitObject : DrawableHitObject<ManiaHitObject>
{ {
/// <summary>
/// Whether this <see cref="DrawableManiaHitObject"/> should always remain alive.
/// </summary>
internal bool AlwaysAlive;
/// <summary> /// <summary>
/// The <see cref="ManiaAction"/> which causes this <see cref="DrawableManiaHitObject{TObject}"/> to be hit. /// The <see cref="ManiaAction"/> which causes this <see cref="DrawableManiaHitObject{TObject}"/> to be hit.
/// </summary> /// </summary>
@ -54,7 +49,62 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
Direction.BindValueChanged(OnDirectionChanged, true); Direction.BindValueChanged(OnDirectionChanged, true);
} }
protected override bool ShouldBeAlive => AlwaysAlive || base.ShouldBeAlive; private double computedLifetimeStart;
public override double LifetimeStart
{
get => base.LifetimeStart;
set
{
computedLifetimeStart = value;
if (!AlwaysAlive)
base.LifetimeStart = value;
}
}
private double computedLifetimeEnd;
public override double LifetimeEnd
{
get => base.LifetimeEnd;
set
{
computedLifetimeEnd = value;
if (!AlwaysAlive)
base.LifetimeEnd = value;
}
}
private bool alwaysAlive;
/// <summary>
/// Whether this <see cref="DrawableManiaHitObject"/> should always remain alive.
/// </summary>
internal bool AlwaysAlive
{
get => alwaysAlive;
set
{
if (alwaysAlive == value)
return;
alwaysAlive = value;
if (value)
{
// Set the base lifetimes directly, to avoid mangling the computed lifetimes
base.LifetimeStart = double.MinValue;
base.LifetimeEnd = double.MaxValue;
}
else
{
LifetimeStart = computedLifetimeStart;
LifetimeEnd = computedLifetimeEnd;
}
}
}
protected virtual void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e) protected virtual void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
{ {

View File

@ -34,7 +34,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
public DefaultBodyPiece() public DefaultBodyPiece()
{ {
RelativeSizeAxes = Axes.Both;
Blending = BlendingParameters.Additive; Blending = BlendingParameters.Additive;
AddLayout(subtractionCache); AddLayout(subtractionCache);

View File

@ -1,6 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Threading;
using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
@ -12,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Objects
/// <summary> /// <summary>
/// Represents a hit object which requires pressing, holding, and releasing a key. /// Represents a hit object which requires pressing, holding, and releasing a key.
/// </summary> /// </summary>
public class HoldNote : ManiaHitObject, IHasEndTime public class HoldNote : ManiaHitObject, IHasDuration
{ {
public double EndTime public double EndTime
{ {
@ -28,6 +31,8 @@ namespace osu.Game.Rulesets.Mania.Objects
set set
{ {
duration = value; duration = value;
if (Tail != null)
Tail.StartTime = EndTime; Tail.StartTime = EndTime;
} }
} }
@ -38,7 +43,11 @@ namespace osu.Game.Rulesets.Mania.Objects
set set
{ {
base.StartTime = value; base.StartTime = value;
if (Head != null)
Head.StartTime = value; Head.StartTime = value;
if (Tail != null)
Tail.StartTime = EndTime; Tail.StartTime = EndTime;
} }
} }
@ -49,20 +58,26 @@ namespace osu.Game.Rulesets.Mania.Objects
set set
{ {
base.Column = value; base.Column = value;
if (Head != null)
Head.Column = value; Head.Column = value;
if (Tail != null)
Tail.Column = value; Tail.Column = value;
} }
} }
public List<IList<HitSampleInfo>> NodeSamples { get; set; }
/// <summary> /// <summary>
/// The head note of the hold. /// The head note of the hold.
/// </summary> /// </summary>
public readonly Note Head = new Note(); public Note Head { get; private set; }
/// <summary> /// <summary>
/// The tail note of the hold. /// The tail note of the hold.
/// </summary> /// </summary>
public readonly TailNote Tail = new TailNote(); public TailNote Tail { get; private set; }
/// <summary> /// <summary>
/// The time between ticks of this hold. /// The time between ticks of this hold.
@ -77,23 +92,36 @@ namespace osu.Game.Rulesets.Mania.Objects
tickSpacing = timingPoint.BeatLength / difficulty.SliderTickRate; tickSpacing = timingPoint.BeatLength / difficulty.SliderTickRate;
} }
protected override void CreateNestedHitObjects() protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
{ {
base.CreateNestedHitObjects(); base.CreateNestedHitObjects(cancellationToken);
createTicks(); createTicks(cancellationToken);
AddNested(Head); AddNested(Head = new Note
AddNested(Tail); {
StartTime = StartTime,
Column = Column,
Samples = getNodeSamples(0),
});
AddNested(Tail = new TailNote
{
StartTime = EndTime,
Column = Column,
Samples = getNodeSamples((NodeSamples?.Count - 1) ?? 1),
});
} }
private void createTicks() private void createTicks(CancellationToken cancellationToken)
{ {
if (tickSpacing == 0) if (tickSpacing == 0)
return; return;
for (double t = StartTime + tickSpacing; t <= EndTime - tickSpacing; t += tickSpacing) for (double t = StartTime + tickSpacing; t <= EndTime - tickSpacing; t += tickSpacing)
{ {
cancellationToken.ThrowIfCancellationRequested();
AddNested(new HoldNoteTick AddNested(new HoldNoteTick
{ {
StartTime = t, StartTime = t,
@ -105,5 +133,8 @@ namespace osu.Game.Rulesets.Mania.Objects
public override Judgement CreateJudgement() => new IgnoreJudgement(); public override Judgement CreateJudgement() => new IgnoreJudgement();
protected override HitWindows CreateHitWindows() => HitWindows.Empty; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
private IList<HitSampleInfo> getNodeSamples(int nodeIndex) =>
nodeIndex < NodeSamples?.Count ? NodeSamples[nodeIndex] : Samples;
} }
} }

View File

@ -5,11 +5,12 @@ using osu.Framework.Bindables;
using osu.Game.Rulesets.Mania.Objects.Types; using osu.Game.Rulesets.Mania.Objects.Types;
using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects namespace osu.Game.Rulesets.Mania.Objects
{ {
public abstract class ManiaHitObject : HitObject, IHasColumn public abstract class ManiaHitObject : HitObject, IHasColumn, IHasXPosition
{ {
public readonly Bindable<int> ColumnBindable = new Bindable<int>(); public readonly Bindable<int> ColumnBindable = new Bindable<int>();
@ -20,5 +21,11 @@ namespace osu.Game.Rulesets.Mania.Objects
} }
protected override HitWindows CreateHitWindows() => new ManiaHitWindows(); protected override HitWindows CreateHitWindows() => new ManiaHitWindows();
#region LegacyBeatmapEncoder
float IHasXPosition.X => Column;
#endregion
} }
} }

View File

@ -7,5 +7,20 @@ namespace osu.Game.Rulesets.Mania.Scoring
{ {
public class ManiaHitWindows : HitWindows public class ManiaHitWindows : HitWindows
{ {
public override bool IsHitResultAllowed(HitResult result)
{
switch (result)
{
case HitResult.Perfect:
case HitResult.Great:
case HitResult.Good:
case HitResult.Ok:
case HitResult.Meh:
case HitResult.Miss:
return true;
}
return false;
}
} }
} }

View File

@ -52,10 +52,10 @@ namespace osu.Game.Rulesets.Mania.Skinning
base.Update(); base.Update();
if (leftSprite?.Height > 0) if (leftSprite?.Height > 0)
leftSprite.Scale = new Vector2(DrawHeight / leftSprite.Height); leftSprite.Scale = new Vector2(1, DrawHeight / leftSprite.Height);
if (rightSprite?.Height > 0) if (rightSprite?.Height > 0)
rightSprite.Scale = new Vector2(DrawHeight / rightSprite.Height); rightSprite.Scale = new Vector2(1, DrawHeight / rightSprite.Height);
} }
} }
} }

View File

@ -37,6 +37,8 @@ namespace osu.Game.Rulesets.Mania.UI
internal readonly Container TopLevelContainer; internal readonly Container TopLevelContainer;
public Container UnderlayElements => hitObjectArea.UnderlayElements;
public Column(int index) public Column(int index)
{ {
Index = index; Index = index;

View File

@ -12,6 +12,9 @@ namespace osu.Game.Rulesets.Mania.UI.Components
public class ColumnHitObjectArea : HitObjectArea public class ColumnHitObjectArea : HitObjectArea
{ {
public readonly Container Explosions; public readonly Container Explosions;
public readonly Container UnderlayElements;
private readonly Drawable hitTarget; private readonly Drawable hitTarget;
public ColumnHitObjectArea(int columnIndex, HitObjectContainer hitObjectContainer) public ColumnHitObjectArea(int columnIndex, HitObjectContainer hitObjectContainer)
@ -19,6 +22,11 @@ namespace osu.Game.Rulesets.Mania.UI.Components
{ {
AddRangeInternal(new[] AddRangeInternal(new[]
{ {
UnderlayElements = new Container
{
RelativeSizeAxes = Axes.Both,
Depth = 2,
},
hitTarget = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitTarget, columnIndex), _ => new DefaultHitTarget()) hitTarget = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitTarget, columnIndex), _ => new DefaultHitTarget())
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,

View File

@ -2,7 +2,9 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -21,7 +23,6 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
namespace osu.Game.Rulesets.Mania.UI namespace osu.Game.Rulesets.Mania.UI
{ {
@ -48,6 +49,10 @@ namespace osu.Game.Rulesets.Mania.UI
protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config; protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config;
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>(); private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>();
private readonly Bindable<double> configTimeRange = new BindableDouble();
// Stores the current speed adjustment active in gameplay.
private readonly Track speedAdjustmentTrack = new TrackVirtual(0);
public DrawableManiaRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null) public DrawableManiaRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
: base(ruleset, beatmap, mods) : base(ruleset, beatmap, mods)
@ -58,6 +63,9 @@ namespace osu.Game.Rulesets.Mania.UI
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
foreach (var mod in Mods.OfType<IApplicableToTrack>())
mod.ApplyToTrack(speedAdjustmentTrack);
bool isForCurrentRuleset = Beatmap.BeatmapInfo.Ruleset.Equals(Ruleset.RulesetInfo); bool isForCurrentRuleset = Beatmap.BeatmapInfo.Ruleset.Equals(Ruleset.RulesetInfo);
foreach (var p in ControlPoints) foreach (var p in ControlPoints)
@ -76,7 +84,7 @@ namespace osu.Game.Rulesets.Mania.UI
Config.BindWith(ManiaRulesetSetting.ScrollDirection, configDirection); Config.BindWith(ManiaRulesetSetting.ScrollDirection, configDirection);
configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true); configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true);
Config.BindWith(ManiaRulesetSetting.ScrollTime, TimeRange); Config.BindWith(ManiaRulesetSetting.ScrollTime, configTimeRange);
} }
protected override void AdjustScrollSpeed(int amount) protected override void AdjustScrollSpeed(int amount)
@ -86,16 +94,18 @@ namespace osu.Game.Rulesets.Mania.UI
private double relativeTimeRange private double relativeTimeRange
{ {
get => MAX_TIME_RANGE / TimeRange.Value; get => MAX_TIME_RANGE / configTimeRange.Value;
set => TimeRange.Value = MAX_TIME_RANGE / value; set => configTimeRange.Value = MAX_TIME_RANGE / value;
} }
/// <summary> protected override void Update()
/// Retrieves the column that intersects a screen-space position. {
/// </summary> base.Update();
/// <param name="screenSpacePosition">The screen-space position.</param>
/// <returns>The column which intersects with <paramref name="screenSpacePosition"/>.</returns> updateTimeRange();
public Column GetColumnByPosition(Vector2 screenSpacePosition) => Playfield.GetColumnByPosition(screenSpacePosition); }
private void updateTimeRange() => TimeRange.Value = configTimeRange.Value * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value;
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new ManiaPlayfieldAdjustmentContainer(); public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new ManiaPlayfieldAdjustmentContainer();

View File

@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Mania.UI
[Cached] [Cached]
public class ManiaPlayfield : ScrollingPlayfield public class ManiaPlayfield : ScrollingPlayfield
{ {
public IReadOnlyList<Stage> Stages => stages;
private readonly List<Stage> stages = new List<Stage>(); private readonly List<Stage> stages = new List<Stage>();
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => stages.Any(s => s.ReceivePositionalInputAt(screenSpacePos)); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => stages.Any(s => s.ReceivePositionalInputAt(screenSpacePos));
@ -73,7 +75,7 @@ namespace osu.Game.Rulesets.Mania.UI
{ {
foreach (var column in stage.Columns) foreach (var column in stage.Columns)
{ {
if (column.ReceivePositionalInputAt(screenSpacePosition)) if (column.ReceivePositionalInputAt(new Vector2(screenSpacePosition.X, column.ScreenSpaceDrawQuad.Centre.Y)))
{ {
found = column; found = column;
break; break;
@ -87,6 +89,31 @@ namespace osu.Game.Rulesets.Mania.UI
return found; return found;
} }
/// <summary>
/// Retrieves a <see cref="Column"/> by index.
/// </summary>
/// <param name="index">The index of the column.</param>
/// <returns>The <see cref="Column"/> corresponding to the given index.</returns>
/// <exception cref="ArgumentOutOfRangeException">If <paramref name="index"/> is less than 0 or greater than <see cref="TotalColumns"/>.</exception>
public Column GetColumn(int index)
{
if (index < 0 || index > TotalColumns - 1)
throw new ArgumentOutOfRangeException(nameof(index));
foreach (var stage in stages)
{
if (index >= stage.Columns.Count)
{
index -= stage.Columns.Count;
continue;
}
return stage.Columns[index];
}
throw new ArgumentOutOfRangeException(nameof(index));
}
/// <summary> /// <summary>
/// Retrieves the total amount of columns across all stages in this playfield. /// Retrieves the total amount of columns across all stages in this playfield.
/// </summary> /// </summary>

View File

@ -1,21 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests namespace osu.Game.Rulesets.Osu.Tests
{ {
public abstract class OsuSkinnableTestScene : SkinnableTestScene public abstract class OsuSkinnableTestScene : SkinnableTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(OsuRuleset),
typeof(OsuLegacySkinTransformer),
};
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
} }
} }

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -15,12 +14,6 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
public class TestSceneDrawableJudgement : OsuSkinnableTestScene public class TestSceneDrawableJudgement : OsuSkinnableTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
{
typeof(DrawableJudgement),
typeof(DrawableOsuJudgement)
}).ToList();
public TestSceneDrawableJudgement() public TestSceneDrawableJudgement()
{ {
foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Skip(1)) foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Skip(1))

View File

@ -2,16 +2,12 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Testing.Input; using osu.Framework.Testing.Input;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Rulesets.Osu.UI.Cursor;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osuTK; using osuTK;
@ -20,16 +16,6 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestFixture] [TestFixture]
public class TestSceneGameplayCursor : OsuSkinnableTestScene public class TestSceneGameplayCursor : OsuSkinnableTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
{
typeof(GameplayCursorContainer),
typeof(OsuCursorContainer),
typeof(OsuCursor),
typeof(LegacyCursor),
typeof(LegacyCursorTrail),
typeof(CursorTrail)
}).ToList();
[Cached] [Cached]
private GameplayBeatmap gameplayBeatmap; private GameplayBeatmap gameplayBeatmap;

View File

@ -8,8 +8,6 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osuTK; using osuTK;
using System.Collections.Generic;
using System;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
@ -20,11 +18,6 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestFixture] [TestFixture]
public class TestSceneHitCircle : OsuSkinnableTestScene public class TestSceneHitCircle : OsuSkinnableTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(DrawableHitCircle)
};
private int depthIndex; private int depthIndex;
public TestSceneHitCircle() public TestSceneHitCircle()

View File

@ -1,9 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
@ -12,8 +9,6 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestFixture] [TestFixture]
public class TestSceneHitCircleHidden : TestSceneHitCircle public class TestSceneHitCircleHidden : TestSceneHitCircle
{ {
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
[SetUp] [SetUp]
public void SetUp() => Schedule(() => public void SetUp() => Schedule(() =>
{ {

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -16,7 +15,6 @@ using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -28,18 +26,13 @@ namespace osu.Game.Rulesets.Osu.Tests
private const double beat_length = 100; private const double beat_length = 100;
private static readonly Vector2 grid_position = new Vector2(512, 384); private static readonly Vector2 grid_position = new Vector2(512, 384);
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(CircularDistanceSnapGrid)
};
[Cached(typeof(EditorBeatmap))] [Cached(typeof(EditorBeatmap))]
private readonly EditorBeatmap editorBeatmap; private readonly EditorBeatmap editorBeatmap;
[Cached] [Cached]
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
[Cached(typeof(IDistanceSnapProvider))] [Cached(typeof(IPositionSnapProvider))]
private readonly SnapProvider snapProvider = new SnapProvider(); private readonly SnapProvider snapProvider = new SnapProvider();
private TestOsuDistanceSnapGrid grid; private TestOsuDistanceSnapGrid grid;
@ -179,9 +172,9 @@ namespace osu.Game.Rulesets.Osu.Tests
} }
} }
private class SnapProvider : IDistanceSnapProvider private class SnapProvider : IPositionSnapProvider
{ {
public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => (position, time); public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length; public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
@ -11,11 +9,6 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestFixture] [TestFixture]
public class TestSceneOsuPlayer : PlayerTestScene public class TestSceneOsuPlayer : PlayerTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(OsuRuleset),
};
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
} }
} }

View File

@ -399,7 +399,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
public TestSlider() public TestSlider()
{ {
DefaultsApplied += () => DefaultsApplied += _ =>
{ {
HeadCircle.HitWindows = new TestHitWindows(); HeadCircle.HitWindows = new TestHitWindows();
TailCircle.HitWindows = new TestHitWindows(); TailCircle.HitWindows = new TestHitWindows();

View File

@ -1,10 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Humanizer;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -19,13 +16,6 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
public class TestScenePathControlPointVisualiser : OsuTestScene public class TestScenePathControlPointVisualiser : OsuTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(StringHumanizeExtensions),
typeof(PathControlPointPiece),
typeof(PathControlPointConnectionPiece)
};
private Slider slider; private Slider slider;
private PathControlPointVisualiser visualiser; private PathControlPointVisualiser visualiser;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
@ -14,11 +12,6 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
public class TestSceneResumeOverlay : OsuManualInputManagerTestScene public class TestSceneResumeOverlay : OsuManualInputManagerTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(OsuResumeOverlay),
};
public TestSceneResumeOverlay() public TestSceneResumeOverlay()
{ {
ManualOsuInputManager osuInputManager; ManualOsuInputManager osuInputManager;

View File

@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -21,29 +20,12 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Osu.Tests namespace osu.Game.Rulesets.Osu.Tests
{ {
[TestFixture] [TestFixture]
public class TestSceneSlider : OsuSkinnableTestScene public class TestSceneSlider : OsuSkinnableTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Slider),
typeof(SliderTick),
typeof(SliderTailCircle),
typeof(SliderBall),
typeof(SliderBody),
typeof(SnakingSliderBody),
typeof(DrawableSlider),
typeof(DrawableSliderTick),
typeof(DrawableSliderTail),
typeof(DrawableSliderHead),
typeof(DrawableSliderRepeat),
typeof(DrawableOsuHitObject)
};
private Container content; private Container content;
protected override Container<Drawable> Content protected override Container<Drawable> Content

View File

@ -1,9 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
@ -12,8 +9,6 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestFixture] [TestFixture]
public class TestSceneSliderHidden : TestSceneSlider public class TestSceneSliderHidden : TestSceneSlider
{ {
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
[SetUp] [SetUp]
public void SetUp() => Schedule(() => public void SetUp() => Schedule(() =>
{ {

View File

@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
@ -13,8 +12,6 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -27,17 +24,6 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
public class TestSceneSliderInput : RateAdjustedBeatmapTestScene public class TestSceneSliderInput : RateAdjustedBeatmapTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(SliderBall),
typeof(DrawableSlider),
typeof(DrawableSliderTick),
typeof(DrawableSliderRepeat),
typeof(DrawableOsuHitObject),
typeof(DrawableSliderHead),
typeof(DrawableSliderTail),
};
private const double time_before_slider = 250; private const double time_before_slider = 250;
private const double time_slider_start = 1500; private const double time_slider_start = 1500;
private const double time_during_slide_1 = 2500; private const double time_during_slide_1 = 2500;

View File

@ -259,6 +259,23 @@ namespace osu.Game.Rulesets.Osu.Tests
assertControlPointType(2, PathType.PerfectCurve); assertControlPointType(2, PathType.PerfectCurve);
} }
[Test]
public void TestBeginPlacementWithoutReleasingMouse()
{
addMovementStep(new Vector2(200));
AddStep("press left button", () => InputManager.PressButton(MouseButton.Left));
addMovementStep(new Vector2(400, 200));
AddStep("release left button", () => InputManager.ReleaseButton(MouseButton.Left));
addClickStep(MouseButton.Right);
assertPlaced(true);
assertLength(200);
assertControlPointCount(2);
assertControlPointType(0, PathType.Linear);
}
private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position))); private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position)));
private void addClickStep(MouseButton button) private void addClickStep(MouseButton button)

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -22,16 +20,6 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
public class TestSceneSliderSelectionBlueprint : SelectionBlueprintTestScene public class TestSceneSliderSelectionBlueprint : SelectionBlueprintTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(SliderSelectionBlueprint),
typeof(SliderCircleSelectionBlueprint),
typeof(SliderBodyPiece),
typeof(SliderCircle),
typeof(PathControlPointVisualiser),
typeof(PathControlPointPiece)
};
private Slider slider; private Slider slider;
private DrawableSlider drawableObject; private DrawableSlider drawableObject;
private TestSliderBlueprint blueprint; private TestSliderBlueprint blueprint;

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -12,7 +10,6 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests namespace osu.Game.Rulesets.Osu.Tests
@ -20,13 +17,6 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestFixture] [TestFixture]
public class TestSceneSpinner : OsuTestScene public class TestSceneSpinner : OsuTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(SpinnerDisc),
typeof(DrawableSpinner),
typeof(DrawableOsuHitObject)
};
private readonly Container content; private readonly Container content;
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;

View File

@ -1,9 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
@ -12,8 +9,6 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestFixture] [TestFixture]
public class TestSceneSpinnerHidden : TestSceneSpinner public class TestSceneSpinnerHidden : TestSceneSpinner
{ {
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
[SetUp] [SetUp]
public void SetUp() => Schedule(() => public void SetUp() => Schedule(() =>
{ {

View File

@ -1,14 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
@ -18,12 +15,6 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
public class TestSceneSpinnerSelectionBlueprint : SelectionBlueprintTestScene public class TestSceneSpinnerSelectionBlueprint : SelectionBlueprintTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(SpinnerSelectionBlueprint),
typeof(SpinnerPiece)
};
public TestSceneSpinnerSelectionBlueprint() public TestSceneSpinnerSelectionBlueprint()
{ {
var spinner = new Spinner var spinner = new Spinner

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -12,7 +10,6 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests namespace osu.Game.Rulesets.Osu.Tests
@ -20,14 +17,6 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestFixture] [TestFixture]
public class TestSceneSpinnerSpunOut : OsuTestScene public class TestSceneSpinnerSpunOut : OsuTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(SpinnerDisc),
typeof(DrawableSpinner),
typeof(DrawableOsuHitObject),
typeof(OsuModSpunOut)
};
[SetUp] [SetUp]
public void SetUp() => Schedule(() => public void SetUp() => Schedule(() =>
{ {

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" /> <Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" /> <PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
switch (original) switch (original)
{ {
case IHasCurve curveData: case IHasPathWithRepeats curveData:
return new Slider return new Slider
{ {
StartTime = original.StartTime, StartTime = original.StartTime,
@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / beatmap.ControlPointInfo.DifficultyPointAt(original.StartTime).SpeedMultiplier : 1 TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / beatmap.ControlPointInfo.DifficultyPointAt(original.StartTime).SpeedMultiplier : 1
}.Yield(); }.Yield();
case IHasEndTime endTimeData: case IHasDuration endTimeData:
return new Spinner return new Spinner
{ {
StartTime = original.StartTime, StartTime = original.StartTime,

Some files were not shown because too many files have changed in this diff Show More