mirror of
https://github.com/ppy/osu.git
synced 2025-01-18 14:43:22 +08:00
Merge branch 'master' into doubles_doubletap_fix
This commit is contained in:
commit
4924f5e5ff
@ -21,7 +21,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ppy.localisationanalyser.tools": {
|
"ppy.localisationanalyser.tools": {
|
||||||
"version": "2024.517.0",
|
"version": "2024.802.0",
|
||||||
"commands": [
|
"commands": [
|
||||||
"localisation"
|
"localisation"
|
||||||
]
|
]
|
||||||
|
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
@ -64,10 +64,11 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
os:
|
os:
|
||||||
- { prettyname: Windows, fullname: windows-latest }
|
- { prettyname: Windows, fullname: windows-latest }
|
||||||
- { prettyname: macOS, fullname: macos-latest }
|
# macOS runner performance has gotten unbearably slow so let's turn them off temporarily.
|
||||||
|
# - { prettyname: macOS, fullname: macos-latest }
|
||||||
- { prettyname: Linux, fullname: ubuntu-latest }
|
- { prettyname: Linux, fullname: ubuntu-latest }
|
||||||
threadingMode: ['SingleThread', 'MultiThreaded']
|
threadingMode: ['SingleThread', 'MultiThreaded']
|
||||||
timeout-minutes: 60
|
timeout-minutes: 120
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@ -120,9 +121,7 @@ jobs:
|
|||||||
|
|
||||||
build-only-ios:
|
build-only-ios:
|
||||||
name: Build only (iOS)
|
name: Build only (iOS)
|
||||||
# `macos-13` is required, because the newest Microsoft.iOS.Sdk versions require Xcode 14.3.
|
runs-on: macos-latest
|
||||||
# TODO: can be changed to `macos-latest` once `macos-13` becomes latest (currently in beta: https://github.com/actions/runner-images/tree/main#available-images)
|
|
||||||
runs-on: macos-13
|
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@ -134,10 +133,7 @@ jobs:
|
|||||||
dotnet-version: "8.0.x"
|
dotnet-version: "8.0.x"
|
||||||
|
|
||||||
- name: Install .NET Workloads
|
- name: Install .NET Workloads
|
||||||
run: dotnet workload install maui-ios
|
run: dotnet workload install ios --from-rollback-file https://raw.githubusercontent.com/ppy/osu-framework/refs/heads/master/workloads.json
|
||||||
|
|
||||||
- name: Select Xcode 15.2
|
|
||||||
run: sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: dotnet build -c Debug osu.iOS
|
run: dotnet build -c Debug osu.iOS
|
||||||
|
35
.github/workflows/diffcalc.yml
vendored
35
.github/workflows/diffcalc.yml
vendored
@ -104,6 +104,25 @@ env:
|
|||||||
EXECUTION_ID: execution-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
|
EXECUTION_ID: execution-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
master-environment:
|
||||||
|
name: Save master environment
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
HEAD: ${{ steps.get-head.outputs.HEAD }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout osu
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: master
|
||||||
|
sparse-checkout: |
|
||||||
|
README.md
|
||||||
|
|
||||||
|
- name: Get HEAD ref
|
||||||
|
id: get-head
|
||||||
|
run: |
|
||||||
|
ref=$(git log -1 --format='%H')
|
||||||
|
echo "HEAD=https://github.com/${{ github.repository }}/commit/${ref}" >> "${GITHUB_OUTPUT}"
|
||||||
|
|
||||||
check-permissions:
|
check-permissions:
|
||||||
name: Check permissions
|
name: Check permissions
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -111,7 +130,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check permissions
|
- name: Check permissions
|
||||||
run: |
|
run: |
|
||||||
ALLOWED_USERS=(smoogipoo peppy bdach)
|
ALLOWED_USERS=(smoogipoo peppy bdach frenzibyte)
|
||||||
for i in "${ALLOWED_USERS[@]}"; do
|
for i in "${ALLOWED_USERS[@]}"; do
|
||||||
if [[ "${{ github.actor }}" == "$i" ]]; then
|
if [[ "${{ github.actor }}" == "$i" ]]; then
|
||||||
exit 0
|
exit 0
|
||||||
@ -121,7 +140,7 @@ jobs:
|
|||||||
|
|
||||||
create-comment:
|
create-comment:
|
||||||
name: Create PR comment
|
name: Create PR comment
|
||||||
needs: check-permissions
|
needs: [ master-environment, check-permissions ]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request }}
|
if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request }}
|
||||||
steps:
|
steps:
|
||||||
@ -158,7 +177,7 @@ jobs:
|
|||||||
|
|
||||||
environment:
|
environment:
|
||||||
name: Setup environment
|
name: Setup environment
|
||||||
needs: directory
|
needs: [ master-environment, directory ]
|
||||||
runs-on: self-hosted
|
runs-on: self-hosted
|
||||||
env:
|
env:
|
||||||
VARS_JSON: ${{ toJSON(vars) }}
|
VARS_JSON: ${{ toJSON(vars) }}
|
||||||
@ -182,6 +201,10 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
- name: Add master environment
|
||||||
|
run: |
|
||||||
|
sed -i "s;^OSU_A=.*$;OSU_A=${{ needs.master-environment.outputs.HEAD }};" "${{ needs.directory.outputs.GENERATOR_ENV }}"
|
||||||
|
|
||||||
- name: Add pull-request environment
|
- name: Add pull-request environment
|
||||||
if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request }}
|
if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request }}
|
||||||
run: |
|
run: |
|
||||||
@ -361,8 +384,7 @@ jobs:
|
|||||||
uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0
|
uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0
|
||||||
with:
|
with:
|
||||||
comment_tag: ${{ env.EXECUTION_ID }}
|
comment_tag: ${{ env.EXECUTION_ID }}
|
||||||
mode: upsert
|
mode: recreate
|
||||||
create_if_not_exists: false
|
|
||||||
message: |
|
message: |
|
||||||
Target: ${{ needs.generator.outputs.TARGET }}
|
Target: ${{ needs.generator.outputs.TARGET }}
|
||||||
Spreadsheet: ${{ needs.generator.outputs.SPREADSHEET_LINK }}
|
Spreadsheet: ${{ needs.generator.outputs.SPREADSHEET_LINK }}
|
||||||
@ -372,8 +394,7 @@ jobs:
|
|||||||
uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0
|
uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0
|
||||||
with:
|
with:
|
||||||
comment_tag: ${{ env.EXECUTION_ID }}
|
comment_tag: ${{ env.EXECUTION_ID }}
|
||||||
mode: upsert
|
mode: recreate
|
||||||
create_if_not_exists: false
|
|
||||||
message: |
|
message: |
|
||||||
Difficulty calculation failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
Difficulty calculation failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||||
|
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -266,6 +266,7 @@ __pycache__/
|
|||||||
.idea/**/dictionaries
|
.idea/**/dictionaries
|
||||||
.idea/**/shelf
|
.idea/**/shelf
|
||||||
.idea/*/.idea/projectSettingsUpdater.xml
|
.idea/*/.idea/projectSettingsUpdater.xml
|
||||||
|
.idea/*/.idea/encodings.xml
|
||||||
|
|
||||||
# Generated files
|
# Generated files
|
||||||
.idea/**/contentModel.xml
|
.idea/**/contentModel.xml
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
|
|
||||||
</project>
|
|
@ -55,7 +55,7 @@ When in doubt, it's probably best to start with a discussion first. We will esca
|
|||||||
|
|
||||||
While pull requests from unaffiliated contributors are welcome, please note that due to significant community interest and limited review throughput, the core team's primary focus is on the issues which are currently [on the roadmap](https://github.com/orgs/ppy/projects/7/views/6). Reviewing PRs that fall outside of the scope of the roadmap is done on a best-effort basis, so please be aware that it may take a while before a core maintainer gets around to review your change.
|
While pull requests from unaffiliated contributors are welcome, please note that due to significant community interest and limited review throughput, the core team's primary focus is on the issues which are currently [on the roadmap](https://github.com/orgs/ppy/projects/7/views/6). Reviewing PRs that fall outside of the scope of the roadmap is done on a best-effort basis, so please be aware that it may take a while before a core maintainer gets around to review your change.
|
||||||
|
|
||||||
The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues to start with. We also have a [`good-first-issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue) label, although from experience it is not used very often, as it is relatively rare that we can spot an issue that will definitively be a good first issue for a new contributor regardless of their programming experience.
|
The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues to start with. We also have a [`good first issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) label, although from experience it is not used very often, as it is relatively rare that we can spot an issue that will definitively be a good first issue for a new contributor regardless of their programming experience.
|
||||||
|
|
||||||
In the case of simple issues, a direct PR is okay. However, if you decide to work on an existing issue which doesn't seem trivial, **please ask us first**. This way we can try to estimate if it is a good fit for you and provide the correct direction on how to address it. In addition, note that while we do not rule out external contributors from working on roadmapped issues, we will generally prefer to handle them ourselves unless they're not very time sensitive.
|
In the case of simple issues, a direct PR is okay. However, if you decide to work on an existing issue which doesn't seem trivial, **please ask us first**. This way we can try to estimate if it is a good fit for you and provide the correct direction on how to address it. In addition, note that while we do not rule out external contributors from working on roadmapped issues, we will generally prefer to handle them ourselves unless they're not very time sensitive.
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ T:SixLabors.ImageSharp.IDeepCloneable`1;Use osu.Game.Utils.IDeepCloneable<T> ins
|
|||||||
M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText.
|
M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText.
|
||||||
M:osu.Framework.Bindables.IBindableList`1.GetBoundCopy();Fails on iOS. Use manual ctor + BindTo instead. (see https://github.com/mono/mono/issues/19900)
|
M:osu.Framework.Bindables.IBindableList`1.GetBoundCopy();Fails on iOS. Use manual ctor + BindTo instead. (see https://github.com/mono/mono/issues/19900)
|
||||||
T:NuGet.Packaging.CollectionExtensions;Don't use internal extension methods.
|
T:NuGet.Packaging.CollectionExtensions;Don't use internal extension methods.
|
||||||
M:System.Enum.HasFlag(System.Enum);Use osu.Framework.Extensions.EnumExtensions.HasFlagFast<T>() instead.
|
|
||||||
M:Realms.IRealmCollection`1.SubscribeForNotifications`1(Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IRealmCollection<T>,NotificationCallbackDelegate<T>) instead.
|
M:Realms.IRealmCollection`1.SubscribeForNotifications`1(Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IRealmCollection<T>,NotificationCallbackDelegate<T>) instead.
|
||||||
M:System.Guid.#ctor;Probably meaning to use Guid.NewGuid() instead. If actually wanting empty, use Guid.Empty.
|
M:System.Guid.#ctor;Probably meaning to use Guid.NewGuid() instead. If actually wanting empty, use Guid.Empty.
|
||||||
M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Linq.IQueryable{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IQueryable<T>,NotificationCallbackDelegate<T>) instead.
|
M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Linq.IQueryable{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IQueryable<T>,NotificationCallbackDelegate<T>) instead.
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.625.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.1007.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||||
|
@ -80,7 +80,7 @@ namespace osu.Android
|
|||||||
host.Window.CursorState |= CursorState.Hidden;
|
host.Window.CursorState |= CursorState.Hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager();
|
protected override UpdateManager CreateUpdateManager() => new MobileUpdateNotifier();
|
||||||
|
|
||||||
protected override BatteryInfo CreateBatteryInfo() => new AndroidBatteryInfo();
|
protected override BatteryInfo CreateBatteryInfo() => new AndroidBatteryInfo();
|
||||||
|
|
||||||
|
@ -2,10 +2,10 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using osu.Desktop.Performance;
|
using osu.Desktop.Performance;
|
||||||
using osu.Desktop.Security;
|
using osu.Desktop.Security;
|
||||||
@ -95,42 +95,20 @@ namespace osu.Desktop
|
|||||||
return key?.OpenSubKey(WindowsAssociationManager.SHELL_OPEN_COMMAND)?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", "");
|
return key?.OpenSubKey(WindowsAssociationManager.SHELL_OPEN_COMMAND)?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsPackageManaged => !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("OSU_EXTERNAL_UPDATE_PROVIDER"));
|
||||||
|
|
||||||
protected override UpdateManager CreateUpdateManager()
|
protected override UpdateManager CreateUpdateManager()
|
||||||
{
|
{
|
||||||
string? packageManaged = Environment.GetEnvironmentVariable("OSU_EXTERNAL_UPDATE_PROVIDER");
|
if (IsPackageManaged)
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(packageManaged))
|
|
||||||
return new NoActionUpdateManager();
|
return new NoActionUpdateManager();
|
||||||
|
|
||||||
switch (RuntimeInfo.OS)
|
return new VelopackUpdateManager();
|
||||||
{
|
|
||||||
case RuntimeInfo.Platform.Windows:
|
|
||||||
Debug.Assert(OperatingSystem.IsWindows());
|
|
||||||
|
|
||||||
return new SquirrelUpdateManager();
|
|
||||||
|
|
||||||
default:
|
|
||||||
return new SimpleUpdateManager();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool RestartAppWhenExited()
|
public override bool RestartAppWhenExited()
|
||||||
{
|
{
|
||||||
switch (RuntimeInfo.OS)
|
Task.Run(() => Velopack.UpdateExe.Start()).FireAndForget();
|
||||||
{
|
return true;
|
||||||
case RuntimeInfo.Platform.Windows:
|
|
||||||
Debug.Assert(OperatingSystem.IsWindows());
|
|
||||||
|
|
||||||
// Of note, this is an async method in squirrel that adds an arbitrary delay before returning
|
|
||||||
// likely to ensure the external process is in a good state.
|
|
||||||
//
|
|
||||||
// We're not waiting on that here, but the outro playing before the actual exit should be enough
|
|
||||||
// to cover this.
|
|
||||||
Squirrel.UpdateManager.RestartAppWhenExited().FireAndForget();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.RestartAppWhenExited();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
|
@ -14,7 +14,7 @@ using osu.Game;
|
|||||||
using osu.Game.IPC;
|
using osu.Game.IPC;
|
||||||
using osu.Game.Tournament;
|
using osu.Game.Tournament;
|
||||||
using SDL;
|
using SDL;
|
||||||
using Squirrel;
|
using Velopack;
|
||||||
|
|
||||||
namespace osu.Desktop
|
namespace osu.Desktop
|
||||||
{
|
{
|
||||||
@ -31,19 +31,11 @@ namespace osu.Desktop
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
/*
|
// IMPORTANT DON'T IGNORE: For general sanity, velopack's setup needs to run before anything else.
|
||||||
* WARNING: DO NOT PLACE **ANY** CODE ABOVE THE FOLLOWING BLOCK!
|
// This has bitten us in the rear before (bricked updater), and although the underlying issue from
|
||||||
*
|
// last time has been fixed, let's not tempt fate.
|
||||||
* Logic handling Squirrel MUST run before EVERYTHING if you do not want to break it.
|
setupVelopack();
|
||||||
* To be more precise: Squirrel is internally using a rather... crude method to determine whether it is running under NUnit,
|
|
||||||
* namely by checking loaded assemblies:
|
|
||||||
* https://github.com/clowd/Clowd.Squirrel/blob/24427217482deeeb9f2cacac555525edfc7bd9ac/src/Squirrel/SimpleSplat/PlatformModeDetector.cs#L17-L32
|
|
||||||
*
|
|
||||||
* If it finds ANY assembly from the ones listed above - REGARDLESS of the reason why it is loaded -
|
|
||||||
* the app will then do completely broken things like:
|
|
||||||
* - not creating system shortcuts (as the logic is if'd out if "running tests")
|
|
||||||
* - not exiting after the install / first-update / uninstall hooks are ran (as the `Environment.Exit()` calls are if'd out if "running tests")
|
|
||||||
*/
|
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
var windowsVersion = Environment.OSVersion.Version;
|
var windowsVersion = Environment.OSVersion.Version;
|
||||||
@ -66,8 +58,6 @@ namespace osu.Desktop
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setupSquirrel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NVIDIA profiles are based on the executable name of a process.
|
// NVIDIA profiles are based on the executable name of a process.
|
||||||
@ -177,32 +167,28 @@ namespace osu.Desktop
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
[SupportedOSPlatform("windows")]
|
private static void setupVelopack()
|
||||||
private static void setupSquirrel()
|
|
||||||
{
|
{
|
||||||
SquirrelAwareApp.HandleEvents(onInitialInstall: (_, tools) =>
|
if (OsuGameDesktop.IsPackageManaged)
|
||||||
{
|
{
|
||||||
tools.CreateShortcutForThisExe();
|
Logger.Log("Updates are being managed by an external provider. Skipping Velopack setup.");
|
||||||
tools.CreateUninstallerRegistryEntry();
|
return;
|
||||||
WindowsAssociationManager.InstallAssociations();
|
}
|
||||||
}, onAppUpdate: (_, tools) =>
|
|
||||||
{
|
var app = VelopackApp.Build();
|
||||||
tools.CreateUninstallerRegistryEntry();
|
|
||||||
WindowsAssociationManager.UpdateAssociations();
|
if (OperatingSystem.IsWindows())
|
||||||
}, onAppUninstall: (_, tools) =>
|
configureWindows(app);
|
||||||
{
|
|
||||||
tools.RemoveShortcutForThisExe();
|
app.Run();
|
||||||
tools.RemoveUninstallerRegistryEntry();
|
}
|
||||||
WindowsAssociationManager.UninstallAssociations();
|
|
||||||
}, onEveryRun: (_, _, _) =>
|
[SupportedOSPlatform("windows")]
|
||||||
{
|
private static void configureWindows(VelopackApp app)
|
||||||
// While setting the `ProcessAppUserModelId` fixes duplicate icons/shortcuts on the taskbar, it currently
|
{
|
||||||
// causes the right-click context menu to function incorrectly.
|
app.WithFirstRun(_ => WindowsAssociationManager.InstallAssociations());
|
||||||
//
|
app.WithAfterUpdateFastCallback(_ => WindowsAssociationManager.UpdateAssociations());
|
||||||
// This may turn out to be non-required after an alternative solution is implemented.
|
app.WithBeforeUninstallFastCallback(_ => WindowsAssociationManager.UninstallAssociations());
|
||||||
// see https://github.com/clowd/Clowd.Squirrel/issues/24
|
|
||||||
// tools.SetProcessAppUserModelId();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Security.Principal;
|
|
||||||
using osu.Framework;
|
using osu.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -21,48 +20,14 @@ namespace osu.Desktop.Security
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private INotificationOverlay notifications { get; set; } = null!;
|
private INotificationOverlay notifications { get; set; } = null!;
|
||||||
|
|
||||||
private bool elevated;
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
elevated = checkElevated();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
if (elevated)
|
if (Environment.IsPrivilegedProcess)
|
||||||
notifications.Post(new ElevatedPrivilegesNotification());
|
notifications.Post(new ElevatedPrivilegesNotification());
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool checkElevated()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
switch (RuntimeInfo.OS)
|
|
||||||
{
|
|
||||||
case RuntimeInfo.Platform.Windows:
|
|
||||||
if (!OperatingSystem.IsWindows()) return false;
|
|
||||||
|
|
||||||
var windowsIdentity = WindowsIdentity.GetCurrent();
|
|
||||||
var windowsPrincipal = new WindowsPrincipal(windowsIdentity);
|
|
||||||
|
|
||||||
return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator);
|
|
||||||
|
|
||||||
case RuntimeInfo.Platform.macOS:
|
|
||||||
case RuntimeInfo.Platform.Linux:
|
|
||||||
return Mono.Unix.Native.Syscall.geteuid() == 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private partial class ElevatedPrivilegesNotification : SimpleNotification
|
private partial class ElevatedPrivilegesNotification : SimpleNotification
|
||||||
{
|
{
|
||||||
public override bool IsImportant => true;
|
public override bool IsImportant => true;
|
||||||
|
@ -1,180 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Runtime.Versioning;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Logging;
|
|
||||||
using osu.Game;
|
|
||||||
using osu.Game.Overlays;
|
|
||||||
using osu.Game.Overlays.Notifications;
|
|
||||||
using osu.Game.Screens.Play;
|
|
||||||
using Squirrel.SimpleSplat;
|
|
||||||
using Squirrel.Sources;
|
|
||||||
using LogLevel = Squirrel.SimpleSplat.LogLevel;
|
|
||||||
using UpdateManager = osu.Game.Updater.UpdateManager;
|
|
||||||
|
|
||||||
namespace osu.Desktop.Updater
|
|
||||||
{
|
|
||||||
[SupportedOSPlatform("windows")]
|
|
||||||
public partial class SquirrelUpdateManager : UpdateManager
|
|
||||||
{
|
|
||||||
private Squirrel.UpdateManager? updateManager;
|
|
||||||
private INotificationOverlay notificationOverlay = null!;
|
|
||||||
|
|
||||||
public Task PrepareUpdateAsync() => Squirrel.UpdateManager.RestartAppWhenExited();
|
|
||||||
|
|
||||||
private static readonly Logger logger = Logger.GetLogger("updater");
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether an update has been downloaded but not yet applied.
|
|
||||||
/// </summary>
|
|
||||||
private bool updatePending;
|
|
||||||
|
|
||||||
private readonly SquirrelLogger squirrelLogger = new SquirrelLogger();
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private OsuGameBase game { get; set; } = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private ILocalUserPlayInfo? localUserInfo { get; set; }
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(INotificationOverlay notifications)
|
|
||||||
{
|
|
||||||
notificationOverlay = notifications;
|
|
||||||
|
|
||||||
SquirrelLocator.CurrentMutable.Register(() => squirrelLogger, typeof(ILogger));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task<bool> PerformUpdateCheck() => await checkForUpdateAsync().ConfigureAwait(false);
|
|
||||||
|
|
||||||
private async Task<bool> checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification? notification = null)
|
|
||||||
{
|
|
||||||
// should we schedule a retry on completion of this check?
|
|
||||||
bool scheduleRecheck = true;
|
|
||||||
|
|
||||||
const string? github_token = null; // TODO: populate.
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Avoid any kind of update checking while gameplay is running.
|
|
||||||
if (localUserInfo?.IsPlaying.Value == true)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
updateManager ??= new Squirrel.UpdateManager(new GithubSource(@"https://github.com/ppy/osu", github_token, false), @"osulazer");
|
|
||||||
|
|
||||||
var info = await updateManager.CheckForUpdate(!useDeltaPatching).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (info.ReleasesToApply.Count == 0)
|
|
||||||
{
|
|
||||||
if (updatePending)
|
|
||||||
{
|
|
||||||
// the user may have dismissed the completion notice, so show it again.
|
|
||||||
notificationOverlay.Post(new UpdateApplicationCompleteNotification
|
|
||||||
{
|
|
||||||
Activated = () =>
|
|
||||||
{
|
|
||||||
restartToApplyUpdate();
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// no updates available. bail and retry later.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
scheduleRecheck = false;
|
|
||||||
|
|
||||||
if (notification == null)
|
|
||||||
{
|
|
||||||
notification = new UpdateProgressNotification
|
|
||||||
{
|
|
||||||
CompletionClickAction = restartToApplyUpdate,
|
|
||||||
};
|
|
||||||
|
|
||||||
Schedule(() => notificationOverlay.Post(notification));
|
|
||||||
}
|
|
||||||
|
|
||||||
notification.StartDownload();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await updateManager.DownloadReleases(info.ReleasesToApply, p => notification.Progress = p / 100f).ConfigureAwait(false);
|
|
||||||
|
|
||||||
notification.StartInstall();
|
|
||||||
|
|
||||||
await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f).ConfigureAwait(false);
|
|
||||||
|
|
||||||
notification.State = ProgressNotificationState.Completed;
|
|
||||||
updatePending = true;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
if (useDeltaPatching)
|
|
||||||
{
|
|
||||||
logger.Add(@"delta patching failed; will attempt full download!");
|
|
||||||
|
|
||||||
// could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959)
|
|
||||||
// try again without deltas.
|
|
||||||
await checkForUpdateAsync(false, notification).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// In the case of an error, a separate notification will be displayed.
|
|
||||||
notification.FailDownload();
|
|
||||||
Logger.Error(e, @"update failed!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
// we'll ignore this and retry later. can be triggered by no internet connection or thread abortion.
|
|
||||||
scheduleRecheck = true;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (scheduleRecheck)
|
|
||||||
{
|
|
||||||
// check again in 30 minutes.
|
|
||||||
Scheduler.AddDelayed(() => Task.Run(async () => await checkForUpdateAsync().ConfigureAwait(false)), 60000 * 30);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool restartToApplyUpdate()
|
|
||||||
{
|
|
||||||
PrepareUpdateAsync()
|
|
||||||
.ContinueWith(_ => Schedule(() => game.AttemptExit()));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
|
||||||
{
|
|
||||||
base.Dispose(isDisposing);
|
|
||||||
updateManager?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SquirrelLogger : ILogger, IDisposable
|
|
||||||
{
|
|
||||||
public LogLevel Level { get; set; } = LogLevel.Info;
|
|
||||||
|
|
||||||
public void Write(string message, LogLevel logLevel)
|
|
||||||
{
|
|
||||||
if (logLevel < Level)
|
|
||||||
return;
|
|
||||||
|
|
||||||
logger.Add(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
140
osu.Desktop/Updater/VelopackUpdateManager.cs
Normal file
140
osu.Desktop/Updater/VelopackUpdateManager.cs
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using osu.Game;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Notifications;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
using Velopack;
|
||||||
|
using Velopack.Sources;
|
||||||
|
|
||||||
|
namespace osu.Desktop.Updater
|
||||||
|
{
|
||||||
|
public partial class VelopackUpdateManager : Game.Updater.UpdateManager
|
||||||
|
{
|
||||||
|
private readonly UpdateManager updateManager;
|
||||||
|
private INotificationOverlay notificationOverlay = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuGameBase game { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private ILocalUserPlayInfo? localUserInfo { get; set; }
|
||||||
|
|
||||||
|
private UpdateInfo? pendingUpdate;
|
||||||
|
|
||||||
|
public VelopackUpdateManager()
|
||||||
|
{
|
||||||
|
updateManager = new UpdateManager(new GithubSource(@"https://github.com/ppy/osu", null, false), new UpdateOptions
|
||||||
|
{
|
||||||
|
AllowVersionDowngrade = true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(INotificationOverlay notifications)
|
||||||
|
{
|
||||||
|
notificationOverlay = notifications;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task<bool> PerformUpdateCheck() => await checkForUpdateAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
private async Task<bool> checkForUpdateAsync(UpdateProgressNotification? notification = null)
|
||||||
|
{
|
||||||
|
// whether to check again in 30 minutes. generally only if there's an error or no update was found (yet).
|
||||||
|
bool scheduleRecheck = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Avoid any kind of update checking while gameplay is running.
|
||||||
|
if (localUserInfo?.IsPlaying.Value == true)
|
||||||
|
{
|
||||||
|
scheduleRecheck = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: we should probably be checking if there's a more recent update, rather than shortcutting here.
|
||||||
|
// Velopack does support this scenario (see https://github.com/ppy/osu/pull/28743#discussion_r1743495975).
|
||||||
|
if (pendingUpdate != null)
|
||||||
|
{
|
||||||
|
// If there is an update pending restart, show the notification to restart again.
|
||||||
|
notificationOverlay.Post(new UpdateApplicationCompleteNotification
|
||||||
|
{
|
||||||
|
Activated = () =>
|
||||||
|
{
|
||||||
|
Task.Run(restartToApplyUpdate);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingUpdate = await updateManager.CheckForUpdatesAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
// No update is available. We'll check again later.
|
||||||
|
if (pendingUpdate == null)
|
||||||
|
{
|
||||||
|
scheduleRecheck = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// An update is found, let's notify the user and start downloading it.
|
||||||
|
if (notification == null)
|
||||||
|
{
|
||||||
|
notification = new UpdateProgressNotification
|
||||||
|
{
|
||||||
|
CompletionClickAction = () =>
|
||||||
|
{
|
||||||
|
Task.Run(restartToApplyUpdate);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Schedule(() => notificationOverlay.Post(notification));
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.StartDownload();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await updateManager.DownloadUpdatesAsync(pendingUpdate, p => notification.Progress = p / 100f).ConfigureAwait(false);
|
||||||
|
|
||||||
|
notification.State = ProgressNotificationState.Completed;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// In the case of an error, a separate notification will be displayed.
|
||||||
|
scheduleRecheck = true;
|
||||||
|
notification.FailDownload();
|
||||||
|
Logger.Error(e, @"update failed!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// we'll ignore this and retry later. can be triggered by no internet connection or thread abortion.
|
||||||
|
scheduleRecheck = true;
|
||||||
|
Logger.Log($@"update check failed ({e.Message})");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (scheduleRecheck)
|
||||||
|
{
|
||||||
|
Scheduler.AddDelayed(() => Task.Run(async () => await checkForUpdateAsync().ConfigureAwait(false)), 60000 * 30);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task restartToApplyUpdate()
|
||||||
|
{
|
||||||
|
await updateManager.WaitExitThenApplyUpdatesAsync(pendingUpdate?.TargetFullRelease).ConfigureAwait(false);
|
||||||
|
Schedule(() => game.AttemptExit());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,5 +13,7 @@ namespace osu.Desktop.Windows
|
|||||||
private static readonly string icon_directory = Path.GetDirectoryName(typeof(Icons).Assembly.Location)!;
|
private static readonly string icon_directory = Path.GetDirectoryName(typeof(Icons).Assembly.Location)!;
|
||||||
|
|
||||||
public static string Lazer => Path.Join(icon_directory, "lazer.ico");
|
public static string Lazer => Path.Join(icon_directory, "lazer.ico");
|
||||||
|
|
||||||
|
public static string Beatmap => Path.Join(icon_directory, "beatmap.ico");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,10 +40,10 @@ namespace osu.Desktop.Windows
|
|||||||
|
|
||||||
private static readonly FileAssociation[] file_associations =
|
private static readonly FileAssociation[] file_associations =
|
||||||
{
|
{
|
||||||
new FileAssociation(@".osz", WindowsAssociationManagerStrings.OsuBeatmap, Icons.Lazer),
|
new FileAssociation(@".osz", WindowsAssociationManagerStrings.OsuBeatmap, Icons.Beatmap),
|
||||||
new FileAssociation(@".olz", WindowsAssociationManagerStrings.OsuBeatmap, Icons.Lazer),
|
new FileAssociation(@".olz", WindowsAssociationManagerStrings.OsuBeatmap, Icons.Beatmap),
|
||||||
new FileAssociation(@".osr", WindowsAssociationManagerStrings.OsuReplay, Icons.Lazer),
|
new FileAssociation(@".osr", WindowsAssociationManagerStrings.OsuReplay, Icons.Beatmap),
|
||||||
new FileAssociation(@".osk", WindowsAssociationManagerStrings.OsuSkin, Icons.Lazer),
|
new FileAssociation(@".osk", WindowsAssociationManagerStrings.OsuSkin, Icons.Beatmap),
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly UriAssociation[] uri_associations =
|
private static readonly UriAssociation[] uri_associations =
|
||||||
|
BIN
osu.Desktop/beatmap.ico
Normal file
BIN
osu.Desktop/beatmap.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 349 KiB |
@ -5,6 +5,7 @@
|
|||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<Description>A free-to-win rhythm game. Rhythm is just a *click* away!</Description>
|
<Description>A free-to-win rhythm game. Rhythm is just a *click* away!</Description>
|
||||||
<AssemblyName>osu!</AssemblyName>
|
<AssemblyName>osu!</AssemblyName>
|
||||||
|
<AssemblyTitle>osu!(lazer)</AssemblyTitle>
|
||||||
<Title>osu!</Title>
|
<Title>osu!</Title>
|
||||||
<Product>osu!(lazer)</Product>
|
<Product>osu!(lazer)</Product>
|
||||||
<ApplicationIcon>lazer.ico</ApplicationIcon>
|
<ApplicationIcon>lazer.ico</ApplicationIcon>
|
||||||
@ -23,10 +24,9 @@
|
|||||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Clowd.Squirrel" Version="2.11.1" />
|
|
||||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
|
||||||
<PackageReference Include="System.IO.Packaging" Version="8.0.0" />
|
<PackageReference Include="System.IO.Packaging" Version="8.0.0" />
|
||||||
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||||
|
<PackageReference Include="Velopack" Version="0.0.630-g9c52e40" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Resources">
|
<ItemGroup Label="Resources">
|
||||||
<EmbeddedResource Include="lazer.ico" />
|
<EmbeddedResource Include="lazer.ico" />
|
||||||
|
29
osu.Game.Benchmarks/BenchmarkGeometryUtils.cs
Normal file
29
osu.Game.Benchmarks/BenchmarkGeometryUtils.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// 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 BenchmarkDotNet.Attributes;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Benchmarks
|
||||||
|
{
|
||||||
|
public class BenchmarkGeometryUtils : BenchmarkTest
|
||||||
|
{
|
||||||
|
[Params(100, 1000, 2000, 4000, 8000, 10000)]
|
||||||
|
public int N;
|
||||||
|
|
||||||
|
private Vector2[] points = null!;
|
||||||
|
|
||||||
|
public override void SetUp()
|
||||||
|
{
|
||||||
|
points = new Vector2[N];
|
||||||
|
|
||||||
|
for (int i = 0; i < points.Length; ++i)
|
||||||
|
points[i] = new Vector2(RNG.Next(512), RNG.Next(384));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void MinimumEnclosingCircle() => GeometryUtils.MinimumEnclosingCircle(points);
|
||||||
|
}
|
||||||
|
}
|
@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
contentContainer.Playfield.HitObjectContainer.Add(hitObject);
|
contentContainer.Playfield.HitObjectContainer.Add(hitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint)
|
protected override SnapResult SnapForBlueprint(HitObjectPlacementBlueprint blueprint)
|
||||||
{
|
{
|
||||||
var result = base.SnapForBlueprint(blueprint);
|
var result = base.SnapForBlueprint(blueprint);
|
||||||
result.Time = Math.Round(HitObjectContainer.TimeAtScreenSpacePosition(result.ScreenSpacePosition) / TIME_SNAP) * TIME_SNAP;
|
result.Time = Math.Round(HitObjectContainer.TimeAtScreenSpacePosition(result.ScreenSpacePosition) / TIME_SNAP) * TIME_SNAP;
|
||||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
{
|
{
|
||||||
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableBananaShower((BananaShower)hitObject);
|
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableBananaShower((BananaShower)hitObject);
|
||||||
|
|
||||||
protected override PlacementBlueprint CreateBlueprint() => new BananaShowerPlacementBlueprint();
|
protected override HitObjectPlacementBlueprint CreateBlueprint() => new BananaShowerPlacementBlueprint();
|
||||||
|
|
||||||
protected override void AddHitObject(DrawableHitObject hitObject)
|
protected override void AddHitObject(DrawableHitObject hitObject)
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||||
|
{
|
||||||
|
public partial class TestSceneCatchEditorSaving : EditorSavingTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreateRuleset() => new CatchRuleset();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCatchJuiceStreamTickCorrect()
|
||||||
|
{
|
||||||
|
AddStep("enter timing mode", () => InputManager.Key(Key.F3));
|
||||||
|
AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint()));
|
||||||
|
AddStep("enter compose mode", () => InputManager.Key(Key.F1));
|
||||||
|
|
||||||
|
Vector2 startPoint = Vector2.Zero;
|
||||||
|
float increment = 0;
|
||||||
|
|
||||||
|
AddUntilStep("wait for playfield", () => this.ChildrenOfType<CatchPlayfield>().FirstOrDefault()?.IsLoaded == true);
|
||||||
|
AddStep("move to centre", () =>
|
||||||
|
{
|
||||||
|
var playfield = this.ChildrenOfType<CatchPlayfield>().Single();
|
||||||
|
startPoint = playfield.ScreenSpaceDrawQuad.Centre + new Vector2(0, playfield.ScreenSpaceDrawQuad.Height / 3);
|
||||||
|
increment = playfield.ScreenSpaceDrawQuad.Height / 10;
|
||||||
|
InputManager.MoveMouseTo(startPoint);
|
||||||
|
});
|
||||||
|
AddStep("choose juice stream placing tool", () => InputManager.Key(Key.Number3));
|
||||||
|
AddStep("start placement", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
AddStep("move to next", () => InputManager.MoveMouseTo(startPoint + new Vector2(2 * increment, -increment)));
|
||||||
|
AddStep("add node", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
AddStep("move to next", () => InputManager.MoveMouseTo(startPoint + new Vector2(-2 * increment, -2 * increment)));
|
||||||
|
AddStep("add node", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
AddStep("move to next", () => InputManager.MoveMouseTo(startPoint + new Vector2(0, -3 * increment)));
|
||||||
|
AddStep("end placement", () => InputManager.Click(MouseButton.Right));
|
||||||
|
|
||||||
|
AddUntilStep("juice stream placed", () => EditorBeatmap.HitObjects, () => Has.Count.EqualTo(1));
|
||||||
|
|
||||||
|
int largeDropletCount = 0, tinyDropletCount = 0;
|
||||||
|
AddStep("store droplet count", () =>
|
||||||
|
{
|
||||||
|
largeDropletCount = EditorBeatmap.HitObjects[0].NestedHitObjects.Count(t => t.GetType() == typeof(Droplet));
|
||||||
|
tinyDropletCount = EditorBeatmap.HitObjects[0].NestedHitObjects.Count(t => t.GetType() == typeof(TinyDroplet));
|
||||||
|
});
|
||||||
|
|
||||||
|
SaveEditor();
|
||||||
|
ReloadEditorToSameBeatmap();
|
||||||
|
|
||||||
|
AddAssert("large droplet count is the same", () => EditorBeatmap.HitObjects[0].NestedHitObjects.Count(t => t.GetType() == typeof(Droplet)), () => Is.EqualTo(largeDropletCount));
|
||||||
|
AddAssert("tiny droplet count is the same", () => EditorBeatmap.HitObjects[0].NestedHitObjects.Count(t => t.GetType() == typeof(TinyDroplet)), () => Is.EqualTo(tinyDropletCount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
{
|
{
|
||||||
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableFruit((Fruit)hitObject);
|
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableFruit((Fruit)hitObject);
|
||||||
|
|
||||||
protected override PlacementBlueprint CreateBlueprint() => new FruitPlacementBlueprint();
|
protected override HitObjectPlacementBlueprint CreateBlueprint() => new FruitPlacementBlueprint();
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestFruitPlacementPosition()
|
public void TestFruitPlacementPosition()
|
||||||
|
@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
|
|
||||||
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableJuiceStream((JuiceStream)hitObject);
|
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableJuiceStream((JuiceStream)hitObject);
|
||||||
|
|
||||||
protected override PlacementBlueprint CreateBlueprint() => new JuiceStreamPlacementBlueprint();
|
protected override HitObjectPlacementBlueprint CreateBlueprint() => new JuiceStreamPlacementBlueprint();
|
||||||
|
|
||||||
private void addMoveAndClickSteps(double time, float position, bool end = false)
|
private void addMoveAndClickSteps(double time, float position, bool end = false)
|
||||||
{
|
{
|
||||||
|
@ -82,6 +82,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
|
|
||||||
AddMouseMoveStep(-100, 100);
|
AddMouseMoveStep(-100, 100);
|
||||||
addVertexCheckStep(3, 1, times[0], positions[0]);
|
addVertexCheckStep(3, 1, times[0], positions[0]);
|
||||||
|
addDragEndStep();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -100,6 +101,9 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
AddMouseMoveStep(times[2] - 50, positions[2] - 50);
|
AddMouseMoveStep(times[2] - 50, positions[2] - 50);
|
||||||
addVertexCheckStep(4, 1, times[1] - 50, positions[1] - 50);
|
addVertexCheckStep(4, 1, times[1] - 50, positions[1] - 50);
|
||||||
addVertexCheckStep(4, 2, times[2] - 50, positions[2] - 50);
|
addVertexCheckStep(4, 2, times[2] - 50, positions[2] - 50);
|
||||||
|
|
||||||
|
AddStep("release control", () => InputManager.ReleaseKey(Key.ControlLeft));
|
||||||
|
addDragEndStep();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -113,6 +117,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
addDragStartStep(times[1], positions[1]);
|
addDragStartStep(times[1], positions[1]);
|
||||||
AddMouseMoveStep(times[1], 400);
|
AddMouseMoveStep(times[1], 400);
|
||||||
AddAssert("slider velocity changed", () => !hitObject.SliderVelocityMultiplierBindable.IsDefault);
|
AddAssert("slider velocity changed", () => !hitObject.SliderVelocityMultiplierBindable.IsDefault);
|
||||||
|
addDragEndStep();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -129,6 +134,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
AddStep("scroll playfield", () => manualClock.CurrentTime += 200);
|
AddStep("scroll playfield", () => manualClock.CurrentTime += 200);
|
||||||
AddMouseMoveStep(times[1] + 200, positions[1] + 100);
|
AddMouseMoveStep(times[1] + 200, positions[1] + 100);
|
||||||
addVertexCheckStep(2, 1, times[1] + 200, positions[1] + 100);
|
addVertexCheckStep(2, 1, times[1] + 200, positions[1] + 100);
|
||||||
|
addDragEndStep();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -161,18 +167,18 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
addAddVertexSteps(500, 150);
|
addAddVertexSteps(500, 150);
|
||||||
addVertexCheckStep(3, 1, 500, 150);
|
addVertexCheckStep(3, 1, 500, 150);
|
||||||
|
|
||||||
addAddVertexSteps(90, 200);
|
addAddVertexSteps(160, 200);
|
||||||
addVertexCheckStep(4, 1, times[0], positions[0]);
|
addVertexCheckStep(4, 1, 160, 200);
|
||||||
|
|
||||||
addAddVertexSteps(750, 180);
|
addAddVertexSteps(750, 180);
|
||||||
addVertexCheckStep(5, 4, 750, 180);
|
addVertexCheckStep(5, 4, 800, 160);
|
||||||
AddAssert("duration is changed", () => Precision.AlmostEquals(hitObject.Duration, 800 - times[0], 1e-3));
|
AddAssert("duration is changed", () => Precision.AlmostEquals(hitObject.Duration, 800 - times[0], 1e-3));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestDeleteVertex()
|
public void TestDeleteVertex()
|
||||||
{
|
{
|
||||||
double[] times = { 100, 300, 500 };
|
double[] times = { 100, 300, 400 };
|
||||||
float[] positions = { 100, 200, 150 };
|
float[] positions = { 100, 200, 150 };
|
||||||
addBlueprintStep(times, positions);
|
addBlueprintStep(times, positions);
|
||||||
|
|
||||||
@ -265,7 +271,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
AddStep("delete vertex", () =>
|
AddStep("delete vertex", () =>
|
||||||
{
|
{
|
||||||
InputManager.PressKey(Key.ShiftLeft);
|
InputManager.PressKey(Key.ShiftLeft);
|
||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Right);
|
||||||
InputManager.ReleaseKey(Key.ShiftLeft);
|
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
@ -19,20 +18,17 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestLegacyHUDComboCounterHidden([Values] bool withModifiedSkin)
|
public void TestLegacyHUDComboCounterNotExistent([Values] bool withModifiedSkin)
|
||||||
{
|
{
|
||||||
if (withModifiedSkin)
|
if (withModifiedSkin)
|
||||||
{
|
{
|
||||||
AddStep("change component scale", () => Player.ChildrenOfType<LegacyScoreCounter>().First().Scale = new Vector2(2f));
|
AddStep("change component scale", () => Player.ChildrenOfType<LegacyScoreCounter>().First().Scale = new Vector2(2f));
|
||||||
AddStep("update target", () => Player.ChildrenOfType<SkinComponentsContainer>().ForEach(LegacySkin.UpdateDrawableTarget));
|
AddStep("update target", () => Player.ChildrenOfType<SkinnableContainer>().ForEach(LegacySkin.UpdateDrawableTarget));
|
||||||
AddStep("exit player", () => Player.Exit());
|
AddStep("exit player", () => Player.Exit());
|
||||||
CreateTest();
|
CreateTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
AddAssert("legacy HUD combo counter hidden", () =>
|
AddAssert("legacy HUD combo counter not added", () => !Player.ChildrenOfType<LegacyDefaultComboCounter>().Any());
|
||||||
{
|
|
||||||
return Player.ChildrenOfType<LegacyComboCounter>().All(c => c.ChildrenOfType<Container>().Single().Alpha == 0f);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
|
{
|
||||||
|
public partial class TestSceneCatchReplayHandling : OsuManualInputManagerTestScene
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestReplayDetach()
|
||||||
|
{
|
||||||
|
DrawableCatchRuleset drawableRuleset = null!;
|
||||||
|
float catcherPosition = 0;
|
||||||
|
|
||||||
|
AddStep("create drawable ruleset", () => Child = drawableRuleset = new DrawableCatchRuleset(new CatchRuleset(), new CatchBeatmap(), []));
|
||||||
|
AddStep("attach replay", () => drawableRuleset.SetReplayScore(new Score()));
|
||||||
|
AddStep("store catcher position", () => catcherPosition = drawableRuleset.ChildrenOfType<Catcher>().Single().X);
|
||||||
|
AddStep("hold down left", () => InputManager.PressKey(Key.Left));
|
||||||
|
AddAssert("catcher didn't move", () => drawableRuleset.ChildrenOfType<Catcher>().Single().X, () => Is.EqualTo(catcherPosition));
|
||||||
|
AddStep("release left", () => InputManager.ReleaseKey(Key.Left));
|
||||||
|
|
||||||
|
AddStep("detach replay", () => drawableRuleset.SetReplayScore(null));
|
||||||
|
AddStep("hold down left", () => InputManager.PressKey(Key.Left));
|
||||||
|
AddUntilStep("catcher moved", () => drawableRuleset.ChildrenOfType<Catcher>().Single().X, () => Is.Not.EqualTo(catcherPosition));
|
||||||
|
AddStep("release left", () => InputManager.ReleaseKey(Key.Left));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -248,7 +248,8 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
{
|
{
|
||||||
AddStep("enable hit lighting", () => config.SetValue(OsuSetting.HitLighting, true));
|
AddStep("enable hit lighting", () => config.SetValue(OsuSetting.HitLighting, true));
|
||||||
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
||||||
AddAssert("correct hit lighting colour", () => catcher.ChildrenOfType<HitExplosion>().First()?.Entry?.ObjectColour == this.ChildrenOfType<DrawableCatchHitObject>().First().AccentColour.Value);
|
AddAssert("correct hit lighting colour",
|
||||||
|
() => catcher.ChildrenOfType<HitExplosion>().First()?.Entry?.ObjectColour == this.ChildrenOfType<DrawableCatchHitObject>().First().AccentColour.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -259,6 +260,16 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
AddAssert("no hit lighting", () => !catcher.ChildrenOfType<HitExplosion>().Any());
|
AddAssert("no hit lighting", () => !catcher.ChildrenOfType<HitExplosion>().Any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAllExplodedObjectsAtUniquePositions()
|
||||||
|
{
|
||||||
|
AddStep("catch normal fruit", () => attemptCatch(new Fruit()));
|
||||||
|
AddStep("catch normal fruit", () => attemptCatch(new Fruit { IndexInBeatmap = 2, LastInCombo = true }));
|
||||||
|
AddAssert("two fruit at distinct x coordinates",
|
||||||
|
() => this.ChildrenOfType<CaughtFruit>().Select(f => f.DrawPosition.X).Distinct(),
|
||||||
|
() => Has.Exactly(2).Items);
|
||||||
|
}
|
||||||
|
|
||||||
private void checkPlate(int count) => AddAssert($"{count} objects on the plate", () => catcher.CaughtObjects.Count() == count);
|
private void checkPlate(int count) => AddAssert($"{count} objects on the plate", () => catcher.CaughtObjects.Count() == count);
|
||||||
|
|
||||||
private void checkState(CatcherAnimationState state) => AddAssert($"catcher state is {state}", () => catcher.CurrentState == state);
|
private void checkState(CatcherAnimationState state) => AddAssert($"catcher state is {state}", () => catcher.CurrentState == state);
|
||||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y,
|
LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y,
|
||||||
// prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance.
|
// prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance.
|
||||||
// this results in more (or less) ticks being generated in <v8 maps for the same time duration.
|
// this results in more (or less) ticks being generated in <v8 maps for the same time duration.
|
||||||
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1 : ((LegacyControlPointInfo)beatmap.ControlPointInfo).DifficultyPointAt(obj.StartTime).SliderVelocity,
|
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / ((LegacyControlPointInfo)beatmap.ControlPointInfo).DifficultyPointAt(obj.StartTime).SliderVelocity : 1,
|
||||||
SliderVelocityMultiplier = sliderVelocityData?.SliderVelocityMultiplier ?? 1
|
SliderVelocityMultiplier = sliderVelocityData?.SliderVelocityMultiplier ?? 1
|
||||||
}.Yield();
|
}.Yield();
|
||||||
|
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Extensions.EnumExtensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
@ -29,8 +29,10 @@ using osu.Game.Rulesets.Scoring;
|
|||||||
using osu.Game.Rulesets.Scoring.Legacy;
|
using osu.Game.Rulesets.Scoring.Legacy;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.Edit.Setup;
|
||||||
using osu.Game.Screens.Ranking.Statistics;
|
using osu.Game.Screens.Ranking.Statistics;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch
|
namespace osu.Game.Rulesets.Catch
|
||||||
{
|
{
|
||||||
@ -62,43 +64,43 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
|
|
||||||
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
|
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
|
||||||
{
|
{
|
||||||
if (mods.HasFlagFast(LegacyMods.Nightcore))
|
if (mods.HasFlag(LegacyMods.Nightcore))
|
||||||
yield return new CatchModNightcore();
|
yield return new CatchModNightcore();
|
||||||
else if (mods.HasFlagFast(LegacyMods.DoubleTime))
|
else if (mods.HasFlag(LegacyMods.DoubleTime))
|
||||||
yield return new CatchModDoubleTime();
|
yield return new CatchModDoubleTime();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.Perfect))
|
if (mods.HasFlag(LegacyMods.Perfect))
|
||||||
yield return new CatchModPerfect();
|
yield return new CatchModPerfect();
|
||||||
else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
|
else if (mods.HasFlag(LegacyMods.SuddenDeath))
|
||||||
yield return new CatchModSuddenDeath();
|
yield return new CatchModSuddenDeath();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.Cinema))
|
if (mods.HasFlag(LegacyMods.Cinema))
|
||||||
yield return new CatchModCinema();
|
yield return new CatchModCinema();
|
||||||
else if (mods.HasFlagFast(LegacyMods.Autoplay))
|
else if (mods.HasFlag(LegacyMods.Autoplay))
|
||||||
yield return new CatchModAutoplay();
|
yield return new CatchModAutoplay();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.Easy))
|
if (mods.HasFlag(LegacyMods.Easy))
|
||||||
yield return new CatchModEasy();
|
yield return new CatchModEasy();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.Flashlight))
|
if (mods.HasFlag(LegacyMods.Flashlight))
|
||||||
yield return new CatchModFlashlight();
|
yield return new CatchModFlashlight();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.HalfTime))
|
if (mods.HasFlag(LegacyMods.HalfTime))
|
||||||
yield return new CatchModHalfTime();
|
yield return new CatchModHalfTime();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.HardRock))
|
if (mods.HasFlag(LegacyMods.HardRock))
|
||||||
yield return new CatchModHardRock();
|
yield return new CatchModHardRock();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.Hidden))
|
if (mods.HasFlag(LegacyMods.Hidden))
|
||||||
yield return new CatchModHidden();
|
yield return new CatchModHidden();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.NoFail))
|
if (mods.HasFlag(LegacyMods.NoFail))
|
||||||
yield return new CatchModNoFail();
|
yield return new CatchModNoFail();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.Relax))
|
if (mods.HasFlag(LegacyMods.Relax))
|
||||||
yield return new CatchModRelax();
|
yield return new CatchModRelax();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.ScoreV2))
|
if (mods.HasFlag(LegacyMods.ScoreV2))
|
||||||
yield return new ModScoreV2();
|
yield return new ModScoreV2();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,6 +225,30 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
|
|
||||||
public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this);
|
public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this);
|
||||||
|
|
||||||
|
public override IEnumerable<Drawable> CreateEditorSetupSections() =>
|
||||||
|
[
|
||||||
|
new MetadataSection(),
|
||||||
|
new DifficultySection(),
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(SetupScreen.SPACING),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new ResourcesSection
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
},
|
||||||
|
new ColoursSection
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new DesignSection(),
|
||||||
|
];
|
||||||
|
|
||||||
public override IBeatmapVerifier CreateBeatmapVerifier() => new CatchBeatmapVerifier();
|
public override IBeatmapVerifier CreateBeatmapVerifier() => new CatchBeatmapVerifier();
|
||||||
|
|
||||||
public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
|
||||||
@ -248,5 +274,7 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
|
|
||||||
return adjustedDifficulty;
|
return adjustedDifficulty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool EditorShowScrollSpeed => false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ using osu.Game.Skinning;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch
|
namespace osu.Game.Rulesets.Catch
|
||||||
{
|
{
|
||||||
public class CatchSkinComponentLookup : GameplaySkinComponentLookup<CatchSkinComponents>
|
public class CatchSkinComponentLookup : SkinComponentLookup<CatchSkinComponents>
|
||||||
{
|
{
|
||||||
public CatchSkinComponentLookup(CatchSkinComponents component)
|
public CatchSkinComponentLookup(CatchSkinComponents component)
|
||||||
: base(component)
|
: base(component)
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
|
||||||
@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
{
|
{
|
||||||
public class CatchDifficultyCalculator : DifficultyCalculator
|
public class CatchDifficultyCalculator : DifficultyCalculator
|
||||||
{
|
{
|
||||||
private const double star_scaling_factor = 0.153;
|
private const double difficulty_multiplier = 4.59;
|
||||||
|
|
||||||
private float halfCatcherWidth;
|
private float halfCatcherWidth;
|
||||||
|
|
||||||
@ -41,10 +40,10 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
|
|
||||||
CatchDifficultyAttributes attributes = new CatchDifficultyAttributes
|
CatchDifficultyAttributes attributes = new CatchDifficultyAttributes
|
||||||
{
|
{
|
||||||
StarRating = Math.Sqrt(skills[0].DifficultyValue()) * star_scaling_factor,
|
StarRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier,
|
||||||
Mods = mods,
|
Mods = mods,
|
||||||
ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0,
|
ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0,
|
||||||
MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType<JuiceStream>().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)),
|
MaxCombo = beatmap.GetMaxCombo(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return attributes;
|
return attributes;
|
||||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
|||||||
private const float normalized_hitobject_radius = 41.0f;
|
private const float normalized_hitobject_radius = 41.0f;
|
||||||
private const double direction_change_bonus = 21.0;
|
private const double direction_change_bonus = 21.0;
|
||||||
|
|
||||||
protected override double SkillMultiplier => 900;
|
protected override double SkillMultiplier => 1;
|
||||||
protected override double StrainDecayBase => 0.2;
|
protected override double StrainDecayBase => 0.2;
|
||||||
|
|
||||||
protected override double DecayWeight => 0.94;
|
protected override double DecayWeight => 0.94;
|
||||||
|
@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit.Tools;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Edit
|
namespace osu.Game.Rulesets.Catch.Edit
|
||||||
{
|
{
|
||||||
public class BananaShowerCompositionTool : HitObjectCompositionTool
|
public class BananaShowerCompositionTool : CompositionTool
|
||||||
{
|
{
|
||||||
public BananaShowerCompositionTool()
|
public BananaShowerCompositionTool()
|
||||||
: base(nameof(BananaShower))
|
: base(nameof(BananaShower))
|
||||||
@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners);
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new BananaShowerPlacementBlueprint();
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new BananaShowerPlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ using osu.Game.Rulesets.UI.Scrolling;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
||||||
{
|
{
|
||||||
public partial class CatchPlacementBlueprint<THitObject> : PlacementBlueprint
|
public partial class CatchPlacementBlueprint<THitObject> : HitObjectPlacementBlueprint
|
||||||
where THitObject : CatchHitObject, new()
|
where THitObject : CatchHitObject, new()
|
||||||
{
|
{
|
||||||
protected new THitObject HitObject => (THitObject)base.HitObject;
|
protected new THitObject HitObject => (THitObject)base.HitObject;
|
||||||
|
@ -13,6 +13,7 @@ using osu.Game.Rulesets.Catch.Objects;
|
|||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
||||||
@ -42,6 +43,9 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private IBeatSnapProvider? beatSnapProvider { get; set; }
|
private IBeatSnapProvider? beatSnapProvider { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
protected EditorBeatmap? EditorBeatmap { get; private set; }
|
||||||
|
|
||||||
protected EditablePath(Func<float, double> positionToTime)
|
protected EditablePath(Func<float, double> positionToTime)
|
||||||
{
|
{
|
||||||
PositionToTime = positionToTime;
|
PositionToTime = positionToTime;
|
||||||
@ -103,15 +107,23 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
|||||||
//
|
//
|
||||||
// The value is clamped here by the bindable min and max values.
|
// The value is clamped here by the bindable min and max values.
|
||||||
// In case the required velocity is too large, the path is not preserved.
|
// In case the required velocity is too large, the path is not preserved.
|
||||||
|
double previousVelocity = svBindable.Value;
|
||||||
svBindable.Value = Math.Ceiling(requiredVelocity / svToVelocityFactor);
|
svBindable.Value = Math.Ceiling(requiredVelocity / svToVelocityFactor);
|
||||||
|
|
||||||
path.ConvertToSliderPath(hitObject.Path, hitObject.LegacyConvertedY, hitObject.Velocity);
|
// adjust velocity locally, so that once the SV change is applied by applying defaults
|
||||||
|
// (triggered by `EditorBeatmap.Update()` call at end of method),
|
||||||
|
// it results in the outcome desired by the user.
|
||||||
|
double relativeChange = svBindable.Value / previousVelocity;
|
||||||
|
double localVelocity = hitObject.Velocity * relativeChange;
|
||||||
|
path.ConvertToSliderPath(hitObject.Path, hitObject.LegacyConvertedY, localVelocity);
|
||||||
|
|
||||||
if (beatSnapProvider == null) return;
|
if (beatSnapProvider == null) return;
|
||||||
|
|
||||||
double endTime = hitObject.StartTime + path.Duration;
|
double endTime = hitObject.StartTime + path.Duration;
|
||||||
double snappedEndTime = beatSnapProvider.SnapTime(endTime, hitObject.StartTime);
|
double snappedEndTime = beatSnapProvider.SnapTime(endTime, hitObject.StartTime);
|
||||||
hitObject.Path.ExpectedDistance.Value = (snappedEndTime - hitObject.StartTime) * hitObject.Velocity;
|
hitObject.Path.ExpectedDistance.Value = (snappedEndTime - hitObject.StartTime) * localVelocity;
|
||||||
|
|
||||||
|
EditorBeatmap?.Update(hitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector2 ToRelativePosition(Vector2 screenSpacePosition)
|
public Vector2 ToRelativePosition(Vector2 screenSpacePosition)
|
||||||
|
@ -4,12 +4,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
@ -19,22 +18,27 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
|||||||
{
|
{
|
||||||
public MenuItem[] ContextMenuItems => getContextMenuItems().ToArray();
|
public MenuItem[] ContextMenuItems => getContextMenuItems().ToArray();
|
||||||
|
|
||||||
|
private readonly JuiceStream juiceStream;
|
||||||
|
|
||||||
// To handle when the editor is scrolled while dragging.
|
// To handle when the editor is scrolled while dragging.
|
||||||
private Vector2 dragStartPosition;
|
private Vector2 dragStartPosition;
|
||||||
|
|
||||||
[Resolved]
|
public SelectionEditablePath(JuiceStream juiceStream, Func<float, double> positionToTime)
|
||||||
private IEditorChangeHandler? changeHandler { get; set; }
|
|
||||||
|
|
||||||
public SelectionEditablePath(Func<float, double> positionToTime)
|
|
||||||
: base(positionToTime)
|
: base(positionToTime)
|
||||||
{
|
{
|
||||||
|
this.juiceStream = juiceStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddVertex(Vector2 relativePosition)
|
public void AddVertex(Vector2 relativePosition)
|
||||||
{
|
{
|
||||||
|
EditorBeatmap?.BeginChange();
|
||||||
|
|
||||||
double time = Math.Max(0, PositionToTime(relativePosition.Y));
|
double time = Math.Max(0, PositionToTime(relativePosition.Y));
|
||||||
int index = AddVertex(time, relativePosition.X);
|
int index = AddVertex(time, relativePosition.X);
|
||||||
|
UpdateHitObjectFromPath(juiceStream);
|
||||||
selectOnly(index);
|
selectOnly(index);
|
||||||
|
|
||||||
|
EditorBeatmap?.EndChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => InternalChildren.Any(d => d.ReceivePositionalInputAt(screenSpacePos));
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => InternalChildren.Any(d => d.ReceivePositionalInputAt(screenSpacePos));
|
||||||
@ -45,9 +49,13 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
|||||||
if (index == -1 || VertexStates[index].IsFixed)
|
if (index == -1 || VertexStates[index].IsFixed)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (e.Button == MouseButton.Left && e.ShiftPressed)
|
if (e.Button == MouseButton.Right && e.ShiftPressed)
|
||||||
{
|
{
|
||||||
|
EditorBeatmap?.BeginChange();
|
||||||
RemoveVertex(index);
|
RemoveVertex(index);
|
||||||
|
UpdateHitObjectFromPath(juiceStream);
|
||||||
|
EditorBeatmap?.EndChange();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +82,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
|||||||
for (int i = 0; i < VertexCount; i++)
|
for (int i = 0; i < VertexCount; i++)
|
||||||
VertexStates[i].VertexBeforeChange = Vertices[i];
|
VertexStates[i].VertexBeforeChange = Vertices[i];
|
||||||
|
|
||||||
changeHandler?.BeginChange();
|
EditorBeatmap?.BeginChange();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +96,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
|||||||
|
|
||||||
protected override void OnDragEnd(DragEndEvent e)
|
protected override void OnDragEnd(DragEndEvent e)
|
||||||
{
|
{
|
||||||
changeHandler?.EndChange();
|
EditorBeatmap?.EndChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getMouseTargetVertex(Vector2 screenSpacePosition)
|
private int getMouseTargetVertex(Vector2 screenSpacePosition)
|
||||||
@ -118,11 +126,17 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
|||||||
|
|
||||||
private void deleteSelectedVertices()
|
private void deleteSelectedVertices()
|
||||||
{
|
{
|
||||||
|
EditorBeatmap?.BeginChange();
|
||||||
|
|
||||||
for (int i = VertexCount - 1; i >= 0; i--)
|
for (int i = VertexCount - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
if (VertexStates[i].IsSelected)
|
if (VertexStates[i].IsSelected)
|
||||||
RemoveVertex(i);
|
RemoveVertex(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateHitObjectFromPath(juiceStream);
|
||||||
|
|
||||||
|
EditorBeatmap?.EndChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -12,6 +13,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
|||||||
{
|
{
|
||||||
public partial class VertexPiece : Circle
|
public partial class VertexPiece : Circle
|
||||||
{
|
{
|
||||||
|
private VertexState state = new VertexState();
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour osuColour { get; set; } = null!;
|
private OsuColour osuColour { get; set; } = null!;
|
||||||
|
|
||||||
@ -24,7 +27,32 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
|||||||
|
|
||||||
public void UpdateFrom(VertexState state)
|
public void UpdateFrom(VertexState state)
|
||||||
{
|
{
|
||||||
Colour = state.IsSelected ? osuColour.Yellow.Lighten(1) : osuColour.Yellow;
|
this.state = state;
|
||||||
|
updateMarkerDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
updateMarkerDisplay();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
updateMarkerDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the state of the circular control point marker.
|
||||||
|
/// </summary>
|
||||||
|
private void updateMarkerDisplay()
|
||||||
|
{
|
||||||
|
var colour = osuColour.Yellow;
|
||||||
|
|
||||||
|
if (IsHovered || state.IsSelected)
|
||||||
|
colour = colour.Lighten(1);
|
||||||
|
|
||||||
|
Colour = colour;
|
||||||
Alpha = state.IsFixed ? 0.5f : 1;
|
Alpha = state.IsFixed ? 0.5f : 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ using osu.Framework.Caching;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
|
||||||
@ -60,7 +61,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
{
|
{
|
||||||
scrollingPath = new ScrollingPath(),
|
scrollingPath = new ScrollingPath(),
|
||||||
nestedOutlineContainer = new NestedOutlineContainer(),
|
nestedOutlineContainer = new NestedOutlineContainer(),
|
||||||
editablePath = new SelectionEditablePath(positionToTime)
|
editablePath = new SelectionEditablePath(hitObject, positionToTime)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,7 +173,10 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
yield return new OsuMenuItem("Add vertex", MenuItemType.Standard, () =>
|
yield return new OsuMenuItem("Add vertex", MenuItemType.Standard, () =>
|
||||||
{
|
{
|
||||||
editablePath.AddVertex(rightMouseDownPosition);
|
editablePath.AddVertex(rightMouseDownPosition);
|
||||||
});
|
})
|
||||||
|
{
|
||||||
|
Hotkey = new Hotkey(new KeyCombination(InputKey.Control, InputKey.MouseLeft))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
|
@ -18,7 +18,9 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
// The implementation below is probably correct but should be checked if/when exposed via controls.
|
// The implementation below is probably correct but should be checked if/when exposed via controls.
|
||||||
|
|
||||||
float expectedDistance = DurationToDistance(before, after.StartTime - before.GetEndTime());
|
float expectedDistance = DurationToDistance(before, after.StartTime - before.GetEndTime());
|
||||||
float actualDistance = Math.Abs(((CatchHitObject)before).EffectiveX - ((CatchHitObject)after).EffectiveX);
|
|
||||||
|
float previousEndX = (before as JuiceStream)?.EndX ?? ((CatchHitObject)before).EffectiveX;
|
||||||
|
float actualDistance = Math.Abs(previousEndX - ((CatchHitObject)after).EffectiveX);
|
||||||
|
|
||||||
return actualDistance / expectedDistance;
|
return actualDistance / expectedDistance;
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.EnumExtensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
@ -85,7 +84,7 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
|
|
||||||
protected override BeatSnapGrid CreateBeatSnapGrid() => new CatchBeatSnapGrid();
|
protected override BeatSnapGrid CreateBeatSnapGrid() => new CatchBeatSnapGrid();
|
||||||
|
|
||||||
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[]
|
protected override IReadOnlyList<CompositionTool> CompositionTools => new CompositionTool[]
|
||||||
{
|
{
|
||||||
new FruitCompositionTool(),
|
new FruitCompositionTool(),
|
||||||
new JuiceStreamCompositionTool(),
|
new JuiceStreamCompositionTool(),
|
||||||
@ -115,13 +114,33 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
|
{
|
||||||
|
if (e.Repeat)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
handleToggleViaKey(e);
|
||||||
|
return base.OnKeyDown(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnKeyUp(KeyUpEvent e)
|
||||||
|
{
|
||||||
|
handleToggleViaKey(e);
|
||||||
|
base.OnKeyUp(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleToggleViaKey(KeyboardEvent key)
|
||||||
|
{
|
||||||
|
DistanceSnapProvider.HandleToggleViaKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
|
public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
|
||||||
{
|
{
|
||||||
var result = base.FindSnappedPositionAndTime(screenSpacePosition, snapType);
|
var result = base.FindSnappedPositionAndTime(screenSpacePosition, snapType);
|
||||||
|
|
||||||
result.ScreenSpacePosition.X = screenSpacePosition.X;
|
result.ScreenSpacePosition.X = screenSpacePosition.X;
|
||||||
|
|
||||||
if (snapType.HasFlagFast(SnapType.RelativeGrids))
|
if (snapType.HasFlag(SnapType.RelativeGrids))
|
||||||
{
|
{
|
||||||
if (distanceSnapGrid.IsPresent && distanceSnapGrid.GetSnappedPosition(result.ScreenSpacePosition) is SnapResult snapResult &&
|
if (distanceSnapGrid.IsPresent && distanceSnapGrid.GetSnappedPosition(result.ScreenSpacePosition) is SnapResult snapResult &&
|
||||||
Vector2.Distance(snapResult.ScreenSpacePosition, result.ScreenSpacePosition) < distance_snap_radius)
|
Vector2.Distance(snapResult.ScreenSpacePosition, result.ScreenSpacePosition) < distance_snap_radius)
|
||||||
|
@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit.Tools;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Edit
|
namespace osu.Game.Rulesets.Catch.Edit
|
||||||
{
|
{
|
||||||
public class FruitCompositionTool : HitObjectCompositionTool
|
public class FruitCompositionTool : CompositionTool
|
||||||
{
|
{
|
||||||
public FruitCompositionTool()
|
public FruitCompositionTool()
|
||||||
: base(nameof(Fruit))
|
: base(nameof(Fruit))
|
||||||
@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new FruitPlacementBlueprint();
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new FruitPlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit.Tools;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Edit
|
namespace osu.Game.Rulesets.Catch.Edit
|
||||||
{
|
{
|
||||||
public class JuiceStreamCompositionTool : HitObjectCompositionTool
|
public class JuiceStreamCompositionTool : CompositionTool
|
||||||
{
|
{
|
||||||
public JuiceStreamCompositionTool()
|
public JuiceStreamCompositionTool()
|
||||||
: base(nameof(JuiceStream))
|
: base(nameof(JuiceStream))
|
||||||
@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new JuiceStreamPlacementBlueprint();
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new JuiceStreamPlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects
|
namespace osu.Game.Rulesets.Catch.Objects
|
||||||
{
|
{
|
||||||
public abstract class CatchHitObject : HitObject, IHasPosition, IHasComboInformation
|
public abstract class CatchHitObject : HitObject, IHasPosition, IHasComboInformation, IHasTimePreempt
|
||||||
{
|
{
|
||||||
public const float OBJECT_RADIUS = 64;
|
public const float OBJECT_RADIUS = 64;
|
||||||
|
|
||||||
|
@ -21,11 +21,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
public Bindable<Color4> AccentColour { get; } = new Bindable<Color4>();
|
public Bindable<Color4> AccentColour { get; } = new Bindable<Color4>();
|
||||||
public Bindable<bool> HyperDash { get; } = new Bindable<bool>();
|
public Bindable<bool> HyperDash { get; } = new Bindable<bool>();
|
||||||
public Bindable<int> IndexInBeatmap { get; } = new Bindable<int>();
|
public Bindable<int> IndexInBeatmap { get; } = new Bindable<int>();
|
||||||
|
public Vector2 DisplayPosition => DrawPosition;
|
||||||
public Vector2 DisplaySize => Size * Scale;
|
public Vector2 DisplaySize => Size * Scale;
|
||||||
|
|
||||||
public float DisplayRotation => Rotation;
|
public float DisplayRotation => Rotation;
|
||||||
|
|
||||||
public double DisplayStartTime => HitObject.StartTime;
|
public double DisplayStartTime => HitObject.StartTime;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -44,19 +42,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
|
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Copies the hit object visual state from another <see cref="IHasCatchObjectState"/> object.
|
|
||||||
/// </summary>
|
|
||||||
public virtual void CopyStateFrom(IHasCatchObjectState objectState)
|
|
||||||
{
|
|
||||||
HitObject = objectState.HitObject;
|
|
||||||
Scale = Vector2.Divide(objectState.DisplaySize, Size);
|
|
||||||
Rotation = objectState.DisplayRotation;
|
|
||||||
AccentColour.Value = objectState.AccentColour.Value;
|
|
||||||
HyperDash.Value = objectState.HyperDash.Value;
|
|
||||||
IndexInBeatmap.Value = objectState.IndexInBeatmap.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void FreeAfterUse()
|
protected override void FreeAfterUse()
|
||||||
{
|
{
|
||||||
ClearTransforms();
|
ClearTransforms();
|
||||||
@ -64,5 +49,16 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
|
|
||||||
base.FreeAfterUse();
|
base.FreeAfterUse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void RestoreState(CatchObjectState state)
|
||||||
|
{
|
||||||
|
HitObject = state.HitObject;
|
||||||
|
AccentColour.Value = state.AccentColour;
|
||||||
|
HyperDash.Value = state.HyperDash;
|
||||||
|
IndexInBeatmap.Value = state.IndexInBeatmap;
|
||||||
|
Position = state.DisplayPosition;
|
||||||
|
Scale = Vector2.Divide(state.DisplaySize, Size);
|
||||||
|
Rotation = state.DisplayRotation;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Rulesets.Catch.Skinning.Default;
|
using osu.Game.Rulesets.Catch.Skinning.Default;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
{
|
{
|
||||||
@ -36,23 +38,43 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
StartTimeBindable.BindValueChanged(_ => UpdateComboColour());
|
StartTimeBindable.BindValueChanged(_ => UpdateComboColour());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private float startScale;
|
||||||
|
private float endScale;
|
||||||
|
|
||||||
|
private float startAngle;
|
||||||
|
private float endAngle;
|
||||||
|
|
||||||
protected override void UpdateInitialTransforms()
|
protected override void UpdateInitialTransforms()
|
||||||
{
|
{
|
||||||
base.UpdateInitialTransforms();
|
base.UpdateInitialTransforms();
|
||||||
|
|
||||||
|
// Important to have this in UpdateInitialTransforms() to it is re-triggered by RefreshStateTransforms().
|
||||||
const float end_scale = 0.6f;
|
const float end_scale = 0.6f;
|
||||||
const float random_scale_range = 1.6f;
|
const float random_scale_range = 1.6f;
|
||||||
|
|
||||||
ScalingContainer.ScaleTo(HitObject.Scale * (end_scale + random_scale_range * RandomSingle(3)))
|
startScale = end_scale + random_scale_range * RandomSingle(3);
|
||||||
.Then().ScaleTo(HitObject.Scale * end_scale, HitObject.TimePreempt);
|
endScale = end_scale;
|
||||||
|
|
||||||
ScalingContainer.RotateTo(getRandomAngle(1))
|
startAngle = getRandomAngle(1);
|
||||||
.Then()
|
endAngle = getRandomAngle(2);
|
||||||
.RotateTo(getRandomAngle(2), HitObject.TimePreempt);
|
|
||||||
|
|
||||||
float getRandomAngle(int series) => 180 * (RandomSingle(series) * 2 - 1);
|
float getRandomAngle(int series) => 180 * (RandomSingle(series) * 2 - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
double preemptProgress = (Time.Current - (HitObject.StartTime - InitialLifetimeOffset)) / HitObject.TimePreempt;
|
||||||
|
|
||||||
|
// Clamp scale and rotation at the point of bananas being caught, else let them freely extrapolate.
|
||||||
|
if (Result.IsHit)
|
||||||
|
preemptProgress = Math.Min(1, preemptProgress);
|
||||||
|
|
||||||
|
ScalingContainer.Scale = new Vector2(HitObject.Scale * (float)Interpolation.Lerp(startScale, endScale, preemptProgress));
|
||||||
|
ScalingContainer.Rotation = (float)Interpolation.Lerp(startAngle, endAngle, preemptProgress);
|
||||||
|
}
|
||||||
|
|
||||||
public override void PlaySamples()
|
public override void PlaySamples()
|
||||||
{
|
{
|
||||||
base.PlaySamples();
|
base.PlaySamples();
|
||||||
|
@ -2,7 +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.Framework.Utils;
|
||||||
using osu.Game.Rulesets.Catch.Skinning.Default;
|
using osu.Game.Rulesets.Catch.Skinning.Default;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
@ -28,15 +28,24 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
_ => new DropletPiece());
|
_ => new DropletPiece());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private float startRotation;
|
||||||
|
|
||||||
protected override void UpdateInitialTransforms()
|
protected override void UpdateInitialTransforms()
|
||||||
{
|
{
|
||||||
base.UpdateInitialTransforms();
|
base.UpdateInitialTransforms();
|
||||||
|
|
||||||
// roughly matches osu-stable
|
// Important to have this in UpdateInitialTransforms() to it is re-triggered by RefreshStateTransforms().
|
||||||
float startRotation = RandomSingle(1) * 20;
|
startRotation = RandomSingle(1) * 20;
|
||||||
double duration = HitObject.TimePreempt + 2000;
|
}
|
||||||
|
|
||||||
ScalingContainer.RotateTo(startRotation).RotateTo(startRotation + 720, duration);
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
// No clamping for droplets. They should be considered indefinitely spinning regardless of time.
|
||||||
|
// They also never end up on the plate, so they shouldn't stop spinning when caught.
|
||||||
|
double preemptProgress = (Time.Current - (HitObject.StartTime - InitialLifetimeOffset)) / (HitObject.TimePreempt + 2000);
|
||||||
|
ScalingContainer.Rotation = (float)Interpolation.Lerp(startRotation, startRotation + 720, preemptProgress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Game.Rulesets.Catch.Skinning.Default;
|
using osu.Game.Rulesets.Catch.Skinning.Default;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
@ -32,7 +31,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
{
|
{
|
||||||
base.UpdateInitialTransforms();
|
base.UpdateInitialTransforms();
|
||||||
|
|
||||||
ScalingContainer.RotateTo((RandomSingle(1) - 0.5f) * 40);
|
// Important to have this in UpdateInitialTransforms() to it is re-triggered by RefreshStateTransforms().
|
||||||
|
ScalingContainer.Rotation = (RandomSingle(1) - 0.5f) * 40;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected readonly Container ScalingContainer;
|
protected readonly Container ScalingContainer;
|
||||||
|
|
||||||
|
public Vector2 DisplayPosition => DrawPosition;
|
||||||
|
|
||||||
public Vector2 DisplaySize => ScalingContainer.Size * ScalingContainer.Scale;
|
public Vector2 DisplaySize => ScalingContainer.Size * ScalingContainer.Scale;
|
||||||
|
|
||||||
public float DisplayRotation => ScalingContainer.Rotation;
|
public float DisplayRotation => ScalingContainer.Rotation;
|
||||||
@ -95,5 +97,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
|
|
||||||
base.OnFree();
|
base.OnFree();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void RestoreState(CatchObjectState state) => throw new NotSupportedException("Cannot restore state into a drawable catch hitobject.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,17 +13,35 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
public interface IHasCatchObjectState
|
public interface IHasCatchObjectState
|
||||||
{
|
{
|
||||||
PalpableCatchHitObject HitObject { get; }
|
PalpableCatchHitObject HitObject { get; }
|
||||||
|
|
||||||
double DisplayStartTime { get; }
|
|
||||||
|
|
||||||
Bindable<Color4> AccentColour { get; }
|
Bindable<Color4> AccentColour { get; }
|
||||||
|
|
||||||
Bindable<bool> HyperDash { get; }
|
Bindable<bool> HyperDash { get; }
|
||||||
|
|
||||||
Bindable<int> IndexInBeatmap { get; }
|
Bindable<int> IndexInBeatmap { get; }
|
||||||
|
double DisplayStartTime { get; }
|
||||||
|
Vector2 DisplayPosition { get; }
|
||||||
Vector2 DisplaySize { get; }
|
Vector2 DisplaySize { get; }
|
||||||
|
|
||||||
float DisplayRotation { get; }
|
float DisplayRotation { get; }
|
||||||
|
|
||||||
|
void RestoreState(CatchObjectState state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class HasCatchObjectStateExtensions
|
||||||
|
{
|
||||||
|
public static CatchObjectState SaveState(this IHasCatchObjectState target) => new CatchObjectState(
|
||||||
|
target.HitObject,
|
||||||
|
target.AccentColour.Value,
|
||||||
|
target.HyperDash.Value,
|
||||||
|
target.IndexInBeatmap.Value,
|
||||||
|
target.DisplayPosition,
|
||||||
|
target.DisplaySize,
|
||||||
|
target.DisplayRotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct CatchObjectState(
|
||||||
|
PalpableCatchHitObject HitObject,
|
||||||
|
Color4 AccentColour,
|
||||||
|
bool HyperDash,
|
||||||
|
int IndexInBeatmap,
|
||||||
|
Vector2 DisplayPosition,
|
||||||
|
Vector2 DisplaySize,
|
||||||
|
float DisplayRotation);
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Legacy;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects
|
namespace osu.Game.Rulesets.Catch.Objects
|
||||||
@ -46,16 +47,10 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
public double TickDistanceMultiplier = 1;
|
public double TickDistanceMultiplier = 1;
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
private double velocityFactor;
|
public double Velocity { get; private set; }
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
private double tickDistanceFactor;
|
public double TickDistance { get; private set; }
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public double Velocity => velocityFactor * SliderVelocityMultiplier;
|
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public double TickDistance => tickDistanceFactor * TickDistanceMultiplier;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The length of one span of this <see cref="JuiceStream"/>.
|
/// The length of one span of this <see cref="JuiceStream"/>.
|
||||||
@ -68,8 +63,13 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
|
||||||
|
|
||||||
velocityFactor = base_scoring_distance * difficulty.SliderMultiplier / timingPoint.BeatLength;
|
Velocity = base_scoring_distance * difficulty.SliderMultiplier / LegacyRulesetExtensions.GetPrecisionAdjustedBeatLength(this, timingPoint, CatchRuleset.SHORT_NAME);
|
||||||
tickDistanceFactor = base_scoring_distance * difficulty.SliderMultiplier / difficulty.SliderTickRate;
|
|
||||||
|
// WARNING: this is intentionally not computed as `BASE_SCORING_DISTANCE * difficulty.SliderMultiplier`
|
||||||
|
// for backwards compatibility reasons (intentionally introducing floating point errors to match stable).
|
||||||
|
double scoringDistance = Velocity * timingPoint.BeatLength;
|
||||||
|
|
||||||
|
TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||||
|
@ -16,9 +16,12 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
{
|
{
|
||||||
public new CatchBeatmap Beatmap => (CatchBeatmap)base.Beatmap;
|
public new CatchBeatmap Beatmap => (CatchBeatmap)base.Beatmap;
|
||||||
|
|
||||||
|
private readonly float halfCatcherWidth;
|
||||||
|
|
||||||
public CatchAutoGenerator(IBeatmap beatmap)
|
public CatchAutoGenerator(IBeatmap beatmap)
|
||||||
: base(beatmap)
|
: base(beatmap)
|
||||||
{
|
{
|
||||||
|
halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.Difficulty) * 0.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void GenerateFrames()
|
protected override void GenerateFrames()
|
||||||
@ -47,10 +50,7 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
bool dashRequired = speedRequired > Catcher.BASE_WALK_SPEED;
|
bool dashRequired = speedRequired > Catcher.BASE_WALK_SPEED;
|
||||||
bool impossibleJump = speedRequired > Catcher.BASE_DASH_SPEED;
|
bool impossibleJump = speedRequired > Catcher.BASE_DASH_SPEED;
|
||||||
|
|
||||||
// todo: get correct catcher size, based on difficulty CS.
|
if (lastPosition - halfCatcherWidth < h.EffectiveX && lastPosition + halfCatcherWidth > h.EffectiveX)
|
||||||
const float catcher_width_half = Catcher.BASE_SIZE * 0.3f * 0.5f;
|
|
||||||
|
|
||||||
if (lastPosition - catcher_width_half < h.EffectiveX && lastPosition + catcher_width_half > h.EffectiveX)
|
|
||||||
{
|
{
|
||||||
// we are already in the correct range.
|
// we are already in the correct range.
|
||||||
lastTime = h.StartTime;
|
lastTime = h.StartTime;
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||||
@ -28,76 +28,94 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
|
|
||||||
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
|
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
|
||||||
{
|
{
|
||||||
if (lookup is SkinComponentsContainerLookup containerLookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
switch (containerLookup.Target)
|
case GlobalSkinnableContainerLookup containerLookup:
|
||||||
{
|
// Only handle per ruleset defaults here.
|
||||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
if (containerLookup.Ruleset == null)
|
||||||
var components = base.GetDrawableComponent(lookup) as Container;
|
return base.GetDrawableComponent(lookup);
|
||||||
|
|
||||||
if (providesComboCounter && components != null)
|
|
||||||
{
|
|
||||||
// catch may provide its own combo counter; hide the default.
|
|
||||||
// todo: this should be done in an elegant way per ruleset, defining which HUD skin components should be displayed.
|
|
||||||
foreach (var legacyComboCounter in components.OfType<LegacyComboCounter>())
|
|
||||||
legacyComboCounter.HiddenByRulesetImplementation = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return components;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lookup is CatchSkinComponentLookup catchSkinComponent)
|
|
||||||
{
|
|
||||||
switch (catchSkinComponent.Component)
|
|
||||||
{
|
|
||||||
case CatchSkinComponents.Fruit:
|
|
||||||
if (hasPear)
|
|
||||||
return new LegacyFruitPiece();
|
|
||||||
|
|
||||||
|
// we don't have enough assets to display these components (this is especially the case on a "beatmap" skin).
|
||||||
|
if (!IsProvidingLegacyResources)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
case CatchSkinComponents.Banana:
|
// Our own ruleset components default.
|
||||||
if (GetTexture("fruit-bananas") != null)
|
switch (containerLookup.Lookup)
|
||||||
return new LegacyBananaPiece();
|
{
|
||||||
|
case GlobalSkinnableContainers.MainHUDComponents:
|
||||||
|
// todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead.
|
||||||
|
return new DefaultSkinComponentsContainer(container =>
|
||||||
|
{
|
||||||
|
var keyCounter = container.OfType<LegacyKeyCounterDisplay>().FirstOrDefault();
|
||||||
|
|
||||||
return null;
|
if (keyCounter != null)
|
||||||
|
{
|
||||||
|
// set the anchor to top right so that it won't squash to the return button to the top
|
||||||
|
keyCounter.Anchor = Anchor.CentreRight;
|
||||||
|
keyCounter.Origin = Anchor.TopRight;
|
||||||
|
keyCounter.Position = new Vector2(0, -40) * 1.6f;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new LegacyKeyCounterDisplay(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
case CatchSkinComponents.Droplet:
|
return null;
|
||||||
if (GetTexture("fruit-drop") != null)
|
|
||||||
return new LegacyDropletPiece();
|
|
||||||
|
|
||||||
return null;
|
case CatchSkinComponentLookup catchSkinComponent:
|
||||||
|
switch (catchSkinComponent.Component)
|
||||||
|
{
|
||||||
|
case CatchSkinComponents.Fruit:
|
||||||
|
if (hasPear)
|
||||||
|
return new LegacyFruitPiece();
|
||||||
|
|
||||||
case CatchSkinComponents.Catcher:
|
return null;
|
||||||
decimal version = GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value ?? 1;
|
|
||||||
|
|
||||||
if (version < 2.3m)
|
case CatchSkinComponents.Banana:
|
||||||
{
|
if (GetTexture("fruit-bananas") != null)
|
||||||
if (hasOldStyleCatcherSprite())
|
return new LegacyBananaPiece();
|
||||||
return new LegacyCatcherOld();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasNewStyleCatcherSprite())
|
return null;
|
||||||
return new LegacyCatcherNew();
|
|
||||||
|
|
||||||
return null;
|
case CatchSkinComponents.Droplet:
|
||||||
|
if (GetTexture("fruit-drop") != null)
|
||||||
|
return new LegacyDropletPiece();
|
||||||
|
|
||||||
case CatchSkinComponents.CatchComboCounter:
|
return null;
|
||||||
if (providesComboCounter)
|
|
||||||
return new LegacyCatchComboCounter();
|
|
||||||
|
|
||||||
return null;
|
case CatchSkinComponents.Catcher:
|
||||||
|
decimal version = GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value ?? 1;
|
||||||
|
|
||||||
case CatchSkinComponents.HitExplosion:
|
if (version < 2.3m)
|
||||||
if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite())
|
{
|
||||||
return new LegacyHitExplosion();
|
if (hasOldStyleCatcherSprite())
|
||||||
|
return new LegacyCatcherOld();
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
if (hasNewStyleCatcherSprite())
|
||||||
|
return new LegacyCatcherNew();
|
||||||
|
|
||||||
default:
|
return null;
|
||||||
throw new UnsupportedSkinComponentException(lookup);
|
|
||||||
}
|
case CatchSkinComponents.CatchComboCounter:
|
||||||
|
if (providesComboCounter)
|
||||||
|
return new LegacyCatchComboCounter();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
case CatchSkinComponents.HitExplosion:
|
||||||
|
if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite())
|
||||||
|
return new LegacyHitExplosion();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new UnsupportedSkinComponentException(lookup);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.GetDrawableComponent(lookup);
|
return base.GetDrawableComponent(lookup);
|
||||||
|
@ -85,9 +85,25 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
|
|
||||||
protected void SetTexture(Texture? texture, Texture? overlayTexture)
|
protected void SetTexture(Texture? texture, Texture? overlayTexture)
|
||||||
{
|
{
|
||||||
colouredSprite.Texture = texture;
|
// Sizes are reset due to an arguable osu!framework bug where Sprite retains the size of the first set texture.
|
||||||
overlaySprite.Texture = overlayTexture;
|
|
||||||
hyperSprite.Texture = texture;
|
if (colouredSprite.Texture != texture)
|
||||||
|
{
|
||||||
|
colouredSprite.Size = Vector2.Zero;
|
||||||
|
colouredSprite.Texture = texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overlaySprite.Texture != overlayTexture)
|
||||||
|
{
|
||||||
|
overlaySprite.Size = Vector2.Zero;
|
||||||
|
overlaySprite.Texture = overlayTexture;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hyperSprite.Texture != texture)
|
||||||
|
{
|
||||||
|
hyperSprite.Size = Vector2.Zero;
|
||||||
|
hyperSprite.Texture = texture;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -362,7 +363,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
if (caughtObject == null) return;
|
if (caughtObject == null) return;
|
||||||
|
|
||||||
caughtObject.CopyStateFrom(drawableObject);
|
caughtObject.RestoreState(drawableObject.SaveState());
|
||||||
caughtObject.Anchor = Anchor.TopCentre;
|
caughtObject.Anchor = Anchor.TopCentre;
|
||||||
caughtObject.Position = position;
|
caughtObject.Position = position;
|
||||||
caughtObject.Scale *= caught_fruit_scale_adjust;
|
caughtObject.Scale *= caught_fruit_scale_adjust;
|
||||||
@ -411,41 +412,50 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private CaughtObject getDroppedObject(CaughtObject caughtObject)
|
private CaughtObject getDroppedObject(CatchObjectState state)
|
||||||
{
|
{
|
||||||
var droppedObject = getCaughtObject(caughtObject.HitObject);
|
var droppedObject = getCaughtObject(state.HitObject);
|
||||||
Debug.Assert(droppedObject != null);
|
Debug.Assert(droppedObject != null);
|
||||||
|
|
||||||
droppedObject.CopyStateFrom(caughtObject);
|
droppedObject.RestoreState(state);
|
||||||
droppedObject.Anchor = Anchor.TopLeft;
|
droppedObject.Anchor = Anchor.TopLeft;
|
||||||
droppedObject.Position = caughtObjectContainer.ToSpaceOfOtherDrawable(caughtObject.DrawPosition, droppedObjectTarget);
|
droppedObject.Position = caughtObjectContainer.ToSpaceOfOtherDrawable(state.DisplayPosition, droppedObjectTarget);
|
||||||
|
|
||||||
return droppedObject;
|
return droppedObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearPlate(DroppedObjectAnimation animation)
|
private void clearPlate(DroppedObjectAnimation animation)
|
||||||
{
|
{
|
||||||
var caughtObjects = caughtObjectContainer.Children.ToArray();
|
int caughtCount = caughtObjectContainer.Children.Count;
|
||||||
|
CatchObjectState[] states = ArrayPool<CatchObjectState>.Shared.Rent(caughtCount);
|
||||||
|
|
||||||
caughtObjectContainer.Clear(false);
|
try
|
||||||
|
{
|
||||||
|
for (int i = 0; i < caughtCount; i++)
|
||||||
|
states[i] = caughtObjectContainer.Children[i].SaveState();
|
||||||
|
|
||||||
// Use the already returned PoolableDrawables for new objects
|
caughtObjectContainer.Clear(false);
|
||||||
var droppedObjects = caughtObjects.Select(getDroppedObject).ToArray();
|
|
||||||
|
|
||||||
droppedObjectTarget.AddRange(droppedObjects);
|
for (int i = 0; i < caughtCount; i++)
|
||||||
|
{
|
||||||
foreach (var droppedObject in droppedObjects)
|
CaughtObject obj = getDroppedObject(states[i]);
|
||||||
applyDropAnimation(droppedObject, animation);
|
droppedObjectTarget.Add(obj);
|
||||||
|
applyDropAnimation(obj, animation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ArrayPool<CatchObjectState>.Shared.Return(states);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeFromPlate(CaughtObject caughtObject, DroppedObjectAnimation animation)
|
private void removeFromPlate(CaughtObject caughtObject, DroppedObjectAnimation animation)
|
||||||
{
|
{
|
||||||
|
CatchObjectState state = caughtObject.SaveState();
|
||||||
caughtObjectContainer.Remove(caughtObject, false);
|
caughtObjectContainer.Remove(caughtObject, false);
|
||||||
|
|
||||||
var droppedObject = getDroppedObject(caughtObject);
|
var droppedObject = getDroppedObject(state);
|
||||||
|
|
||||||
droppedObjectTarget.Add(droppedObject);
|
droppedObjectTarget.Add(droppedObject);
|
||||||
|
|
||||||
applyDropAnimation(droppedObject, animation);
|
applyDropAnimation(droppedObject, animation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,9 +110,9 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
if (Catcher.Dashing || Catcher.HyperDashing)
|
if (Catcher.Dashing || Catcher.HyperDashing)
|
||||||
{
|
{
|
||||||
double generationInterval = Catcher.HyperDashing ? 25 : 50;
|
const double trail_generation_interval = 16;
|
||||||
|
|
||||||
if (Time.Current - catcherTrails.LastDashTrailTime >= generationInterval)
|
if (Time.Current - catcherTrails.LastDashTrailTime >= trail_generation_interval)
|
||||||
displayCatcherTrail(Catcher.HyperDashing ? CatcherTrailAnimation.HyperDashing : CatcherTrailAnimation.Dashing);
|
displayCatcherTrail(Catcher.HyperDashing ? CatcherTrailAnimation.HyperDashing : CatcherTrailAnimation.Dashing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint)
|
protected override SnapResult SnapForBlueprint(HitObjectPlacementBlueprint blueprint)
|
||||||
{
|
{
|
||||||
double time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position);
|
double time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position);
|
||||||
var pos = column.ScreenSpacePositionAtTime(time);
|
var pos = column.ScreenSpacePositionAtTime(time);
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Mania.Configuration;
|
using osu.Game.Rulesets.Mania.Configuration;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Game.Screens.Edit.Timing;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||||
{
|
{
|
||||||
@ -30,5 +35,43 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
var config = (ManiaRulesetConfigManager)RulesetConfigs.GetConfigFor(Ruleset.Value.CreateInstance()).AsNonNull();
|
var config = (ManiaRulesetConfigManager)RulesetConfigs.GetConfigFor(Ruleset.Value.CreateInstance()).AsNonNull();
|
||||||
config.BindWith(ManiaRulesetSetting.ScrollDirection, direction);
|
config.BindWith(ManiaRulesetSetting.ScrollDirection, direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestReloadOnBPMChange()
|
||||||
|
{
|
||||||
|
HitObjectComposer oldComposer = null!;
|
||||||
|
|
||||||
|
AddStep("store composer", () => oldComposer = this.ChildrenOfType<HitObjectComposer>().Single());
|
||||||
|
AddUntilStep("composer stored", () => oldComposer, () => Is.Not.Null);
|
||||||
|
AddStep("switch to timing tab", () => InputManager.Key(Key.F3));
|
||||||
|
AddUntilStep("wait for loaded", () => this.ChildrenOfType<TimingAdjustButton>().ElementAtOrDefault(1), () => Is.Not.Null);
|
||||||
|
AddStep("change timing point BPM", () =>
|
||||||
|
{
|
||||||
|
var bpmControl = this.ChildrenOfType<TimingAdjustButton>().ElementAt(1);
|
||||||
|
InputManager.MoveMouseTo(bpmControl);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("switch back to composer", () => InputManager.Key(Key.F1));
|
||||||
|
AddUntilStep("composer reloaded", () =>
|
||||||
|
{
|
||||||
|
var composer = this.ChildrenOfType<HitObjectComposer>().SingleOrDefault();
|
||||||
|
return composer != null && composer != oldComposer;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("store composer", () => oldComposer = this.ChildrenOfType<HitObjectComposer>().Single());
|
||||||
|
AddUntilStep("composer stored", () => oldComposer, () => Is.Not.Null);
|
||||||
|
AddStep("undo", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.Key(Key.Z);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
AddUntilStep("composer reloaded", () =>
|
||||||
|
{
|
||||||
|
var composer = this.ChildrenOfType<HitObjectComposer>().SingleOrDefault();
|
||||||
|
return composer != null && composer != oldComposer;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
public partial class TestSceneHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestScene
|
public partial class TestSceneHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestScene
|
||||||
{
|
{
|
||||||
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHoldNote((HoldNote)hitObject);
|
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHoldNote((HoldNote)hitObject);
|
||||||
protected override PlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint();
|
protected override HitObjectPlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestKeyCountChange()
|
public void TestKeyCountChange()
|
||||||
{
|
{
|
||||||
LabelledSliderBar<float> keyCount = null!;
|
FormSliderBar<float> keyCount = null!;
|
||||||
|
|
||||||
AddStep("go to setup screen", () => InputManager.Key(Key.F4));
|
AddStep("go to setup screen", () => InputManager.Key(Key.F4));
|
||||||
AddUntilStep("retrieve key count slider", () => keyCount = Editor.ChildrenOfType<SetupScreen>().Single().ChildrenOfType<LabelledSliderBar<float>>().First(), () => Is.Not.Null);
|
AddUntilStep("retrieve key count slider", () => keyCount = Editor.ChildrenOfType<SetupScreen>().Single().ChildrenOfType<FormSliderBar<float>>().First(), () => Is.Not.Null);
|
||||||
AddAssert("key count is 5", () => keyCount.Current.Value, () => Is.EqualTo(5));
|
AddAssert("key count is 5", () => keyCount.Current.Value, () => Is.EqualTo(5));
|
||||||
AddStep("change key count to 8", () =>
|
AddStep("change key count to 8", () =>
|
||||||
{
|
{
|
||||||
|
@ -6,6 +6,7 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
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.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
@ -92,5 +93,30 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
AddAssert("second object flipped", () => second.StartTime, () => Is.EqualTo(250));
|
AddAssert("second object flipped", () => second.StartTime, () => Is.EqualTo(250));
|
||||||
AddAssert("third object flipped", () => third.StartTime, () => Is.EqualTo(1250));
|
AddAssert("third object flipped", () => third.StartTime, () => Is.EqualTo(1250));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOffScreenObjectsRemainSelectedOnColumnChange()
|
||||||
|
{
|
||||||
|
AddStep("create objects", () =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 20; ++i)
|
||||||
|
EditorBeatmap.Add(new Note { StartTime = 1000 * i, Column = 0 });
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
|
||||||
|
AddStep("start drag", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(this.ChildrenOfType<Column>().First());
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddStep("end drag", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(this.ChildrenOfType<Column>().Last());
|
||||||
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("all objects in last column", () => EditorBeatmap.HitObjects.All(ho => ((ManiaHitObject)ho).Column == 3));
|
||||||
|
AddAssert("all objects remain selected", () => EditorBeatmap.SelectedHitObjects.SequenceEqual(EditorBeatmap.HitObjects));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
private Note getNote() => this.ChildrenOfType<DrawableNote>().FirstOrDefault()?.HitObject;
|
private Note getNote() => this.ChildrenOfType<DrawableNote>().FirstOrDefault()?.HitObject;
|
||||||
|
|
||||||
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject);
|
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject);
|
||||||
protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint();
|
protected override HitObjectPlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,8 +12,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
{
|
{
|
||||||
[TestCase(ManiaAction.Key1)]
|
[TestCase(ManiaAction.Key1)]
|
||||||
[TestCase(ManiaAction.Key1, ManiaAction.Key2)]
|
[TestCase(ManiaAction.Key1, ManiaAction.Key2)]
|
||||||
[TestCase(ManiaAction.Special1)]
|
[TestCase(ManiaAction.Key5)]
|
||||||
[TestCase(ManiaAction.Key8)]
|
[TestCase(ManiaAction.Key9)]
|
||||||
public void TestEncodeDecodeSingleStage(params ManiaAction[] actions)
|
public void TestEncodeDecodeSingleStage(params ManiaAction[] actions)
|
||||||
{
|
{
|
||||||
var beatmap = new ManiaBeatmap(new StageDefinition(9));
|
var beatmap = new ManiaBeatmap(new StageDefinition(9));
|
||||||
@ -29,11 +29,11 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
[TestCase(ManiaAction.Key1)]
|
[TestCase(ManiaAction.Key1)]
|
||||||
[TestCase(ManiaAction.Key1, ManiaAction.Key2)]
|
[TestCase(ManiaAction.Key1, ManiaAction.Key2)]
|
||||||
[TestCase(ManiaAction.Special1)]
|
[TestCase(ManiaAction.Key3)]
|
||||||
[TestCase(ManiaAction.Special2)]
|
|
||||||
[TestCase(ManiaAction.Special1, ManiaAction.Special2)]
|
|
||||||
[TestCase(ManiaAction.Special1, ManiaAction.Key5)]
|
|
||||||
[TestCase(ManiaAction.Key8)]
|
[TestCase(ManiaAction.Key8)]
|
||||||
|
[TestCase(ManiaAction.Key3, ManiaAction.Key8)]
|
||||||
|
[TestCase(ManiaAction.Key3, ManiaAction.Key6)]
|
||||||
|
[TestCase(ManiaAction.Key10)]
|
||||||
public void TestEncodeDecodeDualStage(params ManiaAction[] actions)
|
public void TestEncodeDecodeDualStage(params ManiaAction[] actions)
|
||||||
{
|
{
|
||||||
var beatmap = new ManiaBeatmap(new StageDefinition(5));
|
var beatmap = new ManiaBeatmap(new StageDefinition(5));
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// 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 NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.Timing;
|
||||||
using osu.Game.Rulesets.Mania.Mods;
|
using osu.Game.Rulesets.Mania.Mods;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
@ -17,5 +19,22 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
|
|||||||
Mod = new ManiaModInvert(),
|
Mod = new ManiaModInvert(),
|
||||||
PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2
|
PassCondition = () => Player.ScoreProcessor.JudgedHits >= 2
|
||||||
});
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBreaksPreservedOnOriginalBeatmap()
|
||||||
|
{
|
||||||
|
var beatmap = CreateBeatmap(new ManiaRuleset().RulesetInfo);
|
||||||
|
beatmap.Breaks.Clear();
|
||||||
|
beatmap.Breaks.Add(new BreakPeriod(0, 1000));
|
||||||
|
|
||||||
|
var workingBeatmap = new FlatWorkingBeatmap(beatmap);
|
||||||
|
|
||||||
|
var playableWithInvert = workingBeatmap.GetPlayableBeatmap(new ManiaRuleset().RulesetInfo, new[] { new ManiaModInvert() });
|
||||||
|
Assert.That(playableWithInvert.Breaks.Count, Is.Zero);
|
||||||
|
|
||||||
|
var playableWithoutInvert = workingBeatmap.GetPlayableBeatmap(new ManiaRuleset().RulesetInfo);
|
||||||
|
Assert.That(playableWithoutInvert.Breaks.Count, Is.Not.Zero);
|
||||||
|
Assert.That(playableWithoutInvert.Breaks[0], Is.EqualTo(new BreakPeriod(0, 1000)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
643
osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoRelease.cs
Normal file
643
osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModNoRelease.cs
Normal file
@ -0,0 +1,643 @@
|
|||||||
|
// 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.Screens;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Replays;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Mania.Mods;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
|
using osu.Game.Rulesets.Mania.Scoring;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
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.Mods
|
||||||
|
{
|
||||||
|
public partial class TestSceneManiaModNoRelease : RateAdjustedBeatmapTestScene
|
||||||
|
{
|
||||||
|
private const double time_before_head = 250;
|
||||||
|
private const double time_head = 1500;
|
||||||
|
private const double time_during_hold_1 = 2500;
|
||||||
|
private const double time_tail = 4000;
|
||||||
|
private const double time_after_tail = 5250;
|
||||||
|
|
||||||
|
private List<JudgementResult> judgementResults = new List<JudgementResult>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// -----[ ]-----
|
||||||
|
/// o o
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestNoInput()
|
||||||
|
{
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(time_before_head),
|
||||||
|
new ManiaReplayFrame(time_after_tail),
|
||||||
|
});
|
||||||
|
|
||||||
|
assertHeadJudgement(HitResult.Miss);
|
||||||
|
assertTailJudgement(HitResult.Miss);
|
||||||
|
assertNoteJudgement(HitResult.IgnoreMiss);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// -----[ ]-----
|
||||||
|
/// x o
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestCorrectInput()
|
||||||
|
{
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_tail),
|
||||||
|
});
|
||||||
|
|
||||||
|
assertHeadJudgement(HitResult.Perfect);
|
||||||
|
assertTailJudgement(HitResult.Perfect);
|
||||||
|
assertNoteJudgement(HitResult.IgnoreHit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// -----[ ]-----
|
||||||
|
/// x o
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestLateRelease()
|
||||||
|
{
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_after_tail),
|
||||||
|
});
|
||||||
|
|
||||||
|
assertHeadJudgement(HitResult.Perfect);
|
||||||
|
assertTailJudgement(HitResult.Perfect);
|
||||||
|
assertNoteJudgement(HitResult.IgnoreHit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// -----[ ]-----
|
||||||
|
/// x o
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestPressTooEarlyAndReleaseAfterTail()
|
||||||
|
{
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_after_tail, ManiaAction.Key1),
|
||||||
|
});
|
||||||
|
|
||||||
|
assertHeadJudgement(HitResult.Miss);
|
||||||
|
assertTailJudgement(HitResult.Miss);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// -----[ ]-----
|
||||||
|
/// x o
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestPressTooEarlyAndReleaseAtTail()
|
||||||
|
{
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_tail),
|
||||||
|
});
|
||||||
|
|
||||||
|
assertHeadJudgement(HitResult.Miss);
|
||||||
|
assertTailJudgement(HitResult.Miss);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// -----[ ]-----
|
||||||
|
/// xo x o
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestPressTooEarlyThenPressAtStartAndReleaseAfterTail()
|
||||||
|
{
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_before_head + 10),
|
||||||
|
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_after_tail),
|
||||||
|
});
|
||||||
|
|
||||||
|
assertHeadJudgement(HitResult.Perfect);
|
||||||
|
assertTailJudgement(HitResult.Perfect);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// -----[ ]-----
|
||||||
|
/// xo x o
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestPressTooEarlyThenPressAtStartAndReleaseAtTail()
|
||||||
|
{
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_before_head + 10),
|
||||||
|
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_tail),
|
||||||
|
});
|
||||||
|
|
||||||
|
assertHeadJudgement(HitResult.Perfect);
|
||||||
|
assertTailJudgement(HitResult.Perfect);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// -----[ ]-----
|
||||||
|
/// xo o
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestPressAtStartAndBreak()
|
||||||
|
{
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_head + 10),
|
||||||
|
new ManiaReplayFrame(time_after_tail),
|
||||||
|
});
|
||||||
|
|
||||||
|
assertHeadJudgement(HitResult.Perfect);
|
||||||
|
assertTailJudgement(HitResult.Miss);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// -----[ ]-----
|
||||||
|
/// xox o
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestPressAtStartThenReleaseAndImmediatelyRepress()
|
||||||
|
{
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_head + 1),
|
||||||
|
new ManiaReplayFrame(time_head + 2, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_tail),
|
||||||
|
});
|
||||||
|
|
||||||
|
assertHeadJudgement(HitResult.Perfect);
|
||||||
|
assertComboAtJudgement(0, 1);
|
||||||
|
assertTailJudgement(HitResult.Meh);
|
||||||
|
assertComboAtJudgement(1, 0);
|
||||||
|
assertComboAtJudgement(3, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// -----[ ]-----
|
||||||
|
/// xo x o
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestPressAtStartThenBreakThenRepressAndReleaseAfterTail()
|
||||||
|
{
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_head + 10),
|
||||||
|
new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_after_tail),
|
||||||
|
});
|
||||||
|
|
||||||
|
assertHeadJudgement(HitResult.Perfect);
|
||||||
|
assertTailJudgement(HitResult.Meh);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// -----[ ]-----
|
||||||
|
/// xo x o o
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestPressAtStartThenBreakThenRepressAndReleaseAtTail()
|
||||||
|
{
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(time_head, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_head + 10),
|
||||||
|
new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_tail),
|
||||||
|
});
|
||||||
|
|
||||||
|
assertHeadJudgement(HitResult.Perfect);
|
||||||
|
assertTailJudgement(HitResult.Meh);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// -----[ ]-----
|
||||||
|
/// x o
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestPressDuringNoteAndReleaseAfterTail()
|
||||||
|
{
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_after_tail),
|
||||||
|
});
|
||||||
|
|
||||||
|
assertHeadJudgement(HitResult.Miss);
|
||||||
|
assertTailJudgement(HitResult.Meh);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// -----[ ]-----
|
||||||
|
/// x o o
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestPressDuringNoteAndReleaseAtTail()
|
||||||
|
{
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_tail),
|
||||||
|
});
|
||||||
|
|
||||||
|
assertHeadJudgement(HitResult.Miss);
|
||||||
|
assertTailJudgement(HitResult.Meh);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// -----[ ]--------------
|
||||||
|
/// xo
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestPressAndReleaseAfterTailWithCloseByHead()
|
||||||
|
{
|
||||||
|
const int duration = 30;
|
||||||
|
|
||||||
|
var beatmap = new Beatmap<ManiaHitObject>
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
// hold note is very short, to make the head still in range
|
||||||
|
new HoldNote
|
||||||
|
{
|
||||||
|
StartTime = time_head,
|
||||||
|
Duration = duration,
|
||||||
|
Column = 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
||||||
|
Ruleset = new ManiaRuleset().RulesetInfo
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(time_head + duration + 60, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_head + duration + 70),
|
||||||
|
}, beatmap);
|
||||||
|
|
||||||
|
assertHeadJudgement(HitResult.Ok);
|
||||||
|
assertTailJudgement(HitResult.Perfect);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// -----[ ]-O-------------
|
||||||
|
/// xo o
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestPressAndReleaseJustBeforeTailWithNearbyNoteAndCloseByHead()
|
||||||
|
{
|
||||||
|
Note note;
|
||||||
|
|
||||||
|
const int duration = 50;
|
||||||
|
|
||||||
|
var beatmap = new Beatmap<ManiaHitObject>
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
// hold note is very short, to make the head still in range
|
||||||
|
new HoldNote
|
||||||
|
{
|
||||||
|
StartTime = time_head,
|
||||||
|
Duration = duration,
|
||||||
|
Column = 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Next note within tail lenience
|
||||||
|
note = new Note
|
||||||
|
{
|
||||||
|
StartTime = time_head + duration + 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
||||||
|
Ruleset = new ManiaRuleset().RulesetInfo
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(time_head + duration, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_head + duration + 10),
|
||||||
|
}, beatmap);
|
||||||
|
|
||||||
|
assertHeadJudgement(HitResult.Good);
|
||||||
|
assertTailJudgement(HitResult.Perfect);
|
||||||
|
|
||||||
|
assertHitObjectJudgement(note, HitResult.Miss);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// -----[ ]--O--
|
||||||
|
/// xo o
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestPressAndReleaseJustBeforeTailWithNearbyNote()
|
||||||
|
{
|
||||||
|
Note note;
|
||||||
|
|
||||||
|
var beatmap = new Beatmap<ManiaHitObject>
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HoldNote
|
||||||
|
{
|
||||||
|
StartTime = time_head,
|
||||||
|
Duration = time_tail - time_head,
|
||||||
|
Column = 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Next note within tail lenience
|
||||||
|
note = new Note
|
||||||
|
{
|
||||||
|
StartTime = time_tail + 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
||||||
|
Ruleset = new ManiaRuleset().RulesetInfo
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(time_tail - 10, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_tail),
|
||||||
|
}, beatmap);
|
||||||
|
|
||||||
|
assertHeadJudgement(HitResult.Miss);
|
||||||
|
assertTailJudgement(HitResult.Miss);
|
||||||
|
|
||||||
|
assertHitObjectJudgement(note, HitResult.Good);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// -----[ ]-----
|
||||||
|
/// xo
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestPressAndReleaseJustAfterTail()
|
||||||
|
{
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(time_tail + 20, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_tail + 30),
|
||||||
|
});
|
||||||
|
|
||||||
|
assertHeadJudgement(HitResult.Miss);
|
||||||
|
assertTailJudgement(HitResult.Meh);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// -----[ ]--O--
|
||||||
|
/// xo o
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestPressAndReleaseJustAfterTailWithNearbyNote()
|
||||||
|
{
|
||||||
|
// Next note within tail lenience
|
||||||
|
Note note = new Note { StartTime = time_tail + 50 };
|
||||||
|
|
||||||
|
var beatmap = new Beatmap<ManiaHitObject>
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HoldNote
|
||||||
|
{
|
||||||
|
StartTime = time_head,
|
||||||
|
Duration = time_tail - time_head,
|
||||||
|
Column = 0,
|
||||||
|
},
|
||||||
|
note
|
||||||
|
},
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
||||||
|
Ruleset = new ManiaRuleset().RulesetInfo
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(time_tail + 10, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_tail + 20),
|
||||||
|
}, beatmap);
|
||||||
|
|
||||||
|
assertHeadJudgement(HitResult.Miss);
|
||||||
|
assertTailJudgement(HitResult.Miss);
|
||||||
|
|
||||||
|
assertHitObjectJudgement(note, HitResult.Great);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// -----[ ]-----
|
||||||
|
/// xo o
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestPressAndReleaseAtTail()
|
||||||
|
{
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(time_tail, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_tail + 10),
|
||||||
|
});
|
||||||
|
|
||||||
|
assertHeadJudgement(HitResult.Miss);
|
||||||
|
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 =
|
||||||
|
{
|
||||||
|
Difficulty = 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.IsHit()));
|
||||||
|
|
||||||
|
AddAssert("second hold note hit", () => judgementResults.Where(j => beatmap.HitObjects[1].NestedHitObjects.Contains(j.HitObject))
|
||||||
|
.All(j => j.Type.IsHit()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestZeroLength()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<ManiaHitObject>
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HoldNote
|
||||||
|
{
|
||||||
|
StartTime = 1000,
|
||||||
|
Duration = 0,
|
||||||
|
Column = 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo },
|
||||||
|
};
|
||||||
|
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(beatmap.HitObjects[0].StartTime, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(beatmap.HitObjects[0].GetEndTime() + 1),
|
||||||
|
}, beatmap);
|
||||||
|
|
||||||
|
AddAssert("hold note hit", () => judgementResults.Where(j => beatmap.HitObjects[0].NestedHitObjects.Contains(j.HitObject))
|
||||||
|
.All(j => j.Type.IsHit()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertHitObjectJudgement(HitObject hitObject, HitResult result)
|
||||||
|
=> AddAssert($"object judged as {result}", () => judgementResults.First(j => j.HitObject == hitObject).Type, () => Is.EqualTo(result));
|
||||||
|
|
||||||
|
private void assertHeadJudgement(HitResult result)
|
||||||
|
=> AddAssert($"head judged as {result}", () => judgementResults.First(j => j.HitObject is Note).Type, () => Is.EqualTo(result));
|
||||||
|
|
||||||
|
private void assertTailJudgement(HitResult result)
|
||||||
|
=> AddAssert($"tail judged as {result}", () => judgementResults.Single(j => j.HitObject is TailNote).Type, () => Is.EqualTo(result));
|
||||||
|
|
||||||
|
private void assertNoteJudgement(HitResult result)
|
||||||
|
=> AddAssert($"hold note judged as {result}", () => judgementResults.Single(j => j.HitObject is HoldNote).Type, () => Is.EqualTo(result));
|
||||||
|
|
||||||
|
private void assertComboAtJudgement(int judgementIndex, int combo)
|
||||||
|
=> AddAssert($"combo at judgement {judgementIndex} is {combo}", () => judgementResults.ElementAt(judgementIndex).ComboAfterJudgement, () => Is.EqualTo(combo));
|
||||||
|
|
||||||
|
private ScoreAccessibleReplayPlayer currentPlayer = null!;
|
||||||
|
|
||||||
|
private void performTest(List<ReplayFrame> frames, Beatmap<ManiaHitObject>? beatmap = null)
|
||||||
|
{
|
||||||
|
if (beatmap == null)
|
||||||
|
{
|
||||||
|
beatmap = new Beatmap<ManiaHitObject>
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HoldNote
|
||||||
|
{
|
||||||
|
StartTime = time_head,
|
||||||
|
Duration = time_tail - time_head,
|
||||||
|
Column = 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
||||||
|
Ruleset = new ManiaRuleset().RulesetInfo,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 0.1f });
|
||||||
|
}
|
||||||
|
|
||||||
|
AddStep("load player", () =>
|
||||||
|
{
|
||||||
|
SelectedMods.Value = new List<Mod>
|
||||||
|
{
|
||||||
|
new ManiaModNoRelease()
|
||||||
|
};
|
||||||
|
|
||||||
|
Beatmap.Value = CreateWorkingBeatmap(beatmap);
|
||||||
|
|
||||||
|
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 partial class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||||
|
{
|
||||||
|
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||||
|
|
||||||
|
protected override bool PauseOnFocusLost => false;
|
||||||
|
|
||||||
|
public ScoreAccessibleReplayPlayer(Score score)
|
||||||
|
: base(score, new PlayerConfiguration
|
||||||
|
{
|
||||||
|
AllowPause = false,
|
||||||
|
ShowResults = false,
|
||||||
|
})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
public abstract partial class ManiaSkinnableTestScene : SkinnableTestScene
|
public abstract partial class ManiaSkinnableTestScene : SkinnableTestScene
|
||||||
{
|
{
|
||||||
[Cached(Type = typeof(IScrollingInfo))]
|
[Cached(Type = typeof(IScrollingInfo))]
|
||||||
private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo();
|
protected readonly TestScrollingInfo ScrollingInfo = new TestScrollingInfo();
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private readonly StageDefinition stage = new StageDefinition(4);
|
private readonly StageDefinition stage = new StageDefinition(4);
|
||||||
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
|
|
||||||
protected ManiaSkinnableTestScene()
|
protected ManiaSkinnableTestScene()
|
||||||
{
|
{
|
||||||
scrollingInfo.Direction.Value = ScrollingDirection.Down;
|
ScrollingInfo.Direction.Value = ScrollingDirection.Down;
|
||||||
|
|
||||||
Add(new Box
|
Add(new Box
|
||||||
{
|
{
|
||||||
@ -43,16 +43,16 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestScrollingDown()
|
public void TestScrollingDown()
|
||||||
{
|
{
|
||||||
AddStep("change direction to down", () => scrollingInfo.Direction.Value = ScrollingDirection.Down);
|
AddStep("change direction to down", () => ScrollingInfo.Direction.Value = ScrollingDirection.Down);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestScrollingUp()
|
public void TestScrollingUp()
|
||||||
{
|
{
|
||||||
AddStep("change direction to up", () => scrollingInfo.Direction.Value = ScrollingDirection.Up);
|
AddStep("change direction to up", () => ScrollingInfo.Direction.Value = ScrollingDirection.Up);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestScrollingInfo : IScrollingInfo
|
protected class TestScrollingInfo : IScrollingInfo
|
||||||
{
|
{
|
||||||
public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
||||||
|
|
||||||
|
@ -0,0 +1,95 @@
|
|||||||
|
// 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.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Mania.Skinning.Argon;
|
||||||
|
using osu.Game.Rulesets.Mania.Skinning.Legacy;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||||
|
{
|
||||||
|
public partial class TestSceneComboCounter : ManiaSkinnableTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private ScoreProcessor scoreProcessor = new ScoreProcessor(new ManiaRuleset());
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDisplay()
|
||||||
|
{
|
||||||
|
setup(Anchor.Centre);
|
||||||
|
AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Great }), 20);
|
||||||
|
AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAnchorOrigin()
|
||||||
|
{
|
||||||
|
AddStep("set direction down", () => ScrollingInfo.Direction.Value = ScrollingDirection.Down);
|
||||||
|
setup(Anchor.TopCentre, 20);
|
||||||
|
AddStep("set direction up", () => ScrollingInfo.Direction.Value = ScrollingDirection.Up);
|
||||||
|
check(Anchor.BottomCentre, -20);
|
||||||
|
|
||||||
|
AddStep("set direction up", () => ScrollingInfo.Direction.Value = ScrollingDirection.Up);
|
||||||
|
setup(Anchor.BottomCentre, -20);
|
||||||
|
AddStep("set direction down", () => ScrollingInfo.Direction.Value = ScrollingDirection.Down);
|
||||||
|
check(Anchor.TopCentre, 20);
|
||||||
|
|
||||||
|
AddStep("set direction down", () => ScrollingInfo.Direction.Value = ScrollingDirection.Down);
|
||||||
|
setup(Anchor.Centre, 20);
|
||||||
|
AddStep("set direction up", () => ScrollingInfo.Direction.Value = ScrollingDirection.Up);
|
||||||
|
check(Anchor.Centre, 20);
|
||||||
|
|
||||||
|
AddStep("set direction up", () => ScrollingInfo.Direction.Value = ScrollingDirection.Up);
|
||||||
|
setup(Anchor.Centre, -20);
|
||||||
|
AddStep("set direction down", () => ScrollingInfo.Direction.Value = ScrollingDirection.Down);
|
||||||
|
check(Anchor.Centre, -20);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setup(Anchor anchor, float y = 0)
|
||||||
|
{
|
||||||
|
AddStep($"setup {anchor} {y}", () => SetContents(s =>
|
||||||
|
{
|
||||||
|
var container = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (s is ArgonSkin)
|
||||||
|
container.Add(new ArgonManiaComboCounter());
|
||||||
|
else if (s is LegacySkin)
|
||||||
|
container.Add(new LegacyManiaComboCounter());
|
||||||
|
else
|
||||||
|
container.Add(new LegacyManiaComboCounter());
|
||||||
|
|
||||||
|
container.Child.Anchor = anchor;
|
||||||
|
container.Child.Origin = Anchor.Centre;
|
||||||
|
container.Child.Y = y;
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void check(Anchor anchor, float y)
|
||||||
|
{
|
||||||
|
AddAssert($"check {anchor} {y}", () =>
|
||||||
|
{
|
||||||
|
foreach (var combo in this.ChildrenOfType<ISerialisableDrawable>())
|
||||||
|
{
|
||||||
|
var drawableCombo = (Drawable)combo;
|
||||||
|
if (drawableCombo.Anchor != anchor || drawableCombo.Y != y)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,14 +28,20 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
AddStep("Show " + result.GetDescription(), () =>
|
AddStep("Show " + result.GetDescription(), () =>
|
||||||
{
|
{
|
||||||
SetContents(_ =>
|
SetContents(_ =>
|
||||||
new DrawableManiaJudgement(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement())
|
{
|
||||||
{
|
var drawableManiaJudgement = new DrawableManiaJudgement
|
||||||
Type = result
|
|
||||||
}, null)
|
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
drawableManiaJudgement.Apply(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement())
|
||||||
|
{
|
||||||
|
Type = result
|
||||||
|
}, null);
|
||||||
|
|
||||||
|
return drawableManiaJudgement;
|
||||||
|
});
|
||||||
|
|
||||||
// for test purposes, undo the Y adjustment related to the `ScorePosition` legacy positioning config value
|
// for test purposes, undo the Y adjustment related to the `ScorePosition` legacy positioning config value
|
||||||
// (see `LegacyManiaJudgementPiece.load()`).
|
// (see `LegacyManiaJudgementPiece.load()`).
|
||||||
|
@ -3,15 +3,22 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||||
{
|
{
|
||||||
public partial class TestScenePlayfield : ManiaSkinnableTestScene
|
public partial class TestScenePlayfield : ManiaSkinnableTestScene
|
||||||
{
|
{
|
||||||
|
[Cached]
|
||||||
|
private ScoreProcessor scoreProcessor = new ScoreProcessor(new ManiaRuleset());
|
||||||
|
|
||||||
private List<StageDefinition> stageDefinitions = new List<StageDefinition>();
|
private List<StageDefinition> stageDefinitions = new List<StageDefinition>();
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -29,6 +36,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
Child = new ManiaPlayfield(stageDefinitions)
|
Child = new ManiaPlayfield(stageDefinitions)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Perfect }), 20);
|
||||||
|
AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss }));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(2)]
|
[TestCase(2)]
|
||||||
@ -54,6 +64,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Perfect }), 20);
|
||||||
|
AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss }));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IBeatmap CreateBeatmapForSkinProvider()
|
protected override IBeatmap CreateBeatmapForSkinProvider()
|
||||||
|
@ -14,12 +14,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
SetContents(_ =>
|
SetContents(_ =>
|
||||||
{
|
{
|
||||||
ManiaAction normalAction = ManiaAction.Key1;
|
ManiaAction action = ManiaAction.Key1;
|
||||||
ManiaAction specialAction = ManiaAction.Special1;
|
|
||||||
|
|
||||||
return new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4)
|
return new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4)
|
||||||
{
|
{
|
||||||
Child = new Stage(0, new StageDefinition(4), ref normalAction, ref specialAction)
|
Child = new Stage(0, new StageDefinition(4), ref action)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -36,8 +36,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
||||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||||
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed");
|
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
|
||||||
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released");
|
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -57,8 +57,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
||||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||||
Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed");
|
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
|
||||||
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released");
|
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -474,8 +474,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
AddAssert("first hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[0].NestedHitObjects.Contains(j.HitObject))
|
AddAssert("first hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[0].NestedHitObjects.Contains(j.HitObject))
|
||||||
.All(j => !j.Type.IsHit()));
|
.All(j => !j.Type.IsHit()));
|
||||||
|
|
||||||
AddAssert("second hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[1].NestedHitObjects.Contains(j.HitObject))
|
AddAssert("second hold note hit", () => judgementResults.Where(j => beatmap.HitObjects[1].NestedHitObjects.Contains(j.HitObject))
|
||||||
.All(j => j.Type.IsHit()));
|
.All(j => j.Type.IsHit()));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Mods;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
|
{
|
||||||
|
public partial class TestSceneManiaPlayerLegacySkin : LegacySkinPlayerTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||||
|
|
||||||
|
// play with a converted beatmap to allow dual stages mod to work.
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(new RulesetInfo());
|
||||||
|
|
||||||
|
protected override bool HasCustomSteps => true;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSingleStage()
|
||||||
|
{
|
||||||
|
AddStep("Load single stage", LoadPlayer);
|
||||||
|
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDualStage()
|
||||||
|
{
|
||||||
|
AddStep("Load dual stage", () => LoadPlayer(new Mod[] { new ManiaModDualStages() }));
|
||||||
|
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,6 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Extensions.EnumExtensions;
|
|
||||||
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;
|
||||||
@ -100,7 +99,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
private bool verifyAnchors(DrawableHitObject hitObject, Anchor expectedAnchor)
|
private bool verifyAnchors(DrawableHitObject hitObject, Anchor expectedAnchor)
|
||||||
=> hitObject.Anchor.HasFlagFast(expectedAnchor) && hitObject.Origin.HasFlagFast(expectedAnchor);
|
=> hitObject.Anchor.HasFlag(expectedAnchor) && hitObject.Origin.HasFlag(expectedAnchor);
|
||||||
|
|
||||||
private bool verifyAnchors(DrawableHoldNote holdNote, Anchor expectedAnchor)
|
private bool verifyAnchors(DrawableHoldNote holdNote, Anchor expectedAnchor)
|
||||||
=> verifyAnchors((DrawableHitObject)holdNote, expectedAnchor) && holdNote.NestedHitObjects.All(n => verifyAnchors(n, expectedAnchor));
|
=> verifyAnchors((DrawableHitObject)holdNote, expectedAnchor) && holdNote.NestedHitObjects.All(n => verifyAnchors(n, expectedAnchor));
|
||||||
|
@ -131,9 +131,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
private ScrollingTestContainer createStage(ScrollingDirection direction, ManiaAction action)
|
private ScrollingTestContainer createStage(ScrollingDirection direction, ManiaAction action)
|
||||||
{
|
{
|
||||||
var specialAction = ManiaAction.Special1;
|
var stage = new Stage(0, new StageDefinition(2), ref action);
|
||||||
|
|
||||||
var stage = new Stage(0, new StageDefinition(2), ref action, ref specialAction);
|
|
||||||
stages.Add(stage);
|
stages.Add(stage);
|
||||||
|
|
||||||
return new ScrollingTestContainer(direction)
|
return new ScrollingTestContainer(direction)
|
||||||
|
@ -6,7 +6,6 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Game.Audio;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
@ -271,7 +270,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
Duration = endTimeData.Duration,
|
Duration = endTimeData.Duration,
|
||||||
Column = column,
|
Column = column,
|
||||||
Samples = HitObject.Samples,
|
Samples = HitObject.Samples,
|
||||||
NodeSamples = (HitObject as IHasRepeats)?.NodeSamples ?? defaultNodeSamples
|
NodeSamples = (HitObject as IHasRepeats)?.NodeSamples ?? HoldNote.CreateDefaultNodeSamples(HitObject)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (HitObject is IHasXPosition)
|
else if (HitObject is IHasXPosition)
|
||||||
@ -286,16 +285,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
|||||||
|
|
||||||
return pattern;
|
return pattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <remarks>
|
|
||||||
/// osu!mania-specific beatmaps in stable only play samples at the start of the hold note.
|
|
||||||
/// </remarks>
|
|
||||||
private List<IList<HitSampleInfo>> defaultNodeSamples
|
|
||||||
=> new List<IList<HitSampleInfo>>
|
|
||||||
{
|
|
||||||
HitObject.Samples,
|
|
||||||
new List<HitSampleInfo>()
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Extensions.EnumExtensions;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -79,7 +78,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
else
|
else
|
||||||
convertType |= PatternType.LowProbability;
|
convertType |= PatternType.LowProbability;
|
||||||
|
|
||||||
if (!convertType.HasFlagFast(PatternType.KeepSingle))
|
if (!convertType.HasFlag(PatternType.KeepSingle))
|
||||||
{
|
{
|
||||||
if (HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH) && TotalColumns != 8)
|
if (HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH) && TotalColumns != 8)
|
||||||
convertType |= PatternType.Mirror;
|
convertType |= PatternType.Mirror;
|
||||||
@ -102,7 +101,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
|
|
||||||
int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0;
|
int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0;
|
||||||
|
|
||||||
if (convertType.HasFlagFast(PatternType.Reverse) && PreviousPattern.HitObjects.Any())
|
if (convertType.HasFlag(PatternType.Reverse) && PreviousPattern.HitObjects.Any())
|
||||||
{
|
{
|
||||||
// Generate a new pattern by copying the last hit objects in reverse-column order
|
// Generate a new pattern by copying the last hit objects in reverse-column order
|
||||||
for (int i = RandomStart; i < TotalColumns; i++)
|
for (int i = RandomStart; i < TotalColumns; i++)
|
||||||
@ -114,7 +113,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
return pattern;
|
return pattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (convertType.HasFlagFast(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1
|
if (convertType.HasFlag(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1
|
||||||
// If we convert to 7K + 1, let's not overload the special key
|
// If we convert to 7K + 1, let's not overload the special key
|
||||||
&& (TotalColumns != 8 || lastColumn != 0)
|
&& (TotalColumns != 8 || lastColumn != 0)
|
||||||
// Make sure the last column was not the centre column
|
// Make sure the last column was not the centre column
|
||||||
@ -127,7 +126,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
return pattern;
|
return pattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (convertType.HasFlagFast(PatternType.ForceStack) && PreviousPattern.HitObjects.Any())
|
if (convertType.HasFlag(PatternType.ForceStack) && PreviousPattern.HitObjects.Any())
|
||||||
{
|
{
|
||||||
// Generate a new pattern by placing on the already filled columns
|
// Generate a new pattern by placing on the already filled columns
|
||||||
for (int i = RandomStart; i < TotalColumns; i++)
|
for (int i = RandomStart; i < TotalColumns; i++)
|
||||||
@ -141,7 +140,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
|
|
||||||
if (PreviousPattern.HitObjects.Count() == 1)
|
if (PreviousPattern.HitObjects.Count() == 1)
|
||||||
{
|
{
|
||||||
if (convertType.HasFlagFast(PatternType.Stair))
|
if (convertType.HasFlag(PatternType.Stair))
|
||||||
{
|
{
|
||||||
// Generate a new pattern by placing on the next column, cycling back to the start if there is no "next"
|
// Generate a new pattern by placing on the next column, cycling back to the start if there is no "next"
|
||||||
int targetColumn = lastColumn + 1;
|
int targetColumn = lastColumn + 1;
|
||||||
@ -152,7 +151,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
return pattern;
|
return pattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (convertType.HasFlagFast(PatternType.ReverseStair))
|
if (convertType.HasFlag(PatternType.ReverseStair))
|
||||||
{
|
{
|
||||||
// Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous"
|
// Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous"
|
||||||
int targetColumn = lastColumn - 1;
|
int targetColumn = lastColumn - 1;
|
||||||
@ -164,10 +163,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (convertType.HasFlagFast(PatternType.KeepSingle))
|
if (convertType.HasFlag(PatternType.KeepSingle))
|
||||||
return generateRandomNotes(1);
|
return generateRandomNotes(1);
|
||||||
|
|
||||||
if (convertType.HasFlagFast(PatternType.Mirror))
|
if (convertType.HasFlag(PatternType.Mirror))
|
||||||
{
|
{
|
||||||
if (ConversionDifficulty > 6.5)
|
if (ConversionDifficulty > 6.5)
|
||||||
return generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
|
return generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
|
||||||
@ -179,7 +178,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
|
|
||||||
if (ConversionDifficulty > 6.5)
|
if (ConversionDifficulty > 6.5)
|
||||||
{
|
{
|
||||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
if (convertType.HasFlag(PatternType.LowProbability))
|
||||||
return generateRandomPattern(0.78, 0.42, 0, 0);
|
return generateRandomPattern(0.78, 0.42, 0, 0);
|
||||||
|
|
||||||
return generateRandomPattern(1, 0.62, 0, 0);
|
return generateRandomPattern(1, 0.62, 0, 0);
|
||||||
@ -187,7 +186,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
|
|
||||||
if (ConversionDifficulty > 4)
|
if (ConversionDifficulty > 4)
|
||||||
{
|
{
|
||||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
if (convertType.HasFlag(PatternType.LowProbability))
|
||||||
return generateRandomPattern(0.35, 0.08, 0, 0);
|
return generateRandomPattern(0.35, 0.08, 0, 0);
|
||||||
|
|
||||||
return generateRandomPattern(0.52, 0.15, 0, 0);
|
return generateRandomPattern(0.52, 0.15, 0, 0);
|
||||||
@ -195,7 +194,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
|
|
||||||
if (ConversionDifficulty > 2)
|
if (ConversionDifficulty > 2)
|
||||||
{
|
{
|
||||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
if (convertType.HasFlag(PatternType.LowProbability))
|
||||||
return generateRandomPattern(0.18, 0, 0, 0);
|
return generateRandomPattern(0.18, 0, 0, 0);
|
||||||
|
|
||||||
return generateRandomPattern(0.45, 0, 0, 0);
|
return generateRandomPattern(0.45, 0, 0, 0);
|
||||||
@ -208,9 +207,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
|
|
||||||
foreach (var obj in p.HitObjects)
|
foreach (var obj in p.HitObjects)
|
||||||
{
|
{
|
||||||
if (convertType.HasFlagFast(PatternType.Stair) && obj.Column == TotalColumns - 1)
|
if (convertType.HasFlag(PatternType.Stair) && obj.Column == TotalColumns - 1)
|
||||||
StairType = PatternType.ReverseStair;
|
StairType = PatternType.ReverseStair;
|
||||||
if (convertType.HasFlagFast(PatternType.ReverseStair) && obj.Column == RandomStart)
|
if (convertType.HasFlag(PatternType.ReverseStair) && obj.Column == RandomStart)
|
||||||
StairType = PatternType.Stair;
|
StairType = PatternType.Stair;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,7 +229,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
{
|
{
|
||||||
var pattern = new Pattern();
|
var pattern = new Pattern();
|
||||||
|
|
||||||
bool allowStacking = !convertType.HasFlagFast(PatternType.ForceNotStack);
|
bool allowStacking = !convertType.HasFlag(PatternType.ForceNotStack);
|
||||||
|
|
||||||
if (!allowStacking)
|
if (!allowStacking)
|
||||||
noteCount = Math.Min(noteCount, TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects);
|
noteCount = Math.Min(noteCount, TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects);
|
||||||
@ -250,7 +249,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
|
|
||||||
int getNextColumn(int last)
|
int getNextColumn(int last)
|
||||||
{
|
{
|
||||||
if (convertType.HasFlagFast(PatternType.Gathered))
|
if (convertType.HasFlag(PatternType.Gathered))
|
||||||
{
|
{
|
||||||
last++;
|
last++;
|
||||||
if (last == TotalColumns)
|
if (last == TotalColumns)
|
||||||
@ -297,7 +296,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
|
||||||
private Pattern generateRandomPatternWithMirrored(double centreProbability, double p2, double p3)
|
private Pattern generateRandomPatternWithMirrored(double centreProbability, double p2, double p3)
|
||||||
{
|
{
|
||||||
if (convertType.HasFlagFast(PatternType.ForceNotStack))
|
if (convertType.HasFlag(PatternType.ForceNotStack))
|
||||||
return generateRandomPattern(1 / 2f + p2 / 2, p2, (p2 + p3) / 2, p3);
|
return generateRandomPattern(1 / 2f + p2 / 2, p2, (p2 + p3) / 2, p3);
|
||||||
|
|
||||||
var pattern = new Pattern();
|
var pattern = new Pattern();
|
||||||
|
@ -7,7 +7,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Extensions.EnumExtensions;
|
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
@ -139,7 +138,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
|
|
||||||
if (ConversionDifficulty > 6.5)
|
if (ConversionDifficulty > 6.5)
|
||||||
{
|
{
|
||||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
if (convertType.HasFlag(PatternType.LowProbability))
|
||||||
return generateNRandomNotes(StartTime, 0.78, 0.3, 0);
|
return generateNRandomNotes(StartTime, 0.78, 0.3, 0);
|
||||||
|
|
||||||
return generateNRandomNotes(StartTime, 0.85, 0.36, 0.03);
|
return generateNRandomNotes(StartTime, 0.85, 0.36, 0.03);
|
||||||
@ -147,7 +146,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
|
|
||||||
if (ConversionDifficulty > 4)
|
if (ConversionDifficulty > 4)
|
||||||
{
|
{
|
||||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
if (convertType.HasFlag(PatternType.LowProbability))
|
||||||
return generateNRandomNotes(StartTime, 0.43, 0.08, 0);
|
return generateNRandomNotes(StartTime, 0.43, 0.08, 0);
|
||||||
|
|
||||||
return generateNRandomNotes(StartTime, 0.56, 0.18, 0);
|
return generateNRandomNotes(StartTime, 0.56, 0.18, 0);
|
||||||
@ -155,13 +154,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
|
|
||||||
if (ConversionDifficulty > 2.5)
|
if (ConversionDifficulty > 2.5)
|
||||||
{
|
{
|
||||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
if (convertType.HasFlag(PatternType.LowProbability))
|
||||||
return generateNRandomNotes(StartTime, 0.3, 0, 0);
|
return generateNRandomNotes(StartTime, 0.3, 0, 0);
|
||||||
|
|
||||||
return generateNRandomNotes(StartTime, 0.37, 0.08, 0);
|
return generateNRandomNotes(StartTime, 0.37, 0.08, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (convertType.HasFlagFast(PatternType.LowProbability))
|
if (convertType.HasFlag(PatternType.LowProbability))
|
||||||
return generateNRandomNotes(StartTime, 0.17, 0, 0);
|
return generateNRandomNotes(StartTime, 0.17, 0, 0);
|
||||||
|
|
||||||
return generateNRandomNotes(StartTime, 0.27, 0, 0);
|
return generateNRandomNotes(StartTime, 0.27, 0, 0);
|
||||||
@ -219,7 +218,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
var pattern = new Pattern();
|
var pattern = new Pattern();
|
||||||
|
|
||||||
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||||
if (convertType.HasFlagFast(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||||
nextColumn = FindAvailableColumn(nextColumn, PreviousPattern);
|
nextColumn = FindAvailableColumn(nextColumn, PreviousPattern);
|
||||||
|
|
||||||
int lastColumn = nextColumn;
|
int lastColumn = nextColumn;
|
||||||
@ -371,7 +370,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
|
|
||||||
static bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH;
|
static bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH;
|
||||||
|
|
||||||
bool canGenerateTwoNotes = !convertType.HasFlagFast(PatternType.LowProbability);
|
bool canGenerateTwoNotes = !convertType.HasFlag(PatternType.LowProbability);
|
||||||
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(StartTime).Any(isDoubleSample);
|
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(StartTime).Any(isDoubleSample);
|
||||||
|
|
||||||
if (canGenerateTwoNotes)
|
if (canGenerateTwoNotes)
|
||||||
@ -404,7 +403,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
int endTime = startTime + SegmentDuration * SpanCount;
|
int endTime = startTime + SegmentDuration * SpanCount;
|
||||||
|
|
||||||
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||||
if (convertType.HasFlagFast(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||||
nextColumn = FindAvailableColumn(nextColumn, PreviousPattern);
|
nextColumn = FindAvailableColumn(nextColumn, PreviousPattern);
|
||||||
|
|
||||||
for (int i = 0; i < columnRepeat; i++)
|
for (int i = 0; i < columnRepeat; i++)
|
||||||
@ -433,7 +432,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
|
|||||||
var pattern = new Pattern();
|
var pattern = new Pattern();
|
||||||
|
|
||||||
int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
|
||||||
if (convertType.HasFlagFast(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
|
||||||
holdColumn = FindAvailableColumn(holdColumn, PreviousPattern);
|
holdColumn = FindAvailableColumn(holdColumn, PreviousPattern);
|
||||||
|
|
||||||
// Create the hold note
|
// Create the hold note
|
||||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
{
|
{
|
||||||
public class ManiaDifficultyCalculator : DifficultyCalculator
|
public class ManiaDifficultyCalculator : DifficultyCalculator
|
||||||
{
|
{
|
||||||
private const double star_scaling_factor = 0.018;
|
private const double difficulty_multiplier = 0.018;
|
||||||
|
|
||||||
private readonly bool isForCurrentRuleset;
|
private readonly bool isForCurrentRuleset;
|
||||||
private readonly double originalOverallDifficulty;
|
private readonly double originalOverallDifficulty;
|
||||||
@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
|
|
||||||
ManiaDifficultyAttributes attributes = new ManiaDifficultyAttributes
|
ManiaDifficultyAttributes attributes = new ManiaDifficultyAttributes
|
||||||
{
|
{
|
||||||
StarRating = skills[0].DifficultyValue() * star_scaling_factor,
|
StarRating = skills[0].DifficultyValue() * difficulty_multiplier,
|
||||||
Mods = mods,
|
Mods = mods,
|
||||||
// In osu-stable mania, rate-adjustment mods don't affect the hit window.
|
// In osu-stable mania, rate-adjustment mods don't affect the hit window.
|
||||||
// This is done the way it is to introduce fractional differences in order to match osu-stable for the time being.
|
// This is done the way it is to introduce fractional differences in order to match osu-stable for the time being.
|
||||||
|
@ -38,9 +38,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
|
||||||
scoreAccuracy = calculateCustomAccuracy();
|
scoreAccuracy = calculateCustomAccuracy();
|
||||||
|
|
||||||
// Arbitrary initial value for scaling pp in order to standardize distributions across game modes.
|
double multiplier = 1.0;
|
||||||
// The specific number has no intrinsic meaning and can be adjusted as needed.
|
|
||||||
double multiplier = 8.0;
|
|
||||||
|
|
||||||
if (score.Mods.Any(m => m is ModNoFail))
|
if (score.Mods.Any(m => m is ModNoFail))
|
||||||
multiplier *= 0.75;
|
multiplier *= 0.75;
|
||||||
@ -59,9 +57,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
|
|
||||||
private double computeDifficultyValue(ManiaDifficultyAttributes attributes)
|
private double computeDifficultyValue(ManiaDifficultyAttributes attributes)
|
||||||
{
|
{
|
||||||
double difficultyValue = Math.Pow(Math.Max(attributes.StarRating - 0.15, 0.05), 2.2) // Star rating to pp curve
|
double difficultyValue = 8.0 * Math.Pow(Math.Max(attributes.StarRating - 0.15, 0.05), 2.2) // Star rating to pp curve
|
||||||
* Math.Max(0, 5 * scoreAccuracy - 4) // From 80% accuracy, 1/20th of total pp is awarded per additional 1% accuracy
|
* Math.Max(0, 5 * scoreAccuracy - 4) // From 80% accuracy, 1/20th of total pp is awarded per additional 1% accuracy
|
||||||
* (1 + 0.1 * Math.Min(1, totalHits / 1500)); // Length bonus, capped at 1500 notes
|
* (1 + 0.1 * Math.Min(1, totalHits / 1500)); // Length bonus, capped at 1500 notes
|
||||||
|
|
||||||
return difficultyValue;
|
return difficultyValue;
|
||||||
}
|
}
|
||||||
|
@ -45,18 +45,15 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
LeftKeys = stage1LeftKeys,
|
LeftKeys = stage1LeftKeys,
|
||||||
RightKeys = stage1RightKeys,
|
RightKeys = stage1RightKeys,
|
||||||
SpecialKey = InputKey.V,
|
SpecialKey = InputKey.V,
|
||||||
SpecialAction = ManiaAction.Special1,
|
}.GenerateKeyBindingsFor(singleStageVariant);
|
||||||
NormalActionStart = ManiaAction.Key1
|
|
||||||
}.GenerateKeyBindingsFor(singleStageVariant, out var nextNormal);
|
|
||||||
|
|
||||||
var stage2Bindings = new VariantMappingGenerator
|
var stage2Bindings = new VariantMappingGenerator
|
||||||
{
|
{
|
||||||
LeftKeys = stage2LeftKeys,
|
LeftKeys = stage2LeftKeys,
|
||||||
RightKeys = stage2RightKeys,
|
RightKeys = stage2RightKeys,
|
||||||
SpecialKey = InputKey.B,
|
SpecialKey = InputKey.B,
|
||||||
SpecialAction = ManiaAction.Special2,
|
ActionStart = (ManiaAction)singleStageVariant,
|
||||||
NormalActionStart = nextNormal
|
}.GenerateKeyBindingsFor(singleStageVariant);
|
||||||
}.GenerateKeyBindingsFor(singleStageVariant, out _);
|
|
||||||
|
|
||||||
return stage1Bindings.Concat(stage2Bindings);
|
return stage1Bindings.Concat(stage2Bindings);
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ using osuTK.Input;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||||
{
|
{
|
||||||
public abstract partial class ManiaPlacementBlueprint<T> : PlacementBlueprint
|
public abstract partial class ManiaPlacementBlueprint<T> : HitObjectPlacementBlueprint
|
||||||
where T : ManiaHitObject
|
where T : ManiaHitObject
|
||||||
{
|
{
|
||||||
protected new T HitObject => (T)base.HitObject;
|
protected new T HitObject => (T)base.HitObject;
|
||||||
|
@ -6,6 +6,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Mania.Configuration;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
@ -18,6 +19,8 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
{
|
{
|
||||||
public BindableBool ShowSpeedChanges { get; } = new BindableBool();
|
public BindableBool ShowSpeedChanges { get; } = new BindableBool();
|
||||||
|
|
||||||
|
public double? TimelineTimeRange { get; set; }
|
||||||
|
|
||||||
public new IScrollingInfo ScrollingInfo => base.ScrollingInfo;
|
public new IScrollingInfo ScrollingInfo => base.ScrollingInfo;
|
||||||
|
|
||||||
public DrawableManiaEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod>? mods)
|
public DrawableManiaEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod>? mods)
|
||||||
@ -38,5 +41,11 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = Vector2.One
|
Size = Vector2.One
|
||||||
};
|
};
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
TargetTimeRange = TimelineTimeRange == null || ShowSpeedChanges.Value ? ComputeScrollTime(Config.Get<int>(ManiaRulesetSetting.ScrollSpeed)) : TimelineTimeRange.Value;
|
||||||
|
base.Update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit
|
namespace osu.Game.Rulesets.Mania.Edit
|
||||||
{
|
{
|
||||||
public class HoldNoteCompositionTool : HitObjectCompositionTool
|
public class HoldNoteCompositionTool : CompositionTool
|
||||||
{
|
{
|
||||||
public HoldNoteCompositionTool()
|
public HoldNoteCompositionTool()
|
||||||
: base("Hold")
|
: base("Hold")
|
||||||
@ -18,6 +18,6 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint();
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
@ -14,6 +13,7 @@ using osu.Game.Rulesets.Mania.UI;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -21,7 +21,10 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
{
|
{
|
||||||
public partial class ManiaHitObjectComposer : ScrollingHitObjectComposer<ManiaHitObject>
|
public partial class ManiaHitObjectComposer : ScrollingHitObjectComposer<ManiaHitObject>
|
||||||
{
|
{
|
||||||
private DrawableManiaEditorRuleset drawableRuleset;
|
private DrawableManiaEditorRuleset drawableRuleset = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorScreenWithTimeline? screenWithTimeline { get; set; }
|
||||||
|
|
||||||
public ManiaHitObjectComposer(Ruleset ruleset)
|
public ManiaHitObjectComposer(Ruleset ruleset)
|
||||||
: base(ruleset)
|
: base(ruleset)
|
||||||
@ -43,7 +46,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
|
|
||||||
protected override BeatSnapGrid CreateBeatSnapGrid() => new ManiaBeatSnapGrid();
|
protected override BeatSnapGrid CreateBeatSnapGrid() => new ManiaBeatSnapGrid();
|
||||||
|
|
||||||
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[]
|
protected override IReadOnlyList<CompositionTool> CompositionTools => new CompositionTool[]
|
||||||
{
|
{
|
||||||
new NoteCompositionTool(),
|
new NoteCompositionTool(),
|
||||||
new HoldNoteCompositionTool()
|
new HoldNoteCompositionTool()
|
||||||
@ -72,7 +75,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
if (!double.TryParse(split[0], out double time) || !int.TryParse(split[1], out int column))
|
if (!double.TryParse(split[0], out double time) || !int.TryParse(split[1], out int column))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
ManiaHitObject current = remainingHitObjects.FirstOrDefault(h => h.StartTime == time && h.Column == column);
|
ManiaHitObject? current = remainingHitObjects.FirstOrDefault(h => h.StartTime == time && h.Column == column);
|
||||||
|
|
||||||
if (current == null)
|
if (current == null)
|
||||||
continue;
|
continue;
|
||||||
@ -83,5 +86,13 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
remainingHitObjects = remainingHitObjects.Where(h => h != current && h.StartTime >= current.StartTime).ToList();
|
remainingHitObjects = remainingHitObjects.Where(h => h != current && h.StartTime >= current.StartTime).ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (screenWithTimeline?.TimelineArea.Timeline != null)
|
||||||
|
drawableRuleset.TimelineTimeRange = EditorClock.TrackLength / screenWithTimeline.TimelineArea.Timeline.CurrentZoom / 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,8 +104,10 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
int minColumn = int.MaxValue;
|
int minColumn = int.MaxValue;
|
||||||
int maxColumn = int.MinValue;
|
int maxColumn = int.MinValue;
|
||||||
|
|
||||||
|
var selectedObjects = EditorBeatmap.SelectedHitObjects.OfType<ManiaHitObject>().ToArray();
|
||||||
|
|
||||||
// find min/max in an initial pass before actually performing the movement.
|
// find min/max in an initial pass before actually performing the movement.
|
||||||
foreach (var obj in EditorBeatmap.SelectedHitObjects.OfType<ManiaHitObject>())
|
foreach (var obj in selectedObjects)
|
||||||
{
|
{
|
||||||
if (obj.Column < minColumn)
|
if (obj.Column < minColumn)
|
||||||
minColumn = obj.Column;
|
minColumn = obj.Column;
|
||||||
@ -121,6 +123,13 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
((ManiaHitObject)h).Column += columnDelta;
|
((ManiaHitObject)h).Column += columnDelta;
|
||||||
maniaPlayfield.Add(h);
|
maniaPlayfield.Add(h);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// `HitObjectUsageEventBuffer`'s usage transferal flows and the playfield's `SetKeepAlive()` functionality do not combine well with this operation's usage pattern,
|
||||||
|
// leading to selections being sometimes partially dropped if some of the objects being moved are off screen
|
||||||
|
// (check blame for detailed explanation).
|
||||||
|
// thus, ensure that selection is preserved manually.
|
||||||
|
EditorBeatmap.SelectedHitObjects.Clear();
|
||||||
|
EditorBeatmap.SelectedHitObjects.AddRange(selectedObjects);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ using osu.Game.Rulesets.Mania.Objects;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit
|
namespace osu.Game.Rulesets.Mania.Edit
|
||||||
{
|
{
|
||||||
public class NoteCompositionTool : HitObjectCompositionTool
|
public class NoteCompositionTool : CompositionTool
|
||||||
{
|
{
|
||||||
public NoteCompositionTool()
|
public NoteCompositionTool()
|
||||||
: base(nameof(Note))
|
: base(nameof(Note))
|
||||||
@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint();
|
public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,11 +19,12 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
|
|||||||
{
|
{
|
||||||
public override LocalisableString Title => EditorSetupStrings.DifficultyHeader;
|
public override LocalisableString Title => EditorSetupStrings.DifficultyHeader;
|
||||||
|
|
||||||
private LabelledSliderBar<float> keyCountSlider { get; set; } = null!;
|
private FormSliderBar<float> keyCountSlider { get; set; } = null!;
|
||||||
private LabelledSliderBar<float> healthDrainSlider { get; set; } = null!;
|
private FormCheckBox specialStyle { get; set; } = null!;
|
||||||
private LabelledSliderBar<float> overallDifficultySlider { get; set; } = null!;
|
private FormSliderBar<float> healthDrainSlider { get; set; } = null!;
|
||||||
private LabelledSliderBar<double> baseVelocitySlider { get; set; } = null!;
|
private FormSliderBar<float> overallDifficultySlider { get; set; } = null!;
|
||||||
private LabelledSliderBar<double> tickRateSlider { get; set; } = null!;
|
private FormSliderBar<double> baseVelocitySlider { get; set; } = null!;
|
||||||
|
private FormSliderBar<double> tickRateSlider { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private Editor? editor { get; set; }
|
private Editor? editor { get; set; }
|
||||||
@ -36,70 +37,81 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
|
|||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
keyCountSlider = new LabelledSliderBar<float>
|
keyCountSlider = new FormSliderBar<float>
|
||||||
{
|
{
|
||||||
Label = BeatmapsetsStrings.ShowStatsCsMania,
|
Caption = BeatmapsetsStrings.ShowStatsCsMania,
|
||||||
FixedLabelWidth = LABEL_WIDTH,
|
HintText = "The number of columns in the beatmap",
|
||||||
Description = "The number of columns in the beatmap",
|
|
||||||
Current = new BindableFloat(Beatmap.Difficulty.CircleSize)
|
Current = new BindableFloat(Beatmap.Difficulty.CircleSize)
|
||||||
{
|
{
|
||||||
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||||
MinValue = 0,
|
MinValue = 0,
|
||||||
MaxValue = 10,
|
MaxValue = 10,
|
||||||
Precision = 1,
|
Precision = 1,
|
||||||
}
|
},
|
||||||
|
TransferValueOnCommit = true,
|
||||||
|
TabbableContentContainer = this,
|
||||||
},
|
},
|
||||||
healthDrainSlider = new LabelledSliderBar<float>
|
specialStyle = new FormCheckBox
|
||||||
{
|
{
|
||||||
Label = BeatmapsetsStrings.ShowStatsDrain,
|
Caption = "Use special (N+1) style",
|
||||||
FixedLabelWidth = LABEL_WIDTH,
|
HintText = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 6K (5+1) or 8K (7+1) configurations.",
|
||||||
Description = EditorSetupStrings.DrainRateDescription,
|
Current = { Value = Beatmap.BeatmapInfo.SpecialStyle }
|
||||||
|
},
|
||||||
|
healthDrainSlider = new FormSliderBar<float>
|
||||||
|
{
|
||||||
|
Caption = BeatmapsetsStrings.ShowStatsDrain,
|
||||||
|
HintText = EditorSetupStrings.DrainRateDescription,
|
||||||
Current = new BindableFloat(Beatmap.Difficulty.DrainRate)
|
Current = new BindableFloat(Beatmap.Difficulty.DrainRate)
|
||||||
{
|
{
|
||||||
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||||
MinValue = 0,
|
MinValue = 0,
|
||||||
MaxValue = 10,
|
MaxValue = 10,
|
||||||
Precision = 0.1f,
|
Precision = 0.1f,
|
||||||
}
|
},
|
||||||
|
TransferValueOnCommit = true,
|
||||||
|
TabbableContentContainer = this,
|
||||||
},
|
},
|
||||||
overallDifficultySlider = new LabelledSliderBar<float>
|
overallDifficultySlider = new FormSliderBar<float>
|
||||||
{
|
{
|
||||||
Label = BeatmapsetsStrings.ShowStatsAccuracy,
|
Caption = BeatmapsetsStrings.ShowStatsAccuracy,
|
||||||
FixedLabelWidth = LABEL_WIDTH,
|
HintText = EditorSetupStrings.OverallDifficultyDescription,
|
||||||
Description = EditorSetupStrings.OverallDifficultyDescription,
|
|
||||||
Current = new BindableFloat(Beatmap.Difficulty.OverallDifficulty)
|
Current = new BindableFloat(Beatmap.Difficulty.OverallDifficulty)
|
||||||
{
|
{
|
||||||
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
|
||||||
MinValue = 0,
|
MinValue = 0,
|
||||||
MaxValue = 10,
|
MaxValue = 10,
|
||||||
Precision = 0.1f,
|
Precision = 0.1f,
|
||||||
}
|
},
|
||||||
|
TransferValueOnCommit = true,
|
||||||
|
TabbableContentContainer = this,
|
||||||
},
|
},
|
||||||
baseVelocitySlider = new LabelledSliderBar<double>
|
baseVelocitySlider = new FormSliderBar<double>
|
||||||
{
|
{
|
||||||
Label = EditorSetupStrings.BaseVelocity,
|
Caption = EditorSetupStrings.BaseVelocity,
|
||||||
FixedLabelWidth = LABEL_WIDTH,
|
HintText = EditorSetupStrings.BaseVelocityDescription,
|
||||||
Description = EditorSetupStrings.BaseVelocityDescription,
|
|
||||||
Current = new BindableDouble(Beatmap.Difficulty.SliderMultiplier)
|
Current = new BindableDouble(Beatmap.Difficulty.SliderMultiplier)
|
||||||
{
|
{
|
||||||
Default = 1.4,
|
Default = 1.4,
|
||||||
MinValue = 0.4,
|
MinValue = 0.4,
|
||||||
MaxValue = 3.6,
|
MaxValue = 3.6,
|
||||||
Precision = 0.01f,
|
Precision = 0.01f,
|
||||||
}
|
},
|
||||||
|
TransferValueOnCommit = true,
|
||||||
|
TabbableContentContainer = this,
|
||||||
},
|
},
|
||||||
tickRateSlider = new LabelledSliderBar<double>
|
tickRateSlider = new FormSliderBar<double>
|
||||||
{
|
{
|
||||||
Label = EditorSetupStrings.TickRate,
|
Caption = EditorSetupStrings.TickRate,
|
||||||
FixedLabelWidth = LABEL_WIDTH,
|
HintText = EditorSetupStrings.TickRateDescription,
|
||||||
Description = EditorSetupStrings.TickRateDescription,
|
|
||||||
Current = new BindableDouble(Beatmap.Difficulty.SliderTickRate)
|
Current = new BindableDouble(Beatmap.Difficulty.SliderTickRate)
|
||||||
{
|
{
|
||||||
Default = 1,
|
Default = 1,
|
||||||
MinValue = 1,
|
MinValue = 1,
|
||||||
MaxValue = 4,
|
MaxValue = 4,
|
||||||
Precision = 1,
|
Precision = 1,
|
||||||
}
|
},
|
||||||
|
TransferValueOnCommit = true,
|
||||||
|
TabbableContentContainer = this,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -145,6 +157,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
|
|||||||
// for now, update these on commit rather than making BeatmapMetadata bindables.
|
// for now, update these on commit rather than making BeatmapMetadata bindables.
|
||||||
// after switching database engines we can reconsider if switching to bindables is a good direction.
|
// after switching database engines we can reconsider if switching to bindables is a good direction.
|
||||||
Beatmap.Difficulty.CircleSize = keyCountSlider.Current.Value;
|
Beatmap.Difficulty.CircleSize = keyCountSlider.Current.Value;
|
||||||
|
Beatmap.BeatmapInfo.SpecialStyle = specialStyle.Current.Value;
|
||||||
Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value;
|
Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value;
|
||||||
Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
|
Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
|
||||||
Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value;
|
Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value;
|
||||||
|
@ -1,49 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
|
||||||
using osu.Game.Screens.Edit.Setup;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit.Setup
|
|
||||||
{
|
|
||||||
public partial class ManiaSetupSection : RulesetSetupSection
|
|
||||||
{
|
|
||||||
private LabelledSwitchButton specialStyle;
|
|
||||||
|
|
||||||
public ManiaSetupSection()
|
|
||||||
: base(new ManiaRuleset().RulesetInfo)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
specialStyle = new LabelledSwitchButton
|
|
||||||
{
|
|
||||||
Label = "Use special (N+1) style",
|
|
||||||
Description = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 6K (5+1) or 8K (7+1) configurations.",
|
|
||||||
Current = { Value = Beatmap.BeatmapInfo.SpecialStyle }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
|
|
||||||
specialStyle.Current.BindValueChanged(_ => updateBeatmap());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateBeatmap()
|
|
||||||
{
|
|
||||||
Beatmap.BeatmapInfo.SpecialStyle = specialStyle.Current.Value;
|
|
||||||
Beatmap.SaveState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -19,16 +19,8 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
|
|
||||||
public enum ManiaAction
|
public enum ManiaAction
|
||||||
{
|
{
|
||||||
[Description("Special 1")]
|
|
||||||
Special1 = 1,
|
|
||||||
|
|
||||||
[Description("Special 2")]
|
|
||||||
Special2,
|
|
||||||
|
|
||||||
// This offsets the start value of normal keys in-case we add more special keys
|
|
||||||
// above at a later time, without breaking replays/configs.
|
|
||||||
[Description("Key 1")]
|
[Description("Key 1")]
|
||||||
Key1 = 10,
|
Key1,
|
||||||
|
|
||||||
[Description("Key 2")]
|
[Description("Key 2")]
|
||||||
Key2,
|
Key2,
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Extensions.EnumExtensions;
|
|
||||||
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;
|
||||||
@ -89,79 +88,79 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
|
|
||||||
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
|
public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
|
||||||
{
|
{
|
||||||
if (mods.HasFlagFast(LegacyMods.Nightcore))
|
if (mods.HasFlag(LegacyMods.Nightcore))
|
||||||
yield return new ManiaModNightcore();
|
yield return new ManiaModNightcore();
|
||||||
else if (mods.HasFlagFast(LegacyMods.DoubleTime))
|
else if (mods.HasFlag(LegacyMods.DoubleTime))
|
||||||
yield return new ManiaModDoubleTime();
|
yield return new ManiaModDoubleTime();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.Perfect))
|
if (mods.HasFlag(LegacyMods.Perfect))
|
||||||
yield return new ManiaModPerfect();
|
yield return new ManiaModPerfect();
|
||||||
else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
|
else if (mods.HasFlag(LegacyMods.SuddenDeath))
|
||||||
yield return new ManiaModSuddenDeath();
|
yield return new ManiaModSuddenDeath();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.Cinema))
|
if (mods.HasFlag(LegacyMods.Cinema))
|
||||||
yield return new ManiaModCinema();
|
yield return new ManiaModCinema();
|
||||||
else if (mods.HasFlagFast(LegacyMods.Autoplay))
|
else if (mods.HasFlag(LegacyMods.Autoplay))
|
||||||
yield return new ManiaModAutoplay();
|
yield return new ManiaModAutoplay();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.Easy))
|
if (mods.HasFlag(LegacyMods.Easy))
|
||||||
yield return new ManiaModEasy();
|
yield return new ManiaModEasy();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.FadeIn))
|
if (mods.HasFlag(LegacyMods.FadeIn))
|
||||||
yield return new ManiaModFadeIn();
|
yield return new ManiaModFadeIn();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.Flashlight))
|
if (mods.HasFlag(LegacyMods.Flashlight))
|
||||||
yield return new ManiaModFlashlight();
|
yield return new ManiaModFlashlight();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.HalfTime))
|
if (mods.HasFlag(LegacyMods.HalfTime))
|
||||||
yield return new ManiaModHalfTime();
|
yield return new ManiaModHalfTime();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.HardRock))
|
if (mods.HasFlag(LegacyMods.HardRock))
|
||||||
yield return new ManiaModHardRock();
|
yield return new ManiaModHardRock();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.Hidden))
|
if (mods.HasFlag(LegacyMods.Hidden))
|
||||||
yield return new ManiaModHidden();
|
yield return new ManiaModHidden();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.Key1))
|
if (mods.HasFlag(LegacyMods.Key1))
|
||||||
yield return new ManiaModKey1();
|
yield return new ManiaModKey1();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.Key2))
|
if (mods.HasFlag(LegacyMods.Key2))
|
||||||
yield return new ManiaModKey2();
|
yield return new ManiaModKey2();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.Key3))
|
if (mods.HasFlag(LegacyMods.Key3))
|
||||||
yield return new ManiaModKey3();
|
yield return new ManiaModKey3();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.Key4))
|
if (mods.HasFlag(LegacyMods.Key4))
|
||||||
yield return new ManiaModKey4();
|
yield return new ManiaModKey4();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.Key5))
|
if (mods.HasFlag(LegacyMods.Key5))
|
||||||
yield return new ManiaModKey5();
|
yield return new ManiaModKey5();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.Key6))
|
if (mods.HasFlag(LegacyMods.Key6))
|
||||||
yield return new ManiaModKey6();
|
yield return new ManiaModKey6();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.Key7))
|
if (mods.HasFlag(LegacyMods.Key7))
|
||||||
yield return new ManiaModKey7();
|
yield return new ManiaModKey7();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.Key8))
|
if (mods.HasFlag(LegacyMods.Key8))
|
||||||
yield return new ManiaModKey8();
|
yield return new ManiaModKey8();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.Key9))
|
if (mods.HasFlag(LegacyMods.Key9))
|
||||||
yield return new ManiaModKey9();
|
yield return new ManiaModKey9();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.KeyCoop))
|
if (mods.HasFlag(LegacyMods.KeyCoop))
|
||||||
yield return new ManiaModDualStages();
|
yield return new ManiaModDualStages();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.NoFail))
|
if (mods.HasFlag(LegacyMods.NoFail))
|
||||||
yield return new ManiaModNoFail();
|
yield return new ManiaModNoFail();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.Random))
|
if (mods.HasFlag(LegacyMods.Random))
|
||||||
yield return new ManiaModRandom();
|
yield return new ManiaModRandom();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.Mirror))
|
if (mods.HasFlag(LegacyMods.Mirror))
|
||||||
yield return new ManiaModMirror();
|
yield return new ManiaModMirror();
|
||||||
|
|
||||||
if (mods.HasFlagFast(LegacyMods.ScoreV2))
|
if (mods.HasFlag(LegacyMods.ScoreV2))
|
||||||
yield return new ModScoreV2();
|
yield return new ModScoreV2();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,6 +240,7 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
new ManiaModEasy(),
|
new ManiaModEasy(),
|
||||||
new ManiaModNoFail(),
|
new ManiaModNoFail(),
|
||||||
new MultiMod(new ManiaModHalfTime(), new ManiaModDaycore()),
|
new MultiMod(new ManiaModHalfTime(), new ManiaModDaycore()),
|
||||||
|
new ManiaModNoRelease(),
|
||||||
};
|
};
|
||||||
|
|
||||||
case ModType.DifficultyIncrease:
|
case ModType.DifficultyIncrease:
|
||||||
@ -419,9 +419,13 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
return new ManiaFilterCriteria();
|
return new ManiaFilterCriteria();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override RulesetSetupSection CreateEditorSetupSection() => new ManiaSetupSection();
|
public override IEnumerable<Drawable> CreateEditorSetupSections() =>
|
||||||
|
[
|
||||||
public override SetupSection CreateEditorDifficultySection() => new ManiaDifficultySection();
|
new MetadataSection(),
|
||||||
|
new ManiaDifficultySection(),
|
||||||
|
new ResourcesSection(),
|
||||||
|
new DesignSection(),
|
||||||
|
];
|
||||||
|
|
||||||
public int GetKeyCount(IBeatmapInfo beatmapInfo, IReadOnlyList<Mod>? mods = null)
|
public int GetKeyCount(IBeatmapInfo beatmapInfo, IReadOnlyList<Mod>? mods = null)
|
||||||
=> ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo), mods);
|
=> ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo), mods);
|
||||||
|
@ -5,7 +5,7 @@ using osu.Game.Skinning;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania
|
namespace osu.Game.Rulesets.Mania
|
||||||
{
|
{
|
||||||
public class ManiaSkinComponentLookup : GameplaySkinComponentLookup<ManiaSkinComponents>
|
public class ManiaSkinComponentLookup : SkinComponentLookup<ManiaSkinComponents>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="ManiaSkinComponentLookup"/>.
|
/// Creates a new <see cref="ManiaSkinComponentLookup"/>.
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
|
|
||||||
public override ModType Type => ModType.Conversion;
|
public override ModType Type => ModType.Conversion;
|
||||||
|
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ManiaModInvert) };
|
public override Type[] IncompatibleMods => new[] { typeof(ManiaModInvert), typeof(ManiaModNoRelease) };
|
||||||
|
|
||||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
|
110
osu.Game.Rulesets.Mania/Mods/ManiaModNoRelease.cs
Normal file
110
osu.Game.Rulesets.Mania/Mods/ManiaModNoRelease.cs
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
|
{
|
||||||
|
public partial class ManiaModNoRelease : Mod, IApplicableAfterBeatmapConversion, IApplicableToDrawableRuleset<ManiaHitObject>
|
||||||
|
{
|
||||||
|
public override string Name => "No Release";
|
||||||
|
|
||||||
|
public override string Acronym => "NR";
|
||||||
|
|
||||||
|
public override LocalisableString Description => "No more timing the end of hold notes.";
|
||||||
|
|
||||||
|
public override double ScoreMultiplier => 0.9;
|
||||||
|
|
||||||
|
public override ModType Type => ModType.DifficultyReduction;
|
||||||
|
|
||||||
|
public override Type[] IncompatibleMods => new[] { typeof(ManiaModHoldOff) };
|
||||||
|
|
||||||
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
||||||
|
var hitObjects = maniaBeatmap.HitObjects.Select(obj =>
|
||||||
|
{
|
||||||
|
if (obj is HoldNote hold)
|
||||||
|
return new NoReleaseHoldNote(hold);
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
maniaBeatmap.HitObjects = hitObjects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyToDrawableRuleset(DrawableRuleset<ManiaHitObject> drawableRuleset)
|
||||||
|
{
|
||||||
|
var maniaRuleset = (DrawableManiaRuleset)drawableRuleset;
|
||||||
|
|
||||||
|
foreach (var stage in maniaRuleset.Playfield.Stages)
|
||||||
|
{
|
||||||
|
foreach (var column in stage.Columns)
|
||||||
|
{
|
||||||
|
column.RegisterPool<NoReleaseTailNote, NoReleaseDrawableHoldNoteTail>(10, 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private partial class NoReleaseDrawableHoldNoteTail : DrawableHoldNoteTail
|
||||||
|
{
|
||||||
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
|
{
|
||||||
|
// apply perfect once the tail is reached
|
||||||
|
if (HoldNote.HoldStartTime != null && timeOffset >= 0)
|
||||||
|
ApplyResult(GetCappedResult(HitResult.Perfect));
|
||||||
|
else
|
||||||
|
base.CheckForResult(userTriggered, timeOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NoReleaseTailNote : TailNote
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NoReleaseHoldNote : HoldNote
|
||||||
|
{
|
||||||
|
public NoReleaseHoldNote(HoldNote hold)
|
||||||
|
{
|
||||||
|
StartTime = hold.StartTime;
|
||||||
|
Duration = hold.Duration;
|
||||||
|
Column = hold.Column;
|
||||||
|
NodeSamples = hold.NodeSamples;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
AddNested(Head = new HeadNote
|
||||||
|
{
|
||||||
|
StartTime = StartTime,
|
||||||
|
Column = Column,
|
||||||
|
Samples = GetNodeSamples(0),
|
||||||
|
});
|
||||||
|
|
||||||
|
AddNested(Tail = new NoReleaseTailNote
|
||||||
|
{
|
||||||
|
StartTime = EndTime,
|
||||||
|
Column = Column,
|
||||||
|
Samples = GetNodeSamples((NodeSamples?.Count - 1) ?? 1),
|
||||||
|
});
|
||||||
|
|
||||||
|
AddNested(Body = new HoldNoteBody
|
||||||
|
{
|
||||||
|
StartTime = StartTime,
|
||||||
|
Column = Column
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -268,11 +268,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
ApplyMaxResult();
|
ApplyMaxResult();
|
||||||
else
|
else
|
||||||
MissForcefully();
|
MissForcefully();
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure that the hold note is fully judged by giving the body a judgement.
|
// Make sure that the hold note is fully judged by giving the body a judgement.
|
||||||
if (Tail.AllJudged && !Body.AllJudged)
|
if (!Body.AllJudged)
|
||||||
Body.TriggerResult(Tail.IsHit);
|
Body.TriggerResult(Tail.IsHit);
|
||||||
|
|
||||||
|
// Important that this is always called when a result is applied.
|
||||||
|
endHold();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void MissForcefully()
|
public override void MissForcefully()
|
||||||
|
@ -7,6 +7,7 @@ using System.Collections.Generic;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
@ -72,18 +73,18 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The head note of the hold.
|
/// The head note of the hold.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public HeadNote Head { get; private set; }
|
public HeadNote Head { get; protected set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The tail note of the hold.
|
/// The tail note of the hold.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TailNote Tail { get; private set; }
|
public TailNote Tail { get; protected set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The body of the hold.
|
/// The body of the hold.
|
||||||
/// This is an invisible and silent object that tracks the holding state of the <see cref="HoldNote"/>.
|
/// This is an invisible and silent object that tracks the holding state of the <see cref="HoldNote"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public HoldNoteBody Body { get; private set; }
|
public HoldNoteBody Body { get; protected set; }
|
||||||
|
|
||||||
public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
|
public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
|
||||||
|
|
||||||
@ -91,6 +92,10 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
{
|
{
|
||||||
base.CreateNestedHitObjects(cancellationToken);
|
base.CreateNestedHitObjects(cancellationToken);
|
||||||
|
|
||||||
|
// Generally node samples will be populated by ManiaBeatmapConverter, but in a case like the editor they may not be.
|
||||||
|
// Ensure they are set to a sane default here.
|
||||||
|
NodeSamples ??= CreateDefaultNodeSamples(this);
|
||||||
|
|
||||||
AddNested(Head = new HeadNote
|
AddNested(Head = new HeadNote
|
||||||
{
|
{
|
||||||
StartTime = StartTime,
|
StartTime = StartTime,
|
||||||
@ -102,7 +107,7 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
{
|
{
|
||||||
StartTime = EndTime,
|
StartTime = EndTime,
|
||||||
Column = Column,
|
Column = Column,
|
||||||
Samples = GetNodeSamples((NodeSamples?.Count - 1) ?? 1),
|
Samples = GetNodeSamples(NodeSamples.Count - 1),
|
||||||
});
|
});
|
||||||
|
|
||||||
AddNested(Body = new HoldNoteBody
|
AddNested(Body = new HoldNoteBody
|
||||||
@ -116,7 +121,20 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
|
|
||||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||||
|
|
||||||
public IList<HitSampleInfo> GetNodeSamples(int nodeIndex) =>
|
public IList<HitSampleInfo> GetNodeSamples(int nodeIndex) => nodeIndex < NodeSamples?.Count ? NodeSamples[nodeIndex] : Samples;
|
||||||
nodeIndex < NodeSamples?.Count ? NodeSamples[nodeIndex] : Samples;
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create the default note samples for a hold note, based off their main sample.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// By default, osu!mania beatmaps in only play samples at the start of the hold note.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="obj">The object to use as a basis for the head sample.</param>
|
||||||
|
/// <returns>Defaults for assigning to <see cref="HoldNote.NodeSamples"/>.</returns>
|
||||||
|
public static List<IList<HitSampleInfo>> CreateDefaultNodeSamples(HitObject obj) => new List<IList<HitSampleInfo>>
|
||||||
|
{
|
||||||
|
obj.Samples,
|
||||||
|
new List<HitSampleInfo>(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,28 +17,9 @@ namespace osu.Game.Rulesets.Mania.Replays
|
|||||||
|
|
||||||
public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap;
|
public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap;
|
||||||
|
|
||||||
private readonly ManiaAction[] columnActions;
|
|
||||||
|
|
||||||
public ManiaAutoGenerator(ManiaBeatmap beatmap)
|
public ManiaAutoGenerator(ManiaBeatmap beatmap)
|
||||||
: base(beatmap)
|
: base(beatmap)
|
||||||
{
|
{
|
||||||
columnActions = new ManiaAction[Beatmap.TotalColumns];
|
|
||||||
|
|
||||||
var normalAction = ManiaAction.Key1;
|
|
||||||
var specialAction = ManiaAction.Special1;
|
|
||||||
int totalCounter = 0;
|
|
||||||
|
|
||||||
foreach (var stage in Beatmap.Stages)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < stage.Columns; i++)
|
|
||||||
{
|
|
||||||
if (stage.IsSpecialColumn(i))
|
|
||||||
columnActions[totalCounter] = specialAction++;
|
|
||||||
else
|
|
||||||
columnActions[totalCounter] = normalAction++;
|
|
||||||
totalCounter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void GenerateFrames()
|
protected override void GenerateFrames()
|
||||||
@ -57,11 +38,11 @@ namespace osu.Game.Rulesets.Mania.Replays
|
|||||||
switch (point)
|
switch (point)
|
||||||
{
|
{
|
||||||
case HitPoint:
|
case HitPoint:
|
||||||
actions.Add(columnActions[point.Column]);
|
actions.Add(ManiaAction.Key1 + point.Column);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ReleasePoint:
|
case ReleasePoint:
|
||||||
actions.Remove(columnActions[point.Column]);
|
actions.Remove(ManiaAction.Key1 + point.Column);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Replays.Legacy;
|
using osu.Game.Replays.Legacy;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
using osu.Game.Rulesets.Replays.Types;
|
using osu.Game.Rulesets.Replays.Types;
|
||||||
|
|
||||||
@ -27,118 +25,27 @@ namespace osu.Game.Rulesets.Mania.Replays
|
|||||||
|
|
||||||
public void FromLegacy(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame? lastFrame = null)
|
public void FromLegacy(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame? lastFrame = null)
|
||||||
{
|
{
|
||||||
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
var action = ManiaAction.Key1;
|
||||||
|
|
||||||
var normalAction = ManiaAction.Key1;
|
|
||||||
var specialAction = ManiaAction.Special1;
|
|
||||||
|
|
||||||
int activeColumns = (int)(legacyFrame.MouseX ?? 0);
|
int activeColumns = (int)(legacyFrame.MouseX ?? 0);
|
||||||
int counter = 0;
|
|
||||||
|
|
||||||
while (activeColumns > 0)
|
while (activeColumns > 0)
|
||||||
{
|
{
|
||||||
bool isSpecial = isColumnAtIndexSpecial(maniaBeatmap, counter);
|
|
||||||
|
|
||||||
if ((activeColumns & 1) > 0)
|
if ((activeColumns & 1) > 0)
|
||||||
Actions.Add(isSpecial ? specialAction : normalAction);
|
Actions.Add(action);
|
||||||
|
|
||||||
if (isSpecial)
|
action++;
|
||||||
specialAction++;
|
|
||||||
else
|
|
||||||
normalAction++;
|
|
||||||
|
|
||||||
counter++;
|
|
||||||
activeColumns >>= 1;
|
activeColumns >>= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
|
public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
var maniaBeatmap = (ManiaBeatmap)beatmap;
|
|
||||||
|
|
||||||
int keys = 0;
|
int keys = 0;
|
||||||
|
|
||||||
foreach (var action in Actions)
|
foreach (var action in Actions)
|
||||||
{
|
keys |= 1 << (int)action;
|
||||||
switch (action)
|
|
||||||
{
|
|
||||||
case ManiaAction.Special1:
|
|
||||||
keys |= 1 << getSpecialColumnIndex(maniaBeatmap, 0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ManiaAction.Special2:
|
|
||||||
keys |= 1 << getSpecialColumnIndex(maniaBeatmap, 1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// the index in lazer, which doesn't include special keys.
|
|
||||||
int nonSpecialKeyIndex = action - ManiaAction.Key1;
|
|
||||||
|
|
||||||
// the index inclusive of special keys.
|
|
||||||
int overallIndex = 0;
|
|
||||||
|
|
||||||
// iterate to find the index including special keys.
|
|
||||||
for (; overallIndex < maniaBeatmap.TotalColumns; overallIndex++)
|
|
||||||
{
|
|
||||||
// skip over special columns.
|
|
||||||
if (isColumnAtIndexSpecial(maniaBeatmap, overallIndex))
|
|
||||||
continue;
|
|
||||||
// found a non-special column to use.
|
|
||||||
if (nonSpecialKeyIndex == 0)
|
|
||||||
break;
|
|
||||||
// found a non-special column but not ours.
|
|
||||||
nonSpecialKeyIndex--;
|
|
||||||
}
|
|
||||||
|
|
||||||
keys |= 1 << overallIndex;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new LegacyReplayFrame(Time, keys, null, ReplayButtonState.None);
|
return new LegacyReplayFrame(Time, keys, null, ReplayButtonState.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Find the overall index (across all stages) for a specified special key.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="maniaBeatmap">The beatmap.</param>
|
|
||||||
/// <param name="specialOffset">The special key offset (0 is S1).</param>
|
|
||||||
/// <returns>The overall index for the special column.</returns>
|
|
||||||
private int getSpecialColumnIndex(ManiaBeatmap maniaBeatmap, int specialOffset)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < maniaBeatmap.TotalColumns; i++)
|
|
||||||
{
|
|
||||||
if (isColumnAtIndexSpecial(maniaBeatmap, i))
|
|
||||||
{
|
|
||||||
if (specialOffset == 0)
|
|
||||||
return i;
|
|
||||||
|
|
||||||
specialOffset--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ArgumentException("Special key index is too high.", nameof(specialOffset));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Check whether the column at an overall index (across all stages) is a special column.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="beatmap">The beatmap.</param>
|
|
||||||
/// <param name="index">The overall index to check.</param>
|
|
||||||
private bool isColumnAtIndexSpecial(ManiaBeatmap beatmap, int index)
|
|
||||||
{
|
|
||||||
foreach (var stage in beatmap.Stages)
|
|
||||||
{
|
|
||||||
if (index >= stage.Columns)
|
|
||||||
{
|
|
||||||
index -= stage.Columns;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return stage.IsSpecialColumn(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ArgumentException("Column index is too high.", nameof(index));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,8 +34,6 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
LeftKeys = leftKeys,
|
LeftKeys = leftKeys,
|
||||||
RightKeys = rightKeys,
|
RightKeys = rightKeys,
|
||||||
SpecialKey = InputKey.Space,
|
SpecialKey = InputKey.Space,
|
||||||
SpecialAction = ManiaAction.Special1,
|
}.GenerateKeyBindingsFor(variant);
|
||||||
NormalActionStart = ManiaAction.Key1,
|
|
||||||
}.GenerateKeyBindingsFor(variant, out _);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
// 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 osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||||
|
{
|
||||||
|
public partial class ArgonManiaComboCounter : ArgonComboCounter
|
||||||
|
{
|
||||||
|
protected override bool DisplayXSymbol => false;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IScrollingInfo scrollingInfo { get; set; } = null!;
|
||||||
|
|
||||||
|
private IBindable<ScrollingDirection> direction = null!;
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
// the logic of flipping the position of the combo counter w.r.t. the direction does not work with "Closest" anchor,
|
||||||
|
// because it always forces the anchor to be top or bottom based on scrolling direction.
|
||||||
|
UsesFixedAnchor = true;
|
||||||
|
|
||||||
|
direction = scrollingInfo.Direction.GetBoundCopy();
|
||||||
|
direction.BindValueChanged(_ => updateAnchor());
|
||||||
|
|
||||||
|
// two schedules are required so that updateAnchor is executed in the next frame,
|
||||||
|
// which is when the combo counter receives its Y position by the default layout in ArgonManiaSkinTransformer.
|
||||||
|
Schedule(() => Schedule(updateAnchor));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAnchor()
|
||||||
|
{
|
||||||
|
// if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction
|
||||||
|
if (Anchor.HasFlag(Anchor.y1))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Anchor &= ~(Anchor.y0 | Anchor.y2);
|
||||||
|
Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0;
|
||||||
|
|
||||||
|
// change the sign of the Y coordinate in line with the scrolling direction.
|
||||||
|
// i.e. if the user changes direction from down to up, the anchor is changed from top to bottom, and the Y is flipped from positive to negative here.
|
||||||
|
Y = Math.Abs(Y) * (direction.Value == ScrollingDirection.Up ? -1 : 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user