mirror of
https://github.com/ppy/osu.git
synced 2026-05-19 18:19:54 +08:00
Compare commits
1083 Commits
@@ -15,7 +15,7 @@
|
||||
]
|
||||
},
|
||||
"jetbrains.resharper.globaltools": {
|
||||
"version": "2020.2.4",
|
||||
"version": "2020.3.2",
|
||||
"commands": [
|
||||
"jb"
|
||||
]
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="osu! SDL" type="DotNetProject" factoryName=".NET Project" folderName="osu!" activateToolWindowBeforeRun="false">
|
||||
<configuration default="false" name="osu! (legacy osuTK)" type="DotNetProject" factoryName=".NET Project" folderName="osu!" activateToolWindowBeforeRun="false">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="--sdl" />
|
||||
<option name="PROGRAM_PARAMETERS" value="--tk" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.1" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
+15
-7
@@ -16,9 +16,9 @@
|
||||
<EmbeddedResource Include="Resources\**\*.*" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Code Analysis">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.1" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.2" PrivateAssets="All" />
|
||||
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.1" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.2" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Code Analysis">
|
||||
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset</CodeAnalysisRuleSet>
|
||||
@@ -28,9 +28,17 @@
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Project">
|
||||
<!-- DeepEqual is not netstandard-compatible. This is fine since we run tests with .NET Framework anyway.
|
||||
This is required due to https://github.com/NuGet/Home/issues/5740 -->
|
||||
<NoWarn>$(NoWarn);NU1701</NoWarn>
|
||||
<!--
|
||||
NU1701:
|
||||
DeepEqual is not netstandard-compatible. This is fine since we run tests with .NET Framework anyway.
|
||||
This is required due to https://github.com/NuGet/Home/issues/5740
|
||||
|
||||
CA9998:
|
||||
Microsoft.CodeAnalysis.FxCopAnalyzers has been deprecated.
|
||||
The entire package will be able to be removed after migrating to .NET 5,
|
||||
as analysers are shipped as part of the .NET 5 SDK anyway.
|
||||
-->
|
||||
<NoWarn>$(NoWarn);NU1701;CA9998</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Nuget">
|
||||
<IsPackable>false</IsPackable>
|
||||
@@ -40,7 +48,7 @@
|
||||
<RepositoryUrl>https://github.com/ppy/osu</RepositoryUrl>
|
||||
<PackageReleaseNotes>Automated release.</PackageReleaseNotes>
|
||||
<Company>ppy Pty Ltd</Company>
|
||||
<Copyright>Copyright (c) 2020 ppy Pty Ltd</Copyright>
|
||||
<Copyright>Copyright (c) 2021 ppy Pty Ltd</Copyright>
|
||||
<PackageTags>osu game</PackageTags>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
+29
-27
@@ -1,27 +1,27 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.2)
|
||||
CFPropertyList (3.0.3)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.1.0)
|
||||
aws-partitions (1.354.0)
|
||||
aws-sdk-core (3.104.3)
|
||||
aws-partitions (1.413.0)
|
||||
aws-sdk-core (3.110.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.239.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-kms (1.36.0)
|
||||
aws-sdk-core (~> 3, >= 3.99.0)
|
||||
aws-sdk-kms (1.40.0)
|
||||
aws-sdk-core (~> 3, >= 3.109.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.78.0)
|
||||
aws-sdk-core (~> 3, >= 3.104.3)
|
||||
aws-sdk-s3 (1.87.0)
|
||||
aws-sdk-core (~> 3, >= 3.109.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sigv4 (1.2.1)
|
||||
aws-sigv4 (1.2.2)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.3)
|
||||
babosa (1.0.4)
|
||||
claide (1.0.3)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
@@ -29,22 +29,23 @@ GEM
|
||||
highline (~> 1.7.2)
|
||||
declarative (0.0.20)
|
||||
declarative-option (0.1.0)
|
||||
digest-crc (0.6.1)
|
||||
rake (~> 13.0)
|
||||
digest-crc (0.6.3)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.7.6)
|
||||
emoji_regex (3.0.0)
|
||||
excon (0.76.0)
|
||||
faraday (1.0.1)
|
||||
emoji_regex (3.2.1)
|
||||
excon (0.78.1)
|
||||
faraday (1.2.0)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday-cookie_jar (0.0.6)
|
||||
faraday (>= 0.7.4)
|
||||
ruby2_keywords
|
||||
faraday-cookie_jar (0.0.7)
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday_middleware (1.0.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.0)
|
||||
fastlane (2.156.0)
|
||||
fastimage (2.2.1)
|
||||
fastlane (2.170.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.3, < 3.0.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
@@ -96,17 +97,17 @@ GEM
|
||||
google-cloud-core (1.5.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.3.3)
|
||||
google-cloud-env (1.4.0)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
google-cloud-errors (1.0.1)
|
||||
google-cloud-storage (1.27.0)
|
||||
google-cloud-storage (1.29.2)
|
||||
addressable (~> 2.5)
|
||||
digest-crc (~> 0.4)
|
||||
google-api-client (~> 0.33)
|
||||
google-cloud-core (~> 1.2)
|
||||
googleauth (~> 0.9)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (0.13.1)
|
||||
googleauth (0.14.0)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
@@ -118,10 +119,10 @@ GEM
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
jmespath (1.4.0)
|
||||
json (2.3.1)
|
||||
jwt (2.2.1)
|
||||
json (2.5.1)
|
||||
jwt (2.2.2)
|
||||
memoist (0.16.2)
|
||||
mini_magick (4.10.1)
|
||||
mini_magick (4.11.0)
|
||||
mini_mime (1.0.2)
|
||||
mini_portile2 (2.4.0)
|
||||
multi_json (1.15.0)
|
||||
@@ -132,14 +133,15 @@ GEM
|
||||
mini_portile2 (~> 2.4.0)
|
||||
os (1.1.1)
|
||||
plist (3.5.0)
|
||||
public_suffix (4.0.5)
|
||||
rake (13.0.1)
|
||||
public_suffix (4.0.6)
|
||||
rake (13.0.3)
|
||||
representable (3.0.4)
|
||||
declarative (< 0.1.0)
|
||||
declarative-option (< 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rouge (2.0.7)
|
||||
ruby2_keywords (0.0.2)
|
||||
rubyzip (2.3.0)
|
||||
security (0.1.3)
|
||||
signet (0.14.0)
|
||||
@@ -168,7 +170,7 @@ GEM
|
||||
unf_ext (0.0.7.7)
|
||||
unicode-display_width (1.7.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.18.0)
|
||||
xcodeproj (1.19.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2020 ppy Pty Ltd <contact@ppy.sh>.
|
||||
Copyright (c) 2021 ppy Pty Ltd <contact@ppy.sh>.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
+60
-13
@@ -1,21 +1,68 @@
|
||||
clone_depth: 1
|
||||
version: '{build}'
|
||||
image: Visual Studio 2019
|
||||
dotnet_csproj:
|
||||
patch: true
|
||||
file: 'osu.Game\osu.Game.csproj' # Use wildcard when it's able to exclude Xamarin projects
|
||||
version: $(APPVEYOR_REPO_TAG_NAME)
|
||||
before_build:
|
||||
- ps: dotnet --info # Useful when version mismatch between CI and local
|
||||
- ps: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects
|
||||
test: off
|
||||
skip_non_tags: true
|
||||
configuration: Release
|
||||
build:
|
||||
project: build\Desktop.proj # Skipping Xamarin Release that's slow and covered by fastlane
|
||||
parallel: true
|
||||
verbosity: minimal
|
||||
publish_nuget: true
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- job_name: osu-game
|
||||
- job_name: osu-ruleset
|
||||
job_depends_on: osu-game
|
||||
- job_name: taiko-ruleset
|
||||
job_depends_on: osu-game
|
||||
- job_name: catch-ruleset
|
||||
job_depends_on: osu-game
|
||||
- job_name: mania-ruleset
|
||||
job_depends_on: osu-game
|
||||
|
||||
nuget:
|
||||
project_feed: true
|
||||
|
||||
for:
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- job_name: osu-game
|
||||
build_script:
|
||||
- cmd: dotnet pack osu.Game\osu.Game.csproj /p:Version=%APPVEYOR_REPO_TAG_NAME%
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- job_name: osu-ruleset
|
||||
build_script:
|
||||
- cmd: dotnet remove osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj reference osu.Game\osu.Game.csproj
|
||||
- cmd: dotnet add osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME%
|
||||
- cmd: dotnet pack osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj /p:Version=%APPVEYOR_REPO_TAG_NAME%
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- job_name: taiko-ruleset
|
||||
build_script:
|
||||
- cmd: dotnet remove osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj reference osu.Game\osu.Game.csproj
|
||||
- cmd: dotnet add osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME%
|
||||
- cmd: dotnet pack osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj /p:Version=%APPVEYOR_REPO_TAG_NAME%
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- job_name: catch-ruleset
|
||||
build_script:
|
||||
- cmd: dotnet remove osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj reference osu.Game\osu.Game.csproj
|
||||
- cmd: dotnet add osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME%
|
||||
- cmd: dotnet pack osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj /p:Version=%APPVEYOR_REPO_TAG_NAME%
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- job_name: mania-ruleset
|
||||
build_script:
|
||||
- cmd: dotnet remove osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj reference osu.Game\osu.Game.csproj
|
||||
- cmd: dotnet add osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME%
|
||||
- cmd: dotnet pack osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj /p:Version=%APPVEYOR_REPO_TAG_NAME%
|
||||
|
||||
artifacts:
|
||||
- path: '**\*.nupkg'
|
||||
|
||||
deploy:
|
||||
- provider: Environment
|
||||
name: nuget
|
||||
name: nuget
|
||||
+1
-1
@@ -52,6 +52,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1212.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.106.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,18 +1,32 @@
|
||||
// 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.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Content.PM;
|
||||
using Android.Net;
|
||||
using Android.OS;
|
||||
using Android.Provider;
|
||||
using Android.Views;
|
||||
using osu.Framework.Android;
|
||||
|
||||
namespace osu.Android
|
||||
{
|
||||
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)]
|
||||
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance)]
|
||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")]
|
||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")]
|
||||
[IntentFilter(new[] { Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/zip", "application/octet-stream" })]
|
||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })]
|
||||
public class OsuGameActivity : AndroidGameActivity
|
||||
{
|
||||
protected override Framework.Game CreateGame() => new OsuGameAndroid(this);
|
||||
private static readonly string[] osu_url_schemes = { "osu", "osump" };
|
||||
|
||||
private OsuGameAndroid game;
|
||||
|
||||
protected override Framework.Game CreateGame() => game = new OsuGameAndroid(this);
|
||||
|
||||
protected override void OnCreate(Bundle savedInstanceState)
|
||||
{
|
||||
@@ -23,8 +37,60 @@ namespace osu.Android
|
||||
|
||||
base.OnCreate(savedInstanceState);
|
||||
|
||||
// OnNewIntent() only fires for an activity if it's *re-launched* while it's on top of the activity stack.
|
||||
// on first launch we still have to fire manually.
|
||||
// reference: https://developer.android.com/reference/android/app/Activity#onNewIntent(android.content.Intent)
|
||||
handleIntent(Intent);
|
||||
|
||||
Window.AddFlags(WindowManagerFlags.Fullscreen);
|
||||
Window.AddFlags(WindowManagerFlags.KeepScreenOn);
|
||||
}
|
||||
|
||||
protected override void OnNewIntent(Intent intent) => handleIntent(intent);
|
||||
|
||||
private void handleIntent(Intent intent)
|
||||
{
|
||||
switch (intent.Action)
|
||||
{
|
||||
case Intent.ActionDefault:
|
||||
if (intent.Scheme == ContentResolver.SchemeContent)
|
||||
handleImportFromUri(intent.Data);
|
||||
else if (osu_url_schemes.Contains(intent.Scheme))
|
||||
game.HandleLink(intent.DataString);
|
||||
break;
|
||||
|
||||
case Intent.ActionSend:
|
||||
{
|
||||
var content = intent.ClipData?.GetItemAt(0);
|
||||
if (content != null)
|
||||
handleImportFromUri(content.Uri);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleImportFromUri(Uri uri) => Task.Factory.StartNew(async () =>
|
||||
{
|
||||
// there are more performant overloads of this method, but this one is the most backwards-compatible
|
||||
// (dates back to API 1).
|
||||
var cursor = ContentResolver?.Query(uri, null, null, null, null);
|
||||
|
||||
if (cursor == null)
|
||||
return;
|
||||
|
||||
cursor.MoveToFirst();
|
||||
|
||||
var filenameColumn = cursor.GetColumnIndex(OpenableColumns.DisplayName);
|
||||
string filename = cursor.GetString(filenameColumn);
|
||||
|
||||
// SharpCompress requires archive streams to be seekable, which the stream opened by
|
||||
// OpenInputStream() seems to not necessarily be.
|
||||
// copy to an arbitrary-access memory stream to be able to proceed with the import.
|
||||
var copy = new MemoryStream();
|
||||
using (var stream = ContentResolver.OpenInputStream(uri))
|
||||
await stream.CopyToAsync(copy);
|
||||
|
||||
await game.Import(copy, filename);
|
||||
}, TaskCreationOptions.LongRunning);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Users;
|
||||
@@ -26,18 +27,20 @@ namespace osu.Desktop
|
||||
[Resolved]
|
||||
private IBindable<RulesetInfo> ruleset { get; set; }
|
||||
|
||||
private Bindable<User> user;
|
||||
private IBindable<User> user;
|
||||
|
||||
private readonly IBindable<UserStatus> status = new Bindable<UserStatus>();
|
||||
private readonly IBindable<UserActivity> activity = new Bindable<UserActivity>();
|
||||
|
||||
private readonly Bindable<DiscordRichPresenceMode> privacyMode = new Bindable<DiscordRichPresenceMode>();
|
||||
|
||||
private readonly RichPresence presence = new RichPresence
|
||||
{
|
||||
Assets = new Assets { LargeImageKey = "osu_logo_lazer", }
|
||||
};
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IAPIProvider provider)
|
||||
private void load(IAPIProvider provider, OsuConfigManager config)
|
||||
{
|
||||
client = new DiscordRpcClient(client_id)
|
||||
{
|
||||
@@ -51,6 +54,8 @@ namespace osu.Desktop
|
||||
|
||||
client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network);
|
||||
|
||||
config.BindWith(OsuSetting.DiscordRichPresence, privacyMode);
|
||||
|
||||
(user = provider.LocalUser.GetBoundCopy()).BindValueChanged(u =>
|
||||
{
|
||||
status.UnbindBindings();
|
||||
@@ -63,6 +68,7 @@ namespace osu.Desktop
|
||||
ruleset.BindValueChanged(_ => updateStatus());
|
||||
status.BindValueChanged(_ => updateStatus());
|
||||
activity.BindValueChanged(_ => updateStatus());
|
||||
privacyMode.BindValueChanged(_ => updateStatus());
|
||||
|
||||
client.Initialize();
|
||||
}
|
||||
@@ -78,7 +84,7 @@ namespace osu.Desktop
|
||||
if (!client.IsInitialized)
|
||||
return;
|
||||
|
||||
if (status.Value is UserStatusOffline)
|
||||
if (status.Value is UserStatusOffline || privacyMode.Value == DiscordRichPresenceMode.Off)
|
||||
{
|
||||
client.ClearPresence();
|
||||
return;
|
||||
@@ -96,7 +102,10 @@ namespace osu.Desktop
|
||||
}
|
||||
|
||||
// update user information
|
||||
presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.Ranks.Global > 0 ? $" (rank #{user.Value.Statistics.Ranks.Global:N0})" : string.Empty);
|
||||
if (privacyMode.Value == DiscordRichPresenceMode.Limited)
|
||||
presence.Assets.LargeImageText = string.Empty;
|
||||
else
|
||||
presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.Ranks.Global > 0 ? $" (rank #{user.Value.Statistics.Ranks.Global:N0})" : string.Empty);
|
||||
|
||||
// update ruleset
|
||||
presence.Assets.SmallImageKey = ruleset.Value.ID <= 3 ? $"mode_{ruleset.Value.ID}" : "mode_custom";
|
||||
@@ -137,7 +146,7 @@ namespace osu.Desktop
|
||||
return edit.Beatmap.ToString();
|
||||
|
||||
case UserActivity.InLobby lobby:
|
||||
return lobby.Room.Name.Value;
|
||||
return privacyMode.Value == DiscordRichPresenceMode.Limited ? string.Empty : lobby.Room.Name.Value;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
|
||||
@@ -26,9 +26,11 @@ namespace osu.Desktop.Overlays
|
||||
|
||||
Alpha = 0;
|
||||
|
||||
FillFlowContainer mainFill;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
mainFill = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
@@ -55,23 +57,30 @@ namespace osu.Desktop.Overlays
|
||||
},
|
||||
}
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Font = OsuFont.Numeric.With(size: 12),
|
||||
Colour = colours.Yellow,
|
||||
Text = @"Development Build"
|
||||
},
|
||||
new Sprite
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Texture = textures.Get(@"Menu/dev-build-footer"),
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!game.IsDeployedBuild)
|
||||
{
|
||||
mainFill.AddRange(new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Font = OsuFont.Numeric.With(size: 12),
|
||||
Colour = colours.Yellow,
|
||||
Text = @"Development Build"
|
||||
},
|
||||
new Sprite
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Texture = textures.Get(@"Menu/dev-build-footer"),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected override void PopIn()
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>A free-to-win rhythm game. Rhythm is just a *click* away!</description>
|
||||
<releaseNotes>testing</releaseNotes>
|
||||
<copyright>Copyright (c) 2020 ppy Pty Ltd</copyright>
|
||||
<copyright>Copyright (c) 2021 ppy Pty Ltd</copyright>
|
||||
<language>en-AU</language>
|
||||
</metadata>
|
||||
<files>
|
||||
|
||||
@@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
|
||||
public float Position
|
||||
{
|
||||
get => HitObject?.X ?? position;
|
||||
get => HitObject?.EffectiveX ?? position;
|
||||
set => position = value;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
|
||||
private void attemptCatch(Fruit fruit)
|
||||
{
|
||||
fruit.X += catcher.X;
|
||||
fruit.X = fruit.OriginalX + catcher.X;
|
||||
fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty
|
||||
{
|
||||
CircleSize = circleSize
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
|
||||
case JuiceStream juiceStream:
|
||||
// Todo: BUG!! Stable used the last control point as the final position of the path, but it should use the computed path instead.
|
||||
lastPosition = juiceStream.X + juiceStream.Path.ControlPoints[^1].Position.Value.X;
|
||||
lastPosition = juiceStream.OriginalX + juiceStream.Path.ControlPoints[^1].Position.Value.X;
|
||||
|
||||
// Todo: BUG!! Stable attempted to use the end time of the stream, but referenced it too early in execution and used the start time instead.
|
||||
lastStartTime = juiceStream.StartTime;
|
||||
@@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
catchObject.XOffset = 0;
|
||||
|
||||
if (catchObject is TinyDroplet)
|
||||
catchObject.XOffset = Math.Clamp(rng.Next(-20, 20), -catchObject.X, CatchPlayfield.WIDTH - catchObject.X);
|
||||
catchObject.XOffset = Math.Clamp(rng.Next(-20, 20), -catchObject.OriginalX, CatchPlayfield.WIDTH - catchObject.OriginalX);
|
||||
else if (catchObject is Droplet)
|
||||
rng.Next(); // osu!stable retrieved a random droplet rotation
|
||||
}
|
||||
@@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
|
||||
private static void applyHardRockOffset(CatchHitObject hitObject, ref float? lastPosition, ref double lastStartTime, FastRandom rng)
|
||||
{
|
||||
float offsetPosition = hitObject.X;
|
||||
float offsetPosition = hitObject.OriginalX;
|
||||
double startTime = hitObject.StartTime;
|
||||
|
||||
if (lastPosition == null)
|
||||
@@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
if (positionDiff == 0)
|
||||
{
|
||||
applyRandomOffset(ref offsetPosition, timeDiff / 4d, rng);
|
||||
hitObject.XOffset = offsetPosition - hitObject.X;
|
||||
hitObject.XOffset = offsetPosition - hitObject.OriginalX;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
if (Math.Abs(positionDiff) < timeDiff / 3)
|
||||
applyOffset(ref offsetPosition, positionDiff);
|
||||
|
||||
hitObject.XOffset = offsetPosition - hitObject.X;
|
||||
hitObject.XOffset = offsetPosition - hitObject.OriginalX;
|
||||
|
||||
lastPosition = offsetPosition;
|
||||
lastStartTime = startTime;
|
||||
@@ -230,9 +230,9 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
currentObject.HyperDashTarget = null;
|
||||
currentObject.DistanceToHyperDash = 0;
|
||||
|
||||
int thisDirection = nextObject.X > currentObject.X ? 1 : -1;
|
||||
int thisDirection = nextObject.EffectiveX > currentObject.EffectiveX ? 1 : -1;
|
||||
double timeToNext = nextObject.StartTime - currentObject.StartTime - 1000f / 60f / 4; // 1/4th of a frame of grace time, taken from osu-stable
|
||||
double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth);
|
||||
double distanceToNext = Math.Abs(nextObject.EffectiveX - currentObject.EffectiveX) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth);
|
||||
float distanceToHyper = (float)(timeToNext * Catcher.BASE_SPEED - distanceToNext);
|
||||
|
||||
if (distanceToHyper < 0)
|
||||
|
||||
@@ -32,8 +32,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
|
||||
// We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
|
||||
var scalingFactor = normalized_hitobject_radius / halfCatcherWidth;
|
||||
|
||||
NormalizedPosition = BaseObject.X * scalingFactor;
|
||||
LastNormalizedPosition = LastObject.X * scalingFactor;
|
||||
NormalizedPosition = BaseObject.EffectiveX * scalingFactor;
|
||||
LastNormalizedPosition = LastObject.EffectiveX * scalingFactor;
|
||||
|
||||
// Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure
|
||||
StrainTime = Math.Max(40, DeltaTime);
|
||||
|
||||
@@ -59,8 +59,8 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
{
|
||||
base.ApplySettings(difficulty);
|
||||
|
||||
difficulty.CircleSize = CircleSize.Value;
|
||||
difficulty.ApproachRate = ApproachRate.Value;
|
||||
ApplySetting(CircleSize, cs => difficulty.CircleSize = cs);
|
||||
ApplySetting(ApproachRate, ar => difficulty.ApproachRate = ar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
@@ -16,38 +15,46 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
public const float OBJECT_RADIUS = 64;
|
||||
|
||||
// This value is after XOffset applied.
|
||||
public readonly Bindable<float> XBindable = new Bindable<float>();
|
||||
|
||||
// This value is before XOffset applied.
|
||||
private float originalX;
|
||||
public readonly Bindable<float> OriginalXBindable = new Bindable<float>();
|
||||
|
||||
/// <summary>
|
||||
/// The horizontal position of the fruit between 0 and <see cref="CatchPlayfield.WIDTH"/>.
|
||||
/// The horizontal position of the hit object between 0 and <see cref="CatchPlayfield.WIDTH"/>.
|
||||
/// </summary>
|
||||
public float X
|
||||
{
|
||||
// TODO: I don't like this asymmetry.
|
||||
get => XBindable.Value;
|
||||
// originalX is set by `XBindable.BindValueChanged`
|
||||
set => XBindable.Value = value + xOffset;
|
||||
set => OriginalXBindable.Value = value;
|
||||
}
|
||||
|
||||
private float xOffset;
|
||||
float IHasXPosition.X => OriginalXBindable.Value;
|
||||
|
||||
public readonly Bindable<float> XOffsetBindable = new Bindable<float>();
|
||||
|
||||
/// <summary>
|
||||
/// A random offset applied to <see cref="X"/>, set by the <see cref="CatchBeatmapProcessor"/>.
|
||||
/// A random offset applied to the horizontal position, set by the beatmap processing.
|
||||
/// </summary>
|
||||
internal float XOffset
|
||||
public float XOffset
|
||||
{
|
||||
get => xOffset;
|
||||
set
|
||||
{
|
||||
xOffset = value;
|
||||
XBindable.Value = originalX + xOffset;
|
||||
}
|
||||
set => XOffsetBindable.Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The horizontal position of the hit object between 0 and <see cref="CatchPlayfield.WIDTH"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This value is the original <see cref="X"/> value specified in the beatmap, not affected by the beatmap processing.
|
||||
/// Use <see cref="EffectiveX"/> for a gameplay.
|
||||
/// </remarks>
|
||||
public float OriginalX => OriginalXBindable.Value;
|
||||
|
||||
/// <summary>
|
||||
/// The effective horizontal position of the hit object between 0 and <see cref="CatchPlayfield.WIDTH"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This value is the original <see cref="X"/> value plus the offset applied by the beatmap processing.
|
||||
/// Use <see cref="OriginalX"/> if a value not affected by the offset is desired.
|
||||
/// </remarks>
|
||||
public float EffectiveX => OriginalXBindable.Value + XOffsetBindable.Value;
|
||||
|
||||
public double TimePreempt = 1000;
|
||||
|
||||
public readonly Bindable<int> IndexInBeatmapBindable = new Bindable<int>();
|
||||
@@ -113,10 +120,5 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
}
|
||||
|
||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||
|
||||
protected CatchHitObject()
|
||||
{
|
||||
XBindable.BindValueChanged(x => originalX = x.NewValue - xOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
public abstract class DrawableCatchHitObject : DrawableHitObject<CatchHitObject>
|
||||
{
|
||||
public readonly Bindable<float> XBindable = new Bindable<float>();
|
||||
public readonly Bindable<float> OriginalXBindable = new Bindable<float>();
|
||||
public readonly Bindable<float> XOffsetBindable = new Bindable<float>();
|
||||
|
||||
protected override double InitialLifetimeOffset => HitObject.TimePreempt;
|
||||
|
||||
protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH;
|
||||
protected override float SamplePlaybackPosition => HitObject.EffectiveX / CatchPlayfield.WIDTH;
|
||||
|
||||
public int RandomSeed => HitObject?.RandomSeed ?? 0;
|
||||
|
||||
@@ -38,14 +39,16 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
{
|
||||
base.OnApply();
|
||||
|
||||
XBindable.BindTo(HitObject.XBindable);
|
||||
OriginalXBindable.BindTo(HitObject.OriginalXBindable);
|
||||
XOffsetBindable.BindTo(HitObject.XOffsetBindable);
|
||||
}
|
||||
|
||||
protected override void OnFree()
|
||||
{
|
||||
base.OnFree();
|
||||
|
||||
XBindable.UnbindFrom(HitObject.XBindable);
|
||||
OriginalXBindable.UnbindFrom(HitObject.OriginalXBindable);
|
||||
XOffsetBindable.UnbindFrom(HitObject.XOffsetBindable);
|
||||
}
|
||||
|
||||
public Func<CatchHitObject, bool> CheckPosition;
|
||||
|
||||
@@ -55,10 +55,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
XBindable.BindValueChanged(x =>
|
||||
{
|
||||
X = x.NewValue;
|
||||
}, true);
|
||||
OriginalXBindable.BindValueChanged(updateXPosition);
|
||||
XOffsetBindable.BindValueChanged(updateXPosition, true);
|
||||
|
||||
ScaleBindable.BindValueChanged(scale =>
|
||||
{
|
||||
@@ -69,6 +67,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||
IndexInBeatmap.BindValueChanged(_ => UpdateComboColour());
|
||||
}
|
||||
|
||||
private void updateXPosition(ValueChangedEvent<float> _)
|
||||
{
|
||||
X = OriginalXBindable.Value + XOffsetBindable.Value;
|
||||
}
|
||||
|
||||
protected override void OnApply()
|
||||
{
|
||||
base.OnApply();
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
AddNested(new TinyDroplet
|
||||
{
|
||||
StartTime = t + lastEvent.Value.Time,
|
||||
X = X + Path.PositionAt(
|
||||
X = OriginalX + Path.PositionAt(
|
||||
lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X,
|
||||
});
|
||||
}
|
||||
@@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
Samples = dropletSamples,
|
||||
StartTime = e.Time,
|
||||
X = X + Path.PositionAt(e.PathProgress).X,
|
||||
X = OriginalX + Path.PositionAt(e.PathProgress).X,
|
||||
});
|
||||
break;
|
||||
|
||||
@@ -104,14 +104,14 @@ namespace osu.Game.Rulesets.Catch.Objects
|
||||
{
|
||||
Samples = this.GetNodeSamples(nodeIndex++),
|
||||
StartTime = e.Time,
|
||||
X = X + Path.PositionAt(e.PathProgress).X,
|
||||
X = OriginalX + Path.PositionAt(e.PathProgress).X,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float EndX => X + this.CurvePositionAt(1).X;
|
||||
public float EndX => OriginalX + this.CurvePositionAt(1).X;
|
||||
|
||||
public double Duration
|
||||
{
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Replays
|
||||
|
||||
void moveToNext(PalpableCatchHitObject h)
|
||||
{
|
||||
float positionChange = Math.Abs(lastPosition - h.X);
|
||||
float positionChange = Math.Abs(lastPosition - h.EffectiveX);
|
||||
double timeAvailable = h.StartTime - lastTime;
|
||||
|
||||
// So we can either make it there without a dash or not.
|
||||
@@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Catch.Replays
|
||||
// todo: get correct catcher size, based on difficulty CS.
|
||||
const float catcher_width_half = CatcherArea.CATCHER_SIZE * 0.3f * 0.5f;
|
||||
|
||||
if (lastPosition - catcher_width_half < h.X && lastPosition + catcher_width_half > h.X)
|
||||
if (lastPosition - catcher_width_half < h.EffectiveX && lastPosition + catcher_width_half > h.EffectiveX)
|
||||
{
|
||||
// we are already in the correct range.
|
||||
lastTime = h.StartTime;
|
||||
@@ -66,12 +66,12 @@ namespace osu.Game.Rulesets.Catch.Replays
|
||||
|
||||
if (impossibleJump)
|
||||
{
|
||||
addFrame(h.StartTime, h.X);
|
||||
addFrame(h.StartTime, h.EffectiveX);
|
||||
}
|
||||
else if (h.HyperDash)
|
||||
{
|
||||
addFrame(h.StartTime - timeAvailable, lastPosition);
|
||||
addFrame(h.StartTime, h.X);
|
||||
addFrame(h.StartTime, h.EffectiveX);
|
||||
}
|
||||
else if (dashRequired)
|
||||
{
|
||||
@@ -80,23 +80,23 @@ namespace osu.Game.Rulesets.Catch.Replays
|
||||
double timeWeNeedToSave = timeAtNormalSpeed - timeAvailable;
|
||||
double timeAtDashSpeed = timeWeNeedToSave / 2;
|
||||
|
||||
float midPosition = (float)Interpolation.Lerp(lastPosition, h.X, (float)timeAtDashSpeed / timeAvailable);
|
||||
float midPosition = (float)Interpolation.Lerp(lastPosition, h.EffectiveX, (float)timeAtDashSpeed / timeAvailable);
|
||||
|
||||
// dash movement
|
||||
addFrame(h.StartTime - timeAvailable + 1, lastPosition, true);
|
||||
addFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition);
|
||||
addFrame(h.StartTime, h.X);
|
||||
addFrame(h.StartTime, h.EffectiveX);
|
||||
}
|
||||
else
|
||||
{
|
||||
double timeBefore = positionChange / movement_speed;
|
||||
|
||||
addFrame(h.StartTime - timeBefore, lastPosition);
|
||||
addFrame(h.StartTime, h.X);
|
||||
addFrame(h.StartTime, h.EffectiveX);
|
||||
}
|
||||
|
||||
lastTime = h.StartTime;
|
||||
lastPosition = h.X;
|
||||
lastPosition = h.EffectiveX;
|
||||
}
|
||||
|
||||
foreach (var obj in Beatmap.HitObjects)
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Catch.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.UI
|
||||
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
private readonly CatchPlayfield playfield;
|
||||
|
||||
public CatchReplayRecorder(Replay target, CatchPlayfield playfield)
|
||||
public CatchReplayRecorder(Score target, CatchPlayfield playfield)
|
||||
: base(target)
|
||||
{
|
||||
this.playfield = playfield;
|
||||
|
||||
@@ -216,7 +216,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
var halfCatchWidth = catchWidth * 0.5f;
|
||||
|
||||
// this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
|
||||
var catchObjectPosition = fruit.X;
|
||||
var catchObjectPosition = fruit.EffectiveX;
|
||||
var catcherPosition = Position.X;
|
||||
|
||||
return catchObjectPosition >= catcherPosition - halfCatchWidth &&
|
||||
@@ -250,10 +250,10 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
var target = hitObject.HyperDashTarget;
|
||||
var timeDifference = target.StartTime - hitObject.StartTime;
|
||||
double positionDifference = target.X - X;
|
||||
double positionDifference = target.EffectiveX - X;
|
||||
var velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
|
||||
|
||||
SetHyperDashState(Math.Abs(velocity), target.X);
|
||||
SetHyperDashState(Math.Abs(velocity), target.EffectiveX);
|
||||
}
|
||||
else
|
||||
SetHyperDashState();
|
||||
|
||||
@@ -13,6 +13,7 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
@@ -31,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
|
||||
|
||||
protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new CatchReplayRecorder(replay, (CatchPlayfield)Playfield);
|
||||
protected override ReplayRecorder CreateReplayRecorder(Score score) => new CatchReplayRecorder(score, (CatchPlayfield)Playfield);
|
||||
|
||||
protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation);
|
||||
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Description>catch the fruit. to the beat.</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Nuget">
|
||||
<Title>osu!catch (ruleset)</Title>
|
||||
<PackageId>ppy.osu.Game.Rulesets.Catch</PackageId>
|
||||
<IsPackable>true</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Label="Project References">
|
||||
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -355,7 +355,11 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
protected override bool PauseOnFocusLost => false;
|
||||
|
||||
public ScoreAccessibleReplayPlayer(Score score)
|
||||
: base(score, false, false)
|
||||
: base(score, new PlayerConfiguration
|
||||
{
|
||||
AllowPause = false,
|
||||
ShowResults = false,
|
||||
})
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +176,11 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
protected override bool PauseOnFocusLost => false;
|
||||
|
||||
public ScoreAccessibleReplayPlayer(Score score)
|
||||
: base(score, false, false)
|
||||
: base(score, new PlayerConfiguration
|
||||
{
|
||||
AllowPause = false,
|
||||
ShowResults = false,
|
||||
})
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
// 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.Animations;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
{
|
||||
public class LegacyManiaJudgementPiece : CompositeDrawable, IAnimatableJudgement
|
||||
{
|
||||
private readonly HitResult result;
|
||||
private readonly Drawable animation;
|
||||
|
||||
public LegacyManiaJudgementPiece(HitResult result, Drawable animation)
|
||||
{
|
||||
this.result = result;
|
||||
this.animation = animation;
|
||||
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
AutoSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin)
|
||||
{
|
||||
float? scorePosition = skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.ScorePosition)?.Value;
|
||||
|
||||
if (scorePosition != null)
|
||||
scorePosition -= Stage.HIT_TARGET_POSITION + 150;
|
||||
|
||||
Y = scorePosition ?? 0;
|
||||
|
||||
if (animation != null)
|
||||
{
|
||||
InternalChild = animation.With(d =>
|
||||
{
|
||||
d.Anchor = Anchor.Centre;
|
||||
d.Origin = Anchor.Centre;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void PlayAnimation()
|
||||
{
|
||||
if (animation == null)
|
||||
return;
|
||||
|
||||
(animation as IFramedAnimation)?.GotoFrame(0);
|
||||
|
||||
this.FadeInFromZero(20, Easing.Out)
|
||||
.Then().Delay(160)
|
||||
.FadeOutFromOne(40, Easing.In);
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.None:
|
||||
break;
|
||||
|
||||
case HitResult.Miss:
|
||||
animation.ScaleTo(1.2f).Then().ScaleTo(1, 100, Easing.Out);
|
||||
|
||||
animation.RotateTo(0);
|
||||
animation.RotateTo(RNG.NextSingle(-5.73f, 5.73f), 100, Easing.Out);
|
||||
break;
|
||||
|
||||
default:
|
||||
animation.ScaleTo(0.8f)
|
||||
.Then().ScaleTo(1, 40)
|
||||
// this is actually correct to match stable; there were overlapping transforms.
|
||||
.Then().ScaleTo(0.85f)
|
||||
.Then().ScaleTo(0.7f, 40)
|
||||
.Then().Delay(100)
|
||||
.Then().ScaleTo(0.4f, 40, Easing.In);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public Drawable GetAboveHitObjectsProxiedContent() => null;
|
||||
}
|
||||
}
|
||||
@@ -136,7 +136,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
string filename = this.GetManiaSkinConfig<string>(hitresult_mapping[result])?.Value
|
||||
?? default_hitresult_skin_filenames[result];
|
||||
|
||||
return this.GetAnimation(filename, true, true);
|
||||
var animation = this.GetAnimation(filename, true, true);
|
||||
return animation == null ? null : new LegacyManiaJudgementPiece(result, animation);
|
||||
}
|
||||
|
||||
public override SampleChannel GetSample(ISampleInfo sampleInfo)
|
||||
|
||||
@@ -5,7 +5,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
@@ -20,37 +19,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
}
|
||||
|
||||
protected override void ApplyMissAnimations()
|
||||
{
|
||||
if (!(JudgementBody.Drawable is DefaultManiaJudgementPiece))
|
||||
{
|
||||
// this is temporary logic until mania's skin transformer returns IAnimatableJudgements
|
||||
JudgementBody.ScaleTo(1.6f);
|
||||
JudgementBody.ScaleTo(1, 100, Easing.In);
|
||||
|
||||
JudgementBody.MoveTo(Vector2.Zero);
|
||||
JudgementBody.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
|
||||
|
||||
JudgementBody.RotateTo(0);
|
||||
JudgementBody.RotateTo(40, 800, Easing.InQuint);
|
||||
JudgementBody.FadeOutFromOne(800);
|
||||
|
||||
LifetimeEnd = JudgementBody.LatestTransformEndTime;
|
||||
}
|
||||
|
||||
base.ApplyMissAnimations();
|
||||
}
|
||||
|
||||
protected override void ApplyHitAnimations()
|
||||
{
|
||||
JudgementBody.ScaleTo(0.8f);
|
||||
JudgementBody.ScaleTo(1, 250, Easing.OutElastic);
|
||||
|
||||
JudgementBody.Delay(50)
|
||||
.ScaleTo(0.75f, 250)
|
||||
.FadeOut(200);
|
||||
}
|
||||
|
||||
protected override Drawable CreateDefaultJudgement(HitResult result) => new DefaultManiaJudgementPiece(result);
|
||||
|
||||
private class DefaultManiaJudgementPiece : DefaultJudgementPiece
|
||||
@@ -66,6 +34,27 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
JudgementText.Font = JudgementText.Font.With(size: 25);
|
||||
}
|
||||
|
||||
public override void PlayAnimation()
|
||||
{
|
||||
base.PlayAnimation();
|
||||
|
||||
switch (Result)
|
||||
{
|
||||
case HitResult.None:
|
||||
case HitResult.Miss:
|
||||
break;
|
||||
|
||||
default:
|
||||
this.ScaleTo(0.8f);
|
||||
this.ScaleTo(1, 250, Easing.OutElastic);
|
||||
|
||||
this.Delay(50)
|
||||
.ScaleTo(0.75f, 250)
|
||||
.FadeOut(200);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
@@ -132,6 +133,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
|
||||
|
||||
protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new ManiaReplayRecorder(replay);
|
||||
protected override ReplayRecorder CreateReplayRecorder(Score score) => new ManiaReplayRecorder(score);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
public class ManiaReplayRecorder : ReplayRecorder<ManiaAction>
|
||||
{
|
||||
public ManiaReplayRecorder(Replay replay)
|
||||
: base(replay)
|
||||
public ManiaReplayRecorder(Score score)
|
||||
: base(score)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Y = HIT_TARGET_POSITION + 150,
|
||||
Y = HIT_TARGET_POSITION + 150
|
||||
},
|
||||
topLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
|
||||
}
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Description>smash the keys. to the beat.</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Nuget">
|
||||
<Title>osu!mania (ruleset)</Title>
|
||||
<PackageId>ppy.osu.Game.Rulesets.Mania</PackageId>
|
||||
<IsPackable>true</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Label="Project References">
|
||||
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
@@ -18,8 +22,23 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
public void TestNoAdjustment() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModDifficultyAdjust(),
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
BaseDifficulty = new BeatmapDifficulty
|
||||
{
|
||||
CircleSize = 8
|
||||
}
|
||||
},
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HitCircle { StartTime = 1000 },
|
||||
new HitCircle { StartTime = 2000 }
|
||||
}
|
||||
},
|
||||
Autoplay = true,
|
||||
PassCondition = checkSomeHit
|
||||
PassCondition = () => checkSomeHit() && checkObjectsScale(0.29f)
|
||||
});
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -74,7 +74,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
private readonly bool userHasCustomColours;
|
||||
|
||||
public ExposedPlayer(bool userHasCustomColours)
|
||||
: base(false, false)
|
||||
: base(new PlayerConfiguration
|
||||
{
|
||||
AllowPause = false,
|
||||
ShowResults = false,
|
||||
})
|
||||
{
|
||||
this.userHasCustomColours = userHasCustomColours;
|
||||
}
|
||||
|
||||
@@ -439,7 +439,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
protected override bool PauseOnFocusLost => false;
|
||||
|
||||
public ScoreAccessibleReplayPlayer(Score score)
|
||||
: base(score, false, false)
|
||||
: base(score, new PlayerConfiguration
|
||||
{
|
||||
AllowPause = false,
|
||||
ShowResults = false,
|
||||
})
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,7 +378,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
protected override bool PauseOnFocusLost => false;
|
||||
|
||||
public ScoreAccessibleReplayPlayer(Score score)
|
||||
: base(score, false, false)
|
||||
: base(score, new PlayerConfiguration
|
||||
{
|
||||
AllowPause = false,
|
||||
ShowResults = false,
|
||||
})
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
|
||||
|
||||
if (mods.Any(m => m is OsuModNoFail))
|
||||
multiplier *= 0.90;
|
||||
multiplier *= Math.Max(0.90, 1.0 - 0.02 * countMiss);
|
||||
|
||||
if (mods.Any(m => m is OsuModSpunOut))
|
||||
multiplier *= 1.0 - Math.Pow((double)Attributes.SpinnerCount / totalHits, 0.85);
|
||||
@@ -92,8 +92,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
aimValue *= lengthBonus;
|
||||
|
||||
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
|
||||
aimValue *= Math.Pow(0.97, countMiss);
|
||||
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
||||
if (countMiss > 0)
|
||||
aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), countMiss);
|
||||
|
||||
// Combo scaling
|
||||
if (Attributes.MaxCombo > 0)
|
||||
@@ -103,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
if (Attributes.ApproachRate > 10.33)
|
||||
approachRateFactor += 0.4 * (Attributes.ApproachRate - 10.33);
|
||||
else if (Attributes.ApproachRate < 8.0)
|
||||
approachRateFactor += 0.1 * (8.0 - Attributes.ApproachRate);
|
||||
approachRateFactor += 0.01 * (8.0 - Attributes.ApproachRate);
|
||||
|
||||
aimValue *= 1.0 + Math.Min(approachRateFactor, approachRateFactor * (totalHits / 1000.0));
|
||||
|
||||
@@ -138,8 +139,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
||||
speedValue *= lengthBonus;
|
||||
|
||||
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
|
||||
speedValue *= Math.Pow(0.97, countMiss);
|
||||
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
||||
if (countMiss > 0)
|
||||
speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), Math.Pow(countMiss, .875));
|
||||
|
||||
// Combo scaling
|
||||
if (Attributes.MaxCombo > 0)
|
||||
@@ -154,10 +156,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
if (mods.Any(m => m is OsuModHidden))
|
||||
speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate);
|
||||
|
||||
// Scale the speed value with accuracy _slightly_
|
||||
speedValue *= 0.02 + accuracy;
|
||||
// It is important to also consider accuracy difficulty when doing that
|
||||
speedValue *= 0.96 + Math.Pow(Attributes.OverallDifficulty, 2) / 1600;
|
||||
// Scale the speed value with accuracy and OD
|
||||
speedValue *= (0.95 + Math.Pow(Attributes.OverallDifficulty, 2) / 750) * Math.Pow(accuracy, (14.5 - Math.Max(Attributes.OverallDifficulty, 8)) / 2);
|
||||
// Scale the speed value with # of 50s to punish doubletapping.
|
||||
speedValue *= Math.Pow(0.98, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0);
|
||||
|
||||
return speedValue;
|
||||
}
|
||||
|
||||
@@ -157,10 +157,16 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
foreach (var h in hitObjects)
|
||||
{
|
||||
h.Position = new Vector2(
|
||||
quad.TopLeft.X + (h.X - quad.TopLeft.X) / quad.Width * (quad.Width + scale.X),
|
||||
quad.TopLeft.Y + (h.Y - quad.TopLeft.Y) / quad.Height * (quad.Height + scale.Y)
|
||||
);
|
||||
var newPosition = h.Position;
|
||||
|
||||
// guard against no-ops and NaN.
|
||||
if (scale.X != 0 && quad.Width > 0)
|
||||
newPosition.X = quad.TopLeft.X + (h.X - quad.TopLeft.X) / quad.Width * (quad.Width + scale.X);
|
||||
|
||||
if (scale.Y != 0 && quad.Height > 0)
|
||||
newPosition.Y = quad.TopLeft.Y + (h.Y - quad.TopLeft.Y) / quad.Height * (quad.Height + scale.Y);
|
||||
|
||||
h.Position = newPosition;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,8 +59,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
base.ApplySettings(difficulty);
|
||||
|
||||
difficulty.CircleSize = CircleSize.Value;
|
||||
difficulty.ApproachRate = ApproachRate.Value;
|
||||
ApplySetting(CircleSize, cs => difficulty.CircleSize = cs);
|
||||
ApplySetting(ApproachRate, ar => difficulty.ApproachRate = ar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
var destination = e.MousePosition;
|
||||
|
||||
FlashlightPosition = Interpolation.ValueAt(
|
||||
Math.Clamp(Clock.ElapsedFrameTime, 0, follow_delay), position, destination, 0, follow_delay, Easing.Out);
|
||||
Math.Min(Math.Abs(Clock.ElapsedFrameTime), follow_delay), position, destination, 0, follow_delay, Easing.Out);
|
||||
|
||||
return base.OnMouseMove(e);
|
||||
}
|
||||
|
||||
@@ -20,17 +20,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
private double lastTrailTime;
|
||||
private IBindable<float> cursorSize;
|
||||
|
||||
public LegacyCursorTrail()
|
||||
{
|
||||
Blending = BlendingParameters.Additive;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin, OsuConfigManager config)
|
||||
{
|
||||
Texture = skin.GetTexture("cursortrail");
|
||||
disjointTrail = skin.GetTexture("cursormiddle") == null;
|
||||
|
||||
Blending = !disjointTrail ? BlendingParameters.Additive : BlendingParameters.Inherit;
|
||||
|
||||
if (Texture != null)
|
||||
{
|
||||
// stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation.
|
||||
|
||||
@@ -14,6 +14,7 @@ using osu.Game.Rulesets.Osu.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osuTK;
|
||||
|
||||
@@ -44,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuFramedReplayInputHandler(replay);
|
||||
|
||||
protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new OsuReplayRecorder(replay);
|
||||
protected override ReplayRecorder CreateReplayRecorder(Score score) => new OsuReplayRecorder(score);
|
||||
|
||||
public override double GameplayStartTime
|
||||
{
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.UI
|
||||
{
|
||||
public class OsuReplayRecorder : ReplayRecorder<OsuAction>
|
||||
{
|
||||
public OsuReplayRecorder(Replay replay)
|
||||
: base(replay)
|
||||
public OsuReplayRecorder(Score score)
|
||||
: base(score)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Description>click the circles. to the beat.</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Nuget">
|
||||
<Title>osu! (ruleset)</Title>
|
||||
<PackageId>ppy.osu.Game.Rulesets.Osu</PackageId>
|
||||
<IsPackable>true</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Label="Project References">
|
||||
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
{
|
||||
public abstract class DrawableTaikoRulesetTestScene : OsuTestScene
|
||||
{
|
||||
protected const int DEFAULT_PLAYFIELD_CONTAINER_HEIGHT = 768;
|
||||
|
||||
protected DrawableTaikoRuleset DrawableRuleset { get; private set; }
|
||||
protected Container PlayfieldContainer { get; private set; }
|
||||
|
||||
@@ -44,10 +46,10 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
|
||||
Add(PlayfieldContainer = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 768,
|
||||
Height = DEFAULT_PLAYFIELD_CONTAINER_HEIGHT,
|
||||
Children = new[] { DrawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(new TaikoRuleset().RulesetInfo)) }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,12 +13,15 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
{
|
||||
public readonly HitResult Type;
|
||||
|
||||
public DrawableTestHit(Hit hit, HitResult type = HitResult.Great)
|
||||
public DrawableTestHit(Hit hit, HitResult type = HitResult.Great, bool kiai = false)
|
||||
: base(hit)
|
||||
{
|
||||
Type = type;
|
||||
|
||||
HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
var controlPoints = new ControlPointInfo();
|
||||
controlPoints.Add(0, new EffectControlPoint { KiaiMode = kiai });
|
||||
|
||||
HitObject.ApplyDefaults(controlPoints, new BeatmapDifficulty());
|
||||
}
|
||||
|
||||
protected override void UpdateInitialTransforms()
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// 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.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests
|
||||
{
|
||||
public abstract class HitObjectApplicationTestScene : OsuTestScene
|
||||
{
|
||||
[Cached(typeof(IScrollingInfo))]
|
||||
private ScrollingTestContainer.TestScrollingInfo info = new ScrollingTestContainer.TestScrollingInfo
|
||||
{
|
||||
Direction = { Value = ScrollingDirection.Left },
|
||||
TimeRange = { Value = 1000 },
|
||||
};
|
||||
|
||||
private ScrollingHitObjectContainer hitObjectContainer;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Child = hitObjectContainer = new ScrollingHitObjectContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 200,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Clock = new FramedClock(new StopwatchClock())
|
||||
};
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUp()
|
||||
=> AddStep("clear SHOC", () => hitObjectContainer.Clear(false));
|
||||
|
||||
protected void AddHitObject(DrawableHitObject hitObject)
|
||||
=> AddStep("add to SHOC", () => hitObjectContainer.Add(hitObject));
|
||||
|
||||
protected void RemoveHitObject(DrawableHitObject hitObject)
|
||||
=> AddStep("remove from SHOC", () => hitObjectContainer.Remove(hitObject));
|
||||
|
||||
protected TObject PrepareObject<TObject>(TObject hitObject)
|
||||
where TObject : TaikoHitObject
|
||||
{
|
||||
hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
return hitObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
}
|
||||
};
|
||||
|
||||
hoc.Add(new DrawableBarLineMajor(createBarLineAtCurrentTime(true))
|
||||
hoc.Add(new DrawableBarLine(createBarLineAtCurrentTime(true))
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
createDrawableRuleset();
|
||||
|
||||
assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Idle);
|
||||
assertStateAfterResult(new JudgementResult(new StrongHitObject(), new TaikoStrongJudgement()) { Type = HitResult.IgnoreMiss }, TaikoMascotAnimationState.Idle);
|
||||
assertStateAfterResult(new JudgementResult(new Hit.StrongNestedHit(), new TaikoStrongJudgement()) { Type = HitResult.IgnoreMiss }, TaikoMascotAnimationState.Idle);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
[TestCase("sample-to-type-conversions")]
|
||||
[TestCase("slider-conversion-v6")]
|
||||
[TestCase("slider-conversion-v14")]
|
||||
[TestCase("slider-generating-drumroll-2")]
|
||||
public void Test(string name) => base.Test(name);
|
||||
|
||||
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
|
||||
@@ -35,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
IsCentre = (hitObject as Hit)?.Type == HitType.Centre,
|
||||
IsDrumRoll = hitObject is DrumRoll,
|
||||
IsSwell = hitObject is Swell,
|
||||
IsStrong = ((TaikoHitObject)hitObject).IsStrong
|
||||
IsStrong = (hitObject as TaikoStrongableHitObject)?.IsStrong == true
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests
|
||||
{
|
||||
public class TestSceneBarLineApplication : HitObjectApplicationTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestApplyNewBarLine()
|
||||
{
|
||||
DrawableBarLine barLine = new DrawableBarLine();
|
||||
|
||||
AddStep("apply new bar line", () => barLine.Apply(PrepareObject(new BarLine
|
||||
{
|
||||
StartTime = 400,
|
||||
Major = true
|
||||
}), null));
|
||||
AddHitObject(barLine);
|
||||
RemoveHitObject(barLine);
|
||||
|
||||
AddStep("apply new bar line", () => barLine.Apply(PrepareObject(new BarLine
|
||||
{
|
||||
StartTime = 200,
|
||||
Major = false
|
||||
}), null));
|
||||
AddHitObject(barLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 NUnit.Framework;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests
|
||||
{
|
||||
public class TestSceneDrumRollApplication : HitObjectApplicationTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestApplyNewDrumRoll()
|
||||
{
|
||||
var drumRoll = new DrawableDrumRoll();
|
||||
|
||||
AddStep("apply new drum roll", () => drumRoll.Apply(PrepareObject(new DrumRoll
|
||||
{
|
||||
StartTime = 300,
|
||||
Duration = 500,
|
||||
IsStrong = false,
|
||||
TickRate = 2
|
||||
}), null));
|
||||
|
||||
AddHitObject(drumRoll);
|
||||
RemoveHitObject(drumRoll);
|
||||
|
||||
AddStep("apply new drum roll", () => drumRoll.Apply(PrepareObject(new DrumRoll
|
||||
{
|
||||
StartTime = 150,
|
||||
Duration = 400,
|
||||
IsStrong = true,
|
||||
TickRate = 16
|
||||
}), null));
|
||||
|
||||
AddHitObject(drumRoll);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests
|
||||
{
|
||||
public class TestSceneHitApplication : HitObjectApplicationTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestApplyNewHit()
|
||||
{
|
||||
var hit = new DrawableHit();
|
||||
|
||||
AddStep("apply new hit", () => hit.Apply(PrepareObject(new Hit
|
||||
{
|
||||
Type = HitType.Rim,
|
||||
IsStrong = false,
|
||||
StartTime = 300
|
||||
}), null));
|
||||
|
||||
AddHitObject(hit);
|
||||
RemoveHitObject(hit);
|
||||
|
||||
AddStep("apply new hit", () => hit.Apply(PrepareObject(new Hit
|
||||
{
|
||||
Type = HitType.Centre,
|
||||
IsStrong = true,
|
||||
StartTime = 500
|
||||
}), null));
|
||||
|
||||
AddHitObject(hit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,16 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Judgements;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
@@ -97,7 +99,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
break;
|
||||
|
||||
case 6:
|
||||
PlayfieldContainer.Delay(delay).ResizeTo(new Vector2(1, TaikoPlayfield.DEFAULT_HEIGHT), 500);
|
||||
PlayfieldContainer.Delay(delay).ResizeTo(new Vector2(1, DEFAULT_PLAYFIELD_CONTAINER_HEIGHT), 500);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -106,13 +108,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
{
|
||||
HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Ok : HitResult.Great;
|
||||
|
||||
var cpi = new ControlPointInfo();
|
||||
cpi.Add(0, new EffectControlPoint { KiaiMode = kiai });
|
||||
|
||||
Hit hit = new Hit();
|
||||
hit.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||
|
||||
var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) };
|
||||
var h = new DrawableTestHit(hit, kiai: kiai) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) };
|
||||
|
||||
DrawableRuleset.Playfield.Add(h);
|
||||
|
||||
@@ -123,32 +120,38 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
{
|
||||
HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Ok : HitResult.Great;
|
||||
|
||||
var cpi = new ControlPointInfo();
|
||||
cpi.Add(0, new EffectControlPoint { KiaiMode = kiai });
|
||||
|
||||
Hit hit = new Hit();
|
||||
hit.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||
|
||||
var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) };
|
||||
Hit hit = new Hit
|
||||
{
|
||||
IsStrong = true,
|
||||
Samples = createSamples(strong: true)
|
||||
};
|
||||
var h = new DrawableTestHit(hit, kiai: kiai) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) };
|
||||
|
||||
DrawableRuleset.Playfield.Add(h);
|
||||
|
||||
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult });
|
||||
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great });
|
||||
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h.NestedHitObjects.Single(), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great });
|
||||
}
|
||||
|
||||
private void addMissJudgement()
|
||||
{
|
||||
DrawableTestHit h;
|
||||
DrawableRuleset.Playfield.Add(h = new DrawableTestHit(new Hit(), HitResult.Miss));
|
||||
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss });
|
||||
DrawableRuleset.Playfield.Add(h = new DrawableTestHit(new Hit { StartTime = DrawableRuleset.Playfield.Time.Current }, HitResult.Miss)
|
||||
{
|
||||
Alpha = 0
|
||||
});
|
||||
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(h.HitObject, new TaikoJudgement()) { Type = HitResult.Miss });
|
||||
}
|
||||
|
||||
private void addBarLine(bool major, double delay = scroll_time)
|
||||
{
|
||||
BarLine bl = new BarLine { StartTime = DrawableRuleset.Playfield.Time.Current + delay };
|
||||
BarLine bl = new BarLine
|
||||
{
|
||||
StartTime = DrawableRuleset.Playfield.Time.Current + delay,
|
||||
Major = major
|
||||
};
|
||||
|
||||
DrawableRuleset.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl));
|
||||
DrawableRuleset.Playfield.Add(bl);
|
||||
}
|
||||
|
||||
private void addSwell(double duration = default_duration)
|
||||
@@ -173,6 +176,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
{
|
||||
StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time,
|
||||
IsStrong = strong,
|
||||
Samples = createSamples(strong: strong),
|
||||
Duration = duration,
|
||||
TickRate = 8,
|
||||
};
|
||||
@@ -190,7 +194,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
Hit h = new Hit
|
||||
{
|
||||
StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time,
|
||||
IsStrong = strong
|
||||
IsStrong = strong,
|
||||
Samples = createSamples(HitType.Centre, strong)
|
||||
};
|
||||
|
||||
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
@@ -203,7 +208,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
Hit h = new Hit
|
||||
{
|
||||
StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time,
|
||||
IsStrong = strong
|
||||
IsStrong = strong,
|
||||
Samples = createSamples(HitType.Rim, strong)
|
||||
};
|
||||
|
||||
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
@@ -211,14 +217,18 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
DrawableRuleset.Playfield.Add(new DrawableHit(h));
|
||||
}
|
||||
|
||||
private class TestStrongNestedHit : DrawableStrongNestedHit
|
||||
// TODO: can be removed if a better way of handling colour/strong type and samples is developed
|
||||
private IList<HitSampleInfo> createSamples(HitType? hitType = null, bool strong = false)
|
||||
{
|
||||
public TestStrongNestedHit(DrawableHitObject mainObject)
|
||||
: base(new StrongHitObject { StartTime = mainObject.HitObject.StartTime }, mainObject)
|
||||
{
|
||||
}
|
||||
var samples = new List<HitSampleInfo>();
|
||||
|
||||
public override bool OnPressed(TaikoAction action) => false;
|
||||
if (hitType == HitType.Rim)
|
||||
samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP));
|
||||
|
||||
if (strong)
|
||||
samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH));
|
||||
|
||||
return samples;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Audio;
|
||||
@@ -18,24 +19,33 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
AddAssert("has correct samples", () =>
|
||||
|
||||
var expectedSampleNames = new[]
|
||||
{
|
||||
var names = Player.DrawableRuleset.Playfield.AllHitObjects.OfType<DrawableHit>().Select(h => string.Join(',', h.GetSamples().Select(s => s.Name)));
|
||||
string.Empty,
|
||||
string.Empty,
|
||||
string.Empty,
|
||||
string.Empty,
|
||||
HitSampleInfo.HIT_FINISH,
|
||||
HitSampleInfo.HIT_WHISTLE,
|
||||
HitSampleInfo.HIT_WHISTLE,
|
||||
HitSampleInfo.HIT_WHISTLE,
|
||||
};
|
||||
var actualSampleNames = new List<string>();
|
||||
|
||||
var expected = new[]
|
||||
{
|
||||
string.Empty,
|
||||
string.Empty,
|
||||
string.Empty,
|
||||
string.Empty,
|
||||
HitSampleInfo.HIT_FINISH,
|
||||
HitSampleInfo.HIT_WHISTLE,
|
||||
HitSampleInfo.HIT_WHISTLE,
|
||||
HitSampleInfo.HIT_WHISTLE,
|
||||
};
|
||||
// due to pooling we can't access all samples right away due to object re-use,
|
||||
// so we need to collect as we go.
|
||||
AddStep("collect sample names", () => Player.DrawableRuleset.Playfield.NewResult += (dho, _) =>
|
||||
{
|
||||
if (!(dho is DrawableHit h))
|
||||
return;
|
||||
|
||||
return names.SequenceEqual(expected);
|
||||
actualSampleNames.Add(string.Join(',', h.GetSamples().Select(s => s.Name)));
|
||||
});
|
||||
|
||||
AddUntilStep("all samples collected", () => actualSampleNames.Count == expectedSampleNames.Length);
|
||||
|
||||
AddAssert("samples are correct", () => actualSampleNames.SequenceEqual(expectedSampleNames));
|
||||
}
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TaikoBeatmapConversionTest().GetBeatmap("sample-to-type-conversions");
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
|
||||
@@ -65,8 +65,8 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x =>
|
||||
{
|
||||
TaikoHitObject first = x.First();
|
||||
if (x.Skip(1).Any() && first.CanBeStrong)
|
||||
first.IsStrong = true;
|
||||
if (x.Skip(1).Any() && first is TaikoStrongableHitObject strong)
|
||||
strong.IsStrong = true;
|
||||
return first;
|
||||
}).ToList();
|
||||
}
|
||||
@@ -160,7 +160,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
}
|
||||
}
|
||||
|
||||
private bool shouldConvertSliderToHits(HitObject obj, IBeatmap beatmap, IHasDistance distanceData, out double taikoDuration, out double tickSpacing)
|
||||
private bool shouldConvertSliderToHits(HitObject obj, IBeatmap beatmap, IHasDistance distanceData, out int taikoDuration, out double tickSpacing)
|
||||
{
|
||||
// DO NOT CHANGE OR REFACTOR ANYTHING IN HERE WITHOUT TESTING AGAINST _ALL_ BEATMAPS.
|
||||
// Some of these calculations look redundant, but they are not - extremely small floating point errors are introduced to maintain 1:1 compatibility with stable.
|
||||
@@ -185,7 +185,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
|
||||
// The velocity and duration of the taiko hit object - calculated as the velocity of a drum roll.
|
||||
double taikoVelocity = sliderScoringPointDistance * beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate;
|
||||
taikoDuration = distance / taikoVelocity * beatLength;
|
||||
taikoDuration = (int)(distance / taikoVelocity * beatLength);
|
||||
|
||||
if (isForCurrentRuleset)
|
||||
{
|
||||
@@ -200,7 +200,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
beatLength = timingPoint.BeatLength;
|
||||
|
||||
// If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat
|
||||
tickSpacing = Math.Min(beatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, taikoDuration / spans);
|
||||
tickSpacing = Math.Min(beatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, (double)taikoDuration / spans);
|
||||
|
||||
return tickSpacing > 0
|
||||
&& distance / osuVelocity * 1000 < 2 * beatLength;
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
||||
base.UpdateTernaryStates();
|
||||
|
||||
selectionRimState.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects.OfType<Hit>(), h => h.Type == HitType.Rim);
|
||||
selectionStrongState.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects.OfType<TaikoHitObject>(), h => h.IsStrong);
|
||||
selectionStrongState.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects.OfType<TaikoStrongableHitObject>(), h => h.IsStrong);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 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.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
@@ -8,7 +9,13 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
{
|
||||
public class BarLine : TaikoHitObject, IBarLine
|
||||
{
|
||||
public bool Major { get; set; }
|
||||
public bool Major
|
||||
{
|
||||
get => MajorBindable.Value;
|
||||
set => MajorBindable.Value = value;
|
||||
}
|
||||
|
||||
public readonly Bindable<bool> MajorBindable = new BindableBool();
|
||||
|
||||
public override Judgement CreateJudgement() => new IgnoreJudgement();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
// 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 JetBrains.Annotations;
|
||||
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;
|
||||
using osuTK;
|
||||
@@ -15,49 +19,123 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
/// </summary>
|
||||
public class DrawableBarLine : DrawableHitObject<HitObject>
|
||||
{
|
||||
public new BarLine HitObject => (BarLine)base.HitObject;
|
||||
|
||||
/// <summary>
|
||||
/// The width of the line tracker.
|
||||
/// </summary>
|
||||
private const float tracker_width = 2f;
|
||||
|
||||
/// <summary>
|
||||
/// Fade out time calibrated to a pre-empt of 1000ms.
|
||||
/// The vertical offset of the triangles from the line tracker.
|
||||
/// </summary>
|
||||
private const float base_fadeout_time = 100f;
|
||||
private const float triangle_offset = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// The size of the triangles.
|
||||
/// </summary>
|
||||
private const float triangle_size = 20f;
|
||||
|
||||
/// <summary>
|
||||
/// The visual line tracker.
|
||||
/// </summary>
|
||||
protected SkinnableDrawable Line;
|
||||
private SkinnableDrawable line;
|
||||
|
||||
/// <summary>
|
||||
/// The bar line.
|
||||
/// Container with triangles. Only visible for major lines.
|
||||
/// </summary>
|
||||
protected readonly BarLine BarLine;
|
||||
private Container triangleContainer;
|
||||
|
||||
public DrawableBarLine(BarLine barLine)
|
||||
private readonly Bindable<bool> major = new Bindable<bool>();
|
||||
|
||||
public DrawableBarLine()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public DrawableBarLine([CanBeNull] BarLine barLine)
|
||||
: base(barLine)
|
||||
{
|
||||
BarLine = barLine;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Anchor = Anchor.CentreLeft;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
Width = tracker_width;
|
||||
|
||||
AddInternal(Line = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.BarLine), _ => new Box
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
EdgeSmoothness = new Vector2(0.5f, 0),
|
||||
})
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Alpha = 0.75f,
|
||||
line = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.BarLine), _ => new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
EdgeSmoothness = new Vector2(0.5f, 0),
|
||||
})
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
triangleContainer = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new[]
|
||||
{
|
||||
new EquilateralTriangle
|
||||
{
|
||||
Name = "Top",
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Position = new Vector2(0, -triangle_offset),
|
||||
Size = new Vector2(-triangle_size),
|
||||
EdgeSmoothness = new Vector2(1),
|
||||
},
|
||||
new EquilateralTriangle
|
||||
{
|
||||
Name = "Bottom",
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Position = new Vector2(0, triangle_offset),
|
||||
Size = new Vector2(triangle_size),
|
||||
EdgeSmoothness = new Vector2(1),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateHitStateTransforms(ArmedState state) => this.FadeOut(150);
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
major.BindValueChanged(updateMajor, true);
|
||||
}
|
||||
|
||||
private void updateMajor(ValueChangedEvent<bool> major)
|
||||
{
|
||||
line.Alpha = major.NewValue ? 1f : 0.75f;
|
||||
triangleContainer.Alpha = major.NewValue ? 1 : 0;
|
||||
}
|
||||
|
||||
protected override void OnApply()
|
||||
{
|
||||
base.OnApply();
|
||||
major.BindTo(HitObject.MajorBindable);
|
||||
}
|
||||
|
||||
protected override void OnFree()
|
||||
{
|
||||
base.OnFree();
|
||||
major.UnbindFrom(HitObject.MajorBindable);
|
||||
}
|
||||
|
||||
protected override void UpdateHitStateTransforms(ArmedState state)
|
||||
{
|
||||
using (BeginAbsoluteSequence(HitObject.StartTime))
|
||||
this.FadeOutFromOne(150).Expire();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osuTK;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
public class DrawableBarLineMajor : DrawableBarLine
|
||||
{
|
||||
/// <summary>
|
||||
/// The vertical offset of the triangles from the line tracker.
|
||||
/// </summary>
|
||||
private const float triangle_offfset = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// The size of the triangles.
|
||||
/// </summary>
|
||||
private const float triangle_size = 20f;
|
||||
|
||||
private readonly Container triangleContainer;
|
||||
|
||||
public DrawableBarLineMajor(BarLine barLine)
|
||||
: base(barLine)
|
||||
{
|
||||
AddInternal(triangleContainer = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new[]
|
||||
{
|
||||
new EquilateralTriangle
|
||||
{
|
||||
Name = "Top",
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Position = new Vector2(0, -triangle_offfset),
|
||||
Size = new Vector2(-triangle_size),
|
||||
EdgeSmoothness = new Vector2(1),
|
||||
},
|
||||
new EquilateralTriangle
|
||||
{
|
||||
Name = "Bottom",
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Position = new Vector2(0, triangle_offfset),
|
||||
Size = new Vector2(triangle_size),
|
||||
EdgeSmoothness = new Vector2(1),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Line.Alpha = 1f;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
using (triangleContainer.BeginAbsoluteSequence(HitObject.StartTime))
|
||||
triangleContainer.FadeOut(150);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
@@ -19,7 +20,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
public class DrawableDrumRoll : DrawableTaikoHitObject<DrumRoll>
|
||||
public class DrawableDrumRoll : DrawableTaikoStrongableHitObject<DrumRoll, DrumRoll.StrongNestedHit>
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of rolling hits required to reach the dark/final colour.
|
||||
@@ -31,15 +32,26 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
/// </summary>
|
||||
private int rollingHits;
|
||||
|
||||
private Container tickContainer;
|
||||
private readonly Container tickContainer;
|
||||
|
||||
private Color4 colourIdle;
|
||||
private Color4 colourEngaged;
|
||||
|
||||
public DrawableDrumRoll(DrumRoll drumRoll)
|
||||
public DrawableDrumRoll()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public DrawableDrumRoll([CanBeNull] DrumRoll drumRoll)
|
||||
: base(drumRoll)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
|
||||
Content.Add(tickContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Depth = float.MinValue
|
||||
});
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@@ -47,12 +59,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
colourIdle = colours.YellowDark;
|
||||
colourEngaged = colours.YellowDarker;
|
||||
|
||||
Content.Add(tickContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Depth = float.MinValue
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@@ -68,6 +74,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
updateColour();
|
||||
}
|
||||
|
||||
protected override void OnFree()
|
||||
{
|
||||
base.OnFree();
|
||||
rollingHits = 0;
|
||||
}
|
||||
|
||||
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||
{
|
||||
base.AddNestedHitObject(hitObject);
|
||||
@@ -83,7 +95,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
protected override void ClearNestedHitObjects()
|
||||
{
|
||||
base.ClearNestedHitObjects();
|
||||
tickContainer.Clear();
|
||||
tickContainer.Clear(false);
|
||||
}
|
||||
|
||||
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
||||
@@ -114,7 +126,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
|
||||
rollingHits = Math.Clamp(rollingHits, 0, rolling_hits_for_engaged_colour);
|
||||
|
||||
updateColour();
|
||||
updateColour(100);
|
||||
}
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
@@ -154,27 +166,34 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
Content.X = DrawHeight / 2;
|
||||
}
|
||||
|
||||
protected override DrawableStrongNestedHit CreateStrongHit(StrongHitObject hitObject) => new StrongNestedHit(hitObject, this);
|
||||
protected override DrawableStrongNestedHit CreateStrongNestedHit(DrumRoll.StrongNestedHit hitObject) => new StrongNestedHit(hitObject);
|
||||
|
||||
private void updateColour()
|
||||
private void updateColour(double fadeDuration = 0)
|
||||
{
|
||||
Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1);
|
||||
(MainPiece.Drawable as IHasAccentColour)?.FadeAccent(newColour, 100);
|
||||
(MainPiece.Drawable as IHasAccentColour)?.FadeAccent(newColour, fadeDuration);
|
||||
}
|
||||
|
||||
private class StrongNestedHit : DrawableStrongNestedHit
|
||||
public class StrongNestedHit : DrawableStrongNestedHit
|
||||
{
|
||||
public StrongNestedHit(StrongHitObject strong, DrawableDrumRoll drumRoll)
|
||||
: base(strong, drumRoll)
|
||||
public new DrawableDrumRoll ParentHitObject => (DrawableDrumRoll)base.ParentHitObject;
|
||||
|
||||
public StrongNestedHit()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public StrongNestedHit([CanBeNull] DrumRoll.StrongNestedHit nestedHit)
|
||||
: base(nestedHit)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
if (!MainObject.Judged)
|
||||
if (!ParentHitObject.Judged)
|
||||
return;
|
||||
|
||||
ApplyResult(r => r.Type = MainObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||
ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||
}
|
||||
|
||||
public override bool OnPressed(TaikoAction action) => false;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.Skinning.Default;
|
||||
@@ -9,14 +10,19 @@ using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
public class DrawableDrumRollTick : DrawableTaikoHitObject<DrumRollTick>
|
||||
public class DrawableDrumRollTick : DrawableTaikoStrongableHitObject<DrumRollTick, DrumRollTick.StrongNestedHit>
|
||||
{
|
||||
/// <summary>
|
||||
/// The hit type corresponding to the <see cref="TaikoAction"/> that the user pressed to hit this <see cref="DrawableDrumRollTick"/>.
|
||||
/// </summary>
|
||||
public HitType JudgementType;
|
||||
|
||||
public DrawableDrumRollTick(DrumRollTick tick)
|
||||
public DrawableDrumRollTick()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public DrawableDrumRollTick([CanBeNull] DrumRollTick tick)
|
||||
: base(tick)
|
||||
{
|
||||
FillMode = FillMode.Fit;
|
||||
@@ -61,21 +67,28 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
return UpdateResult(true);
|
||||
}
|
||||
|
||||
protected override DrawableStrongNestedHit CreateStrongHit(StrongHitObject hitObject) => new StrongNestedHit(hitObject, this);
|
||||
protected override DrawableStrongNestedHit CreateStrongNestedHit(DrumRollTick.StrongNestedHit hitObject) => new StrongNestedHit(hitObject);
|
||||
|
||||
private class StrongNestedHit : DrawableStrongNestedHit
|
||||
public class StrongNestedHit : DrawableStrongNestedHit
|
||||
{
|
||||
public StrongNestedHit(StrongHitObject strong, DrawableDrumRollTick tick)
|
||||
: base(strong, tick)
|
||||
public new DrawableDrumRollTick ParentHitObject => (DrawableDrumRollTick)base.ParentHitObject;
|
||||
|
||||
public StrongNestedHit()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public StrongNestedHit([CanBeNull] DrumRollTick.StrongNestedHit nestedHit)
|
||||
: base(nestedHit)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
if (!MainObject.Judged)
|
||||
if (!ParentHitObject.Judged)
|
||||
return;
|
||||
|
||||
ApplyResult(r => r.Type = MainObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||
ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||
}
|
||||
|
||||
public override bool OnPressed(TaikoAction action) => false;
|
||||
|
||||
@@ -5,7 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Audio;
|
||||
@@ -16,7 +16,7 @@ using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
public class DrawableHit : DrawableTaikoHitObject<Hit>
|
||||
public class DrawableHit : DrawableTaikoStrongableHitObject<Hit, Hit.StrongNestedHit>
|
||||
{
|
||||
/// <summary>
|
||||
/// A list of keys which can result in hits for this HitObject.
|
||||
@@ -36,29 +36,51 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
|
||||
private bool pressHandledThisFrame;
|
||||
|
||||
private readonly Bindable<HitType> type;
|
||||
private readonly Bindable<HitType> type = new Bindable<HitType>();
|
||||
|
||||
public DrawableHit(Hit hit)
|
||||
: base(hit)
|
||||
public DrawableHit()
|
||||
: this(null)
|
||||
{
|
||||
type = HitObject.TypeBindable.GetBoundCopy();
|
||||
FillMode = FillMode.Fit;
|
||||
|
||||
updateActionsFromType();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
public DrawableHit([CanBeNull] Hit hit)
|
||||
: base(hit)
|
||||
{
|
||||
FillMode = FillMode.Fit;
|
||||
}
|
||||
|
||||
protected override void OnApply()
|
||||
{
|
||||
type.BindTo(HitObject.TypeBindable);
|
||||
type.BindValueChanged(_ =>
|
||||
{
|
||||
updateActionsFromType();
|
||||
|
||||
// will overwrite samples, should only be called on change.
|
||||
// will overwrite samples, should only be called on subsequent changes
|
||||
// after the initial application.
|
||||
updateSamplesFromTypeChange();
|
||||
|
||||
RecreatePieces();
|
||||
});
|
||||
|
||||
// action update also has to happen immediately on application.
|
||||
updateActionsFromType();
|
||||
|
||||
base.OnApply();
|
||||
}
|
||||
|
||||
protected override void OnFree()
|
||||
{
|
||||
base.OnFree();
|
||||
|
||||
type.UnbindFrom(HitObject.TypeBindable);
|
||||
type.UnbindEvents();
|
||||
|
||||
UnproxyContent();
|
||||
|
||||
HitActions = null;
|
||||
HitAction = null;
|
||||
validActionPressed = pressHandledThisFrame = false;
|
||||
}
|
||||
|
||||
private HitSampleInfo[] getRimSamples() => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray();
|
||||
@@ -228,32 +250,37 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
}
|
||||
}
|
||||
|
||||
protected override DrawableStrongNestedHit CreateStrongHit(StrongHitObject hitObject) => new StrongNestedHit(hitObject, this);
|
||||
protected override DrawableStrongNestedHit CreateStrongNestedHit(Hit.StrongNestedHit hitObject) => new StrongNestedHit(hitObject);
|
||||
|
||||
private class StrongNestedHit : DrawableStrongNestedHit
|
||||
public class StrongNestedHit : DrawableStrongNestedHit
|
||||
{
|
||||
public new DrawableHit ParentHitObject => (DrawableHit)base.ParentHitObject;
|
||||
|
||||
/// <summary>
|
||||
/// The lenience for the second key press.
|
||||
/// This does not adjust by map difficulty in ScoreV2 yet.
|
||||
/// </summary>
|
||||
private const double second_hit_window = 30;
|
||||
|
||||
public new DrawableHit MainObject => (DrawableHit)base.MainObject;
|
||||
public StrongNestedHit()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public StrongNestedHit(StrongHitObject strong, DrawableHit hit)
|
||||
: base(strong, hit)
|
||||
public StrongNestedHit([CanBeNull] Hit.StrongNestedHit nestedHit)
|
||||
: base(nestedHit)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
if (!MainObject.Result.HasResult)
|
||||
if (!ParentHitObject.Result.HasResult)
|
||||
{
|
||||
base.CheckForResult(userTriggered, timeOffset);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MainObject.Result.IsHit)
|
||||
if (!ParentHitObject.Result.IsHit)
|
||||
{
|
||||
ApplyResult(r => r.Type = r.Judgement.MinResult);
|
||||
return;
|
||||
@@ -261,27 +288,27 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
|
||||
if (!userTriggered)
|
||||
{
|
||||
if (timeOffset - MainObject.Result.TimeOffset > second_hit_window)
|
||||
if (timeOffset - ParentHitObject.Result.TimeOffset > second_hit_window)
|
||||
ApplyResult(r => r.Type = r.Judgement.MinResult);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Math.Abs(timeOffset - MainObject.Result.TimeOffset) <= second_hit_window)
|
||||
if (Math.Abs(timeOffset - ParentHitObject.Result.TimeOffset) <= second_hit_window)
|
||||
ApplyResult(r => r.Type = r.Judgement.MaxResult);
|
||||
}
|
||||
|
||||
public override bool OnPressed(TaikoAction action)
|
||||
{
|
||||
// Don't process actions until the main hitobject is hit
|
||||
if (!MainObject.IsHit)
|
||||
if (!ParentHitObject.IsHit)
|
||||
return false;
|
||||
|
||||
// Don't process actions if the pressed button was released
|
||||
if (MainObject.HitAction == null)
|
||||
if (ParentHitObject.HitAction == null)
|
||||
return false;
|
||||
|
||||
// Don't handle invalid hit action presses
|
||||
if (!MainObject.HitActions.Contains(action))
|
||||
if (!ParentHitObject.HitActions.Contains(action))
|
||||
return false;
|
||||
|
||||
return UpdateResult(true);
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Game.Rulesets.Taiko.Judgements;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
/// <summary>
|
||||
/// Used as a nested hitobject to provide <see cref="TaikoStrongJudgement"/>s for <see cref="DrawableTaikoHitObject"/>s.
|
||||
/// Used as a nested hitobject to provide <see cref="TaikoStrongJudgement"/>s for <see cref="DrawableTaikoStrongableHitObject{TObject,TStrongNestedObject}"/>s.
|
||||
/// </summary>
|
||||
public abstract class DrawableStrongNestedHit : DrawableTaikoHitObject
|
||||
{
|
||||
public readonly DrawableHitObject MainObject;
|
||||
public new DrawableTaikoHitObject ParentHitObject => (DrawableTaikoHitObject)base.ParentHitObject;
|
||||
|
||||
protected DrawableStrongNestedHit(StrongHitObject strong, DrawableHitObject mainObject)
|
||||
: base(strong)
|
||||
protected DrawableStrongNestedHit([CanBeNull] StrongNestedHitObject nestedHit)
|
||||
: base(nestedHit)
|
||||
{
|
||||
MainObject = mainObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -35,7 +36,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
private readonly CircularContainer targetRing;
|
||||
private readonly CircularContainer expandingRing;
|
||||
|
||||
public DrawableSwell(Swell swell)
|
||||
public DrawableSwell()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public DrawableSwell([CanBeNull] Swell swell)
|
||||
: base(swell)
|
||||
{
|
||||
FillMode = FillMode.Fit;
|
||||
@@ -123,12 +129,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
Origin = Anchor.Centre,
|
||||
});
|
||||
|
||||
protected override void LoadComplete()
|
||||
protected override void OnFree()
|
||||
{
|
||||
base.LoadComplete();
|
||||
base.OnFree();
|
||||
|
||||
// We need to set this here because RelativeSizeAxes won't/can't set our size by default with a different RelativeChildSize
|
||||
Width *= Parent.RelativeChildSize.X;
|
||||
UnproxyContent();
|
||||
|
||||
lastWasCentre = null;
|
||||
}
|
||||
|
||||
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||
@@ -146,7 +153,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
protected override void ClearNestedHitObjects()
|
||||
{
|
||||
base.ClearNestedHitObjects();
|
||||
ticks.Clear();
|
||||
ticks.Clear(false);
|
||||
}
|
||||
|
||||
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
||||
@@ -168,7 +175,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
|
||||
foreach (var t in ticks)
|
||||
{
|
||||
if (!t.IsHit)
|
||||
if (!t.Result.HasResult)
|
||||
{
|
||||
nextTick = t;
|
||||
break;
|
||||
@@ -208,7 +215,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
continue;
|
||||
}
|
||||
|
||||
tick.TriggerResult(false);
|
||||
if (!tick.Result.HasResult)
|
||||
tick.TriggerResult(false);
|
||||
}
|
||||
|
||||
ApplyResult(r => r.Type = numHits > HitObject.RequiredHits / 2 ? HitResult.Ok : r.Judgement.MinResult);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 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 JetBrains.Annotations;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Taiko.Skinning.Default;
|
||||
using osu.Game.Skinning;
|
||||
@@ -11,7 +12,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
public override bool DisplayResult => false;
|
||||
|
||||
public DrawableSwellTick(SwellTick hitObject)
|
||||
public DrawableSwellTick()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public DrawableSwellTick([CanBeNull] SwellTick hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -3,14 +3,12 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
@@ -24,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
|
||||
private readonly Container nonProxiedContent;
|
||||
|
||||
protected DrawableTaikoHitObject(TaikoHitObject hitObject)
|
||||
protected DrawableTaikoHitObject([CanBeNull] TaikoHitObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
AddRangeInternal(new[]
|
||||
@@ -115,117 +113,37 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
public override Vector2 OriginPosition => new Vector2(DrawHeight / 2);
|
||||
|
||||
public new TObject HitObject;
|
||||
public new TObject HitObject => (TObject)base.HitObject;
|
||||
|
||||
protected Vector2 BaseSize;
|
||||
protected SkinnableDrawable MainPiece;
|
||||
|
||||
private readonly Bindable<bool> isStrong;
|
||||
|
||||
private readonly Container<DrawableStrongNestedHit> strongHitContainer;
|
||||
|
||||
protected DrawableTaikoHitObject(TObject hitObject)
|
||||
protected DrawableTaikoHitObject([CanBeNull] TObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
HitObject = hitObject;
|
||||
isStrong = HitObject.IsStrongBindable.GetBoundCopy();
|
||||
|
||||
Anchor = Anchor.CentreLeft;
|
||||
Origin = Anchor.Custom;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
AddInternal(strongHitContainer = new Container<DrawableStrongNestedHit>());
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
protected override void OnApply()
|
||||
{
|
||||
isStrong.BindValueChanged(_ =>
|
||||
{
|
||||
// will overwrite samples, should only be called on change.
|
||||
updateSamplesFromStrong();
|
||||
|
||||
RecreatePieces();
|
||||
});
|
||||
|
||||
base.OnApply();
|
||||
RecreatePieces();
|
||||
}
|
||||
|
||||
private HitSampleInfo[] getStrongSamples() => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToArray();
|
||||
|
||||
protected override void LoadSamples()
|
||||
{
|
||||
base.LoadSamples();
|
||||
|
||||
if (HitObject.CanBeStrong)
|
||||
isStrong.Value = getStrongSamples().Any();
|
||||
}
|
||||
|
||||
private void updateSamplesFromStrong()
|
||||
{
|
||||
var strongSamples = getStrongSamples();
|
||||
|
||||
if (isStrong.Value != strongSamples.Any())
|
||||
{
|
||||
if (isStrong.Value)
|
||||
HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH));
|
||||
else
|
||||
{
|
||||
foreach (var sample in strongSamples)
|
||||
HitObject.Samples.Remove(sample);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void RecreatePieces()
|
||||
{
|
||||
Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE);
|
||||
Size = BaseSize = new Vector2(TaikoHitObject.DEFAULT_SIZE);
|
||||
|
||||
MainPiece?.Expire();
|
||||
Content.Add(MainPiece = CreateMainPiece());
|
||||
}
|
||||
|
||||
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||
{
|
||||
base.AddNestedHitObject(hitObject);
|
||||
|
||||
switch (hitObject)
|
||||
{
|
||||
case DrawableStrongNestedHit strong:
|
||||
strongHitContainer.Add(strong);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ClearNestedHitObjects()
|
||||
{
|
||||
base.ClearNestedHitObjects();
|
||||
strongHitContainer.Clear();
|
||||
}
|
||||
|
||||
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
||||
{
|
||||
switch (hitObject)
|
||||
{
|
||||
case StrongHitObject strong:
|
||||
return CreateStrongHit(strong);
|
||||
}
|
||||
|
||||
return base.CreateNestedHitObject(hitObject);
|
||||
}
|
||||
|
||||
// Most osu!taiko hitsounds are managed by the drum (see DrumSampleMapping).
|
||||
public override IEnumerable<HitSampleInfo> GetSamples() => Enumerable.Empty<HitSampleInfo>();
|
||||
|
||||
protected abstract SkinnableDrawable CreateMainPiece();
|
||||
|
||||
/// <summary>
|
||||
/// Creates the handler for this <see cref="DrawableHitObject"/>'s <see cref="StrongHitObject"/>.
|
||||
/// This is only invoked if <see cref="TaikoHitObject.IsStrong"/> is true for <see cref="HitObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="hitObject">The strong hitobject.</param>
|
||||
/// <returns>The strong hitobject handler.</returns>
|
||||
protected virtual DrawableStrongNestedHit CreateStrongHit(StrongHitObject hitObject) => null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
public abstract class DrawableTaikoStrongableHitObject<TObject, TStrongNestedObject> : DrawableTaikoHitObject<TObject>
|
||||
where TObject : TaikoStrongableHitObject
|
||||
where TStrongNestedObject : StrongNestedHitObject
|
||||
{
|
||||
private readonly Bindable<bool> isStrong = new BindableBool();
|
||||
|
||||
private readonly Container<DrawableStrongNestedHit> strongHitContainer;
|
||||
|
||||
protected DrawableTaikoStrongableHitObject([CanBeNull] TObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
AddInternal(strongHitContainer = new Container<DrawableStrongNestedHit>());
|
||||
}
|
||||
|
||||
protected override void OnApply()
|
||||
{
|
||||
isStrong.BindTo(HitObject.IsStrongBindable);
|
||||
isStrong.BindValueChanged(_ =>
|
||||
{
|
||||
// will overwrite samples, should only be called on subsequent changes
|
||||
// after the initial application.
|
||||
updateSamplesFromStrong();
|
||||
|
||||
RecreatePieces();
|
||||
});
|
||||
|
||||
base.OnApply();
|
||||
}
|
||||
|
||||
protected override void OnFree()
|
||||
{
|
||||
base.OnFree();
|
||||
|
||||
isStrong.UnbindFrom(HitObject.IsStrongBindable);
|
||||
// ensure the next application does not accidentally overwrite samples.
|
||||
isStrong.UnbindEvents();
|
||||
}
|
||||
|
||||
private HitSampleInfo[] getStrongSamples() => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToArray();
|
||||
|
||||
protected override void LoadSamples()
|
||||
{
|
||||
base.LoadSamples();
|
||||
isStrong.Value = getStrongSamples().Any();
|
||||
}
|
||||
|
||||
private void updateSamplesFromStrong()
|
||||
{
|
||||
var strongSamples = getStrongSamples();
|
||||
|
||||
if (isStrong.Value != strongSamples.Any())
|
||||
{
|
||||
if (isStrong.Value)
|
||||
HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH));
|
||||
else
|
||||
{
|
||||
foreach (var sample in strongSamples)
|
||||
HitObject.Samples.Remove(sample);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RecreatePieces()
|
||||
{
|
||||
base.RecreatePieces();
|
||||
if (HitObject.IsStrong)
|
||||
Size = BaseSize = new Vector2(TaikoStrongableHitObject.DEFAULT_STRONG_SIZE);
|
||||
}
|
||||
|
||||
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||
{
|
||||
base.AddNestedHitObject(hitObject);
|
||||
|
||||
switch (hitObject)
|
||||
{
|
||||
case DrawableStrongNestedHit strong:
|
||||
strongHitContainer.Add(strong);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ClearNestedHitObjects()
|
||||
{
|
||||
base.ClearNestedHitObjects();
|
||||
strongHitContainer.Clear(false);
|
||||
}
|
||||
|
||||
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
||||
{
|
||||
switch (hitObject)
|
||||
{
|
||||
case TStrongNestedObject strong:
|
||||
return CreateStrongNestedHit(strong);
|
||||
}
|
||||
|
||||
return base.CreateNestedHitObject(hitObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the handler for this <see cref="DrawableHitObject"/>'s <see cref="StrongNestedHitObject"/>.
|
||||
/// This is only invoked if <see cref="TaikoStrongableHitObject.IsStrong"/> is true for <see cref="HitObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="hitObject">The strong hitobject.</param>
|
||||
/// <returns>The strong hitobject handler.</returns>
|
||||
protected abstract DrawableStrongNestedHit CreateStrongNestedHit(TStrongNestedObject hitObject);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects
|
||||
{
|
||||
public class DrumRoll : TaikoHitObject, IHasPath
|
||||
public class DrumRoll : TaikoStrongableHitObject, IHasPath
|
||||
{
|
||||
/// <summary>
|
||||
/// Drum roll distance that results in a duration of 1 speed-adjusted beat length.
|
||||
@@ -109,6 +109,12 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
|
||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||
|
||||
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime };
|
||||
|
||||
public class StrongNestedHit : StrongNestedHitObject
|
||||
{
|
||||
}
|
||||
|
||||
#region LegacyBeatmapEncoder
|
||||
|
||||
double IHasDistance.Distance => Duration * Velocity;
|
||||
|
||||
@@ -7,7 +7,7 @@ using osu.Game.Rulesets.Taiko.Judgements;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects
|
||||
{
|
||||
public class DrumRollTick : TaikoHitObject
|
||||
public class DrumRollTick : TaikoStrongableHitObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether this is the first (initial) tick of the slider.
|
||||
@@ -28,5 +28,11 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
public override Judgement CreateJudgement() => new TaikoDrumRollTickJudgement();
|
||||
|
||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||
|
||||
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime };
|
||||
|
||||
public class StrongNestedHit : StrongNestedHitObject
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ using osu.Framework.Bindables;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects
|
||||
{
|
||||
public class Hit : TaikoHitObject
|
||||
public class Hit : TaikoStrongableHitObject
|
||||
{
|
||||
public readonly Bindable<HitType> TypeBindable = new Bindable<HitType>();
|
||||
|
||||
@@ -17,5 +17,11 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
get => TypeBindable.Value;
|
||||
set => TypeBindable.Value = value;
|
||||
}
|
||||
|
||||
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime };
|
||||
|
||||
public class StrongNestedHit : StrongNestedHitObject
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -7,7 +7,11 @@ using osu.Game.Rulesets.Taiko.Judgements;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects
|
||||
{
|
||||
public class StrongHitObject : TaikoHitObject
|
||||
/// <summary>
|
||||
/// Base type for nested strong hits.
|
||||
/// Used by <see cref="TaikoStrongableHitObject"/>s to represent their strong bonus scoring portions.
|
||||
/// </summary>
|
||||
public abstract class StrongNestedHitObject : TaikoHitObject
|
||||
{
|
||||
public override Judgement CreateJudgement() => new TaikoStrongJudgement();
|
||||
|
||||
@@ -17,8 +17,6 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
set => Duration = value - StartTime;
|
||||
}
|
||||
|
||||
public override bool CanBeStrong => false;
|
||||
|
||||
public double Duration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@@ -19,47 +16,6 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
/// </summary>
|
||||
public const float DEFAULT_SIZE = 0.45f;
|
||||
|
||||
/// <summary>
|
||||
/// Scale multiplier for a strong drawable taiko hit object.
|
||||
/// </summary>
|
||||
public const float STRONG_SCALE = 1.4f;
|
||||
|
||||
/// <summary>
|
||||
/// Default size of a strong drawable taiko hit object.
|
||||
/// </summary>
|
||||
public const float DEFAULT_STRONG_SIZE = DEFAULT_SIZE * STRONG_SCALE;
|
||||
|
||||
public readonly Bindable<bool> IsStrongBindable = new BindableBool();
|
||||
|
||||
/// <summary>
|
||||
/// Whether this <see cref="TaikoHitObject"/> can be made a "strong" (large) hit.
|
||||
/// </summary>
|
||||
public virtual bool CanBeStrong => true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this HitObject is a "strong" type.
|
||||
/// Strong hit objects give more points for hitting the hit object with both keys.
|
||||
/// </summary>
|
||||
public bool IsStrong
|
||||
{
|
||||
get => IsStrongBindable.Value;
|
||||
set
|
||||
{
|
||||
if (value && !CanBeStrong)
|
||||
throw new InvalidOperationException($"Object of type {GetType()} cannot be strong");
|
||||
|
||||
IsStrongBindable.Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
{
|
||||
base.CreateNestedHitObjects(cancellationToken);
|
||||
|
||||
if (IsStrong)
|
||||
AddNested(new StrongHitObject { StartTime = this.GetEndTime() });
|
||||
}
|
||||
|
||||
public override Judgement CreateJudgement() => new TaikoJudgement();
|
||||
|
||||
protected override HitWindows CreateHitWindows() => new TaikoHitWindows();
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Threading;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for taiko hitobjects that can become strong (large).
|
||||
/// </summary>
|
||||
public abstract class TaikoStrongableHitObject : TaikoHitObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Scale multiplier for a strong drawable taiko hit object.
|
||||
/// </summary>
|
||||
public const float STRONG_SCALE = 1.4f;
|
||||
|
||||
/// <summary>
|
||||
/// Default size of a strong drawable taiko hit object.
|
||||
/// </summary>
|
||||
public const float DEFAULT_STRONG_SIZE = DEFAULT_SIZE * STRONG_SCALE;
|
||||
|
||||
public readonly Bindable<bool> IsStrongBindable = new BindableBool();
|
||||
|
||||
/// <summary>
|
||||
/// Whether this HitObject is a "strong" type.
|
||||
/// Strong hit objects give more points for hitting the hit object with both keys.
|
||||
/// </summary>
|
||||
public bool IsStrong
|
||||
{
|
||||
get => IsStrongBindable.Value;
|
||||
set => IsStrongBindable.Value = value;
|
||||
}
|
||||
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
{
|
||||
base.CreateNestedHitObjects(cancellationToken);
|
||||
|
||||
if (IsStrong)
|
||||
AddNested(CreateStrongNestedHit(this.GetEndTime()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="StrongNestedHitObject"/> representing a second hit on this object.
|
||||
/// This is only called if <see cref="IsStrong"/> is true.
|
||||
/// </summary>
|
||||
/// <param name="startTime">The start time of the nested hit.</param>
|
||||
protected abstract StrongNestedHitObject CreateStrongNestedHit(double startTime);
|
||||
}
|
||||
}
|
||||
@@ -102,13 +102,13 @@ namespace osu.Game.Rulesets.Taiko.Replays
|
||||
|
||||
if (hit.Type == HitType.Centre)
|
||||
{
|
||||
actions = h.IsStrong
|
||||
actions = hit.IsStrong
|
||||
? new[] { TaikoAction.LeftCentre, TaikoAction.RightCentre }
|
||||
: new[] { hitButton ? TaikoAction.LeftCentre : TaikoAction.RightCentre };
|
||||
}
|
||||
else
|
||||
{
|
||||
actions = h.IsStrong
|
||||
actions = hit.IsStrong
|
||||
? new[] { TaikoAction.LeftRim, TaikoAction.RightRim }
|
||||
: new[] { hitButton ? TaikoAction.LeftRim : TaikoAction.RightRim };
|
||||
}
|
||||
|
||||
+12
-295
@@ -1,7 +1,9 @@
|
||||
{
|
||||
"Mappings": [{
|
||||
"Mappings": [
|
||||
{
|
||||
"StartTime": 2000,
|
||||
"Objects": [{
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 2000,
|
||||
"EndTime": 2000,
|
||||
"IsRim": false,
|
||||
@@ -23,7 +25,8 @@
|
||||
},
|
||||
{
|
||||
"StartTime": 4000,
|
||||
"Objects": [{
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 4000,
|
||||
"EndTime": 4000,
|
||||
"IsRim": false,
|
||||
@@ -45,7 +48,8 @@
|
||||
},
|
||||
{
|
||||
"StartTime": 6000,
|
||||
"Objects": [{
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 6000,
|
||||
"EndTime": 6000,
|
||||
"IsRim": true,
|
||||
@@ -76,300 +80,13 @@
|
||||
},
|
||||
{
|
||||
"StartTime": 8000,
|
||||
"Objects": [{
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 8000,
|
||||
"EndTime": 8000,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8026,
|
||||
"EndTime": 8026,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8053,
|
||||
"EndTime": 8053,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8080,
|
||||
"EndTime": 8080,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8107,
|
||||
"EndTime": 8107,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8133,
|
||||
"EndTime": 8133,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8160,
|
||||
"EndTime": 8160,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8187,
|
||||
"EndTime": 8187,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8214,
|
||||
"EndTime": 8214,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8241,
|
||||
"EndTime": 8241,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8267,
|
||||
"EndTime": 8267,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8294,
|
||||
"EndTime": 8294,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8321,
|
||||
"EndTime": 8321,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8348,
|
||||
"EndTime": 8348,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8374,
|
||||
"EndTime": 8374,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8401,
|
||||
"EndTime": 8401,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8428,
|
||||
"EndTime": 8428,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8455,
|
||||
"EndTime": 8455,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8482,
|
||||
"EndTime": 8482,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8508,
|
||||
"EndTime": 8508,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8535,
|
||||
"EndTime": 8535,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8562,
|
||||
"EndTime": 8562,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8589,
|
||||
"EndTime": 8589,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8615,
|
||||
"EndTime": 8615,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8642,
|
||||
"EndTime": 8642,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8669,
|
||||
"EndTime": 8669,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8696,
|
||||
"EndTime": 8696,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8723,
|
||||
"EndTime": 8723,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8749,
|
||||
"EndTime": 8749,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8776,
|
||||
"EndTime": 8776,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8803,
|
||||
"EndTime": 8803,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8830,
|
||||
"EndTime": 8830,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
},
|
||||
{
|
||||
"StartTime": 8857,
|
||||
"EndTime": 8857,
|
||||
"IsRim": false,
|
||||
"IsCentre": true,
|
||||
"IsDrumRoll": false,
|
||||
"IsCentre": false,
|
||||
"IsDrumRoll": true,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
}
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"Mappings": [
|
||||
{
|
||||
"StartTime": 51532,
|
||||
"Objects": [
|
||||
{
|
||||
"StartTime": 51532,
|
||||
"EndTime": 52301,
|
||||
"IsRim": false,
|
||||
"IsCentre": false,
|
||||
"IsDrumRoll": true,
|
||||
"IsSwell": false,
|
||||
"IsStrong": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
Mode: 0
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:2
|
||||
CircleSize:3.2
|
||||
OverallDifficulty:2
|
||||
ApproachRate:3
|
||||
SliderMultiplier:0.999999999999999
|
||||
SliderTickRate:1
|
||||
|
||||
[TimingPoints]
|
||||
763,384.615384615385,4,2,0,70,1,0
|
||||
49993,-90.9090909090909,4,2,0,75,0,1
|
||||
|
||||
[HitObjects]
|
||||
51,245,51532,2,0,P|18:150|17:122,2,110.000003356934,0|8|0,0:0|0:0|0:0,0:0:0:0:
|
||||
@@ -7,7 +7,7 @@ using osu.Framework.Graphics.Animations;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
@@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
const string normal_hit = "taikohit";
|
||||
const string big_hit = "taikobig";
|
||||
|
||||
string prefix = ((drawableHitObject as DrawableTaikoHitObject)?.HitObject.IsStrong ?? false) ? big_hit : normal_hit;
|
||||
string prefix = ((drawableHitObject.HitObject as TaikoStrongableHitObject)?.IsStrong ?? false) ? big_hit : normal_hit;
|
||||
|
||||
return skin.GetAnimation($"{prefix}{lookup}", true, false) ??
|
||||
// fallback to regular size if "big" version doesn't exist.
|
||||
|
||||
@@ -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.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.UI
|
||||
{
|
||||
public class BarLinePlayfield : ScrollingPlayfield
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
RegisterPool<BarLine, DrawableBarLine>(15);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.Taiko.Replays;
|
||||
using osu.Framework.Input;
|
||||
@@ -17,6 +16,7 @@ using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
new BarLineGenerator<BarLine>(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar)));
|
||||
new BarLineGenerator<BarLine>(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar));
|
||||
|
||||
FrameStableComponents.Add(scroller = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Scroller), _ => Empty())
|
||||
{
|
||||
@@ -63,25 +63,10 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
|
||||
protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo);
|
||||
|
||||
public override DrawableHitObject<TaikoHitObject> CreateDrawableRepresentation(TaikoHitObject h)
|
||||
{
|
||||
switch (h)
|
||||
{
|
||||
case Hit hit:
|
||||
return new DrawableHit(hit);
|
||||
|
||||
case DrumRoll drumRoll:
|
||||
return new DrawableDrumRoll(drumRoll);
|
||||
|
||||
case Swell swell:
|
||||
return new DrawableSwell(swell);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
public override DrawableHitObject<TaikoHitObject> CreateDrawableRepresentation(TaikoHitObject h) => null;
|
||||
|
||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new TaikoFramedReplayInputHandler(replay);
|
||||
|
||||
protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new TaikoReplayRecorder(replay);
|
||||
protected override ReplayRecorder CreateReplayRecorder(Score score) => new TaikoReplayRecorder(score);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
/// </summary>
|
||||
public void VisualiseSecondHit()
|
||||
{
|
||||
this.ResizeTo(new Vector2(TaikoHitObject.DEFAULT_STRONG_SIZE), 50);
|
||||
this.ResizeTo(new Vector2(TaikoStrongableHitObject.DEFAULT_STRONG_SIZE), 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Size = new Vector2(border_thickness, (1 - TaikoHitObject.DEFAULT_STRONG_SIZE) / 2f),
|
||||
Size = new Vector2(border_thickness, (1 - TaikoStrongableHitObject.DEFAULT_STRONG_SIZE) / 2f),
|
||||
Alpha = 0.1f
|
||||
},
|
||||
new CircularContainer
|
||||
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(TaikoHitObject.DEFAULT_STRONG_SIZE),
|
||||
Size = new Vector2(TaikoStrongableHitObject.DEFAULT_STRONG_SIZE),
|
||||
Masking = true,
|
||||
BorderColour = Color4.White,
|
||||
BorderThickness = border_thickness,
|
||||
@@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Size = new Vector2(border_thickness, (1 - TaikoHitObject.DEFAULT_STRONG_SIZE) / 2f),
|
||||
Size = new Vector2(border_thickness, (1 - TaikoStrongableHitObject.DEFAULT_STRONG_SIZE) / 2f),
|
||||
Alpha = 0.1f
|
||||
},
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
@@ -38,10 +39,15 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
private SkinnableDrawable mascot;
|
||||
|
||||
private ProxyContainer topLevelHitContainer;
|
||||
private ScrollingHitObjectContainer barlineContainer;
|
||||
private Container rightArea;
|
||||
private Container leftArea;
|
||||
|
||||
/// <remarks>
|
||||
/// <see cref="Playfield.AddNested"/> is purposefully not called on this to prevent i.e. being able to interact
|
||||
/// with bar lines in the editor.
|
||||
/// </remarks>
|
||||
private BarLinePlayfield barLinePlayfield;
|
||||
|
||||
private Container hitTargetOffsetContent;
|
||||
|
||||
public TaikoPlayfield(ControlPointInfo controlPoints)
|
||||
@@ -84,7 +90,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
barlineContainer = new ScrollingHitObjectContainer(),
|
||||
barLinePlayfield = new BarLinePlayfield(),
|
||||
new Container
|
||||
{
|
||||
Name = "Hit objects",
|
||||
@@ -141,6 +147,32 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
},
|
||||
drumRollHitContainer.CreateProxy(),
|
||||
};
|
||||
|
||||
RegisterPool<Hit, DrawableHit>(50);
|
||||
RegisterPool<Hit.StrongNestedHit, DrawableHit.StrongNestedHit>(50);
|
||||
|
||||
RegisterPool<DrumRoll, DrawableDrumRoll>(5);
|
||||
RegisterPool<DrumRoll.StrongNestedHit, DrawableDrumRoll.StrongNestedHit>(5);
|
||||
|
||||
RegisterPool<DrumRollTick, DrawableDrumRollTick>(100);
|
||||
RegisterPool<DrumRollTick.StrongNestedHit, DrawableDrumRollTick.StrongNestedHit>(100);
|
||||
|
||||
RegisterPool<Swell, DrawableSwell>(5);
|
||||
RegisterPool<SwellTick, DrawableSwellTick>(100);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
NewResult += OnNewResult;
|
||||
}
|
||||
|
||||
protected override void OnNewDrawableHitObject(DrawableHitObject drawableHitObject)
|
||||
{
|
||||
base.OnNewDrawableHitObject(drawableHitObject);
|
||||
|
||||
var taikoObject = (DrawableTaikoHitObject)drawableHitObject;
|
||||
topLevelHitContainer.Add(taikoObject.CreateProxiedContent());
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
@@ -155,22 +187,58 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
mascot.Scale = new Vector2(DrawHeight / DEFAULT_HEIGHT);
|
||||
}
|
||||
|
||||
#region Pooling support
|
||||
|
||||
public override void Add(HitObject h)
|
||||
{
|
||||
switch (h)
|
||||
{
|
||||
case BarLine barLine:
|
||||
barLinePlayfield.Add(barLine);
|
||||
break;
|
||||
|
||||
case TaikoHitObject taikoHitObject:
|
||||
base.Add(taikoHitObject);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported {nameof(HitObject)} type: {h.GetType()}");
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Remove(HitObject h)
|
||||
{
|
||||
switch (h)
|
||||
{
|
||||
case BarLine barLine:
|
||||
return barLinePlayfield.Remove(barLine);
|
||||
|
||||
case TaikoHitObject taikoHitObject:
|
||||
return base.Remove(taikoHitObject);
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported {nameof(HitObject)} type: {h.GetType()}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Non-pooling support
|
||||
|
||||
public override void Add(DrawableHitObject h)
|
||||
{
|
||||
switch (h)
|
||||
{
|
||||
case DrawableBarLine barline:
|
||||
barlineContainer.Add(barline);
|
||||
case DrawableBarLine barLine:
|
||||
barLinePlayfield.Add(barLine);
|
||||
break;
|
||||
|
||||
case DrawableTaikoHitObject taikoObject:
|
||||
h.OnNewResult += OnNewResult;
|
||||
topLevelHitContainer.Add(taikoObject.CreateProxiedContent());
|
||||
case DrawableTaikoHitObject _:
|
||||
base.Add(h);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported {nameof(DrawableHitObject)} type");
|
||||
throw new ArgumentException($"Unsupported {nameof(DrawableHitObject)} type: {h.GetType()}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,19 +246,19 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
{
|
||||
switch (h)
|
||||
{
|
||||
case DrawableBarLine barline:
|
||||
return barlineContainer.Remove(barline);
|
||||
case DrawableBarLine barLine:
|
||||
return barLinePlayfield.Remove(barLine);
|
||||
|
||||
case DrawableTaikoHitObject _:
|
||||
h.OnNewResult -= OnNewResult;
|
||||
// todo: consider tidying of proxied content if required.
|
||||
return base.Remove(h);
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported {nameof(DrawableHitObject)} type");
|
||||
throw new ArgumentException($"Unsupported {nameof(DrawableHitObject)} type: {h.GetType()}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||
{
|
||||
if (!DisplayJudgements.Value)
|
||||
@@ -202,7 +270,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
{
|
||||
case TaikoStrongJudgement _:
|
||||
if (result.IsHit)
|
||||
hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == ((DrawableStrongNestedHit)judgedObject).MainObject)?.VisualiseSecondHit();
|
||||
hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == ((DrawableStrongNestedHit)judgedObject).ParentHitObject)?.VisualiseSecondHit();
|
||||
break;
|
||||
|
||||
case TaikoDrumRollTickJudgement _:
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.Taiko.Replays;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.UI
|
||||
{
|
||||
public class TaikoReplayRecorder : ReplayRecorder<TaikoAction>
|
||||
{
|
||||
public TaikoReplayRecorder(Replay replay)
|
||||
: base(replay)
|
||||
public TaikoReplayRecorder(Score score)
|
||||
: base(score)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Description>bash the drum. to the beat.</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Nuget">
|
||||
<Title>osu!taiko (ruleset)</Title>
|
||||
<PackageId>ppy.osu.Game.Rulesets.Taiko</PackageId>
|
||||
<IsPackable>true</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Label="Project References">
|
||||
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -95,6 +95,26 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOutOfOrderStartTimes()
|
||||
{
|
||||
var decoder = new LegacyStoryboardDecoder();
|
||||
|
||||
using (var resStream = TestResources.OpenResource("out-of-order-starttimes.osb"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
var storyboard = decoder.Decode(stream);
|
||||
|
||||
StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3);
|
||||
Assert.AreEqual(2, background.Elements.Count);
|
||||
|
||||
Assert.AreEqual(1500, background.Elements[0].StartTime);
|
||||
Assert.AreEqual(1000, background.Elements[1].StartTime);
|
||||
|
||||
Assert.AreEqual(1000, storyboard.EarliestEventTime);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodeVariableWithSuffix()
|
||||
{
|
||||
|
||||
@@ -10,9 +10,11 @@ using NUnit.Framework;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Graphics.Audio;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
@@ -27,7 +29,7 @@ using osu.Game.Tests.Visual;
|
||||
namespace osu.Game.Tests.Gameplay
|
||||
{
|
||||
[HeadlessTest]
|
||||
public class TestSceneStoryboardSamples : OsuTestScene
|
||||
public class TestSceneStoryboardSamples : OsuTestScene, IStorageResourceProvider
|
||||
{
|
||||
[Test]
|
||||
public void TestRetrieveTopLevelSample()
|
||||
@@ -35,7 +37,7 @@ namespace osu.Game.Tests.Gameplay
|
||||
ISkin skin = null;
|
||||
SampleChannel channel = null;
|
||||
|
||||
AddStep("create skin", () => skin = new TestSkin("test-sample", Audio));
|
||||
AddStep("create skin", () => skin = new TestSkin("test-sample", this));
|
||||
AddStep("retrieve sample", () => channel = skin.GetSample(new SampleInfo("test-sample")));
|
||||
|
||||
AddAssert("sample is non-null", () => channel != null);
|
||||
@@ -47,7 +49,7 @@ namespace osu.Game.Tests.Gameplay
|
||||
ISkin skin = null;
|
||||
SampleChannel channel = null;
|
||||
|
||||
AddStep("create skin", () => skin = new TestSkin("folder/test-sample", Audio));
|
||||
AddStep("create skin", () => skin = new TestSkin("folder/test-sample", this));
|
||||
AddStep("retrieve sample", () => channel = skin.GetSample(new SampleInfo("folder/test-sample")));
|
||||
|
||||
AddAssert("sample is non-null", () => channel != null);
|
||||
@@ -105,7 +107,7 @@ namespace osu.Game.Tests.Gameplay
|
||||
|
||||
AddStep("setup storyboard sample", () =>
|
||||
{
|
||||
Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, Audio);
|
||||
Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, this);
|
||||
SelectedMods.Value = new[] { testedMod };
|
||||
|
||||
var beatmapSkinSourceContainer = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
|
||||
@@ -128,8 +130,8 @@ namespace osu.Game.Tests.Gameplay
|
||||
|
||||
private class TestSkin : LegacySkin
|
||||
{
|
||||
public TestSkin(string resourceName, AudioManager audioManager)
|
||||
: base(DefaultLegacySkin.Info, new TestResourceStore(resourceName), audioManager, "skin.ini")
|
||||
public TestSkin(string resourceName, IStorageResourceProvider resources)
|
||||
: base(DefaultLegacySkin.Info, new TestResourceStore(resourceName), resources, "skin.ini")
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -158,15 +160,15 @@ namespace osu.Game.Tests.Gameplay
|
||||
|
||||
private class TestCustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap
|
||||
{
|
||||
private readonly AudioManager audio;
|
||||
private readonly IStorageResourceProvider resources;
|
||||
|
||||
public TestCustomSkinWorkingBeatmap(RulesetInfo ruleset, AudioManager audio)
|
||||
: base(ruleset, null, audio)
|
||||
public TestCustomSkinWorkingBeatmap(RulesetInfo ruleset, IStorageResourceProvider resources)
|
||||
: base(ruleset, null, resources.AudioManager)
|
||||
{
|
||||
this.audio = audio;
|
||||
this.resources = resources;
|
||||
}
|
||||
|
||||
protected override ISkin GetSkin() => new TestSkin("test-sample", audio);
|
||||
protected override ISkin GetSkin() => new TestSkin("test-sample", resources);
|
||||
}
|
||||
|
||||
private class TestDrawableStoryboardSample : DrawableStoryboardSample
|
||||
@@ -176,5 +178,13 @@ namespace osu.Game.Tests.Gameplay
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
#region IResourceStorageProvider
|
||||
|
||||
public AudioManager AudioManager => Audio;
|
||||
public IResourceStore<byte[]> Files => null;
|
||||
public IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => null;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,5 +246,32 @@ namespace osu.Game.Tests.NonVisual
|
||||
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0));
|
||||
Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCreateCopyIsDeepClone()
|
||||
{
|
||||
var cpi = new ControlPointInfo();
|
||||
|
||||
cpi.Add(1000, new TimingControlPoint { BeatLength = 500 });
|
||||
|
||||
var cpiCopy = cpi.CreateCopy();
|
||||
|
||||
cpiCopy.Add(2000, new TimingControlPoint { BeatLength = 500 });
|
||||
|
||||
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
||||
Assert.That(cpiCopy.Groups.Count, Is.EqualTo(2));
|
||||
|
||||
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(1));
|
||||
Assert.That(cpiCopy.TimingPoints.Count, Is.EqualTo(2));
|
||||
|
||||
Assert.That(cpi.TimingPoints[0], Is.Not.SameAs(cpiCopy.TimingPoints[0]));
|
||||
Assert.That(cpi.TimingPoints[0].BeatLengthBindable, Is.Not.SameAs(cpiCopy.TimingPoints[0].BeatLengthBindable));
|
||||
|
||||
Assert.That(cpi.TimingPoints[0].BeatLength, Is.EqualTo(cpiCopy.TimingPoints[0].BeatLength));
|
||||
|
||||
cpi.TimingPoints[0].BeatLength = 800;
|
||||
|
||||
Assert.That(cpi.TimingPoints[0].BeatLength, Is.Not.EqualTo(cpiCopy.TimingPoints[0].BeatLength));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using Humanizer;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Tests.Visual.Multiplayer;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Tests.NonVisual.Multiplayer
|
||||
{
|
||||
[HeadlessTest]
|
||||
public class StatefulMultiplayerClientTest : MultiplayerTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestPlayingUserTracking()
|
||||
{
|
||||
int id = 2000;
|
||||
|
||||
AddRepeatStep("add some users", () => Client.AddUser(new User { Id = id++ }), 5);
|
||||
checkPlayingUserCount(0);
|
||||
|
||||
changeState(3, MultiplayerUserState.WaitingForLoad);
|
||||
checkPlayingUserCount(3);
|
||||
|
||||
changeState(3, MultiplayerUserState.Playing);
|
||||
checkPlayingUserCount(3);
|
||||
|
||||
changeState(3, MultiplayerUserState.Results);
|
||||
checkPlayingUserCount(0);
|
||||
|
||||
changeState(6, MultiplayerUserState.WaitingForLoad);
|
||||
checkPlayingUserCount(6);
|
||||
|
||||
AddStep("another user left", () => Client.RemoveUser(Client.Room?.Users.Last().User));
|
||||
checkPlayingUserCount(5);
|
||||
|
||||
AddStep("leave room", () => Client.LeaveRoom());
|
||||
checkPlayingUserCount(0);
|
||||
}
|
||||
|
||||
private void checkPlayingUserCount(int expectedCount)
|
||||
=> AddAssert($"{"user".ToQuantity(expectedCount)} playing", () => Client.CurrentMatchPlayingUserIds.Count == expectedCount);
|
||||
|
||||
private void changeState(int userCount, MultiplayerUserState state)
|
||||
=> AddStep($"{"user".ToQuantity(userCount)} in {state}", () =>
|
||||
{
|
||||
for (int i = 0; i < userCount; ++i)
|
||||
{
|
||||
var userId = Client.Room?.Users[i].UserID ?? throw new AssertionException("Room cannot be null!");
|
||||
Client.ChangeUserState(userId, state);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
[Events]
|
||||
//Storyboard Layer 0 (Background)
|
||||
Sprite,Background,TopCentre,"img.jpg",320,240
|
||||
F,0,1500,1600,0,1
|
||||
Sprite,Background,TopCentre,"img.jpg",320,240
|
||||
F,0,1000,1100,0,1
|
||||
@@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Background
|
||||
});
|
||||
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
|
||||
AddStep("Stop background preview", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
|
||||
AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
|
||||
AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.CheckBackgroundBlur(playerLoader.ExpectedBackgroundBlur));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -106,6 +106,7 @@ namespace osu.Game.Tests.Visual.Background
|
||||
public void TestStoryboardBackgroundVisibility()
|
||||
{
|
||||
performFullSetup();
|
||||
AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent());
|
||||
createFakeStoryboard();
|
||||
AddStep("Enable Storyboard", () =>
|
||||
{
|
||||
@@ -198,8 +199,9 @@ namespace osu.Game.Tests.Visual.Background
|
||||
})));
|
||||
|
||||
AddUntilStep("Wait for results is current", () => results.IsCurrentScreen());
|
||||
|
||||
AddUntilStep("Screen is undimmed, original background retained", () =>
|
||||
songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect());
|
||||
songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && songSelect.CheckBackgroundBlur(results.ExpectedBackgroundBlur));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -224,7 +226,7 @@ namespace osu.Game.Tests.Visual.Background
|
||||
AddStep("Resume PlayerLoader", () => player.Restart());
|
||||
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
|
||||
AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
|
||||
AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
|
||||
AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.CheckBackgroundBlur(playerLoader.ExpectedBackgroundBlur));
|
||||
}
|
||||
|
||||
private void createFakeStoryboard() => AddStep("Create storyboard", () =>
|
||||
@@ -274,9 +276,11 @@ namespace osu.Game.Tests.Visual.Background
|
||||
|
||||
private class DummySongSelect : PlaySongSelect
|
||||
{
|
||||
private FadeAccessibleBackground background;
|
||||
|
||||
protected override BackgroundScreen CreateBackground()
|
||||
{
|
||||
FadeAccessibleBackground background = new FadeAccessibleBackground(Beatmap.Value);
|
||||
background = new FadeAccessibleBackground(Beatmap.Value);
|
||||
DimEnabled.BindTo(background.EnableUserDim);
|
||||
return background;
|
||||
}
|
||||
@@ -294,25 +298,27 @@ namespace osu.Game.Tests.Visual.Background
|
||||
config.BindWith(OsuSetting.BlurLevel, BlurLevel);
|
||||
}
|
||||
|
||||
public bool IsBackgroundDimmed() => ((FadeAccessibleBackground)Background).CurrentColour == OsuColour.Gray(1f - ((FadeAccessibleBackground)Background).CurrentDim);
|
||||
public bool IsBackgroundDimmed() => background.CurrentColour == OsuColour.Gray(1f - background.CurrentDim);
|
||||
|
||||
public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == Color4.White;
|
||||
public bool IsBackgroundUndimmed() => background.CurrentColour == Color4.White;
|
||||
|
||||
public bool IsUserBlurApplied() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR);
|
||||
public bool IsUserBlurApplied() => background.CurrentBlur == new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR);
|
||||
|
||||
public bool IsUserBlurDisabled() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(0);
|
||||
public bool IsUserBlurDisabled() => background.CurrentBlur == new Vector2(0);
|
||||
|
||||
public bool IsBackgroundInvisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 0;
|
||||
public bool IsBackgroundInvisible() => background.CurrentAlpha == 0;
|
||||
|
||||
public bool IsBackgroundVisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 1;
|
||||
public bool IsBackgroundVisible() => background.CurrentAlpha == 1;
|
||||
|
||||
public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
|
||||
public bool IsBlurCorrect() => background.CurrentBlur == new Vector2(BACKGROUND_BLUR);
|
||||
|
||||
public bool CheckBackgroundBlur(Vector2 expected) => background.CurrentBlur == expected;
|
||||
|
||||
/// <summary>
|
||||
/// Make sure every time a screen gets pushed, the background doesn't get replaced
|
||||
/// </summary>
|
||||
/// <returns>Whether or not the original background (The one created in DummySongSelect) is still the current background</returns>
|
||||
public bool IsBackgroundCurrent() => ((FadeAccessibleBackground)Background).IsCurrentScreen();
|
||||
public bool IsBackgroundCurrent() => background?.IsCurrentScreen() == true;
|
||||
}
|
||||
|
||||
private class FadeAccessibleResults : ResultsScreen
|
||||
@@ -324,12 +330,20 @@ namespace osu.Game.Tests.Visual.Background
|
||||
|
||||
protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
|
||||
|
||||
public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
|
||||
public Vector2 ExpectedBackgroundBlur => new Vector2(BACKGROUND_BLUR);
|
||||
}
|
||||
|
||||
private class LoadBlockingTestPlayer : TestPlayer
|
||||
{
|
||||
protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
|
||||
protected override BackgroundScreen CreateBackground() =>
|
||||
new FadeAccessibleBackground(Beatmap.Value);
|
||||
|
||||
public override void OnEntering(IScreen last)
|
||||
{
|
||||
base.OnEntering(last);
|
||||
|
||||
ApplyToBackground(b => ReplacesBackground.BindTo(b.StoryboardReplacesBackground));
|
||||
}
|
||||
|
||||
public new DimmableStoryboard DimmableStoryboard => base.DimmableStoryboard;
|
||||
|
||||
@@ -354,15 +368,16 @@ namespace osu.Game.Tests.Visual.Background
|
||||
Thread.Sleep(1);
|
||||
|
||||
StoryboardEnabled = config.GetBindable<bool>(OsuSetting.ShowStoryboard);
|
||||
ReplacesBackground.BindTo(Background.StoryboardReplacesBackground);
|
||||
DrawableRuleset.IsPaused.BindTo(IsPaused);
|
||||
}
|
||||
}
|
||||
|
||||
private class TestPlayerLoader : PlayerLoader
|
||||
{
|
||||
private FadeAccessibleBackground background;
|
||||
|
||||
public VisualSettings VisualSettingsPos => VisualSettings;
|
||||
public BackgroundScreen ScreenPos => Background;
|
||||
public BackgroundScreen ScreenPos => background;
|
||||
|
||||
public TestPlayerLoader(Player player)
|
||||
: base(() => player)
|
||||
@@ -371,9 +386,9 @@ namespace osu.Game.Tests.Visual.Background
|
||||
|
||||
public void TriggerOnHover() => OnHover(new HoverEvent(new InputState()));
|
||||
|
||||
public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
|
||||
public Vector2 ExpectedBackgroundBlur => new Vector2(BACKGROUND_BLUR);
|
||||
|
||||
protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
|
||||
protected override BackgroundScreen CreateBackground() => background = new FadeAccessibleBackground(Beatmap.Value);
|
||||
}
|
||||
|
||||
private class FadeAccessibleBackground : BackgroundScreenBeatmap
|
||||
|
||||
@@ -61,12 +61,12 @@ namespace osu.Game.Tests.Visual.Components
|
||||
{
|
||||
createPoller(true);
|
||||
|
||||
AddStep("set poll interval to 1", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust);
|
||||
AddStep("set poll interval to 1", () => poller.TimeBetweenPolls.Value = TimePerAction * safety_adjust);
|
||||
checkCount(1);
|
||||
checkCount(2);
|
||||
checkCount(3);
|
||||
|
||||
AddStep("set poll interval to 5", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust * 5);
|
||||
AddStep("set poll interval to 5", () => poller.TimeBetweenPolls.Value = TimePerAction * safety_adjust * 5);
|
||||
checkCount(4);
|
||||
checkCount(4);
|
||||
checkCount(4);
|
||||
@@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Components
|
||||
checkCount(5);
|
||||
checkCount(5);
|
||||
|
||||
AddStep("set poll interval to 1", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust);
|
||||
AddStep("set poll interval to 1", () => poller.TimeBetweenPolls.Value = TimePerAction * safety_adjust);
|
||||
checkCount(6);
|
||||
checkCount(7);
|
||||
}
|
||||
@@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Components
|
||||
{
|
||||
createPoller(false);
|
||||
|
||||
AddStep("set poll interval to 1", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust * 5);
|
||||
AddStep("set poll interval to 1", () => poller.TimeBetweenPolls.Value = TimePerAction * safety_adjust * 5);
|
||||
checkCount(0);
|
||||
skip();
|
||||
checkCount(0);
|
||||
@@ -141,7 +141,7 @@ namespace osu.Game.Tests.Visual.Components
|
||||
|
||||
public class TestSlowPoller : TestPoller
|
||||
{
|
||||
protected override Task Poll() => Task.Delay((int)(TimeBetweenPolls / 2f / Clock.Rate)).ContinueWith(_ => base.Poll());
|
||||
protected override Task Poll() => Task.Delay((int)(TimeBetweenPolls.Value / 2f / Clock.Rate)).ContinueWith(_ => base.Poll());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user