mirror of
https://github.com/osukey/osukey.git
synced 2025-08-07 00:23:59 +09:00
Merge remote-tracking branch 'osulazer/master' into develop
This commit is contained in:
@ -3,7 +3,7 @@
|
|||||||
"isRoot": true,
|
"isRoot": true,
|
||||||
"tools": {
|
"tools": {
|
||||||
"jetbrains.resharper.globaltools": {
|
"jetbrains.resharper.globaltools": {
|
||||||
"version": "2022.1.1",
|
"version": "2022.2.3",
|
||||||
"commands": [
|
"commands": [
|
||||||
"jb"
|
"jb"
|
||||||
]
|
]
|
||||||
|
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@ -1,5 +1,11 @@
|
|||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
name: Continuous Integration
|
name: Continuous Integration
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
inspect-code:
|
inspect-code:
|
||||||
@ -82,7 +88,7 @@ jobs:
|
|||||||
run: dotnet build -c Debug -warnaserror osu.Desktop.slnf
|
run: dotnet build -c Debug -warnaserror osu.Desktop.slnf
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: dotnet test $pwd/**/*.Tests/bin/Debug/*/*.Tests.dll --logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx"
|
run: dotnet test $pwd/**/*.Tests/bin/Debug/*/*.Tests.dll --logger "trx;LogFileName=TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx" -- NUnit.ConsoleOut=0
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
||||||
# Attempt to upload results even if test fails.
|
# Attempt to upload results even if test fails.
|
||||||
|
4
.github/workflows/report-nunit.yml
vendored
4
.github/workflows/report-nunit.yml
vendored
@ -8,8 +8,12 @@ on:
|
|||||||
workflows: ["Continuous Integration"]
|
workflows: ["Continuous Integration"]
|
||||||
types:
|
types:
|
||||||
- completed
|
- completed
|
||||||
|
permissions: {}
|
||||||
jobs:
|
jobs:
|
||||||
annotate:
|
annotate:
|
||||||
|
permissions:
|
||||||
|
checks: write # to create checks (dorny/test-reporter)
|
||||||
|
|
||||||
name: Annotate CI run with test results
|
name: Annotate CI run with test results
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ github.event.workflow_run.conclusion != 'cancelled' }}
|
if: ${{ github.event.workflow_run.conclusion != 'cancelled' }}
|
||||||
|
3
.github/workflows/sentry-release.yml
vendored
3
.github/workflows/sentry-release.yml
vendored
@ -5,6 +5,9 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- '*'
|
- '*'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sentry_release:
|
sentry_release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -6,8 +6,6 @@ T:System.IComparable;Don't use non-generic IComparable. Use generic version inst
|
|||||||
T:SixLabors.ImageSharp.IDeepCloneable`1;Use osu.Game.Utils.IDeepCloneable<T> instead.
|
T:SixLabors.ImageSharp.IDeepCloneable`1;Use osu.Game.Utils.IDeepCloneable<T> instead.
|
||||||
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:Microsoft.EntityFrameworkCore.Internal.EnumerableExtensions;Don't use internal extension methods.
|
|
||||||
T:Microsoft.EntityFrameworkCore.Internal.TypeExtensions;Don't use internal extension methods.
|
|
||||||
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: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.
|
||||||
|
@ -9,10 +9,9 @@
|
|||||||
<GenerateProgramFile>false</GenerateProgramFile>
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.EmptyFreeform\osu.Game.Rulesets.EmptyFreeform.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.EmptyFreeform\osu.Game.Rulesets.EmptyFreeform.csproj" />
|
||||||
|
@ -9,10 +9,9 @@
|
|||||||
<GenerateProgramFile>false</GenerateProgramFile>
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj" />
|
||||||
|
@ -9,10 +9,9 @@
|
|||||||
<GenerateProgramFile>false</GenerateProgramFile>
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.EmptyScrolling\osu.Game.Rulesets.EmptyScrolling.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.EmptyScrolling\osu.Game.Rulesets.EmptyScrolling.csproj" />
|
||||||
|
@ -9,10 +9,9 @@
|
|||||||
<GenerateProgramFile>false</GenerateProgramFile>
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj" />
|
||||||
|
@ -51,11 +51,11 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.831.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.1005.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.908.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.1005.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||||
<PackageReference Include="Realm" Version="10.15.1" />
|
<PackageReference Include="Realm" Version="10.17.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -137,12 +137,13 @@ namespace osu.Desktop
|
|||||||
{
|
{
|
||||||
base.SetHost(host);
|
base.SetHost(host);
|
||||||
|
|
||||||
var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
|
|
||||||
|
|
||||||
var desktopWindow = (SDL2DesktopWindow)host.Window;
|
var desktopWindow = (SDL2DesktopWindow)host.Window;
|
||||||
|
|
||||||
desktopWindow.CursorState |= CursorState.Hidden;
|
var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
|
||||||
|
if (iconStream != null)
|
||||||
desktopWindow.SetIconFromStream(iconStream);
|
desktopWindow.SetIconFromStream(iconStream);
|
||||||
|
|
||||||
|
desktopWindow.CursorState |= CursorState.Hidden;
|
||||||
desktopWindow.Title = Name;
|
desktopWindow.Title = Name;
|
||||||
desktopWindow.DragDrop += f => fileDrop(new[] { f });
|
desktopWindow.DragDrop += f => fileDrop(new[] { f });
|
||||||
}
|
}
|
||||||
|
@ -5,26 +5,24 @@ using System;
|
|||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Game;
|
using osu.Game;
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
using osuTK;
|
|
||||||
using Squirrel;
|
using Squirrel;
|
||||||
using Squirrel.SimpleSplat;
|
using Squirrel.SimpleSplat;
|
||||||
|
using LogLevel = Squirrel.SimpleSplat.LogLevel;
|
||||||
|
using UpdateManager = osu.Game.Updater.UpdateManager;
|
||||||
|
|
||||||
namespace osu.Desktop.Updater
|
namespace osu.Desktop.Updater
|
||||||
{
|
{
|
||||||
[SupportedOSPlatform("windows")]
|
[SupportedOSPlatform("windows")]
|
||||||
public class SquirrelUpdateManager : osu.Game.Updater.UpdateManager
|
public class SquirrelUpdateManager : UpdateManager
|
||||||
{
|
{
|
||||||
private UpdateManager? updateManager;
|
private Squirrel.UpdateManager? updateManager;
|
||||||
private INotificationOverlay notificationOverlay = null!;
|
private INotificationOverlay notificationOverlay = null!;
|
||||||
|
|
||||||
public Task PrepareUpdateAsync() => UpdateManager.RestartAppWhenExited();
|
public Task PrepareUpdateAsync() => Squirrel.UpdateManager.RestartAppWhenExited();
|
||||||
|
|
||||||
private static readonly Logger logger = Logger.GetLogger("updater");
|
private static readonly Logger logger = Logger.GetLogger("updater");
|
||||||
|
|
||||||
@ -35,6 +33,9 @@ namespace osu.Desktop.Updater
|
|||||||
|
|
||||||
private readonly SquirrelLogger squirrelLogger = new SquirrelLogger();
|
private readonly SquirrelLogger squirrelLogger = new SquirrelLogger();
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuGameBase game { get; set; } = null!;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(INotificationOverlay notifications)
|
private void load(INotificationOverlay notifications)
|
||||||
{
|
{
|
||||||
@ -63,7 +64,14 @@ namespace osu.Desktop.Updater
|
|||||||
if (updatePending)
|
if (updatePending)
|
||||||
{
|
{
|
||||||
// the user may have dismissed the completion notice, so show it again.
|
// the user may have dismissed the completion notice, so show it again.
|
||||||
notificationOverlay.Post(new UpdateCompleteNotification(this));
|
notificationOverlay.Post(new UpdateApplicationCompleteNotification
|
||||||
|
{
|
||||||
|
Activated = () =>
|
||||||
|
{
|
||||||
|
restartToApplyUpdate();
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,19 +83,21 @@ namespace osu.Desktop.Updater
|
|||||||
|
|
||||||
if (notification == null)
|
if (notification == null)
|
||||||
{
|
{
|
||||||
notification = new UpdateProgressNotification(this) { State = ProgressNotificationState.Active };
|
notification = new UpdateProgressNotification
|
||||||
|
{
|
||||||
|
CompletionClickAction = restartToApplyUpdate,
|
||||||
|
};
|
||||||
|
|
||||||
Schedule(() => notificationOverlay.Post(notification));
|
Schedule(() => notificationOverlay.Post(notification));
|
||||||
}
|
}
|
||||||
|
|
||||||
notification.Progress = 0;
|
notification.StartDownload();
|
||||||
notification.Text = @"Downloading update...";
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await updateManager.DownloadReleases(info.ReleasesToApply, p => notification.Progress = p / 100f).ConfigureAwait(false);
|
await updateManager.DownloadReleases(info.ReleasesToApply, p => notification.Progress = p / 100f).ConfigureAwait(false);
|
||||||
|
|
||||||
notification.Progress = 0;
|
notification.StartInstall();
|
||||||
notification.Text = @"Installing update...";
|
|
||||||
|
|
||||||
await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f).ConfigureAwait(false);
|
await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f).ConfigureAwait(false);
|
||||||
|
|
||||||
@ -107,9 +117,7 @@ namespace osu.Desktop.Updater
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// In the case of an error, a separate notification will be displayed.
|
// In the case of an error, a separate notification will be displayed.
|
||||||
notification.State = ProgressNotificationState.Cancelled;
|
notification.FailDownload();
|
||||||
notification.Close();
|
|
||||||
|
|
||||||
Logger.Error(e, @"update failed!");
|
Logger.Error(e, @"update failed!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,78 +139,24 @@ namespace osu.Desktop.Updater
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool restartToApplyUpdate()
|
||||||
|
{
|
||||||
|
PrepareUpdateAsync()
|
||||||
|
.ContinueWith(_ => Schedule(() => game.AttemptExit()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
updateManager?.Dispose();
|
updateManager?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class UpdateCompleteNotification : ProgressCompletionNotification
|
|
||||||
{
|
|
||||||
[Resolved]
|
|
||||||
private OsuGame game { get; set; } = null!;
|
|
||||||
|
|
||||||
public UpdateCompleteNotification(SquirrelUpdateManager updateManager)
|
|
||||||
{
|
|
||||||
Text = @"Update ready to install. Click to restart!";
|
|
||||||
|
|
||||||
Activated = () =>
|
|
||||||
{
|
|
||||||
updateManager.PrepareUpdateAsync()
|
|
||||||
.ContinueWith(_ => updateManager.Schedule(() => game.AttemptExit()));
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class UpdateProgressNotification : ProgressNotification
|
|
||||||
{
|
|
||||||
private readonly SquirrelUpdateManager updateManager;
|
|
||||||
|
|
||||||
public UpdateProgressNotification(SquirrelUpdateManager updateManager)
|
|
||||||
{
|
|
||||||
this.updateManager = updateManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Notification CreateCompletionNotification()
|
|
||||||
{
|
|
||||||
return new UpdateCompleteNotification(updateManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OsuColour colours)
|
|
||||||
{
|
|
||||||
IconContent.AddRange(new Drawable[]
|
|
||||||
{
|
|
||||||
new SpriteIcon
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Icon = FontAwesome.Solid.Upload,
|
|
||||||
Size = new Vector2(20),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Close()
|
|
||||||
{
|
|
||||||
// cancelling updates is not currently supported by the underlying updater.
|
|
||||||
// only allow dismissing for now.
|
|
||||||
|
|
||||||
switch (State)
|
|
||||||
{
|
|
||||||
case ProgressNotificationState.Cancelled:
|
|
||||||
base.Close();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SquirrelLogger : ILogger, IDisposable
|
private class SquirrelLogger : ILogger, IDisposable
|
||||||
{
|
{
|
||||||
public Squirrel.SimpleSplat.LogLevel Level { get; set; } = Squirrel.SimpleSplat.LogLevel.Info;
|
public LogLevel Level { get; set; } = LogLevel.Info;
|
||||||
|
|
||||||
public void Write(string message, Squirrel.SimpleSplat.LogLevel logLevel)
|
public void Write(string message, LogLevel logLevel)
|
||||||
{
|
{
|
||||||
if (logLevel < Level)
|
if (logLevel < Level)
|
||||||
return;
|
return;
|
||||||
|
@ -24,11 +24,6 @@
|
|||||||
<PackageReference Include="Clowd.Squirrel" Version="2.9.42" />
|
<PackageReference Include="Clowd.Squirrel" Version="2.9.42" />
|
||||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||||
<PackageReference Include="System.IO.Packaging" Version="6.0.0" />
|
<PackageReference Include="System.IO.Packaging" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.14">
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
|
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Resources">
|
<ItemGroup Label="Resources">
|
||||||
|
@ -7,9 +7,9 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
|
||||||
<PackageReference Include="nunit" Version="3.13.3" />
|
<PackageReference Include="nunit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.Mods;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
@ -12,11 +17,11 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneCatchTouchInput : OsuTestScene
|
public class TestSceneCatchTouchInput : OsuTestScene
|
||||||
{
|
{
|
||||||
private CatchTouchInputMapper catchTouchInputMapper = null!;
|
[Test]
|
||||||
|
public void TestBasic()
|
||||||
[SetUpSteps]
|
|
||||||
public void SetUpSteps()
|
|
||||||
{
|
{
|
||||||
|
CatchTouchInputMapper catchTouchInputMapper = null!;
|
||||||
|
|
||||||
AddStep("create input overlay", () =>
|
AddStep("create input overlay", () =>
|
||||||
{
|
{
|
||||||
Child = new CatchInputManager(new CatchRuleset().RulesetInfo)
|
Child = new CatchInputManager(new CatchRuleset().RulesetInfo)
|
||||||
@ -32,12 +37,30 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddStep("show overlay", () => catchTouchInputMapper.Show());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestBasic()
|
public void TestWithoutRelax()
|
||||||
{
|
{
|
||||||
AddStep("show overlay", () => catchTouchInputMapper.Show());
|
AddStep("create drawable ruleset without relax mod", () =>
|
||||||
|
{
|
||||||
|
Child = new DrawableCatchRuleset(new CatchRuleset(), new CatchBeatmap(), new List<Mod>());
|
||||||
|
});
|
||||||
|
AddUntilStep("wait for load", () => Child.IsLoaded);
|
||||||
|
AddAssert("check touch input is shown", () => this.ChildrenOfType<CatchTouchInputMapper>().Any());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestWithRelax()
|
||||||
|
{
|
||||||
|
AddStep("create drawable ruleset with relax mod", () =>
|
||||||
|
{
|
||||||
|
Child = new DrawableCatchRuleset(new CatchRuleset(), new CatchBeatmap(), new List<Mod> { new CatchModRelax() });
|
||||||
|
});
|
||||||
|
AddUntilStep("wait for load", () => Child.IsLoaded);
|
||||||
|
AddAssert("check touch input is not shown", () => !this.ChildrenOfType<CatchTouchInputMapper>().Any());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
49
osu.Game.Rulesets.Mania.Tests/ManiaSpecialColumnTest.cs
Normal file
49
osu.Game.Rulesets.Mania.Tests/ManiaSpecialColumnTest.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// 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 System.Collections.Generic;
|
||||||
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class ManiaSpecialColumnTest
|
||||||
|
{
|
||||||
|
[TestCase(new[]
|
||||||
|
{
|
||||||
|
true
|
||||||
|
}, 1)]
|
||||||
|
[TestCase(new[]
|
||||||
|
{
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
}, 4)]
|
||||||
|
[TestCase(new[]
|
||||||
|
{
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
}, 7)]
|
||||||
|
public void Test(IEnumerable<bool> special, int columns)
|
||||||
|
{
|
||||||
|
var definition = new StageDefinition(columns);
|
||||||
|
var results = getResults(definition);
|
||||||
|
Assert.AreEqual(special, results);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<bool> getResults(StageDefinition definition)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < definition.Columns; i++)
|
||||||
|
yield return definition.IsSpecialColumn(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
var stageDefinitions = new List<StageDefinition>
|
var stageDefinitions = new List<StageDefinition>
|
||||||
{
|
{
|
||||||
new StageDefinition { Columns = 4 },
|
new StageDefinition(4),
|
||||||
};
|
};
|
||||||
|
|
||||||
SetContents(_ => new ManiaPlayfield(stageDefinitions).With(s =>
|
SetContents(_ => new ManiaPlayfield(stageDefinitions).With(s =>
|
||||||
|
97
osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitExplosion.cs
Normal file
97
osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitExplosion.cs
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonHitExplosion : CompositeDrawable, IHitExplosion
|
||||||
|
{
|
||||||
|
public override bool RemoveWhenNotAlive => true;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Column column { get; set; } = null!;
|
||||||
|
|
||||||
|
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||||
|
|
||||||
|
private Container largeFaint = null!;
|
||||||
|
|
||||||
|
private Bindable<Color4> accentColour = null!;
|
||||||
|
|
||||||
|
public ArgonHitExplosion()
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = ArgonNotePiece.NOTE_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(IScrollingInfo scrollingInfo)
|
||||||
|
{
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
largeFaint = new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
Colour = Color4.White,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
direction.BindTo(scrollingInfo.Direction);
|
||||||
|
direction.BindValueChanged(onDirectionChanged, true);
|
||||||
|
|
||||||
|
accentColour = column.AccentColour.GetBoundCopy();
|
||||||
|
accentColour.BindValueChanged(colour =>
|
||||||
|
{
|
||||||
|
largeFaint.Colour = Interpolation.ValueAt(0.8f, colour.NewValue, Color4.White, 0, 1);
|
||||||
|
|
||||||
|
largeFaint.EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Colour = colour.NewValue,
|
||||||
|
Roundness = 40,
|
||||||
|
Radius = 60,
|
||||||
|
};
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||||
|
{
|
||||||
|
if (direction.NewValue == ScrollingDirection.Up)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre;
|
||||||
|
Y = ArgonNotePiece.NOTE_HEIGHT / 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre;
|
||||||
|
Y = -ArgonNotePiece.NOTE_HEIGHT / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Animate(JudgementResult result)
|
||||||
|
{
|
||||||
|
this.FadeOutFromOne(PoolableHitExplosion.DURATION, Easing.Out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitTarget.cs
Normal file
47
osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHitTarget.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonHitTarget : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(IScrollingInfo scrollingInfo)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = ArgonNotePiece.NOTE_HEIGHT;
|
||||||
|
|
||||||
|
Masking = true;
|
||||||
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
||||||
|
|
||||||
|
InternalChildren = new[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0.3f,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Colour = Color4.White
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
direction.BindTo(scrollingInfo.Direction);
|
||||||
|
direction.BindValueChanged(onDirectionChanged, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||||
|
{
|
||||||
|
Anchor = Origin = direction.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
97
osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldBodyPiece.cs
Normal file
97
osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldBodyPiece.cs
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Mania.Skinning.Default;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents length-wise portion of a hold note.
|
||||||
|
/// </summary>
|
||||||
|
public class ArgonHoldBodyPiece : CompositeDrawable, IHoldNoteBody
|
||||||
|
{
|
||||||
|
protected readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
||||||
|
protected readonly IBindable<bool> IsHitting = new Bindable<bool>();
|
||||||
|
|
||||||
|
private Drawable background = null!;
|
||||||
|
private Box foreground = null!;
|
||||||
|
|
||||||
|
public ArgonHoldBodyPiece()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
// Without this, the width of the body will be slightly larger than the head/tail.
|
||||||
|
Masking = true;
|
||||||
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
||||||
|
Blending = BlendingParameters.Additive;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader(true)]
|
||||||
|
private void load(DrawableHitObject? drawableObject)
|
||||||
|
{
|
||||||
|
InternalChildren = new[]
|
||||||
|
{
|
||||||
|
background = new Box { RelativeSizeAxes = Axes.Both },
|
||||||
|
foreground = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Alpha = 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (drawableObject != null)
|
||||||
|
{
|
||||||
|
var holdNote = (DrawableHoldNote)drawableObject;
|
||||||
|
|
||||||
|
AccentColour.BindTo(holdNote.AccentColour);
|
||||||
|
IsHitting.BindTo(holdNote.IsHitting);
|
||||||
|
}
|
||||||
|
|
||||||
|
AccentColour.BindValueChanged(colour =>
|
||||||
|
{
|
||||||
|
background.Colour = colour.NewValue.Darken(1.2f);
|
||||||
|
foreground.Colour = colour.NewValue.Opacity(0.2f);
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
IsHitting.BindValueChanged(hitting =>
|
||||||
|
{
|
||||||
|
const float animation_length = 50;
|
||||||
|
|
||||||
|
foreground.ClearTransforms();
|
||||||
|
|
||||||
|
if (hitting.NewValue)
|
||||||
|
{
|
||||||
|
// wait for the next sync point
|
||||||
|
double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
|
||||||
|
|
||||||
|
using (foreground.BeginDelayedSequence(synchronisedOffset))
|
||||||
|
{
|
||||||
|
foreground.FadeTo(1, animation_length).Then()
|
||||||
|
.FadeTo(0.5f, animation_length)
|
||||||
|
.Loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreground.FadeOut(animation_length);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Recycle()
|
||||||
|
{
|
||||||
|
foreground.ClearTransforms();
|
||||||
|
foreground.Alpha = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||||
|
{
|
||||||
|
internal class ArgonHoldNoteTailPiece : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||||
|
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||||
|
|
||||||
|
private readonly Box colouredBox;
|
||||||
|
private readonly Box shadow;
|
||||||
|
|
||||||
|
public ArgonHoldNoteTailPiece()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = ArgonNotePiece.NOTE_HEIGHT;
|
||||||
|
|
||||||
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
||||||
|
Masking = true;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
shadow = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Height = 0.82f,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
colouredBox = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = ArgonNotePiece.CORNER_RADIUS * 2,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader(true)]
|
||||||
|
private void load(IScrollingInfo scrollingInfo, DrawableHitObject? drawableObject)
|
||||||
|
{
|
||||||
|
direction.BindTo(scrollingInfo.Direction);
|
||||||
|
direction.BindValueChanged(onDirectionChanged, true);
|
||||||
|
|
||||||
|
if (drawableObject != null)
|
||||||
|
{
|
||||||
|
accentColour.BindTo(drawableObject.AccentColour);
|
||||||
|
accentColour.BindValueChanged(onAccentChanged, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||||
|
{
|
||||||
|
colouredBox.Anchor = colouredBox.Origin = direction.NewValue == ScrollingDirection.Up
|
||||||
|
? Anchor.TopCentre
|
||||||
|
: Anchor.BottomCentre;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onAccentChanged(ValueChangedEvent<Color4> accent)
|
||||||
|
{
|
||||||
|
colouredBox.Colour = ColourInfo.GradientVertical(
|
||||||
|
accent.NewValue,
|
||||||
|
accent.NewValue.Darken(0.1f)
|
||||||
|
);
|
||||||
|
|
||||||
|
shadow.Colour = accent.NewValue.Darken(0.5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
193
osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs
Normal file
193
osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
// 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.Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement
|
||||||
|
{
|
||||||
|
protected readonly HitResult Result;
|
||||||
|
|
||||||
|
protected SpriteText JudgementText { get; private set; } = null!;
|
||||||
|
|
||||||
|
private RingExplosion? ringExplosion;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuColour colours { get; set; } = null!;
|
||||||
|
|
||||||
|
public ArgonJudgementPiece(HitResult result)
|
||||||
|
{
|
||||||
|
Result = result;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
Y = 160;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
JudgementText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Text = Result.GetDescription().ToUpperInvariant(),
|
||||||
|
Colour = colours.ForHitResult(Result),
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Spacing = new Vector2(10, 0),
|
||||||
|
Font = OsuFont.Default.With(size: 28, weight: FontWeight.Regular),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Result.IsHit())
|
||||||
|
{
|
||||||
|
AddInternal(ringExplosion = new RingExplosion(Result)
|
||||||
|
{
|
||||||
|
Colour = colours.ForHitResult(Result),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plays the default animation for this judgement piece.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The base implementation only handles fade (for all result types) and misses.
|
||||||
|
/// Individual rulesets are recommended to implement their appropriate hit animations.
|
||||||
|
/// </remarks>
|
||||||
|
public virtual void PlayAnimation()
|
||||||
|
{
|
||||||
|
switch (Result)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
JudgementText
|
||||||
|
.ScaleTo(Vector2.One)
|
||||||
|
.ScaleTo(new Vector2(1.4f), 1800, Easing.OutQuint);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HitResult.Miss:
|
||||||
|
this.ScaleTo(1.6f);
|
||||||
|
this.ScaleTo(1, 100, Easing.In);
|
||||||
|
|
||||||
|
this.MoveTo(Vector2.Zero);
|
||||||
|
this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
|
||||||
|
|
||||||
|
this.RotateTo(0);
|
||||||
|
this.RotateTo(40, 800, Easing.InQuint);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.FadeOutFromOne(800);
|
||||||
|
|
||||||
|
ringExplosion?.PlayAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Drawable? GetAboveHitObjectsProxiedContent() => null;
|
||||||
|
|
||||||
|
private class RingExplosion : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly float travel = 52;
|
||||||
|
|
||||||
|
public RingExplosion(HitResult result)
|
||||||
|
{
|
||||||
|
const float thickness = 4;
|
||||||
|
|
||||||
|
const float small_size = 9;
|
||||||
|
const float large_size = 14;
|
||||||
|
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
Blending = BlendingParameters.Additive;
|
||||||
|
|
||||||
|
int countSmall = 0;
|
||||||
|
int countLarge = 0;
|
||||||
|
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case HitResult.Meh:
|
||||||
|
countSmall = 3;
|
||||||
|
travel *= 0.3f;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HitResult.Ok:
|
||||||
|
case HitResult.Good:
|
||||||
|
countSmall = 4;
|
||||||
|
travel *= 0.6f;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HitResult.Great:
|
||||||
|
case HitResult.Perfect:
|
||||||
|
countSmall = 4;
|
||||||
|
countLarge = 4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < countSmall; i++)
|
||||||
|
AddInternal(new RingPiece(thickness) { Size = new Vector2(small_size) });
|
||||||
|
|
||||||
|
for (int i = 0; i < countLarge; i++)
|
||||||
|
AddInternal(new RingPiece(thickness) { Size = new Vector2(large_size) });
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PlayAnimation()
|
||||||
|
{
|
||||||
|
foreach (var c in InternalChildren)
|
||||||
|
{
|
||||||
|
const float start_position_ratio = 0.3f;
|
||||||
|
|
||||||
|
float direction = RNG.NextSingle(0, 360);
|
||||||
|
float distance = RNG.NextSingle(travel / 2, travel);
|
||||||
|
|
||||||
|
c.MoveTo(new Vector2(
|
||||||
|
MathF.Cos(direction) * distance * start_position_ratio,
|
||||||
|
MathF.Sin(direction) * distance * start_position_ratio
|
||||||
|
));
|
||||||
|
|
||||||
|
c.MoveTo(new Vector2(
|
||||||
|
MathF.Cos(direction) * distance,
|
||||||
|
MathF.Sin(direction) * distance
|
||||||
|
), 600, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.FadeOutFromOne(1000, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RingPiece : CircularContainer
|
||||||
|
{
|
||||||
|
public RingPiece(float thickness = 9)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
Masking = true;
|
||||||
|
BorderThickness = thickness;
|
||||||
|
BorderColour = Color4.White;
|
||||||
|
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
AlwaysPresent = true,
|
||||||
|
Alpha = 0,
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
272
osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs
Normal file
272
osu.Game.Rulesets.Mania/Skinning/Argon/ArgonKeyArea.cs
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonKeyArea : CompositeDrawable, IKeyBindingHandler<ManiaAction>
|
||||||
|
{
|
||||||
|
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||||
|
|
||||||
|
private Container directionContainer = null!;
|
||||||
|
private Drawable background = null!;
|
||||||
|
|
||||||
|
private Circle hitTargetLine = null!;
|
||||||
|
|
||||||
|
private Container<Circle> bottomIcon = null!;
|
||||||
|
private CircularContainer topIcon = null!;
|
||||||
|
|
||||||
|
private Bindable<Color4> accentColour = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Column column { get; set; } = null!;
|
||||||
|
|
||||||
|
public ArgonKeyArea()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(IScrollingInfo scrollingInfo)
|
||||||
|
{
|
||||||
|
const float icon_circle_size = 8;
|
||||||
|
const float icon_spacing = 7;
|
||||||
|
const float icon_vertical_offset = -30;
|
||||||
|
|
||||||
|
InternalChild = directionContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
// Ensure the area is tall enough to put the target line in the correct location.
|
||||||
|
// This is to also allow the main background component to overlap the target line
|
||||||
|
// and avoid an inner corner radius being shown below the target line.
|
||||||
|
Height = Stage.HIT_TARGET_POSITION + ArgonNotePiece.CORNER_RADIUS * 2,
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Masking = true,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
||||||
|
Child = background = new Box
|
||||||
|
{
|
||||||
|
Name = "Key gradient",
|
||||||
|
Alpha = 0,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hitTargetLine = new Circle
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Colour = OsuColour.Gray(196 / 255f),
|
||||||
|
Height = ArgonNotePiece.CORNER_RADIUS * 2,
|
||||||
|
Masking = true,
|
||||||
|
EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow },
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Name = "Icons",
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
bottomIcon = new Container<Circle>
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Y = icon_vertical_offset,
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
Size = new Vector2(icon_circle_size),
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow },
|
||||||
|
},
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
X = -icon_spacing,
|
||||||
|
Y = icon_spacing * 1.2f,
|
||||||
|
Size = new Vector2(icon_circle_size),
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow },
|
||||||
|
},
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
X = icon_spacing,
|
||||||
|
Y = icon_spacing * 1.2f,
|
||||||
|
Size = new Vector2(icon_circle_size),
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
topIcon = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Y = -icon_vertical_offset,
|
||||||
|
Size = new Vector2(22, 14),
|
||||||
|
Masking = true,
|
||||||
|
BorderThickness = 4,
|
||||||
|
BorderColour = Color4.White,
|
||||||
|
EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow },
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
AlwaysPresent = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
direction.BindTo(scrollingInfo.Direction);
|
||||||
|
direction.BindValueChanged(onDirectionChanged, true);
|
||||||
|
|
||||||
|
accentColour = column.AccentColour.GetBoundCopy();
|
||||||
|
accentColour.BindValueChanged(colour =>
|
||||||
|
{
|
||||||
|
background.Colour = colour.NewValue.Darken(0.2f);
|
||||||
|
bottomIcon.Colour = colour.NewValue;
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
|
||||||
|
// Yes, proxy everything.
|
||||||
|
column.TopLevelContainer.Add(CreateProxy());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||||
|
{
|
||||||
|
switch (direction.NewValue)
|
||||||
|
{
|
||||||
|
case ScrollingDirection.Up:
|
||||||
|
directionContainer.Scale = new Vector2(1, -1);
|
||||||
|
directionContainer.Anchor = Anchor.TopLeft;
|
||||||
|
directionContainer.Origin = Anchor.BottomLeft;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScrollingDirection.Down:
|
||||||
|
directionContainer.Scale = new Vector2(1, 1);
|
||||||
|
directionContainer.Anchor = Anchor.BottomLeft;
|
||||||
|
directionContainer.Origin = Anchor.BottomLeft;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnPressed(KeyBindingPressEvent<ManiaAction> e)
|
||||||
|
{
|
||||||
|
if (e.Action != column.Action.Value) return false;
|
||||||
|
|
||||||
|
const double lighting_fade_in_duration = 70;
|
||||||
|
Color4 lightingColour = getLightingColour();
|
||||||
|
|
||||||
|
background
|
||||||
|
.FlashColour(accentColour.Value.Lighten(0.8f), 200, Easing.OutQuint)
|
||||||
|
.FadeTo(1, lighting_fade_in_duration, Easing.OutQuint)
|
||||||
|
.Then()
|
||||||
|
.FadeTo(0.8f, 500);
|
||||||
|
|
||||||
|
hitTargetLine.FadeColour(Color4.White, lighting_fade_in_duration, Easing.OutQuint);
|
||||||
|
hitTargetLine.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Colour = lightingColour.Opacity(0.4f),
|
||||||
|
Radius = 20,
|
||||||
|
}, lighting_fade_in_duration, Easing.OutQuint);
|
||||||
|
|
||||||
|
topIcon.ScaleTo(0.9f, lighting_fade_in_duration, Easing.OutQuint);
|
||||||
|
topIcon.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Colour = lightingColour.Opacity(0.1f),
|
||||||
|
Radius = 20,
|
||||||
|
}, lighting_fade_in_duration, Easing.OutQuint);
|
||||||
|
|
||||||
|
bottomIcon.FadeColour(Color4.White, lighting_fade_in_duration, Easing.OutQuint);
|
||||||
|
|
||||||
|
foreach (var circle in bottomIcon)
|
||||||
|
{
|
||||||
|
circle.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Colour = lightingColour.Opacity(0.2f),
|
||||||
|
Radius = 60,
|
||||||
|
}, lighting_fade_in_duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnReleased(KeyBindingReleaseEvent<ManiaAction> e)
|
||||||
|
{
|
||||||
|
if (e.Action != column.Action.Value) return;
|
||||||
|
|
||||||
|
const double lighting_fade_out_duration = 800;
|
||||||
|
|
||||||
|
Color4 lightingColour = getLightingColour().Opacity(0);
|
||||||
|
|
||||||
|
// background fades out faster than lighting elements to give better definition to the player.
|
||||||
|
background.FadeTo(0.3f, 50, Easing.OutQuint)
|
||||||
|
.Then()
|
||||||
|
.FadeOut(lighting_fade_out_duration, Easing.OutQuint);
|
||||||
|
|
||||||
|
topIcon.ScaleTo(1f, 200, Easing.OutQuint);
|
||||||
|
topIcon.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Colour = lightingColour,
|
||||||
|
Radius = 20,
|
||||||
|
}, lighting_fade_out_duration, Easing.OutQuint);
|
||||||
|
|
||||||
|
hitTargetLine.FadeColour(OsuColour.Gray(196 / 255f), lighting_fade_out_duration, Easing.OutQuint);
|
||||||
|
hitTargetLine.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Colour = lightingColour,
|
||||||
|
Radius = 25,
|
||||||
|
}, lighting_fade_out_duration, Easing.OutQuint);
|
||||||
|
|
||||||
|
bottomIcon.FadeColour(accentColour.Value, lighting_fade_out_duration, Easing.OutQuint);
|
||||||
|
|
||||||
|
foreach (var circle in bottomIcon)
|
||||||
|
{
|
||||||
|
circle.TransformTo(nameof(EdgeEffect), new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Colour = lightingColour,
|
||||||
|
Radius = 30,
|
||||||
|
}, lighting_fade_out_duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color4 getLightingColour() => Interpolation.ValueAt(0.2f, accentColour.Value, Color4.White, 0, 1);
|
||||||
|
}
|
||||||
|
}
|
110
osu.Game.Rulesets.Mania/Skinning/Argon/ArgonNotePiece.cs
Normal file
110
osu.Game.Rulesets.Mania/Skinning/Argon/ArgonNotePiece.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 osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||||
|
{
|
||||||
|
internal class ArgonNotePiece : CompositeDrawable
|
||||||
|
{
|
||||||
|
public const float NOTE_HEIGHT = 42;
|
||||||
|
|
||||||
|
public const float CORNER_RADIUS = 3.4f;
|
||||||
|
|
||||||
|
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||||
|
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||||
|
|
||||||
|
private readonly Box colouredBox;
|
||||||
|
private readonly Box shadow;
|
||||||
|
|
||||||
|
public ArgonNotePiece()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = NOTE_HEIGHT;
|
||||||
|
|
||||||
|
CornerRadius = CORNER_RADIUS;
|
||||||
|
Masking = true;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
shadow = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Height = 0.82f,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = CORNER_RADIUS,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
colouredBox = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = CORNER_RADIUS * 2,
|
||||||
|
},
|
||||||
|
new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Y = 4,
|
||||||
|
Icon = FontAwesome.Solid.AngleDown,
|
||||||
|
Size = new Vector2(20),
|
||||||
|
Scale = new Vector2(1, 0.7f)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader(true)]
|
||||||
|
private void load(IScrollingInfo scrollingInfo, DrawableHitObject? drawableObject)
|
||||||
|
{
|
||||||
|
direction.BindTo(scrollingInfo.Direction);
|
||||||
|
direction.BindValueChanged(onDirectionChanged, true);
|
||||||
|
|
||||||
|
if (drawableObject != null)
|
||||||
|
{
|
||||||
|
accentColour.BindTo(drawableObject.AccentColour);
|
||||||
|
accentColour.BindValueChanged(onAccentChanged, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||||
|
{
|
||||||
|
colouredBox.Anchor = colouredBox.Origin = direction.NewValue == ScrollingDirection.Up
|
||||||
|
? Anchor.TopCentre
|
||||||
|
: Anchor.BottomCentre;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onAccentChanged(ValueChangedEvent<Color4> accent)
|
||||||
|
{
|
||||||
|
colouredBox.Colour = ColourInfo.GradientVertical(
|
||||||
|
accent.NewValue.Lighten(0.1f),
|
||||||
|
accent.NewValue
|
||||||
|
);
|
||||||
|
|
||||||
|
shadow.Colour = accent.NewValue.Darken(0.5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonStageBackground : CompositeDrawable
|
||||||
|
{
|
||||||
|
public ArgonStageBackground()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,139 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ManiaArgonSkinTransformer : SkinTransformer
|
||||||
|
{
|
||||||
|
private readonly ManiaBeatmap beatmap;
|
||||||
|
|
||||||
|
public ManiaArgonSkinTransformer(ISkin skin, IBeatmap beatmap)
|
||||||
|
: base(skin)
|
||||||
|
{
|
||||||
|
this.beatmap = (ManiaBeatmap)beatmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Drawable? GetDrawableComponent(ISkinComponent component)
|
||||||
|
{
|
||||||
|
switch (component)
|
||||||
|
{
|
||||||
|
case GameplaySkinComponent<HitResult> resultComponent:
|
||||||
|
return new ArgonJudgementPiece(resultComponent.Component);
|
||||||
|
|
||||||
|
case ManiaSkinComponent maniaComponent:
|
||||||
|
// TODO: Once everything is finalised, consider throwing UnsupportedSkinComponentException on missing entries.
|
||||||
|
switch (maniaComponent.Component)
|
||||||
|
{
|
||||||
|
case ManiaSkinComponents.StageBackground:
|
||||||
|
return new ArgonStageBackground();
|
||||||
|
|
||||||
|
case ManiaSkinComponents.ColumnBackground:
|
||||||
|
return new ArgonColumnBackground();
|
||||||
|
|
||||||
|
case ManiaSkinComponents.HoldNoteBody:
|
||||||
|
return new ArgonHoldBodyPiece();
|
||||||
|
|
||||||
|
case ManiaSkinComponents.HoldNoteTail:
|
||||||
|
return new ArgonHoldNoteTailPiece();
|
||||||
|
|
||||||
|
case ManiaSkinComponents.HoldNoteHead:
|
||||||
|
case ManiaSkinComponents.Note:
|
||||||
|
return new ArgonNotePiece();
|
||||||
|
|
||||||
|
case ManiaSkinComponents.HitTarget:
|
||||||
|
return new ArgonHitTarget();
|
||||||
|
|
||||||
|
case ManiaSkinComponents.KeyArea:
|
||||||
|
return new ArgonKeyArea();
|
||||||
|
|
||||||
|
case ManiaSkinComponents.HitExplosion:
|
||||||
|
return new ArgonHitExplosion();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.GetDrawableComponent(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
|
{
|
||||||
|
if (lookup is ManiaSkinConfigurationLookup maniaLookup)
|
||||||
|
{
|
||||||
|
int column = maniaLookup.ColumnIndex ?? 0;
|
||||||
|
var stage = beatmap.GetStageForColumnIndex(column);
|
||||||
|
|
||||||
|
switch (maniaLookup.Lookup)
|
||||||
|
{
|
||||||
|
case LegacyManiaSkinConfigurationLookups.ColumnSpacing:
|
||||||
|
return SkinUtils.As<TValue>(new Bindable<float>(2));
|
||||||
|
|
||||||
|
case LegacyManiaSkinConfigurationLookups.StagePaddingBottom:
|
||||||
|
case LegacyManiaSkinConfigurationLookups.StagePaddingTop:
|
||||||
|
return SkinUtils.As<TValue>(new Bindable<float>(30));
|
||||||
|
|
||||||
|
case LegacyManiaSkinConfigurationLookups.ColumnWidth:
|
||||||
|
return SkinUtils.As<TValue>(new Bindable<float>(
|
||||||
|
stage.IsSpecialColumn(column) ? 120 : 60
|
||||||
|
));
|
||||||
|
|
||||||
|
case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour:
|
||||||
|
|
||||||
|
Color4 colour;
|
||||||
|
|
||||||
|
if (stage.IsSpecialColumn(column))
|
||||||
|
colour = new Color4(159, 101, 255, 255);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (column % 8)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
colour = new Color4(240, 216, 0, 255);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
colour = new Color4(240, 101, 0, 255);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
colour = new Color4(240, 0, 130, 255);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
colour = new Color4(192, 0, 240, 255);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
colour = new Color4(178, 0, 240, 255);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
colour = new Color4(0, 96, 240, 255);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 6:
|
||||||
|
colour = new Color4(0, 226, 240, 255);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 7:
|
||||||
|
colour = new Color4(0, 240, 96, 255);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SkinUtils.As<TValue>(new Bindable<Color4>(colour));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.GetConfig<TLookup, TValue>(lookup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.Default
|
||||||
|
{
|
||||||
|
public class ManiaTrianglesSkinTransformer : SkinTransformer
|
||||||
|
{
|
||||||
|
private readonly ManiaBeatmap beatmap;
|
||||||
|
|
||||||
|
public ManiaTrianglesSkinTransformer(ISkin skin, IBeatmap beatmap)
|
||||||
|
: base(skin)
|
||||||
|
{
|
||||||
|
this.beatmap = (ManiaBeatmap)beatmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Color4 colourEven = new Color4(6, 84, 0, 255);
|
||||||
|
private readonly Color4 colourOdd = new Color4(94, 0, 57, 255);
|
||||||
|
private readonly Color4 colourSpecial = new Color4(0, 48, 63, 255);
|
||||||
|
|
||||||
|
public override IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
|
{
|
||||||
|
if (lookup is ManiaSkinConfigurationLookup maniaLookup)
|
||||||
|
{
|
||||||
|
switch (maniaLookup.Lookup)
|
||||||
|
{
|
||||||
|
case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour:
|
||||||
|
int column = maniaLookup.ColumnIndex ?? 0;
|
||||||
|
|
||||||
|
var stage = beatmap.GetStageForColumnIndex(column);
|
||||||
|
|
||||||
|
if (stage.IsSpecialColumn(column))
|
||||||
|
return SkinUtils.As<TValue>(new Bindable<Color4>(colourSpecial));
|
||||||
|
|
||||||
|
int distanceToEdge = Math.Min(column, (stage.Columns - 1) - column);
|
||||||
|
return SkinUtils.As<TValue>(new Bindable<Color4>(distanceToEdge % 2 == 0 ? colourOdd : colourEven));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.GetConfig<TLookup, TValue>(lookup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||||
|
{
|
||||||
|
public class ManiaClassicSkinTransformer : ManiaLegacySkinTransformer
|
||||||
|
{
|
||||||
|
public ManiaClassicSkinTransformer(ISkin skin, IBeatmap beatmap)
|
||||||
|
: base(skin, beatmap)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
|
{
|
||||||
|
if (lookup is ManiaSkinConfigurationLookup maniaLookup)
|
||||||
|
{
|
||||||
|
var baseLookup = base.GetConfig<TLookup, TValue>(lookup);
|
||||||
|
|
||||||
|
if (baseLookup != null)
|
||||||
|
return baseLookup;
|
||||||
|
|
||||||
|
// default provisioning.
|
||||||
|
switch (maniaLookup.Lookup)
|
||||||
|
{
|
||||||
|
case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour:
|
||||||
|
return SkinUtils.As<TValue>(new Bindable<Color4>(Color4.Black));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.GetConfig<TLookup, TValue>(lookup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.Timing;
|
using osu.Game.Beatmaps.Timing;
|
||||||
@ -125,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
/// Ensures alternation is reset before the first hitobject after a break.
|
/// Ensures alternation is reset before the first hitobject after a break.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void TestInputSingularWithBreak() => CreateModTest(new ModTestData
|
public void TestInputSingularWithBreak([Values] bool pressBeforeSecondObject) => CreateModTest(new ModTestData
|
||||||
{
|
{
|
||||||
Mod = new OsuModAlternate(),
|
Mod = new OsuModAlternate(),
|
||||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 2,
|
PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 2,
|
||||||
@ -155,21 +156,26 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ReplayFrames = new List<ReplayFrame>
|
ReplayFrames = new ReplayFrame[]
|
||||||
{
|
{
|
||||||
// first press to start alternate lock.
|
// first press to start alternate lock.
|
||||||
new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
|
new OsuReplayFrame(450, new Vector2(100), OsuAction.LeftButton),
|
||||||
new OsuReplayFrame(501, new Vector2(100)),
|
new OsuReplayFrame(451, new Vector2(100)),
|
||||||
|
// press same key at second hitobject and ensure it has been hit.
|
||||||
|
new OsuReplayFrame(2450, new Vector2(500, 100), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(2451, new Vector2(500, 100)),
|
||||||
|
// press same key at third hitobject and ensure it has been missed.
|
||||||
|
new OsuReplayFrame(2950, new Vector2(500, 100), OsuAction.LeftButton),
|
||||||
|
new OsuReplayFrame(2951, new Vector2(500, 100)),
|
||||||
|
}.Concat(!pressBeforeSecondObject
|
||||||
|
? Enumerable.Empty<ReplayFrame>()
|
||||||
|
: new ReplayFrame[]
|
||||||
|
{
|
||||||
// press same key after break but before hit object.
|
// press same key after break but before hit object.
|
||||||
new OsuReplayFrame(2250, new Vector2(300, 100), OsuAction.LeftButton),
|
new OsuReplayFrame(2250, new Vector2(300, 100), OsuAction.LeftButton),
|
||||||
new OsuReplayFrame(2251, new Vector2(300, 100)),
|
new OsuReplayFrame(2251, new Vector2(300, 100)),
|
||||||
// press same key at second hitobject and ensure it has been hit.
|
|
||||||
new OsuReplayFrame(2500, new Vector2(500, 100), OsuAction.LeftButton),
|
|
||||||
new OsuReplayFrame(2501, new Vector2(500, 100)),
|
|
||||||
// press same key at third hitobject and ensure it has been missed.
|
|
||||||
new OsuReplayFrame(3000, new Vector2(500, 100), OsuAction.LeftButton),
|
|
||||||
new OsuReplayFrame(3001, new Vector2(500, 100)),
|
|
||||||
}
|
}
|
||||||
|
).ToList()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
@ -88,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
if (!objects.Any())
|
if (!objects.Any())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return objects.All(o => Precision.AlmostEquals(o.ChildrenOfType<ShakeContainer>().First().Children.OfType<Container>().Single().Scale.X, target));
|
return objects.All(o => Precision.AlmostEquals(o.ChildrenOfType<Container>().First().Scale.X, target));
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool checkSomeHit()
|
private bool checkSomeHit()
|
||||||
|
102
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs
Normal file
102
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
|
{
|
||||||
|
public class TestSceneOsuModRandom : OsuModTestScene
|
||||||
|
{
|
||||||
|
[TestCase(1)]
|
||||||
|
[TestCase(7)]
|
||||||
|
[TestCase(10)]
|
||||||
|
public void TestDefaultBeatmap(float angleSharpness) => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModRandom
|
||||||
|
{
|
||||||
|
AngleSharpness = { Value = angleSharpness }
|
||||||
|
},
|
||||||
|
Autoplay = true,
|
||||||
|
PassCondition = () => true
|
||||||
|
});
|
||||||
|
|
||||||
|
[TestCase(1)]
|
||||||
|
[TestCase(7)]
|
||||||
|
[TestCase(10)]
|
||||||
|
public void TestJumpBeatmap(float angleSharpness) => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModRandom
|
||||||
|
{
|
||||||
|
AngleSharpness = { Value = angleSharpness }
|
||||||
|
},
|
||||||
|
Beatmap = jumpBeatmap,
|
||||||
|
Autoplay = true,
|
||||||
|
PassCondition = () => true
|
||||||
|
});
|
||||||
|
|
||||||
|
[TestCase(1)]
|
||||||
|
[TestCase(7)]
|
||||||
|
[TestCase(10)]
|
||||||
|
public void TestStreamBeatmap(float angleSharpness) => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new OsuModRandom
|
||||||
|
{
|
||||||
|
AngleSharpness = { Value = angleSharpness }
|
||||||
|
},
|
||||||
|
Beatmap = streamBeatmap,
|
||||||
|
Autoplay = true,
|
||||||
|
PassCondition = () => true
|
||||||
|
});
|
||||||
|
|
||||||
|
private OsuBeatmap jumpBeatmap =>
|
||||||
|
createHitCircleBeatmap(new[] { 100, 200, 300, 400 }, 8, 300, 2 * 300);
|
||||||
|
|
||||||
|
private OsuBeatmap streamBeatmap =>
|
||||||
|
createHitCircleBeatmap(new[] { 10, 20, 30, 40, 50, 60, 70, 80 }, 16, 150, 4 * 150);
|
||||||
|
|
||||||
|
private OsuBeatmap createHitCircleBeatmap(IEnumerable<int> spacings, int objectsPerSpacing, int interval, int beatLength)
|
||||||
|
{
|
||||||
|
var controlPointInfo = new ControlPointInfo();
|
||||||
|
controlPointInfo.Add(0, new TimingControlPoint
|
||||||
|
{
|
||||||
|
Time = 0,
|
||||||
|
BeatLength = beatLength
|
||||||
|
});
|
||||||
|
|
||||||
|
var beatmap = new OsuBeatmap
|
||||||
|
{
|
||||||
|
BeatmapInfo = new BeatmapInfo
|
||||||
|
{
|
||||||
|
StackLeniency = 0,
|
||||||
|
Difficulty = new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
ApproachRate = 8.5f
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ControlPointInfo = controlPointInfo
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (int spacing in spacings)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < objectsPerSpacing; i++)
|
||||||
|
{
|
||||||
|
beatmap.HitObjects.Add(new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = interval * beatmap.HitObjects.Count,
|
||||||
|
Position = beatmap.HitObjects.Count % 2 == 0 ? Vector2.Zero : new Vector2(spacing, 0),
|
||||||
|
NewCombo = i == 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return beatmap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 251 B |
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/cursor-smoke.png
Normal file
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/cursor-smoke.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
@ -170,7 +170,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
});
|
});
|
||||||
AddStep("setup default legacy skin", () =>
|
AddStep("setup default legacy skin", () =>
|
||||||
{
|
{
|
||||||
skinManager.CurrentSkinInfo.Value = skinManager.DefaultLegacySkin.SkinInfo;
|
skinManager.CurrentSkinInfo.Value = skinManager.DefaultClassicSkin.SkinInfo;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -58,10 +58,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null)
|
private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null)
|
||||||
{
|
{
|
||||||
var drawable = createSingle(circleSize, auto, timeOffset, positionOffset);
|
|
||||||
|
|
||||||
var playfield = new TestOsuPlayfield();
|
var playfield = new TestOsuPlayfield();
|
||||||
playfield.Add(drawable);
|
|
||||||
|
for (double t = timeOffset; t < timeOffset + 60000; t += 2000)
|
||||||
|
playfield.Add(createSingle(circleSize, auto, t, positionOffset));
|
||||||
|
|
||||||
return playfield;
|
return playfield;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
Child = new SkinProvidingContainer(new DefaultSkin(null))
|
Child = new SkinProvidingContainer(new TrianglesSkin(null))
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = drawableHitCircle = new DrawableHitCircle(hitCircle)
|
Child = drawableHitCircle = new DrawableHitCircle(hitCircle)
|
||||||
|
@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
var tintingSkin = skinManager.GetSkin(DefaultLegacySkin.CreateInfo());
|
var tintingSkin = skinManager.GetSkin(DefaultLegacySkin.CreateInfo());
|
||||||
tintingSkin.Configuration.ConfigDictionary["AllowSliderBallTint"] = "1";
|
tintingSkin.Configuration.ConfigDictionary["AllowSliderBallTint"] = "1";
|
||||||
|
|
||||||
var provider = Ruleset.Value.CreateInstance().CreateLegacySkinProvider(tintingSkin, Beatmap.Value.Beatmap);
|
var provider = Ruleset.Value.CreateInstance().CreateSkinTransformer(tintingSkin, Beatmap.Value.Beatmap);
|
||||||
|
|
||||||
Child = new SkinProvidingContainer(provider)
|
Child = new SkinProvidingContainer(provider)
|
||||||
{
|
{
|
||||||
|
136
osu.Game.Rulesets.Osu.Tests/TestSceneSmoke.cs
Normal file
136
osu.Game.Rulesets.Osu.Tests/TestSceneSmoke.cs
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Input.States;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using osu.Framework.Testing.Input;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneSmoke : OsuSkinnableTestScene
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestSmoking()
|
||||||
|
{
|
||||||
|
addStep("Create short smoke", 2_000);
|
||||||
|
addStep("Create medium smoke", 5_000);
|
||||||
|
addStep("Create long smoke", 10_000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addStep(string stepName, double duration)
|
||||||
|
{
|
||||||
|
var smokeContainers = new List<SmokeContainer>();
|
||||||
|
|
||||||
|
AddStep(stepName, () =>
|
||||||
|
{
|
||||||
|
smokeContainers.Clear();
|
||||||
|
SetContents(_ =>
|
||||||
|
{
|
||||||
|
smokeContainers.Add(new TestSmokeContainer
|
||||||
|
{
|
||||||
|
Duration = duration,
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
});
|
||||||
|
|
||||||
|
return new SmokingInputManager
|
||||||
|
{
|
||||||
|
Duration = duration,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Size = new Vector2(0.95f),
|
||||||
|
Child = smokeContainers[^1],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("Until skinnable expires", () =>
|
||||||
|
{
|
||||||
|
if (smokeContainers.Count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Logger.Log("How many: " + smokeContainers.Count);
|
||||||
|
|
||||||
|
foreach (var smokeContainer in smokeContainers)
|
||||||
|
{
|
||||||
|
if (smokeContainer.Children.Count != 0)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SmokingInputManager : ManualInputManager
|
||||||
|
{
|
||||||
|
public double Duration { get; init; }
|
||||||
|
|
||||||
|
private double? startTime;
|
||||||
|
|
||||||
|
public SmokingInputManager()
|
||||||
|
{
|
||||||
|
UseParentInput = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
MoveMouseTo(ToScreenSpace(DrawSize / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
const float spin_angle = 4 * MathF.PI;
|
||||||
|
|
||||||
|
startTime ??= Time.Current;
|
||||||
|
|
||||||
|
float fraction = (float)((Time.Current - startTime) / Duration);
|
||||||
|
|
||||||
|
float angle = fraction * spin_angle;
|
||||||
|
float radius = fraction * Math.Min(DrawSize.X, DrawSize.Y) / 2;
|
||||||
|
|
||||||
|
Vector2 pos = radius * new Vector2(MathF.Cos(angle), MathF.Sin(angle)) + DrawSize / 2;
|
||||||
|
MoveMouseTo(ToScreenSpace(pos));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestSmokeContainer : SmokeContainer
|
||||||
|
{
|
||||||
|
public double Duration { get; init; }
|
||||||
|
|
||||||
|
private bool isPressing;
|
||||||
|
private bool isFinished;
|
||||||
|
|
||||||
|
private double? startTime;
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
startTime ??= Time.Current + 0.1;
|
||||||
|
|
||||||
|
if (!isPressing && !isFinished && Time.Current > startTime)
|
||||||
|
{
|
||||||
|
OnPressed(new KeyBindingPressEvent<OsuAction>(new InputState(), OsuAction.Smoke));
|
||||||
|
isPressing = true;
|
||||||
|
isFinished = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPressing && Time.Current > startTime + Duration)
|
||||||
|
{
|
||||||
|
OnReleased(new KeyBindingReleaseEvent<OsuAction>(new InputState(), OsuAction.Smoke));
|
||||||
|
isPressing = false;
|
||||||
|
isFinished = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,6 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -43,7 +42,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
=> new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
=> new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||||
|
|
||||||
private DrawableSpinner drawableSpinner = null!;
|
private DrawableSpinner drawableSpinner = null!;
|
||||||
private SpriteIcon spinnerSymbol => drawableSpinner.ChildrenOfType<SpriteIcon>().Single();
|
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public override void SetUpSteps()
|
public override void SetUpSteps()
|
||||||
@ -77,7 +75,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
double finalCumulativeTrackerRotation = 0;
|
double finalCumulativeTrackerRotation = 0;
|
||||||
double finalTrackerRotation = 0, trackerRotationTolerance = 0;
|
double finalTrackerRotation = 0, trackerRotationTolerance = 0;
|
||||||
double finalSpinnerSymbolRotation = 0, spinnerSymbolRotationTolerance = 0;
|
|
||||||
|
|
||||||
addSeekStep(spinner_start_time + 5000);
|
addSeekStep(spinner_start_time + 5000);
|
||||||
AddStep("retrieve disc rotation", () =>
|
AddStep("retrieve disc rotation", () =>
|
||||||
@ -85,11 +82,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
finalTrackerRotation = drawableSpinner.RotationTracker.Rotation;
|
finalTrackerRotation = drawableSpinner.RotationTracker.Rotation;
|
||||||
trackerRotationTolerance = Math.Abs(finalTrackerRotation * 0.05f);
|
trackerRotationTolerance = Math.Abs(finalTrackerRotation * 0.05f);
|
||||||
});
|
});
|
||||||
AddStep("retrieve spinner symbol rotation", () =>
|
|
||||||
{
|
|
||||||
finalSpinnerSymbolRotation = spinnerSymbol.Rotation;
|
|
||||||
spinnerSymbolRotationTolerance = Math.Abs(finalSpinnerSymbolRotation * 0.05f);
|
|
||||||
});
|
|
||||||
AddStep("retrieve cumulative disc rotation", () => finalCumulativeTrackerRotation = drawableSpinner.Result.RateAdjustedRotation);
|
AddStep("retrieve cumulative disc rotation", () => finalCumulativeTrackerRotation = drawableSpinner.Result.RateAdjustedRotation);
|
||||||
|
|
||||||
addSeekStep(spinner_start_time + 2500);
|
addSeekStep(spinner_start_time + 2500);
|
||||||
@ -98,8 +90,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
// due to the exponential damping applied we're allowing a larger margin of error of about 10%
|
// due to the exponential damping applied we're allowing a larger margin of error of about 10%
|
||||||
// (5% relative to the final rotation value, but we're half-way through the spin).
|
// (5% relative to the final rotation value, but we're half-way through the spin).
|
||||||
() => drawableSpinner.RotationTracker.Rotation, () => Is.EqualTo(finalTrackerRotation / 2).Within(trackerRotationTolerance));
|
() => drawableSpinner.RotationTracker.Rotation, () => Is.EqualTo(finalTrackerRotation / 2).Within(trackerRotationTolerance));
|
||||||
AddAssert("symbol rotation rewound",
|
|
||||||
() => spinnerSymbol.Rotation, () => Is.EqualTo(finalSpinnerSymbolRotation / 2).Within(spinnerSymbolRotationTolerance));
|
|
||||||
AddAssert("is cumulative rotation rewound",
|
AddAssert("is cumulative rotation rewound",
|
||||||
// cumulative rotation is not damped, so we're treating it as the "ground truth" and allowing a comparatively smaller margin of error.
|
// cumulative rotation is not damped, so we're treating it as the "ground truth" and allowing a comparatively smaller margin of error.
|
||||||
() => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(finalCumulativeTrackerRotation / 2).Within(100));
|
() => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(finalCumulativeTrackerRotation / 2).Within(100));
|
||||||
@ -107,8 +97,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
addSeekStep(spinner_start_time + 5000);
|
addSeekStep(spinner_start_time + 5000);
|
||||||
AddAssert("is disc rotation almost same",
|
AddAssert("is disc rotation almost same",
|
||||||
() => drawableSpinner.RotationTracker.Rotation, () => Is.EqualTo(finalTrackerRotation).Within(trackerRotationTolerance));
|
() => drawableSpinner.RotationTracker.Rotation, () => Is.EqualTo(finalTrackerRotation).Within(trackerRotationTolerance));
|
||||||
AddAssert("is symbol rotation almost same",
|
|
||||||
() => spinnerSymbol.Rotation, () => Is.EqualTo(finalSpinnerSymbolRotation).Within(spinnerSymbolRotationTolerance));
|
|
||||||
AddAssert("is cumulative rotation almost same",
|
AddAssert("is cumulative rotation almost same",
|
||||||
() => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(finalCumulativeTrackerRotation).Within(100));
|
() => drawableSpinner.Result.RateAdjustedRotation, () => Is.EqualTo(finalCumulativeTrackerRotation).Within(100));
|
||||||
}
|
}
|
||||||
@ -122,7 +110,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
addSeekStep(5000);
|
addSeekStep(5000);
|
||||||
|
|
||||||
AddAssert("disc spin direction correct", () => clockwise ? drawableSpinner.RotationTracker.Rotation > 0 : drawableSpinner.RotationTracker.Rotation < 0);
|
AddAssert("disc spin direction correct", () => clockwise ? drawableSpinner.RotationTracker.Rotation > 0 : drawableSpinner.RotationTracker.Rotation < 0);
|
||||||
AddAssert("spinner symbol direction correct", () => clockwise ? spinnerSymbol.Rotation > 0 : spinnerSymbol.Rotation < 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Replay flip(Replay scoreReplay) => new Replay
|
private Replay flip(Replay scoreReplay) => new Replay
|
||||||
|
149
osu.Game.Rulesets.Osu.Tests/TestSceneTrianglesSpinnerRotation.cs
Normal file
149
osu.Game.Rulesets.Osu.Tests/TestSceneTrianglesSpinnerRotation.cs
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Replays;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Rulesets.Replays;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneTrianglesSpinnerRotation : TestSceneOsuPlayer
|
||||||
|
{
|
||||||
|
private const double spinner_start_time = 100;
|
||||||
|
private const double spinner_duration = 6000;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private SkinManager skinManager { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private AudioManager audioManager { get; set; } = null!;
|
||||||
|
|
||||||
|
protected override bool Autoplay => true;
|
||||||
|
|
||||||
|
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new ScoreExposedPlayer();
|
||||||
|
|
||||||
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null)
|
||||||
|
=> new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||||
|
|
||||||
|
private DrawableSpinner drawableSpinner = null!;
|
||||||
|
private SpriteIcon spinnerSymbol => drawableSpinner.ChildrenOfType<SpriteIcon>().Single();
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
|
||||||
|
AddStep("set triangles skin", () => skinManager.CurrentSkinInfo.Value = TrianglesSkin.CreateInfo().ToLiveUnmanaged());
|
||||||
|
|
||||||
|
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||||
|
AddStep("retrieve spinner", () => drawableSpinner = (DrawableSpinner)Player.DrawableRuleset.Playfield.AllHitObjects.First());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSymbolMiddleRewindingRotation()
|
||||||
|
{
|
||||||
|
double finalSpinnerSymbolRotation = 0, spinnerSymbolRotationTolerance = 0;
|
||||||
|
|
||||||
|
addSeekStep(spinner_start_time + 5000);
|
||||||
|
AddStep("retrieve spinner symbol rotation", () =>
|
||||||
|
{
|
||||||
|
finalSpinnerSymbolRotation = spinnerSymbol.Rotation;
|
||||||
|
spinnerSymbolRotationTolerance = Math.Abs(finalSpinnerSymbolRotation * 0.05f);
|
||||||
|
});
|
||||||
|
|
||||||
|
addSeekStep(spinner_start_time + 2500);
|
||||||
|
AddAssert("symbol rotation rewound",
|
||||||
|
() => spinnerSymbol.Rotation, () => Is.EqualTo(finalSpinnerSymbolRotation / 2).Within(spinnerSymbolRotationTolerance));
|
||||||
|
|
||||||
|
addSeekStep(spinner_start_time + 5000);
|
||||||
|
AddAssert("is symbol rotation almost same",
|
||||||
|
() => spinnerSymbol.Rotation, () => Is.EqualTo(finalSpinnerSymbolRotation).Within(spinnerSymbolRotationTolerance));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSymbolRotationDirection([Values(true, false)] bool clockwise)
|
||||||
|
{
|
||||||
|
if (clockwise)
|
||||||
|
transformReplay(flip);
|
||||||
|
|
||||||
|
addSeekStep(5000);
|
||||||
|
AddAssert("spinner symbol direction correct", () => clockwise ? spinnerSymbol.Rotation > 0 : spinnerSymbol.Rotation < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Replay flip(Replay scoreReplay) => new Replay
|
||||||
|
{
|
||||||
|
Frames = scoreReplay
|
||||||
|
.Frames
|
||||||
|
.Cast<OsuReplayFrame>()
|
||||||
|
.Select(replayFrame =>
|
||||||
|
{
|
||||||
|
var flippedPosition = new Vector2(OsuPlayfield.BASE_SIZE.X - replayFrame.Position.X, replayFrame.Position.Y);
|
||||||
|
return new OsuReplayFrame(replayFrame.Time, flippedPosition, replayFrame.Actions.ToArray());
|
||||||
|
})
|
||||||
|
.Cast<ReplayFrame>()
|
||||||
|
.ToList()
|
||||||
|
};
|
||||||
|
|
||||||
|
private void addSeekStep(double time)
|
||||||
|
{
|
||||||
|
AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time));
|
||||||
|
AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(time).Within(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void transformReplay(Func<Replay, Replay> replayTransformation) => AddStep("set replay", () =>
|
||||||
|
{
|
||||||
|
var drawableRuleset = this.ChildrenOfType<DrawableOsuRuleset>().Single();
|
||||||
|
var score = drawableRuleset.ReplayScore;
|
||||||
|
var transformedScore = new Score
|
||||||
|
{
|
||||||
|
ScoreInfo = score.ScoreInfo,
|
||||||
|
Replay = replayTransformation.Invoke(score.Replay)
|
||||||
|
};
|
||||||
|
drawableRuleset.SetReplayScore(transformedScore);
|
||||||
|
});
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Spinner
|
||||||
|
{
|
||||||
|
Position = new Vector2(256, 192),
|
||||||
|
StartTime = spinner_start_time,
|
||||||
|
Duration = spinner_duration
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private class ScoreExposedPlayer : TestPlayer
|
||||||
|
{
|
||||||
|
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||||
|
|
||||||
|
public ScoreExposedPlayer()
|
||||||
|
: base(false, false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,10 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||||
<PackageReference Include="Moq" Version="4.18.2" />
|
<PackageReference Include="Moq" Version="4.18.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
|
@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
|
|
||||||
if (!(currentObj.BaseObject is Spinner))
|
if (!(currentObj.BaseObject is Spinner))
|
||||||
{
|
{
|
||||||
double jumpDistance = (osuHitObject.StackedPosition - currentHitObject.EndPosition).Length;
|
double jumpDistance = (osuHitObject.StackedPosition - currentHitObject.StackedEndPosition).Length;
|
||||||
|
|
||||||
cumulativeStrainTime += lastObj.StrainTime;
|
cumulativeStrainTime += lastObj.StrainTime;
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current <see cref="OsuDifficultyHitObject"/>.
|
/// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current <see cref="OsuDifficultyHitObject"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static double EvaluateDifficultyOf(DifficultyHitObject current, double greatWindow)
|
public static double EvaluateDifficultyOf(DifficultyHitObject current)
|
||||||
{
|
{
|
||||||
if (current.BaseObject is Spinner)
|
if (current.BaseObject is Spinner)
|
||||||
return 0;
|
return 0;
|
||||||
@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
double lastDelta = lastObj.StrainTime;
|
double lastDelta = lastObj.StrainTime;
|
||||||
double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
|
double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses.
|
||||||
|
|
||||||
double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - greatWindow * 0.6) / (greatWindow * 0.6));
|
double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - currObj.HitWindowGreat * 0.3) / (currObj.HitWindowGreat * 0.3));
|
||||||
|
|
||||||
windowPenalty = Math.Min(1, windowPenalty);
|
windowPenalty = Math.Min(1, windowPenalty);
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
/// <item><description>and how easily they can be cheesed.</description></item>
|
/// <item><description>and how easily they can be cheesed.</description></item>
|
||||||
/// </list>
|
/// </list>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static double EvaluateDifficultyOf(DifficultyHitObject current, double greatWindow)
|
public static double EvaluateDifficultyOf(DifficultyHitObject current)
|
||||||
{
|
{
|
||||||
if (current.BaseObject is Spinner)
|
if (current.BaseObject is Spinner)
|
||||||
return 0;
|
return 0;
|
||||||
@ -35,7 +35,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
var osuNextObj = (OsuDifficultyHitObject)current.Next(0);
|
var osuNextObj = (OsuDifficultyHitObject)current.Next(0);
|
||||||
|
|
||||||
double strainTime = osuCurrObj.StrainTime;
|
double strainTime = osuCurrObj.StrainTime;
|
||||||
double greatWindowFull = greatWindow * 2;
|
|
||||||
double doubletapness = 1;
|
double doubletapness = 1;
|
||||||
|
|
||||||
// Nerf doubletappable doubles.
|
// Nerf doubletappable doubles.
|
||||||
@ -45,13 +44,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
|||||||
double nextDeltaTime = Math.Max(1, osuNextObj.DeltaTime);
|
double nextDeltaTime = Math.Max(1, osuNextObj.DeltaTime);
|
||||||
double deltaDifference = Math.Abs(nextDeltaTime - currDeltaTime);
|
double deltaDifference = Math.Abs(nextDeltaTime - currDeltaTime);
|
||||||
double speedRatio = currDeltaTime / Math.Max(currDeltaTime, deltaDifference);
|
double speedRatio = currDeltaTime / Math.Max(currDeltaTime, deltaDifference);
|
||||||
double windowRatio = Math.Pow(Math.Min(1, currDeltaTime / greatWindowFull), 2);
|
double windowRatio = Math.Pow(Math.Min(1, currDeltaTime / osuCurrObj.HitWindowGreat), 2);
|
||||||
doubletapness = Math.Pow(speedRatio, 1 - windowRatio);
|
doubletapness = Math.Pow(speedRatio, 1 - windowRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cap deltatime to the OD 300 hitwindow.
|
// Cap deltatime to the OD 300 hitwindow.
|
||||||
// 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap.
|
// 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap.
|
||||||
strainTime /= Math.Clamp((strainTime / greatWindowFull) / 0.93, 0.92, 1);
|
strainTime /= Math.Clamp((strainTime / osuCurrObj.HitWindowGreat) / 0.93, 0.92, 1);
|
||||||
|
|
||||||
// derive speedBonus for calculation
|
// derive speedBonus for calculation
|
||||||
double speedBonus = 1.0;
|
double speedBonus = 1.0;
|
||||||
|
@ -23,7 +23,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
public class OsuDifficultyCalculator : DifficultyCalculator
|
public class OsuDifficultyCalculator : DifficultyCalculator
|
||||||
{
|
{
|
||||||
private const double difficulty_multiplier = 0.0675;
|
private const double difficulty_multiplier = 0.0675;
|
||||||
private double hitWindowGreat;
|
|
||||||
|
|
||||||
public override int Version => 20220902;
|
public override int Version => 20220902;
|
||||||
|
|
||||||
@ -45,6 +44,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
|
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
|
||||||
|
|
||||||
|
if (mods.Any(m => m is OsuModTouchDevice))
|
||||||
|
{
|
||||||
|
aimRating = Math.Pow(aimRating, 0.8);
|
||||||
|
flashlightRating = Math.Pow(flashlightRating, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
if (mods.Any(h => h is OsuModRelax))
|
if (mods.Any(h => h is OsuModRelax))
|
||||||
{
|
{
|
||||||
aimRating *= 0.9;
|
aimRating *= 0.9;
|
||||||
@ -76,6 +81,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
int sliderCount = beatmap.HitObjects.Count(h => h is Slider);
|
int sliderCount = beatmap.HitObjects.Count(h => h is Slider);
|
||||||
int spinnerCount = beatmap.HitObjects.Count(h => h is Spinner);
|
int spinnerCount = beatmap.HitObjects.Count(h => h is Spinner);
|
||||||
|
|
||||||
|
HitWindows hitWindows = new OsuHitWindows();
|
||||||
|
hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);
|
||||||
|
|
||||||
|
double hitWindowGreat = hitWindows.WindowFor(HitResult.Great) / clockRate;
|
||||||
|
|
||||||
return new OsuDifficultyAttributes
|
return new OsuDifficultyAttributes
|
||||||
{
|
{
|
||||||
StarRating = starRating,
|
StarRating = starRating,
|
||||||
@ -112,22 +122,18 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate)
|
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate)
|
||||||
{
|
{
|
||||||
HitWindows hitWindows = new OsuHitWindows();
|
|
||||||
hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);
|
|
||||||
|
|
||||||
hitWindowGreat = hitWindows.WindowFor(HitResult.Great) / clockRate;
|
|
||||||
|
|
||||||
return new Skill[]
|
return new Skill[]
|
||||||
{
|
{
|
||||||
new Aim(mods, true),
|
new Aim(mods, true),
|
||||||
new Aim(mods, false),
|
new Aim(mods, false),
|
||||||
new Speed(mods, hitWindowGreat),
|
new Speed(mods),
|
||||||
new Flashlight(mods)
|
new Flashlight(mods)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
|
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
|
||||||
{
|
{
|
||||||
|
new OsuModTouchDevice(),
|
||||||
new OsuModDoubleTime(),
|
new OsuModDoubleTime(),
|
||||||
new OsuModHalfTime(),
|
new OsuModHalfTime(),
|
||||||
new OsuModEasy(),
|
new OsuModEasy(),
|
||||||
|
@ -88,12 +88,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attributes)
|
private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attributes)
|
||||||
{
|
{
|
||||||
double rawAim = attributes.AimDifficulty;
|
double aimValue = Math.Pow(5.0 * Math.Max(1.0, attributes.AimDifficulty / 0.0675) - 4.0, 3.0) / 100000.0;
|
||||||
|
|
||||||
if (score.Mods.Any(m => m is OsuModTouchDevice))
|
|
||||||
rawAim = Math.Pow(rawAim, 0.8);
|
|
||||||
|
|
||||||
double aimValue = Math.Pow(5.0 * Math.Max(1.0, rawAim / 0.0675) - 4.0, 3.0) / 100000.0;
|
|
||||||
|
|
||||||
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
||||||
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
||||||
@ -233,12 +228,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
if (!score.Mods.Any(h => h is OsuModFlashlight))
|
if (!score.Mods.Any(h => h is OsuModFlashlight))
|
||||||
return 0.0;
|
return 0.0;
|
||||||
|
|
||||||
double rawFlashlight = attributes.FlashlightDifficulty;
|
double flashlightValue = Math.Pow(attributes.FlashlightDifficulty, 2.0) * 25.0;
|
||||||
|
|
||||||
if (score.Mods.Any(m => m is OsuModTouchDevice))
|
|
||||||
rawFlashlight = Math.Pow(rawFlashlight, 0.8);
|
|
||||||
|
|
||||||
double flashlightValue = Math.Pow(rawFlashlight, 2.0) * 25.0;
|
|
||||||
|
|
||||||
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
||||||
if (effectiveMissCount > 0)
|
if (effectiveMissCount > 0)
|
||||||
|
@ -9,6 +9,7 @@ using osu.Game.Rulesets.Difficulty.Preprocessing;
|
|||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||||
@ -78,6 +79,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public double? Angle { get; private set; }
|
public double? Angle { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the full hit window for a Great <see cref="HitResult"/>.
|
||||||
|
/// </summary>
|
||||||
|
public double HitWindowGreat { get; private set; }
|
||||||
|
|
||||||
private readonly OsuHitObject lastLastObject;
|
private readonly OsuHitObject lastLastObject;
|
||||||
private readonly OsuHitObject lastObject;
|
private readonly OsuHitObject lastObject;
|
||||||
|
|
||||||
@ -90,6 +96,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
// Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects.
|
// Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects.
|
||||||
StrainTime = Math.Max(DeltaTime, min_delta_time);
|
StrainTime = Math.Max(DeltaTime, min_delta_time);
|
||||||
|
|
||||||
|
if (BaseObject is Slider sliderObject)
|
||||||
|
{
|
||||||
|
HitWindowGreat = 2 * sliderObject.HeadCircle.HitWindows.WindowFor(HitResult.Great) / clockRate;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HitWindowGreat = 2 * BaseObject.HitWindows.WindowFor(HitResult.Great) / clockRate;
|
||||||
|
}
|
||||||
|
|
||||||
setDistances(clockRate);
|
setDistances(clockRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,14 +26,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
|
|
||||||
protected override int ReducedSectionCount => 5;
|
protected override int ReducedSectionCount => 5;
|
||||||
protected override double DifficultyMultiplier => 1.04;
|
protected override double DifficultyMultiplier => 1.04;
|
||||||
private readonly double greatWindow;
|
|
||||||
|
|
||||||
private readonly List<double> objectStrains = new List<double>();
|
private readonly List<double> objectStrains = new List<double>();
|
||||||
|
|
||||||
public Speed(Mod[] mods, double hitWindowGreat)
|
public Speed(Mod[] mods)
|
||||||
: base(mods)
|
: base(mods)
|
||||||
{
|
{
|
||||||
greatWindow = hitWindowGreat;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
||||||
@ -43,9 +41,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
protected override double StrainValueAt(DifficultyHitObject current)
|
protected override double StrainValueAt(DifficultyHitObject current)
|
||||||
{
|
{
|
||||||
currentStrain *= strainDecay(((OsuDifficultyHitObject)current).StrainTime);
|
currentStrain *= strainDecay(((OsuDifficultyHitObject)current).StrainTime);
|
||||||
currentStrain += SpeedEvaluator.EvaluateDifficultyOf(current, greatWindow) * skillMultiplier;
|
currentStrain += SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier;
|
||||||
|
|
||||||
currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current, greatWindow);
|
currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current);
|
||||||
|
|
||||||
double totalStrain = currentStrain * currentRhythm;
|
double totalStrain = currentStrain * currentRhythm;
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
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;
|
||||||
@ -53,6 +54,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
private IBindable<Vector2> sliderPosition;
|
private IBindable<Vector2> sliderPosition;
|
||||||
private IBindable<float> sliderScale;
|
private IBindable<float> sliderScale;
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
|
private readonly IBindable<int> sliderVersion;
|
||||||
|
|
||||||
public PathControlPointPiece(Slider slider, PathControlPoint controlPoint)
|
public PathControlPointPiece(Slider slider, PathControlPoint controlPoint)
|
||||||
{
|
{
|
||||||
this.slider = slider;
|
this.slider = slider;
|
||||||
@ -61,11 +65,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
// we don't want to run the path type update on construction as it may inadvertently change the slider.
|
// we don't want to run the path type update on construction as it may inadvertently change the slider.
|
||||||
cachePoints(slider);
|
cachePoints(slider);
|
||||||
|
|
||||||
slider.Path.Version.BindValueChanged(_ =>
|
sliderVersion = slider.Path.Version.GetBoundCopy();
|
||||||
|
|
||||||
|
// schedule ensure that updates are only applied after all operations from a single frame are applied.
|
||||||
|
// this avoids inadvertently changing the slider path type for batch operations.
|
||||||
|
sliderVersion.BindValueChanged(_ => Scheduler.AddOnce(() =>
|
||||||
{
|
{
|
||||||
cachePoints(slider);
|
cachePoints(slider);
|
||||||
updatePathType();
|
updatePathType();
|
||||||
});
|
}));
|
||||||
|
|
||||||
controlPoint.Changed += updateMarkerDisplay;
|
controlPoint.Changed += updateMarkerDisplay;
|
||||||
|
|
||||||
|
@ -303,11 +303,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
var result = snapProvider?.FindSnappedPositionAndTime(Parent.ToScreenSpace(e.MousePosition));
|
||||||
|
|
||||||
|
Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? Parent.ToScreenSpace(e.MousePosition)) - dragStartPositions[draggedControlPointIndex] - slider.Position;
|
||||||
|
|
||||||
for (int i = 0; i < controlPoints.Count; ++i)
|
for (int i = 0; i < controlPoints.Count; ++i)
|
||||||
{
|
{
|
||||||
var controlPoint = controlPoints[i];
|
var controlPoint = controlPoints[i];
|
||||||
if (selectedControlPoints.Contains(controlPoint))
|
if (selectedControlPoints.Contains(controlPoint))
|
||||||
controlPoint.Position = dragStartPositions[i] + (e.MousePosition - e.MouseDownPosition);
|
controlPoint.Position = dragStartPositions[i] + movementDelta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,7 +198,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the cursor position.
|
// Update the cursor position.
|
||||||
cursor.Position = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position;
|
var result = snapProvider?.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position);
|
||||||
|
cursor.Position = ToLocalSpace(result?.ScreenSpacePosition ?? inputManager.CurrentState.Mouse.Position) - HitObject.Position;
|
||||||
}
|
}
|
||||||
else if (cursor != null)
|
else if (cursor != null)
|
||||||
{
|
{
|
||||||
|
@ -163,7 +163,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
protected override void OnDrag(DragEvent e)
|
protected override void OnDrag(DragEvent e)
|
||||||
{
|
{
|
||||||
if (placementControlPoint != null)
|
if (placementControlPoint != null)
|
||||||
placementControlPoint.Position = e.MousePosition - HitObject.Position;
|
{
|
||||||
|
var result = snapProvider?.FindSnappedPositionAndTime(ToScreenSpace(e.MousePosition));
|
||||||
|
placementControlPoint.Position = ToLocalSpace(result?.ScreenSpacePosition ?? ToScreenSpace(e.MousePosition)) - HitObject.Position;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnMouseUp(MouseUpEvent e)
|
protected override void OnMouseUp(MouseUpEvent e)
|
||||||
|
@ -127,16 +127,13 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
didFlip = true;
|
didFlip = true;
|
||||||
|
|
||||||
var controlPoints = slider.Path.ControlPoints.Select(p =>
|
foreach (var cp in slider.Path.ControlPoints)
|
||||||
new PathControlPoint(new Vector2(
|
{
|
||||||
(direction == Direction.Horizontal ? -1 : 1) * p.Position.X,
|
cp.Position = new Vector2(
|
||||||
(direction == Direction.Vertical ? -1 : 1) * p.Position.Y
|
(direction == Direction.Horizontal ? -1 : 1) * cp.Position.X,
|
||||||
), p.Type)).ToArray();
|
(direction == Direction.Vertical ? -1 : 1) * cp.Position.Y
|
||||||
|
);
|
||||||
// Importantly, update as a single operation so automatic adjustment of control points to different
|
}
|
||||||
// curve types does not unexpectedly trigger and change the slider's shape.
|
|
||||||
slider.Path.ControlPoints.Clear();
|
|
||||||
slider.Path.ControlPoints.AddRange(controlPoints);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,13 +183,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
if (h is IHasPath path)
|
if (h is IHasPath path)
|
||||||
{
|
{
|
||||||
var controlPoints = path.Path.ControlPoints.Select(p =>
|
foreach (PathControlPoint cp in path.Path.ControlPoints)
|
||||||
new PathControlPoint(RotatePointAroundOrigin(p.Position, Vector2.Zero, delta), p.Type)).ToArray();
|
cp.Position = RotatePointAroundOrigin(cp.Position, Vector2.Zero, delta);
|
||||||
|
|
||||||
// Importantly, update as a single operation so automatic adjustment of control points to different
|
|
||||||
// curve types does not unexpectedly trigger and change the slider's shape.
|
|
||||||
path.Path.ControlPoints.Clear();
|
|
||||||
path.Path.ControlPoints.AddRange(controlPoints);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ using osu.Game.Utils;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
{
|
{
|
||||||
public abstract class InputBlockingMod : Mod, IApplicableToDrawableRuleset<OsuHitObject>
|
public abstract class InputBlockingMod : Mod, IApplicableToDrawableRuleset<OsuHitObject>, IUpdatableByPlayfield
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1.0;
|
public override double ScoreMultiplier => 1.0;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax), typeof(OsuModCinema) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax), typeof(OsuModCinema) };
|
||||||
@ -62,15 +62,18 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
gameplayClock = drawableRuleset.FrameStableClock;
|
gameplayClock = drawableRuleset.FrameStableClock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Update(Playfield playfield)
|
||||||
|
{
|
||||||
|
if (LastAcceptedAction != null && nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime))
|
||||||
|
LastAcceptedAction = null;
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract bool CheckValidNewAction(OsuAction action);
|
protected abstract bool CheckValidNewAction(OsuAction action);
|
||||||
|
|
||||||
private bool checkCorrectAction(OsuAction action)
|
private bool checkCorrectAction(OsuAction action)
|
||||||
{
|
{
|
||||||
if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime))
|
if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime))
|
||||||
{
|
|
||||||
LastAcceptedAction = null;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
switch (action)
|
switch (action)
|
||||||
{
|
{
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Configuration;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
{
|
{
|
||||||
@ -18,13 +17,10 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public override LocalisableString Description => "Hit them at the right size!";
|
public override LocalisableString Description => "Hit them at the right size!";
|
||||||
|
|
||||||
[SettingSource("Starting Size", "The initial size multiplier applied to all objects.")]
|
public override BindableNumber<float> StartScale { get; } = new BindableFloat(2)
|
||||||
public override BindableNumber<float> StartScale { get; } = new BindableFloat
|
|
||||||
{
|
{
|
||||||
MinValue = 1f,
|
MinValue = 1f,
|
||||||
MaxValue = 25f,
|
MaxValue = 25f,
|
||||||
Default = 2f,
|
|
||||||
Value = 2f,
|
|
||||||
Precision = 0.1f,
|
Precision = 0.1f,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -32,24 +32,16 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
Precision = default_follow_delay,
|
Precision = default_follow_delay,
|
||||||
};
|
};
|
||||||
|
|
||||||
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
|
public override BindableFloat SizeMultiplier { get; } = new BindableFloat(1)
|
||||||
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
|
|
||||||
{
|
{
|
||||||
MinValue = 0.5f,
|
MinValue = 0.5f,
|
||||||
MaxValue = 2f,
|
MaxValue = 2f,
|
||||||
Default = 1f,
|
|
||||||
Value = 1f,
|
|
||||||
Precision = 0.1f
|
Precision = 0.1f
|
||||||
};
|
};
|
||||||
|
|
||||||
[SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")]
|
public override BindableBool ComboBasedSize { get; } = new BindableBool(true);
|
||||||
public override BindableBool ComboBasedSize { get; } = new BindableBool
|
|
||||||
{
|
|
||||||
Default = true,
|
|
||||||
Value = true
|
|
||||||
};
|
|
||||||
|
|
||||||
public override float DefaultFlashlightSize => 180;
|
public override float DefaultFlashlightSize => 200;
|
||||||
|
|
||||||
private OsuFlashlight flashlight = null!;
|
private OsuFlashlight flashlight = null!;
|
||||||
|
|
||||||
@ -71,6 +63,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
followDelay = modFlashlight.FollowDelay.Value;
|
followDelay = modFlashlight.FollowDelay.Value;
|
||||||
|
|
||||||
FlashlightSize = new Vector2(0, GetSizeFor(0));
|
FlashlightSize = new Vector2(0, GetSizeFor(0));
|
||||||
|
FlashlightSmoothness = 1.4f;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnSliderTrackingChange(ValueChangedEvent<bool> e)
|
public void OnSliderTrackingChange(ValueChangedEvent<bool> e)
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Configuration;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
{
|
{
|
||||||
@ -18,13 +17,10 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public override LocalisableString Description => "Hit them at the right size!";
|
public override LocalisableString Description => "Hit them at the right size!";
|
||||||
|
|
||||||
[SettingSource("Starting Size", "The initial size multiplier applied to all objects.")]
|
public override BindableNumber<float> StartScale { get; } = new BindableFloat(0.5f)
|
||||||
public override BindableNumber<float> StartScale { get; } = new BindableFloat
|
|
||||||
{
|
{
|
||||||
MinValue = 0f,
|
MinValue = 0f,
|
||||||
MaxValue = 0.99f,
|
MaxValue = 0.99f,
|
||||||
Default = 0.5f,
|
|
||||||
Value = 0.5f,
|
|
||||||
Precision = 0.01f,
|
Precision = 0.01f,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,6 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
|
||||||
using osu.Game.Overlays.Settings;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
@ -22,15 +20,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
private PeriodTracker spinnerPeriods = null!;
|
private PeriodTracker spinnerPeriods = null!;
|
||||||
|
|
||||||
[SettingSource(
|
public override BindableInt HiddenComboCount { get; } = new BindableInt(10)
|
||||||
"Hidden at combo",
|
|
||||||
"The combo count at which the cursor becomes completely hidden",
|
|
||||||
SettingControlType = typeof(SettingsSlider<int, HiddenComboSlider>)
|
|
||||||
)]
|
|
||||||
public override BindableInt HiddenComboCount { get; } = new BindableInt
|
|
||||||
{
|
{
|
||||||
Default = 10,
|
|
||||||
Value = 10,
|
|
||||||
MinValue = 0,
|
MinValue = 0,
|
||||||
MaxValue = 50,
|
MaxValue = 50,
|
||||||
};
|
};
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
@ -20,6 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
|
[SettingSource("Starting Size", "The initial size multiplier applied to all objects.")]
|
||||||
public abstract BindableNumber<float> StartScale { get; }
|
public abstract BindableNumber<float> StartScale { get; }
|
||||||
|
|
||||||
protected virtual float EndScale => 1;
|
protected virtual float EndScale => 1;
|
||||||
|
@ -4,9 +4,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
@ -25,6 +28,14 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModTarget)).ToArray();
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModTarget)).ToArray();
|
||||||
|
|
||||||
|
[SettingSource("Angle sharpness", "How sharp angles should be", SettingControlType = typeof(SettingsSlider<float>))]
|
||||||
|
public BindableFloat AngleSharpness { get; } = new BindableFloat(7)
|
||||||
|
{
|
||||||
|
MinValue = 1,
|
||||||
|
MaxValue = 10,
|
||||||
|
Precision = 0.1f
|
||||||
|
};
|
||||||
|
|
||||||
private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast;
|
private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast;
|
||||||
|
|
||||||
private Random random = null!;
|
private Random random = null!;
|
||||||
@ -50,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
if (shouldStartNewSection(osuBeatmap, positionInfos, i))
|
if (shouldStartNewSection(osuBeatmap, positionInfos, i))
|
||||||
{
|
{
|
||||||
sectionOffset = OsuHitObjectGenerationUtils.RandomGaussian(random, 0, 0.0008f);
|
sectionOffset = getRandomOffset(0.0008f);
|
||||||
flowDirection = !flowDirection;
|
flowDirection = !flowDirection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,11 +76,11 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
float flowChangeOffset = 0;
|
float flowChangeOffset = 0;
|
||||||
|
|
||||||
// Offsets only the angle of the current hit object.
|
// Offsets only the angle of the current hit object.
|
||||||
float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(random, 0, 0.002f);
|
float oneTimeOffset = getRandomOffset(0.002f);
|
||||||
|
|
||||||
if (shouldApplyFlowChange(positionInfos, i))
|
if (shouldApplyFlowChange(positionInfos, i))
|
||||||
{
|
{
|
||||||
flowChangeOffset = OsuHitObjectGenerationUtils.RandomGaussian(random, 0, 0.002f);
|
flowChangeOffset = getRandomOffset(0.002f);
|
||||||
flowDirection = !flowDirection;
|
flowDirection = !flowDirection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,13 +97,36 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
osuBeatmap.HitObjects = OsuHitObjectGenerationUtils.RepositionHitObjects(positionInfos);
|
osuBeatmap.HitObjects = OsuHitObjectGenerationUtils.RepositionHitObjects(positionInfos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private float getRandomOffset(float stdDev)
|
||||||
|
{
|
||||||
|
// Range: [0.5, 2]
|
||||||
|
// Higher angle sharpness -> lower multiplier
|
||||||
|
float customMultiplier = (1.5f * AngleSharpness.MaxValue - AngleSharpness.Value) / (1.5f * AngleSharpness.MaxValue - AngleSharpness.Default);
|
||||||
|
|
||||||
|
return OsuHitObjectGenerationUtils.RandomGaussian(random, 0, stdDev * customMultiplier);
|
||||||
|
}
|
||||||
|
|
||||||
/// <param name="targetDistance">The target distance between the previous and the current <see cref="OsuHitObject"/>.</param>
|
/// <param name="targetDistance">The target distance between the previous and the current <see cref="OsuHitObject"/>.</param>
|
||||||
/// <param name="offset">The angle (in rad) by which the target angle should be offset.</param>
|
/// <param name="offset">The angle (in rad) by which the target angle should be offset.</param>
|
||||||
/// <param name="flowDirection">Whether the relative angle should be positive or negative.</param>
|
/// <param name="flowDirection">Whether the relative angle should be positive or negative.</param>
|
||||||
private static float getRelativeTargetAngle(float targetDistance, float offset, bool flowDirection)
|
private float getRelativeTargetAngle(float targetDistance, float offset, bool flowDirection)
|
||||||
{
|
{
|
||||||
float angle = (float)(2.16 / (1 + 200 * Math.Exp(0.036 * (targetDistance - 310))) + 0.5 + offset);
|
// Range: [0.1, 1]
|
||||||
|
float angleSharpness = AngleSharpness.Value / AngleSharpness.MaxValue;
|
||||||
|
// Range: [0, 0.9]
|
||||||
|
float angleWideness = 1 - angleSharpness;
|
||||||
|
|
||||||
|
// Range: [-60, 30]
|
||||||
|
float customOffsetX = angleSharpness * 100 - 70;
|
||||||
|
// Range: [-0.075, 0.15]
|
||||||
|
float customOffsetY = angleWideness * 0.25f - 0.075f;
|
||||||
|
|
||||||
|
targetDistance += customOffsetX;
|
||||||
|
float angle = (float)(2.16 / (1 + 200 * Math.Exp(0.036 * (targetDistance - 310 + customOffsetX))) + 0.5);
|
||||||
|
angle += offset + customOffsetY;
|
||||||
|
|
||||||
float relativeAngle = (float)Math.PI - angle;
|
float relativeAngle = (float)Math.PI - angle;
|
||||||
|
|
||||||
return flowDirection ? -relativeAngle : relativeAngle;
|
return flowDirection ? -relativeAngle : relativeAngle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,11 +53,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
[SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))]
|
[SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))]
|
||||||
public Bindable<int?> Seed { get; } = new Bindable<int?>
|
public Bindable<int?> Seed { get; } = new Bindable<int?>();
|
||||||
{
|
|
||||||
Default = null,
|
|
||||||
Value = null
|
|
||||||
};
|
|
||||||
|
|
||||||
[SettingSource("Metronome ticks", "Whether a metronome beat should play in the background")]
|
[SettingSource("Metronome ticks", "Whether a metronome beat should play in the background")]
|
||||||
public Bindable<bool> Metronome { get; } = new BindableBool(true);
|
public Bindable<bool> Metronome { get; } = new BindableBool(true);
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Pooling;
|
using osu.Framework.Graphics.Pooling;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -21,32 +20,36 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
public const int SPACING = 32;
|
public const int SPACING = 32;
|
||||||
public const double PREEMPT = 800;
|
public const double PREEMPT = 800;
|
||||||
|
|
||||||
public DrawablePool<FollowPoint> Pool;
|
public DrawablePool<FollowPoint>? Pool { private get; set; }
|
||||||
|
|
||||||
protected override void OnApply(FollowPointLifetimeEntry entry)
|
protected override void OnApply(FollowPointLifetimeEntry entry)
|
||||||
{
|
{
|
||||||
base.OnApply(entry);
|
base.OnApply(entry);
|
||||||
|
|
||||||
entry.Invalidated += onEntryInvalidated;
|
entry.Invalidated += scheduleRefresh;
|
||||||
refreshPoints();
|
|
||||||
|
// Our clock may not be correct at this point if `LoadComplete` has not run yet.
|
||||||
|
// Without a schedule, animations referencing FollowPoint's clock (see `IAnimationTimeReference`) would be incorrect on first pool usage.
|
||||||
|
scheduleRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnFree(FollowPointLifetimeEntry entry)
|
protected override void OnFree(FollowPointLifetimeEntry entry)
|
||||||
{
|
{
|
||||||
base.OnFree(entry);
|
base.OnFree(entry);
|
||||||
|
|
||||||
entry.Invalidated -= onEntryInvalidated;
|
entry.Invalidated -= scheduleRefresh;
|
||||||
// Return points to the pool.
|
// Return points to the pool.
|
||||||
ClearInternal(false);
|
ClearInternal(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onEntryInvalidated() => Scheduler.AddOnce(refreshPoints);
|
private void scheduleRefresh() => Scheduler.AddOnce(() =>
|
||||||
|
|
||||||
private void refreshPoints()
|
|
||||||
{
|
{
|
||||||
|
Debug.Assert(Pool != null);
|
||||||
|
|
||||||
ClearInternal(false);
|
ClearInternal(false);
|
||||||
|
|
||||||
var entry = Entry;
|
var entry = Entry;
|
||||||
|
|
||||||
if (entry?.End == null) return;
|
if (entry?.End == null) return;
|
||||||
|
|
||||||
OsuHitObject start = entry.Start;
|
OsuHitObject start = entry.Start;
|
||||||
@ -95,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
}
|
}
|
||||||
|
|
||||||
entry.LifetimeEnd = finalTransformEndTime;
|
entry.LifetimeEnd = finalTransformEndTime;
|
||||||
}
|
});
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Computes the fade time of follow point positioned between two hitobjects.
|
/// Computes the fade time of follow point positioned between two hitobjects.
|
||||||
|
@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Judgements;
|
using osu.Game.Rulesets.Osu.Judgements;
|
||||||
@ -47,12 +48,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ShakeContainer shakeContainer;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
{
|
{
|
||||||
scaleContainer = new Container
|
scaleContainer = new Container
|
||||||
{
|
{
|
||||||
@ -72,6 +75,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
shakeContainer = new ShakeContainer
|
||||||
|
{
|
||||||
|
ShakeDuration = 30,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
CirclePiece = new SkinnableDrawable(new OsuSkinComponent(CirclePieceComponent), _ => new MainCirclePiece())
|
CirclePiece = new SkinnableDrawable(new OsuSkinComponent(CirclePieceComponent), _ => new MainCirclePiece())
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -86,8 +95,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
Scale = new Vector2(4),
|
Scale = new Vector2(4),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
Size = HitArea.DrawSize;
|
Size = HitArea.DrawSize;
|
||||||
|
|
||||||
@ -123,6 +134,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Shake() => shakeContainer.Shake();
|
||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
{
|
{
|
||||||
Debug.Assert(HitObject.HitWindows != null);
|
Debug.Assert(HitObject.HitWindows != null);
|
||||||
@ -139,7 +152,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
if (result == HitResult.None || CheckHittable?.Invoke(this, Time.Current) == false)
|
if (result == HitResult.None || CheckHittable?.Invoke(this, Time.Current) == false)
|
||||||
{
|
{
|
||||||
Shake(Math.Abs(timeOffset) - HitObject.HitWindows.WindowFor(HitResult.Miss));
|
Shake();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,12 +204,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
// todo: temporary / arbitrary, used for lifetime optimisation.
|
// todo: temporary / arbitrary, used for lifetime optimisation.
|
||||||
this.Delay(800).FadeOut();
|
this.Delay(800).FadeOut();
|
||||||
|
|
||||||
// in the case of an early state change, the fade should be expedited to the current point in time.
|
|
||||||
if (HitStateUpdateTime < HitObject.StartTime)
|
|
||||||
ApproachCircle.FadeOut(50);
|
|
||||||
|
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
|
default:
|
||||||
|
ApproachCircle.FadeOut();
|
||||||
|
break;
|
||||||
|
|
||||||
case ArmedState.Idle:
|
case ArmedState.Idle:
|
||||||
HitArea.HitAction = null;
|
HitArea.HitAction = null;
|
||||||
break;
|
break;
|
||||||
|
@ -6,17 +6,18 @@
|
|||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Judgements;
|
using osu.Game.Rulesets.Osu.Judgements;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableOsuHitObject : DrawableHitObject<OsuHitObject>
|
public abstract class DrawableOsuHitObject : DrawableHitObject<OsuHitObject>
|
||||||
{
|
{
|
||||||
public readonly IBindable<Vector2> PositionBindable = new Bindable<Vector2>();
|
public readonly IBindable<Vector2> PositionBindable = new Bindable<Vector2>();
|
||||||
public readonly IBindable<int> StackHeightBindable = new Bindable<int>();
|
public readonly IBindable<int> StackHeightBindable = new Bindable<int>();
|
||||||
@ -34,8 +35,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Func<DrawableHitObject, double, bool> CheckHittable;
|
public Func<DrawableHitObject, double, bool> CheckHittable;
|
||||||
|
|
||||||
private ShakeContainer shakeContainer;
|
|
||||||
|
|
||||||
protected DrawableOsuHitObject(OsuHitObject hitObject)
|
protected DrawableOsuHitObject(OsuHitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
@ -45,12 +44,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Alpha = 0;
|
Alpha = 0;
|
||||||
|
|
||||||
base.AddInternal(shakeContainer = new ShakeContainer
|
|
||||||
{
|
|
||||||
ShakeDuration = 30,
|
|
||||||
RelativeSizeAxes = Axes.Both
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnApply()
|
protected override void OnApply()
|
||||||
@ -73,18 +66,32 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
ScaleBindable.UnbindFrom(HitObject.ScaleBindable);
|
ScaleBindable.UnbindFrom(HitObject.ScaleBindable);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forward all internal management to shakeContainer.
|
protected override void UpdateInitialTransforms()
|
||||||
// This is a bit ugly but we don't have the concept of InternalContent so it'll have to do for now. (https://github.com/ppy/osu-framework/issues/1690)
|
{
|
||||||
protected override void AddInternal(Drawable drawable) => shakeContainer.Add(drawable);
|
base.UpdateInitialTransforms();
|
||||||
protected override void ClearInternal(bool disposeChildren = true) => shakeContainer.Clear(disposeChildren);
|
|
||||||
protected override bool RemoveInternal(Drawable drawable, bool disposeImmediately) => shakeContainer.Remove(drawable, disposeImmediately);
|
// Dim should only be applied at a top level, as it will be implicitly applied to nested objects.
|
||||||
|
if (ParentHitObject == null)
|
||||||
|
{
|
||||||
|
// Of note, no one noticed this was missing for years, but it definitely feels like it should still exist.
|
||||||
|
// For now this is applied across all skins, and matches stable.
|
||||||
|
// For simplicity, dim colour is applied to the DrawableHitObject itself.
|
||||||
|
// We may need to make a nested container setup if this even causes a usage conflict (ie. with a mod).
|
||||||
|
this.FadeColour(new Color4(195, 195, 195, 255));
|
||||||
|
using (BeginDelayedSequence(InitialLifetimeOffset - OsuHitWindows.MISS_WINDOW))
|
||||||
|
this.FadeColour(Color4.White, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
|
protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
|
||||||
|
|
||||||
private OsuInputManager osuActionInputManager;
|
private OsuInputManager osuActionInputManager;
|
||||||
internal OsuInputManager OsuActionInputManager => osuActionInputManager ??= GetContainingInputManager() as OsuInputManager;
|
internal OsuInputManager OsuActionInputManager => osuActionInputManager ??= GetContainingInputManager() as OsuInputManager;
|
||||||
|
|
||||||
public virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength);
|
/// <summary>
|
||||||
|
/// Shake the hit object in case it was clicked far too early or late (aka "note lock").
|
||||||
|
/// </summary>
|
||||||
|
public virtual void Shake() { }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Causes this <see cref="DrawableOsuHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>.
|
/// Causes this <see cref="DrawableOsuHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>.
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Skinning;
|
using osu.Game.Rulesets.Osu.Skinning;
|
||||||
@ -34,6 +35,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
public SkinnableDrawable Body { get; private set; }
|
public SkinnableDrawable Body { get; private set; }
|
||||||
|
|
||||||
|
private ShakeContainer shakeContainer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A target container which can be used to add top level elements to the slider's display.
|
/// A target container which can be used to add top level elements to the slider's display.
|
||||||
/// Intended to be used for proxy purposes only.
|
/// Intended to be used for proxy purposes only.
|
||||||
@ -74,17 +77,26 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
|
{
|
||||||
|
shakeContainer = new ShakeContainer
|
||||||
|
{
|
||||||
|
ShakeDuration = 30,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
Body = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBody), _ => new DefaultSliderBody(), confineMode: ConfineMode.NoScaling),
|
Body = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBody), _ => new DefaultSliderBody(), confineMode: ConfineMode.NoScaling),
|
||||||
tailContainer = new Container<DrawableSliderTail> { RelativeSizeAxes = Axes.Both },
|
tailContainer = new Container<DrawableSliderTail> { RelativeSizeAxes = Axes.Both },
|
||||||
tickContainer = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
|
tickContainer = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
|
||||||
repeatContainer = new Container<DrawableSliderRepeat> { RelativeSizeAxes = Axes.Both },
|
repeatContainer = new Container<DrawableSliderRepeat> { RelativeSizeAxes = Axes.Both },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// slider head is not included in shake as it handles hit detection, and handles its own shaking.
|
||||||
headContainer = new Container<DrawableSliderHead> { RelativeSizeAxes = Axes.Both },
|
headContainer = new Container<DrawableSliderHead> { RelativeSizeAxes = Axes.Both },
|
||||||
OverlayElementContainer = new Container { RelativeSizeAxes = Axes.Both, },
|
OverlayElementContainer = new Container { RelativeSizeAxes = Axes.Both, },
|
||||||
Ball,
|
Ball,
|
||||||
slidingSample = new PausableSkinnableSound { Looping = true }
|
slidingSample = new PausableSkinnableSound { Looping = true }
|
||||||
};
|
});
|
||||||
|
|
||||||
PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
PositionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
||||||
StackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
StackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
||||||
@ -109,6 +121,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
PathVersion.BindTo(HitObject.Path.Version);
|
PathVersion.BindTo(HitObject.Path.Version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Shake() => shakeContainer.Shake();
|
||||||
|
|
||||||
protected override void OnFree()
|
protected override void OnFree()
|
||||||
{
|
{
|
||||||
base.OnFree();
|
base.OnFree();
|
||||||
|
@ -63,7 +63,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
pathVersion.BindTo(DrawableSlider.PathVersion);
|
pathVersion.BindTo(DrawableSlider.PathVersion);
|
||||||
|
|
||||||
OnShake = DrawableSlider.Shake;
|
|
||||||
CheckHittable = (d, t) => DrawableSlider.CheckHittable?.Invoke(d, t) ?? true;
|
CheckHittable = (d, t) => DrawableSlider.CheckHittable?.Invoke(d, t) ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,9 +95,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
return result.IsHit() ? HitResult.LargeTickHit : HitResult.LargeTickMiss;
|
return result.IsHit() ? HitResult.LargeTickHit : HitResult.LargeTickMiss;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Action<double> OnShake;
|
public override void Shake()
|
||||||
|
{
|
||||||
public override void Shake(double maximumLength) => OnShake?.Invoke(maximumLength);
|
base.Shake();
|
||||||
|
DrawableSlider.Shake();
|
||||||
|
}
|
||||||
|
|
||||||
private void updatePosition()
|
private void updatePosition()
|
||||||
{
|
{
|
||||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||||
|
|
||||||
InternalChild = scaleContainer = new Container
|
AddInternal(scaleContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
},
|
},
|
||||||
Arrow = new ReverseArrowPiece(),
|
Arrow = new ReverseArrowPiece(),
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
{
|
{
|
||||||
scaleContainer = new Container
|
scaleContainer = new Container
|
||||||
{
|
{
|
||||||
@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty())
|
CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
InternalChild = scaleContainer = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderScorePoint), _ => new CircularContainer
|
AddInternal(scaleContainer = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderScorePoint), _ => new CircularContainer
|
||||||
{
|
{
|
||||||
Masking = true,
|
Masking = true,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
};
|
});
|
||||||
|
|
||||||
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
ScaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue));
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration;
|
public override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Apply a judgement result.
|
/// Apply a judgement result.
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
// This is so on repeats ticks don't appear too late to be visually processed by the player.
|
// This is so on repeats ticks don't appear too late to be visually processed by the player.
|
||||||
offset = 200;
|
offset = 200;
|
||||||
else
|
else
|
||||||
offset = TimeFadeIn * 0.66f;
|
offset = TimePreempt * 0.66f;
|
||||||
|
|
||||||
TimePreempt = (StartTime - SpanStartTime) / 2 + offset;
|
TimePreempt = (StartTime - SpanStartTime) / 2 + offset;
|
||||||
}
|
}
|
||||||
|
@ -80,6 +80,9 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
LeftButton,
|
LeftButton,
|
||||||
|
|
||||||
[Description("Right button")]
|
[Description("Right button")]
|
||||||
RightButton
|
RightButton,
|
||||||
|
|
||||||
|
[Description("Smoke")]
|
||||||
|
Smoke,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -29,6 +27,7 @@ using osu.Game.Rulesets.Osu.Mods;
|
|||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Replays;
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
using osu.Game.Rulesets.Osu.Scoring;
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning.Argon;
|
||||||
using osu.Game.Rulesets.Osu.Skinning.Legacy;
|
using osu.Game.Rulesets.Osu.Skinning.Legacy;
|
||||||
using osu.Game.Rulesets.Osu.Statistics;
|
using osu.Game.Rulesets.Osu.Statistics;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
@ -44,7 +43,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
{
|
{
|
||||||
public class OsuRuleset : Ruleset, ILegacyRuleset
|
public class OsuRuleset : Ruleset, ILegacyRuleset
|
||||||
{
|
{
|
||||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableOsuRuleset(this, beatmap, mods);
|
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod>? mods = null) => new DrawableOsuRuleset(this, beatmap, mods);
|
||||||
|
|
||||||
public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor();
|
public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor();
|
||||||
|
|
||||||
@ -60,6 +59,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
{
|
{
|
||||||
new KeyBinding(InputKey.Z, OsuAction.LeftButton),
|
new KeyBinding(InputKey.Z, OsuAction.LeftButton),
|
||||||
new KeyBinding(InputKey.X, OsuAction.RightButton),
|
new KeyBinding(InputKey.X, OsuAction.RightButton),
|
||||||
|
new KeyBinding(InputKey.C, OsuAction.Smoke),
|
||||||
new KeyBinding(InputKey.MouseLeft, OsuAction.LeftButton),
|
new KeyBinding(InputKey.MouseLeft, OsuAction.LeftButton),
|
||||||
new KeyBinding(InputKey.MouseRight, OsuAction.RightButton),
|
new KeyBinding(InputKey.MouseRight, OsuAction.RightButton),
|
||||||
};
|
};
|
||||||
@ -233,13 +233,25 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
|
|
||||||
public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this);
|
public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this);
|
||||||
|
|
||||||
public override ISkin CreateLegacySkinProvider(ISkin skin, IBeatmap beatmap) => new OsuLegacySkinTransformer(skin);
|
public override ISkin? CreateSkinTransformer(ISkin skin, IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
switch (skin)
|
||||||
|
{
|
||||||
|
case LegacySkin:
|
||||||
|
return new OsuLegacySkinTransformer(skin);
|
||||||
|
|
||||||
|
case ArgonSkin:
|
||||||
|
return new OsuArgonSkinTransformer(skin);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public int LegacyID => 0;
|
public int LegacyID => 0;
|
||||||
|
|
||||||
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame();
|
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame();
|
||||||
|
|
||||||
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo);
|
public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new OsuRulesetConfigManager(settings, RulesetInfo);
|
||||||
|
|
||||||
protected override IEnumerable<HitResult> GetValidHitResults()
|
protected override IEnumerable<HitResult> GetValidHitResults()
|
||||||
{
|
{
|
||||||
|
@ -21,6 +21,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
SliderBall,
|
SliderBall,
|
||||||
SliderBody,
|
SliderBody,
|
||||||
SpinnerBody,
|
SpinnerBody,
|
||||||
|
CursorSmoke,
|
||||||
ApproachCircle,
|
ApproachCircle,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
Position = currentFrame.Position;
|
Position = currentFrame.Position;
|
||||||
if (currentFrame.MouseLeft) Actions.Add(OsuAction.LeftButton);
|
if (currentFrame.MouseLeft) Actions.Add(OsuAction.LeftButton);
|
||||||
if (currentFrame.MouseRight) Actions.Add(OsuAction.RightButton);
|
if (currentFrame.MouseRight) Actions.Add(OsuAction.RightButton);
|
||||||
|
if (currentFrame.Smoke) Actions.Add(OsuAction.Smoke);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
|
public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
|
||||||
@ -41,6 +42,8 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
state |= ReplayButtonState.Left1;
|
state |= ReplayButtonState.Left1;
|
||||||
if (Actions.Contains(OsuAction.RightButton))
|
if (Actions.Contains(OsuAction.RightButton))
|
||||||
state |= ReplayButtonState.Right1;
|
state |= ReplayButtonState.Right1;
|
||||||
|
if (Actions.Contains(OsuAction.Smoke))
|
||||||
|
state |= ReplayButtonState.Smoke;
|
||||||
|
|
||||||
return new LegacyReplayFrame(Time, Position.X, Position.Y, state);
|
return new LegacyReplayFrame(Time, Position.X, Position.Y, state);
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Scoring
|
namespace osu.Game.Rulesets.Osu.Scoring
|
||||||
{
|
{
|
||||||
public class OsuHitWindows : HitWindows
|
public class OsuHitWindows : HitWindows
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// osu! ruleset has a fixed miss window regardless of difficulty settings.
|
||||||
|
/// </summary>
|
||||||
|
public const double MISS_WINDOW = 400;
|
||||||
|
|
||||||
private static readonly DifficultyRange[] osu_ranges =
|
private static readonly DifficultyRange[] osu_ranges =
|
||||||
{
|
{
|
||||||
new DifficultyRange(HitResult.Great, 80, 50, 20),
|
new DifficultyRange(HitResult.Great, 80, 50, 20),
|
||||||
new DifficultyRange(HitResult.Ok, 140, 100, 60),
|
new DifficultyRange(HitResult.Ok, 140, 100, 60),
|
||||||
new DifficultyRange(HitResult.Meh, 200, 150, 100),
|
new DifficultyRange(HitResult.Meh, 200, 150, 100),
|
||||||
new DifficultyRange(HitResult.Miss, 400, 400, 400),
|
new DifficultyRange(HitResult.Miss, MISS_WINDOW, MISS_WINDOW, MISS_WINDOW),
|
||||||
};
|
};
|
||||||
|
|
||||||
public override bool IsHitResultAllowed(HitResult result)
|
public override bool IsHitResultAllowed(HitResult result)
|
||||||
|
80
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursor.cs
Normal file
80
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursor.cs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonCursor : OsuCursorSprite
|
||||||
|
{
|
||||||
|
public ArgonCursor()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
InternalChildren = new[]
|
||||||
|
{
|
||||||
|
ExpandTarget = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
BorderThickness = 6,
|
||||||
|
BorderColour = ColourInfo.GradientVertical(Colour4.FromHex("FC618F"), Colour4.FromHex("BB1A41")),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0.4f,
|
||||||
|
Colour = Colour4.FromHex("FC618F").Darken(0.6f),
|
||||||
|
},
|
||||||
|
new CircularContainer
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
BorderThickness = 2,
|
||||||
|
BorderColour = Color4.White.Opacity(0.8f),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
AlwaysPresent = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Scale = new Vector2(0.2f),
|
||||||
|
Colour = new Color4(255, 255, 255, 255),
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Radius = 20,
|
||||||
|
Colour = new Color4(171, 255, 255, 100),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursorTrail.cs
Normal file
29
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonCursorTrail.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 osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonCursorTrail : CursorTrail
|
||||||
|
{
|
||||||
|
protected override float IntervalMultiplier => 0.4f;
|
||||||
|
|
||||||
|
protected override float FadeExponent => 4;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(TextureStore textures)
|
||||||
|
{
|
||||||
|
Texture = textures.Get(@"Cursor/cursortrail");
|
||||||
|
Scale = new Vector2(0.8f / Texture.ScaleAdjust);
|
||||||
|
|
||||||
|
Blending = BlendingParameters.Additive;
|
||||||
|
|
||||||
|
Alpha = 0.8f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
71
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowCircle.cs
Normal file
71
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowCircle.cs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonFollowCircle : FollowCircle
|
||||||
|
{
|
||||||
|
public ArgonFollowCircle()
|
||||||
|
{
|
||||||
|
InternalChild = new CircularContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
BorderThickness = 4,
|
||||||
|
BorderColour = ColourInfo.GradientVertical(Colour4.FromHex("FC618F"), Colour4.FromHex("BB1A41")),
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
Colour = ColourInfo.GradientVertical(Colour4.FromHex("FC618F"), Colour4.FromHex("BB1A41")),
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0.3f,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSliderPress()
|
||||||
|
{
|
||||||
|
const float duration = 300f;
|
||||||
|
|
||||||
|
if (Precision.AlmostEquals(0, Alpha))
|
||||||
|
this.ScaleTo(1);
|
||||||
|
|
||||||
|
this.ScaleTo(DrawableSliderBall.FOLLOW_AREA, duration, Easing.OutQuint)
|
||||||
|
.FadeIn(duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSliderRelease()
|
||||||
|
{
|
||||||
|
const float duration = 150;
|
||||||
|
|
||||||
|
this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.2f, duration, Easing.OutQuint)
|
||||||
|
.FadeTo(0, duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSliderEnd()
|
||||||
|
{
|
||||||
|
const float duration = 300;
|
||||||
|
|
||||||
|
this.ScaleTo(1, duration, Easing.OutQuint)
|
||||||
|
.FadeOut(duration / 2, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSliderTick()
|
||||||
|
{
|
||||||
|
this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.08f, 40, Easing.OutQuint)
|
||||||
|
.Then()
|
||||||
|
.ScaleTo(DrawableSliderBall.FOLLOW_AREA, 200f, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSliderBreak()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
39
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowPoint.cs
Normal file
39
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowPoint.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonFollowPoint : CompositeDrawable
|
||||||
|
{
|
||||||
|
public ArgonFollowPoint()
|
||||||
|
{
|
||||||
|
Blending = BlendingParameters.Additive;
|
||||||
|
|
||||||
|
Colour = ColourInfo.GradientVertical(Colour4.FromHex("FC618F"), Colour4.FromHex("BB1A41"));
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new SpriteIcon
|
||||||
|
{
|
||||||
|
Icon = FontAwesome.Solid.ChevronRight,
|
||||||
|
Size = new Vector2(8),
|
||||||
|
Colour = OsuColour.Gray(0.2f),
|
||||||
|
},
|
||||||
|
new SpriteIcon
|
||||||
|
{
|
||||||
|
Icon = FontAwesome.Solid.ChevronRight,
|
||||||
|
Size = new Vector2(8),
|
||||||
|
X = 4,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
171
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs
Normal file
171
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
// 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.Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement
|
||||||
|
{
|
||||||
|
protected readonly HitResult Result;
|
||||||
|
|
||||||
|
protected SpriteText JudgementText { get; private set; } = null!;
|
||||||
|
|
||||||
|
private RingExplosion? ringExplosion;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuColour colours { get; set; } = null!;
|
||||||
|
|
||||||
|
public ArgonJudgementPiece(HitResult result)
|
||||||
|
{
|
||||||
|
Result = result;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
JudgementText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Text = Result.GetDescription().ToUpperInvariant(),
|
||||||
|
Colour = colours.ForHitResult(Result),
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Spacing = new Vector2(5, 0),
|
||||||
|
Font = OsuFont.Default.With(size: 20, weight: FontWeight.Bold),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Result.IsHit())
|
||||||
|
{
|
||||||
|
AddInternal(ringExplosion = new RingExplosion(Result)
|
||||||
|
{
|
||||||
|
Colour = colours.ForHitResult(Result),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plays the default animation for this judgement piece.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The base implementation only handles fade (for all result types) and misses.
|
||||||
|
/// Individual rulesets are recommended to implement their appropriate hit animations.
|
||||||
|
/// </remarks>
|
||||||
|
public virtual void PlayAnimation()
|
||||||
|
{
|
||||||
|
switch (Result)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
JudgementText
|
||||||
|
.ScaleTo(Vector2.One)
|
||||||
|
.ScaleTo(new Vector2(1.2f), 1800, Easing.OutQuint);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HitResult.Miss:
|
||||||
|
this.ScaleTo(1.6f);
|
||||||
|
this.ScaleTo(1, 100, Easing.In);
|
||||||
|
|
||||||
|
this.MoveTo(Vector2.Zero);
|
||||||
|
this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
|
||||||
|
|
||||||
|
this.RotateTo(0);
|
||||||
|
this.RotateTo(40, 800, Easing.InQuint);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.FadeOutFromOne(800);
|
||||||
|
|
||||||
|
ringExplosion?.PlayAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Drawable? GetAboveHitObjectsProxiedContent() => null;
|
||||||
|
|
||||||
|
private class RingExplosion : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly float travel = 52;
|
||||||
|
|
||||||
|
public RingExplosion(HitResult result)
|
||||||
|
{
|
||||||
|
const float thickness = 4;
|
||||||
|
|
||||||
|
const float small_size = 9;
|
||||||
|
const float large_size = 14;
|
||||||
|
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
Blending = BlendingParameters.Additive;
|
||||||
|
|
||||||
|
int countSmall = 0;
|
||||||
|
int countLarge = 0;
|
||||||
|
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case HitResult.Meh:
|
||||||
|
countSmall = 3;
|
||||||
|
travel *= 0.3f;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HitResult.Ok:
|
||||||
|
case HitResult.Good:
|
||||||
|
countSmall = 4;
|
||||||
|
travel *= 0.6f;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HitResult.Great:
|
||||||
|
case HitResult.Perfect:
|
||||||
|
countSmall = 4;
|
||||||
|
countLarge = 4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < countSmall; i++)
|
||||||
|
AddInternal(new RingPiece(thickness) { Size = new Vector2(small_size) });
|
||||||
|
|
||||||
|
for (int i = 0; i < countLarge; i++)
|
||||||
|
AddInternal(new RingPiece(thickness) { Size = new Vector2(large_size) });
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PlayAnimation()
|
||||||
|
{
|
||||||
|
foreach (var c in InternalChildren)
|
||||||
|
{
|
||||||
|
const float start_position_ratio = 0.3f;
|
||||||
|
|
||||||
|
float direction = RNG.NextSingle(0, 360);
|
||||||
|
float distance = RNG.NextSingle(travel / 2, travel);
|
||||||
|
|
||||||
|
c.MoveTo(new Vector2(
|
||||||
|
MathF.Cos(direction) * distance * start_position_ratio,
|
||||||
|
MathF.Sin(direction) * distance * start_position_ratio
|
||||||
|
));
|
||||||
|
|
||||||
|
c.MoveTo(new Vector2(
|
||||||
|
MathF.Cos(direction) * distance,
|
||||||
|
MathF.Sin(direction) * distance
|
||||||
|
), 600, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.FadeOutFromOne(1000, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
226
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs
Normal file
226
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonMainCirclePiece : CompositeDrawable
|
||||||
|
{
|
||||||
|
public const float BORDER_THICKNESS = (OsuHitObject.OBJECT_RADIUS * 2) * (2f / 58);
|
||||||
|
|
||||||
|
public const float GRADIENT_THICKNESS = BORDER_THICKNESS * 2.5f;
|
||||||
|
|
||||||
|
public const float OUTER_GRADIENT_SIZE = (OsuHitObject.OBJECT_RADIUS * 2) - BORDER_THICKNESS * 4;
|
||||||
|
|
||||||
|
public const float INNER_GRADIENT_SIZE = OUTER_GRADIENT_SIZE - GRADIENT_THICKNESS * 2;
|
||||||
|
public const float INNER_FILL_SIZE = INNER_GRADIENT_SIZE - GRADIENT_THICKNESS * 2;
|
||||||
|
|
||||||
|
private readonly Circle outerFill;
|
||||||
|
private readonly Circle outerGradient;
|
||||||
|
private readonly Circle innerGradient;
|
||||||
|
private readonly Circle innerFill;
|
||||||
|
|
||||||
|
private readonly RingPiece border;
|
||||||
|
private readonly OsuSpriteText number;
|
||||||
|
|
||||||
|
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||||
|
private readonly IBindable<int> indexInCurrentCombo = new Bindable<int>();
|
||||||
|
private readonly FlashPiece flash;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private DrawableHitObject drawableObject { get; set; } = null!;
|
||||||
|
|
||||||
|
public ArgonMainCirclePiece(bool withOuterFill)
|
||||||
|
{
|
||||||
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||||
|
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
outerFill = new Circle // renders white outer border and dark fill
|
||||||
|
{
|
||||||
|
Size = Size,
|
||||||
|
Alpha = withOuterFill ? 1 : 0,
|
||||||
|
},
|
||||||
|
outerGradient = new Circle // renders the outer bright gradient
|
||||||
|
{
|
||||||
|
Size = new Vector2(OUTER_GRADIENT_SIZE),
|
||||||
|
Alpha = 1,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
innerGradient = new Circle // renders the inner bright gradient
|
||||||
|
{
|
||||||
|
Size = new Vector2(INNER_GRADIENT_SIZE),
|
||||||
|
Alpha = 1,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
innerFill = new Circle // renders the inner dark fill
|
||||||
|
{
|
||||||
|
Size = new Vector2(INNER_FILL_SIZE),
|
||||||
|
Alpha = 1,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
number = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Font = OsuFont.Default.With(size: 52, weight: FontWeight.Bold),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Y = -2,
|
||||||
|
Text = @"1",
|
||||||
|
},
|
||||||
|
flash = new FlashPiece(),
|
||||||
|
border = new RingPiece(BORDER_THICKNESS),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
var drawableOsuObject = (DrawableOsuHitObject)drawableObject;
|
||||||
|
|
||||||
|
accentColour.BindTo(drawableObject.AccentColour);
|
||||||
|
indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
accentColour.BindValueChanged(colour =>
|
||||||
|
{
|
||||||
|
outerFill.Colour = innerFill.Colour = colour.NewValue.Darken(4);
|
||||||
|
outerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.1f));
|
||||||
|
innerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue.Darken(0.5f), colour.NewValue.Darken(0.6f));
|
||||||
|
flash.Colour = colour.NewValue;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
indexInCurrentCombo.BindValueChanged(index => number.Text = (index.NewValue + 1).ToString(), true);
|
||||||
|
|
||||||
|
drawableObject.ApplyCustomUpdateState += updateStateTransforms;
|
||||||
|
updateStateTransforms(drawableObject, drawableObject.State.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
||||||
|
{
|
||||||
|
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case ArmedState.Hit:
|
||||||
|
// Fade out time is at a maximum of 800. Must match `DrawableHitCircle`'s arbitrary lifetime spec.
|
||||||
|
const double fade_out_time = 800;
|
||||||
|
|
||||||
|
const double flash_in_duration = 150;
|
||||||
|
const double resize_duration = 400;
|
||||||
|
|
||||||
|
const float shrink_size = 0.8f;
|
||||||
|
|
||||||
|
// Animating with the number present is distracting.
|
||||||
|
// The number disappearing is hidden by the bright flash.
|
||||||
|
number.FadeOut(flash_in_duration / 2);
|
||||||
|
|
||||||
|
// The fill layers add too much noise during the explosion animation.
|
||||||
|
// They will be hidden by the additive effects anyway.
|
||||||
|
outerFill.FadeOut(flash_in_duration, Easing.OutQuint);
|
||||||
|
innerFill.FadeOut(flash_in_duration, Easing.OutQuint);
|
||||||
|
|
||||||
|
// The inner-most gradient should actually be resizing, but is only visible for
|
||||||
|
// a few milliseconds before it's hidden by the flash, so it's pointless overhead to bother with it.
|
||||||
|
innerGradient.FadeOut(flash_in_duration, Easing.OutQuint);
|
||||||
|
|
||||||
|
// The border is always white, but after hit it gets coloured by the skin/beatmap's colouring.
|
||||||
|
// A gradient is applied to make the border less prominent over the course of the animation.
|
||||||
|
// Without this, the border dominates the visual presence of the explosion animation in a bad way.
|
||||||
|
border.TransformTo(nameof
|
||||||
|
(BorderColour), ColourInfo.GradientVertical(
|
||||||
|
accentColour.Value.Opacity(0.5f),
|
||||||
|
accentColour.Value.Opacity(0)), fade_out_time);
|
||||||
|
|
||||||
|
// The outer ring shrinks immediately, but accounts for its thickness so it doesn't overlap the inner
|
||||||
|
// gradient layers.
|
||||||
|
border.ResizeTo(Size * shrink_size + new Vector2(border.BorderThickness), resize_duration, Easing.OutElasticHalf);
|
||||||
|
|
||||||
|
// The outer gradient is resize with a slight delay from the border.
|
||||||
|
// This is to give it a bomb-like effect, with the border "triggering" its animation when getting close.
|
||||||
|
using (BeginDelayedSequence(flash_in_duration / 12))
|
||||||
|
{
|
||||||
|
outerGradient.ResizeTo(outerGradient.Size * shrink_size, resize_duration, Easing.OutElasticHalf);
|
||||||
|
outerGradient
|
||||||
|
.FadeColour(Color4.White, 80)
|
||||||
|
.Then()
|
||||||
|
.FadeOut(flash_in_duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The flash layer starts white to give the wanted brightness, but is almost immediately
|
||||||
|
// recoloured to the accent colour. This would more correctly be done with two layers (one for the initial flash)
|
||||||
|
// but works well enough with the colour fade.
|
||||||
|
flash.FadeTo(1, flash_in_duration, Easing.OutQuint);
|
||||||
|
flash.FlashColour(accentColour.Value, fade_out_time, Easing.OutQuint);
|
||||||
|
|
||||||
|
this.FadeOut(fade_out_time, Easing.OutQuad);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (drawableObject.IsNotNull())
|
||||||
|
drawableObject.ApplyCustomUpdateState -= updateStateTransforms;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FlashPiece : Circle
|
||||||
|
{
|
||||||
|
public FlashPiece()
|
||||||
|
{
|
||||||
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS);
|
||||||
|
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
Alpha = 0;
|
||||||
|
Blending = BlendingParameters.Additive;
|
||||||
|
|
||||||
|
// The edge effect provides the fill due to not being rendered hollow.
|
||||||
|
Child.Alpha = 0;
|
||||||
|
Child.AlwaysPresent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Colour = Colour,
|
||||||
|
Radius = OsuHitObject.OBJECT_RADIUS * 1.2f,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs
Normal file
54
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonReverseArrow.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonReverseArrow : CompositeDrawable
|
||||||
|
{
|
||||||
|
private Bindable<Color4> accentColour = null!;
|
||||||
|
|
||||||
|
private SpriteIcon icon = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(DrawableHitObject hitObject)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
Size = new Vector2(40, 20),
|
||||||
|
Colour = Color4.White,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
icon = new SpriteIcon
|
||||||
|
{
|
||||||
|
Icon = FontAwesome.Solid.AngleDoubleRight,
|
||||||
|
Size = new Vector2(16),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
accentColour = hitObject.AccentColour.GetBoundCopy();
|
||||||
|
accentColour.BindValueChanged(accent => icon.Colour = accent.NewValue.Darken(4), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
109
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs
Normal file
109
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonSliderBall : CircularContainer
|
||||||
|
{
|
||||||
|
private readonly Box fill;
|
||||||
|
private readonly SpriteIcon icon;
|
||||||
|
|
||||||
|
private readonly Vector2 defaultIconScale = new Vector2(0.6f, 0.8f);
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private DrawableHitObject? parentObject { get; set; }
|
||||||
|
|
||||||
|
public ArgonSliderBall()
|
||||||
|
{
|
||||||
|
Size = new Vector2(ArgonMainCirclePiece.OUTER_GRADIENT_SIZE);
|
||||||
|
|
||||||
|
Masking = true;
|
||||||
|
|
||||||
|
BorderThickness = ArgonMainCirclePiece.GRADIENT_THICKNESS;
|
||||||
|
BorderColour = Color4.White;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
fill = new Box
|
||||||
|
{
|
||||||
|
Colour = ColourInfo.GradientVertical(Colour4.FromHex("FC618F"), Colour4.FromHex("BB1A41")),
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
icon = new SpriteIcon
|
||||||
|
{
|
||||||
|
Size = new Vector2(48),
|
||||||
|
Scale = defaultIconScale,
|
||||||
|
Icon = FontAwesome.Solid.AngleRight,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
if (parentObject != null)
|
||||||
|
{
|
||||||
|
parentObject.ApplyCustomUpdateState += updateStateTransforms;
|
||||||
|
updateStateTransforms(parentObject, parentObject.State.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState _)
|
||||||
|
{
|
||||||
|
// Gets called by slider ticks, tails, etc., leading to duplicated
|
||||||
|
// animations which in this case have no visual impact (due to
|
||||||
|
// instant fade) but may negatively affect performance
|
||||||
|
if (drawableObject is not DrawableSlider)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const float duration = 200;
|
||||||
|
const float icon_scale = 0.9f;
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(drawableObject.StateUpdateTime))
|
||||||
|
{
|
||||||
|
this.FadeInFromZero(duration, Easing.OutQuint);
|
||||||
|
icon.ScaleTo(0).Then().ScaleTo(defaultIconScale, duration, Easing.OutElasticHalf);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
|
||||||
|
{
|
||||||
|
this.FadeOut(duration, Easing.OutQuint);
|
||||||
|
icon.ScaleTo(defaultIconScale * icon_scale, duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
//undo rotation on layers which should not be rotated.
|
||||||
|
float appliedRotation = Parent.Rotation;
|
||||||
|
|
||||||
|
fill.Rotation = -appliedRotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (parentObject != null)
|
||||||
|
parentObject.ApplyCustomUpdateState -= updateStateTransforms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBody.cs
Normal file
40
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBody.cs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonSliderBody : PlaySliderBody
|
||||||
|
{
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
const float path_radius = ArgonMainCirclePiece.OUTER_GRADIENT_SIZE / 2;
|
||||||
|
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
AccentColourBindable.BindValueChanged(accent => BorderColour = accent.NewValue, true);
|
||||||
|
ScaleBindable.BindValueChanged(scale => PathRadius = path_radius * scale.NewValue, true);
|
||||||
|
|
||||||
|
// This border size thing is kind of weird, hey.
|
||||||
|
const float intended_thickness = ArgonMainCirclePiece.GRADIENT_THICKNESS / path_radius;
|
||||||
|
|
||||||
|
BorderSize = intended_thickness / Default.DrawableSliderPath.BORDER_PORTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Default.DrawableSliderPath CreateSliderPath() => new DrawableSliderPath();
|
||||||
|
|
||||||
|
private class DrawableSliderPath : Default.DrawableSliderPath
|
||||||
|
{
|
||||||
|
protected override Color4 ColourAt(float position)
|
||||||
|
{
|
||||||
|
if (CalculatedBorderPortion != 0f && position <= CalculatedBorderPortion)
|
||||||
|
return BorderColour;
|
||||||
|
|
||||||
|
return AccentColour.Darken(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonSliderScorePoint : CircularContainer
|
||||||
|
{
|
||||||
|
private Bindable<Color4> accentColour = null!;
|
||||||
|
|
||||||
|
private const float size = 12;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(DrawableHitObject hitObject)
|
||||||
|
{
|
||||||
|
Masking = true;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
Size = new Vector2(size);
|
||||||
|
BorderThickness = 3;
|
||||||
|
BorderColour = Color4.White;
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
AlwaysPresent = true,
|
||||||
|
Alpha = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
accentColour = hitObject.AccentColour.GetBoundCopy();
|
||||||
|
accentColour.BindValueChanged(accent => BorderColour = accent.NewValue, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
146
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs
Normal file
146
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinner.cs
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonSpinner : CompositeDrawable
|
||||||
|
{
|
||||||
|
private DrawableSpinner drawableSpinner = null!;
|
||||||
|
|
||||||
|
private OsuSpriteText bonusCounter = null!;
|
||||||
|
|
||||||
|
private Container spmContainer = null!;
|
||||||
|
private OsuSpriteText spmCounter = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(DrawableHitObject drawableHitObject)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
drawableSpinner = (DrawableSpinner)drawableHitObject;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
bonusCounter = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Alpha = 0,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Font = OsuFont.Default.With(size: 24),
|
||||||
|
Y = -120,
|
||||||
|
},
|
||||||
|
new ArgonSpinnerDisc
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
},
|
||||||
|
bonusCounter = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Alpha = 0,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Font = OsuFont.Default.With(size: 28, weight: FontWeight.Bold),
|
||||||
|
Y = -100,
|
||||||
|
},
|
||||||
|
spmContainer = new Container
|
||||||
|
{
|
||||||
|
Alpha = 0f,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Y = 60,
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
spmCounter = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Text = @"0",
|
||||||
|
Font = OsuFont.Default.With(size: 28, weight: FontWeight.SemiBold)
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Text = @"SPINS PER MINUTE",
|
||||||
|
Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold),
|
||||||
|
Y = 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private IBindable<double> gainedBonus = null!;
|
||||||
|
private IBindable<double> spinsPerMinute = null!;
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
gainedBonus = drawableSpinner.GainedBonus.GetBoundCopy();
|
||||||
|
gainedBonus.BindValueChanged(bonus =>
|
||||||
|
{
|
||||||
|
bonusCounter.Text = bonus.NewValue.ToString(NumberFormatInfo.InvariantInfo);
|
||||||
|
bonusCounter.FadeOutFromOne(1500);
|
||||||
|
bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint);
|
||||||
|
});
|
||||||
|
|
||||||
|
spinsPerMinute = drawableSpinner.SpinsPerMinute.GetBoundCopy();
|
||||||
|
spinsPerMinute.BindValueChanged(spm =>
|
||||||
|
{
|
||||||
|
spmCounter.Text = Math.Truncate(spm.NewValue).ToString(@"#0");
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
||||||
|
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (!spmContainer.IsPresent && drawableSpinner.Result?.TimeStarted != null)
|
||||||
|
fadeCounterOnTimeStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
||||||
|
{
|
||||||
|
if (!(drawableHitObject is DrawableSpinner))
|
||||||
|
return;
|
||||||
|
|
||||||
|
fadeCounterOnTimeStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fadeCounterOnTimeStart()
|
||||||
|
{
|
||||||
|
if (drawableSpinner.Result?.TimeStarted is double startTime)
|
||||||
|
{
|
||||||
|
using (BeginAbsoluteSequence(startTime))
|
||||||
|
spmContainer.FadeIn(drawableSpinner.HitObject.TimeFadeIn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (drawableSpinner.IsNotNull())
|
||||||
|
drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
247
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerDisc.cs
Normal file
247
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerDisc.cs
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonSpinnerDisc : CompositeDrawable
|
||||||
|
{
|
||||||
|
private const float initial_scale = 1f;
|
||||||
|
private const float idle_alpha = 0.2f;
|
||||||
|
private const float tracking_alpha = 0.4f;
|
||||||
|
|
||||||
|
private const float idle_centre_size = 80f;
|
||||||
|
private const float tracking_centre_size = 40f;
|
||||||
|
|
||||||
|
private DrawableSpinner drawableSpinner = null!;
|
||||||
|
|
||||||
|
private readonly BindableBool complete = new BindableBool();
|
||||||
|
|
||||||
|
private int wholeRotationCount;
|
||||||
|
|
||||||
|
private bool checkNewRotationCount
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
int rotations = (int)(drawableSpinner.Result.RateAdjustedRotation / 360);
|
||||||
|
|
||||||
|
if (wholeRotationCount == rotations) return false;
|
||||||
|
|
||||||
|
wholeRotationCount = rotations;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Container disc = null!;
|
||||||
|
private Container centre = null!;
|
||||||
|
private CircularContainer fill = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(DrawableHitObject drawableHitObject)
|
||||||
|
{
|
||||||
|
drawableSpinner = (DrawableSpinner)drawableHitObject;
|
||||||
|
|
||||||
|
// we are slightly bigger than our parent, to clip the top and bottom of the circle
|
||||||
|
// this should probably be revisited when scaled spinners are a thing.
|
||||||
|
Scale = new Vector2(initial_scale);
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
disc = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
fill = new CircularContainer
|
||||||
|
{
|
||||||
|
Name = @"Fill",
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Colour = Colour4.FromHex("FC618F").Opacity(1f),
|
||||||
|
Radius = 40,
|
||||||
|
},
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0f,
|
||||||
|
AlwaysPresent = true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new CircularContainer
|
||||||
|
{
|
||||||
|
Name = @"Ring",
|
||||||
|
Masking = true,
|
||||||
|
BorderColour = Color4.White,
|
||||||
|
BorderThickness = 5,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
AlwaysPresent = true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new ArgonSpinnerTicks(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
centre = new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(idle_centre_size),
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
new RingPiece(10)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Size = new Vector2(0.8f),
|
||||||
|
},
|
||||||
|
new RingPiece(3)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Size = new Vector2(1f),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
||||||
|
|
||||||
|
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
complete.Value = Time.Current >= drawableSpinner.Result.TimeCompleted;
|
||||||
|
|
||||||
|
if (complete.Value)
|
||||||
|
{
|
||||||
|
if (checkNewRotationCount)
|
||||||
|
{
|
||||||
|
fill.FinishTransforms(false, nameof(Alpha));
|
||||||
|
fill
|
||||||
|
.FadeTo(tracking_alpha + 0.2f, 60, Easing.OutExpo)
|
||||||
|
.Then()
|
||||||
|
.FadeTo(tracking_alpha, 250, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fill.Alpha = (float)Interpolation.Damp(fill.Alpha, drawableSpinner.RotationTracker.Tracking ? tracking_alpha : idle_alpha, 0.98f, (float)Math.Abs(Clock.ElapsedFrameTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (centre.Width == idle_centre_size && drawableSpinner.Result?.TimeStarted != null)
|
||||||
|
updateCentrePieceSize();
|
||||||
|
|
||||||
|
const float initial_fill_scale = 0.1f;
|
||||||
|
float targetScale = initial_fill_scale + (0.98f - initial_fill_scale) * drawableSpinner.Progress;
|
||||||
|
|
||||||
|
fill.Scale = new Vector2((float)Interpolation.Lerp(fill.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1)));
|
||||||
|
disc.Rotation = drawableSpinner.RotationTracker.Rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
||||||
|
{
|
||||||
|
if (!(drawableHitObject is DrawableSpinner))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Spinner spinner = drawableSpinner.HitObject;
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
|
||||||
|
{
|
||||||
|
this.ScaleTo(initial_scale);
|
||||||
|
this.RotateTo(0);
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(spinner.TimePreempt / 2))
|
||||||
|
{
|
||||||
|
// constant ambient rotation to give the spinner "spinning" character.
|
||||||
|
this.RotateTo((float)(25 * spinner.Duration / 2000), spinner.TimePreempt + spinner.Duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(spinner.TimePreempt + spinner.Duration + drawableHitObject.Result.TimeOffset))
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case ArmedState.Hit:
|
||||||
|
this.ScaleTo(initial_scale * 1.2f, 320, Easing.Out);
|
||||||
|
this.RotateTo(Rotation + 180, 320);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ArmedState.Miss:
|
||||||
|
this.ScaleTo(initial_scale * 0.8f, 320, Easing.In);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
|
||||||
|
{
|
||||||
|
centre.ScaleTo(0);
|
||||||
|
disc.ScaleTo(0);
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(spinner.TimePreempt / 2))
|
||||||
|
{
|
||||||
|
centre.ScaleTo(0.3f, spinner.TimePreempt / 4, Easing.OutQuint);
|
||||||
|
disc.ScaleTo(0.2f, spinner.TimePreempt / 4, Easing.OutQuint);
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(spinner.TimePreempt / 2))
|
||||||
|
{
|
||||||
|
centre.ScaleTo(0.8f, spinner.TimePreempt / 2, Easing.OutQuint);
|
||||||
|
disc.ScaleTo(1, spinner.TimePreempt / 2, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drawableSpinner.Result?.TimeStarted != null)
|
||||||
|
updateCentrePieceSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCentrePieceSize()
|
||||||
|
{
|
||||||
|
Debug.Assert(drawableSpinner.Result?.TimeStarted != null);
|
||||||
|
|
||||||
|
Spinner spinner = drawableSpinner.HitObject;
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(drawableSpinner.Result.TimeStarted.Value))
|
||||||
|
centre.ResizeTo(new Vector2(tracking_centre_size), spinner.TimePreempt / 2, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (drawableSpinner.IsNotNull())
|
||||||
|
drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerTicks.cs
Normal file
61
osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSpinnerTicks.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// 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.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class ArgonSpinnerTicks : CompositeDrawable
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
const float count = 25;
|
||||||
|
|
||||||
|
for (float i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
AddInternal(new CircularContainer
|
||||||
|
{
|
||||||
|
RelativePositionAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 5,
|
||||||
|
BorderColour = Color4.White,
|
||||||
|
BorderThickness = 2f,
|
||||||
|
Size = new Vector2(30, 5),
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Position = new Vector2(
|
||||||
|
0.5f + MathF.Sin(i / count * 2 * MathF.PI) / 2 * 0.75f,
|
||||||
|
0.5f + MathF.Cos(i / count * 2 * MathF.PI) / 2 * 0.75f
|
||||||
|
),
|
||||||
|
Rotation = -i / count * 360 - 120,
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Colour = Colour4.White.Opacity(0.2f),
|
||||||
|
Radius = 30,
|
||||||
|
},
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
AlwaysPresent = true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||||
|
{
|
||||||
|
public class OsuArgonSkinTransformer : SkinTransformer
|
||||||
|
{
|
||||||
|
public OsuArgonSkinTransformer(ISkin skin)
|
||||||
|
: base(skin)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Drawable? GetDrawableComponent(ISkinComponent component)
|
||||||
|
{
|
||||||
|
switch (component)
|
||||||
|
{
|
||||||
|
case GameplaySkinComponent<HitResult> resultComponent:
|
||||||
|
return new ArgonJudgementPiece(resultComponent.Component);
|
||||||
|
|
||||||
|
case OsuSkinComponent osuComponent:
|
||||||
|
// TODO: Once everything is finalised, consider throwing UnsupportedSkinComponentException on missing entries.
|
||||||
|
switch (osuComponent.Component)
|
||||||
|
{
|
||||||
|
case OsuSkinComponents.HitCircle:
|
||||||
|
return new ArgonMainCirclePiece(true);
|
||||||
|
|
||||||
|
case OsuSkinComponents.SliderHeadHitCircle:
|
||||||
|
return new ArgonMainCirclePiece(false);
|
||||||
|
|
||||||
|
case OsuSkinComponents.SliderBody:
|
||||||
|
return new ArgonSliderBody();
|
||||||
|
|
||||||
|
case OsuSkinComponents.SliderBall:
|
||||||
|
return new ArgonSliderBall();
|
||||||
|
|
||||||
|
case OsuSkinComponents.SliderFollowCircle:
|
||||||
|
return new ArgonFollowCircle();
|
||||||
|
|
||||||
|
case OsuSkinComponents.SliderScorePoint:
|
||||||
|
return new ArgonSliderScorePoint();
|
||||||
|
|
||||||
|
case OsuSkinComponents.SpinnerBody:
|
||||||
|
return new ArgonSpinner();
|
||||||
|
|
||||||
|
case OsuSkinComponents.ReverseArrow:
|
||||||
|
return new ArgonReverseArrow();
|
||||||
|
|
||||||
|
case OsuSkinComponents.FollowPoint:
|
||||||
|
return new ArgonFollowPoint();
|
||||||
|
|
||||||
|
case OsuSkinComponents.Cursor:
|
||||||
|
return new ArgonCursor();
|
||||||
|
|
||||||
|
case OsuSkinComponents.CursorTrail:
|
||||||
|
return new ArgonCursorTrail();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.GetDrawableComponent(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||||
|
{
|
||||||
|
public class DefaultSmokeSegment : SmokeSegment
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(TextureStore textures)
|
||||||
|
{
|
||||||
|
// ISkinSource doesn't currently fallback to global textures.
|
||||||
|
// We might want to change this in the future if the intention is to allow the user to skin this as per legacy skins.
|
||||||
|
Texture = textures.Get("Gameplay/osu/cursor-smoke");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,8 +10,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
{
|
{
|
||||||
public abstract class DrawableSliderPath : SmoothPath
|
public abstract class DrawableSliderPath : SmoothPath
|
||||||
{
|
{
|
||||||
protected const float BORDER_PORTION = 0.128f;
|
public const float BORDER_PORTION = 0.128f;
|
||||||
protected const float GRADIENT_PORTION = 1 - BORDER_PORTION;
|
public const float GRADIENT_PORTION = 1 - BORDER_PORTION;
|
||||||
|
|
||||||
private const float border_max_size = 8f;
|
private const float border_max_size = 8f;
|
||||||
private const float border_min_size = 0f;
|
private const float border_min_size = 0f;
|
||||||
|
@ -16,9 +16,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
{
|
{
|
||||||
public abstract class PlaySliderBody : SnakingSliderBody
|
public abstract class PlaySliderBody : SnakingSliderBody
|
||||||
{
|
{
|
||||||
private IBindable<float> scaleBindable;
|
protected IBindable<float> ScaleBindable { get; private set; } = null!;
|
||||||
|
|
||||||
|
protected IBindable<Color4> AccentColourBindable { get; private set; } = null!;
|
||||||
|
|
||||||
private IBindable<int> pathVersion;
|
private IBindable<int> pathVersion;
|
||||||
private IBindable<Color4> accentColour;
|
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private OsuRulesetConfigManager config { get; set; }
|
private OsuRulesetConfigManager config { get; set; }
|
||||||
@ -30,14 +32,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
{
|
{
|
||||||
var drawableSlider = (DrawableSlider)drawableObject;
|
var drawableSlider = (DrawableSlider)drawableObject;
|
||||||
|
|
||||||
scaleBindable = drawableSlider.ScaleBindable.GetBoundCopy();
|
ScaleBindable = drawableSlider.ScaleBindable.GetBoundCopy();
|
||||||
scaleBindable.BindValueChanged(scale => PathRadius = OsuHitObject.OBJECT_RADIUS * scale.NewValue, true);
|
ScaleBindable.BindValueChanged(scale => PathRadius = OsuHitObject.OBJECT_RADIUS * scale.NewValue, true);
|
||||||
|
|
||||||
pathVersion = drawableSlider.PathVersion.GetBoundCopy();
|
pathVersion = drawableSlider.PathVersion.GetBoundCopy();
|
||||||
pathVersion.BindValueChanged(_ => Refresh());
|
pathVersion.BindValueChanged(_ => Refresh());
|
||||||
|
|
||||||
accentColour = drawableObject.AccentColour.GetBoundCopy();
|
AccentColourBindable = drawableObject.AccentColour.GetBoundCopy();
|
||||||
accentColour.BindValueChanged(accent => AccentColour = GetBodyAccentColour(skin, accent.NewValue), true);
|
AccentColourBindable.BindValueChanged(accent => AccentColour = GetBodyAccentColour(skin, accent.NewValue), true);
|
||||||
|
|
||||||
config?.BindWith(OsuRulesetSetting.SnakingInSliders, SnakingIn);
|
config?.BindWith(OsuRulesetSetting.SnakingInSliders, SnakingIn);
|
||||||
config?.BindWith(OsuRulesetSetting.SnakingOutSliders, configSnakingOut);
|
config?.BindWith(OsuRulesetSetting.SnakingOutSliders, configSnakingOut);
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.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;
|
||||||
@ -14,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
{
|
{
|
||||||
public class RingPiece : CircularContainer
|
public class RingPiece : CircularContainer
|
||||||
{
|
{
|
||||||
public RingPiece()
|
public RingPiece(float thickness = 9)
|
||||||
{
|
{
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||||
|
|
||||||
@ -22,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
Masking = true;
|
Masking = true;
|
||||||
BorderThickness = 9; // roughly matches slider borders and makes stacked circles distinctly visible from each other.
|
BorderThickness = thickness;
|
||||||
BorderColour = Color4.White;
|
BorderColour = Color4.White;
|
||||||
|
|
||||||
Child = new Box
|
Child = new Box
|
||||||
|
@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
|
|
||||||
var topProvider = source.FindProvider(s => s.GetTexture("spinner-top") != null);
|
var topProvider = source.FindProvider(s => s.GetTexture("spinner-top") != null);
|
||||||
|
|
||||||
if (topProvider is LegacySkinTransformer transformer && !(transformer.Skin is DefaultLegacySkin))
|
if (topProvider is ISkinTransformer transformer && !(transformer.Skin is DefaultLegacySkin))
|
||||||
{
|
{
|
||||||
AddInternal(ApproachCircle = new Sprite
|
AddInternal(ApproachCircle = new Sprite
|
||||||
{
|
{
|
||||||
|
19
osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmokeSegment.cs
Normal file
19
osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySmokeSegment.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||||
|
{
|
||||||
|
public class LegacySmokeSegment : SmokeSegment
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(ISkinSource skin)
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Texture = skin.GetTexture("cursor-smoke");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -106,6 +106,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
case OsuSkinComponents.CursorSmoke:
|
||||||
|
if (GetTexture("cursor-smoke") != null)
|
||||||
|
return new LegacySmokeSegment();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
case OsuSkinComponents.HitCircleText:
|
case OsuSkinComponents.HitCircleText:
|
||||||
if (!this.HasFont(LegacyFont.HitCircle))
|
if (!this.HasFont(LegacyFont.HitCircle))
|
||||||
return null;
|
return null;
|
||||||
|
366
osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs
Normal file
366
osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
|
using osu.Framework.Graphics.Rendering;
|
||||||
|
using osu.Framework.Graphics.Rendering.Vertices;
|
||||||
|
using osu.Framework.Graphics.Shaders;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Skinning
|
||||||
|
{
|
||||||
|
public abstract class SmokeSegment : Drawable, ITexturedShaderDrawable
|
||||||
|
{
|
||||||
|
private const int max_point_count = 18_000;
|
||||||
|
|
||||||
|
// fade anim values
|
||||||
|
private const double initial_fade_out_duration = 4000;
|
||||||
|
|
||||||
|
private const double re_fade_in_speed = 3;
|
||||||
|
private const double re_fade_in_duration = 50;
|
||||||
|
|
||||||
|
private const double final_fade_out_speed = 2;
|
||||||
|
private const double final_fade_out_duration = 8000;
|
||||||
|
|
||||||
|
private const float initial_alpha = 0.6f;
|
||||||
|
private const float re_fade_in_alpha = 1f;
|
||||||
|
|
||||||
|
private readonly int rotationSeed = RNG.Next();
|
||||||
|
|
||||||
|
// scale anim values
|
||||||
|
private const double scale_duration = 1200;
|
||||||
|
|
||||||
|
private const float initial_scale = 0.65f;
|
||||||
|
private const float final_scale = 1f;
|
||||||
|
|
||||||
|
// rotation anim values
|
||||||
|
private const double rotation_duration = 500;
|
||||||
|
|
||||||
|
private const float max_rotation = 0.25f;
|
||||||
|
|
||||||
|
public IShader? TextureShader { get; private set; }
|
||||||
|
public IShader? RoundedTextureShader { get; private set; }
|
||||||
|
|
||||||
|
protected Texture? Texture { get; set; }
|
||||||
|
|
||||||
|
private float radius => Texture?.DisplayWidth * 0.165f ?? 3;
|
||||||
|
|
||||||
|
protected readonly List<SmokePoint> SmokePoints = new List<SmokePoint>();
|
||||||
|
|
||||||
|
private float pointInterval => radius * 7f / 8;
|
||||||
|
|
||||||
|
private double smokeStartTime { get; set; } = double.MinValue;
|
||||||
|
|
||||||
|
private double smokeEndTime { get; set; } = double.MaxValue;
|
||||||
|
|
||||||
|
private float totalDistance;
|
||||||
|
private Vector2? lastPosition;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(ShaderManager shaders)
|
||||||
|
{
|
||||||
|
RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED);
|
||||||
|
TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
LifetimeStart = smokeStartTime = Time.Current;
|
||||||
|
|
||||||
|
totalDistance = pointInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector2 nextPointDirection()
|
||||||
|
{
|
||||||
|
float angle = RNG.NextSingle(0, 2 * MathF.PI);
|
||||||
|
return new Vector2(MathF.Sin(angle), -MathF.Cos(angle));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddPosition(Vector2 position, double time)
|
||||||
|
{
|
||||||
|
lastPosition ??= position;
|
||||||
|
|
||||||
|
float delta = (position - (Vector2)lastPosition).LengthFast;
|
||||||
|
totalDistance += delta;
|
||||||
|
int count = (int)(totalDistance / pointInterval);
|
||||||
|
|
||||||
|
if (count > 0)
|
||||||
|
{
|
||||||
|
Vector2 increment = position - (Vector2)lastPosition;
|
||||||
|
increment.NormalizeFast();
|
||||||
|
|
||||||
|
Vector2 pointPos = (pointInterval - (totalDistance - delta)) * increment + (Vector2)lastPosition;
|
||||||
|
increment *= pointInterval;
|
||||||
|
|
||||||
|
if (SmokePoints.Count > 0 && SmokePoints[^1].Time > time)
|
||||||
|
{
|
||||||
|
int index = ~SmokePoints.BinarySearch(new SmokePoint { Time = time }, new SmokePoint.UpperBoundComparer());
|
||||||
|
SmokePoints.RemoveRange(index, SmokePoints.Count - index);
|
||||||
|
}
|
||||||
|
|
||||||
|
totalDistance %= pointInterval;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
SmokePoints.Add(new SmokePoint
|
||||||
|
{
|
||||||
|
Position = pointPos,
|
||||||
|
Time = time,
|
||||||
|
Direction = nextPointDirection(),
|
||||||
|
});
|
||||||
|
|
||||||
|
pointPos += increment;
|
||||||
|
}
|
||||||
|
|
||||||
|
Invalidate(Invalidation.DrawNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastPosition = position;
|
||||||
|
|
||||||
|
if (SmokePoints.Count >= max_point_count)
|
||||||
|
FinishDrawing(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FinishDrawing(double time)
|
||||||
|
{
|
||||||
|
smokeEndTime = time;
|
||||||
|
|
||||||
|
double initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, smokeEndTime - smokeStartTime);
|
||||||
|
LifetimeEnd = smokeEndTime + final_fade_out_duration + initialFadeOutDurationTrunc / re_fade_in_speed + initialFadeOutDurationTrunc / final_fade_out_speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override DrawNode CreateDrawNode() => new SmokeDrawNode(this);
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
Invalidate(Invalidation.DrawNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected struct SmokePoint
|
||||||
|
{
|
||||||
|
public Vector2 Position;
|
||||||
|
public double Time;
|
||||||
|
public Vector2 Direction;
|
||||||
|
|
||||||
|
public struct UpperBoundComparer : IComparer<SmokePoint>
|
||||||
|
{
|
||||||
|
public int Compare(SmokePoint x, SmokePoint target)
|
||||||
|
{
|
||||||
|
// By returning -1 when the target value is equal to x, guarantees that the
|
||||||
|
// element at BinarySearch's returned index will always be the first element
|
||||||
|
// larger. Since 0 is never returned, the target is never "found", so the return
|
||||||
|
// value will be the index's complement.
|
||||||
|
|
||||||
|
return x.Time > target.Time ? 1 : -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class SmokeDrawNode : TexturedShaderDrawNode
|
||||||
|
{
|
||||||
|
protected new SmokeSegment Source => (SmokeSegment)base.Source;
|
||||||
|
|
||||||
|
protected double SmokeStartTime { get; private set; }
|
||||||
|
protected double SmokeEndTime { get; private set; }
|
||||||
|
protected double CurrentTime { get; private set; }
|
||||||
|
|
||||||
|
private readonly List<SmokePoint> points = new List<SmokePoint>();
|
||||||
|
private IVertexBatch<TexturedVertex2D>? quadBatch;
|
||||||
|
private float radius;
|
||||||
|
private Vector2 drawSize;
|
||||||
|
private Texture? texture;
|
||||||
|
|
||||||
|
// anim calculation vars (color, scale, direction)
|
||||||
|
private double initialFadeOutDurationTrunc;
|
||||||
|
private double firstVisiblePointTime;
|
||||||
|
|
||||||
|
private double initialFadeOutTime;
|
||||||
|
private double reFadeInTime;
|
||||||
|
private double finalFadeOutTime;
|
||||||
|
|
||||||
|
private Random rotationRNG = new Random();
|
||||||
|
|
||||||
|
public SmokeDrawNode(ITexturedShaderDrawable source)
|
||||||
|
: base(source)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ApplyState()
|
||||||
|
{
|
||||||
|
base.ApplyState();
|
||||||
|
|
||||||
|
points.Clear();
|
||||||
|
points.AddRange(Source.SmokePoints);
|
||||||
|
|
||||||
|
radius = Source.radius;
|
||||||
|
drawSize = Source.DrawSize;
|
||||||
|
texture = Source.Texture;
|
||||||
|
|
||||||
|
SmokeStartTime = Source.smokeStartTime;
|
||||||
|
SmokeEndTime = Source.smokeEndTime;
|
||||||
|
CurrentTime = Source.Clock.CurrentTime;
|
||||||
|
|
||||||
|
rotationRNG = new Random(Source.rotationSeed);
|
||||||
|
|
||||||
|
initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime);
|
||||||
|
firstVisiblePointTime = SmokeEndTime - initialFadeOutDurationTrunc;
|
||||||
|
|
||||||
|
initialFadeOutTime = CurrentTime;
|
||||||
|
reFadeInTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTime * (1 - 1 / re_fade_in_speed);
|
||||||
|
finalFadeOutTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTime * (1 - 1 / final_fade_out_speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed override void Draw(IRenderer renderer)
|
||||||
|
{
|
||||||
|
base.Draw(renderer);
|
||||||
|
|
||||||
|
if (points.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
quadBatch ??= renderer.CreateQuadBatch<TexturedVertex2D>(max_point_count / 10, 10);
|
||||||
|
texture ??= renderer.WhitePixel;
|
||||||
|
RectangleF textureRect = texture.GetTextureRect();
|
||||||
|
|
||||||
|
var shader = GetAppropriateShader(renderer);
|
||||||
|
|
||||||
|
renderer.SetBlend(BlendingParameters.Additive);
|
||||||
|
renderer.PushLocalMatrix(DrawInfo.Matrix);
|
||||||
|
|
||||||
|
shader.Bind();
|
||||||
|
texture.Bind();
|
||||||
|
|
||||||
|
foreach (var point in points)
|
||||||
|
drawPointQuad(point, textureRect);
|
||||||
|
|
||||||
|
shader.Unbind();
|
||||||
|
renderer.PopLocalMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Color4 ColourAtPosition(Vector2 localPos) => DrawColourInfo.Colour.HasSingleColour
|
||||||
|
? ((SRGBColour)DrawColourInfo.Colour).Linear
|
||||||
|
: DrawColourInfo.Colour.Interpolate(Vector2.Divide(localPos, drawSize)).Linear;
|
||||||
|
|
||||||
|
protected virtual Color4 PointColour(SmokePoint point)
|
||||||
|
{
|
||||||
|
var color = Color4.White;
|
||||||
|
|
||||||
|
double timeDoingInitialFadeOut = Math.Min(initialFadeOutTime, SmokeEndTime) - point.Time;
|
||||||
|
|
||||||
|
if (timeDoingInitialFadeOut > 0)
|
||||||
|
{
|
||||||
|
float fraction = Math.Clamp((float)(timeDoingInitialFadeOut / initial_fade_out_duration), 0, 1);
|
||||||
|
color.A = (1 - fraction) * initial_alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color.A > 0)
|
||||||
|
{
|
||||||
|
double timeDoingReFadeIn = reFadeInTime - point.Time / re_fade_in_speed;
|
||||||
|
double timeDoingFinalFadeOut = finalFadeOutTime - point.Time / final_fade_out_speed;
|
||||||
|
|
||||||
|
if (timeDoingFinalFadeOut > 0)
|
||||||
|
{
|
||||||
|
float fraction = Math.Clamp((float)(timeDoingFinalFadeOut / final_fade_out_duration), 0, 1);
|
||||||
|
fraction = MathF.Pow(fraction, 5);
|
||||||
|
color.A = (1 - fraction) * re_fade_in_alpha;
|
||||||
|
}
|
||||||
|
else if (timeDoingReFadeIn > 0)
|
||||||
|
{
|
||||||
|
float fraction = Math.Clamp((float)(timeDoingReFadeIn / re_fade_in_duration), 0, 1);
|
||||||
|
fraction = 1 - MathF.Pow(1 - fraction, 5);
|
||||||
|
color.A = fraction * (re_fade_in_alpha - color.A) + color.A;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual float PointScale(SmokePoint point)
|
||||||
|
{
|
||||||
|
double timeDoingScale = CurrentTime - point.Time;
|
||||||
|
float fraction = Math.Clamp((float)(timeDoingScale / scale_duration), 0, 1);
|
||||||
|
fraction = 1 - MathF.Pow(1 - fraction, 5);
|
||||||
|
return fraction * (final_scale - initial_scale) + initial_scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual Vector2 PointDirection(SmokePoint point)
|
||||||
|
{
|
||||||
|
float initialAngle = MathF.Atan2(point.Direction.Y, point.Direction.X);
|
||||||
|
float finalAngle = initialAngle + nextRotation();
|
||||||
|
|
||||||
|
double timeDoingRotation = CurrentTime - point.Time;
|
||||||
|
float fraction = Math.Clamp((float)(timeDoingRotation / rotation_duration), 0, 1);
|
||||||
|
fraction = 1 - MathF.Pow(1 - fraction, 5);
|
||||||
|
float angle = fraction * (finalAngle - initialAngle) + initialAngle;
|
||||||
|
|
||||||
|
return new Vector2(MathF.Sin(angle), -MathF.Cos(angle));
|
||||||
|
}
|
||||||
|
|
||||||
|
private float nextRotation() => max_rotation * ((float)rotationRNG.NextDouble() * 2 - 1);
|
||||||
|
|
||||||
|
private void drawPointQuad(SmokePoint point, RectangleF textureRect)
|
||||||
|
{
|
||||||
|
Debug.Assert(quadBatch != null);
|
||||||
|
|
||||||
|
var colour = PointColour(point);
|
||||||
|
float scale = PointScale(point);
|
||||||
|
var dir = PointDirection(point);
|
||||||
|
var ortho = dir.PerpendicularLeft;
|
||||||
|
|
||||||
|
if (colour.A == 0 || scale == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var localTopLeft = point.Position + (radius * scale * (-ortho - dir));
|
||||||
|
var localTopRight = point.Position + (radius * scale * (-ortho + dir));
|
||||||
|
var localBotLeft = point.Position + (radius * scale * (ortho - dir));
|
||||||
|
var localBotRight = point.Position + (radius * scale * (ortho + dir));
|
||||||
|
|
||||||
|
quadBatch.Add(new TexturedVertex2D
|
||||||
|
{
|
||||||
|
Position = localTopLeft,
|
||||||
|
TexturePosition = textureRect.TopLeft,
|
||||||
|
Colour = Color4Extensions.Multiply(ColourAtPosition(localTopLeft), colour),
|
||||||
|
});
|
||||||
|
quadBatch.Add(new TexturedVertex2D
|
||||||
|
{
|
||||||
|
Position = localTopRight,
|
||||||
|
TexturePosition = textureRect.TopRight,
|
||||||
|
Colour = Color4Extensions.Multiply(ColourAtPosition(localTopRight), colour),
|
||||||
|
});
|
||||||
|
quadBatch.Add(new TexturedVertex2D
|
||||||
|
{
|
||||||
|
Position = localBotRight,
|
||||||
|
TexturePosition = textureRect.BottomRight,
|
||||||
|
Colour = Color4Extensions.Multiply(ColourAtPosition(localBotRight), colour),
|
||||||
|
});
|
||||||
|
quadBatch.Add(new TexturedVertex2D
|
||||||
|
{
|
||||||
|
Position = localBotLeft,
|
||||||
|
TexturePosition = textureRect.BottomLeft,
|
||||||
|
Colour = Color4Extensions.Multiply(ColourAtPosition(localBotLeft), colour),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
quadBatch?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -54,6 +54,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both },
|
playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both },
|
||||||
|
new SmokeContainer { RelativeSizeAxes = Axes.Both },
|
||||||
spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both },
|
spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both },
|
||||||
FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both },
|
FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both },
|
||||||
judgementLayer = new JudgementContainer<DrawableOsuJudgement> { RelativeSizeAxes = Axes.Both },
|
judgementLayer = new JudgementContainer<DrawableOsuJudgement> { RelativeSizeAxes = Axes.Both },
|
||||||
|
77
osu.Game.Rulesets.Osu/UI/SmokeContainer.cs
Normal file
77
osu.Game.Rulesets.Osu/UI/SmokeContainer.cs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// 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.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Input;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Manages smoke trails generated from user input.
|
||||||
|
/// </summary>
|
||||||
|
public class SmokeContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler<OsuAction>
|
||||||
|
{
|
||||||
|
private SmokeSkinnableDrawable? currentSegmentSkinnable;
|
||||||
|
|
||||||
|
private Vector2 lastMousePosition;
|
||||||
|
|
||||||
|
public override bool ReceivePositionalInputAt(Vector2 _) => true;
|
||||||
|
|
||||||
|
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
||||||
|
{
|
||||||
|
if (e.Action == OsuAction.Smoke)
|
||||||
|
{
|
||||||
|
AddInternal(currentSegmentSkinnable = new SmokeSkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.CursorSmoke), _ => new DefaultSmokeSegment()));
|
||||||
|
|
||||||
|
// Add initial position immediately.
|
||||||
|
addPosition();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
||||||
|
{
|
||||||
|
if (e.Action == OsuAction.Smoke)
|
||||||
|
{
|
||||||
|
if (currentSegmentSkinnable?.Drawable is SmokeSegment segment)
|
||||||
|
{
|
||||||
|
segment.FinishDrawing(Time.Current);
|
||||||
|
currentSegmentSkinnable = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||||
|
{
|
||||||
|
lastMousePosition = e.MousePosition;
|
||||||
|
addPosition();
|
||||||
|
|
||||||
|
return base.OnMouseMove(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addPosition() => (currentSegmentSkinnable?.Drawable as SmokeSegment)?.AddPosition(lastMousePosition, Time.Current);
|
||||||
|
|
||||||
|
private class SmokeSkinnableDrawable : SkinnableDrawable
|
||||||
|
{
|
||||||
|
public override bool RemoveWhenNotAlive => true;
|
||||||
|
|
||||||
|
public override double LifetimeStart => Drawable.LifetimeStart;
|
||||||
|
public override double LifetimeEnd => Drawable.LifetimeEnd;
|
||||||
|
|
||||||
|
public SmokeSkinnableDrawable(ISkinComponent component, Func<ISkinComponent, Drawable>? defaultImplementation = null, ConfineMode confineMode = ConfineMode.NoScaling)
|
||||||
|
: base(component, defaultImplementation, confineMode)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user