mirror of
https://github.com/osukey/osukey.git
synced 2025-06-09 05:19:11 +09:00
Merge remote-tracking branch 'upstream/master' into visible-playfield-boundary
This commit is contained in:
commit
96f39dd350
@ -192,3 +192,6 @@ dotnet_diagnostic.IDE0052.severity = silent
|
|||||||
dotnet_diagnostic.IDE0067.severity = none
|
dotnet_diagnostic.IDE0067.severity = none
|
||||||
dotnet_diagnostic.IDE0068.severity = none
|
dotnet_diagnostic.IDE0068.severity = none
|
||||||
dotnet_diagnostic.IDE0069.severity = none
|
dotnet_diagnostic.IDE0069.severity = none
|
||||||
|
|
||||||
|
#Disable operator overloads requiring alternate named methods
|
||||||
|
dotnet_diagnostic.CA2225.severity = none
|
@ -16,7 +16,7 @@
|
|||||||
<EmbeddedResource Include="Resources\**\*.*" />
|
<EmbeddedResource Include="Resources\**\*.*" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Code Analysis">
|
<ItemGroup Label="Code Analysis">
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.0.0" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.0" PrivateAssets="All" />
|
||||||
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
|
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.0.0" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.0.0" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
73
Gemfile.lock
73
Gemfile.lock
@ -6,35 +6,36 @@ GEM
|
|||||||
public_suffix (>= 2.0.2, < 5.0)
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
aws-eventstream (1.1.0)
|
aws-eventstream (1.1.0)
|
||||||
aws-partitions (1.329.0)
|
aws-partitions (1.354.0)
|
||||||
aws-sdk-core (3.99.2)
|
aws-sdk-core (3.104.3)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
aws-partitions (~> 1, >= 1.239.0)
|
aws-partitions (~> 1, >= 1.239.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
jmespath (~> 1.0)
|
jmespath (~> 1.0)
|
||||||
aws-sdk-kms (1.34.1)
|
aws-sdk-kms (1.36.0)
|
||||||
aws-sdk-core (~> 3, >= 3.99.0)
|
aws-sdk-core (~> 3, >= 3.99.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.68.1)
|
aws-sdk-s3 (1.78.0)
|
||||||
aws-sdk-core (~> 3, >= 3.99.0)
|
aws-sdk-core (~> 3, >= 3.104.3)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sigv4 (1.1.4)
|
aws-sigv4 (1.2.1)
|
||||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
babosa (1.0.3)
|
babosa (1.0.3)
|
||||||
claide (1.0.3)
|
claide (1.0.3)
|
||||||
colored (1.2)
|
colored (1.2)
|
||||||
colored2 (3.1.2)
|
colored2 (3.1.2)
|
||||||
commander-fastlane (4.4.6)
|
commander-fastlane (4.4.6)
|
||||||
highline (~> 1.7.2)
|
highline (~> 1.7.2)
|
||||||
declarative (0.0.10)
|
declarative (0.0.20)
|
||||||
declarative-option (0.1.0)
|
declarative-option (0.1.0)
|
||||||
digest-crc (0.5.1)
|
digest-crc (0.6.1)
|
||||||
|
rake (~> 13.0)
|
||||||
domain_name (0.5.20190701)
|
domain_name (0.5.20190701)
|
||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
dotenv (2.7.5)
|
dotenv (2.7.6)
|
||||||
emoji_regex (1.0.1)
|
emoji_regex (3.0.0)
|
||||||
excon (0.74.0)
|
excon (0.76.0)
|
||||||
faraday (1.0.1)
|
faraday (1.0.1)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
faraday-cookie_jar (0.0.6)
|
faraday-cookie_jar (0.0.6)
|
||||||
@ -42,34 +43,32 @@ GEM
|
|||||||
http-cookie (~> 1.0.0)
|
http-cookie (~> 1.0.0)
|
||||||
faraday_middleware (1.0.0)
|
faraday_middleware (1.0.0)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
fastimage (2.1.7)
|
fastimage (2.2.0)
|
||||||
fastlane (2.149.1)
|
fastlane (2.156.0)
|
||||||
CFPropertyList (>= 2.3, < 4.0.0)
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
addressable (>= 2.3, < 3.0.0)
|
addressable (>= 2.3, < 3.0.0)
|
||||||
aws-sdk-s3 (~> 1.0)
|
aws-sdk-s3 (~> 1.0)
|
||||||
babosa (>= 1.0.2, < 2.0.0)
|
babosa (>= 1.0.3, < 2.0.0)
|
||||||
bundler (>= 1.12.0, < 3.0.0)
|
bundler (>= 1.12.0, < 3.0.0)
|
||||||
colored
|
colored
|
||||||
commander-fastlane (>= 4.4.6, < 5.0.0)
|
commander-fastlane (>= 4.4.6, < 5.0.0)
|
||||||
dotenv (>= 2.1.1, < 3.0.0)
|
dotenv (>= 2.1.1, < 3.0.0)
|
||||||
emoji_regex (>= 0.1, < 2.0)
|
emoji_regex (>= 0.1, < 4.0)
|
||||||
excon (>= 0.71.0, < 1.0.0)
|
excon (>= 0.71.0, < 1.0.0)
|
||||||
faraday (>= 0.17, < 2.0)
|
faraday (~> 1.0)
|
||||||
faraday-cookie_jar (~> 0.0.6)
|
faraday-cookie_jar (~> 0.0.6)
|
||||||
faraday_middleware (>= 0.13.1, < 2.0)
|
faraday_middleware (~> 1.0)
|
||||||
fastimage (>= 2.1.0, < 3.0.0)
|
fastimage (>= 2.1.0, < 3.0.0)
|
||||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||||
google-api-client (>= 0.37.0, < 0.39.0)
|
google-api-client (>= 0.37.0, < 0.39.0)
|
||||||
google-cloud-storage (>= 1.15.0, < 2.0.0)
|
google-cloud-storage (>= 1.15.0, < 2.0.0)
|
||||||
highline (>= 1.7.2, < 2.0.0)
|
highline (>= 1.7.2, < 2.0.0)
|
||||||
json (< 3.0.0)
|
json (< 3.0.0)
|
||||||
jwt (~> 2.1.0)
|
jwt (>= 2.1.0, < 3)
|
||||||
mini_magick (>= 4.9.4, < 5.0.0)
|
mini_magick (>= 4.9.4, < 5.0.0)
|
||||||
multi_xml (~> 0.5)
|
|
||||||
multipart-post (~> 2.0.0)
|
multipart-post (~> 2.0.0)
|
||||||
plist (>= 3.1.0, < 4.0.0)
|
plist (>= 3.1.0, < 4.0.0)
|
||||||
public_suffix (~> 2.0.0)
|
rubyzip (>= 2.0.0, < 3.0.0)
|
||||||
rubyzip (>= 1.3.0, < 2.0.0)
|
|
||||||
security (= 0.1.3)
|
security (= 0.1.3)
|
||||||
simctl (~> 1.6.3)
|
simctl (~> 1.6.3)
|
||||||
slack-notifier (>= 2.0.0, < 3.0.0)
|
slack-notifier (>= 2.0.0, < 3.0.0)
|
||||||
@ -97,17 +96,17 @@ GEM
|
|||||||
google-cloud-core (1.5.0)
|
google-cloud-core (1.5.0)
|
||||||
google-cloud-env (~> 1.0)
|
google-cloud-env (~> 1.0)
|
||||||
google-cloud-errors (~> 1.0)
|
google-cloud-errors (~> 1.0)
|
||||||
google-cloud-env (1.3.2)
|
google-cloud-env (1.3.3)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
google-cloud-errors (1.0.1)
|
google-cloud-errors (1.0.1)
|
||||||
google-cloud-storage (1.26.2)
|
google-cloud-storage (1.27.0)
|
||||||
addressable (~> 2.5)
|
addressable (~> 2.5)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
google-api-client (~> 0.33)
|
google-api-client (~> 0.33)
|
||||||
google-cloud-core (~> 1.2)
|
google-cloud-core (~> 1.2)
|
||||||
googleauth (~> 0.9)
|
googleauth (~> 0.9)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
googleauth (0.12.0)
|
googleauth (0.13.1)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
jwt (>= 1.4, < 3.0)
|
jwt (>= 1.4, < 3.0)
|
||||||
memoist (~> 0.16)
|
memoist (~> 0.16)
|
||||||
@ -119,29 +118,29 @@ GEM
|
|||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
jmespath (1.4.0)
|
jmespath (1.4.0)
|
||||||
json (2.3.0)
|
json (2.3.1)
|
||||||
jwt (2.1.0)
|
jwt (2.2.1)
|
||||||
memoist (0.16.2)
|
memoist (0.16.2)
|
||||||
mini_magick (4.10.1)
|
mini_magick (4.10.1)
|
||||||
mini_mime (1.0.2)
|
mini_mime (1.0.2)
|
||||||
mini_portile2 (2.4.0)
|
mini_portile2 (2.4.0)
|
||||||
multi_json (1.14.1)
|
multi_json (1.15.0)
|
||||||
multi_xml (0.6.0)
|
|
||||||
multipart-post (2.0.0)
|
multipart-post (2.0.0)
|
||||||
nanaimo (0.2.6)
|
nanaimo (0.3.0)
|
||||||
naturally (2.2.0)
|
naturally (2.2.0)
|
||||||
nokogiri (1.10.7)
|
nokogiri (1.10.10)
|
||||||
mini_portile2 (~> 2.4.0)
|
mini_portile2 (~> 2.4.0)
|
||||||
os (1.1.0)
|
os (1.1.1)
|
||||||
plist (3.5.0)
|
plist (3.5.0)
|
||||||
public_suffix (2.0.5)
|
public_suffix (4.0.5)
|
||||||
|
rake (13.0.1)
|
||||||
representable (3.0.4)
|
representable (3.0.4)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
declarative-option (< 0.2.0)
|
declarative-option (< 0.2.0)
|
||||||
uber (< 0.2.0)
|
uber (< 0.2.0)
|
||||||
retriable (3.1.2)
|
retriable (3.1.2)
|
||||||
rouge (2.0.7)
|
rouge (2.0.7)
|
||||||
rubyzip (1.3.0)
|
rubyzip (2.3.0)
|
||||||
security (0.1.3)
|
security (0.1.3)
|
||||||
signet (0.14.0)
|
signet (0.14.0)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
@ -160,7 +159,7 @@ GEM
|
|||||||
terminal-table (1.8.0)
|
terminal-table (1.8.0)
|
||||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||||
tty-cursor (0.7.1)
|
tty-cursor (0.7.1)
|
||||||
tty-screen (0.8.0)
|
tty-screen (0.8.1)
|
||||||
tty-spinner (0.9.3)
|
tty-spinner (0.9.3)
|
||||||
tty-cursor (~> 0.7)
|
tty-cursor (~> 0.7)
|
||||||
uber (0.1.0)
|
uber (0.1.0)
|
||||||
@ -169,12 +168,12 @@ GEM
|
|||||||
unf_ext (0.0.7.7)
|
unf_ext (0.0.7.7)
|
||||||
unicode-display_width (1.7.0)
|
unicode-display_width (1.7.0)
|
||||||
word_wrap (1.0.0)
|
word_wrap (1.0.0)
|
||||||
xcodeproj (1.16.0)
|
xcodeproj (1.18.0)
|
||||||
CFPropertyList (>= 2.3.3, < 4.0)
|
CFPropertyList (>= 2.3.3, < 4.0)
|
||||||
atomos (~> 0.1.3)
|
atomos (~> 0.1.3)
|
||||||
claide (>= 1.0.2, < 2.0)
|
claide (>= 1.0.2, < 2.0)
|
||||||
colored2 (~> 3.1)
|
colored2 (~> 3.1)
|
||||||
nanaimo (~> 0.2.6)
|
nanaimo (~> 0.3.0)
|
||||||
xcpretty (0.3.0)
|
xcpretty (0.3.0)
|
||||||
rouge (~> 2.0.7)
|
rouge (~> 2.0.7)
|
||||||
xcpretty-travis-formatter (1.0.0)
|
xcpretty-travis-formatter (1.0.0)
|
||||||
|
@ -9,7 +9,9 @@
|
|||||||
[](https://www.codefactor.io/repository/github/ppy/osu)
|
[](https://www.codefactor.io/repository/github/ppy/osu)
|
||||||
[](https://discord.gg/ppy)
|
[](https://discord.gg/ppy)
|
||||||
|
|
||||||
Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename *osu!lazer*. Pew pew.
|
A free-to-win rhythm game. Rhythm is just a *click* away!
|
||||||
|
|
||||||
|
The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename *osu!lazer*. Pew pew.
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ platform :ios do
|
|||||||
|
|
||||||
souyuz(
|
souyuz(
|
||||||
platform: "ios",
|
platform: "ios",
|
||||||
plist_path: "../osu.iOS/Info.plist"
|
plist_path: "osu.iOS/Info.plist"
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -127,7 +127,7 @@ platform :ios do
|
|||||||
end
|
end
|
||||||
|
|
||||||
lane :update_version do |options|
|
lane :update_version do |options|
|
||||||
options[:plist_path] = '../osu.iOS/Info.plist'
|
options[:plist_path] = 'osu.iOS/Info.plist'
|
||||||
app_version(options)
|
app_version(options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -5,6 +5,6 @@
|
|||||||
"version": "3.1.100"
|
"version": "3.1.100"
|
||||||
},
|
},
|
||||||
"msbuild-sdks": {
|
"msbuild-sdks": {
|
||||||
"Microsoft.Build.Traversal": "2.0.50"
|
"Microsoft.Build.Traversal": "2.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -51,7 +51,7 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.622.1" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.709.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.904.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -16,6 +16,7 @@ using osu.Framework.Logging;
|
|||||||
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;
|
||||||
|
using osu.Desktop.Windows;
|
||||||
|
|
||||||
namespace osu.Desktop
|
namespace osu.Desktop
|
||||||
{
|
{
|
||||||
@ -98,6 +99,9 @@ namespace osu.Desktop
|
|||||||
LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, Add);
|
LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, Add);
|
||||||
|
|
||||||
LoadComponentAsync(new DiscordRichPresence(), Add);
|
LoadComponentAsync(new DiscordRichPresence(), Add);
|
||||||
|
|
||||||
|
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows)
|
||||||
|
LoadComponentAsync(new GameplayWinKeyBlocker(), Add);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen)
|
protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen)
|
||||||
|
41
osu.Desktop/Windows/GameplayWinKeyBlocker.cs
Normal file
41
osu.Desktop/Windows/GameplayWinKeyBlocker.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
|
||||||
|
namespace osu.Desktop.Windows
|
||||||
|
{
|
||||||
|
public class GameplayWinKeyBlocker : Component
|
||||||
|
{
|
||||||
|
private Bindable<bool> allowScreenSuspension;
|
||||||
|
private Bindable<bool> disableWinKey;
|
||||||
|
|
||||||
|
private GameHost host;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(GameHost host, OsuConfigManager config)
|
||||||
|
{
|
||||||
|
this.host = host;
|
||||||
|
|
||||||
|
allowScreenSuspension = host.AllowScreenSuspension.GetBoundCopy();
|
||||||
|
allowScreenSuspension.BindValueChanged(_ => updateBlocking());
|
||||||
|
|
||||||
|
disableWinKey = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey);
|
||||||
|
disableWinKey.BindValueChanged(_ => updateBlocking(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBlocking()
|
||||||
|
{
|
||||||
|
bool shouldDisable = disableWinKey.Value && !allowScreenSuspension.Value;
|
||||||
|
|
||||||
|
if (shouldDisable)
|
||||||
|
host.InputThread.Scheduler.Add(WindowsKey.Disable);
|
||||||
|
else
|
||||||
|
host.InputThread.Scheduler.Add(WindowsKey.Enable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
80
osu.Desktop/Windows/WindowsKey.cs
Normal file
80
osu.Desktop/Windows/WindowsKey.cs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// 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.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace osu.Desktop.Windows
|
||||||
|
{
|
||||||
|
internal class WindowsKey
|
||||||
|
{
|
||||||
|
private delegate int LowLevelKeyboardProcDelegate(int nCode, int wParam, ref KdDllHookStruct lParam);
|
||||||
|
|
||||||
|
private static bool isBlocked;
|
||||||
|
|
||||||
|
private const int wh_keyboard_ll = 13;
|
||||||
|
private const int wm_keydown = 256;
|
||||||
|
private const int wm_syskeyup = 261;
|
||||||
|
|
||||||
|
//Resharper disable once NotAccessedField.Local
|
||||||
|
private static LowLevelKeyboardProcDelegate keyboardHookDelegate; // keeping a reference alive for the GC
|
||||||
|
private static IntPtr keyHook;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
|
private readonly struct KdDllHookStruct
|
||||||
|
{
|
||||||
|
[FieldOffset(0)]
|
||||||
|
public readonly int VkCode;
|
||||||
|
|
||||||
|
[FieldOffset(8)]
|
||||||
|
public readonly int Flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int lowLevelKeyboardProc(int nCode, int wParam, ref KdDllHookStruct lParam)
|
||||||
|
{
|
||||||
|
if (wParam >= wm_keydown && wParam <= wm_syskeyup)
|
||||||
|
{
|
||||||
|
switch (lParam.VkCode)
|
||||||
|
{
|
||||||
|
case 0x5B: // left windows key
|
||||||
|
case 0x5C: // right windows key
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return callNextHookEx(0, nCode, wParam, ref lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void Disable()
|
||||||
|
{
|
||||||
|
if (keyHook != IntPtr.Zero || isBlocked)
|
||||||
|
return;
|
||||||
|
|
||||||
|
keyHook = setWindowsHookEx(wh_keyboard_ll, (keyboardHookDelegate = lowLevelKeyboardProc), Marshal.GetHINSTANCE(System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0]), 0);
|
||||||
|
|
||||||
|
isBlocked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void Enable()
|
||||||
|
{
|
||||||
|
if (keyHook == IntPtr.Zero || !isBlocked)
|
||||||
|
return;
|
||||||
|
|
||||||
|
keyHook = unhookWindowsHookEx(keyHook);
|
||||||
|
keyboardHookDelegate = null;
|
||||||
|
|
||||||
|
keyHook = IntPtr.Zero;
|
||||||
|
|
||||||
|
isBlocked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport(@"user32.dll", EntryPoint = @"SetWindowsHookExA")]
|
||||||
|
private static extern IntPtr setWindowsHookEx(int idHook, LowLevelKeyboardProcDelegate lpfn, IntPtr hMod, int dwThreadId);
|
||||||
|
|
||||||
|
[DllImport(@"user32.dll", EntryPoint = @"UnhookWindowsHookEx")]
|
||||||
|
private static extern IntPtr unhookWindowsHookEx(IntPtr hHook);
|
||||||
|
|
||||||
|
[DllImport(@"user32.dll", EntryPoint = @"CallNextHookEx")]
|
||||||
|
private static extern int callNextHookEx(int hHook, int nCode, int wParam, ref KdDllHookStruct lParam);
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,7 @@
|
|||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<Description>click the circles. to the beat.</Description>
|
<Description>A free-to-win rhythm game. Rhythm is just a *click* away!</Description>
|
||||||
<AssemblyName>osu!</AssemblyName>
|
<AssemblyName>osu!</AssemblyName>
|
||||||
<Title>osu!lazer</Title>
|
<Title>osu!lazer</Title>
|
||||||
<Product>osu!lazer</Product>
|
<Product>osu!lazer</Product>
|
||||||
|
@ -9,8 +9,7 @@
|
|||||||
<projectUrl>https://osu.ppy.sh/</projectUrl>
|
<projectUrl>https://osu.ppy.sh/</projectUrl>
|
||||||
<iconUrl>https://puu.sh/tYyXZ/9a01a5d1b0.ico</iconUrl>
|
<iconUrl>https://puu.sh/tYyXZ/9a01a5d1b0.ico</iconUrl>
|
||||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
<description>click the circles. to the beat.</description>
|
<description>A free-to-win rhythm game. Rhythm is just a *click* away!</description>
|
||||||
<summary>click the circles.</summary>
|
|
||||||
<releaseNotes>testing</releaseNotes>
|
<releaseNotes>testing</releaseNotes>
|
||||||
<copyright>Copyright (c) 2020 ppy Pty Ltd</copyright>
|
<copyright>Copyright (c) 2020 ppy Pty Ltd</copyright>
|
||||||
<language>en-AU</language>
|
<language>en-AU</language>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.12.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.17.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
[TestCase("hardrock-stream", new[] { typeof(CatchModHardRock) })]
|
[TestCase("hardrock-stream", new[] { typeof(CatchModHardRock) })]
|
||||||
[TestCase("hardrock-repeat-slider", new[] { typeof(CatchModHardRock) })]
|
[TestCase("hardrock-repeat-slider", new[] { typeof(CatchModHardRock) })]
|
||||||
[TestCase("hardrock-spinner", new[] { typeof(CatchModHardRock) })]
|
[TestCase("hardrock-spinner", new[] { typeof(CatchModHardRock) })]
|
||||||
|
[TestCase("right-bound-hr-offset", new[] { typeof(CatchModHardRock) })]
|
||||||
public new void Test(string name, params Type[] mods) => base.Test(name, mods);
|
public new void Test(string name, params Type[] mods) => base.Test(name, mods);
|
||||||
|
|
||||||
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
|
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
|
||||||
|
@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods
|
|||||||
public void TestDroplet(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Droplet { StartTime = 1000 }), shouldMiss);
|
public void TestDroplet(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Droplet { StartTime = 1000 }), shouldMiss);
|
||||||
|
|
||||||
// We only care about testing misses, hits are tested via JuiceStream
|
// We only care about testing misses, hits are tested via JuiceStream
|
||||||
[TestCase(true)]
|
[TestCase(false)]
|
||||||
public void TestTinyDroplet(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new TinyDroplet { StartTime = 1000 }), shouldMiss);
|
public void TestTinyDroplet(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new TinyDroplet { StartTime = 1000 }), shouldMiss);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
84
osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs
Normal file
84
osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.Mods;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests.Mods
|
||||||
|
{
|
||||||
|
public class TestSceneCatchModRelax : ModTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestModRelax() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new CatchModRelax(),
|
||||||
|
Autoplay = false,
|
||||||
|
PassCondition = passCondition,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Fruit
|
||||||
|
{
|
||||||
|
X = CatchPlayfield.CENTER_X,
|
||||||
|
StartTime = 0
|
||||||
|
},
|
||||||
|
new Fruit
|
||||||
|
{
|
||||||
|
X = 0,
|
||||||
|
StartTime = 250
|
||||||
|
},
|
||||||
|
new Fruit
|
||||||
|
{
|
||||||
|
X = CatchPlayfield.WIDTH,
|
||||||
|
StartTime = 500
|
||||||
|
},
|
||||||
|
new JuiceStream
|
||||||
|
{
|
||||||
|
X = CatchPlayfield.CENTER_X,
|
||||||
|
StartTime = 750,
|
||||||
|
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
private bool passCondition()
|
||||||
|
{
|
||||||
|
var playfield = this.ChildrenOfType<CatchPlayfield>().Single();
|
||||||
|
|
||||||
|
switch (Player.ScoreProcessor.Combo.Value)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.BottomLeft);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.BottomRight);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Player.ScoreProcessor.Combo.Value >= 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +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.Linq;
|
using System.Linq;
|
||||||
|
using osu.Game.Audio;
|
||||||
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.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
@ -38,7 +40,11 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
new Vector2(width, 0)
|
new Vector2(width, 0)
|
||||||
}),
|
}),
|
||||||
StartTime = i * 2000,
|
StartTime = i * 2000,
|
||||||
NewCombo = i % 8 == 0
|
NewCombo = i % 8 == 0,
|
||||||
|
Samples = new List<HitSampleInfo>(new[]
|
||||||
|
{
|
||||||
|
new HitSampleInfo { Bank = "normal", Name = "hitnormal", Volume = 100 }
|
||||||
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
56
osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs
Normal file
56
osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Catch.Mods;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneCatchModHidden : ModTestScene
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
LocalConfig.Set(OsuSetting.IncreaseFirstObjectVisibility, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestJuiceStream()
|
||||||
|
{
|
||||||
|
CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new JuiceStream
|
||||||
|
{
|
||||||
|
StartTime = 1000,
|
||||||
|
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(0, -192) }),
|
||||||
|
X = CatchPlayfield.WIDTH / 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Mod = new CatchModHidden(),
|
||||||
|
PassCondition = () => Player.Results.Count > 0
|
||||||
|
&& Player.ChildrenOfType<DrawableJuiceStream>().Single().Alpha > 0
|
||||||
|
&& Player.ChildrenOfType<DrawableFruit>().Last().Alpha > 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Judgements;
|
using osu.Game.Rulesets.Catch.Judgements;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
@ -25,6 +26,11 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
{
|
{
|
||||||
private RulesetInfo catchRuleset;
|
private RulesetInfo catchRuleset;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuConfigManager config { get; set; }
|
||||||
|
|
||||||
|
private Catcher catcher => this.ChildrenOfType<CatcherArea>().First().MovableCatcher;
|
||||||
|
|
||||||
public TestSceneCatcherArea()
|
public TestSceneCatcherArea()
|
||||||
{
|
{
|
||||||
AddSliderStep<float>("CircleSize", 0, 8, 5, createCatcher);
|
AddSliderStep<float>("CircleSize", 0, 8, 5, createCatcher);
|
||||||
@ -34,24 +40,43 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
AddRepeatStep("catch fruit", () => catchFruit(new TestFruit(false)
|
AddRepeatStep("catch fruit", () => catchFruit(new TestFruit(false)
|
||||||
{
|
{
|
||||||
X = this.ChildrenOfType<CatcherArea>().First().MovableCatcher.X
|
X = catcher.X
|
||||||
}), 20);
|
}), 20);
|
||||||
AddRepeatStep("catch fruit last in combo", () => catchFruit(new TestFruit(false)
|
AddRepeatStep("catch fruit last in combo", () => catchFruit(new TestFruit(false)
|
||||||
{
|
{
|
||||||
X = this.ChildrenOfType<CatcherArea>().First().MovableCatcher.X,
|
X = catcher.X,
|
||||||
LastInCombo = true,
|
LastInCombo = true,
|
||||||
}), 20);
|
}), 20);
|
||||||
AddRepeatStep("catch kiai fruit", () => catchFruit(new TestFruit(true)
|
AddRepeatStep("catch kiai fruit", () => catchFruit(new TestFruit(true)
|
||||||
{
|
{
|
||||||
X = this.ChildrenOfType<CatcherArea>().First().MovableCatcher.X,
|
X = catcher.X
|
||||||
}), 20);
|
}), 20);
|
||||||
AddRepeatStep("miss fruit", () => catchFruit(new Fruit
|
AddRepeatStep("miss fruit", () => catchFruit(new Fruit
|
||||||
{
|
{
|
||||||
X = this.ChildrenOfType<CatcherArea>().First().MovableCatcher.X + 100,
|
X = catcher.X + 100,
|
||||||
LastInCombo = true,
|
LastInCombo = true,
|
||||||
}, true), 20);
|
}, true), 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase(true)]
|
||||||
|
[TestCase(false)]
|
||||||
|
public void TestHitLighting(bool enable)
|
||||||
|
{
|
||||||
|
AddStep("create catcher", () => createCatcher(5));
|
||||||
|
|
||||||
|
AddStep("toggle hit lighting", () => config.Set(OsuSetting.HitLighting, enable));
|
||||||
|
AddStep("catch fruit", () => catchFruit(new TestFruit(false)
|
||||||
|
{
|
||||||
|
X = catcher.X
|
||||||
|
}));
|
||||||
|
AddStep("catch fruit last in combo", () => catchFruit(new TestFruit(false)
|
||||||
|
{
|
||||||
|
X = catcher.X,
|
||||||
|
LastInCombo = true
|
||||||
|
}));
|
||||||
|
AddAssert("check hit explosion", () => catcher.ChildrenOfType<HitExplosion>().Any() == enable);
|
||||||
|
}
|
||||||
|
|
||||||
private void catchFruit(Fruit fruit, bool miss = false)
|
private void catchFruit(Fruit fruit, bool miss = false)
|
||||||
{
|
{
|
||||||
this.ChildrenOfType<CatcherArea>().ForEach(area =>
|
this.ChildrenOfType<CatcherArea>().ForEach(area =>
|
||||||
|
@ -20,19 +20,19 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation)))
|
foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation)))
|
||||||
AddStep($"show {rep}", () => SetContents(() => createDrawable(rep)));
|
AddStep($"show {rep}", () => SetContents(() => createDrawable(rep)));
|
||||||
|
|
||||||
AddStep("show droplet", () => SetContents(createDrawableDroplet));
|
AddStep("show droplet", () => SetContents(() => createDrawableDroplet()));
|
||||||
|
|
||||||
AddStep("show tiny droplet", () => SetContents(createDrawableTinyDroplet));
|
AddStep("show tiny droplet", () => SetContents(createDrawableTinyDroplet));
|
||||||
|
|
||||||
foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation)))
|
foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation)))
|
||||||
AddStep($"show hyperdash {rep}", () => SetContents(() => createDrawable(rep, true)));
|
AddStep($"show hyperdash {rep}", () => SetContents(() => createDrawable(rep, true)));
|
||||||
|
|
||||||
|
AddStep("show hyperdash droplet", () => SetContents(() => createDrawableDroplet(true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Drawable createDrawableTinyDroplet()
|
private Drawable createDrawableTinyDroplet()
|
||||||
{
|
{
|
||||||
var droplet = new TinyDroplet
|
var droplet = new TestCatchTinyDroplet
|
||||||
{
|
{
|
||||||
StartTime = Clock.CurrentTime,
|
|
||||||
Scale = 1.5f,
|
Scale = 1.5f,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -47,12 +47,12 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private Drawable createDrawableDroplet()
|
private Drawable createDrawableDroplet(bool hyperdash = false)
|
||||||
{
|
{
|
||||||
var droplet = new Droplet
|
var droplet = new TestCatchDroplet
|
||||||
{
|
{
|
||||||
StartTime = Clock.CurrentTime,
|
|
||||||
Scale = 1.5f,
|
Scale = 1.5f,
|
||||||
|
HyperDashTarget = hyperdash ? new Banana() : null
|
||||||
};
|
};
|
||||||
|
|
||||||
return new DrawableDroplet(droplet)
|
return new DrawableDroplet(droplet)
|
||||||
@ -95,5 +95,21 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
public override FruitVisualRepresentation VisualRepresentation { get; }
|
public override FruitVisualRepresentation VisualRepresentation { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class TestCatchDroplet : Droplet
|
||||||
|
{
|
||||||
|
public TestCatchDroplet()
|
||||||
|
{
|
||||||
|
StartTime = 1000000000000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestCatchTinyDroplet : TinyDroplet
|
||||||
|
{
|
||||||
|
public TestCatchTinyDroplet()
|
||||||
|
{
|
||||||
|
StartTime = 1000000000000;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -18,22 +19,42 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
{
|
{
|
||||||
protected override bool Autoplay => true;
|
protected override bool Autoplay => true;
|
||||||
|
|
||||||
|
private int hyperDashCount;
|
||||||
|
private bool inHyperDash;
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHyperDash()
|
public void TestHyperDash()
|
||||||
{
|
{
|
||||||
AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash);
|
AddStep("reset count", () =>
|
||||||
AddUntilStep("wait for right movement", () => getCatcher().Scale.X > 0); // don't check hyperdashing as it happens too fast.
|
|
||||||
|
|
||||||
AddUntilStep("wait for left movement", () => getCatcher().Scale.X < 0);
|
|
||||||
|
|
||||||
for (int i = 0; i < 3; i++)
|
|
||||||
{
|
{
|
||||||
AddUntilStep("wait for right hyperdash", () => getCatcher().Scale.X > 0 && getCatcher().HyperDashing);
|
inHyperDash = false;
|
||||||
AddUntilStep("wait for left hyperdash", () => getCatcher().Scale.X < 0 && getCatcher().HyperDashing);
|
hyperDashCount = 0;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Catcher getCatcher() => Player.ChildrenOfType<CatcherArea>().First().MovableCatcher;
|
// this needs to be done within the frame stable context due to how quickly hyperdash state changes occur.
|
||||||
|
Player.DrawableRuleset.FrameStableComponents.OnUpdate += d =>
|
||||||
|
{
|
||||||
|
var catcher = Player.ChildrenOfType<CatcherArea>().FirstOrDefault()?.MovableCatcher;
|
||||||
|
|
||||||
|
if (catcher == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (catcher.HyperDashing != inHyperDash)
|
||||||
|
{
|
||||||
|
inHyperDash = catcher.HyperDashing;
|
||||||
|
if (catcher.HyperDashing)
|
||||||
|
hyperDashCount++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash);
|
||||||
|
|
||||||
|
for (int i = 0; i < 9; i++)
|
||||||
|
{
|
||||||
|
int count = i + 1;
|
||||||
|
AddUntilStep($"wait for hyperdash #{count}", () => hyperDashCount >= count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||||
{
|
{
|
||||||
@ -46,6 +67,8 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
beatmap.ControlPointInfo.Add(0, new TimingControlPoint());
|
||||||
|
|
||||||
// Should produce a hyper-dash (edge case test)
|
// Should produce a hyper-dash (edge case test)
|
||||||
beatmap.HitObjects.Add(new Fruit { StartTime = 1816, X = 56, NewCombo = true });
|
beatmap.HitObjects.Add(new Fruit { StartTime = 1816, X = 56, NewCombo = true });
|
||||||
beatmap.HitObjects.Add(new Fruit { StartTime = 2008, X = 308, NewCombo = true });
|
beatmap.HitObjects.Add(new Fruit { StartTime = 2008, X = 308, NewCombo = true });
|
||||||
@ -63,6 +86,20 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
createObjects(() => new Fruit { X = right_x });
|
createObjects(() => new Fruit { X = right_x });
|
||||||
createObjects(() => new TestJuiceStream(left_x), 1);
|
createObjects(() => new TestJuiceStream(left_x), 1);
|
||||||
|
|
||||||
|
beatmap.ControlPointInfo.Add(startTime, new TimingControlPoint
|
||||||
|
{
|
||||||
|
BeatLength = 50
|
||||||
|
});
|
||||||
|
|
||||||
|
createObjects(() => new TestJuiceStream(left_x)
|
||||||
|
{
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(Vector2.Zero),
|
||||||
|
new PathControlPoint(new Vector2(512, 0))
|
||||||
|
})
|
||||||
|
}, 1);
|
||||||
|
|
||||||
return beatmap;
|
return beatmap;
|
||||||
|
|
||||||
void createObjects(Func<CatchHitObject> createObject, int count = 3)
|
void createObjects(Func<CatchHitObject> createObject, int count = 3)
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
<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.6.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.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.17.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
|
||||||
@ -23,19 +22,19 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
{
|
{
|
||||||
Name = @"Fruit Count",
|
Name = @"Fruit Count",
|
||||||
Content = fruits.ToString(),
|
Content = fruits.ToString(),
|
||||||
Icon = FontAwesome.Regular.Circle
|
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles),
|
||||||
},
|
},
|
||||||
new BeatmapStatistic
|
new BeatmapStatistic
|
||||||
{
|
{
|
||||||
Name = @"Juice Stream Count",
|
Name = @"Juice Stream Count",
|
||||||
Content = juiceStreams.ToString(),
|
Content = juiceStreams.ToString(),
|
||||||
Icon = FontAwesome.Regular.Circle
|
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
|
||||||
},
|
},
|
||||||
new BeatmapStatistic
|
new BeatmapStatistic
|
||||||
{
|
{
|
||||||
Name = @"Banana Shower Count",
|
Name = @"Banana Shower Count",
|
||||||
Content = bananaShowers.ToString(),
|
Content = bananaShowers.ToString(),
|
||||||
Icon = FontAwesome.Regular.Circle
|
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -179,7 +179,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
if (amount > 0)
|
if (amount > 0)
|
||||||
{
|
{
|
||||||
// Clamp to the right bound
|
// Clamp to the right bound
|
||||||
if (position + amount < 1)
|
if (position + amount < CatchPlayfield.WIDTH)
|
||||||
position += amount;
|
position += amount;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -212,6 +212,12 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
|
objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
|
||||||
|
|
||||||
double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) / 2;
|
double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) / 2;
|
||||||
|
|
||||||
|
// Todo: This is wrong. osu!stable calculated hyperdashes using the full catcher size, excluding the margins.
|
||||||
|
// This should theoretically cause impossible scenarios, but practically, likely due to the size of the playfield, it doesn't seem possible.
|
||||||
|
// For now, to bring gameplay (and diffcalc!) completely in-line with stable, this code also uses the full catcher size.
|
||||||
|
halfCatcherWidth /= Catcher.ALLOWED_CATCH_RANGE;
|
||||||
|
|
||||||
int lastDirection = 0;
|
int lastDirection = 0;
|
||||||
double lastExcess = halfCatcherWidth;
|
double lastExcess = halfCatcherWidth;
|
||||||
|
|
||||||
|
@ -21,11 +21,13 @@ using osu.Game.Rulesets.Difficulty;
|
|||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using System;
|
using System;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Rulesets.Catch.Skinning;
|
using osu.Game.Rulesets.Catch.Skinning;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch
|
namespace osu.Game.Rulesets.Catch
|
||||||
{
|
{
|
||||||
|
[ExcludeFromDynamicCompile]
|
||||||
public class CatchRuleset : Ruleset, ILegacyRuleset
|
public class CatchRuleset : Ruleset, ILegacyRuleset
|
||||||
{
|
{
|
||||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableCatchRuleset(this, beatmap, mods);
|
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableCatchRuleset(this, beatmap, mods);
|
||||||
|
@ -78,7 +78,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
|
|
||||||
if (mods.Any(m => m is ModHidden))
|
if (mods.Any(m => m is ModHidden))
|
||||||
{
|
{
|
||||||
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
|
// Hiddens gives almost nothing on max approach rate, and more the lower it is
|
||||||
if (approachRate <= 10.0)
|
if (approachRate <= 10.0)
|
||||||
value *= 1.05 + 0.075 * (10.0 - approachRate); // 7.5% for each AR below 10
|
value *= 1.05 + 0.075 * (10.0 - approachRate); // 7.5% for each AR below 10
|
||||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Judgements
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
case HitResult.Perfect:
|
case HitResult.Perfect:
|
||||||
return 0.01;
|
return DEFAULT_MAX_HEALTH_INCREASE * 0.75;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,17 +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 osu.Game.Rulesets.Catch.Judgements;
|
|
||||||
using osu.Game.Rulesets.Judgements;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Mods
|
namespace osu.Game.Rulesets.Catch.Mods
|
||||||
{
|
{
|
||||||
public class CatchModPerfect : ModPerfect
|
public class CatchModPerfect : ModPerfect
|
||||||
{
|
{
|
||||||
protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
|
|
||||||
=> !(result.Judgement is CatchBananaJudgement)
|
|
||||||
&& base.FailCondition(healthProcessor, result);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
|
|
||||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||||
{
|
{
|
||||||
catcher.UpdatePosition(e.MousePosition.X / DrawSize.X);
|
catcher.UpdatePosition(e.MousePosition.X / DrawSize.X * CatchPlayfield.WIDTH);
|
||||||
return base.OnMouseMove(e);
|
return base.OnMouseMove(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +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.Collections.Generic;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Rulesets.Catch.Judgements;
|
using osu.Game.Rulesets.Catch.Judgements;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
|
||||||
@ -8,8 +10,27 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
{
|
{
|
||||||
public class Banana : Fruit
|
public class Banana : Fruit
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Index of banana in current shower.
|
||||||
|
/// </summary>
|
||||||
|
public int BananaIndex;
|
||||||
|
|
||||||
public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
|
public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
|
||||||
|
|
||||||
public override Judgement CreateJudgement() => new CatchBananaJudgement();
|
public override Judgement CreateJudgement() => new CatchBananaJudgement();
|
||||||
|
|
||||||
|
private static readonly List<HitSampleInfo> samples = new List<HitSampleInfo> { new BananaHitSampleInfo() };
|
||||||
|
|
||||||
|
public Banana()
|
||||||
|
{
|
||||||
|
Samples = samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BananaHitSampleInfo : HitSampleInfo
|
||||||
|
{
|
||||||
|
private static string[] lookupNames { get; } = { "metronomelow", "catch-banana" };
|
||||||
|
|
||||||
|
public override IEnumerable<string> LookupNames => lookupNames;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,15 +30,21 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
if (spacing <= 0)
|
if (spacing <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (double i = StartTime; i <= EndTime; i += spacing)
|
double time = StartTime;
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
while (time <= EndTime)
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
AddNested(new Banana
|
AddNested(new Banana
|
||||||
{
|
{
|
||||||
Samples = Samples,
|
StartTime = time,
|
||||||
StartTime = i
|
BananaIndex = i,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
time += spacing;
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,11 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
set => x = value;
|
set => x = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this object can be placed on the catcher's plate.
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool CanBePlated => false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A random offset applied to <see cref="X"/>, set by the <see cref="CatchBeatmapProcessor"/>.
|
/// A random offset applied to <see cref="X"/>, set by the <see cref="CatchBeatmapProcessor"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -100,6 +105,14 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a single object that can be caught by the catcher.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class PalpableCatchHitObject : CatchHitObject
|
||||||
|
{
|
||||||
|
public override bool CanBePlated => true;
|
||||||
|
}
|
||||||
|
|
||||||
public enum FruitVisualRepresentation
|
public enum FruitVisualRepresentation
|
||||||
{
|
{
|
||||||
Pear,
|
Pear,
|
||||||
|
@ -40,6 +40,13 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
float getRandomAngle() => 180 * (RNG.NextSingle() * 2 - 1);
|
float getRandomAngle() => 180 * (RNG.NextSingle() * 2 - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void PlaySamples()
|
||||||
|
{
|
||||||
|
base.PlaySamples();
|
||||||
|
if (Samples != null)
|
||||||
|
Samples.Frequency.Value = 0.77f + ((Banana)HitObject).BananaIndex * 0.006f;
|
||||||
|
}
|
||||||
|
|
||||||
private Color4 getBananaColour()
|
private Color4 getBananaColour()
|
||||||
{
|
{
|
||||||
switch (RNG.Next(0, 3))
|
switch (RNG.Next(0, 3))
|
||||||
|
@ -15,14 +15,12 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
{
|
{
|
||||||
public abstract class PalpableCatchHitObject<TObject> : DrawableCatchHitObject<TObject>
|
public abstract class PalpableDrawableCatchHitObject<TObject> : DrawableCatchHitObject<TObject>
|
||||||
where TObject : CatchHitObject
|
where TObject : PalpableCatchHitObject
|
||||||
{
|
{
|
||||||
public override bool CanBePlated => true;
|
|
||||||
|
|
||||||
protected Container ScaleContainer { get; private set; }
|
protected Container ScaleContainer { get; private set; }
|
||||||
|
|
||||||
protected PalpableCatchHitObject(TObject hitObject)
|
protected PalpableDrawableCatchHitObject(TObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
@ -65,9 +63,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
|
|
||||||
public abstract class DrawableCatchHitObject : DrawableHitObject<CatchHitObject>
|
public abstract class DrawableCatchHitObject : DrawableHitObject<CatchHitObject>
|
||||||
{
|
{
|
||||||
public virtual bool CanBePlated => false;
|
public virtual bool StaysOnPlate => HitObject.CanBePlated;
|
||||||
|
|
||||||
public virtual bool StaysOnPlate => CanBePlated;
|
|
||||||
|
|
||||||
public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale;
|
public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale;
|
||||||
|
|
||||||
|
@ -4,12 +4,11 @@
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableDroplet : PalpableCatchHitObject<Droplet>
|
public class DrawableDroplet : PalpableDrawableCatchHitObject<Droplet>
|
||||||
{
|
{
|
||||||
public override bool StaysOnPlate => false;
|
public override bool StaysOnPlate => false;
|
||||||
|
|
||||||
@ -21,11 +20,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
ScaleContainer.Child = new SkinnableDrawable(new CatchSkinComponent(CatchSkinComponents.Droplet), _ => new Pulp
|
ScaleContainer.Child = new SkinnableDrawable(new CatchSkinComponent(CatchSkinComponents.Droplet), _ => new DropletPiece());
|
||||||
{
|
|
||||||
Size = Size / 4,
|
|
||||||
AccentColour = { BindTarget = AccentColour }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateInitialTransforms()
|
protected override void UpdateInitialTransforms()
|
||||||
|
@ -8,7 +8,7 @@ using osu.Game.Skinning;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableFruit : PalpableCatchHitObject<Fruit>
|
public class DrawableFruit : PalpableDrawableCatchHitObject<Fruit>
|
||||||
{
|
{
|
||||||
public DrawableFruit(Fruit h)
|
public DrawableFruit(Fruit h)
|
||||||
: base(h)
|
: base(h)
|
||||||
|
69
osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs
Normal file
69
osu.Game.Rulesets.Catch/Objects/Drawables/DropletPiece.cs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
|
{
|
||||||
|
public class DropletPiece : CompositeDrawable
|
||||||
|
{
|
||||||
|
public DropletPiece()
|
||||||
|
{
|
||||||
|
Size = new Vector2(CatchHitObject.OBJECT_RADIUS / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(DrawableHitObject drawableObject)
|
||||||
|
{
|
||||||
|
DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
|
||||||
|
var hitObject = drawableCatchObject.HitObject;
|
||||||
|
|
||||||
|
InternalChild = new Pulp
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
AccentColour = { BindTarget = drawableObject.AccentColour }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (hitObject.HyperDash)
|
||||||
|
{
|
||||||
|
AddInternal(new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Size = new Vector2(2f),
|
||||||
|
Depth = 1,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
||||||
|
BorderThickness = 6,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
AlwaysPresent = true,
|
||||||
|
Alpha = 0.3f,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
@ -21,11 +20,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
public const float RADIUS_ADJUST = 1.1f;
|
public const float RADIUS_ADJUST = 1.1f;
|
||||||
|
|
||||||
private Circle border;
|
private Circle border;
|
||||||
|
|
||||||
private CatchHitObject hitObject;
|
private CatchHitObject hitObject;
|
||||||
|
|
||||||
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
|
||||||
|
|
||||||
public FruitPiece()
|
public FruitPiece()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
@ -37,8 +33,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
|
DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
|
||||||
hitObject = drawableCatchObject.HitObject;
|
hitObject = drawableCatchObject.HitObject;
|
||||||
|
|
||||||
accentColour.BindTo(drawableCatchObject.AccentColour);
|
|
||||||
|
|
||||||
AddRangeInternal(new[]
|
AddRangeInternal(new[]
|
||||||
{
|
{
|
||||||
getFruitFor(drawableCatchObject.HitObject.VisualRepresentation),
|
getFruitFor(drawableCatchObject.HitObject.VisualRepresentation),
|
||||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
|
|||||||
EdgeEffect = new EdgeEffectParameters
|
EdgeEffect = new EdgeEffectParameters
|
||||||
{
|
{
|
||||||
Type = EdgeEffectType.Glow,
|
Type = EdgeEffectType.Glow,
|
||||||
Radius = Size.X / 2,
|
Radius = DrawWidth / 2,
|
||||||
Colour = colour.NewValue.Darken(0.2f).Opacity(0.75f)
|
Colour = colour.NewValue.Darken(0.2f).Opacity(0.75f)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ using osu.Game.Rulesets.Judgements;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects
|
namespace osu.Game.Rulesets.Catch.Objects
|
||||||
{
|
{
|
||||||
public class Droplet : CatchHitObject
|
public class Droplet : PalpableCatchHitObject
|
||||||
{
|
{
|
||||||
public override Judgement CreateJudgement() => new CatchDropletJudgement();
|
public override Judgement CreateJudgement() => new CatchDropletJudgement();
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ using osu.Game.Rulesets.Judgements;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects
|
namespace osu.Game.Rulesets.Catch.Objects
|
||||||
{
|
{
|
||||||
public class Fruit : CatchHitObject
|
public class Fruit : PalpableCatchHitObject
|
||||||
{
|
{
|
||||||
public override Judgement CreateJudgement() => new CatchJudgement();
|
public override Judgement CreateJudgement() => new CatchJudgement();
|
||||||
}
|
}
|
||||||
|
@ -35,18 +35,15 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override List<IInput> GetPendingInputs()
|
public override void CollectPendingInputs(List<IInput> inputs)
|
||||||
{
|
{
|
||||||
if (!Position.HasValue) return new List<IInput>();
|
if (!Position.HasValue) return;
|
||||||
|
|
||||||
return new List<IInput>
|
inputs.Add(new CatchReplayState
|
||||||
{
|
|
||||||
new CatchReplayState
|
|
||||||
{
|
{
|
||||||
PressedActions = CurrentFrame?.Actions ?? new List<CatchAction>(),
|
PressedActions = CurrentFrame?.Actions ?? new List<CatchAction>(),
|
||||||
CatcherX = Position.Value
|
CatcherX = Position.Value
|
||||||
},
|
});
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CatchReplayState : ReplayState<CatchAction>
|
public class CatchReplayState : ReplayState<CatchAction>
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"Mappings": [{
|
||||||
|
"StartTime": 3368,
|
||||||
|
"Objects": [{
|
||||||
|
"StartTime": 3368,
|
||||||
|
"Position": 374
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"StartTime": 3501,
|
||||||
|
"Objects": [{
|
||||||
|
"StartTime": 3501,
|
||||||
|
"Position": 446
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
osu file format v14
|
||||||
|
|
||||||
|
[General]
|
||||||
|
StackLeniency: 0.7
|
||||||
|
Mode: 2
|
||||||
|
|
||||||
|
[Difficulty]
|
||||||
|
HPDrainRate:6
|
||||||
|
CircleSize:4
|
||||||
|
OverallDifficulty:9.6
|
||||||
|
ApproachRate:9.6
|
||||||
|
SliderMultiplier:1.9
|
||||||
|
SliderTickRate:1
|
||||||
|
|
||||||
|
[TimingPoints]
|
||||||
|
2169,266.666666666667,4,2,1,70,1,0
|
||||||
|
|
||||||
|
[HitObjects]
|
||||||
|
374,60,3368,1,0,0:0:0:0:
|
||||||
|
410,146,3501,1,2,0:1:0:0:
|
@ -6,6 +6,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Skinning
|
namespace osu.Game.Rulesets.Catch.Skinning
|
||||||
{
|
{
|
||||||
@ -61,7 +62,12 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
|||||||
switch (lookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
case CatchSkinColour colour:
|
case CatchSkinColour colour:
|
||||||
return Source.GetConfig<SkinCustomColourLookup, TValue>(new SkinCustomColourLookup(colour));
|
var result = (Bindable<Color4>)Source.GetConfig<SkinCustomColourLookup, TValue>(new SkinCustomColourLookup(colour));
|
||||||
|
if (result == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
result.Value = LegacyColourCompatibility.DisallowZeroAlpha(result.Value);
|
||||||
|
return (IBindable<TValue>)result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Source.GetConfig<TLookup, TValue>(lookup);
|
return Source.GetConfig<TLookup, TValue>(lookup);
|
||||||
|
@ -40,7 +40,6 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
|||||||
colouredSprite = new Sprite
|
colouredSprite = new Sprite
|
||||||
{
|
{
|
||||||
Texture = skin.GetTexture(lookupName),
|
Texture = skin.GetTexture(lookupName),
|
||||||
Colour = drawableObject.AccentColour.Value,
|
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
},
|
},
|
||||||
@ -76,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
accentColour.BindValueChanged(colour => colouredSprite.Colour = colour.NewValue, true);
|
accentColour.BindValueChanged(colour => colouredSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,22 +35,25 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation)
|
public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation)
|
||||||
{
|
{
|
||||||
Container explodingFruitContainer;
|
var explodingFruitContainer = new Container
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
|
||||||
{
|
|
||||||
explodingFruitContainer = new Container
|
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
};
|
||||||
|
|
||||||
CatcherArea = new CatcherArea(difficulty)
|
CatcherArea = new CatcherArea(difficulty)
|
||||||
{
|
{
|
||||||
CreateDrawableRepresentation = createDrawableRepresentation,
|
CreateDrawableRepresentation = createDrawableRepresentation,
|
||||||
ExplodingFruitTarget = explodingFruitContainer,
|
ExplodingFruitTarget = explodingFruitContainer,
|
||||||
Anchor = Anchor.BottomLeft,
|
Anchor = Anchor.BottomLeft,
|
||||||
Origin = Anchor.TopLeft,
|
Origin = Anchor.TopLeft,
|
||||||
},
|
};
|
||||||
HitObjectContainer
|
|
||||||
|
InternalChildren = new[]
|
||||||
|
{
|
||||||
|
explodingFruitContainer,
|
||||||
|
CatcherArea.MovableCatcher.CreateProxiedContent(),
|
||||||
|
HitObjectContainer,
|
||||||
|
CatcherArea
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,15 +10,21 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
public class CatchPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer
|
public class CatchPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer
|
||||||
{
|
{
|
||||||
|
private const float playfield_size_adjust = 0.8f;
|
||||||
|
|
||||||
protected override Container<Drawable> Content => content;
|
protected override Container<Drawable> Content => content;
|
||||||
private readonly Container content;
|
private readonly Container content;
|
||||||
|
|
||||||
public CatchPlayfieldAdjustmentContainer()
|
public CatchPlayfieldAdjustmentContainer()
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre;
|
// because we are using centre anchor/origin, we will need to limit visibility in the future
|
||||||
Origin = Anchor.TopCentre;
|
// to ensure tall windows do not get a readability advantage.
|
||||||
|
// it may be possible to bake the catch-specific offsets (-100..340 mentioned below) into new values
|
||||||
|
// which are compatible with TopCentre alignment.
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
Size = new Vector2(0.86f); // matches stable's vertical offset for catcher plate
|
Size = new Vector2(playfield_size_adjust);
|
||||||
|
|
||||||
InternalChild = new Container
|
InternalChild = new Container
|
||||||
{
|
{
|
||||||
@ -27,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
FillMode = FillMode.Fit,
|
FillMode = FillMode.Fit,
|
||||||
FillAspectRatio = 4f / 3,
|
FillAspectRatio = 4f / 3,
|
||||||
Child = content = new ScalingContainer { RelativeSizeAxes = Axes.Both }
|
Child = content = new ScalingContainer { RelativeSizeAxes = Axes.Both, }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,8 +46,14 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
|
// in stable, fruit fall vertically from -100 to 340.
|
||||||
|
// to emulate this, we want to make our playfield 440 gameplay pixels high.
|
||||||
|
// we then offset it -100 vertically in the position set below.
|
||||||
|
const float stable_v_offset_ratio = 440 / 384f;
|
||||||
|
|
||||||
Scale = new Vector2(Parent.ChildSize.X / CatchPlayfield.WIDTH);
|
Scale = new Vector2(Parent.ChildSize.X / CatchPlayfield.WIDTH);
|
||||||
Size = Vector2.Divide(Vector2.One, Scale);
|
Position = new Vector2(0, -100 * stable_v_offset_ratio + Scale.X);
|
||||||
|
Size = Vector2.Divide(new Vector2(1, stable_v_offset_ratio), Scale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,14 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
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.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.Configuration;
|
||||||
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.Catch.Skinning;
|
||||||
@ -46,6 +48,12 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
public Container ExplodingFruitTarget;
|
public Container ExplodingFruitTarget;
|
||||||
|
|
||||||
|
private Container<DrawableHitObject> caughtFruitContainer { get; } = new Container<DrawableHitObject>
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
};
|
||||||
|
|
||||||
[NotNull]
|
[NotNull]
|
||||||
private readonly Container trailsTarget;
|
private readonly Container trailsTarget;
|
||||||
|
|
||||||
@ -56,7 +64,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The width of the catcher which can receive fruit. Equivalent to "catchMargin" in osu-stable.
|
/// The width of the catcher which can receive fruit. Equivalent to "catchMargin" in osu-stable.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const float allowed_catch_range = 0.8f;
|
public const float ALLOWED_CATCH_RANGE = 0.8f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The drawable catcher for <see cref="CurrentState"/>.
|
/// The drawable catcher for <see cref="CurrentState"/>.
|
||||||
@ -83,8 +91,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly float catchWidth;
|
private readonly float catchWidth;
|
||||||
|
|
||||||
private Container<DrawableHitObject> caughtFruit;
|
|
||||||
|
|
||||||
private CatcherSprite catcherIdle;
|
private CatcherSprite catcherIdle;
|
||||||
private CatcherSprite catcherKiai;
|
private CatcherSprite catcherKiai;
|
||||||
private CatcherSprite catcherFail;
|
private CatcherSprite catcherFail;
|
||||||
@ -99,6 +105,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
private double hyperDashModifier = 1;
|
private double hyperDashModifier = 1;
|
||||||
private int hyperDashDirection;
|
private int hyperDashDirection;
|
||||||
private float hyperDashTargetPosition;
|
private float hyperDashTargetPosition;
|
||||||
|
private Bindable<bool> hitLighting;
|
||||||
|
|
||||||
public Catcher([NotNull] Container trailsTarget, BeatmapDifficulty difficulty = null)
|
public Catcher([NotNull] Container trailsTarget, BeatmapDifficulty difficulty = null)
|
||||||
{
|
{
|
||||||
@ -114,15 +121,13 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(OsuConfigManager config)
|
||||||
{
|
{
|
||||||
|
hitLighting = config.GetBindable<bool>(OsuSetting.HitLighting);
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
caughtFruit = new Container<DrawableHitObject>
|
caughtFruitContainer,
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
},
|
|
||||||
catcherIdle = new CatcherSprite(CatcherAnimationState.Idle)
|
catcherIdle = new CatcherSprite(CatcherAnimationState.Idle)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
@ -145,6 +150,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
updateCatcher();
|
updateCatcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates proxied content to be displayed beneath hitobjects.
|
||||||
|
/// </summary>
|
||||||
|
public Drawable CreateProxiedContent() => caughtFruitContainer.CreateProxy();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the scale of the catcher based off the provided beatmap difficulty.
|
/// Calculates the scale of the catcher based off the provided beatmap difficulty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -156,7 +166,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="scale">The scale of the catcher.</param>
|
/// <param name="scale">The scale of the catcher.</param>
|
||||||
internal static float CalculateCatchWidth(Vector2 scale)
|
internal static float CalculateCatchWidth(Vector2 scale)
|
||||||
=> CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * allowed_catch_range;
|
=> CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * ALLOWED_CATCH_RANGE;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the width of the area used for attempting catches in gameplay.
|
/// Calculates the width of the area used for attempting catches in gameplay.
|
||||||
@ -176,7 +186,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
const float allowance = 10;
|
const float allowance = 10;
|
||||||
|
|
||||||
while (caughtFruit.Any(f =>
|
while (caughtFruitContainer.Any(f =>
|
||||||
f.LifetimeEnd == double.MaxValue &&
|
f.LifetimeEnd == double.MaxValue &&
|
||||||
Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2)))
|
Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2)))
|
||||||
{
|
{
|
||||||
@ -187,14 +197,17 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
fruit.X = Math.Clamp(fruit.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2);
|
fruit.X = Math.Clamp(fruit.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2);
|
||||||
|
|
||||||
caughtFruit.Add(fruit);
|
caughtFruitContainer.Add(fruit);
|
||||||
|
|
||||||
|
if (hitLighting.Value)
|
||||||
|
{
|
||||||
AddInternal(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)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Let the catcher attempt to catch a fruit.
|
/// Let the catcher attempt to catch a fruit.
|
||||||
@ -203,6 +216,9 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// <returns>Whether the catch is possible.</returns>
|
/// <returns>Whether the catch is possible.</returns>
|
||||||
public bool AttemptCatch(CatchHitObject fruit)
|
public bool AttemptCatch(CatchHitObject fruit)
|
||||||
{
|
{
|
||||||
|
if (!fruit.CanBePlated)
|
||||||
|
return false;
|
||||||
|
|
||||||
var halfCatchWidth = catchWidth * 0.5f;
|
var halfCatchWidth = catchWidth * 0.5f;
|
||||||
|
|
||||||
// this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
|
// this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
|
||||||
@ -213,9 +229,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
catchObjectPosition >= catcherPosition - halfCatchWidth &&
|
catchObjectPosition >= catcherPosition - halfCatchWidth &&
|
||||||
catchObjectPosition <= catcherPosition + halfCatchWidth;
|
catchObjectPosition <= catcherPosition + halfCatchWidth;
|
||||||
|
|
||||||
// only update hyperdash state if we are catching a fruit.
|
// only update hyperdash state if we are not catching a tiny droplet.
|
||||||
// exceptions are Droplets and JuiceStreams.
|
if (fruit is TinyDroplet) return validCatch;
|
||||||
if (!(fruit is Fruit)) return validCatch;
|
|
||||||
|
|
||||||
if (validCatch && fruit.HyperDash)
|
if (validCatch && fruit.HyperDash)
|
||||||
{
|
{
|
||||||
@ -270,8 +285,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
private void runHyperDashStateTransition(bool hyperDashing)
|
private void runHyperDashStateTransition(bool hyperDashing)
|
||||||
{
|
{
|
||||||
trails.HyperDashTrailsColour = hyperDashColour;
|
|
||||||
trails.EndGlowSpritesColour = hyperDashEndGlowColour;
|
|
||||||
updateTrailVisibility();
|
updateTrailVisibility();
|
||||||
|
|
||||||
if (hyperDashing)
|
if (hyperDashing)
|
||||||
@ -342,7 +355,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Drop()
|
public void Drop()
|
||||||
{
|
{
|
||||||
foreach (var f in caughtFruit.ToArray())
|
foreach (var f in caughtFruitContainer.ToArray())
|
||||||
Drop(f);
|
Drop(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -351,7 +364,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Explode()
|
public void Explode()
|
||||||
{
|
{
|
||||||
foreach (var f in caughtFruit.ToArray())
|
foreach (var f in caughtFruitContainer.ToArray())
|
||||||
Explode(f);
|
Explode(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -388,6 +401,9 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashAfterImage)?.Value ??
|
skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashAfterImage)?.Value ??
|
||||||
hyperDashColour;
|
hyperDashColour;
|
||||||
|
|
||||||
|
trails.HyperDashTrailsColour = hyperDashColour;
|
||||||
|
trails.EndGlowSpritesColour = hyperDashEndGlowColour;
|
||||||
|
|
||||||
runHyperDashStateTransition(HyperDashing);
|
runHyperDashStateTransition(HyperDashing);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -450,9 +466,9 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
if (ExplodingFruitTarget != null)
|
if (ExplodingFruitTarget != null)
|
||||||
{
|
{
|
||||||
fruit.Anchor = Anchor.TopLeft;
|
fruit.Anchor = Anchor.TopLeft;
|
||||||
fruit.Position = caughtFruit.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget);
|
fruit.Position = caughtFruitContainer.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget);
|
||||||
|
|
||||||
if (!caughtFruit.Remove(fruit))
|
if (!caughtFruitContainer.Remove(fruit))
|
||||||
// we may have already been removed by a previous operation (due to the weird OnLoadComplete scheduling).
|
// we may have already been removed by a previous operation (due to the weird OnLoadComplete scheduling).
|
||||||
// this avoids a crash on potentially attempting to Add a fruit to ExplodingFruitTarget twice.
|
// this avoids a crash on potentially attempting to Add a fruit to ExplodingFruitTarget twice.
|
||||||
return;
|
return;
|
||||||
|
@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
public Func<CatchHitObject, DrawableHitObject<CatchHitObject>> CreateDrawableRepresentation;
|
public Func<CatchHitObject, DrawableHitObject<CatchHitObject>> CreateDrawableRepresentation;
|
||||||
|
|
||||||
|
public readonly Catcher MovableCatcher;
|
||||||
|
|
||||||
public Container ExplodingFruitTarget
|
public Container ExplodingFruitTarget
|
||||||
{
|
{
|
||||||
set => MovableCatcher.ExplodingFruitTarget = value;
|
set => MovableCatcher.ExplodingFruitTarget = value;
|
||||||
@ -53,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
lastPlateableFruit.OnLoadComplete += _ => action();
|
lastPlateableFruit.OnLoadComplete += _ => action();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.IsHit && fruit.CanBePlated)
|
if (result.IsHit && fruit.HitObject.CanBePlated)
|
||||||
{
|
{
|
||||||
// create a new (cloned) fruit to stay on the plate. the original is faded out immediately.
|
// create a new (cloned) fruit to stay on the plate. the original is faded out immediately.
|
||||||
var caughtFruit = (DrawableCatchHitObject)CreateDrawableRepresentation?.Invoke(fruit.HitObject);
|
var caughtFruit = (DrawableCatchHitObject)CreateDrawableRepresentation?.Invoke(fruit.HitObject);
|
||||||
@ -104,7 +106,5 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
if (state?.CatcherX != null)
|
if (state?.CatcherX != null)
|
||||||
MovableCatcher.X = state.CatcherX.Value;
|
MovableCatcher.X = state.CatcherX.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected internal readonly Catcher MovableCatcher;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
private readonly Container<CatcherTrailSprite> hyperDashTrails;
|
private readonly Container<CatcherTrailSprite> hyperDashTrails;
|
||||||
private readonly Container<CatcherTrailSprite> endGlowSprites;
|
private readonly Container<CatcherTrailSprite> endGlowSprites;
|
||||||
|
|
||||||
private Color4 hyperDashTrailsColour;
|
private Color4 hyperDashTrailsColour = Catcher.DEFAULT_HYPER_DASH_COLOUR;
|
||||||
|
|
||||||
public Color4 HyperDashTrailsColour
|
public Color4 HyperDashTrailsColour
|
||||||
{
|
{
|
||||||
@ -35,11 +35,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
hyperDashTrailsColour = value;
|
hyperDashTrailsColour = value;
|
||||||
hyperDashTrails.FadeColour(hyperDashTrailsColour, Catcher.HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
hyperDashTrails.Colour = hyperDashTrailsColour;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Color4 endGlowSpritesColour;
|
private Color4 endGlowSpritesColour = Catcher.DEFAULT_HYPER_DASH_COLOUR;
|
||||||
|
|
||||||
public Color4 EndGlowSpritesColour
|
public Color4 EndGlowSpritesColour
|
||||||
{
|
{
|
||||||
@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
endGlowSpritesColour = value;
|
endGlowSpritesColour = value;
|
||||||
endGlowSprites.FadeColour(endGlowSpritesColour, Catcher.HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
endGlowSprites.Colour = endGlowSpritesColour;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(ManiaModPerfect) })]
|
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(ManiaModPerfect) })]
|
||||||
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(ManiaModDoubleTime), typeof(ManiaModPerfect) })]
|
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(ManiaModDoubleTime), typeof(ManiaModPerfect) })]
|
||||||
[TestCase(LegacyMods.Random | LegacyMods.SuddenDeath, new[] { typeof(ManiaModRandom), typeof(ManiaModSuddenDeath) })]
|
[TestCase(LegacyMods.Random | LegacyMods.SuddenDeath, new[] { typeof(ManiaModRandom), typeof(ManiaModSuddenDeath) })]
|
||||||
|
[TestCase(LegacyMods.Flashlight | LegacyMods.Mirror, new[] { typeof(ManiaModFlashlight), typeof(ManiaModMirror) })]
|
||||||
public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods);
|
public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods);
|
||||||
|
|
||||||
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Rulesets.Mania.Mods;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||||
|
{
|
||||||
|
public class TestSceneManiaModInvert : ModTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInversion() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new ManiaModInvert(),
|
||||||
|
PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -22,18 +22,22 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
[Cached]
|
[Cached]
|
||||||
private readonly Column column;
|
private readonly Column column;
|
||||||
|
|
||||||
public ColumnTestContainer(int column, ManiaAction action)
|
public ColumnTestContainer(int column, ManiaAction action, bool showColumn = false)
|
||||||
|
{
|
||||||
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
this.column = new Column(column)
|
this.column = new Column(column)
|
||||||
{
|
{
|
||||||
Action = { Value = action },
|
Action = { Value = action },
|
||||||
AccentColour = Color4.Orange,
|
AccentColour = Color4.Orange,
|
||||||
ColumnType = column % 2 == 0 ? ColumnType.Even : ColumnType.Odd
|
ColumnType = column % 2 == 0 ? ColumnType.Even : ColumnType.Odd,
|
||||||
};
|
Alpha = showColumn ? 1 : 0
|
||||||
|
},
|
||||||
InternalChild = content = new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4)
|
content = new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
|
},
|
||||||
|
this.column.TopLevelContainer.CreateProxy()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
Direction = FillDirection.Horizontal,
|
Direction = FillDirection.Horizontal,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new ColumnTestContainer(0, ManiaAction.Key1)
|
new ColumnTestContainer(0, ManiaAction.Key1, true)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
}));
|
}));
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
new ColumnTestContainer(1, ManiaAction.Key2)
|
new ColumnTestContainer(1, ManiaAction.Key2, true)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
@ -1,23 +1,27 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Pooling;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Mania.Judgements;
|
||||||
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.Rulesets.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneHitExplosion : ManiaSkinnableTestScene
|
public class TestSceneHitExplosion : ManiaSkinnableTestScene
|
||||||
{
|
{
|
||||||
|
private readonly List<DrawablePool<PoolableHitExplosion>> hitExplosionPools = new List<DrawablePool<PoolableHitExplosion>>();
|
||||||
|
|
||||||
public TestSceneHitExplosion()
|
public TestSceneHitExplosion()
|
||||||
{
|
{
|
||||||
int runcount = 0;
|
int runcount = 0;
|
||||||
@ -29,28 +33,40 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
if (runcount % 15 > 12)
|
if (runcount % 15 > 12)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
CreatedDrawables.OfType<Container>().ForEach(c =>
|
int poolIndex = 0;
|
||||||
|
|
||||||
|
foreach (var c in CreatedDrawables.OfType<Container>())
|
||||||
{
|
{
|
||||||
c.Add(new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion, 0),
|
c.Add(hitExplosionPools[poolIndex].Get(e =>
|
||||||
_ => new DefaultHitExplosion((runcount / 15) % 2 == 0 ? new Color4(94, 0, 57, 255) : new Color4(6, 84, 0, 255), runcount % 6 != 0)
|
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
e.Apply(new JudgementResult(new HitObject(), runcount % 6 == 0 ? new HoldNoteTickJudgement() : new ManiaJudgement()));
|
||||||
Origin = Anchor.Centre,
|
|
||||||
|
e.Anchor = Anchor.Centre;
|
||||||
|
e.Origin = Anchor.Centre;
|
||||||
}));
|
}));
|
||||||
});
|
|
||||||
|
poolIndex++;
|
||||||
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
SetContents(() => new ColumnTestContainer(0, ManiaAction.Key1)
|
SetContents(() =>
|
||||||
|
{
|
||||||
|
var pool = new DrawablePool<PoolableHitExplosion>(5);
|
||||||
|
hitExplosionPools.Add(pool);
|
||||||
|
|
||||||
|
return new ColumnTestContainer(0, ManiaAction.Key1)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativePositionAxes = Axes.Y,
|
RelativePositionAxes = Axes.Y,
|
||||||
Y = -0.25f,
|
Y = -0.25f,
|
||||||
Size = new Vector2(Column.COLUMN_WIDTH, DefaultNotePiece.NOTE_HEIGHT),
|
Size = new Vector2(Column.COLUMN_WIDTH, DefaultNotePiece.NOTE_HEIGHT),
|
||||||
|
Child = pool
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.UI.Components;
|
using osu.Game.Rulesets.Mania.UI.Components;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
@ -13,7 +14,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground), _ => new DefaultStageBackground())
|
SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageBackground, stageDefinition: new StageDefinition { Columns = 4 }),
|
||||||
|
_ => new DefaultStageBackground())
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||||
@ -12,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground), _ => null)
|
SetContents(() => new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.StageForeground, stageDefinition: new StageDefinition { Columns = 4 }), _ => null)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -10,6 +11,8 @@ using osu.Game.Replays;
|
|||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Replays;
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
|
using osu.Game.Rulesets.Mania.Scoring;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
@ -236,6 +239,53 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
assertTailJudgement(HitResult.Meh);
|
assertTailJudgement(HitResult.Meh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMissReleaseAndHitSecondRelease()
|
||||||
|
{
|
||||||
|
var windows = new ManiaHitWindows();
|
||||||
|
windows.SetDifficulty(10);
|
||||||
|
|
||||||
|
var beatmap = new Beatmap<ManiaHitObject>
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HoldNote
|
||||||
|
{
|
||||||
|
StartTime = 1000,
|
||||||
|
Duration = 500,
|
||||||
|
Column = 0,
|
||||||
|
},
|
||||||
|
new HoldNote
|
||||||
|
{
|
||||||
|
StartTime = 1000 + 500 + windows.WindowFor(HitResult.Miss) + 10,
|
||||||
|
Duration = 500,
|
||||||
|
Column = 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
BaseDifficulty = new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
SliderTickRate = 4,
|
||||||
|
OverallDifficulty = 10,
|
||||||
|
},
|
||||||
|
Ruleset = new ManiaRuleset().RulesetInfo
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(beatmap.HitObjects[1].StartTime, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(beatmap.HitObjects[1].GetEndTime()),
|
||||||
|
}, beatmap);
|
||||||
|
|
||||||
|
AddAssert("first hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[0].NestedHitObjects.Contains(j.HitObject))
|
||||||
|
.All(j => j.Type == HitResult.Miss));
|
||||||
|
|
||||||
|
AddAssert("second hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[1].NestedHitObjects.Contains(j.HitObject))
|
||||||
|
.All(j => j.Type == HitResult.Perfect));
|
||||||
|
}
|
||||||
|
|
||||||
private void assertHeadJudgement(HitResult result)
|
private void assertHeadJudgement(HitResult result)
|
||||||
=> AddAssert($"head judged as {result}", () => judgementResults[0].Type == result);
|
=> AddAssert($"head judged as {result}", () => judgementResults[0].Type == result);
|
||||||
|
|
||||||
@ -250,11 +300,11 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
private ScoreAccessibleReplayPlayer currentPlayer;
|
private ScoreAccessibleReplayPlayer currentPlayer;
|
||||||
|
|
||||||
private void performTest(List<ReplayFrame> frames)
|
private void performTest(List<ReplayFrame> frames, Beatmap<ManiaHitObject> beatmap = null)
|
||||||
{
|
{
|
||||||
AddStep("load player", () =>
|
if (beatmap == null)
|
||||||
{
|
{
|
||||||
Beatmap.Value = CreateWorkingBeatmap(new Beatmap<ManiaHitObject>
|
beatmap = new Beatmap<ManiaHitObject>
|
||||||
{
|
{
|
||||||
HitObjects =
|
HitObjects =
|
||||||
{
|
{
|
||||||
@ -270,9 +320,14 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
||||||
Ruleset = new ManiaRuleset().RulesetInfo
|
Ruleset = new ManiaRuleset().RulesetInfo
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
|
beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
|
||||||
|
}
|
||||||
|
|
||||||
|
AddStep("load player", () =>
|
||||||
|
{
|
||||||
|
Beatmap.Value = CreateWorkingBeatmap(beatmap);
|
||||||
|
|
||||||
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
||||||
|
|
||||||
|
117
osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs
Normal file
117
osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Extensions.TypeExtensions;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Replays;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
|
using osu.Game.Rulesets.Replays;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneOutOfOrderHits : RateAdjustedBeatmapTestScene
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestPreviousHitWindowDoesNotExtendPastNextObject()
|
||||||
|
{
|
||||||
|
var objects = new List<ManiaHitObject>();
|
||||||
|
var frames = new List<ReplayFrame>();
|
||||||
|
|
||||||
|
for (int i = 0; i < 7; i++)
|
||||||
|
{
|
||||||
|
double time = 1000 + i * 100;
|
||||||
|
|
||||||
|
objects.Add(new Note { StartTime = time });
|
||||||
|
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
frames.Add(new ManiaReplayFrame(time + 10, ManiaAction.Key1));
|
||||||
|
frames.Add(new ManiaReplayFrame(time + 11));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
performTest(objects, frames);
|
||||||
|
|
||||||
|
addJudgementAssert(objects[0], HitResult.Miss);
|
||||||
|
|
||||||
|
for (int i = 1; i < 7; i++)
|
||||||
|
{
|
||||||
|
addJudgementAssert(objects[i], HitResult.Perfect);
|
||||||
|
addJudgementOffsetAssert(objects[i], 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addJudgementAssert(ManiaHitObject hitObject, HitResult result)
|
||||||
|
{
|
||||||
|
AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}",
|
||||||
|
() => judgementResults.Single(r => r.HitObject == hitObject).Type == result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addJudgementOffsetAssert(ManiaHitObject hitObject, double offset)
|
||||||
|
{
|
||||||
|
AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judged at {offset}",
|
||||||
|
() => Precision.AlmostEquals(judgementResults.Single(r => r.HitObject == hitObject).TimeOffset, offset, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ScoreAccessibleReplayPlayer currentPlayer;
|
||||||
|
private List<JudgementResult> judgementResults;
|
||||||
|
|
||||||
|
private void performTest(List<ManiaHitObject> hitObjects, List<ReplayFrame> frames)
|
||||||
|
{
|
||||||
|
AddStep("load player", () =>
|
||||||
|
{
|
||||||
|
Beatmap.Value = CreateWorkingBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 })
|
||||||
|
{
|
||||||
|
HitObjects = hitObjects,
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
Ruleset = new ManiaRuleset().RulesetInfo
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
|
||||||
|
|
||||||
|
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
||||||
|
|
||||||
|
p.OnLoadComplete += _ =>
|
||||||
|
{
|
||||||
|
p.ScoreProcessor.NewJudgement += result =>
|
||||||
|
{
|
||||||
|
if (currentPlayer == p) judgementResults.Add(result);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
LoadScreen(currentPlayer = p);
|
||||||
|
judgementResults = new List<JudgementResult>();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||||
|
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
||||||
|
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||||
|
{
|
||||||
|
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||||
|
|
||||||
|
protected override bool PauseOnFocusLost => false;
|
||||||
|
|
||||||
|
public ScoreAccessibleReplayPlayer(Score score)
|
||||||
|
: base(score, false, false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
|
{
|
||||||
|
public class TestScenePlayfieldCoveringContainer : OsuTestScene
|
||||||
|
{
|
||||||
|
private readonly ScrollingTestContainer scrollingContainer;
|
||||||
|
private readonly PlayfieldCoveringWrapper cover;
|
||||||
|
|
||||||
|
public TestScenePlayfieldCoveringContainer()
|
||||||
|
{
|
||||||
|
Child = scrollingContainer = new ScrollingTestContainer(ScrollingDirection.Down)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(300, 500),
|
||||||
|
Child = cover = new PlayfieldCoveringWrapper(new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.Orange
|
||||||
|
})
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScrollingDownwards()
|
||||||
|
{
|
||||||
|
AddStep("set down scroll", () => scrollingContainer.Direction = ScrollingDirection.Down);
|
||||||
|
AddStep("set coverage = 0.5", () => cover.Coverage = 0.5f);
|
||||||
|
AddStep("set coverage = 0.8f", () => cover.Coverage = 0.8f);
|
||||||
|
AddStep("set coverage = 0.2f", () => cover.Coverage = 0.2f);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScrollingUpwards()
|
||||||
|
{
|
||||||
|
AddStep("set up scroll", () => scrollingContainer.Direction = ScrollingDirection.Up);
|
||||||
|
AddStep("set coverage = 0.5", () => cover.Coverage = 0.5f);
|
||||||
|
AddStep("set coverage = 0.8f", () => cover.Coverage = 0.8f);
|
||||||
|
AddStep("set coverage = 0.2f", () => cover.Coverage = 0.2f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,9 @@
|
|||||||
<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.6.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.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.17.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
@ -41,14 +40,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
new BeatmapStatistic
|
new BeatmapStatistic
|
||||||
{
|
{
|
||||||
Name = @"Note Count",
|
Name = @"Note Count",
|
||||||
|
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles),
|
||||||
Content = notes.ToString(),
|
Content = notes.ToString(),
|
||||||
Icon = FontAwesome.Regular.Circle
|
|
||||||
},
|
},
|
||||||
new BeatmapStatistic
|
new BeatmapStatistic
|
||||||
{
|
{
|
||||||
Name = @"Hold Note Count",
|
Name = @"Hold Note Count",
|
||||||
|
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
|
||||||
Content = holdnotes.ToString(),
|
Content = holdnotes.ToString(),
|
||||||
Icon = FontAwesome.Regular.Circle
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -21,14 +21,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="column">The 0-based column index.</param>
|
/// <param name="column">The 0-based column index.</param>
|
||||||
/// <returns>Whether the column is a special column.</returns>
|
/// <returns>Whether the column is a special column.</returns>
|
||||||
public bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2;
|
public readonly bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the type of column given a column index.
|
/// Get the type of column given a column index.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="column">The 0-based column index.</param>
|
/// <param name="column">The 0-based column index.</param>
|
||||||
/// <returns>The type of the column.</returns>
|
/// <returns>The type of the column.</returns>
|
||||||
public ColumnType GetTypeOfColumn(int column)
|
public readonly ColumnType GetTypeOfColumn(int column)
|
||||||
{
|
{
|
||||||
if (IsSpecialColumn(column))
|
if (IsSpecialColumn(column))
|
||||||
return ColumnType.Special;
|
return ColumnType.Special;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
||||||
|
|
||||||
@ -15,7 +16,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components
|
|||||||
AccentColour.Value = colours.Yellow;
|
AccentColour.Value = colours.Yellow;
|
||||||
|
|
||||||
Background.Alpha = 0.5f;
|
Background.Alpha = 0.5f;
|
||||||
Foreground.Alpha = 0;
|
}
|
||||||
}
|
|
||||||
|
protected override Drawable CreateForeground() => base.CreateForeground().With(d => d.Alpha = 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Judgements
|
|||||||
{
|
{
|
||||||
public class HoldNoteTickJudgement : ManiaJudgement
|
public class HoldNoteTickJudgement : ManiaJudgement
|
||||||
{
|
{
|
||||||
protected override int NumericResultFor(HitResult result) => 20;
|
protected override int NumericResultFor(HitResult result) => result == MaxResult ? 20 : 0;
|
||||||
|
|
||||||
protected override double HealthIncreaseFor(HitResult result)
|
protected override double HealthIncreaseFor(HitResult result)
|
||||||
{
|
{
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Judgements
|
|||||||
return 300;
|
return 300;
|
||||||
|
|
||||||
case HitResult.Perfect:
|
case HitResult.Perfect:
|
||||||
return 320;
|
return 350;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ using System.Linq;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Mania.Replays;
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
using osu.Game.Rulesets.Replays.Types;
|
using osu.Game.Rulesets.Replays.Types;
|
||||||
@ -34,6 +35,7 @@ using osu.Game.Screens.Ranking.Statistics;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania
|
namespace osu.Game.Rulesets.Mania
|
||||||
{
|
{
|
||||||
|
[ExcludeFromDynamicCompile]
|
||||||
public class ManiaRuleset : Ruleset, ILegacyRuleset
|
public class ManiaRuleset : Ruleset, ILegacyRuleset
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -124,6 +126,9 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
|
|
||||||
if (mods.HasFlag(LegacyMods.Random))
|
if (mods.HasFlag(LegacyMods.Random))
|
||||||
yield return new ManiaModRandom();
|
yield return new ManiaModRandom();
|
||||||
|
|
||||||
|
if (mods.HasFlag(LegacyMods.Mirror))
|
||||||
|
yield return new ManiaModMirror();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override LegacyMods ConvertToLegacyMods(Mod[] mods)
|
public override LegacyMods ConvertToLegacyMods(Mod[] mods)
|
||||||
@ -173,6 +178,10 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
case ManiaModFadeIn _:
|
case ManiaModFadeIn _:
|
||||||
value |= LegacyMods.FadeIn;
|
value |= LegacyMods.FadeIn;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ManiaModMirror _:
|
||||||
|
value |= LegacyMods.Mirror;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,6 +227,7 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
new ManiaModDualStages(),
|
new ManiaModDualStages(),
|
||||||
new ManiaModMirror(),
|
new ManiaModMirror(),
|
||||||
new ManiaModDifficultyAdjust(),
|
new ManiaModDifficultyAdjust(),
|
||||||
|
new ManiaModInvert(),
|
||||||
};
|
};
|
||||||
|
|
||||||
case ModType.Automation:
|
case ModType.Automation:
|
||||||
@ -323,6 +333,16 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
Height = 250
|
Height = 250
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
new StatisticRow
|
||||||
|
{
|
||||||
|
Columns = new[]
|
||||||
|
{
|
||||||
|
new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[]
|
||||||
|
{
|
||||||
|
new UnstableRate(score.HitEvents)
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
@ -14,15 +15,23 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly int? TargetColumn;
|
public readonly int? TargetColumn;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The intended <see cref="StageDefinition"/> for this component.
|
||||||
|
/// May be null if the component is not a direct member of a <see cref="Stage"/>.
|
||||||
|
/// </summary>
|
||||||
|
public readonly StageDefinition? StageDefinition;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="ManiaSkinComponent"/>.
|
/// Creates a new <see cref="ManiaSkinComponent"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="component">The component.</param>
|
/// <param name="component">The component.</param>
|
||||||
/// <param name="targetColumn">The intended <see cref="Column"/> index for this component. May be null if the component does not exist in a <see cref="Column"/>.</param>
|
/// <param name="targetColumn">The intended <see cref="Column"/> index for this component. May be null if the component does not exist in a <see cref="Column"/>.</param>
|
||||||
public ManiaSkinComponent(ManiaSkinComponents component, int? targetColumn = null)
|
/// <param name="stageDefinition">The intended <see cref="StageDefinition"/> for this component. May be null if the component is not a direct member of a <see cref="Stage"/>.</param>
|
||||||
|
public ManiaSkinComponent(ManiaSkinComponents component, int? targetColumn = null, StageDefinition? stageDefinition = null)
|
||||||
: base(component)
|
: base(component)
|
||||||
{
|
{
|
||||||
TargetColumn = targetColumn;
|
TargetColumn = targetColumn;
|
||||||
|
StageDefinition = stageDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string RulesetPrefix => ManiaRuleset.SHORT_NAME;
|
protected override string RulesetPrefix => ManiaRuleset.SHORT_NAME;
|
||||||
|
@ -1,23 +1,19 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModFadeIn : Mod
|
public class ManiaModFadeIn : ManiaModHidden
|
||||||
{
|
{
|
||||||
public override string Name => "Fade In";
|
public override string Name => "Fade In";
|
||||||
public override string Acronym => "FI";
|
public override string Acronym => "FI";
|
||||||
public override IconUsage? Icon => OsuIcon.ModHidden;
|
public override IconUsage? Icon => OsuIcon.ModHidden;
|
||||||
public override ModType Type => ModType.DifficultyIncrease;
|
|
||||||
public override string Description => @"Keys appear out of nowhere!";
|
public override string Description => @"Keys appear out of nowhere!";
|
||||||
public override double ScoreMultiplier => 1;
|
|
||||||
public override bool Ranked => true;
|
protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AlongScroll;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight<ManiaHitObject>) };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,44 @@
|
|||||||
// 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.Linq;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModHidden : ModHidden
|
public class ManiaModHidden : ModHidden, IApplicableToDrawableRuleset<ManiaHitObject>
|
||||||
{
|
{
|
||||||
public override string Description => @"Keys fade out before you hit them!";
|
public override string Description => @"Keys fade out before you hit them!";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight<ManiaHitObject>) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight<ManiaHitObject>) };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The direction in which the cover should expand.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll;
|
||||||
|
|
||||||
|
public virtual void ApplyToDrawableRuleset(DrawableRuleset<ManiaHitObject> drawableRuleset)
|
||||||
|
{
|
||||||
|
ManiaPlayfield maniaPlayfield = (ManiaPlayfield)drawableRuleset.Playfield;
|
||||||
|
|
||||||
|
foreach (Column column in maniaPlayfield.Stages.SelectMany(stage => stage.Columns))
|
||||||
|
{
|
||||||
|
HitObjectContainer hoc = column.HitObjectArea.HitObjectContainer;
|
||||||
|
Container hocParent = (Container)hoc.Parent;
|
||||||
|
|
||||||
|
hocParent.Remove(hoc);
|
||||||
|
hocParent.Add(new PlayfieldCoveringWrapper(hoc).With(c =>
|
||||||
|
{
|
||||||
|
c.RelativeSizeAxes = Axes.Both;
|
||||||
|
c.Direction = ExpandDirection;
|
||||||
|
c.Coverage = 0.5f;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
76
osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs
Normal file
76
osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
|
{
|
||||||
|
public class ManiaModInvert : Mod, IApplicableAfterBeatmapConversion
|
||||||
|
{
|
||||||
|
public override string Name => "Invert";
|
||||||
|
|
||||||
|
public override string Acronym => "IN";
|
||||||
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
|
public override string Description => "Hold the keys. To the beat.";
|
||||||
|
|
||||||
|
public override IconUsage? Icon => FontAwesome.Solid.YinYang;
|
||||||
|
|
||||||
|
public override ModType Type => ModType.Conversion;
|
||||||
|
|
||||||
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
||||||
|
|
||||||
|
var newObjects = new List<ManiaHitObject>();
|
||||||
|
|
||||||
|
foreach (var column in maniaBeatmap.HitObjects.GroupBy(h => h.Column))
|
||||||
|
{
|
||||||
|
var newColumnObjects = new List<ManiaHitObject>();
|
||||||
|
|
||||||
|
var locations = column.OfType<Note>().Select(n => (startTime: n.StartTime, samples: n.Samples))
|
||||||
|
.Concat(column.OfType<HoldNote>().SelectMany(h => new[]
|
||||||
|
{
|
||||||
|
(startTime: h.StartTime, samples: h.GetNodeSamples(0)),
|
||||||
|
(startTime: h.EndTime, samples: h.GetNodeSamples(1))
|
||||||
|
}))
|
||||||
|
.OrderBy(h => h.startTime).ToList();
|
||||||
|
|
||||||
|
for (int i = 0; i < locations.Count - 1; i++)
|
||||||
|
{
|
||||||
|
// Full duration of the hold note.
|
||||||
|
double duration = locations[i + 1].startTime - locations[i].startTime;
|
||||||
|
|
||||||
|
// Beat length at the end of the hold note.
|
||||||
|
double beatLength = beatmap.ControlPointInfo.TimingPointAt(locations[i + 1].startTime).BeatLength;
|
||||||
|
|
||||||
|
// Decrease the duration by at most a 1/4 beat to ensure there's no instantaneous notes.
|
||||||
|
duration = Math.Max(duration / 2, duration - beatLength / 4);
|
||||||
|
|
||||||
|
newColumnObjects.Add(new HoldNote
|
||||||
|
{
|
||||||
|
Column = column.Key,
|
||||||
|
StartTime = locations[i].startTime,
|
||||||
|
Duration = duration,
|
||||||
|
NodeSamples = new List<IList<HitSampleInfo>> { locations[i].samples, Array.Empty<HitSampleInfo>() }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
newObjects.AddRange(newColumnObjects);
|
||||||
|
}
|
||||||
|
|
||||||
|
maniaBeatmap.HitObjects = newObjects.OrderBy(h => h.StartTime).ToList();
|
||||||
|
|
||||||
|
// No breaks
|
||||||
|
maniaBeatmap.Breaks.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
||||||
@ -32,7 +33,17 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
private readonly Container<DrawableHoldNoteTail> tailContainer;
|
private readonly Container<DrawableHoldNoteTail> tailContainer;
|
||||||
private readonly Container<DrawableHoldNoteTick> tickContainer;
|
private readonly Container<DrawableHoldNoteTick> tickContainer;
|
||||||
|
|
||||||
private readonly Drawable bodyPiece;
|
/// <summary>
|
||||||
|
/// Contains the size of the hold note covering the whole head/tail bounds. The size of this container changes as the hold note is being pressed.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Container sizingContainer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains the contents of the hold note that should be masked as the hold note is being pressed. Follows changes in the size of <see cref="sizingContainer"/>.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Container maskingContainer;
|
||||||
|
|
||||||
|
private readonly SkinnableDrawable bodyPiece;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Time at which the user started holding this hold note. Null if the user is not holding this hold note.
|
/// Time at which the user started holding this hold note. Null if the user is not holding this hold note.
|
||||||
@ -44,24 +55,54 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HasBroken { get; private set; }
|
public bool HasBroken { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the hold note has been released potentially without having caused a break.
|
||||||
|
/// </summary>
|
||||||
|
private double? releaseTime;
|
||||||
|
|
||||||
public DrawableHoldNote(HoldNote hitObject)
|
public DrawableHoldNote(HoldNote hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
|
|
||||||
AddRangeInternal(new[]
|
Container maskedContents;
|
||||||
|
|
||||||
|
AddRangeInternal(new Drawable[]
|
||||||
{
|
{
|
||||||
|
sizingContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
maskingContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = maskedContents = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
headContainer = new Container<DrawableHoldNoteHead> { RelativeSizeAxes = Axes.Both }
|
||||||
|
}
|
||||||
|
},
|
||||||
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.Both,
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X
|
RelativeSizeAxes = Axes.X
|
||||||
},
|
},
|
||||||
tickContainer = new Container<DrawableHoldNoteTick> { RelativeSizeAxes = Axes.Both },
|
tickContainer = new Container<DrawableHoldNoteTick> { RelativeSizeAxes = Axes.Both },
|
||||||
headContainer = new Container<DrawableHoldNoteHead> { RelativeSizeAxes = Axes.Both },
|
|
||||||
tailContainer = new Container<DrawableHoldNoteTail> { RelativeSizeAxes = Axes.Both },
|
tailContainer = new Container<DrawableHoldNoteTail> { RelativeSizeAxes = Axes.Both },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
maskedContents.AddRange(new[]
|
||||||
|
{
|
||||||
|
bodyPiece.CreateProxy(),
|
||||||
|
tickContainer.CreateProxy(),
|
||||||
|
tailContainer.CreateProxy(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||||
@ -127,7 +168,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
{
|
{
|
||||||
base.OnDirectionChanged(e);
|
base.OnDirectionChanged(e);
|
||||||
|
|
||||||
bodyPiece.Anchor = bodyPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
|
if (e.NewValue == ScrollingDirection.Up)
|
||||||
|
{
|
||||||
|
bodyPiece.Anchor = bodyPiece.Origin = Anchor.TopLeft;
|
||||||
|
sizingContainer.Anchor = sizingContainer.Origin = Anchor.BottomLeft;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bodyPiece.Anchor = bodyPiece.Origin = Anchor.BottomLeft;
|
||||||
|
sizingContainer.Anchor = sizingContainer.Origin = Anchor.TopLeft;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void PlaySamples()
|
public override void PlaySamples()
|
||||||
@ -135,13 +185,48 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
// Samples are played by the head/tail notes.
|
// Samples are played by the head/tail notes.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void OnKilled()
|
||||||
|
{
|
||||||
|
base.OnKilled();
|
||||||
|
(bodyPiece.Drawable as IHoldNoteBody)?.Recycle();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
// Make the body piece not lie under the head note
|
if (Time.Current < releaseTime)
|
||||||
|
releaseTime = null;
|
||||||
|
|
||||||
|
// Pad the full size container so its contents (i.e. the masking container) reach under the tail.
|
||||||
|
// This is required for the tail to not be masked away, since it lies outside the bounds of the hold note.
|
||||||
|
sizingContainer.Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Top = Direction.Value == ScrollingDirection.Down ? -Tail.Height : 0,
|
||||||
|
Bottom = Direction.Value == ScrollingDirection.Up ? -Tail.Height : 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pad the masking container to the starting position of the body piece (half-way under the head).
|
||||||
|
// This is required to make the body start getting masked immediately as soon as the note is held.
|
||||||
|
maskingContainer.Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Top = Direction.Value == ScrollingDirection.Up ? Head.Height / 2 : 0,
|
||||||
|
Bottom = Direction.Value == ScrollingDirection.Down ? Head.Height / 2 : 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Position and resize the body to lie half-way under the head and the tail notes.
|
||||||
bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2;
|
bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2;
|
||||||
bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2;
|
bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2;
|
||||||
|
|
||||||
|
// As the note is being held, adjust the size of the sizing container. This has two effects:
|
||||||
|
// 1. The contained masking container will mask the body and ticks.
|
||||||
|
// 2. The head note will move along with the new "head position" in the container.
|
||||||
|
if (Head.IsHit && releaseTime == null)
|
||||||
|
{
|
||||||
|
// How far past the hit target this hold note is. Always a positive value.
|
||||||
|
float yOffset = Math.Max(0, Direction.Value == ScrollingDirection.Up ? -Y : Y);
|
||||||
|
sizingContainer.Height = Math.Clamp(1 - yOffset / DrawHeight, 0, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateStateTransforms(ArmedState state)
|
protected override void UpdateStateTransforms(ArmedState state)
|
||||||
@ -153,7 +238,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
{
|
{
|
||||||
if (Tail.AllJudged)
|
if (Tail.AllJudged)
|
||||||
|
{
|
||||||
ApplyResult(r => r.Type = HitResult.Perfect);
|
ApplyResult(r => r.Type = HitResult.Perfect);
|
||||||
|
endHold();
|
||||||
|
}
|
||||||
|
|
||||||
if (Tail.Result.Type == HitResult.Miss)
|
if (Tail.Result.Type == HitResult.Miss)
|
||||||
HasBroken = true;
|
HasBroken = true;
|
||||||
@ -167,6 +255,15 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
if (action != Action.Value)
|
if (action != Action.Value)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (CheckHittable?.Invoke(this, Time.Current) == false)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// The tail has a lenience applied to it which is factored into the miss window (i.e. the miss judgement will be delayed).
|
||||||
|
// But the hold cannot ever be started within the late-lenience window, so we should skip trying to begin the hold during that time.
|
||||||
|
// Note: Unlike below, we use the tail's start time to determine the time offset.
|
||||||
|
if (Time.Current > Tail.HitObject.StartTime && !Tail.HitObject.HitWindows.CanBeHit(Time.Current - Tail.HitObject.StartTime))
|
||||||
|
return false;
|
||||||
|
|
||||||
beginHoldAt(Time.Current - Head.HitObject.StartTime);
|
beginHoldAt(Time.Current - Head.HitObject.StartTime);
|
||||||
Head.UpdateResult();
|
Head.UpdateResult();
|
||||||
|
|
||||||
@ -200,6 +297,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
// If the key has been released too early, the user should not receive full score for the release
|
// If the key has been released too early, the user should not receive full score for the release
|
||||||
if (!Tail.IsHit)
|
if (!Tail.IsHit)
|
||||||
HasBroken = true;
|
HasBroken = true;
|
||||||
|
|
||||||
|
releaseTime = Time.Current;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void endHold()
|
private void endHold()
|
||||||
|
@ -1,6 +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 osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -17,6 +19,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
|
|
||||||
public void UpdateResult() => base.UpdateResult(true);
|
public void UpdateResult() => base.UpdateResult(true);
|
||||||
|
|
||||||
|
protected override void UpdateStateTransforms(ArmedState state)
|
||||||
|
{
|
||||||
|
// This hitobject should never expire, so this is just a safe maximum.
|
||||||
|
LifetimeEnd = LifetimeStart + 30000;
|
||||||
|
}
|
||||||
|
|
||||||
public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note
|
public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note
|
||||||
|
|
||||||
public override void OnReleased(ManiaAction action)
|
public override void OnReleased(ManiaAction action)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -8,6 +9,7 @@ using osu.Framework.Graphics;
|
|||||||
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.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||||
{
|
{
|
||||||
@ -34,6 +36,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this <see cref="DrawableManiaHitObject"/> can be hit, given a time value.
|
||||||
|
/// If non-null, judgements will be ignored whilst the function returns false.
|
||||||
|
/// </summary>
|
||||||
|
public Func<DrawableHitObject, double, bool> CheckHittable;
|
||||||
|
|
||||||
protected DrawableManiaHitObject(ManiaHitObject hitObject)
|
protected DrawableManiaHitObject(ManiaHitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
@ -120,10 +128,15 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case ArmedState.Hit:
|
case ArmedState.Hit:
|
||||||
this.FadeOut(150, Easing.OutQuint);
|
this.FadeOut();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Causes this <see cref="DrawableManiaHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>.
|
||||||
|
/// </summary>
|
||||||
|
public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class DrawableManiaHitObject<TObject> : DrawableManiaHitObject
|
public abstract class DrawableManiaHitObject<TObject> : DrawableManiaHitObject
|
||||||
|
@ -64,6 +64,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
if (action != Action.Value)
|
if (action != Action.Value)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (CheckHittable?.Invoke(this, Time.Current) == false)
|
||||||
|
return false;
|
||||||
|
|
||||||
return UpdateResult(true);
|
return UpdateResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,24 +19,17 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents length-wise portion of a hold note.
|
/// Represents length-wise portion of a hold note.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DefaultBodyPiece : CompositeDrawable
|
public class DefaultBodyPiece : CompositeDrawable, IHoldNoteBody
|
||||||
{
|
{
|
||||||
protected readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
protected readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
||||||
|
protected readonly IBindable<bool> IsHitting = new Bindable<bool>();
|
||||||
private readonly LayoutValue subtractionCache = new LayoutValue(Invalidation.DrawSize);
|
|
||||||
private readonly IBindable<bool> isHitting = new Bindable<bool>();
|
|
||||||
|
|
||||||
protected Drawable Background { get; private set; }
|
protected Drawable Background { get; private set; }
|
||||||
protected BufferedContainer Foreground { get; private set; }
|
private Container foregroundContainer;
|
||||||
|
|
||||||
private BufferedContainer subtractionContainer;
|
|
||||||
private Container subtractionLayer;
|
|
||||||
|
|
||||||
public DefaultBodyPiece()
|
public DefaultBodyPiece()
|
||||||
{
|
{
|
||||||
Blending = BlendingParameters.Additive;
|
Blending = BlendingParameters.Additive;
|
||||||
|
|
||||||
AddLayout(subtractionCache);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
@ -45,7 +38,54 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
|||||||
InternalChildren = new[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
Background = new Box { RelativeSizeAxes = Axes.Both },
|
Background = new Box { RelativeSizeAxes = Axes.Both },
|
||||||
Foreground = new BufferedContainer
|
foregroundContainer = new Container { RelativeSizeAxes = Axes.Both }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (drawableObject != null)
|
||||||
|
{
|
||||||
|
var holdNote = (DrawableHoldNote)drawableObject;
|
||||||
|
|
||||||
|
AccentColour.BindTo(drawableObject.AccentColour);
|
||||||
|
IsHitting.BindTo(holdNote.IsHitting);
|
||||||
|
}
|
||||||
|
|
||||||
|
AccentColour.BindValueChanged(onAccentChanged, true);
|
||||||
|
|
||||||
|
Recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Recycle() => foregroundContainer.Child = CreateForeground();
|
||||||
|
|
||||||
|
protected virtual Drawable CreateForeground() => new ForegroundPiece
|
||||||
|
{
|
||||||
|
AccentColour = { BindTarget = AccentColour },
|
||||||
|
IsHitting = { BindTarget = IsHitting }
|
||||||
|
};
|
||||||
|
|
||||||
|
private void onAccentChanged(ValueChangedEvent<Color4> accent) => Background.Colour = accent.NewValue.Opacity(0.7f);
|
||||||
|
|
||||||
|
private class ForegroundPiece : CompositeDrawable
|
||||||
|
{
|
||||||
|
public readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
||||||
|
public readonly IBindable<bool> IsHitting = new Bindable<bool>();
|
||||||
|
|
||||||
|
private readonly LayoutValue subtractionCache = new LayoutValue(Invalidation.DrawSize);
|
||||||
|
|
||||||
|
private BufferedContainer foregroundBuffer;
|
||||||
|
private BufferedContainer subtractionBuffer;
|
||||||
|
private Container subtractionLayer;
|
||||||
|
|
||||||
|
public ForegroundPiece()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
AddLayout(subtractionCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
InternalChild = foregroundBuffer = new BufferedContainer
|
||||||
{
|
{
|
||||||
Blending = BlendingParameters.Additive,
|
Blending = BlendingParameters.Additive,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
@ -53,7 +93,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
|||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Box { RelativeSizeAxes = Axes.Both },
|
new Box { RelativeSizeAxes = Axes.Both },
|
||||||
subtractionContainer = new BufferedContainer
|
subtractionBuffer = new BufferedContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
// This is needed because we're blending with another object
|
// This is needed because we're blending with another object
|
||||||
@ -77,36 +117,26 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (drawableObject != null)
|
|
||||||
{
|
|
||||||
var holdNote = (DrawableHoldNote)drawableObject;
|
|
||||||
|
|
||||||
AccentColour.BindTo(drawableObject.AccentColour);
|
|
||||||
isHitting.BindTo(holdNote.IsHitting);
|
|
||||||
}
|
|
||||||
|
|
||||||
AccentColour.BindValueChanged(onAccentChanged, true);
|
AccentColour.BindValueChanged(onAccentChanged, true);
|
||||||
isHitting.BindValueChanged(_ => onAccentChanged(new ValueChangedEvent<Color4>(AccentColour.Value, AccentColour.Value)), true);
|
IsHitting.BindValueChanged(_ => onAccentChanged(new ValueChangedEvent<Color4>(AccentColour.Value, AccentColour.Value)), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onAccentChanged(ValueChangedEvent<Color4> accent)
|
private void onAccentChanged(ValueChangedEvent<Color4> accent)
|
||||||
{
|
{
|
||||||
Foreground.Colour = accent.NewValue.Opacity(0.5f);
|
foregroundBuffer.Colour = accent.NewValue.Opacity(0.5f);
|
||||||
Background.Colour = accent.NewValue.Opacity(0.7f);
|
|
||||||
|
|
||||||
const float animation_length = 50;
|
const float animation_length = 50;
|
||||||
|
|
||||||
Foreground.ClearTransforms(false, nameof(Foreground.Colour));
|
foregroundBuffer.ClearTransforms(false, nameof(foregroundBuffer.Colour));
|
||||||
|
|
||||||
if (isHitting.Value)
|
if (IsHitting.Value)
|
||||||
{
|
{
|
||||||
// wait for the next sync point
|
// wait for the next sync point
|
||||||
double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
|
double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
|
||||||
using (Foreground.BeginDelayedSequence(synchronisedOffset))
|
using (foregroundBuffer.BeginDelayedSequence(synchronisedOffset))
|
||||||
Foreground.FadeColour(accent.NewValue.Lighten(0.2f), animation_length).Then().FadeColour(Foreground.Colour, animation_length).Loop();
|
foregroundBuffer.FadeColour(accent.NewValue.Lighten(0.2f), animation_length).Then().FadeColour(foregroundBuffer.Colour, animation_length).Loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
subtractionCache.Invalidate();
|
subtractionCache.Invalidate();
|
||||||
@ -127,11 +157,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
|||||||
Radius = DrawWidth
|
Radius = DrawWidth
|
||||||
};
|
};
|
||||||
|
|
||||||
Foreground.ForceRedraw();
|
foregroundBuffer.ForceRedraw();
|
||||||
subtractionContainer.ForceRedraw();
|
subtractionBuffer.ForceRedraw();
|
||||||
|
|
||||||
subtractionCache.Validate();
|
subtractionCache.Validate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for mania hold note bodies.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHoldNoteBody
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Recycles the contents of this <see cref="IHoldNoteBody"/> to free used resources.
|
||||||
|
/// </summary>
|
||||||
|
void Recycle();
|
||||||
|
}
|
||||||
|
}
|
@ -102,14 +102,14 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
{
|
{
|
||||||
StartTime = StartTime,
|
StartTime = StartTime,
|
||||||
Column = Column,
|
Column = Column,
|
||||||
Samples = getNodeSamples(0),
|
Samples = GetNodeSamples(0),
|
||||||
});
|
});
|
||||||
|
|
||||||
AddNested(Tail = new TailNote
|
AddNested(Tail = new TailNote
|
||||||
{
|
{
|
||||||
StartTime = EndTime,
|
StartTime = EndTime,
|
||||||
Column = Column,
|
Column = Column,
|
||||||
Samples = getNodeSamples((NodeSamples?.Count - 1) ?? 1),
|
Samples = GetNodeSamples((NodeSamples?.Count - 1) ?? 1),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
|
|
||||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||||
|
|
||||||
private IList<HitSampleInfo> getNodeSamples(int nodeIndex) =>
|
public IList<HitSampleInfo> GetNodeSamples(int nodeIndex) =>
|
||||||
nodeIndex < NodeSamples?.Count ? NodeSamples[nodeIndex] : Samples;
|
nodeIndex < NodeSamples?.Count ? NodeSamples[nodeIndex] : Samples;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,9 @@ namespace osu.Game.Rulesets.Mania.Replays
|
|||||||
|
|
||||||
protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any();
|
protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any();
|
||||||
|
|
||||||
public override List<IInput> GetPendingInputs() => new List<IInput> { new ReplayState<ManiaAction> { PressedActions = CurrentFrame?.Actions ?? new List<ManiaAction>() } };
|
public override void CollectPendingInputs(List<IInput> inputs)
|
||||||
|
{
|
||||||
|
inputs.Add(new ReplayState<ManiaAction> { PressedActions = CurrentFrame?.Actions ?? new List<ManiaAction>() });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,10 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
|||||||
{
|
{
|
||||||
internal class ManiaScoreProcessor : ScoreProcessor
|
internal class ManiaScoreProcessor : ScoreProcessor
|
||||||
{
|
{
|
||||||
|
protected override double DefaultAccuracyPortion => 0.95;
|
||||||
|
|
||||||
|
protected override double DefaultComboPortion => 0.05;
|
||||||
|
|
||||||
public override HitWindows CreateHitWindows() => new ManiaHitWindows();
|
public override HitWindows CreateHitWindows() => new ManiaHitWindows();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
46
osu.Game.Rulesets.Mania/Skinning/HitTargetInsetContainer.cs
Normal file
46
osu.Game.Rulesets.Mania/Skinning/HitTargetInsetContainer.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning
|
||||||
|
{
|
||||||
|
public class HitTargetInsetContainer : Container
|
||||||
|
{
|
||||||
|
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content => content;
|
||||||
|
private readonly Container content;
|
||||||
|
|
||||||
|
private float hitPosition;
|
||||||
|
|
||||||
|
public HitTargetInsetContainer()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChild = content = new Container { RelativeSizeAxes = Axes.Both };
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
|
||||||
|
{
|
||||||
|
hitPosition = skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.HitPosition)?.Value ?? Stage.HIT_TARGET_POSITION;
|
||||||
|
|
||||||
|
direction.BindTo(scrollingInfo.Direction);
|
||||||
|
direction.BindValueChanged(onDirectionChanged, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||||
|
{
|
||||||
|
content.Padding = direction.NewValue == ScrollingDirection.Up
|
||||||
|
? new MarginPadding { Top = hitPosition }
|
||||||
|
: new MarginPadding { Bottom = hitPosition };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +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;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Animations;
|
using osu.Framework.Graphics.Animations;
|
||||||
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
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;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
@ -18,7 +21,14 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||||
private readonly IBindable<bool> isHitting = new Bindable<bool>();
|
private readonly IBindable<bool> isHitting = new Bindable<bool>();
|
||||||
|
|
||||||
private Drawable sprite;
|
[CanBeNull]
|
||||||
|
private Drawable bodySprite;
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
private Drawable lightContainer;
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
private Drawable light;
|
||||||
|
|
||||||
public LegacyBodyPiece()
|
public LegacyBodyPiece()
|
||||||
{
|
{
|
||||||
@ -31,7 +41,39 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
string imageName = GetColumnSkinConfig<string>(skin, LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage)?.Value
|
string imageName = GetColumnSkinConfig<string>(skin, LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage)?.Value
|
||||||
?? $"mania-note{FallbackColumnIndex}L";
|
?? $"mania-note{FallbackColumnIndex}L";
|
||||||
|
|
||||||
sprite = skin.GetAnimation(imageName, true, true).With(d =>
|
string lightImage = GetColumnSkinConfig<string>(skin, LegacyManiaSkinConfigurationLookups.HoldNoteLightImage)?.Value
|
||||||
|
?? "lightingL";
|
||||||
|
|
||||||
|
float lightScale = GetColumnSkinConfig<float>(skin, LegacyManiaSkinConfigurationLookups.HoldNoteLightScale)?.Value
|
||||||
|
?? 1;
|
||||||
|
|
||||||
|
// Create a temporary animation to retrieve the number of frames, in an effort to calculate the intended frame length.
|
||||||
|
// This animation is discarded and re-queried with the appropriate frame length afterwards.
|
||||||
|
var tmp = skin.GetAnimation(lightImage, true, false);
|
||||||
|
double frameLength = 0;
|
||||||
|
if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0)
|
||||||
|
frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount);
|
||||||
|
|
||||||
|
light = skin.GetAnimation(lightImage, true, true, frameLength: frameLength).With(d =>
|
||||||
|
{
|
||||||
|
if (d == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
d.Origin = Anchor.Centre;
|
||||||
|
d.Blending = BlendingParameters.Additive;
|
||||||
|
d.Scale = new Vector2(lightScale);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (light != null)
|
||||||
|
{
|
||||||
|
lightContainer = new HitTargetInsetContainer
|
||||||
|
{
|
||||||
|
Alpha = 0,
|
||||||
|
Child = light
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
bodySprite = skin.GetAnimation(imageName, WrapMode.ClampToEdge, WrapMode.ClampToEdge, true, true).With(d =>
|
||||||
{
|
{
|
||||||
if (d == null)
|
if (d == null)
|
||||||
return;
|
return;
|
||||||
@ -46,8 +88,8 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
// Todo: Wrap
|
// Todo: Wrap
|
||||||
});
|
});
|
||||||
|
|
||||||
if (sprite != null)
|
if (bodySprite != null)
|
||||||
InternalChild = sprite;
|
InternalChild = bodySprite;
|
||||||
|
|
||||||
direction.BindTo(scrollingInfo.Direction);
|
direction.BindTo(scrollingInfo.Direction);
|
||||||
direction.BindValueChanged(onDirectionChanged, true);
|
direction.BindValueChanged(onDirectionChanged, true);
|
||||||
@ -59,28 +101,68 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
|
|
||||||
private void onIsHittingChanged(ValueChangedEvent<bool> isHitting)
|
private void onIsHittingChanged(ValueChangedEvent<bool> isHitting)
|
||||||
{
|
{
|
||||||
if (!(sprite is TextureAnimation animation))
|
if (bodySprite is TextureAnimation bodyAnimation)
|
||||||
|
{
|
||||||
|
bodyAnimation.GotoFrame(0);
|
||||||
|
bodyAnimation.IsPlaying = isHitting.NewValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lightContainer == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
animation.GotoFrame(0);
|
if (isHitting.NewValue)
|
||||||
animation.IsPlaying = isHitting.NewValue;
|
{
|
||||||
|
// Clear the fade out and, more importantly, the removal.
|
||||||
|
lightContainer.ClearTransforms();
|
||||||
|
|
||||||
|
// Only add the container if the removal has taken place.
|
||||||
|
if (lightContainer.Parent == null)
|
||||||
|
Column.TopLevelContainer.Add(lightContainer);
|
||||||
|
|
||||||
|
// The light must be seeked only after being loaded, otherwise a nullref occurs (https://github.com/ppy/osu-framework/issues/3847).
|
||||||
|
if (light is TextureAnimation lightAnimation)
|
||||||
|
lightAnimation.GotoFrame(0);
|
||||||
|
|
||||||
|
lightContainer.FadeIn(80);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lightContainer.FadeOut(120)
|
||||||
|
.OnComplete(d => Column.TopLevelContainer.Remove(d));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||||
{
|
{
|
||||||
if (sprite == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (direction.NewValue == ScrollingDirection.Up)
|
if (direction.NewValue == ScrollingDirection.Up)
|
||||||
{
|
{
|
||||||
sprite.Origin = Anchor.BottomCentre;
|
if (bodySprite != null)
|
||||||
sprite.Scale = new Vector2(1, -1);
|
{
|
||||||
|
bodySprite.Origin = Anchor.BottomCentre;
|
||||||
|
bodySprite.Scale = new Vector2(1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (light != null)
|
||||||
|
light.Anchor = Anchor.TopCentre;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sprite.Origin = Anchor.TopCentre;
|
if (bodySprite != null)
|
||||||
sprite.Scale = Vector2.One;
|
{
|
||||||
}
|
bodySprite.Origin = Anchor.TopCentre;
|
||||||
|
bodySprite.Scale = Vector2.One;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (light != null)
|
||||||
|
light.Anchor = Anchor.BottomCentre;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
lightContainer?.Expire();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
@ -18,14 +17,12 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
public class LegacyColumnBackground : LegacyManiaColumnElement, IKeyBindingHandler<ManiaAction>
|
public class LegacyColumnBackground : LegacyManiaColumnElement, IKeyBindingHandler<ManiaAction>
|
||||||
{
|
{
|
||||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||||
private readonly bool isLastColumn;
|
|
||||||
|
|
||||||
private Container lightContainer;
|
private Container lightContainer;
|
||||||
private Sprite light;
|
private Sprite light;
|
||||||
|
|
||||||
public LegacyColumnBackground(bool isLastColumn)
|
public LegacyColumnBackground()
|
||||||
{
|
{
|
||||||
this.isLastColumn = isLastColumn;
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,52 +32,14 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
string lightImage = skin.GetManiaSkinConfig<string>(LegacyManiaSkinConfigurationLookups.LightImage)?.Value
|
string lightImage = skin.GetManiaSkinConfig<string>(LegacyManiaSkinConfigurationLookups.LightImage)?.Value
|
||||||
?? "mania-stage-light";
|
?? "mania-stage-light";
|
||||||
|
|
||||||
float leftLineWidth = GetColumnSkinConfig<float>(skin, LegacyManiaSkinConfigurationLookups.LeftLineWidth)
|
|
||||||
?.Value ?? 1;
|
|
||||||
float rightLineWidth = GetColumnSkinConfig<float>(skin, LegacyManiaSkinConfigurationLookups.RightLineWidth)
|
|
||||||
?.Value ?? 1;
|
|
||||||
|
|
||||||
bool hasLeftLine = leftLineWidth > 0;
|
|
||||||
bool hasRightLine = rightLineWidth > 0 && skin.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m
|
|
||||||
|| isLastColumn;
|
|
||||||
|
|
||||||
float lightPosition = GetColumnSkinConfig<float>(skin, LegacyManiaSkinConfigurationLookups.LightPosition)?.Value
|
float lightPosition = GetColumnSkinConfig<float>(skin, LegacyManiaSkinConfigurationLookups.LightPosition)?.Value
|
||||||
?? 0;
|
?? 0;
|
||||||
|
|
||||||
Color4 lineColour = GetColumnSkinConfig<Color4>(skin, LegacyManiaSkinConfigurationLookups.ColumnLineColour)?.Value
|
|
||||||
?? Color4.White;
|
|
||||||
|
|
||||||
Color4 backgroundColour = GetColumnSkinConfig<Color4>(skin, LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour)?.Value
|
|
||||||
?? Color4.Black;
|
|
||||||
|
|
||||||
Color4 lightColour = GetColumnSkinConfig<Color4>(skin, LegacyManiaSkinConfigurationLookups.ColumnLightColour)?.Value
|
Color4 lightColour = GetColumnSkinConfig<Color4>(skin, LegacyManiaSkinConfigurationLookups.ColumnLightColour)?.Value
|
||||||
?? Color4.White;
|
?? Color4.White;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = backgroundColour
|
|
||||||
},
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Y,
|
|
||||||
Width = leftLineWidth,
|
|
||||||
Scale = new Vector2(0.740f, 1),
|
|
||||||
Colour = lineColour,
|
|
||||||
Alpha = hasLeftLine ? 1 : 0
|
|
||||||
},
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopRight,
|
|
||||||
Origin = Anchor.TopRight,
|
|
||||||
RelativeSizeAxes = Axes.Y,
|
|
||||||
Width = rightLineWidth,
|
|
||||||
Scale = new Vector2(0.740f, 1),
|
|
||||||
Colour = lineColour,
|
|
||||||
Alpha = hasRightLine ? 1 : 0
|
|
||||||
},
|
|
||||||
lightContainer = new Container
|
lightContainer = new Container
|
||||||
{
|
{
|
||||||
Origin = Anchor.BottomCentre,
|
Origin = Anchor.BottomCentre,
|
||||||
@ -90,7 +49,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.BottomCentre,
|
Anchor = Anchor.BottomCentre,
|
||||||
Origin = Anchor.BottomCentre,
|
Origin = Anchor.BottomCentre,
|
||||||
Colour = lightColour,
|
Colour = LegacyColourCompatibility.DisallowZeroAlpha(lightColour),
|
||||||
Texture = skin.GetTexture(lightImage),
|
Texture = skin.GetTexture(lightImage),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Width = 1,
|
Width = 1,
|
||||||
|
@ -6,13 +6,16 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Animations;
|
using osu.Framework.Graphics.Animations;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Mania.Judgements;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Skinning
|
namespace osu.Game.Rulesets.Mania.Skinning
|
||||||
{
|
{
|
||||||
public class LegacyHitExplosion : LegacyManiaColumnElement
|
public class LegacyHitExplosion : LegacyManiaColumnElement, IHitExplosion
|
||||||
{
|
{
|
||||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||||
|
|
||||||
@ -62,9 +65,12 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
explosion.Anchor = direction.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
explosion.Anchor = direction.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
public void Animate(JudgementResult result)
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
if (result.Judgement is HoldNoteTickJudgement)
|
||||||
|
return;
|
||||||
|
|
||||||
|
(explosion as IFramedAnimation)?.GotoFrame(0);
|
||||||
|
|
||||||
explosion?.FadeInFromZero(80)
|
explosion?.FadeInFromZero(80)
|
||||||
.Then().FadeOut(120);
|
.Then().FadeOut(120);
|
||||||
|
@ -20,11 +20,6 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
|
|
||||||
private Container directionContainer;
|
private Container directionContainer;
|
||||||
|
|
||||||
public LegacyHitTarget()
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
|
private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
|
||||||
{
|
{
|
||||||
@ -56,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = 1,
|
Height = 1,
|
||||||
Colour = lineColour,
|
Colour = LegacyColourCompatibility.DisallowZeroAlpha(lineColour),
|
||||||
Alpha = showJudgementLine ? 0.9f : 0
|
Alpha = showJudgementLine ? 0.9f : 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,9 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
|
|
||||||
direction.BindTo(scrollingInfo.Direction);
|
direction.BindTo(scrollingInfo.Direction);
|
||||||
direction.BindValueChanged(onDirectionChanged, true);
|
direction.BindValueChanged(onDirectionChanged, true);
|
||||||
|
|
||||||
|
if (GetColumnSkinConfig<bool>(skin, LegacyManiaSkinConfigurationLookups.KeysUnderNotes)?.Value ?? false)
|
||||||
|
Column.UnderlayElements.Add(CreateProxy());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||||
|
@ -5,6 +5,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
@ -92,7 +93,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
string noteImage = GetColumnSkinConfig<string>(skin, lookup)?.Value
|
string noteImage = GetColumnSkinConfig<string>(skin, lookup)?.Value
|
||||||
?? $"mania-note{FallbackColumnIndex}{suffix}";
|
?? $"mania-note{FallbackColumnIndex}{suffix}";
|
||||||
|
|
||||||
return skin.GetTexture(noteImage);
|
return skin.GetTexture(noteImage, WrapMode.ClampToEdge, WrapMode.ClampToEdge);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,19 +4,27 @@
|
|||||||
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.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Skinning
|
namespace osu.Game.Rulesets.Mania.Skinning
|
||||||
{
|
{
|
||||||
public class LegacyStageBackground : CompositeDrawable
|
public class LegacyStageBackground : CompositeDrawable
|
||||||
{
|
{
|
||||||
|
private readonly StageDefinition stageDefinition;
|
||||||
|
|
||||||
private Drawable leftSprite;
|
private Drawable leftSprite;
|
||||||
private Drawable rightSprite;
|
private Drawable rightSprite;
|
||||||
|
private ColumnFlow<Drawable> columnBackgrounds;
|
||||||
|
|
||||||
public LegacyStageBackground()
|
public LegacyStageBackground(StageDefinition stageDefinition)
|
||||||
{
|
{
|
||||||
|
this.stageDefinition = stageDefinition;
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,8 +52,19 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
Origin = Anchor.TopLeft,
|
Origin = Anchor.TopLeft,
|
||||||
X = -0.05f,
|
X = -0.05f,
|
||||||
Texture = skin.GetTexture(rightImage)
|
Texture = skin.GetTexture(rightImage)
|
||||||
|
},
|
||||||
|
columnBackgrounds = new ColumnFlow<Drawable>(stageDefinition)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Y
|
||||||
|
},
|
||||||
|
new HitTargetInsetContainer
|
||||||
|
{
|
||||||
|
Child = new LegacyHitTarget { RelativeSizeAxes = Axes.Both }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < stageDefinition.Columns; i++)
|
||||||
|
columnBackgrounds.SetContentForColumn(i, new ColumnBackground(i, i == stageDefinition.Columns - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
@ -58,5 +77,72 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
if (rightSprite?.Height > 0)
|
if (rightSprite?.Height > 0)
|
||||||
rightSprite.Scale = new Vector2(1, DrawHeight / rightSprite.Height);
|
rightSprite.Scale = new Vector2(1, DrawHeight / rightSprite.Height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ColumnBackground : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly int columnIndex;
|
||||||
|
private readonly bool isLastColumn;
|
||||||
|
|
||||||
|
public ColumnBackground(int columnIndex, bool isLastColumn)
|
||||||
|
{
|
||||||
|
this.columnIndex = columnIndex;
|
||||||
|
this.isLastColumn = isLastColumn;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(ISkinSource skin)
|
||||||
|
{
|
||||||
|
float leftLineWidth = skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.LeftLineWidth, columnIndex)?.Value ?? 1;
|
||||||
|
float rightLineWidth = skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.RightLineWidth, columnIndex)?.Value ?? 1;
|
||||||
|
|
||||||
|
bool hasLeftLine = leftLineWidth > 0;
|
||||||
|
bool hasRightLine = rightLineWidth > 0 && skin.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m
|
||||||
|
|| isLastColumn;
|
||||||
|
|
||||||
|
Color4 lineColour = skin.GetManiaSkinConfig<Color4>(LegacyManiaSkinConfigurationLookups.ColumnLineColour, columnIndex)?.Value ?? Color4.White;
|
||||||
|
Color4 backgroundColour = skin.GetManiaSkinConfig<Color4>(LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour, columnIndex)?.Value ?? Color4.Black;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
LegacyColourCompatibility.ApplyWithDoubledAlpha(new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
}, backgroundColour),
|
||||||
|
new HitTargetInsetContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Width = leftLineWidth,
|
||||||
|
Scale = new Vector2(0.740f, 1),
|
||||||
|
Alpha = hasLeftLine ? 1 : 0,
|
||||||
|
Child = LegacyColourCompatibility.ApplyWithDoubledAlpha(new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
}, lineColour)
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Width = rightLineWidth,
|
||||||
|
Scale = new Vector2(0.740f, 1),
|
||||||
|
Alpha = hasRightLine ? 1 : 0,
|
||||||
|
Child = LegacyColourCompatibility.ApplyWithDoubledAlpha(new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
}, lineColour)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Rulesets.Objects.Legacy;
|
using osu.Game.Rulesets.Objects.Legacy;
|
||||||
@ -88,10 +89,12 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
switch (maniaComponent.Component)
|
switch (maniaComponent.Component)
|
||||||
{
|
{
|
||||||
case ManiaSkinComponents.ColumnBackground:
|
case ManiaSkinComponents.ColumnBackground:
|
||||||
return new LegacyColumnBackground(maniaComponent.TargetColumn == beatmap.TotalColumns - 1);
|
return new LegacyColumnBackground();
|
||||||
|
|
||||||
case ManiaSkinComponents.HitTarget:
|
case ManiaSkinComponents.HitTarget:
|
||||||
return new LegacyHitTarget();
|
// Legacy skins sandwich the hit target between the column background and the column light.
|
||||||
|
// To preserve this ordering, it's created manually inside LegacyStageBackground.
|
||||||
|
return Drawable.Empty();
|
||||||
|
|
||||||
case ManiaSkinComponents.KeyArea:
|
case ManiaSkinComponents.KeyArea:
|
||||||
return new LegacyKeyArea();
|
return new LegacyKeyArea();
|
||||||
@ -112,7 +115,8 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
return new LegacyHitExplosion();
|
return new LegacyHitExplosion();
|
||||||
|
|
||||||
case ManiaSkinComponents.StageBackground:
|
case ManiaSkinComponents.StageBackground:
|
||||||
return new LegacyStageBackground();
|
Debug.Assert(maniaComponent.StageDefinition != null);
|
||||||
|
return new LegacyStageBackground(maniaComponent.StageDefinition.Value);
|
||||||
|
|
||||||
case ManiaSkinComponents.StageForeground:
|
case ManiaSkinComponents.StageForeground:
|
||||||
return new LegacyStageForeground();
|
return new LegacyStageForeground();
|
||||||
|
@ -9,14 +9,15 @@ using osu.Game.Graphics;
|
|||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics.Pooling;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Mania.UI.Components;
|
using osu.Game.Rulesets.Mania.UI.Components;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.UI
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
{
|
{
|
||||||
@ -33,11 +34,12 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
public readonly Bindable<ManiaAction> Action = new Bindable<ManiaAction>();
|
public readonly Bindable<ManiaAction> Action = new Bindable<ManiaAction>();
|
||||||
|
|
||||||
private readonly ColumnHitObjectArea hitObjectArea;
|
public readonly ColumnHitObjectArea HitObjectArea;
|
||||||
|
|
||||||
internal readonly Container TopLevelContainer;
|
internal readonly Container TopLevelContainer;
|
||||||
|
private readonly DrawablePool<PoolableHitExplosion> hitExplosionPool;
|
||||||
|
private readonly OrderedHitPolicy hitPolicy;
|
||||||
|
|
||||||
public Container UnderlayElements => hitObjectArea.UnderlayElements;
|
public Container UnderlayElements => HitObjectArea.UnderlayElements;
|
||||||
|
|
||||||
public Column(int index)
|
public Column(int index)
|
||||||
{
|
{
|
||||||
@ -53,9 +55,10 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
InternalChildren = new[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
|
hitExplosionPool = new DrawablePool<PoolableHitExplosion>(5),
|
||||||
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
|
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
|
||||||
background.CreateProxy(),
|
background.CreateProxy(),
|
||||||
hitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both },
|
HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both },
|
||||||
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea, Index), _ => new DefaultKeyArea())
|
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea, Index), _ => new DefaultKeyArea())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
@ -64,10 +67,10 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
|
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
|
||||||
};
|
};
|
||||||
|
|
||||||
TopLevelContainer.Add(hitObjectArea.Explosions.CreateProxy());
|
hitPolicy = new OrderedHitPolicy(HitObjectContainer);
|
||||||
}
|
|
||||||
|
|
||||||
public override Axes RelativeSizeAxes => Axes.Y;
|
TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy());
|
||||||
|
}
|
||||||
|
|
||||||
public ColumnType ColumnType { get; set; }
|
public ColumnType ColumnType { get; set; }
|
||||||
|
|
||||||
@ -91,6 +94,9 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
hitObject.AccentColour.Value = AccentColour;
|
hitObject.AccentColour.Value = AccentColour;
|
||||||
hitObject.OnNewResult += OnNewResult;
|
hitObject.OnNewResult += OnNewResult;
|
||||||
|
|
||||||
|
DrawableManiaHitObject maniaObject = (DrawableManiaHitObject)hitObject;
|
||||||
|
maniaObject.CheckHittable = hitPolicy.IsHittable;
|
||||||
|
|
||||||
HitObjectContainer.Add(hitObject);
|
HitObjectContainer.Add(hitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,18 +111,13 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||||
{
|
{
|
||||||
|
if (result.IsHit)
|
||||||
|
hitPolicy.HandleHit(judgedObject);
|
||||||
|
|
||||||
if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value)
|
if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var explosion = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion, Index), _ =>
|
HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result)));
|
||||||
new DefaultHitExplosion(judgedObject.AccentColour.Value, judgedObject is DrawableHoldNoteTick))
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both
|
|
||||||
};
|
|
||||||
|
|
||||||
hitObjectArea.Explosions.Add(explosion);
|
|
||||||
|
|
||||||
explosion.Delay(200).Expire(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool OnPressed(ManiaAction action)
|
public bool OnPressed(ManiaAction action)
|
||||||
|
105
osu.Game.Rulesets.Mania/UI/ColumnFlow.cs
Normal file
105
osu.Game.Rulesets.Mania/UI/ColumnFlow.cs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
// 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.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Skinning;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="Drawable"/> which flows its contents according to the <see cref="Column"/>s in a <see cref="Stage"/>.
|
||||||
|
/// Content can be added to individual columns via <see cref="SetContentForColumn"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TContent">The type of content in each column.</typeparam>
|
||||||
|
public class ColumnFlow<TContent> : CompositeDrawable
|
||||||
|
where TContent : Drawable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// All contents added to this <see cref="ColumnFlow{TContent}"/>.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<TContent> Content => columns.Children.Select(c => c.Count == 0 ? null : (TContent)c.Child).ToList();
|
||||||
|
|
||||||
|
private readonly FillFlowContainer<Container> columns;
|
||||||
|
private readonly StageDefinition stageDefinition;
|
||||||
|
|
||||||
|
public ColumnFlow(StageDefinition stageDefinition)
|
||||||
|
{
|
||||||
|
this.stageDefinition = stageDefinition;
|
||||||
|
|
||||||
|
AutoSizeAxes = Axes.X;
|
||||||
|
|
||||||
|
InternalChild = columns = new FillFlowContainer<Container>
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
AutoSizeAxes = Axes.X,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < stageDefinition.Columns; i++)
|
||||||
|
columns.Add(new Container { RelativeSizeAxes = Axes.Y });
|
||||||
|
}
|
||||||
|
|
||||||
|
private ISkinSource currentSkin;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(ISkinSource skin)
|
||||||
|
{
|
||||||
|
currentSkin = skin;
|
||||||
|
|
||||||
|
skin.SourceChanged += onSkinChanged;
|
||||||
|
onSkinChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSkinChanged()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < stageDefinition.Columns; i++)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
float spacing = currentSkin.GetConfig<ManiaSkinConfigurationLookup, float>(
|
||||||
|
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnSpacing, i - 1))
|
||||||
|
?.Value ?? Stage.COLUMN_SPACING;
|
||||||
|
|
||||||
|
columns[i].Margin = new MarginPadding { Left = spacing };
|
||||||
|
}
|
||||||
|
|
||||||
|
float? width = currentSkin.GetConfig<ManiaSkinConfigurationLookup, float>(
|
||||||
|
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnWidth, i))
|
||||||
|
?.Value;
|
||||||
|
|
||||||
|
if (width == null)
|
||||||
|
// only used by default skin (legacy skins get defaults set in LegacyManiaSkinConfiguration)
|
||||||
|
columns[i].Width = stageDefinition.IsSpecialColumn(i) ? Column.SPECIAL_COLUMN_WIDTH : Column.COLUMN_WIDTH;
|
||||||
|
else
|
||||||
|
columns[i].Width = width.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the content of one of the columns of this <see cref="ColumnFlow{TContent}"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="column">The index of the column to set the content of.</param>
|
||||||
|
/// <param name="content">The content.</param>
|
||||||
|
public void SetContentForColumn(int column, TContent content) => columns[column].Child = content;
|
||||||
|
|
||||||
|
public new MarginPadding Padding
|
||||||
|
{
|
||||||
|
get => base.Padding;
|
||||||
|
set => base.Padding = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (currentSkin != null)
|
||||||
|
currentSkin.SourceChanged -= onSkinChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Mania.Skinning;
|
using osu.Game.Rulesets.Mania.Skinning;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
@ -14,12 +15,14 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
public class HitObjectArea : SkinReloadableDrawable
|
public class HitObjectArea : SkinReloadableDrawable
|
||||||
{
|
{
|
||||||
protected readonly IBindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
protected readonly IBindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
||||||
|
public readonly HitObjectContainer HitObjectContainer;
|
||||||
|
|
||||||
public HitObjectArea(HitObjectContainer hitObjectContainer)
|
public HitObjectArea(HitObjectContainer hitObjectContainer)
|
||||||
{
|
{
|
||||||
InternalChildren = new[]
|
InternalChild = new Container
|
||||||
{
|
{
|
||||||
hitObjectContainer,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = HitObjectContainer = hitObjectContainer
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Effects;
|
using osu.Framework.Graphics.Effects;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Mania.Judgements;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -15,35 +17,36 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.UI
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
{
|
{
|
||||||
public class DefaultHitExplosion : CompositeDrawable
|
public class DefaultHitExplosion : CompositeDrawable, IHitExplosion
|
||||||
{
|
{
|
||||||
|
private const float default_large_faint_size = 0.8f;
|
||||||
|
|
||||||
public override bool RemoveWhenNotAlive => true;
|
public override bool RemoveWhenNotAlive => true;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Column column { get; set; }
|
||||||
|
|
||||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||||
|
|
||||||
private readonly CircularContainer largeFaint;
|
private CircularContainer largeFaint;
|
||||||
private readonly CircularContainer mainGlow1;
|
private CircularContainer mainGlow1;
|
||||||
|
|
||||||
public DefaultHitExplosion(Color4 objectColour, bool isSmall = false)
|
public DefaultHitExplosion()
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = DefaultNotePiece.NOTE_HEIGHT;
|
Height = DefaultNotePiece.NOTE_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
// scale roughly in-line with visual appearance of notes
|
[BackgroundDependencyLoader]
|
||||||
Scale = new Vector2(1f, 0.6f);
|
private void load(IScrollingInfo scrollingInfo)
|
||||||
|
{
|
||||||
if (isSmall)
|
|
||||||
Scale *= 0.5f;
|
|
||||||
|
|
||||||
const float angle_variangle = 15; // should be less than 45
|
const float angle_variangle = 15; // should be less than 45
|
||||||
|
|
||||||
const float roundness = 80;
|
const float roundness = 80;
|
||||||
|
|
||||||
const float initial_height = 10;
|
const float initial_height = 10;
|
||||||
|
|
||||||
var colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1);
|
var colour = Interpolation.ValueAt(0.4f, column.AccentColour, Color4.White, 0, 1);
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -54,12 +57,12 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
// we want our size to be very small so the glow dominates it.
|
// we want our size to be very small so the glow dominates it.
|
||||||
Size = new Vector2(0.8f),
|
Size = new Vector2(default_large_faint_size),
|
||||||
Blending = BlendingParameters.Additive,
|
Blending = BlendingParameters.Additive,
|
||||||
EdgeEffect = new EdgeEffectParameters
|
EdgeEffect = new EdgeEffectParameters
|
||||||
{
|
{
|
||||||
Type = EdgeEffectType.Glow,
|
Type = EdgeEffectType.Glow,
|
||||||
Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
|
Colour = Interpolation.ValueAt(0.1f, column.AccentColour, Color4.White, 0, 1).Opacity(0.3f),
|
||||||
Roundness = 160,
|
Roundness = 160,
|
||||||
Radius = 200,
|
Radius = 200,
|
||||||
},
|
},
|
||||||
@ -74,7 +77,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
EdgeEffect = new EdgeEffectParameters
|
EdgeEffect = new EdgeEffectParameters
|
||||||
{
|
{
|
||||||
Type = EdgeEffectType.Glow,
|
Type = EdgeEffectType.Glow,
|
||||||
Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
|
Colour = Interpolation.ValueAt(0.6f, column.AccentColour, Color4.White, 0, 1),
|
||||||
Roundness = 20,
|
Roundness = 20,
|
||||||
Radius = 50,
|
Radius = 50,
|
||||||
},
|
},
|
||||||
@ -114,30 +117,11 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(IScrollingInfo scrollingInfo)
|
|
||||||
{
|
|
||||||
direction.BindTo(scrollingInfo.Direction);
|
direction.BindTo(scrollingInfo.Direction);
|
||||||
direction.BindValueChanged(onDirectionChanged, true);
|
direction.BindValueChanged(onDirectionChanged, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
const double duration = 200;
|
|
||||||
|
|
||||||
base.LoadComplete();
|
|
||||||
|
|
||||||
largeFaint
|
|
||||||
.ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
|
|
||||||
.FadeOut(duration * 2);
|
|
||||||
|
|
||||||
mainGlow1.ScaleTo(1.4f, duration, Easing.OutQuint);
|
|
||||||
|
|
||||||
this.FadeOut(duration, Easing.Out);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||||
{
|
{
|
||||||
if (direction.NewValue == ScrollingDirection.Up)
|
if (direction.NewValue == ScrollingDirection.Up)
|
||||||
@ -151,5 +135,29 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
Y = -DefaultNotePiece.NOTE_HEIGHT / 2;
|
Y = -DefaultNotePiece.NOTE_HEIGHT / 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Animate(JudgementResult result)
|
||||||
|
{
|
||||||
|
// scale roughly in-line with visual appearance of notes
|
||||||
|
Vector2 scale = new Vector2(1, 0.6f);
|
||||||
|
|
||||||
|
if (result.Judgement is HoldNoteTickJudgement)
|
||||||
|
scale *= 0.5f;
|
||||||
|
|
||||||
|
this.ScaleTo(scale);
|
||||||
|
|
||||||
|
largeFaint
|
||||||
|
.ResizeTo(default_large_faint_size)
|
||||||
|
.Then()
|
||||||
|
.ResizeTo(default_large_faint_size * new Vector2(5, 1), PoolableHitExplosion.DURATION, Easing.OutQuint)
|
||||||
|
.FadeOut(PoolableHitExplosion.DURATION * 2);
|
||||||
|
|
||||||
|
mainGlow1
|
||||||
|
.ScaleTo(1)
|
||||||
|
.Then()
|
||||||
|
.ScaleTo(1.4f, PoolableHitExplosion.DURATION, Easing.OutQuint);
|
||||||
|
|
||||||
|
this.FadeOutFromOne(PoolableHitExplosion.DURATION, Easing.Out);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,10 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DrawableManiaJudgement()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
@ -31,12 +31,12 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The minimum time range. This occurs at a <see cref="relativeTimeRange"/> of 40.
|
/// The minimum time range. This occurs at a <see cref="relativeTimeRange"/> of 40.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const double MIN_TIME_RANGE = 150;
|
public const double MIN_TIME_RANGE = 340;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The maximum time range. This occurs at a <see cref="relativeTimeRange"/> of 1.
|
/// The maximum time range. This occurs at a <see cref="relativeTimeRange"/> of 1.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const double MAX_TIME_RANGE = 6000;
|
public const double MAX_TIME_RANGE = 13720;
|
||||||
|
|
||||||
protected new ManiaPlayfield Playfield => (ManiaPlayfield)base.Playfield;
|
protected new ManiaPlayfield Playfield => (ManiaPlayfield)base.Playfield;
|
||||||
|
|
||||||
|
19
osu.Game.Rulesets.Mania/UI/IHitExplosion.cs
Normal file
19
osu.Game.Rulesets.Mania/UI/IHitExplosion.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// 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.Judgements;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Common interface for all hit explosion bodies.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHitExplosion
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Begins animating this <see cref="IHitExplosion"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="result">The type of <see cref="JudgementResult"/> that caused this explosion.</param>
|
||||||
|
void Animate(JudgementResult result);
|
||||||
|
}
|
||||||
|
}
|
78
osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs
Normal file
78
osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
// 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 osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that only the most recent <see cref="HitObject"/> is hittable, affectionately known as "note lock".
|
||||||
|
/// </summary>
|
||||||
|
public class OrderedHitPolicy
|
||||||
|
{
|
||||||
|
private readonly HitObjectContainer hitObjectContainer;
|
||||||
|
|
||||||
|
public OrderedHitPolicy(HitObjectContainer hitObjectContainer)
|
||||||
|
{
|
||||||
|
this.hitObjectContainer = hitObjectContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether a <see cref="DrawableHitObject"/> can be hit at a point in time.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Only the most recent <see cref="DrawableHitObject"/> can be hit, a previous hitobject's window cannot extend past the next one.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="hitObject">The <see cref="DrawableHitObject"/> to check.</param>
|
||||||
|
/// <param name="time">The time to check.</param>
|
||||||
|
/// <returns>Whether <paramref name="hitObject"/> can be hit at the given <paramref name="time"/>.</returns>
|
||||||
|
public bool IsHittable(DrawableHitObject hitObject, double time)
|
||||||
|
{
|
||||||
|
var nextObject = hitObjectContainer.AliveObjects.GetNext(hitObject);
|
||||||
|
return nextObject == null || time < nextObject.HitObject.StartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles a <see cref="HitObject"/> being hit to potentially miss all earlier <see cref="HitObject"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hitObject">The <see cref="HitObject"/> that was hit.</param>
|
||||||
|
public void HandleHit(DrawableHitObject hitObject)
|
||||||
|
{
|
||||||
|
if (!IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset))
|
||||||
|
throw new InvalidOperationException($"A {hitObject} was hit before it became hittable!");
|
||||||
|
|
||||||
|
foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime))
|
||||||
|
{
|
||||||
|
if (obj.Judged)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
((DrawableManiaHitObject)obj).MissForcefully();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<DrawableHitObject> enumerateHitObjectsUpTo(double targetTime)
|
||||||
|
{
|
||||||
|
foreach (var obj in hitObjectContainer.AliveObjects)
|
||||||
|
{
|
||||||
|
if (obj.HitObject.GetEndTime() >= targetTime)
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
yield return obj;
|
||||||
|
|
||||||
|
foreach (var nestedObj in obj.NestedHitObjects)
|
||||||
|
{
|
||||||
|
if (nestedObj.HitObject.GetEndTime() >= targetTime)
|
||||||
|
break;
|
||||||
|
|
||||||
|
yield return nestedObj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
133
osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs
Normal file
133
osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="Container"/> that has its contents partially hidden by an adjustable "cover". This is intended to be used in a playfield.
|
||||||
|
/// </summary>
|
||||||
|
public class PlayfieldCoveringWrapper : CompositeDrawable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The complete cover, including gradient and fill.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Drawable cover;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The gradient portion of the cover.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Box gradient;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The fully-opaque portion of the cover.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Box filled;
|
||||||
|
|
||||||
|
private readonly IBindable<ScrollingDirection> scrollDirection = new Bindable<ScrollingDirection>();
|
||||||
|
|
||||||
|
public PlayfieldCoveringWrapper(Drawable content)
|
||||||
|
{
|
||||||
|
InternalChild = new BufferedContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
content,
|
||||||
|
cover = new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Blending = new BlendingParameters
|
||||||
|
{
|
||||||
|
// Don't change the destination colour.
|
||||||
|
RGBEquation = BlendingEquation.Add,
|
||||||
|
Source = BlendingType.Zero,
|
||||||
|
Destination = BlendingType.One,
|
||||||
|
// Subtract the cover's alpha from the destination (points with alpha 1 should make the destination completely transparent).
|
||||||
|
AlphaEquation = BlendingEquation.Add,
|
||||||
|
SourceAlpha = BlendingType.Zero,
|
||||||
|
DestinationAlpha = BlendingType.OneMinusSrcAlpha
|
||||||
|
},
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
gradient = new Box
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
RelativePositionAxes = Axes.Both,
|
||||||
|
Height = 0.25f,
|
||||||
|
Colour = ColourInfo.GradientVertical(
|
||||||
|
Color4.White.Opacity(0f),
|
||||||
|
Color4.White.Opacity(1f)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
filled = new Box
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Height = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(IScrollingInfo scrollingInfo)
|
||||||
|
{
|
||||||
|
scrollDirection.BindTo(scrollingInfo.Direction);
|
||||||
|
scrollDirection.BindValueChanged(onScrollDirectionChanged, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onScrollDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||||
|
=> cover.Rotation = direction.NewValue == ScrollingDirection.Up ? 0 : 180f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The relative area that should be completely covered. This does not include the fade.
|
||||||
|
/// </summary>
|
||||||
|
public float Coverage
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
filled.Height = value;
|
||||||
|
gradient.Y = -value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The direction in which the cover expands.
|
||||||
|
/// </summary>
|
||||||
|
public CoverExpandDirection Direction
|
||||||
|
{
|
||||||
|
set => cover.Scale = value == CoverExpandDirection.AlongScroll ? Vector2.One : new Vector2(1, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum CoverExpandDirection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The cover expands along the scrolling direction.
|
||||||
|
/// </summary>
|
||||||
|
AlongScroll,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The cover expands against the scrolling direction.
|
||||||
|
/// </summary>
|
||||||
|
AgainstScroll
|
||||||
|
}
|
||||||
|
}
|
51
osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs
Normal file
51
osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Pooling;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
|
{
|
||||||
|
public class PoolableHitExplosion : PoolableDrawable
|
||||||
|
{
|
||||||
|
public const double DURATION = 200;
|
||||||
|
|
||||||
|
public JudgementResult Result { get; private set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Column column { get; set; }
|
||||||
|
|
||||||
|
private SkinnableDrawable skinnableExplosion;
|
||||||
|
|
||||||
|
public PoolableHitExplosion()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
InternalChild = skinnableExplosion = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion, column.Index), _ => new DefaultHitExplosion())
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Apply(JudgementResult result)
|
||||||
|
{
|
||||||
|
Result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PrepareForUse()
|
||||||
|
{
|
||||||
|
base.PrepareForUse();
|
||||||
|
|
||||||
|
(skinnableExplosion?.Drawable as IHitExplosion)?.Animate(Result);
|
||||||
|
|
||||||
|
this.Delay(DURATION).Then().Expire();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user